diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..90314ef --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +ColumnLimit: 180 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentWidth: 4 +PointerAlignment: Left +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never + +AllowShortEnumsOnASingleLine: false + +BraceWrapping: + AfterEnum: false + +AlignConsecutiveDeclarations: AcrossEmptyLines + +NamespaceIndentation: All diff --git a/.gitignore b/.gitignore index 259148f..936c972 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,8 @@ *.exe *.out *.app + +.vscode/ +.cache/ +build/ + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..160cc55 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.16) + +# Get version +file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) +string(STRIP ${VER_RAW} VER) + +project(hsi VERSION ${VER} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Quick QuickControls2) +find_package(PkgConfig REQUIRED) +set(CMAKE_CXX_STANDARD 23) + +pkg_check_modules( + deps + REQUIRED + IMPORTED_TARGET + hyprutils) + +qt_standard_project_setup(REQUIRES 6.5) + +qt_add_executable(hyprsysteminfo + src/main.cpp src/util/Utils.cpp src/util/Utils.hpp +) + +qt_add_qml_module(hyprsysteminfo + URI hsi + VERSION 1.0 + QML_FILES + qml/main.qml + SOURCES + qmlSources/SystemInternals.hpp qmlSources/SystemInternals.cpp qmlSources/SystemIconProvider.hpp +) + +qt_add_resources(hyprsysteminfo "resource" + PREFIX "/" + FILES + resource/hyprlandlogo.svg + resource/hyprlandlogo.png +) + +target_link_libraries(hyprsysteminfo + PRIVATE Qt6::Quick Qt6::Gui Qt6::QuickControls2 PkgConfig::deps +) + +include(GNUInstallDirs) +install(TARGETS hyprsysteminfo + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6c6aa7c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..d0e0713 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,217 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + property var _width: 620 + property var _height: 400 + property var _first_panel_height: 120 + + minimumWidth: _width + maximumWidth: _width + width: _width + minimumHeight: _height + maximumHeight: _height + height: _height + visible: true + + SystemPalette { + id: system + + colorGroup: SystemPalette.Active + } + + ColumnLayout { + id: mainLayout + + anchors.margins: 4 + + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: parent.bottom + } + + RowLayout { + // First panel hyprland and distro info + Layout.fillWidth: true + Layout.preferredHeight: _first_panel_height + Layout.alignment: Qt.AlignHCenter + + RowLayout { + id: distroLogoName + + Layout.preferredWidth: _width / 2 - 40 + spacing: 10 + + Image { + visible: hsi.hasSystemLogoName() + source: "image://systemIcons/" + hsi.getSystemLogoName() + sourceSize.width: _first_panel_height - 10 + sourceSize.height: _first_panel_height - 10 + Layout.preferredWidth: _first_panel_height - 10 + Layout.preferredHeight: _first_panel_height - 10 + Layout.alignment: Qt.AlignCenter + smooth: true + } + + ColumnLayout { + spacing: 2 + + Text { + text: hsi.getSystemName() + Layout.alignment: Qt.AlignHCenter + color: system.windowText + } + + Text { + text: hsi.getSystemURL() + Layout.alignment: Qt.AlignHCenter + color: system.windowText + } + + Text { + text: hsi.getSystemKernel() + Layout.alignment: Qt.AlignHCenter + color: system.windowText + } + + } + + } + + Rectangle { + visible: hsi.hasHyprland() + color: Qt.darker(system.text, 1.5) + Layout.preferredWidth: 1 + Layout.preferredHeight: _first_panel_height - 40 + Layout.leftMargin: 20 + Layout.rightMargin: 20 + Layout.alignment: Qt.AlignVCenter + } + + RowLayout { + id: hyprlandInfo + + visible: hsi.hasHyprland() + Layout.preferredWidth: _width / 2 - 40 + spacing: 10 + + Image { + source: "qrc:/resource/hyprlandlogo.svg" + sourceSize.width: _first_panel_height - 10 + sourceSize.height: _first_panel_height - 10 + Layout.preferredWidth: _first_panel_height - 10 + Layout.preferredHeight: _first_panel_height - 10 + Layout.alignment: Qt.AlignCenter + smooth: true + } + + ColumnLayout { + spacing: 2 + + Text { + text: "Hyprland" + Layout.alignment: Qt.AlignHCenter + color: system.windowText + } + + Text { + text: hsi.getHyprlandVersion() + Layout.alignment: Qt.AlignHCenter + color: system.windowText + } + + Text { + text: hsi.getHyprlandVersionLong() + Layout.alignment: Qt.AlignHCenter + color: system.windowText + } + + } + + } + + } + + Rectangle { + color: Qt.darker(system.text, 1.5) + Layout.preferredHeight: 1 + Layout.fillWidth: true + Layout.leftMargin: 180 + Layout.rightMargin: 180 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + + ColumnLayout { + spacing: 6 + Layout.leftMargin: 60 + Layout.rightMargin: 60 + + Text { + text: "CPU: " + hsi.getCPUInfo() + Layout.maximumWidth: _width - 120 + color: system.windowText + elide: Text.ElideRight + textFormat: Text.PlainText + wrapMode: Text.NoWrap + } + + Text { + text: hsi.getGPUInfo() + Layout.maximumWidth: _width - 120 + color: system.windowText + elide: Text.ElideRight + textFormat: Text.PlainText + wrapMode: Text.NoWrap + } + + Text { + text: "Memory: " + hsi.getRAMInfo() + Layout.maximumWidth: _width - 120 + color: system.windowText + elide: Text.ElideRight + textFormat: Text.PlainText + wrapMode: Text.NoWrap + } + + } + + Rectangle { + color: "transparent" + Layout.fillWidth: true + Layout.fillHeight: true + } + + Rectangle { + color: Qt.darker(system.text, 1.5) + Layout.preferredHeight: 1 + Layout.fillWidth: true + Layout.leftMargin: 180 + Layout.rightMargin: 180 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + + RowLayout { + spacing: 6 + Layout.leftMargin: 20 + Layout.rightMargin: 20 + + Button { + text: "Copy Hyprland System Info" + onClicked: (e) => {hsi.copySystemInfo();} + } + + Button { + text: "Copy Hyprland Version" + onClicked: (e) => {hsi.copyVersion();} + } + + } + + } + +} diff --git a/qmlSources/SystemIconProvider.hpp b/qmlSources/SystemIconProvider.hpp new file mode 100644 index 0000000..d435d82 --- /dev/null +++ b/qmlSources/SystemIconProvider.hpp @@ -0,0 +1,20 @@ +#ifndef SYSTEMICONPROVIDER_H +#define SYSTEMICONPROVIDER_H + +#include +#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 { + return QIcon::fromTheme(id).pixmap({512, 512}); + } +}; + +#endif // SYSTEMICONPROVIDER_H \ No newline at end of file diff --git a/qmlSources/SystemInternals.cpp b/qmlSources/SystemInternals.cpp new file mode 100644 index 0000000..4e7724a --- /dev/null +++ b/qmlSources/SystemInternals.cpp @@ -0,0 +1,11 @@ +#include "SystemInternals.hpp" +#include "src/util/Utils.hpp" +#include + +void CSystemInternals::copySystemInfo() { + execAndGet(std::format("echo '{}' | wl-copy && hyprctl notify 5 5000 0 'Copied system info to the clipboard.'", hlSystemInfo.toStdString()).c_str()); +} + +void CSystemInternals::copyVersion() { + execAndGet(std::format("echo '{}' | wl-copy && hyprctl notify 5 5000 0 'Copied version info to the clipboard.'", hlSystemVersion.toStdString()).c_str()); +} \ No newline at end of file diff --git a/qmlSources/SystemInternals.hpp b/qmlSources/SystemInternals.hpp new file mode 100644 index 0000000..6a9a520 --- /dev/null +++ b/qmlSources/SystemInternals.hpp @@ -0,0 +1,74 @@ +#ifndef SYSTEMINTERNALS_H +#define SYSTEMINTERNALS_H + +#include +#include +#include +#include + +class CSystemInternals : public QObject { + Q_OBJECT; + + public: + explicit CSystemInternals(QObject* parent = nullptr) : QObject(parent) { + ; + } + + QString systemLogoName, systemName = "Linux", systemURL = "https://kernel.org/", systemKernel = "unknown"; + QString hyprlandVersion, hyprlandVersionLong; + + QString cpuInfo = "missing dependency: lscpu"; + QString gpuInfo = "GPU: missing dependency: lspci"; + QString ramInfo = "?"; + + QString hlSystemInfo = "[error]", hlSystemVersion = "[error]"; + + Q_INVOKABLE bool hasSystemLogoName() { + return systemLogoName.size() > 0; + } + + Q_INVOKABLE QString getSystemLogoName() { + return systemLogoName; + } + + Q_INVOKABLE QString getSystemName() { + return systemName; + } + + Q_INVOKABLE QString getSystemURL() { + return systemURL; + } + + Q_INVOKABLE QString getSystemKernel() { + return systemKernel; + } + + Q_INVOKABLE bool hasHyprland() { + return hyprlandVersionLong.size() > 0; + } + + Q_INVOKABLE QString getHyprlandVersion() { + return hyprlandVersion; + } + + Q_INVOKABLE QString getHyprlandVersionLong() { + return hyprlandVersionLong; + } + + Q_INVOKABLE QString getCPUInfo() { + return cpuInfo; + } + + Q_INVOKABLE QString getGPUInfo() { + return gpuInfo; + } + + Q_INVOKABLE QString getRAMInfo() { + return ramInfo; + } + + Q_INVOKABLE void copySystemInfo(); + Q_INVOKABLE void copyVersion(); +}; + +#endif // SYSTEMINTERNALS_H diff --git a/resource/hyprlandlogo.png b/resource/hyprlandlogo.png new file mode 100755 index 0000000..14ea6a4 Binary files /dev/null and b/resource/hyprlandlogo.png differ diff --git a/resource/hyprlandlogo.svg b/resource/hyprlandlogo.svg new file mode 100644 index 0000000..12925aa --- /dev/null +++ b/resource/hyprlandlogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a8ae370 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,159 @@ +#include "qmlSources/SystemIconProvider.hpp" +#include "qmlSources/SystemInternals.hpp" +#include +#include +#include +#include +#include +#include +#include "util/Utils.hpp" + +#include +#include +using namespace Hyprutils::String; + +static void getSystemInfo(CSystemInternals* hsi) { + + // gather data from os-release + std::ifstream osInfo("/etc/os-release"); + if (osInfo.good()) { + std::string data(std::istreambuf_iterator{osInfo}, {}); + + osInfo.close(); + + CVarList lines(data, 0, '\n'); + + for (auto& line : lines) { + CVarList param(line, 2, '='); + + static auto stripName = [](const std::string& in) -> std::string { return in.length() > 0 && in.at(0) == '\"' ? in.substr(1, in.length() - 2) : in; }; + + if (param[0] == "PRETTY_NAME") { + hsi->systemName = stripName(param[1]).c_str(); + continue; + } + + if (param[0] == "HOME_URL") { + hsi->systemURL = stripName(param[1]).c_str(); + continue; + } + + if (param[0] == "LOGO") { + hsi->systemLogoName = stripName(param[1]).c_str(); + continue; + } + } + } + + // get kernel ver + hsi->systemKernel = execAndGet("uname -r").c_str(); + + // get hyprland info + if (getenv("HYPRLAND_INSTANCE_SIGNATURE")) { + const auto DATA = execAndGet("hyprctl version"); + if (DATA.contains("Tag:")) { + hsi->hlSystemVersion = DATA.c_str(); + + auto temp = DATA.substr(DATA.find("Tag:") + 5); + temp = temp.substr(0, temp.find(",")); + hsi->hyprlandVersionLong = temp.c_str(); + hsi->hyprlandVersion = temp.substr(0, temp.find("-")).c_str(); + } + + const auto DATA2 = execAndGet("hyprctl systeminfo"); + if (DATA2.contains("Tag:")) + hsi->hlSystemInfo = DATA2.c_str(); + } + + // get cpu info + { + const auto DATA = execAndGet("lscpu"); + 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; + } + } + + hsi->cpuInfo = std::format("{} at {}x{} ({})", model, nproc, ghz, arch).c_str(); + } + } + + // get gpu info + { + const auto DATA = execAndGet("lspci -vnn | grep VGA"); + if (DATA.contains("VGA")) { + CVarList data(DATA, 0, '\n'); + std::string gpuInfo; + for (auto& line : data) { + if (!line.contains("VGA")) + continue; + gpuInfo += std::format("GPU: {}\n", line.substr(line.find(":", line.find("VGA")) + 1)); + } + hsi->gpuInfo = gpuInfo.substr(0, gpuInfo.length() - 1).c_str(); + } else { + hsi->gpuInfo = "No GPUs found"; + } + } + + // get ram info + { + const auto DATA = execAndGet("free"); + 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); + + hsi->ramInfo = std::format("{} / {}", ramIntToReadable(props[2]), ramIntToReadable(props[1])).c_str(); + } + } +} + +int main(int argc, char* argv[]) { + QGuiApplication app(argc, argv); + + app.setApplicationName("Hyprland System Info"); + + QQuickStyle::setStyle("org.kde.desktop"); + + auto systemInternals = new CSystemInternals; + + getSystemInfo(systemInternals); + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("hsi", systemInternals); + engine.addImageProvider("systemIcons", new CSystemIconProvider); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.load(QUrl{u"qrc:/qt/qml/hsi/qml/main.qml"_qs}); + + return app.exec(); +} diff --git a/src/util/Utils.cpp b/src/util/Utils.cpp new file mode 100644 index 0000000..60cb70f --- /dev/null +++ b/src/util/Utils.cpp @@ -0,0 +1,18 @@ +#include "Utils.hpp" + +#include +#include +#include + +std::string execAndGet(const char* cmd) { + std::array buffer; + std::string result; + const std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) + return ""; + + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} \ No newline at end of file diff --git a/src/util/Utils.hpp b/src/util/Utils.hpp new file mode 100644 index 0000000..9bb0827 --- /dev/null +++ b/src/util/Utils.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string execAndGet(const char* cmd); \ No newline at end of file