/* * Copyright (C) 2016 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "TopLevelSurfaceList.h" // unity-api #include #include #include #include Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg) #define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__ using namespace unity::shell::application; TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) : QAbstractListModel(parent) { DEBUG_MSG << "()"; } TopLevelSurfaceList::~TopLevelSurfaceList() { DEBUG_MSG << "()"; } int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? m_surfaceList.size() : 0; } QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const { if (index.row() < 0 || index.row() >= m_surfaceList.size()) return QVariant(); if (role == SurfaceRole) { MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface; return QVariant::fromValue(surface); } else if (role == ApplicationRole) { return QVariant::fromValue(m_surfaceList.at(index.row()).application); } else if (role == IdRole) { return QVariant::fromValue(m_surfaceList.at(index.row()).id); } else { return QVariant(); } } void TopLevelSurfaceList::raise(MirSurfaceInterface *surface) { if (!surface) return; DEBUG_MSG << "(MirSurface[" << (void*)surface << "])"; int i = indexOf(surface); if (i != -1) { raiseId(m_surfaceList.at(i).id); } } void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application) { DEBUG_MSG << "(" << application->appId() << ")"; appendSurfaceHelper(nullptr, application); } void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application) { Q_ASSERT(surface != nullptr); bool filledPlaceholder = false; for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) { ModelEntry &entry = m_surfaceList[i]; if (entry.application == application && entry.surface == nullptr) { entry.surface = surface; connectSurface(surface); DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", filling out placeholder. after: " << toString(); Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector() << SurfaceRole); filledPlaceholder = true; } } if (!filledPlaceholder) { DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row"; appendSurfaceHelper(surface, application); } } void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application) { if (m_modelState == IdleState) { m_modelState = InsertingState; beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/); } else { Q_ASSERT(m_modelState == ResettingState); // No point in signaling anything if we're resetting the whole model } int id = generateId(); m_surfaceList.append(ModelEntry(surface, application, id)); if (surface) { connectSurface(surface); } if (m_modelState == InsertingState) { endInsertRows(); Q_EMIT countChanged(); Q_EMIT listChanged(); m_modelState = IdleState; } DEBUG_MSG << " after " << toString(); } void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface) { connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){ if (!live) { onSurfaceDied(surface); } }); connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); }); } void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface) { int i = indexOf(surface); if (i == -1) { return; } auto application = m_surfaceList[i].application; // can't be starting if it already has a surface Q_ASSERT(application->state() != ApplicationInfoInterface::Starting); if (application->state() == ApplicationInfoInterface::Running) { m_surfaceList[i].removeOnceSurfaceDestroyed = true; } else { // assume it got killed by the out-of-memory daemon. // // So leave entry in the model and only remove its surface, so shell can display a screenshot // in its place. m_surfaceList[i].removeOnceSurfaceDestroyed = false; } } void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface) { int i = indexOf(surface); if (i == -1) { return; } if (m_surfaceList[i].removeOnceSurfaceDestroyed) { removeAt(i); } else { m_surfaceList[i].surface = nullptr; Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector() << SurfaceRole); DEBUG_MSG << " Removed surface from entry. After: " << toString(); } } void TopLevelSurfaceList::removeAt(int index) { if (m_modelState == IdleState) { beginRemoveRows(QModelIndex(), index, index); m_modelState = RemovingState; } else { Q_ASSERT(m_modelState == ResettingState); // No point in signaling anything if we're resetting the whole model } m_surfaceList.removeAt(index); if (m_modelState == RemovingState) { endRemoveRows(); Q_EMIT countChanged(); Q_EMIT listChanged(); m_modelState = IdleState; } DEBUG_MSG << " after " << toString(); } int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface) { for (int i = 0; i < m_surfaceList.count(); ++i) { if (m_surfaceList.at(i).surface == surface) { return i; } } return -1; } void TopLevelSurfaceList::move(int from, int to) { if (from == to) return; DEBUG_MSG << " from=" << from << " to=" << to; if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) { QModelIndex parent; /* When moving an item down, the destination index needs to be incremented by one, as explained in the documentation: http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */ Q_ASSERT(m_modelState == IdleState); m_modelState = MovingState; beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0)); m_surfaceList.move(from, to); endMoveRows(); Q_EMIT listChanged(); m_modelState = IdleState; DEBUG_MSG << " after " << toString(); } } MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const { if (index >=0 && index < m_surfaceList.count()) { return m_surfaceList[index].surface; } else { return nullptr; } } ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const { if (index >=0 && index < m_surfaceList.count()) { return m_surfaceList[index].application; } else { return nullptr; } } int TopLevelSurfaceList::idAt(int index) const { if (index >=0 && index < m_surfaceList.count()) { return m_surfaceList[index].id; } else { return 0; } } int TopLevelSurfaceList::indexForId(int id) const { for (int i = 0; i < m_surfaceList.count(); ++i) { if (m_surfaceList[i].id == id) { return i; } } return -1; } void TopLevelSurfaceList::doRaiseId(int id) { int fromIndex = indexForId(id); if (fromIndex != -1) { move(fromIndex, 0 /* toIndex */); } } void TopLevelSurfaceList::raiseId(int id) { if (m_modelState == IdleState) { DEBUG_MSG << "(id=" << id << ") - do it now."; doRaiseId(id); } else { DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop."; // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts // if we perform yet another model change straight away. // // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says // the index is definitely within bounds. QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id)); } } int TopLevelSurfaceList::generateId() { int id = m_nextId; m_nextId = nextFreeId(m_nextId + 1); Q_EMIT nextIdChanged(); return id; } int TopLevelSurfaceList::nextFreeId(int candidateId) { if (candidateId > m_maxId) { return nextFreeId(1); } else { if (indexForId(candidateId) == -1) { // it's indeed free return candidateId; } else { return nextFreeId(candidateId + 1); } } } QString TopLevelSurfaceList::toString() { QString str; for (int i = 0; i < m_surfaceList.count(); ++i) { auto item = m_surfaceList.at(i); QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)") .arg(i) .arg(item.application->appId()) .arg((qintptr)item.surface, 0, 16) .arg(item.id); if (i > 0) { str.append(","); } str.append(itemStr); } return str; } void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application) { DEBUG_MSG << "(" << application->appId() << ")"; Q_ASSERT(!m_applications.contains(application)); m_applications.append(application); MirSurfaceListInterface *surfaceList = application->surfaceList(); if (application->state() != ApplicationInfoInterface::Stopped) { if (surfaceList->count() == 0) { appendPlaceholder(application); } else { for (int i = 0; i < surfaceList->count(); ++i) { appendSurface(surfaceList->get(i), application); } } } connect(surfaceList, &QAbstractItemModel::rowsInserted, this, [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last) { for (int i = last; i >= first; --i) { this->appendSurface(surfaceList->get(i), application); } }); } void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application) { DEBUG_MSG << "(" << application->appId() << ")"; Q_ASSERT(m_applications.contains(application)); MirSurfaceListInterface *surfaceList = application->surfaceList(); disconnect(surfaceList, 0, this, 0); Q_ASSERT(m_modelState == IdleState); m_modelState = RemovingState; int i = 0; while (i < m_surfaceList.count()) { if (m_surfaceList.at(i).application == application) { beginRemoveRows(QModelIndex(), i, i); m_surfaceList.removeAt(i); endRemoveRows(); Q_EMIT countChanged(); Q_EMIT listChanged(); } else { ++i; } } m_modelState = IdleState; DEBUG_MSG << " after " << toString(); m_applications.removeAll(application); } QAbstractListModel *TopLevelSurfaceList::applicationsModel() const { return m_applicationsModel; } void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value) { if (m_applicationsModel == value) { return; } DEBUG_MSG << "(" << value << ")"; Q_ASSERT(m_modelState == IdleState); m_modelState = ResettingState; beginResetModel(); if (m_applicationsModel) { m_surfaceList.clear(); m_applications.clear(); disconnect(m_applicationsModel, 0, this, 0); } m_applicationsModel = value; if (m_applicationsModel) { findApplicationRole(); connect(m_applicationsModel, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &/*parent*/, int first, int last) { for (int i = first; i <= last; ++i) { auto application = getApplicationFromModelAt(i); addApplication(application); } }); connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, [this](const QModelIndex &/*parent*/, int first, int last) { for (int i = first; i <= last; ++i) { auto application = getApplicationFromModelAt(i); removeApplication(application); } }); for (int i = 0; i < m_applicationsModel->rowCount(); ++i) { auto application = getApplicationFromModelAt(i); addApplication(application); } } endResetModel(); m_modelState = IdleState; } ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index) { QModelIndex modelIndex = m_applicationsModel->index(index); QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole); // variant.value() returns null for some reason. return static_cast(variant.value()); } void TopLevelSurfaceList::findApplicationRole() { QHash namesHash = m_applicationsModel->roleNames(); m_applicationRole = -1; for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) { if (i.value() == "application") { m_applicationRole = i.key(); } } if (m_applicationRole == -1) { qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role."); } }