core: rewrite entire app in hyprtoolkit (#25)

* core: rewrite entire app in hyprtoolkit

* Nix: update deps

* CMake: fix .desktop path, dedup deps

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
Vaxry 2025-12-30 14:20:58 +01:00 committed by GitHub
parent fe81610278
commit 1959f049f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1072 additions and 775 deletions

View file

@ -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)

View file

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 103 KiB

158
flake.lock generated
View file

@ -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"
}

View file

@ -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";
};
};

View file

@ -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;
};
}

View file

@ -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;
};
};
}

View file

@ -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
)

View file

@ -1,25 +0,0 @@
#include "SystemIconProvider.hpp"
#include <qicon.h>
#include <qlogging.h>
#include <qpixmap.h>
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;
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <QIcon>
#include <QObject>
#include <QPixmap>
#include <QQmlApplicationEngine>
#include <QQuickImageProvider>
class CSystemIconProvider : public QQuickImageProvider {
public:
CSystemIconProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {}
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
};

View file

@ -1,226 +0,0 @@
#include "SystemInfo.hpp"
#include "WaylandScreen.hpp"
#include "util/Utils.hpp"
#include <qclipboard.h>
#include <array>
#include <qcontainerfwd.h>
#include <qfiledevice.h>
#include <qimage.h>
#include <qobject.h>
#include <qscreen.h>
#include <qfile.h>
#include <qstringliteral.h>
#include <string>
#include <format>
#include <qguiapplication.h>
#include <qtenvironmentvariables.h>
#include <qwindowdefs.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/String.hpp>
#include <cstdlib>
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<char, 128> 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."});
}

View file

@ -1,46 +0,0 @@
#pragma once
#include <QObject>
#include <QQmlApplicationEngine>
#include <QPixmap>
#include <QIcon>
#include <qcontainerfwd.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
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<QString> 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<QString> 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();
};

View file

@ -1,60 +0,0 @@
#include "WaylandScreen.hpp"
#include <QtWaylandClient/private/qwayland-wayland.h>
#include <QtWaylandClient/private/qwaylanddisplay_p.h>
#include <QtWaylandClient/private/qwaylandintegration_p.h>
#include <cstdint>
#include <qcontainerfwd.h>
#include <qsize.h>
#include <qstringliteral.h>
#include <qtclasshelpermacros.h>
#include <vector>
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> SWaylandScreenInfo::enumerateScreens() {
auto* display = QWaylandIntegration::instance()->display();
std::vector<CWaylandScreen*> 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<SWaylandScreenInfo> 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;
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <qcontainerfwd.h>
#include <qsize.h>
#include <qstring.h>
#include <qcontainerfwd.h>
#include <vector>
struct SWaylandScreenInfo {
QString name;
QSize pixelSize;
static std::vector<SWaylandScreenInfo> enumerateScreens();
};

12
src/helpers/Logger.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <hyprutils/cli/Logger.hpp>
#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<Hyprutils::CLI::CLogger> g_logger = makeUnique<Hyprutils::CLI::CLogger>();

15
src/helpers/Memory.hpp Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <bit>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer

9
src/icons/Icons.hpp Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <cstdint>
namespace Icons {
inline constexpr uint8_t HYPRLAND_LOGO[] = {
#embed "../../resource/hyprlandlogo.svg"
};
};

View file

@ -1,21 +1,278 @@
#include "SystemIconProvider.hpp"
#include <qapplication.h>
#include <qqmlapplicationengine.h>
#include <qquickstyle.h>
#include <qtenvironmentvariables.h>
#include <hyprtoolkit/core/Backend.hpp>
#include <hyprtoolkit/window/Window.hpp>
#include <hyprtoolkit/element/Rectangle.hpp>
#include <hyprtoolkit/element/RowLayout.hpp>
#include <hyprtoolkit/element/ColumnLayout.hpp>
#include <hyprtoolkit/element/Text.hpp>
#include <hyprtoolkit/element/Image.hpp>
#include <hyprtoolkit/element/Button.hpp>
#include <hyprtoolkit/element/Null.hpp>
#include <hyprtoolkit/system/Icons.hpp>
#include <hyprtoolkit/core/Output.hpp>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/os/Process.hpp>
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 <cstring>
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<IBackend> backend;
//
static SP<IElement> space(SP<IElement> 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("<a href=\"{}\">{}</a>", 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<uint8_t> 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("<a href=\"{}\">{}</a>", 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<CButtonElement> 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<IWindow>{window}](SP<CButtonElement> e) {
w->close();
backend->destroy();
})
->commence() //
);
//
bottomNull->addChild(bottomLayout);
window->m_rootElement->addChild(bottomNull);
window->m_events.closeRequest.listenStatic([w = WP<IWindow>{window}] {
w->close();
backend->destroy();
});
window->open();
backend->enterLoop();
return 0;
}

View file

@ -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();
}
}
}
}

View file

@ -1,46 +0,0 @@
#include "Utils.hpp"
#include <fstream>
#include <qdebug.h>
#include <qlogging.h>
#include <qprocess.h>
#include <optional>
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<std::string> readFile(const std::string& filename) {
try {
std::ifstream ifs(filename);
if (ifs.good()) {
std::string data(std::istreambuf_iterator<char>{ifs}, {});
ifs.close();
return data;
}
} catch (...) {}
return {};
}

View file

@ -1,8 +0,0 @@
#pragma once
#include <qlist.h>
#include <qstring.h>
#include <optional>
QString execAndGet(const QString& program, const QStringList& arguments = {}, bool* ok = nullptr);
std::optional<std::string> readFile(const std::string& filename);

86
src/utils/HyprlandIPC.cpp Normal file
View file

@ -0,0 +1,86 @@
#include "HyprlandIPC.hpp"
#include <pwd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <format>
#include <hyprutils/memory/Casts.hpp>
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<std::string, std::string> 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<sockaddr*>(&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;
}

View file

@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <expected>
namespace HyprlandIPC {
std::expected<std::string, std::string> getFromSocket(const std::string& cmd);
};

358
src/utils/SystemInfo.cpp Normal file
View file

@ -0,0 +1,358 @@
#include "SystemInfo.hpp"
#include <optional>
#include <format>
#include <algorithm>
#include <hyprutils/os/File.hpp>
#include <hyprutils/string/VarList2.hpp>
#include <hyprutils/string/String.hpp>
#include "HyprlandIPC.hpp"
#include "../helpers/Logger.hpp"
#include "../icons/Icons.hpp"
#include <sys/utsname.h>
#include <glaze/glaze.hpp>
#include <pwd.h>
#include <sys/types.h>
#include <sys/sysinfo.h>
#include <linux/sysinfo.h>
#include <unistd.h>
#include <glob.h>
#include <pci/pci.h>
using namespace Hyprutils::File;
using namespace Hyprutils::String;
std::optional<std::string> 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<std::string, std::string> 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<glz::generic>(*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<std::string> 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<std::string> 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<uint32_t> 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<uint32_t>(std::stoul(s, nullptr, 16));
} catch (...) { return std::nullopt; }
}
std::vector<std::string> 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<std::string> 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<float>(1024 * 1024 * 1024 * 1024ULL));
if (bytes > 1024 * 1024 * 1024)
return std::format("{:.1f}GiB", bytes / sc<float>(1024 * 1024 * 1024));
if (bytes > 1024 * 1024)
return std::format("{:.1f}MiB", bytes / sc<float>(1024 * 1024));
if (bytes > 1024)
return std::format("{:.1f}KiB", bytes / sc<float>(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<float>((TOTAL - FREE) / sc<float>(TOTAL)) * 100.F);
}
std::string Info::uptime() {
struct sysinfo info{};
if (::sysinfo(&info) != 0)
return "";
uint64_t s = sc<uint64_t>(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<std::span<const uint8_t>> Info::deLogo() {
const auto DE = desktop();
if (DE == "Hyprland")
return std::span<const uint8_t>{Icons::HYPRLAND_LOGO, sizeof(Icons::HYPRLAND_LOGO)};
return std::nullopt;
}

24
src/utils/SystemInfo.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <expected>
#include <string>
#include <optional>
#include <filesystem>
#include <vector>
namespace Info {
std::expected<std::string, std::string> getDistroLogoName();
std::optional<std::string> getFromEtcOsRelease(const std::string_view& sv);
std::string kernel();
std::string desktop();
std::string desktopHome();
std::string desktopVersion();
std::string user();
std::optional<std::string> cat(const std::filesystem::path& p);
std::string model();
std::string cpu();
std::vector<std::string> gpus();
std::string mem();
std::string uptime();
std::optional<std::span<const uint8_t>> deLogo();
};