init
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
qt-navidrome-client/.qtcreator
|
||||
qt-navidrome-client/build
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user