Unity 8
TopLevelSurfaceList.cpp
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  */
16 
17 #include "TopLevelSurfaceList.h"
18 
19 // unity-api
20 #include <unity/shell/application/ApplicationInfoInterface.h>
21 #include <unity/shell/application/MirSurfaceInterface.h>
22 #include <unity/shell/application/MirSurfaceListInterface.h>
23 
24 #include <QMetaObject>
25 
26 Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
27 
28 #define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
29 
30 using namespace unity::shell::application;
31 
32 TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
33  QAbstractListModel(parent)
34 {
35  DEBUG_MSG << "()";
36 }
37 
38 TopLevelSurfaceList::~TopLevelSurfaceList()
39 {
40  DEBUG_MSG << "()";
41 }
42 
43 int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
44 {
45  return !parent.isValid() ? m_surfaceList.size() : 0;
46 }
47 
48 QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
49 {
50  if (index.row() < 0 || index.row() >= m_surfaceList.size())
51  return QVariant();
52 
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  }
63 }
64 
65 void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
66 {
67  if (!surface)
68  return;
69 
70  DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
71 
72  int i = indexOf(surface);
73  if (i != -1) {
74  raiseId(m_surfaceList.at(i).id);
75  }
76 }
77 
78 void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
79 {
80  DEBUG_MSG << "(" << application->appId() << ")";
81 
82  appendSurfaceHelper(nullptr, application);
83 }
84 
85 void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
86 {
87  Q_ASSERT(surface != nullptr);
88 
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  }
101 
102  if (!filledPlaceholder) {
103  DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
104  appendSurfaceHelper(surface, application);
105  }
106 }
107 
108 void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
109 {
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  }
117 
118  int id = generateId();
119  m_surfaceList.append(ModelEntry(surface, application, id));
120  if (surface) {
121  connectSurface(surface);
122  }
123 
124  if (m_modelState == InsertingState) {
125  endInsertRows();
126  Q_EMIT countChanged();
127  Q_EMIT listChanged();
128  m_modelState = IdleState;
129  }
130 
131  DEBUG_MSG << " after " << toString();
132 }
133 
134 void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
135 {
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); });
147 }
148 
149 void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
150 {
151  int i = indexOf(surface);
152  if (i == -1) {
153  return;
154  }
155 
156  auto application = m_surfaceList[i].application;
157 
158  // can't be starting if it already has a surface
159  Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
160 
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  }
170 }
171 
172 void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
173 {
174  int i = indexOf(surface);
175  if (i == -1) {
176  return;
177  }
178 
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  }
186 }
187 
188 void TopLevelSurfaceList::removeAt(int index)
189 {
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  }
197 
198  m_surfaceList.removeAt(index);
199 
200  if (m_modelState == RemovingState) {
201  endRemoveRows();
202  Q_EMIT countChanged();
203  Q_EMIT listChanged();
204  m_modelState = IdleState;
205  }
206 
207  DEBUG_MSG << " after " << toString();
208 }
209 
210 int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
211 {
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;
218 }
219 
220 void TopLevelSurfaceList::move(int from, int to)
221 {
222  if (from == to) return;
223  DEBUG_MSG << " from=" << from << " to=" << to;
224 
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 */
230 
231  Q_ASSERT(m_modelState == IdleState);
232  m_modelState = MovingState;
233 
234  beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
235  m_surfaceList.move(from, to);
236  endMoveRows();
237  Q_EMIT listChanged();
238 
239  m_modelState = IdleState;
240 
241  DEBUG_MSG << " after " << toString();
242  }
243 }
244 
245 MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
246 {
247  if (index >=0 && index < m_surfaceList.count()) {
248  return m_surfaceList[index].surface;
249  } else {
250  return nullptr;
251  }
252 }
253 
254 ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
255 {
256  if (index >=0 && index < m_surfaceList.count()) {
257  return m_surfaceList[index].application;
258  } else {
259  return nullptr;
260  }
261 }
262 
263 int TopLevelSurfaceList::idAt(int index) const
264 {
265  if (index >=0 && index < m_surfaceList.count()) {
266  return m_surfaceList[index].id;
267  } else {
268  return 0;
269  }
270 }
271 
273 {
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;
280 }
281 
282 void TopLevelSurfaceList::doRaiseId(int id)
283 {
284  int fromIndex = indexForId(id);
285  if (fromIndex != -1) {
286  move(fromIndex, 0 /* toIndex */);
287  }
288 }
289 
291 {
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  }
304 }
305 
306 int TopLevelSurfaceList::generateId()
307 {
308  int id = m_nextId;
309  m_nextId = nextFreeId(m_nextId + 1);
310  Q_EMIT nextIdChanged();
311  return id;
312 }
313 
314 int TopLevelSurfaceList::nextFreeId(int candidateId)
315 {
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  }
326 }
327 
328 QString TopLevelSurfaceList::toString()
329 {
330  QString str;
331  for (int i = 0; i < m_surfaceList.count(); ++i) {
332  auto item = m_surfaceList.at(i);
333 
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);
339 
340  if (i > 0) {
341  str.append(",");
342  }
343  str.append(itemStr);
344  }
345  return str;
346 }
347 
348 void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
349 {
350  DEBUG_MSG << "(" << application->appId() << ")";
351  Q_ASSERT(!m_applications.contains(application));
352  m_applications.append(application);
353 
354  MirSurfaceListInterface *surfaceList = application->surfaceList();
355 
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  }
365 
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  });
373 }
374 
375 void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
376 {
377  DEBUG_MSG << "(" << application->appId() << ")";
378  Q_ASSERT(m_applications.contains(application));
379 
380  MirSurfaceListInterface *surfaceList = application->surfaceList();
381 
382  disconnect(surfaceList, 0, this, 0);
383 
384  Q_ASSERT(m_modelState == IdleState);
385  m_modelState = RemovingState;
386 
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  }
399 
400  m_modelState = IdleState;
401 
402  DEBUG_MSG << " after " << toString();
403 
404  m_applications.removeAll(application);
405 }
406 
407 QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
408 {
409  return m_applicationsModel;
410 }
411 
412 void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
413 {
414  if (m_applicationsModel == value) {
415  return;
416  }
417 
418  DEBUG_MSG << "(" << value << ")";
419 
420  Q_ASSERT(m_modelState == IdleState);
421  m_modelState = ResettingState;
422 
423  beginResetModel();
424 
425  if (m_applicationsModel) {
426  m_surfaceList.clear();
427  m_applications.clear();
428  disconnect(m_applicationsModel, 0, this, 0);
429  }
430 
431  m_applicationsModel = value;
432 
433  if (m_applicationsModel) {
434  findApplicationRole();
435 
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  });
443 
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  });
451 
452  for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
453  auto application = getApplicationFromModelAt(i);
454  addApplication(application);
455  }
456  }
457 
458  endResetModel();
459  m_modelState = IdleState;
460 }
461 
462 ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
463 {
464  QModelIndex modelIndex = m_applicationsModel->index(index);
465 
466  QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
467 
468  // variant.value<ApplicationInfoInterface*>() returns null for some reason.
469  return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
470 }
471 
472 void TopLevelSurfaceList::findApplicationRole()
473 {
474  QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
475 
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  }
482 
483  if (m_applicationRole == -1) {
484  qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
485  }
486 }
unity::shell::application::MirSurfaceInterface * surfaceAt(int index) const
Returns the surface at the given index.
int idAt(int index) const
Returns the unique id of the element at the given index.
int indexForId(int id) const
Returns the index where the row with the given id is located.
unity::shell::application::ApplicationInfoInterface * applicationAt(int index) const
Returns the application at the given index.
void raiseId(int id)
Raises the row with the given id to index 0.
QAbstractListModel applicationsModel
A list model of applications.