on 47ffbf2 help sign in
unity8/plugins/WindowManager/TopLevelSurfaceList.cpp
486 lines 15 kB history raw
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17#include "TopLevelSurfaceList.h"
19// unity-api
20#include <unity/shell/application/ApplicationInfoInterface.h>
21#include <unity/shell/application/MirSurfaceInterface.h>
22#include <unity/shell/application/MirSurfaceListInterface.h>
24#include <QMetaObject>
26Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
28#define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
30using namespace unity::shell::application;
32TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
33 QAbstractListModel(parent)
35 DEBUG_MSG << "()";
38TopLevelSurfaceList::~TopLevelSurfaceList()
40 DEBUG_MSG << "()";
43int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
45 return !parent.isValid() ? m_surfaceList.size() : 0;
48QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
50 if (index.row() < 0 || index.row() >= m_surfaceList.size())
51 return QVariant();
53 if (role == SurfaceRole) {
54 MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface;
55 return QVariant::fromValue(surface);
56 } else if (role == ApplicationRole) {
57 return QVariant::fromValue(m_surfaceList.at(index.row()).application);
58 } else if (role == IdRole) {
59 return QVariant::fromValue(m_surfaceList.at(index.row()).id);
60 } else {
61 return QVariant();
62 }
65void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
67 if (!surface)
68 return;
70 DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
72 int i = indexOf(surface);
73 if (i != -1) {
74 raiseId(m_surfaceList.at(i).id);
75 }
78void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
80 DEBUG_MSG << "(" << application->appId() << ")";
82 appendSurfaceHelper(nullptr, application);
85void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
87 Q_ASSERT(surface != nullptr);
89 bool filledPlaceholder = false;
90 for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) {
91 ModelEntry &entry = m_surfaceList[i];
92 if (entry.application == application && entry.surface == nullptr) {
93 entry.surface = surface;
94 connectSurface(surface);
95 DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
96 << ", filling out placeholder. after: " << toString();
97 Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
98 filledPlaceholder = true;
99 }
100 }
102 if (!filledPlaceholder) {
103 DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
104 appendSurfaceHelper(surface, application);
105 }
108void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
110 if (m_modelState == IdleState) {
111 m_modelState = InsertingState;
112 beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/);
113 } else {
114 Q_ASSERT(m_modelState == ResettingState);
115 // No point in signaling anything if we're resetting the whole model
116 }
118 int id = generateId();
119 m_surfaceList.append(ModelEntry(surface, application, id));
120 if (surface) {
121 connectSurface(surface);
122 }
124 if (m_modelState == InsertingState) {
125 endInsertRows();
126 Q_EMIT countChanged();
127 Q_EMIT listChanged();
128 m_modelState = IdleState;
129 }
131 DEBUG_MSG << " after " << toString();
134void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
136 connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool focused){
137 if (focused) {
138 this->raise(surface);
139 }
140 });
141 connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
142 if (!live) {
143 onSurfaceDied(surface);
144 }
145 });
146 connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
149void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
151 int i = indexOf(surface);
152 if (i == -1) {
153 return;
154 }
156 auto application = m_surfaceList[i].application;
158 // can't be starting if it already has a surface
159 Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
161 if (application->state() == ApplicationInfoInterface::Running) {
162 m_surfaceList[i].removeOnceSurfaceDestroyed = true;
163 } else {
164 // assume it got killed by the out-of-memory daemon.
165 //
166 // So leave entry in the model and only remove its surface, so shell can display a screenshot
167 // in its place.
168 m_surfaceList[i].removeOnceSurfaceDestroyed = false;
169 }
172void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
174 int i = indexOf(surface);
175 if (i == -1) {
176 return;
177 }
179 if (m_surfaceList[i].removeOnceSurfaceDestroyed) {
180 removeAt(i);
181 } else {
182 m_surfaceList[i].surface = nullptr;
183 Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
184 DEBUG_MSG << " Removed surface from entry. After: " << toString();
185 }
188void TopLevelSurfaceList::removeAt(int index)
190 if (m_modelState == IdleState) {
191 beginRemoveRows(QModelIndex(), index, index);
192 m_modelState = RemovingState;
193 } else {
194 Q_ASSERT(m_modelState == ResettingState);
195 // No point in signaling anything if we're resetting the whole model
196 }
198 m_surfaceList.removeAt(index);
200 if (m_modelState == RemovingState) {
201 endRemoveRows();
202 Q_EMIT countChanged();
203 Q_EMIT listChanged();
204 m_modelState = IdleState;
205 }
207 DEBUG_MSG << " after " << toString();
210int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
212 for (int i = 0; i < m_surfaceList.count(); ++i) {
213 if (m_surfaceList.at(i).surface == surface) {
214 return i;
215 }
216 }
217 return -1;
220void TopLevelSurfaceList::move(int from, int to)
222 if (from == to) return;
223 DEBUG_MSG << " from=" << from << " to=" << to;
225 if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) {
226 QModelIndex parent;
227 /* When moving an item down, the destination index needs to be incremented
228 by one, as explained in the documentation:
229 http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
231 Q_ASSERT(m_modelState == IdleState);
232 m_modelState = MovingState;
234 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
235 m_surfaceList.move(from, to);
236 endMoveRows();
237 Q_EMIT listChanged();
239 m_modelState = IdleState;
241 DEBUG_MSG << " after " << toString();
242 }
245MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
247 if (index >=0 && index < m_surfaceList.count()) {
248 return m_surfaceList[index].surface;
249 } else {
250 return nullptr;
251 }
254ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
256 if (index >=0 && index < m_surfaceList.count()) {
257 return m_surfaceList[index].application;
258 } else {
259 return nullptr;
260 }
263int TopLevelSurfaceList::idAt(int index) const
265 if (index >=0 && index < m_surfaceList.count()) {
266 return m_surfaceList[index].id;
267 } else {
268 return 0;
269 }
272int TopLevelSurfaceList::indexForId(int id) const
274 for (int i = 0; i < m_surfaceList.count(); ++i) {
275 if (m_surfaceList[i].id == id) {
276 return i;
277 }
278 }
279 return -1;
282void TopLevelSurfaceList::doRaiseId(int id)
284 int fromIndex = indexForId(id);
285 if (fromIndex != -1) {
286 move(fromIndex, 0 /* toIndex */);
287 }
290void TopLevelSurfaceList::raiseId(int id)
292 if (m_modelState == IdleState) {
293 DEBUG_MSG << "(id=" << id << ") - do it now.";
294 doRaiseId(id);
295 } else {
296 DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
297 // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
298 // if we perform yet another model change straight away.
299 //
300 // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
301 // the index is definitely within bounds.
302 QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
303 }
306int TopLevelSurfaceList::generateId()
308 int id = m_nextId;
309 m_nextId = nextFreeId(m_nextId + 1);
310 Q_EMIT nextIdChanged();
311 return id;
314int TopLevelSurfaceList::nextFreeId(int candidateId)
316 if (candidateId > m_maxId) {
317 return nextFreeId(1);
318 } else {
319 if (indexForId(candidateId) == -1) {
320 // it's indeed free
321 return candidateId;
322 } else {
323 return nextFreeId(candidateId + 1);
324 }
325 }
328QString TopLevelSurfaceList::toString()
330 QString str;
331 for (int i = 0; i < m_surfaceList.count(); ++i) {
332 auto item = m_surfaceList.at(i);
334 QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
335 .arg(i)
336 .arg(item.application->appId())
337 .arg((qintptr)item.surface, 0, 16)
338 .arg(item.id);
340 if (i > 0) {
341 str.append(",");
342 }
343 str.append(itemStr);
344 }
345 return str;
348void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
350 DEBUG_MSG << "(" << application->appId() << ")";
351 Q_ASSERT(!m_applications.contains(application));
352 m_applications.append(application);
354 MirSurfaceListInterface *surfaceList = application->surfaceList();
356 if (application->state() != ApplicationInfoInterface::Stopped) {
357 if (surfaceList->count() == 0) {
358 appendPlaceholder(application);
359 } else {
360 for (int i = 0; i < surfaceList->count(); ++i) {
361 appendSurface(surfaceList->get(i), application);
362 }
363 }
364 }
366 connect(surfaceList, &QAbstractItemModel::rowsInserted, this,
367 [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last)
368 {
369 for (int i = last; i >= first; --i) {
370 this->appendSurface(surfaceList->get(i), application);
371 }
372 });
375void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
377 DEBUG_MSG << "(" << application->appId() << ")";
378 Q_ASSERT(m_applications.contains(application));
380 MirSurfaceListInterface *surfaceList = application->surfaceList();
382 disconnect(surfaceList, 0, this, 0);
384 Q_ASSERT(m_modelState == IdleState);
385 m_modelState = RemovingState;
387 int i = 0;
388 while (i < m_surfaceList.count()) {
389 if (m_surfaceList.at(i).application == application) {
390 beginRemoveRows(QModelIndex(), i, i);
391 m_surfaceList.removeAt(i);
392 endRemoveRows();
393 Q_EMIT countChanged();
394 Q_EMIT listChanged();
395 } else {
396 ++i;
397 }
398 }
400 m_modelState = IdleState;
402 DEBUG_MSG << " after " << toString();
404 m_applications.removeAll(application);
407QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
409 return m_applicationsModel;
412void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
414 if (m_applicationsModel == value) {
415 return;
416 }
418 DEBUG_MSG << "(" << value << ")";
420 Q_ASSERT(m_modelState == IdleState);
421 m_modelState = ResettingState;
423 beginResetModel();
425 if (m_applicationsModel) {
426 m_surfaceList.clear();
427 m_applications.clear();
428 disconnect(m_applicationsModel, 0, this, 0);
429 }
431 m_applicationsModel = value;
433 if (m_applicationsModel) {
434 findApplicationRole();
436 connect(m_applicationsModel, &QAbstractItemModel::rowsInserted,
437 this, [this](const QModelIndex &/*parent*/, int first, int last) {
438 for (int i = first; i <= last; ++i) {
439 auto application = getApplicationFromModelAt(i);
440 addApplication(application);
441 }
442 });
444 connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
445 this, [this](const QModelIndex &/*parent*/, int first, int last) {
446 for (int i = first; i <= last; ++i) {
447 auto application = getApplicationFromModelAt(i);
448 removeApplication(application);
449 }
450 });
452 for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
453 auto application = getApplicationFromModelAt(i);
454 addApplication(application);
455 }
456 }
458 endResetModel();
459 m_modelState = IdleState;
462ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
464 QModelIndex modelIndex = m_applicationsModel->index(index);
466 QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
468 // variant.value<ApplicationInfoInterface*>() returns null for some reason.
469 return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
472void TopLevelSurfaceList::findApplicationRole()
474 QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
476 m_applicationRole = -1;
477 for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) {
478 if (i.value() == "application") {
479 m_applicationRole = i.key();
480 }
481 }
483 if (m_applicationRole == -1) {
484 qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
485 }