From 4421b0167429f303e0157bbd2f821f93a59eefac Mon Sep 17 00:00:00 2001 From: cybrneko Date: Wed, 16 Sep 2026 01:46:52 -0500 Subject: [PATCH] initial commit --- .gitignore | 30 ++++++++++ CMakeLists.txt | 27 +++++++++ Main.qml | 155 +++++++++++++++++++++++++++++++++++++++++++++++++ SystemInfo.cpp | 89 ++++++++++++++++++++++++++++ SystemInfo.h | 31 ++++++++++ main.cpp | 26 +++++++++ 6 files changed, 358 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Main.qml create mode 100644 SystemInfo.cpp create mode 100644 SystemInfo.h create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab584fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# CMake build directories +build/ +cmake-build-*/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile +compile_commands.json + +# Qt generated files +.qmlcache/ +.qtc_clangd/ +*.qmlc +*.jsc + +# IDE/editor junk +.vscode/ +.idea/ +*.user +*.user.* + +# Temporary files +*~ +*.swp +*.swo + +# Misc +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8430989 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) + +project(bottomdash VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Quick) + +qt_standard_project_setup(REQUIRES 6.8) + +qt_add_executable(bottomdash + main.cpp + SystemInfo.cpp + SystemInfo.h +) + +qt_add_qml_module(bottomdash + URI BottomDash + VERSION 1.0 + QML_FILES + Main.qml +) + +target_link_libraries(bottomdash + PRIVATE Qt6::Quick +) diff --git a/Main.qml b/Main.qml new file mode 100644 index 0000000..a5f9df1 --- /dev/null +++ b/Main.qml @@ -0,0 +1,155 @@ +import QtQuick +import QtQuick.Window + +Window { + id: root + + width: 1240 + height: 1080 + visible: true + + flags: Qt.FramelessWindowHint + color: "#000000" + + // ───────────────────────────────────────────────────────────── + // Clock + // ───────────────────────────────────────────────────────────── + + property date currentTime: new Date() + + Timer { + interval: 1000 + running: true + repeat: true + + onTriggered: root.currentTime = new Date() + } + + Text { + id: clock + + anchors { + top: parent.top + left: parent.left + topMargin: 40 + leftMargin: 48 + } + + text: Qt.formatTime(root.currentTime, "h:mm AP") + + color: "#ffffff" + + font { + pixelSize: 64 + weight: Font.Medium + } + } + + + // ───────────────────────────────────────────────────────────── + // Battery + // + // Fake percentage for the moment. We'll connect this to + // /sys/class/power_supply from C++ next. + // ───────────────────────────────────────────────────────────── + + property int batteryPercent: systemInfo.batteryPercent + + Item { + id: battery + + width: batteryIcon.width + 18 + batteryText.width + height: 72 + + anchors { + top: parent.top + right: parent.right + topMargin: 40 + rightMargin: 48 + } + + // Battery body + Rectangle { + id: batteryIcon + + width: 72 + height: 38 + + anchors.verticalCenter: parent.verticalCenter + + color: "transparent" + border { + width: 4 + color: "#ffffff" + } + + radius: 5 + + // Battery fill + Rectangle { + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + + leftMargin: 7 + topMargin: 7 + bottomMargin: 7 + } + + width: Math.max( + 0, + (parent.width - 14) * + root.batteryPercent / 100 + ) + + color: { + if (root.batteryPercent <= 15) + return "#ff453a" + + if (root.batteryPercent <= 35) + return "#ffd60a" + + return "#32d74b" + } + + radius: 2 + } + + // Battery terminal + Rectangle { + width: 7 + height: 18 + + anchors { + left: parent.right + verticalCenter: parent.verticalCenter + } + + color: "#ffffff" + + topRightRadius: 3 + bottomRightRadius: 3 + } + } + + Text { + id: batteryText + + anchors { + left: batteryIcon.right + leftMargin: 18 + verticalCenter: parent.verticalCenter + } + + text: root.batteryPercent + "%" + + color: "#ffffff" + + font { + pixelSize: 64 + weight: Font.Medium + } + } + } +} diff --git a/SystemInfo.cpp b/SystemInfo.cpp new file mode 100644 index 0000000..fec7d2d --- /dev/null +++ b/SystemInfo.cpp @@ -0,0 +1,89 @@ +#include "SystemInfo.h" + +#include +#include +#include + +SystemInfo::SystemInfo(QObject *parent) + : QObject(parent) +{ + m_capacityPath = findBatteryCapacityPath(); + + updateBattery(); + + connect(&m_batteryTimer, + &QTimer::timeout, + this, + &SystemInfo::updateBattery); + + // Battery percentage doesn't need millisecond precision. + m_batteryTimer.start(10000); +} + +int SystemInfo::batteryPercent() const +{ + return m_batteryPercent; +} + +QString SystemInfo::findBatteryCapacityPath() const +{ + QDir powerSupply("/sys/class/power_supply"); + + const QStringList devices = + powerSupply.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + for (const QString &device : devices) { + const QString base = + powerSupply.absoluteFilePath(device); + + QFile typeFile(base + "/type"); + + if (!typeFile.open(QIODevice::ReadOnly | QIODevice::Text)) + continue; + + const QString type = + QString::fromUtf8(typeFile.readAll()).trimmed(); + + if (type != "Battery") + continue; + + const QString capacityPath = base + "/capacity"; + + if (QFile::exists(capacityPath)) + return capacityPath; + } + + return {}; +} + +void SystemInfo::updateBattery() +{ + if (m_capacityPath.isEmpty()) + m_capacityPath = findBatteryCapacityPath(); + + if (m_capacityPath.isEmpty()) + return; + + QFile capacityFile(m_capacityPath); + + if (!capacityFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + + bool ok = false; + + int percent = + QString::fromUtf8(capacityFile.readAll()) + .trimmed() + .toInt(&ok); + + if (!ok) + return; + + percent = qBound(0, percent, 100); + + if (percent == m_batteryPercent) + return; + + m_batteryPercent = percent; + emit batteryPercentChanged(); +} diff --git a/SystemInfo.h b/SystemInfo.h new file mode 100644 index 0000000..4815d30 --- /dev/null +++ b/SystemInfo.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class SystemInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(int batteryPercent + READ batteryPercent + NOTIFY batteryPercentChanged) + +public: + explicit SystemInfo(QObject *parent = nullptr); + + int batteryPercent() const; + +signals: + void batteryPercentChanged(); + +private slots: + void updateBattery(); + +private: + QString findBatteryCapacityPath() const; + + int m_batteryPercent = -1; + QString m_capacityPath; + QTimer m_batteryTimer; +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..9b84c29 --- /dev/null +++ b/main.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "SystemInfo.h" + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + SystemInfo systemInfo; + + QQmlApplicationEngine engine; + + engine.rootContext()->setContextProperty( + "systemInfo", + &systemInfo + ); + + engine.loadFromModule("BottomDash", "Main"); + + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +}