Introduces a new `core/windows` module (`Nebula.Windows`) with `WindowService`, window/workspace models, and a Sway IPC backend for live window tracking and control (focus, close, move/resize, snap, maximize/restore, minimize, workspace switch). The desktop shell now links this module, uses compositor focus for top-bar active app text, and adds an Open Windows section in the launcher with focus/close actions. Also updates application metadata handling to parse `StartupWMClass` and map window `app_id`/class to friendly names and icons, and revises Sway config/docs for the new floating-first Desktop 0.2 behavior with temporary compositor-provided decorations.
145 lines
3.8 KiB
C++
145 lines
3.8 KiB
C++
#include "ApplicationService.hpp"
|
|
|
|
#include "DesktopEntry.hpp"
|
|
|
|
#include <QDir>
|
|
#include <QDirIterator>
|
|
#include <QFileInfo>
|
|
#include <QSet>
|
|
#include <QVector>
|
|
#include <QtGlobal>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
ApplicationService *s_instance = nullptr;
|
|
}
|
|
|
|
ApplicationService::ApplicationService(QObject *parent)
|
|
: QObject(parent)
|
|
, m_model(new ApplicationModel(this))
|
|
, m_filtered(new ApplicationFilterModel(this))
|
|
{
|
|
if (!s_instance)
|
|
s_instance = this;
|
|
|
|
m_filtered->setSourceModel(m_model);
|
|
m_filtered->setSortRole(ApplicationModel::NameRole);
|
|
m_filtered->sort(0, Qt::AscendingOrder);
|
|
|
|
connect(m_model, &ApplicationModel::countChanged, this, &ApplicationService::countChanged);
|
|
connect(m_filtered, &ApplicationFilterModel::filterChanged, this, &ApplicationService::filterChanged);
|
|
|
|
refresh();
|
|
}
|
|
|
|
ApplicationService::~ApplicationService()
|
|
{
|
|
if (s_instance == this)
|
|
s_instance = nullptr;
|
|
}
|
|
|
|
ApplicationService *ApplicationService::instance()
|
|
{
|
|
return s_instance;
|
|
}
|
|
|
|
ApplicationService *ApplicationService::create(QQmlEngine *engine, QJSEngine *)
|
|
{
|
|
auto *service = new ApplicationService(engine);
|
|
QQmlEngine::setObjectOwnership(service, QQmlEngine::CppOwnership);
|
|
return service;
|
|
}
|
|
|
|
ApplicationModel *ApplicationService::model() const
|
|
{
|
|
return m_model;
|
|
}
|
|
|
|
ApplicationFilterModel *ApplicationService::filteredModel() const
|
|
{
|
|
return m_filtered;
|
|
}
|
|
|
|
QString ApplicationService::filter() const
|
|
{
|
|
return m_filtered->filter();
|
|
}
|
|
|
|
void ApplicationService::setFilter(const QString &filter)
|
|
{
|
|
m_filtered->setFilter(filter);
|
|
}
|
|
|
|
int ApplicationService::count() const
|
|
{
|
|
return m_model->count();
|
|
}
|
|
|
|
bool ApplicationService::launch(const QString &desktopId) const
|
|
{
|
|
const DesktopEntry *entry = m_model->entryById(desktopId);
|
|
if (!entry) {
|
|
qWarning("nebula: unknown application %s", qPrintable(desktopId));
|
|
return false;
|
|
}
|
|
return entry->launch();
|
|
}
|
|
|
|
QString ApplicationService::displayNameForWindow(const QString &appId, const QString &windowClass) const
|
|
{
|
|
const DesktopEntry *entry = m_model->entryMatchingWindow(appId, windowClass);
|
|
if (entry && !entry->name.isEmpty())
|
|
return entry->name;
|
|
return {};
|
|
}
|
|
|
|
QString ApplicationService::iconNameForWindow(const QString &appId, const QString &windowClass) const
|
|
{
|
|
const DesktopEntry *entry = m_model->entryMatchingWindow(appId, windowClass);
|
|
if (entry)
|
|
return entry->icon;
|
|
return {};
|
|
}
|
|
|
|
void ApplicationService::refresh()
|
|
{
|
|
QVector<DesktopEntry> entries;
|
|
QSet<QString> seenIds;
|
|
|
|
const QStringList directories = applicationDirectories();
|
|
qInfo("nebula: scanning %d application director%s",
|
|
int(directories.size()),
|
|
directories.size() == 1 ? "y" : "ies");
|
|
|
|
for (const QString &directory : directories) {
|
|
QDir dir(directory);
|
|
if (!dir.exists())
|
|
continue;
|
|
|
|
QDirIterator iterator(directory, {QStringLiteral("*.desktop")}, QDir::Files, QDirIterator::Subdirectories);
|
|
while (iterator.hasNext()) {
|
|
const QString filePath = iterator.next();
|
|
QString relative = QDir(directory).relativeFilePath(filePath);
|
|
relative.replace(QLatin1Char('/'), QLatin1Char('-'));
|
|
const QString desktopId = relative;
|
|
|
|
if (seenIds.contains(desktopId))
|
|
continue;
|
|
|
|
seenIds.insert(desktopId);
|
|
DesktopEntry entry = parseDesktopEntryFile(filePath, desktopId);
|
|
if (entry.name.isEmpty())
|
|
entry.name = QFileInfo(filePath).completeBaseName();
|
|
if (!entry.isLauncherVisible())
|
|
continue;
|
|
|
|
entries.append(std::move(entry));
|
|
}
|
|
}
|
|
|
|
qInfo("nebula: discovered %d launcher application%s",
|
|
int(entries.size()),
|
|
entries.size() == 1 ? "" : "s");
|
|
m_model->setEntries(std::move(entries));
|
|
}
|