initial commit

This commit is contained in:
2026-09-16 01:46:52 -05:00
commit 4421b01674
6 changed files with 358 additions and 0 deletions
+30
View File
@@ -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
+27
View File
@@ -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
)
+155
View File
@@ -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
}
}
}
}
+89
View File
@@ -0,0 +1,89 @@
#include "SystemInfo.h"
#include <QDir>
#include <QFile>
#include <QTextStream>
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();
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <QObject>
#include <QTimer>
#include <QString>
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;
};
+26
View File
@@ -0,0 +1,26 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#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();
}