This commit is contained in:
2026-01-03 23:27:14 +01:00
commit 61486008f6
11 changed files with 412 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
qt-navidrome-client/.qtcreator
qt-navidrome-client/build
View File
+25
View File
@@ -0,0 +1,25 @@
{ lib, stdenv, cmake, ninja, pkg-config, qt6 }:
stdenv.mkDerivation {
pname = "qt-example";
version = "1.0";
src = ./qt-example;
nativeBuildInputs = [
cmake
ninja
pkg-config
qt6.wrapQtAppsHook
];
buildInputs = [
qt6.qtbase
qt6.qtdeclarative
qt6.qtwayland
];
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
];
}
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1767379071,
"narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "fb7944c166a3b630f177938e478f0378e64ce108",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+39
View File
@@ -0,0 +1,39 @@
{
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
pkg = pkgs.callPackage ./build.nix {};
in {
packages.${system}.default = pkg;
apps.${system}.default = {
type = "app";
program = "${pkg}/bin/appuntitled";
};
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
cmake
gdb
qt6.qtbase
qt6.qtdeclarative
qt6.qtwayland
qtcreator
qt6.wrapQtAppsHook
makeWrapper
bashInteractive
];
shellHook = ''
export QT_QPA_PLATFORM=wayland
bashdir=$(mktemp -d)
makeWrapper "$(type -p bash)" "$bashdir/bash" "''${qtWrapperArgs[@]}"
exec "$bashdir/bash"
'';
};
};
}
+46
View File
@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.16)
project(navidrome_client VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt6 REQUIRED COMPONENTS Quick Network)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(appnavidrome_client
src/main.cpp
src/navidrome/NavidromeApi.cpp
src/navidrome/NavidromeApi.h
)
qt_add_qml_module(appnavidrome_client
URI Navidrome
VERSION 1.0
QML_FILES
qml/Main.qml
)
set_target_properties(appnavidrome_client PROPERTIES
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_include_directories(appnavidrome_client
PRIVATE src
)
target_link_libraries(appnavidrome_client
PRIVATE Qt6::Quick Qt6::Network
)
include(GNUInstallDirs)
install(TARGETS appnavidrome_client
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
+31
View File
@@ -0,0 +1,31 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "dev-debug",
"displayName": "Debug (Nix Umgebung)",
"description": "Debug Build mit Compiler aus der Flake",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_PREFIX_PATH": "$env{CMAKE_PREFIX_PATH}"
},
"environment": {
"QT_QPA_PLATFORM": "wayland;xcb"
}
}
],
"buildPresets": [
{
"name": "build-debug",
"configurePreset": "dev-debug"
}
]
}
+86
View File
@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
id: win
width: 420
height: 320
visible: true
title: "Navidrome Login"
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 10
Label {
text: "Server"
font.bold: true
}
TextField {
id: urlField
Layout.fillWidth: true
placeholderText: "https://navidrome.timvandenboom.eth64.de"
text: "https://navidrome.timvandenboom.eth64.de"
}
Label {
text: "Username"
font.bold: true
}
TextField {
id: userField
Layout.fillWidth: true
placeholderText: "username"
}
Label {
text: "Password"
font.bold: true
}
TextField {
id: passField
Layout.fillWidth: true
placeholderText: "password"
echoMode: TextInput.Password
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
text: navidromeApi.busy ? "Connecting..." : "Login (Ping)"
enabled: !navidromeApi.busy
Layout.fillWidth: true
onClicked: {
navidromeApi.ping(urlField.text, userField.text, passField.text)
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
opacity: 0.2
color: "white"
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: {
if (navidromeApi.busy) return "Verbinde…"
if (navidromeApi.lastError.length > 0) return "❌ " + navidromeApi.lastError
if (navidromeApi.lastStatus.length > 0) return "✅ status: " + navidromeApi.lastStatus
return "Noch nicht verbunden."
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "navidrome/NavidromeApi.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
NavidromeApi api;
engine.rootContext()->setContextProperty("navidromeApi", &api);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(1); },
Qt::QueuedConnection
);
engine.loadFromModule("Navidrome", "Main");
return app.exec();
}
@@ -0,0 +1,87 @@
#include "NavidromeApi.h"
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
NavidromeApi::NavidromeApi(QObject *parent)
: QObject(parent)
{}
void NavidromeApi::setBusy(bool v)
{
if (m_busy == v) return;
m_busy = v;
emit busyChanged();
}
void NavidromeApi::setLastError(const QString &e)
{
if (m_lastError == e) return;
m_lastError = e;
emit lastErrorChanged();
}
void NavidromeApi::setLastStatus(const QString &s)
{
if (m_lastStatus == s) return;
m_lastStatus = s;
emit lastStatusChanged();
}
void NavidromeApi::ping(const QString &baseUrl,
const QString &user,
const QString &password)
{
setLastError({});
setLastStatus({});
QUrl url(baseUrl.trimmed());
if (!url.isValid() || url.scheme().isEmpty()) {
setLastError("Ungültige URL (z.B. http://localhost:4533)");
emit pingFailed(lastError());
return;
}
// /rest/ping.view anhängen
url.setPath("/rest/ping.view");
QUrlQuery q;
q.addQueryItem("u", user);
q.addQueryItem("p", password); // später Token-Auth (t/s)
q.addQueryItem("v", "1.16.1");
q.addQueryItem("c", "qt-navidrome");
q.addQueryItem("f", "json");
url.setQuery(q);
setBusy(true);
auto *reply = m_net.get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
setBusy(false);
if (reply->error() != QNetworkReply::NoError) {
setLastError(reply->errorString());
emit pingFailed(lastError());
reply->deleteLater();
return;
}
// Optional: JSON prüfen, ob status == "ok"
const QByteArray body = reply->readAll();
const auto doc = QJsonDocument::fromJson(body);
if (doc.isObject()) {
const auto root = doc.object();
const auto ssr = root.value("subsonic-response").toObject();
const QString status = ssr.value("status").toString();
if (!status.isEmpty()) setLastStatus(status);
}
if (lastStatus().isEmpty())
setLastStatus("ok");
emit pingOk();
reply->deleteLater();
});
}
@@ -0,0 +1,45 @@
#pragma once
#include <QObject>
#include <QNetworkAccessManager>
#include <QUrl>
class NavidromeApi : public QObject
{
Q_OBJECT
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
Q_PROPERTY(QString lastError READ lastError NOTIFY lastErrorChanged)
Q_PROPERTY(QString lastStatus READ lastStatus NOTIFY lastStatusChanged)
public:
explicit NavidromeApi(QObject *parent = nullptr);
// QML Entry: baseUrl als String, damit TextField direkt passt
Q_INVOKABLE void ping(const QString &baseUrl,
const QString &user,
const QString &password);
bool busy() const { return m_busy; }
QString lastError() const { return m_lastError; }
QString lastStatus() const { return m_lastStatus; }
signals:
void pingOk();
void pingFailed(const QString &error);
void busyChanged();
void lastErrorChanged();
void lastStatusChanged();
private:
QNetworkAccessManager m_net;
bool m_busy = false;
QString m_lastError;
QString m_lastStatus;
void setBusy(bool v);
void setLastError(const QString &e);
void setLastStatus(const QString &s);
};