Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/app.pro
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ SOURCES += \
backend/usbforwardingbackend.cpp \
backend/usbforwardingelevation.cpp \
backend/usbforwardingmanager.cpp \
backend/usbforwardingsessionglue.cpp \
backend/usbforwardingsessionseams.cpp \
wm.cpp

HEADERS += \
Expand Down Expand Up @@ -258,7 +260,10 @@ HEADERS += \
backend/usbforwardingenvironment.h \
backend/usbforwardingbackend.h \
backend/usbforwardingelevation.h \
backend/usbforwardingmanager.h
backend/usbforwardingmanager.h \
backend/usbforwardingsessionglue.h \
backend/usbforwardingsessionseams.h \
backend/usbforwardinguilogic.h

# Platform-specific renderers and decoders
ffmpeg {
Expand Down
6 changes: 6 additions & 0 deletions app/backend/systemproperties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ SystemProperties::SystemProperties()
logFilesAvailable = false;
#endif

#ifdef Q_OS_WIN
usbForwardingAvailable = true;
#else
usbForwardingAvailable = false;
#endif

#ifdef Q_OS_DARWIN
isDarwin = true;
#else
Expand Down
2 changes: 2 additions & 0 deletions app/backend/systemproperties.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class SystemProperties : public QObject
Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT)
Q_PROPERTY(QString versionString MEMBER versionString CONSTANT)
Q_PROPERTY(bool logFilesAvailable MEMBER logFilesAvailable CONSTANT)
Q_PROPERTY(bool usbForwardingAvailable MEMBER usbForwardingAvailable CONSTANT)
Q_PROPERTY(bool diagnosticLoggingEnabled READ diagnosticLoggingEnabled NOTIFY diagnosticLoggingEnabledChanged)

// Properties queried asynchronously (startAsyncLoad() must be called!)
Expand Down Expand Up @@ -75,6 +76,7 @@ private slots:
bool usesMaterial3Theme;
bool isDarwin;
bool logFilesAvailable;
bool usbForwardingAvailable;

// Properties only set if startAsyncLoad() is called
bool hasHardwareAcceleration;
Expand Down
53 changes: 53 additions & 0 deletions app/backend/usbforwardingbackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include "usbforwardingbackend.h"
#include "usbforwardingcapability.h"
#include "usbforwardingenvironment.h"
#include "usbforwardinguilogic.h"

#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QJsonArray>
Expand Down Expand Up @@ -104,13 +106,39 @@ UsbForwardingBackend::UsbForwardingBackend(QObject *parent)

UsbForwardingBackend::~UsbForwardingBackend()
{
shutdown();
std::lock_guard<std::mutex> lock(m_Link->mutex);
m_Link->backend = nullptr;
}

void UsbForwardingBackend::shutdown()
{
const QList<QProcess *> probes = findChildren<QProcess *>(QString(), Qt::FindDirectChildrenOnly);
for (QProcess *probe : probes) {
probe->disconnect();
if (probe->state() != QProcess::NotRunning) {
probe->kill();
probe->waitForFinished(1000);
}
delete probe;
}
if (!probes.isEmpty()) {
m_Busy = false;
}
}

UsbForwardingBackend *UsbForwardingBackend::get()
{
// A function-local static is destroyed after the application object is gone,
// so a probe still running at exit is stopped from aboutToQuit instead.
static UsbForwardingBackend backend;
static const bool wired = [] {
if (QCoreApplication *app = QCoreApplication::instance()) {
QObject::connect(app, &QCoreApplication::aboutToQuit, &backend, [] { backend.shutdown(); });
}
return true;
}();
Q_UNUSED(wired)
return &backend;
}

Expand Down Expand Up @@ -195,6 +223,31 @@ QVariantList UsbForwardingBackend::parseUsbipdState(const QByteArray &stateJson,
return devices;
}

QString UsbForwardingBackend::shareStateText(const QVariantMap &device) const
{
return UsbForwarding::Ui::deviceShareStateText(UsbForwarding::Ui::deviceShareState(device));
}

bool UsbForwardingBackend::canShare(const QVariantMap &device) const
{
return UsbForwarding::Ui::canShare(device);
}

bool UsbForwardingBackend::canStopSharing(const QVariantMap &device) const
{
return UsbForwarding::Ui::canStopSharing(device);
}

bool UsbForwardingBackend::autoDevicesContain(const QStringList &list, const QString &identity) const
{
return UsbForwarding::Ui::autoDevicesContain(list, identity);
}

QStringList UsbForwardingBackend::autoDevicesWith(const QStringList &list, const QString &identity, bool on) const
{
return UsbForwarding::Ui::autoDevicesWith(list, identity, on);
}

void UsbForwardingBackend::setBusy(bool busy)
{
if (m_Busy == busy) {
Expand Down
12 changes: 12 additions & 0 deletions app/backend/usbforwardingbackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <QObject>
#include <QString>
#include <QStringList>
#include <QVariantMap>
#include <QVariantList>
#include <functional>
#include <memory>
Expand Down Expand Up @@ -86,9 +88,19 @@ class UsbForwardingBackend : public QObject
~UsbForwardingBackend() override;

Q_INVOKABLE void refresh();
// Kills and deletes a usbipd state probe that is still running. Called on
// application quit and by the destructor.
void shutdown();
Q_INVOKABLE void bind(const QString &busId);
Q_INVOKABLE void unbind(const QString &busId, const QString &persistedGuid);

// Helpers for the settings list (see usbforwardinguilogic.h).
Q_INVOKABLE QString shareStateText(const QVariantMap &device) const;
Q_INVOKABLE bool canShare(const QVariantMap &device) const;
Q_INVOKABLE bool canStopSharing(const QVariantMap &device) const;
Q_INVOKABLE bool autoDevicesContain(const QStringList &list, const QString &identity) const;
Q_INVOKABLE QStringList autoDevicesWith(const QStringList &list, const QString &identity, bool on) const;

QVariantList devices() const { return m_Devices; }
bool busy() const { return m_Busy; }
QString error() const { return m_Error; }
Expand Down
40 changes: 40 additions & 0 deletions app/backend/usbforwardingenvironment.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Derived from qiin2333/moonlight-qt (GPL-3.0).

#include "usbforwardingenvironment.h"
#include "usbforwardinguilogic.h"

#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
Expand Down Expand Up @@ -246,9 +248,42 @@ UsbForwardingEnvironment::UsbForwardingEnvironment(QObject *parent)
{
}

UsbForwardingEnvironment::~UsbForwardingEnvironment()
{
shutdown();
}

void UsbForwardingEnvironment::shutdown()
{
const QList<QProcess *> probes = findChildren<QProcess *>(QString(), Qt::FindDirectChildrenOnly);
for (QProcess *probe : probes) {
probe->disconnect();
if (probe->state() != QProcess::NotRunning) {
probe->kill();
probe->waitForFinished(1000);
}
delete probe;
}
if (m_Checking) {
m_Checking = false;
m_State = CheckFailed;
emit checkingChanged();
emit stateChanged();
}
}

UsbForwardingEnvironment* UsbForwardingEnvironment::get()
{
// A function-local static is destroyed after the application object is gone,
// so a probe still running at exit is stopped from aboutToQuit instead.
static UsbForwardingEnvironment environment;
static const bool wired = [] {
if (QCoreApplication *app = QCoreApplication::instance()) {
QObject::connect(app, &QCoreApplication::aboutToQuit, &environment, [] { environment.shutdown(); });
}
return true;
}();
Q_UNUSED(wired)
return &environment;
}

Expand Down Expand Up @@ -517,6 +552,11 @@ UsbForwardingEnvironment::State UsbForwardingEnvironment::probeServices()
#endif
}

QString UsbForwardingEnvironment::statusText() const
{
return UsbForwarding::Ui::environmentStatusText(m_State, m_Version);
}

QString UsbForwardingEnvironment::readinessError(State state)
{
switch (state) {
Expand Down
9 changes: 9 additions & 0 deletions app/backend/usbforwardingenvironment.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class UsbForwardingEnvironment : public QObject
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(QString usbipdVersion READ usbipdVersion NOTIFY stateChanged)
Q_PROPERTY(bool checking READ checking NOTIFY checkingChanged)
Q_PROPERTY(bool ready READ ready NOTIFY stateChanged)
Q_PROPERTY(QString statusText READ statusText NOTIFY stateChanged)

public:
enum State
Expand Down Expand Up @@ -80,10 +82,17 @@ class UsbForwardingEnvironment : public QObject
static QString readinessError(State state);

Q_INVOKABLE void refresh();
// Kills and deletes any probe that is still running. Called on application
// quit (and by the destructor), so no QProcess outlives the application.
void shutdown();
~UsbForwardingEnvironment() override;

State state() const { return m_State; }
QString usbipdVersion() const { return m_Version; }
bool checking() const { return m_Checking; }
bool ready() const { return m_State == Ready; }
// Plain text for the settings page (see usbforwardinguilogic.h).
QString statusText() const;

signals:
void stateChanged();
Expand Down
56 changes: 47 additions & 9 deletions app/backend/usbforwardingmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,19 +458,42 @@ class Engine final : public QObject
});
}

/* Asks which devices usbipd is sharing. The lister can wait on a process
* for seconds, so it runs on a pool thread, never on this event loop:
* stop() must not wait for it. A late answer finds no engine, or an id
* that is no longer expected, and is dropped. */
void reconcile()
{
QList<SharedDevice> shared;
try {
if (m_Seams.listDevices) {
shared = m_Seams.listDevices();
}
} catch (...) {
shared.clear();
}
if (!m_Running || m_Paused) {
return; // stopped or paused from inside the device lister
return;
}
const quint64 id = ++m_ListSeq;
m_ExpectedList = id;
const DeviceLister lister = m_Seams.listDevices;
const std::shared_ptr<Link> link = m_Link;
QThreadPool::globalInstance()->start([lister, link, id] {
QList<SharedDevice> shared;
try {
if (lister) {
shared = lister();
}
} catch (...) {
shared.clear();
}
std::lock_guard<std::mutex> lock(link->mutex);
if (Engine *engine = link->engine) {
QMetaObject::invokeMethod(
engine, [engine, id, shared] { engine->onListed(id, shared); }, Qt::QueuedConnection);
}
});
}

void onListed(quint64 id, const QList<SharedDevice> &shared)
{
if (!m_Running || m_Paused || id != m_ExpectedList) {
return; // stale: stopped, paused, or a newer listing replaced it
}
m_ExpectedList = 0;
/* NotShared rows hold no resources: rebuild them from the current list. */
m_Devs.erase(std::remove_if(m_Devs.begin(), m_Devs.end(),
[](const std::unique_ptr<Dev> &d) { return d->state == DeviceState::NotShared; }),
Expand Down Expand Up @@ -640,6 +663,7 @@ class Engine final : public QObject
void teardown()
{
m_ExpectedFetch = 0;
m_ExpectedList = 0;
m_FetchTimer->stop();
for (auto &dev : m_Devs) {
if (dev->retry != nullptr) {
Expand Down Expand Up @@ -678,6 +702,8 @@ class Engine final : public QObject
std::vector<std::unique_ptr<TunnelHandle>> m_Graveyard;
quint64 m_FetchSeq = 0;
quint64 m_ExpectedFetch = 0;
quint64 m_ListSeq = 0;
quint64 m_ExpectedList = 0;
int m_CapFailures = 0;
int m_Rechecks = 0;
bool m_Started = false;
Expand Down Expand Up @@ -725,12 +751,23 @@ Snapshot Manager::snapshot() const
return m_Shared->snapshot;
}

void Manager::abandonListing() noexcept
{
try {
if (m_Seams.abandonListing) {
m_Seams.abandonListing();
}
} catch (...) {
}
}

void Manager::teardownLocked()
{
if (!m_Runtime) {
return;
}
m_Running = false;
abandonListing();
if (m_Runtime->thread.isRunning()) {
Engine *engine = m_Runtime->engine;
QMetaObject::invokeMethod(engine, [engine] { engine->stopAll(); }, Qt::BlockingQueuedConnection);
Expand Down Expand Up @@ -788,6 +825,7 @@ void Manager::stop() noexcept
QThread *worker = m_WorkerThread.load();
if (worker != nullptr && QThread::currentThread() == worker) {
m_Running = false;
abandonListing();
static_cast<Engine *>(m_WorkerEngine.load())->requestStop();
return;
}
Expand Down
5 changes: 5 additions & 0 deletions app/backend/usbforwardingmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ struct ManagerSeams
DeviceLister listDevices;
/* Optional: defaults to the real UsbForwarding::Tunnel. */
TunnelFactory createTunnel;
/* Optional. Called by stop() before it joins the worker, from any thread:
* a lister that is waiting on something (a process) must give up and
* return soon. stop() never waits for a lister either way. */
std::function<void()> abandonListing;
};

struct ManagerOptions
Expand Down Expand Up @@ -218,6 +222,7 @@ class Manager final : public QObject
struct Runtime;
void publishLocal(State state);
void teardownLocked();
void abandonListing() noexcept;

ManagerSeams m_Seams;
ManagerOptions m_Options;
Expand Down
Loading
Loading