initial commit
This commit is contained in:
+30
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user