Unity 8
TopLevelWindowModel.cpp
1 /*
2  * Copyright (C) 2016 Canonical, Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it under
5  * the terms of the GNU Lesser General Public License version 3, as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10  * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "TopLevelWindowModel.h"
18 
19 // unity-api
20 #include <unity/shell/application/ApplicationInfoInterface.h>
21 #include <unity/shell/application/ApplicationManagerInterface.h>
22 #include <unity/shell/application/MirSurfaceInterface.h>
23 #include <unity/shell/application/MirSurfaceListInterface.h>
24 #include <unity/shell/application/SurfaceManagerInterface.h>
25 
26 // Qt
27 #include <QGuiApplication>
28 #include <QDebug>
29 
30 // local
31 #include "Window.h"
32 
33 Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34 
35 #define DEBUG_MSG qCDebug(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
36 #define INFO_MSG qCInfo(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
37 
39 
40 TopLevelWindowModel::TopLevelWindowModel()
41 {
42 }
43 
44 void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
45 {
46  if (m_applicationManager == value) {
47  return;
48  }
49 
50  DEBUG_MSG << "(" << value << ")";
51 
52  Q_ASSERT(m_modelState == IdleState);
53  m_modelState = ResettingState;
54 
55  beginResetModel();
56 
57  if (m_applicationManager) {
58  m_windowModel.clear();
59  disconnect(m_applicationManager, 0, this, 0);
60  }
61 
62  m_applicationManager = value;
63 
64  if (m_applicationManager) {
65  connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
66  this, [this](const QModelIndex &/*parent*/, int first, int last) {
67  for (int i = first; i <= last; ++i) {
68  auto application = m_applicationManager->get(i);
69  addApplication(application);
70  }
71  });
72 
73  connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
74  this, [this](const QModelIndex &/*parent*/, int first, int last) {
75  for (int i = first; i <= last; ++i) {
76  auto application = m_applicationManager->get(i);
77  removeApplication(application);
78  }
79  });
80 
81  for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
82  auto application = m_applicationManager->get(i);
83  addApplication(application);
84  }
85  }
86 
87  endResetModel();
88  m_modelState = IdleState;
89 }
90 
91 void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *surfaceManager)
92 {
93  if (surfaceManager == m_surfaceManager) {
94  return;
95  }
96 
97  DEBUG_MSG << "(" << surfaceManager << ")";
98 
99  if (m_surfaceManager) {
100  disconnect(m_surfaceManager, 0, this, 0);
101  }
102 
103  m_surfaceManager = surfaceManager;
104 
105  if (m_surfaceManager) {
106  connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
107  connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
108  connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
109  connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
110  }
111 
112  Q_EMIT surfaceManagerChanged(m_surfaceManager);
113 }
114 
115 void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
116 {
117  DEBUG_MSG << "(" << application->appId() << ")";
118 
119  if (application->state() != unityapi::ApplicationInfoInterface::Stopped && application->surfaceList()->count() == 0) {
120  prependPlaceholder(application);
121  }
122 }
123 
124 void TopLevelWindowModel::removeApplication(unityapi::ApplicationInfoInterface *application)
125 {
126  DEBUG_MSG << "(" << application->appId() << ")";
127 
128  Q_ASSERT(m_modelState == IdleState);
129 
130  int i = 0;
131  while (i < m_windowModel.count()) {
132  if (m_windowModel.at(i).application == application) {
133  removeAt(i);
134  } else {
135  ++i;
136  }
137  }
138 }
139 
140 void TopLevelWindowModel::prependPlaceholder(unityapi::ApplicationInfoInterface *application)
141 {
142  INFO_MSG << "(" << application->appId() << ")";
143 
144  prependSurfaceHelper(nullptr, application);
145 }
146 
147 void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
148 {
149  Q_ASSERT(surface != nullptr);
150 
151  bool filledPlaceholder = false;
152  for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
153  ModelEntry &entry = m_windowModel[i];
154  if (entry.application == application && entry.window->surface() == nullptr) {
155  entry.window->setSurface(surface);
156  connectSurface(surface);
157  INFO_MSG << " appId=" << application->appId() << " surface=" << surface
158  << ", filling out placeholder. after: " << toString();
159  filledPlaceholder = true;
160  }
161  }
162 
163  if (!filledPlaceholder) {
164  INFO_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
165  prependSurfaceHelper(surface, application);
166  }
167 }
168 
169 void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
170 {
171  if (m_modelState == IdleState) {
172  m_modelState = InsertingState;
173  beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
174  } else {
175  Q_ASSERT(m_modelState == ResettingState);
176  // No point in signaling anything if we're resetting the whole model
177  }
178 
179  int id = generateId();
180  Window *window = new Window(id, this);
181  if (surface) {
182  window->setSurface(surface);
183  }
184  m_windowModel.prepend(ModelEntry(window, application));
185  if (surface) {
186  connectSurface(surface);
187  }
188 
189  connectWindow(window);
190 
191  if (m_modelState == InsertingState) {
192  endInsertRows();
193  Q_EMIT countChanged();
194  Q_EMIT listChanged();
195  m_modelState = IdleState;
196  }
197 
198  if (!surface) {
199  activateEmptyWindow(window);
200  }
201 
202  INFO_MSG << " after " << toString();
203 }
204 
205 void TopLevelWindowModel::connectWindow(Window *window)
206 {
207  connect(window, &Window::focusRequested, this, [this, window]() {
208  if (!window->surface()) {
209  activateEmptyWindow(window);
210  }
211  });
212 
213  connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
214  if (window->surface()) {
215  // Condense changes to the focused window
216  // eg: Do focusedWindow=A to focusedWindow=B instead of
217  // focusedWindow=A to focusedWindow=null to focusedWindow=B
218  if (focused) {
219  Q_ASSERT(m_newlyFocusedWindow == nullptr);
220  m_focusedWindowChanged = true;
221  m_newlyFocusedWindow = window;
222  } else if (m_focusedWindow == window) {
223  m_focusedWindowChanged = true;
224  } else {
225  // don't clear the focused window if you were not there in the first place
226  // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
227  }
228  }
229  });
230 
231  connect(window, &Window::closeRequested, this, [this, window]() {
232  if (!window->surface()) {
233  // do things ourselves as miral doesn't know about this window
234  int id = window->id();
235  int index = indexForId(id);
236  bool focusOther = false;
237  Q_ASSERT(index >= 0);
238  if (window->focused()) {
239  focusOther = true;
240  }
241  m_windowModel[index].application->close();
242  if (focusOther) {
243  activateTopMostWindowWithoutId(id);
244  }
245  }
246  });
247 
248  connect(window, &Window::emptyWindowActivated, this, [this, window]() {
249  activateEmptyWindow(window);
250  });
251 }
252 
253 void TopLevelWindowModel::activateEmptyWindow(Window *window)
254 {
255  Q_ASSERT(!window->surface());
256  DEBUG_MSG << "(" << window << ")";
257 
258  // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
259  // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
260 
261  window->setFocused(true);
262  raiseId(window->id());
263  Window *previousWindow = m_focusedWindow;
264  setFocusedWindow(window);
265  if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
266  m_surfaceManager->activate(nullptr);
267  }
268 }
269 
270 void TopLevelWindowModel::connectSurface(unityapi::MirSurfaceInterface *surface)
271 {
272  connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
273  if (!live) {
274  onSurfaceDied(surface);
275  }
276  });
277  connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
278 }
279 
280 void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
281 {
282  if (surface->type() == Mir::InputMethodType) {
283  removeInputMethodWindow();
284  return;
285  }
286 
287  int i = indexOf(surface);
288  if (i == -1) {
289  return;
290  }
291 
292  auto application = m_windowModel[i].application;
293 
294  // can't be starting if it already has a surface
295  Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
296 
297  if (application->state() == unityapi::ApplicationInfoInterface::Running) {
298  m_windowModel[i].removeOnceSurfaceDestroyed = true;
299  } else {
300  // assume it got killed by the out-of-memory daemon.
301  //
302  // So leave entry in the model and only remove its surface, so shell can display a screenshot
303  // in its place.
304  m_windowModel[i].removeOnceSurfaceDestroyed = false;
305  }
306 }
307 
308 void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surface)
309 {
310  int i = indexOf(surface);
311  if (i == -1) {
312  return;
313  }
314 
315  if (m_windowModel[i].removeOnceSurfaceDestroyed) {
316  removeAt(i);
317  } else {
318  auto window = m_windowModel[i].window;
319  window->setSurface(nullptr);
320  window->setFocused(false);
321  INFO_MSG << " Removed surface from entry. After: " << toString();
322  }
323 }
324 
325 void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
326 {
327  DEBUG_MSG << "(" << surface << ")";
328  if (surface->type() == Mir::InputMethodType) {
329  int id = generateId();
330  Window *qmlWindow = new Window(id, this);
331  connectWindow(qmlWindow);
332  qmlWindow->setSurface(surface);
333  setInputMethodWindow(qmlWindow);
334  } else {
335  auto application = m_applicationManager->findApplicationWithSurface(surface);
336  if (application) {
337  prependSurface(surface, application);
338  } else {
339  // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
340  // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
341  // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
342  int id = generateId();
343  Window *promptWindow = new Window(id, this);
344  connectWindow(promptWindow);
345  promptWindow->setSurface(surface);
346  connect(surface, &QObject::destroyed, promptWindow, [=](){
347  promptWindow->setSurface(nullptr);
348  promptWindow->deleteLater();
349  });
350  }
351  }
352  // TODO: handle surfaces that are neither top-level windows nor input method. eg: child dialogs, popups, menus
353 }
354 
355 void TopLevelWindowModel::removeAt(int index)
356 {
357  if (m_modelState == IdleState) {
358  beginRemoveRows(QModelIndex(), index, index);
359  m_modelState = RemovingState;
360  } else {
361  Q_ASSERT(m_modelState == ResettingState);
362  // No point in signaling anything if we're resetting the whole model
363  }
364 
365  auto window = m_windowModel[index].window;
366 
367  window->setSurface(nullptr);
368  window->setFocused(false);
369 
370  m_windowModel.removeAt(index);
371 
372  if (m_modelState == RemovingState) {
373  endRemoveRows();
374  Q_EMIT countChanged();
375  Q_EMIT listChanged();
376  m_modelState = IdleState;
377  }
378 
379  disconnect(window, 0, this, 0);
380  if (m_focusedWindow == window) {
381  setFocusedWindow(nullptr);
382  }
383  delete window;
384 
385  INFO_MSG << " after " << toString();
386 }
387 
388 void TopLevelWindowModel::setInputMethodWindow(Window *window)
389 {
390  if (m_inputMethodWindow) {
391  qWarning("Multiple Input Method Surfaces created, removing the old one!");
392  delete m_inputMethodWindow;
393  }
394  m_inputMethodWindow = window;
395  Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
396 }
397 
398 void TopLevelWindowModel::removeInputMethodWindow()
399 {
400  if (m_inputMethodWindow) {
401  delete m_inputMethodWindow;
402  m_inputMethodWindow = nullptr;
403  Q_EMIT inputMethodSurfaceChanged(nullptr);
404  }
405 }
406 
407 void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInterface*> &surfaces)
408 {
409  DEBUG_MSG << "(" << surfaces << ")";
410  const int raiseCount = surfaces.size();
411  for (int i = 0; i < raiseCount; i++) {
412  int fromIndex = findIndexOf(surfaces[i]);
413  if (fromIndex != -1) {
414  move(fromIndex, 0);
415  }
416  }
417 }
418 
419 int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
420 {
421  return m_windowModel.count();
422 }
423 
424 QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
425 {
426  if (index.row() < 0 || index.row() >= m_windowModel.size())
427  return QVariant();
428 
429  if (role == WindowRole) {
430  Window *window = m_windowModel.at(index.row()).window;
431  return QVariant::fromValue(window);
432  } else if (role == ApplicationRole) {
433  return QVariant::fromValue(m_windowModel.at(index.row()).application);
434  } else {
435  return QVariant();
436  }
437 }
438 
439 int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
440 {
441  for (int i=0; i<m_windowModel.count(); i++) {
442  if (m_windowModel[i].window->surface() == surface) {
443  return i;
444  }
445  }
446  return -1;
447 }
448 
449 int TopLevelWindowModel::generateId()
450 {
451  int id = m_nextId;
452  m_nextId = nextFreeId(nextId(id), id);
453  return id;
454 }
455 
456 int TopLevelWindowModel::nextId(int id) const
457 {
458  if (id == m_maxId) {
459  return id = 1;
460  } else {
461  return id + 1;
462  }
463 }
464 
465 int TopLevelWindowModel::nextFreeId(int candidateId, const int latestId)
466 {
467  int firstCandidateId = candidateId;
468 
469  while (indexForId(candidateId) != -1 || candidateId == latestId) {
470  candidateId = nextId(candidateId);
471 
472  if (candidateId == firstCandidateId) {
473  qFatal("TopLevelWindowModel: run out of window ids.");
474  }
475  }
476 
477  return candidateId;
478 }
479 
480 QString TopLevelWindowModel::toString()
481 {
482  QString str;
483  for (int i = 0; i < m_windowModel.count(); ++i) {
484  auto item = m_windowModel.at(i);
485 
486  QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
487  .arg(QString::number(i),
488  item.application->appId(),
489  QString::number((qintptr)item.window->surface(), 16),
490  QString::number(item.window->id()));
491 
492  if (i > 0) {
493  str.append(",");
494  }
495  str.append(itemStr);
496  }
497  return str;
498 }
499 
500 int TopLevelWindowModel::indexOf(unityapi::MirSurfaceInterface *surface)
501 {
502  for (int i = 0; i < m_windowModel.count(); ++i) {
503  if (m_windowModel.at(i).window->surface() == surface) {
504  return i;
505  }
506  }
507  return -1;
508 }
509 
511 {
512  for (int i = 0; i < m_windowModel.count(); ++i) {
513  if (m_windowModel[i].window->id() == id) {
514  return i;
515  }
516  }
517  return -1;
518 }
519 
521 {
522  if (index >=0 && index < m_windowModel.count()) {
523  return m_windowModel[index].window;
524  } else {
525  return nullptr;
526  }
527 }
528 
529 unityapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
530 {
531  if (index >=0 && index < m_windowModel.count()) {
532  return m_windowModel[index].window->surface();
533  } else {
534  return nullptr;
535  }
536 }
537 
538 unityapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
539 {
540  if (index >=0 && index < m_windowModel.count()) {
541  return m_windowModel[index].application;
542  } else {
543  return nullptr;
544  }
545 }
546 
547 int TopLevelWindowModel::idAt(int index) const
548 {
549  if (index >=0 && index < m_windowModel.count()) {
550  return m_windowModel[index].window->id();
551  } else {
552  return 0;
553  }
554 }
555 
557 {
558  if (m_modelState == IdleState) {
559  DEBUG_MSG << "(id=" << id << ") - do it now.";
560  doRaiseId(id);
561  } else {
562  DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
563  // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
564  // if we perform yet another model change straight away.
565  //
566  // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
567  // the index is definitely within bounds.
568  QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
569  }
570 }
571 
572 void TopLevelWindowModel::doRaiseId(int id)
573 {
574  int fromIndex = indexForId(id);
575  // can't raise something that doesn't exist or that it's already on top
576  if (fromIndex != -1 && fromIndex != 0) {
577  auto surface = m_windowModel[fromIndex].window->surface();
578  if (surface) {
579  m_surfaceManager->raise(surface);
580  } else {
581  // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
582  // miral can do about it.
583  move(fromIndex, 0);
584  }
585  }
586 }
587 
588 void TopLevelWindowModel::setFocusedWindow(Window *window)
589 {
590  if (window != m_focusedWindow) {
591  INFO_MSG << "(" << window << ")";
592 
593  Window* previousWindow = m_focusedWindow;
594 
595  m_focusedWindow = window;
596  Q_EMIT focusedWindowChanged(m_focusedWindow);
597 
598  if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
599  // do it ourselves. miral doesn't know about this window
600  previousWindow->setFocused(false);
601  }
602  }
603 }
604 
605 unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
606 {
607  return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
608 }
609 
611 {
612  return m_focusedWindow;
613 }
614 
615 void TopLevelWindowModel::move(int from, int to)
616 {
617  if (from == to) return;
618  DEBUG_MSG << " from=" << from << " to=" << to;
619 
620  if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
621  QModelIndex parent;
622  /* When moving an item down, the destination index needs to be incremented
623  by one, as explained in the documentation:
624  http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
625 
626  Q_ASSERT(m_modelState == IdleState);
627  m_modelState = MovingState;
628 
629  beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
630 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
631  const auto &window = m_windowModel.takeAt(from);
632  m_windowModel.insert(to, window);
633 #else
634  m_windowModel.move(from, to);
635 #endif
636  endMoveRows();
637 
638  Q_EMIT listChanged();
639  m_modelState = IdleState;
640 
641  INFO_MSG << " after " << toString();
642  }
643 }
644 void TopLevelWindowModel::onModificationsStarted()
645 {
646 }
647 
648 void TopLevelWindowModel::onModificationsEnded()
649 {
650  if (m_focusedWindowChanged) {
651  setFocusedWindow(m_newlyFocusedWindow);
652  }
653  // reset
654  m_focusedWindowChanged = false;
655  m_newlyFocusedWindow = nullptr;
656 }
657 
658 void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
659 {
660  DEBUG_MSG << "(" << forbiddenId << ")";
661 
662  for (int i = 0; i < m_windowModel.count(); ++i) {
663  Window *window = m_windowModel[i].window;
664  if (window->id() != forbiddenId) {
665  window->activate();
666  }
667  }
668 }
Q_INVOKABLE Window * windowAt(int index) const
Returns the window at the given index.
A slightly higher concept than MirSurface.
Definition: Window.h:46
void focusRequested()
Emitted when focus for this window is requested by an external party.
void listChanged()
Emitted when the list changes.
Q_INVOKABLE int indexForId(int id) const
Returns the index where the row with the given id is located.
void activate()
Focuses and raises the window.
Definition: Window.cpp:119
unity::shell::application::MirSurfaceInterface surface
Surface backing up this window It might be null if a surface hasn&#39;t been created yet (application is ...
Definition: Window.h:91
bool focused
Whether the surface is focused.
Definition: Window.h:70
int id
A unique identifier for this window. Useful for telling windows apart in a list model as they get mov...
Definition: Window.h:83
unity::shell::application::MirSurfaceInterface inputMethodSurface
The input method surface, if any.
Window focusedWindow
The currently focused window, if any.
Q_INVOKABLE unity::shell::application::MirSurfaceInterface * surfaceAt(int index) const
Returns the surface at the given index.
Q_INVOKABLE unity::shell::application::ApplicationInfoInterface * applicationAt(int index) const
Returns the application at the given index.
Q_INVOKABLE int idAt(int index) const
Returns the unique id of the element at the given index.
Q_INVOKABLE void raiseId(int id)
Raises the row with the given id to the top of the window stack (index == count-1) ...