Files
logos-app/app/ShellHostAdapter.cpp
Khushboo Mehta c4976bb7dc feat(intents): app-to-app intents, on the host/shell split
app/            IntentRegistry, IntentBroker, IntentBridgeAdapter,
                  UIPluginPresenter, ShellIntentEndpoint.
                  IntentBridgeAdapter includes LogosQmlBridge.h, so it must
                  be host-side — the shell is not allowed to see logos
                  runtime types.
  app/interfaces/ ShellSections.h, following the InstallEnums.h precedent
                  for a QML-registered enum both sides need.
  src/            The ContentViews.qml handler and MainContainer's half of
                  the presentation seam.

An app calls logos.request(); PluginLoader has already attached its bridge
at the finishUiQmlLoad funnel, before setSource(), because setSource is
where QML runs and an app requesting from Component.onCompleted would
otherwise be an unknown bridge. The broker checks the caller's own `uses`
declaration, resolves a provider, mints a SEPARATE dispatch id the
requester never sees, has the presenter load and present the provider, and
routes the answer back to that requester alone.

The chooser is raised on every ambiguous request. Remembering a pick
("always use this app") is deliberately not shipped: it needs a settings
screen to review and revoke from, and a way to mark intents that must
never be remembered. Recorded as a follow-up in docs/app-to-app-intents.md.

The shell is a provider like any other for dispatch, and emphatically not
an app: it has no widget and must never be loaded. IntentRegistry owns
that fact — it already tracks the shell's module name to refuse disk
records claiming it — and IntentBroker asks rather than keeping a copy.
2026-08-28 15:34:05 +02:00

88 lines
2.5 KiB
C++

#include "ShellHostAdapter.h"
#include "MainUIBackend.h"
#include <QWidget>
ShellHostAdapter::ShellHostAdapter(MainUIBackend* backend, QObject* parent)
: QObject(parent)
, m_backend(backend)
{
if (!m_backend) {
qFatal("ShellHostAdapter requires a MainUIBackend instance");
}
// Signal → observer translation, guarded on m_observer: the shell detaches
// in destroyShell(), but PluginLoader's QTimer::singleShot(0, ...) and a 30s
// ViewModuleHost timeout can still deliver a backend signal afterwards.
connect(m_backend, &MainUIBackend::currentActiveSectionIndexChanged, this, [this]() {
if (!m_observer || !m_backend) return;
m_observer->onSectionIndexChanged(m_backend->currentActiveSectionIndex());
});
connect(m_backend, &MainUIBackend::navigateToApps, this, [this]() {
if (!m_observer) return;
m_observer->onNavigateToApps();
});
connect(m_backend, &MainUIBackend::pluginWindowRequested, this,
[this](QWidget* widget, const QString& title) {
if (!m_observer) return;
m_observer->onPluginWindowRequested(widget, title);
});
connect(m_backend, &MainUIBackend::pluginWindowRemoveRequested, this,
[this](QWidget* widget) {
if (!m_observer) return;
m_observer->onPluginWindowRemoveRequested(widget);
});
connect(m_backend, &MainUIBackend::presentAppRequested, this,
[this](QWidget* widget) {
if (!m_observer) return;
m_observer->onPresentAppRequested(widget);
});
}
ShellHostAdapter::~ShellHostAdapter() = default;
QObject* ShellHostAdapter::backendObject()
{
return m_backend.data();
}
int ShellHostAdapter::currentSectionIndex() const
{
return m_backend ? m_backend->currentActiveSectionIndex() : 0;
}
void ShellHostAdapter::setCurrentSectionIndex(int index)
{
if (m_backend) m_backend->setCurrentActiveSectionIndex(index);
}
void ShellHostAdapter::loadUiModule(const QString& name)
{
if (m_backend) m_backend->loadUiModule(name);
}
void ShellHostAdapter::unloadUiModule(const QString& name)
{
if (m_backend) m_backend->unloadUiModule(name);
}
void ShellHostAdapter::setCurrentVisibleApp(const QString& name)
{
if (m_backend) m_backend->setCurrentVisibleApp(name);
}
QString ShellHostAdapter::displayNameFor(const QString& name) const
{
return m_backend ? m_backend->displayNameFor(name) : name;
}
void ShellHostAdapter::setObserver(IShellObserver* observer)
{
m_observer = observer;
}