Add the "HA Dashboard" tab with HA's web UI (DSH-1)
A second dashboard surface shows a WebEngineView at homeassistant.url, or a hint when none is set. HaWebUi (QML singleton owned by Dashboard) holds the URL and a persistent WebEngine profile under ~/.local/share/frameassistant/webengine so the HA login survives restarts. QtWebEngineQuick::initialize() runs in main() before the app object (D39). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+8
-2
@@ -58,14 +58,20 @@ target_link_libraries(frameassistant_lib PUBLIC Qt6::Core)
|
|||||||
target_compile_options(frameassistant_lib PRIVATE -Wall -Wextra)
|
target_compile_options(frameassistant_lib PRIVATE -Wall -Wextra)
|
||||||
|
|
||||||
# The app: its UI is the QML module "FrameAssistant" (src/qml/).
|
# The app: its UI is the QML module "FrameAssistant" (src/qml/).
|
||||||
|
# Qt WebEngine shows HA's web UI (DSH-1); on the Frame it comes with the system Qt.
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS WebEngineQuick)
|
||||||
vrdash_add_dashboard(frameassistant
|
vrdash_add_dashboard(frameassistant
|
||||||
OUTPUT_NAME ${VRDASH_EXECUTABLE_NAME}
|
OUTPUT_NAME ${VRDASH_EXECUTABLE_NAME}
|
||||||
QML_URI FrameAssistant
|
QML_URI FrameAssistant
|
||||||
SOURCES main.cpp Dashboard.h Dashboard.cpp
|
SOURCES main.cpp Dashboard.h Dashboard.cpp
|
||||||
|
QML_SOURCES ha/HaWebUi.h ha/HaWebUi.cpp
|
||||||
QML_FILES
|
QML_FILES
|
||||||
qml/Main.qml
|
qml/Main.qml
|
||||||
qml/Card.qml)
|
qml/Card.qml
|
||||||
target_link_libraries(frameassistant PRIVATE frameassistant_lib)
|
qml/HaDashboard.qml)
|
||||||
|
target_link_libraries(frameassistant PRIVATE frameassistant_lib Qt6::WebEngineQuick)
|
||||||
|
# The generated QML type registration includes QML_SOURCES headers by file name.
|
||||||
|
target_include_directories(frameassistant PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ha)
|
||||||
|
|
||||||
install(FILES ${OPENVR_LIBRARY} DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
install(FILES ${OPENVR_LIBRARY} DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ Dashboard::Dashboard(int& argc, char** argv)
|
|||||||
.arg(Config::defaultPath()),
|
.arg(Config::defaultPath()),
|
||||||
QStringLiteral("path"))}) {
|
QStringLiteral("path"))}) {
|
||||||
addSurface({.qmlRootType = QStringLiteral("Main")});
|
addSurface({.qmlRootType = QStringLiteral("Main")});
|
||||||
|
addSurface({.id = QStringLiteral("dashboard"),
|
||||||
|
.qmlRootType = QStringLiteral("HaDashboard"),
|
||||||
|
.displayName = QStringLiteral("HA Dashboard")});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Dashboard::prepare(QString* error) {
|
bool Dashboard::prepare(QString* error) {
|
||||||
@@ -34,3 +37,5 @@ bool Dashboard::prepare(QString* error) {
|
|||||||
<< m_config.deviceName << "(" + m_config.deviceId + ")";
|
<< m_config.deviceName << "(" + m_config.deviceId + ")";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Dashboard::loading() { m_haWebUi.setUrl(m_config.haUrl); }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "app/DashboardApplication.h"
|
#include "app/DashboardApplication.h"
|
||||||
#include "config/Config.h"
|
#include "config/Config.h"
|
||||||
|
#include "ha/HaWebUi.h"
|
||||||
|
|
||||||
// The dashboard app. Its UI is the QML module set up in src/CMakeLists.txt
|
// The dashboard app. Its UI is the QML module set up in src/CMakeLists.txt
|
||||||
// (QML_URI FrameAssistant, files in src/qml/).
|
// (QML_URI FrameAssistant, files in src/qml/).
|
||||||
@@ -16,7 +17,10 @@ class Dashboard : public DashboardApplication {
|
|||||||
protected:
|
protected:
|
||||||
// Loads the settings (overlay and desktop mode; docs/DESIGN.md, D22).
|
// Loads the settings (overlay and desktop mode; docs/DESIGN.md, D22).
|
||||||
bool prepare(QString* error) override;
|
bool prepare(QString* error) override;
|
||||||
|
void loading() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Config m_config;
|
Config m_config;
|
||||||
|
// QML singleton (HaWebUi); a member, so it outlives all QML.
|
||||||
|
HaWebUi m_haWebUi{nullptr};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#include "ha/HaWebUi.h"
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQuickWebEngineProfile>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
HaWebUi* g_instance = nullptr;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HaWebUi::HaWebUi(QObject* parent) : QObject(parent) {
|
||||||
|
Q_ASSERT(!g_instance);
|
||||||
|
g_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
HaWebUi::~HaWebUi() { g_instance = nullptr; }
|
||||||
|
|
||||||
|
QQuickWebEngineProfile* HaWebUi::profile() {
|
||||||
|
if (!m_profile) {
|
||||||
|
m_profile = new QQuickWebEngineProfile(this);
|
||||||
|
m_profile->setStorageName(QStringLiteral("frameassistant"));
|
||||||
|
m_profile->setOffTheRecord(false);
|
||||||
|
m_profile->setPersistentStoragePath(storagePath());
|
||||||
|
m_profile->setCachePath(cachePath());
|
||||||
|
m_profile->setPersistentCookiesPolicy(
|
||||||
|
QQuickWebEngineProfile::AllowPersistentCookies);
|
||||||
|
}
|
||||||
|
return m_profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HaWebUi::storagePath() {
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
|
||||||
|
QStringLiteral("/frameassistant/webengine");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HaWebUi::cachePath() {
|
||||||
|
return QStandardPaths::writableLocation(
|
||||||
|
QStandardPaths::GenericCacheLocation) +
|
||||||
|
QStringLiteral("/frameassistant/webengine");
|
||||||
|
}
|
||||||
|
|
||||||
|
HaWebUi* HaWebUi::create(QQmlEngine*, QJSEngine*) {
|
||||||
|
Q_ASSERT(g_instance);
|
||||||
|
// Owned by the app, not by the QML engine.
|
||||||
|
QJSEngine::setObjectOwnership(g_instance, QJSEngine::CppOwnership);
|
||||||
|
return g_instance;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtQml/qqmlregistration.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQuickWebEngineProfile>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
class QJSEngine;
|
||||||
|
class QQmlEngine;
|
||||||
|
|
||||||
|
// HA's web UI in the "HA Dashboard" tab (DSH-1): the URL from the settings
|
||||||
|
// and a persistent WebEngine profile, so the HA login survives restarts
|
||||||
|
// (docs/DESIGN.md, D39). A QML singleton owned by the app (Dashboard).
|
||||||
|
class HaWebUi : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_SINGLETON
|
||||||
|
// Empty: no URL configured (the tab shows a hint).
|
||||||
|
Q_PROPERTY(QUrl url READ url CONSTANT)
|
||||||
|
Q_PROPERTY(QQuickWebEngineProfile* profile READ profile CONSTANT)
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Not default-constructible, so QML uses create() instead of making its
|
||||||
|
// own instance.
|
||||||
|
explicit HaWebUi(QObject* parent);
|
||||||
|
~HaWebUi() override;
|
||||||
|
|
||||||
|
// Call before the QML is loaded.
|
||||||
|
void setUrl(const QUrl& url) { m_url = url; }
|
||||||
|
|
||||||
|
QUrl url() const { return m_url; }
|
||||||
|
// Created on first use: only a tab that shows the web UI needs it.
|
||||||
|
QQuickWebEngineProfile* profile();
|
||||||
|
|
||||||
|
// Where the profile keeps its data (cookies, local storage) and cache.
|
||||||
|
static QString storagePath();
|
||||||
|
static QString cachePath();
|
||||||
|
|
||||||
|
// QML singleton factory: the instance the app created.
|
||||||
|
static HaWebUi* create(QQmlEngine*, QJSEngine*);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUrl m_url;
|
||||||
|
QQuickWebEngineProfile* m_profile = nullptr;
|
||||||
|
};
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
|
#include <QtWebEngineQuick/qtwebenginequickglobal.h>
|
||||||
|
|
||||||
#include "Dashboard.h"
|
#include "Dashboard.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
// Must run before the Qt application object exists (the Dashboard
|
||||||
|
// constructor creates it).
|
||||||
|
QtWebEngineQuick::initialize();
|
||||||
Dashboard app(argc, argv);
|
Dashboard app(argc, argv);
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.Material
|
||||||
|
import QtWebEngine
|
||||||
|
|
||||||
|
// "HA Dashboard" tab (DSH-1): HA's own web UI at the URL from the settings.
|
||||||
|
// Without a URL it shows a hint instead.
|
||||||
|
Pane {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Material.theme: Material.Dark
|
||||||
|
padding: 0
|
||||||
|
font.pixelSize: 24
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: HaWebUi.url.toString() !== "" ? webUi : hint
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: webUi
|
||||||
|
|
||||||
|
WebEngineView {
|
||||||
|
profile: HaWebUi.profile
|
||||||
|
url: HaWebUi.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: hint
|
||||||
|
|
||||||
|
Label {
|
||||||
|
padding: 48
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: qsTr("No Home Assistant URL configured.\n\nSet \"homeassistant.url\" in the settings file and restart.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user