Files
logos-basecamp/app/ShellHostAdapter.cpp
T
Dario Gabriel Lipicar 9c3d023062 feat(shell): put the UI shell behind IShellHost, and order its shutdown
The shell held the host's objects: MainContainer owned MainUIBackend, took a
LogosAPI* and a QtLogosCore*, and connected to backend signals by concrete
type. Nothing stopped it minting identities or reading the token store.

Three headers in app/interfaces/, on the include path both targets share, so
there is exactly one copy and source drift is impossible:

  * IShellHost     -- 8 operations the shell may perform
  * IShellObserver -- 5 notifications the host may deliver
  * IShellView     -- how the host builds and tears down the shell

Only QObject*, QWidget* and Qt value types cross. MainContainer holds one
IShellHost* and nothing else; QML reaches the backend as an opaque QObject*
via backendObject(), resolved through the metaobject, so no host C++ type has
to be nameable by the shell.

Ownership inverts: Window owns MainUIBackend, ShellHostAdapter and
MainShellView; the shell borrows. Teardown stops being a consequence of
construction order and becomes a stated contract:

  1. beginShutdown() unmounts in-process UI plugin widgets WHILE the shell's
     tree is intact -- they are docked inside it
  2. destroyShell() detaches the observer, then deletes the shell
  3. the backend goes, tearing down Package -> UIPlugin -> Core
  4. main() destroys the core facade

Every observer forward is null-guarded: PluginLoader dispatches through
QTimer::singleShot(0, ...) and a 30s ViewModuleHost timeout, so a callback can
land after teardown starts, and QPointer cannot help -- IShellObserver is not
a QObject. UIPluginManager's plugin-widget maps become QPointer for the
mirror-image reason: those widgets are docked inside the shell, so its Qt
parent can destroy them without going through unloadUiModule.

IComponent is untouched and still serves the third-party legacy widget
plugins PluginLoader loads.
2026-08-22 16:42:13 -03:00

90 lines
2.6 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. Every forward is guarded on m_observer:
// the shell detaches during destroyShell(), but PluginLoader dispatches
// through QTimer::singleShot(0, ...) and a 30s ViewModuleHost timeout, so
// a backend signal can still arrive 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::pluginWindowActivateRequested, this,
[this](QWidget* widget) {
if (!m_observer) return;
m_observer->onPluginWindowActivateRequested(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
{
// Same fallback the shell used to apply inline when the backend was null.
return m_backend ? m_backend->displayNameFor(name) : name;
}
void ShellHostAdapter::setObserver(IShellObserver* observer)
{
m_observer = observer;
}