diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eaeb6f..d8fd6bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,10 +6,13 @@ string(STRIP ${VER_RAW} VER) project(hsi VERSION ${VER} LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2) -set(CMAKE_CXX_STANDARD 20) +find_package(PkgConfig REQUIRED) + +pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils) qt_standard_project_setup(REQUIRES 6.5) diff --git a/flake.lock b/flake.lock index a765b11..6f527f9 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,28 @@ { "nodes": { + "hyprutils": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1728941256, + "narHash": "sha256-WRypmcZ2Bw94lLmcmxYokVOHPJSZ7T06V49QZ4tkZeQ=", + "owner": "hyprwm", + "repo": "hyprutils", + "rev": "fd4be8b9ca932f7384e454bcd923c5451ef2aa85", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprutils", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1728492678, @@ -18,6 +41,7 @@ }, "root": { "inputs": { + "hyprutils": "hyprutils", "nixpkgs": "nixpkgs", "systems": "systems" } diff --git a/flake.nix b/flake.nix index d252350..798fdfb 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,12 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; + + hyprutils = { + url = "github:hyprwm/hyprutils"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + }; }; outputs = { @@ -30,14 +36,9 @@ }); devShells = eachSystem (system: { - default = pkgsFor.${system}.mkShell { - inputsFrom = [self.packages.${system}.hyprsysteminfo]; - - shellHook = '' - # Generate compile_commands.json - CMAKE_EXPORT_COMPILE_COMMANDS=1 cmake -S . -B ./build - ln -s build/compile_commands.json . - ''; + default = import ./nix/shell.nix { + pkgs = pkgsFor.${system}; + inherit (pkgsFor.${system}) hyprsysteminfo; }; }); }; diff --git a/nix/default.nix b/nix/default.nix index 795cdeb..f59cfa3 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,6 +5,8 @@ kdePackages, pciutils, qt6, + pkg-config, + hyprutils, version ? "0", }: let inherit (lib.sources) cleanSource cleanSourceWith; @@ -24,6 +26,7 @@ in nativeBuildInputs = [ cmake + pkg-config qt6.wrapQtAppsHook ]; @@ -32,6 +35,7 @@ in qt6.qtbase qt6.qtsvg qt6.qtwayland + hyprutils ]; preFixup = '' diff --git a/nix/overlays.nix b/nix/overlays.nix index 1eda43b..61f57f9 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -14,6 +14,7 @@ in { default = self.overlays.hyprsysteminfo; hyprsysteminfo = lib.composeManyExtensions [ + inputs.hyprutils.overlays.default (final: prev: { hyprsysteminfo = final.callPackage ./. { version = "${version}+date=${date}_${self.shortRev or "dirty"}"; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2e4aa8c..064efef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,4 +11,4 @@ qt_add_qml_module(hyprsysteminfo QML_FILES main.qml ) -target_link_libraries(hyprsysteminfo PRIVATE Qt6::Widgets Qt6::QuickControls2) +target_link_libraries(hyprsysteminfo PRIVATE Qt6::Widgets Qt6::QuickControls2 PkgConfig::hyprutils) diff --git a/src/SystemInfo.cpp b/src/SystemInfo.cpp index 2d520ef..d8d645f 100644 --- a/src/SystemInfo.cpp +++ b/src/SystemInfo.cpp @@ -2,51 +2,53 @@ #include "util/Utils.hpp" #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) { // gather data from os-release - { - QFile osInfo("/etc/os-release"); - if (osInfo.open(QIODevice::ReadOnly | QIODevice::Text)) { - auto stream = QTextStream(&osInfo); - - while (!stream.atEnd()) { - auto line = stream.readLine(); - auto split = line.split('='); - if (split.length() != 2) - continue; + if (auto data = readFile("/etc/os-release")) { + CVarList lines(data.value(), 0, '\n'); - const auto& key = split.at(0); - auto value = split.at(1); + for (auto& line : lines) { + CVarList param(line, 2, '='); - if (value.length() >= 2 && value.at(0) == '\"') - value = value.sliced(1).chopped(1); + const auto& key = param[0]; + auto value = param[1]; - if (key == "PRETTY_NAME") { - systemName = value; - continue; - } + if (value.length() >= 2 && value.at(0) == '\"') + value = value.substr(1, value.length() - 2); - if (key == "HOME_URL") { - systemURL = value; - continue; - } + if (key == "PRETTY_NAME") { + systemName = QString::fromLocal8Bit(value); + continue; + } - if (key == "LOGO") { - systemLogoName = value; - continue; - } + if (key == "HOME_URL") { + systemURL = QString::fromLocal8Bit(value); + continue; + } + + if (key == "LOGO") { + systemLogoName = QString::fromLocal8Bit(value); + continue; } } } @@ -60,34 +62,25 @@ CSystemInternals::CSystemInternals(QObject* parent) : QObject(parent) { // get hyprland info if (getenv("HYPRLAND_INSTANCE_SIGNATURE")) { hlSystemVersion = execAndGet("hyprctl", {"version"}); - auto tagIndex = hlSystemVersion.indexOf("Tag: "); - - if (tagIndex != -1) { - hyprlandVersionLong = hlSystemVersion.sliced(tagIndex + 5); - auto commaIndex = hyprlandVersionLong.indexOf(','); - if (commaIndex != -1) - hyprlandVersionLong = hyprlandVersionLong.first(commaIndex); - - auto dashIndex = hyprlandVersionLong.indexOf('-'); - if (dashIndex == -1) - hyprlandVersion = hyprlandVersionLong; - else - hyprlandVersion = hyprlandVersionLong.first(dashIndex); - } + auto DATA = hlSystemVersion.toStdString(); - if (hyprlandVersionLong.isEmpty() && hlSystemVersion.contains("at commit")) { - auto commitIndex = hlSystemVersion.indexOf("at commit "); - if (commitIndex != -1) { - hyprlandVersionLong = hlSystemVersion.sliced(commitIndex + 10); + 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("-"))); + } - hyprlandVersionLong = substrUntil(hyprlandVersionLong, ' ').left(7); - hyprlandVersion = hyprlandVersionLong; - } + 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 = "unknown"; - hyprlandVersion = "unknown"; + hyprlandVersionLong = QStringLiteral("unknown"); + hyprlandVersion = QStringLiteral("unknown"); } hlSystemInfo = execAndGet("hyprctl", {"systeminfo"}); @@ -97,101 +90,75 @@ CSystemInternals::CSystemInternals(QObject* parent) : QObject(parent) { // get cpu info { - auto DATA = execAndGet("lscpu"); + const auto DATA = execAndGet("lscpu").toStdString(); if (DATA.contains("odel name")) { - QString arch, model, ghz, nproc; - auto textStream = QTextStream(&DATA); + std::string arch, model, ghz, nproc; - while (!textStream.atEnd()) { - auto line = textStream.readLine(); - auto indexOfColon = line.indexOf(':'); - if (indexOfColon == -1) - continue; - - auto left = line.sliced(0, indexOfColon).trimmed(); - auto right = line.sliced(indexOfColon + 1).trimmed(); + 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") { - auto ok = false; - auto ghzFloat = right.toFloat(&ok); - - if (ok) - ghz = QString("%1Ghz").arg(ghzFloat / 1000.F, 2, 'g', 2); - else - ghz = "?Ghz"; - + try { + ghz = std::format("{:.02}GHz", std::stof(right) / 1000.F); + } catch (...) { ghz = "?GHz"; } continue; } } - cpuInfo = QString("%1 at %2x%3 (%4)").arg(model).arg(nproc).arg(ghz).arg(arch); + cpuInfo = QString::fromLocal8Bit(std::format("{} at {}x{} ({})", model, nproc, ghz, arch)); } } // get gpu info { - auto DATA = execAndGet("lspci", {"-vnn"}); - auto textStream = QTextStream(&DATA); - - if (!DATA.isEmpty()) - gpuInfo.clear(); - - while (!textStream.atEnd()) { - auto line = textStream.readLine(); - - auto indexOfVga = line.indexOf("VGA"); - if (indexOfVga == -1) - continue; + auto ok = false; + const auto DATA = execAndGet("lspci", {"-vnn"}, &ok).toStdString(); + CVarList lines(DATA, 0, '\n'); - line = line.sliced(indexOfVga); - - auto indexOfColon = line.indexOf(": "); - if (indexOfColon == -1) - continue; - - line = line.sliced(indexOfColon + 2); - - gpuInfo.emplaceBack(line); - } + 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("No GPUs found"); - } + if (gpuInfo.isEmpty()) + gpuInfo.emplaceBack(QStringLiteral("No GPUs found")); + } else + gpuInfo.emplaceBack(QStringLiteral("missing dependency: lspci")); } // get ram info { - auto DATA = execAndGet("free"); - auto textStream = QTextStream(&DATA); - - if (textStream.readLine().contains("total")) { - auto ramIntToReadable = [](QStringView datapoint) -> QString { - auto ok = false; - auto ghzFloat = datapoint.toULongLong(&ok); - - if (ok) - return QString("%1GB").arg(ghzFloat / 1000000.0, 3, 'g', 3); - else - return "[error]"; + 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]"; } }; - auto props = textStream.readLine().simplified().split(' '); - ramInfo = QString("%1 / %2").arg(ramIntToReadable(props[2])).arg(ramIntToReadable(props[1])); + CVarList props(data[1], 0, 's', true); + + ramInfo = QString::fromLocal8Bit(std::format("{} / {}", ramIntToReadable(props[2]), ramIntToReadable(props[1]))); } } @@ -199,66 +166,49 @@ CSystemInternals::CSystemInternals(QObject* parent) : QObject(parent) { if (auto DE = qEnvironmentVariable("XDG_CURRENT_DESKTOP"); !DE.isEmpty()) this->DE = DE; - { - QFile uptimeFile("/proc/uptime"); - if (uptimeFile.open(QFile::ReadOnly | QFile::Text)) { - auto ok = false; - auto uptime = substrUntil(uptimeFile.readAll(), ' ').toFloat(&ok); - - if (ok) { - int uptimeSeconds = std::round(uptime); - 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); + if (auto uptime = readFile("/proc/uptime")) { + CVarList data(uptime.value(), 0, 's', true); - QString upStr; - auto textStream = QTextStream(&upStr); + 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); - if (uptimeDays > 0) - textStream << uptimeDays << " days, "; + auto upStr = std::format("{}{}{}", (uptimeDays > 0 ? std::format("{} days, ", uptimeDays) : ""), (uptimeHours > 0 ? std::format("{} hours, ", uptimeHours) : ""), + (uptimeMinutes > 0 ? std::format("{} minutes, ", uptimeMinutes) : "")); - if (uptimeHours > 0) - textStream << uptimeHours << " hours, "; + if (!upStr.empty()) + upStr = upStr.substr(0, upStr.length() - 2); - if (uptimeMinutes > 0) - textStream << uptimeMinutes << " minutes, "; - - if (!upStr.isEmpty()) - upStr.chop(2); - - this->uptime = upStr; - } - } + this->uptime = QString::fromLocal8Bit(upStr); + } catch (...) { ; } } { - QString screens; - QTextStream textStream(&screens); + std::string screens; for (auto* s : QGuiApplication::screens()) { - textStream << s->name() << ' ' << '(' << s->geometry().width() << 'x' << s->geometry().height() << "), "; + auto ratio = s->devicePixelRatio(); + screens += std::format("{} ({}x{}), ", s->name().toStdString(), s->geometry().width() * ratio, s->geometry().height() * ratio); } - if (!screens.isEmpty()) - screens.chop(2); + if (!screens.empty()) + screens = screens.substr(0, screens.length() - 2); - this->screens = screens; + this->screens = QString::fromLocal8Bit(screens); } if (auto* username = getlogin()) { std::array hostname; if (gethostname(hostname.data(), hostname.size()) == 0) - user = QString("%1@%2").arg(QLatin1StringView(username)).arg(QLatin1StringView(hostname.data())); + user = QString::fromLocal8Bit(std::format("{}@{}", username, hostname.data())); } { - QFile productFile("/sys/devices/virtual/dmi/id/product_name"); - if (productFile.open(QFile::ReadOnly | QFile::Text)) - board = productFile.readAll().trimmed(); - else { - productFile.setFileName("/sys/devices/virtual/dmi/id/board_name"); - if (productFile.open(QFile::ReadOnly | QFile::Text)) - board = productFile.readAll().trimmed(); - } + 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())); } } diff --git a/src/SystemInfo.hpp b/src/SystemInfo.hpp index bfb3d80..3c91a39 100644 --- a/src/SystemInfo.hpp +++ b/src/SystemInfo.hpp @@ -34,7 +34,7 @@ class CSystemInternals : public QObject { QString hyprlandVersion, hyprlandVersionLong; QString cpuInfo = "missing dependency: lscpu"; - QVector gpuInfo = {"missing dependency: lspci"}; + QVector gpuInfo; QString ramInfo = "?"; QString hlSystemInfo = "[error]", hlSystemVersion = "[error]"; diff --git a/src/util/Utils.cpp b/src/util/Utils.cpp index e51f701..22510b6 100644 --- a/src/util/Utils.cpp +++ b/src/util/Utils.cpp @@ -1,32 +1,46 @@ #include "Utils.hpp" +#include #include #include #include +#include -QString execAndGet(const QString& program, const QStringList& arguments) { +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(); } -QString substrUntil(const QString& string, char until) { - auto index = string.indexOf(until); +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 (...) {} - if (index == -1) - return string; - else - return string.first(index); + return {}; } diff --git a/src/util/Utils.hpp b/src/util/Utils.hpp index 1d5342f..8b268c2 100644 --- a/src/util/Utils.hpp +++ b/src/util/Utils.hpp @@ -2,6 +2,7 @@ #include #include +#include -QString execAndGet(const QString& program, const QStringList& arguments = {}); -QString substrUntil(const QString& string, char until); +QString execAndGet(const QString& program, const QStringList& arguments = {}, bool* ok = nullptr); +std::optional readFile(const std::string& filename);