mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 22:33:08 +00:00
Fix the model screen filtering not working properly, and account for case when opened window is already active.
272 lines
7.8 KiB
C++
272 lines
7.8 KiB
C++
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "startupfeedbackmodel.h"
|
|
#include "windowlistener.h"
|
|
|
|
constexpr int STARTUP_FEEDBACK_TIMEOUT_MS = 8000;
|
|
|
|
StartupFeedback::StartupFeedback(QObject *parent,
|
|
QString iconName,
|
|
QString title,
|
|
QString storageId,
|
|
qreal iconStartX,
|
|
qreal iconStartY,
|
|
qreal iconSize,
|
|
int screen)
|
|
: QObject{parent}
|
|
, m_iconName{iconName}
|
|
, m_title{title}
|
|
, m_storageId{storageId}
|
|
, m_iconStartX{iconStartX}
|
|
, m_iconStartY{iconStartY}
|
|
, m_iconSize{iconSize}
|
|
, m_screen{screen}
|
|
, m_timeoutTimer{new QTimer{this}}
|
|
{
|
|
connect(m_timeoutTimer, &QTimer::timeout, this, &StartupFeedback::timeout);
|
|
}
|
|
|
|
QString StartupFeedback::iconName() const
|
|
{
|
|
return m_iconName;
|
|
}
|
|
|
|
QString StartupFeedback::title() const
|
|
{
|
|
return m_title;
|
|
}
|
|
|
|
QString StartupFeedback::storageId() const
|
|
{
|
|
return m_storageId;
|
|
}
|
|
|
|
qreal StartupFeedback::iconStartX() const
|
|
{
|
|
return m_iconStartX;
|
|
}
|
|
|
|
qreal StartupFeedback::iconStartY() const
|
|
{
|
|
return m_iconStartY;
|
|
}
|
|
|
|
qreal StartupFeedback::iconSize() const
|
|
{
|
|
return m_iconSize;
|
|
}
|
|
|
|
int StartupFeedback::screen() const
|
|
{
|
|
return m_screen;
|
|
}
|
|
|
|
QString StartupFeedback::windowUuid() const
|
|
{
|
|
return m_windowUuid;
|
|
}
|
|
|
|
void StartupFeedback::setWindowUuid(QString uuid)
|
|
{
|
|
m_windowUuid = uuid;
|
|
}
|
|
|
|
void StartupFeedback::startTimeoutTimer()
|
|
{
|
|
// Timeout of 5 seconds before closing
|
|
m_timeoutTimer->start(STARTUP_FEEDBACK_TIMEOUT_MS);
|
|
}
|
|
|
|
StartupFeedbackModel::StartupFeedbackModel(QObject *parent)
|
|
: QAbstractListModel{parent}
|
|
{
|
|
connect(WindowListener::instance(), &WindowListener::windowCreated, this, &StartupFeedbackModel::onWindowOpened);
|
|
connect(WindowListener::instance(), &WindowListener::plasmaWindowCreated, this, &StartupFeedbackModel::onPlasmaWindowOpened);
|
|
connect(WindowListener::instance(), &WindowListener::activeWindowChanged, this, &StartupFeedbackModel::onActiveWindowChanged);
|
|
}
|
|
|
|
void StartupFeedbackModel::addApp(StartupFeedback *startupFeedback)
|
|
{
|
|
beginInsertRows(QModelIndex{}, m_list.size(), m_list.size());
|
|
|
|
m_list.append(startupFeedback);
|
|
updateActiveWindowIsStartupFeedback();
|
|
|
|
startupFeedback->startTimeoutTimer();
|
|
|
|
connect(startupFeedback, &StartupFeedback::timeout, this, [this, startupFeedback]() {
|
|
int index = m_list.indexOf(startupFeedback);
|
|
if (index == -1) {
|
|
return;
|
|
}
|
|
|
|
beginRemoveRows(QModelIndex{}, index, index);
|
|
m_list.removeAt(index);
|
|
updateActiveWindowIsStartupFeedback();
|
|
endRemoveRows();
|
|
});
|
|
|
|
// Prepare state for active window being startupfeedback early, otherwise we have a race condition between
|
|
// the Plasma window opening and the visual (causes panels to flash background color)
|
|
m_activeWindowIsStartupFeedback = true;
|
|
Q_EMIT activeWindowIsStartupFeedbackChanged();
|
|
|
|
endInsertRows();
|
|
}
|
|
|
|
int StartupFeedbackModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent)
|
|
return m_list.count();
|
|
}
|
|
|
|
QVariant StartupFeedbackModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
|
|
auto delegate = m_list[index.row()];
|
|
|
|
switch (role) {
|
|
case DelegateRole:
|
|
return QVariant::fromValue(delegate);
|
|
case ScreenRole:
|
|
return delegate->screen();
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
QHash<int, QByteArray> StartupFeedbackModel::roleNames() const
|
|
{
|
|
return {{DelegateRole, QByteArrayLiteral("delegate")}, {ScreenRole, QByteArrayLiteral("screen")}};
|
|
}
|
|
|
|
bool StartupFeedbackModel::activeWindowIsStartupFeedback() const
|
|
{
|
|
return m_activeWindowIsStartupFeedback;
|
|
}
|
|
|
|
void StartupFeedbackModel::onWindowOpened(KWayland::Client::PlasmaWindow *window)
|
|
{
|
|
if (!window) {
|
|
return;
|
|
}
|
|
|
|
QString appId = window->appId();
|
|
|
|
int indexToRemove = 0;
|
|
|
|
// storageId may get suffixed with ".desktop", check for that
|
|
const QString suffix = QStringLiteral(".desktop");
|
|
|
|
// Remove StartupFeedback when the respective window is created
|
|
// NOTE: often, the window "appId" does not match the actual app storageId in third-party apps, so we can't rely on this.
|
|
for (int i = 0; i < m_list.size(); ++i) {
|
|
auto *startupFeedback = m_list[i];
|
|
if (startupFeedback->storageId() == appId || startupFeedback->storageId() == appId + suffix) {
|
|
indexToRemove = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no windows were matched, the oldest StartupFeedback (since indexToRemove = 0)
|
|
// NOTE: This is our fallback if the window "appId" doesn't match anything.
|
|
|
|
if (m_list.size() > indexToRemove) {
|
|
StartupFeedback *feedbackToRemove = m_list[indexToRemove];
|
|
|
|
// Function to remove the startup feedback from the model
|
|
auto removeFunction = [this, window, feedbackToRemove]() {
|
|
if (!window->isActive()) {
|
|
return;
|
|
}
|
|
|
|
int indexToRemove = m_list.indexOf(feedbackToRemove);
|
|
|
|
if (indexToRemove != -1) {
|
|
beginRemoveRows(QModelIndex{}, indexToRemove, indexToRemove);
|
|
|
|
m_list[indexToRemove]->deleteLater();
|
|
m_list.removeAt(indexToRemove);
|
|
updateActiveWindowIsStartupFeedback();
|
|
|
|
endRemoveRows();
|
|
}
|
|
|
|
window->disconnect(this);
|
|
};
|
|
|
|
// Only delete StartupFeedback once the window becomes active
|
|
// -> There is a potential gap of time between when a window is created and when it is actually visible/active
|
|
if (window->isActive()) {
|
|
removeFunction();
|
|
} else {
|
|
connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, removeFunction);
|
|
}
|
|
}
|
|
}
|
|
|
|
void StartupFeedbackModel::onPlasmaWindowOpened(KWayland::Client::PlasmaWindow *window)
|
|
{
|
|
// Fill in the respective StartupFeedback with the window uuid
|
|
// Heuristic: window title should match
|
|
for (auto *startupFeedback : m_list) {
|
|
if (startupFeedback->title() == window->title() && startupFeedback->windowUuid().isEmpty()) {
|
|
startupFeedback->setWindowUuid(window->uuid());
|
|
}
|
|
}
|
|
|
|
// Update variable that depends on window uuid
|
|
updateActiveWindowIsStartupFeedback();
|
|
}
|
|
|
|
void StartupFeedbackModel::onActiveWindowChanged(KWayland::Client::PlasmaWindow *activeWindow)
|
|
{
|
|
m_activeWindow = activeWindow;
|
|
updateActiveWindowIsStartupFeedback();
|
|
}
|
|
|
|
void StartupFeedbackModel::updateActiveWindowIsStartupFeedback()
|
|
{
|
|
bool isStartupFeedback = false;
|
|
|
|
if (m_activeWindow) {
|
|
// Check if there exists a StartupFeedback window with the same id as the active window
|
|
for (const auto *startupFeedback : m_list) {
|
|
if (startupFeedback->windowUuid() == m_activeWindow->uuid()) {
|
|
isStartupFeedback = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isStartupFeedback != m_activeWindowIsStartupFeedback) {
|
|
m_activeWindowIsStartupFeedback = isStartupFeedback;
|
|
Q_EMIT activeWindowIsStartupFeedbackChanged();
|
|
}
|
|
}
|
|
|
|
StartupFeedbackFilterModel::StartupFeedbackFilterModel(QObject *parent)
|
|
: QSortFilterProxyModel(parent)
|
|
{
|
|
setFilterRole(StartupFeedbackModel::ScreenRole);
|
|
}
|
|
|
|
StartupFeedbackModel *StartupFeedbackFilterModel::startupFeedbackModel() const
|
|
{
|
|
return m_startupFeedbackModel;
|
|
}
|
|
|
|
void StartupFeedbackFilterModel::setStartupFeedbackModel(StartupFeedbackModel *startupFeedbackModel)
|
|
{
|
|
if (startupFeedbackModel == m_startupFeedbackModel) {
|
|
return;
|
|
}
|
|
|
|
m_startupFeedbackModel = startupFeedbackModel;
|
|
setSourceModel(m_startupFeedbackModel);
|
|
Q_EMIT startupFeedbackModelChanged();
|
|
}
|