Lomiri
TopLevelWindowModel.cpp
1 /*
2  * Copyright (C) 2016-2017 Canonical Ltd.
3  * Copyright 2019 UBports Foundation
4  *
5  * This program is free software: you can redistribute it and/or modify it under
6  * the terms of the GNU Lesser General Public License version 3, as published by
7  * the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
11  * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "TopLevelWindowModel.h"
19 #include "WindowManagerObjects.h"
20 
21 // lomiri-api
22 #include <lomiri/shell/application/ApplicationInfoInterface.h>
23 #include <lomiri/shell/application/ApplicationManagerInterface.h>
24 #include <lomiri/shell/application/MirSurfaceInterface.h>
25 #include <lomiri/shell/application/MirSurfaceListInterface.h>
26 #include <lomiri/shell/application/SurfaceManagerInterface.h>
27 
28 // Qt
29 #include <QDebug>
30 
31 // local
32 #include "Window.h"
33 #include "Workspace.h"
34 #include "InputMethodManager.h"
35 
36 Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
37 
38 #define DEBUG_MSG qCDebug(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
39 #define INFO_MSG qCInfo(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
40 
41 namespace lomiriapi = lomiri::shell::application;
42 
43 TopLevelWindowModel::TopLevelWindowModel(Workspace* workspace)
44  : m_nullWindow(createWindow(nullptr)),
45  m_workspace(workspace),
46  m_surfaceManagerBusy(false)
47 {
48  connect(WindowManagerObjects::instance(), &WindowManagerObjects::applicationManagerChanged,
49  this, &TopLevelWindowModel::setApplicationManager);
50  connect(WindowManagerObjects::instance(), &WindowManagerObjects::surfaceManagerChanged,
51  this, &TopLevelWindowModel::setSurfaceManager);
52 
53  setApplicationManager(WindowManagerObjects::instance()->applicationManager());
54  setSurfaceManager(WindowManagerObjects::instance()->surfaceManager());
55 
56  connect(m_nullWindow, &Window::focusedChanged, this, [this] {
57  Q_EMIT rootFocusChanged();
58  });
59 }
60 
61 TopLevelWindowModel::~TopLevelWindowModel()
62 {
63 }
64 
65 void TopLevelWindowModel::setApplicationManager(lomiriapi::ApplicationManagerInterface* value)
66 {
67  if (m_applicationManager == value) {
68  return;
69  }
70 
71  DEBUG_MSG << "(" << value << ")";
72 
73  Q_ASSERT(m_modelState == IdleState);
74  m_modelState = ResettingState;
75 
76  beginResetModel();
77 
78  if (m_applicationManager) {
79  disconnect(m_applicationManager, 0, this, 0);
80  }
81 
82  m_applicationManager = value;
83 
84  if (m_applicationManager) {
85  connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
86  this, [this](const QModelIndex &/*parent*/, int first, int last) {
87  if (!m_workspace || !m_workspace->isActive())
88  return;
89 
90  for (int i = first; i <= last; ++i) {
91  auto application = m_applicationManager->get(i);
92  addApplication(application);
93  }
94  });
95 
96  connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
97  this, [this](const QModelIndex &/*parent*/, int first, int last) {
98  for (int i = first; i <= last; ++i) {
99  auto application = m_applicationManager->get(i);
100  removeApplication(application);
101  }
102  });
103  }
104 
105  refreshWindows();
106 
107  endResetModel();
108  m_modelState = IdleState;
109 }
110 
111 void TopLevelWindowModel::setSurfaceManager(lomiriapi::SurfaceManagerInterface *surfaceManager)
112 {
113  if (surfaceManager == m_surfaceManager) {
114  return;
115  }
116 
117  DEBUG_MSG << "(" << surfaceManager << ")";
118 
119  Q_ASSERT(m_modelState == IdleState);
120  m_modelState = ResettingState;
121 
122  beginResetModel();
123 
124  if (m_surfaceManager) {
125  disconnect(m_surfaceManager, 0, this, 0);
126  }
127 
128  m_surfaceManager = surfaceManager;
129 
130  if (m_surfaceManager) {
131  connect(m_surfaceManager, &lomiriapi::SurfaceManagerInterface::surfacesAddedToWorkspace, this, &TopLevelWindowModel::onSurfacesAddedToWorkspace);
132  connect(m_surfaceManager, &lomiriapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
133  connect(m_surfaceManager, &lomiriapi::SurfaceManagerInterface::surfaceRemoved, this, &TopLevelWindowModel::onSurfaceDestroyed);
134  connect(m_surfaceManager, &lomiriapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
135  connect(m_surfaceManager, &lomiriapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
136  }
137 
138  refreshWindows();
139 
140  endResetModel();
141  m_modelState = IdleState;
142 }
143 
144 void TopLevelWindowModel::addApplication(lomiriapi::ApplicationInfoInterface *application)
145 {
146  DEBUG_MSG << "(" << application->appId() << ")";
147 
148  if (application->state() != lomiriapi::ApplicationInfoInterface::Stopped && application->surfaceList()->count() == 0) {
149  prependPlaceholder(application);
150  }
151 }
152 
153 void TopLevelWindowModel::removeApplication(lomiriapi::ApplicationInfoInterface *application)
154 {
155  DEBUG_MSG << "(" << application->appId() << ")";
156 
157  Q_ASSERT(m_modelState == IdleState);
158 
159  int i = 0;
160  while (i < m_windowModel.count()) {
161  if (m_windowModel.at(i).application == application) {
162  deleteAt(i);
163  } else {
164  ++i;
165  }
166  }
167 }
168 
169 void TopLevelWindowModel::prependPlaceholder(lomiriapi::ApplicationInfoInterface *application)
170 {
171  DEBUG_MSG << "(" << application->appId() << ")";
172 
173  if (!application->showSplash())
174  return;
175 
176  prependSurfaceHelper(nullptr, application);
177 }
178 
179 void TopLevelWindowModel::prependSurface(lomiriapi::MirSurfaceInterface *surface, lomiriapi::ApplicationInfoInterface *application)
180 {
181  Q_ASSERT(surface != nullptr);
182 
183  connectSurface(surface);
184  m_allSurfaces.insert(surface);
185 
186  bool filledPlaceholder = false;
187  for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
188  ModelEntry &entry = m_windowModel[i];
189  if (entry.application == application && (entry.window->surface() == nullptr || !entry.window->surface()->live())) {
190  entry.window->setSurface(surface);
191  DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
192  << ", filling out placeholder. after: " << toString();
193  filledPlaceholder = true;
194  }
195  }
196 
197  if (!filledPlaceholder) {
198  DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
199  prependSurfaceHelper(surface, application);
200  }
201 }
202 
203 void TopLevelWindowModel::prependSurfaceHelper(lomiriapi::MirSurfaceInterface *surface, lomiriapi::ApplicationInfoInterface *application)
204 {
205 
206  Window *window = createWindow(surface);
207 
208  connect(window, &Window::stateChanged, this, [=](Mir::State newState) {
209  if (newState == Mir::HiddenState) {
210  // Comply, removing it from our model. Just as if it didn't exist anymore.
211  removeAt(indexForId(window->id()));
212  } else {
213  if (indexForId(window->id()) == -1) {
214  // was probably hidden before. put it back on the list
215  auto *application = m_applicationManager->findApplicationWithSurface(window->surface());
216  Q_ASSERT(application);
217  prependWindow(window, application);
218  }
219  }
220  });
221 
222  prependWindow(window, application);
223 
224  // Activate the newly-prepended window.
225  window->activate();
226 
227  DEBUG_MSG << " after " << toString();
228 }
229 
230 void TopLevelWindowModel::prependWindow(Window *window, lomiriapi::ApplicationInfoInterface *application)
231 {
232  if (m_modelState == IdleState) {
233  m_modelState = InsertingState;
234  beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
235  } else {
236  Q_ASSERT(m_modelState == ResettingState);
237  // No point in signaling anything if we're resetting the whole model
238  }
239 
240  m_windowModel.prepend(ModelEntry(window, application));
241 
242  if (m_modelState == InsertingState) {
243  endInsertRows();
244  Q_EMIT countChanged();
245  Q_EMIT listChanged();
246  m_modelState = IdleState;
247  }
248 }
249 
250 void TopLevelWindowModel::connectWindow(Window *window)
251 {
252  connect(window, &Window::focusRequested, this, [this, window]() {
253  if (!window->surface()) {
254  activateEmptyWindow(window);
255  }
256  });
257 
258  connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
259  if (window->surface()) {
260  if (focused) {
261  setFocusedWindow(window);
262  m_focusedWindowCleared = false;
263  } else if (m_focusedWindow == window) {
264  // Condense changes to the focused window
265  // eg: Do focusedWindow=A to focusedWindow=B instead of
266  // focusedWindow=A to focusedWindow=null to focusedWindow=B
267  m_focusedWindowCleared = true;
268  } else {
269  // don't clear the focused window if you were not there in the first place
270  // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
271  }
272  }
273  });
274 
275  connect(window, &Window::closeRequested, this, [this, window]() {
276  if (!window->surface()) {
277  // do things ourselves as miral doesn't know about this window
278  int id = window->id();
279  int index = indexForId(id);
280  bool focusOther = false;
281  Q_ASSERT(index >= 0);
282  if (window->focused()) {
283  focusOther = true;
284  }
285  m_windowModel[index].application->close();
286  if (focusOther) {
287  activateTopMostWindowWithoutId(id);
288  }
289  }
290  });
291 
292  connect(window, &Window::emptyWindowActivated, this, [this, window]() {
293  activateEmptyWindow(window);
294  });
295 
296  connect(window, &Window::liveChanged, this, [this, window](bool isAlive) {
297  if (!isAlive && window->state() == Mir::HiddenState) {
298  // Hidden windows are not in the model. So just delete it right away.
299  delete window;
300  }
301  });
302 }
303 
304 void TopLevelWindowModel::activateEmptyWindow(Window *window)
305 {
306  Q_ASSERT(!window->surface());
307  DEBUG_MSG << "(" << window << ")";
308 
309  // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
310  // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
311 
312  window->setFocused(true);
313  raiseId(window->id());
314  Window *previousWindow = m_focusedWindow;
315  setFocusedWindow(window);
316  if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
317  m_surfaceManager->activate(nullptr);
318  }
319 }
320 
321 void TopLevelWindowModel::connectSurface(lomiriapi::MirSurfaceInterface *surface)
322 {
323  connect(surface, &lomiriapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
324  if (!live) {
325  onSurfaceDied(surface);
326  }
327  });
328  connect(surface, &QObject::destroyed, this, [this, surface](QObject*){ this->onSurfaceDestroyed(surface); });
329 }
330 
331 void TopLevelWindowModel::onSurfaceDied(lomiriapi::MirSurfaceInterface *surface)
332 {
333  if (surface->type() == Mir::InputMethodType) {
334  removeInputMethodWindow();
335  return;
336  }
337 
338  int i = indexOf(surface);
339  if (i == -1) {
340  return;
341  }
342 
343  auto application = m_windowModel[i].application;
344 
345  DEBUG_MSG << " application->name()=" << application->name()
346  << " application->state()=" << application->state();
347 
348  // assume it got killed by the out-of-memory daemon.
349  //
350  // Leave at most 1 entry in the model and only remove its surface, so shell can display a screenshot
351  // in its place.
352  if (application->surfaceList()->count() == 1)
353  m_windowModel[i].removeOnceSurfaceDestroyed = false;
354  else
355  m_windowModel[i].removeOnceSurfaceDestroyed = true;
356 }
357 
358 void TopLevelWindowModel::onSurfaceDestroyed(lomiriapi::MirSurfaceInterface *surface)
359 {
360  int i = indexOf(surface);
361  if (i == -1) {
362  return;
363  }
364 
365  auto application = m_windowModel[i].application;
366  if (application->appId() == QStringLiteral("xwayland.qtmir")) {
367  m_windowModel[i].removeOnceSurfaceDestroyed = true;
368  }
369 
370  if (m_windowModel[i].removeOnceSurfaceDestroyed) {
371  deleteAt(i);
372  } else {
373  auto window = m_windowModel[i].window;
374  window->setFocused(false);
375  m_allSurfaces.remove(surface);
376  DEBUG_MSG << " Removed surface from entry. After: " << toString();
377  }
378 }
379 
380 Window *TopLevelWindowModel::createWindow(lomiriapi::MirSurfaceInterface *surface)
381 {
382  int id = m_nextId.fetchAndAddAcquire(1);
383  return createWindowWithId(surface, id);
384 }
385 
386 Window *TopLevelWindowModel::createNullWindow()
387 {
388  return createWindowWithId(nullptr, 0);
389 }
390 
391 Window *TopLevelWindowModel::createWindowWithId(lomiriapi::MirSurfaceInterface *surface, int id)
392 {
393  Window *qmlWindow = new Window(id, this);
394  connectWindow(qmlWindow);
395  if (surface) {
396  qmlWindow->setSurface(surface);
397  }
398  return qmlWindow;
399 }
400 
401 void TopLevelWindowModel::onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
402  const QVector<lomiri::shell::application::MirSurfaceInterface*> surfaces)
403 {
404  if (!m_workspace || !m_applicationManager) return;
405  if (workspace != m_workspace->workspace()) {
406  removeSurfaces(surfaces);
407  return;
408  }
409 
410  Q_FOREACH(auto surface, surfaces) {
411  if (m_allSurfaces.contains(surface)) continue;
412 
413  if (surface->parentSurface()) {
414  // Wrap it in a Window so that we keep focusedWindow() up to date.
415  Window *window = createWindow(surface);
416  connect(surface, &QObject::destroyed, window, [=](){
417  window->setSurface(nullptr);
418  window->deleteLater();
419  });
420  } else {
421  if (surface->type() == Mir::InputMethodType) {
422  connectSurface(surface);
423  setInputMethodWindow(createWindow(surface));
424  } else {
425  auto *application = m_applicationManager->findApplicationWithSurface(surface);
426  if (application) {
427  if (surface->state() == Mir::HiddenState) {
428  // Ignore it until it's finally shown
429  connect(surface, &lomiriapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
430  Q_ASSERT(newState != Mir::HiddenState);
431  disconnect(surface, &lomiriapi::MirSurfaceInterface::stateChanged, this, 0);
432  prependSurface(surface, application);
433  });
434  } else {
435  prependSurface(surface, application);
436  }
437  } else {
438  // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
439  // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
440  // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
441  Window *promptWindow = createWindow(surface);
442  connect(surface, &QObject::destroyed, promptWindow, [=](){
443  promptWindow->setSurface(nullptr);
444  promptWindow->deleteLater();
445  });
446  }
447  }
448  }
449  }
450 }
451 
452 void TopLevelWindowModel::removeSurfaces(const QVector<lomiri::shell::application::MirSurfaceInterface *> surfaces)
453 {
454  int start = -1;
455  int end = -1;
456  for (auto iter = surfaces.constBegin(); iter != surfaces.constEnd();) {
457  auto surface = *iter;
458  iter++;
459 
460  // Do removals in adjacent blocks.
461  start = end = indexOf(surface);
462  if (start == -1) {
463  // could be a child surface
464  m_allSurfaces.remove(surface);
465  continue;
466  }
467  while(iter != surfaces.constEnd()) {
468  int index = indexOf(*iter);
469  if (index != end+1) {
470  break;
471  }
472  end++;
473  iter++;
474  }
475 
476  if (m_modelState == IdleState) {
477  beginRemoveRows(QModelIndex(), start, end);
478  m_modelState = RemovingState;
479  } else {
480  Q_ASSERT(m_modelState == ResettingState);
481  // No point in signaling anything if we're resetting the whole model
482  }
483 
484  for (int index = start; index <= end; index++) {
485  auto window = m_windowModel[start].window;
486  window->setSurface(nullptr);
487  window->setFocused(false);
488 
489  if (window == m_previousWindow) {
490  m_previousWindow = nullptr;
491  }
492 
493  m_windowModel.removeAt(start);
494  m_allSurfaces.remove(surface);
495  }
496 
497  if (m_modelState == RemovingState) {
498  endRemoveRows();
499  Q_EMIT countChanged();
500  Q_EMIT listChanged();
501  m_modelState = IdleState;
502  }
503  }
504 }
505 
506 void TopLevelWindowModel::deleteAt(int index)
507 {
508  auto window = m_windowModel[index].window;
509 
510  removeAt(index);
511 
512  window->setSurface(nullptr);
513 
514  delete window;
515 }
516 
517 void TopLevelWindowModel::removeAt(int index)
518 {
519  if (m_modelState == IdleState) {
520  beginRemoveRows(QModelIndex(), index, index);
521  m_modelState = RemovingState;
522  } else {
523  Q_ASSERT(m_modelState == ResettingState);
524  // No point in signaling anything if we're resetting the whole model
525  }
526 
527  auto window = m_windowModel[index].window;
528  auto surface = window->surface();
529 
530  if (!window->surface()) {
531  window->setFocused(false);
532  }
533 
534  if (window == m_previousWindow) {
535  m_previousWindow = nullptr;
536  }
537 
538  m_windowModel.removeAt(index);
539  m_allSurfaces.remove(surface);
540 
541  if (m_modelState == RemovingState) {
542  endRemoveRows();
543  Q_EMIT countChanged();
544  Q_EMIT listChanged();
545  m_modelState = IdleState;
546  }
547 
548  if (m_focusedWindow == window) {
549  setFocusedWindow(nullptr);
550  m_focusedWindowCleared = false;
551  }
552 
553  if (m_previousWindow == window) {
554  m_previousWindow = nullptr;
555  }
556 
557  if (m_closingAllApps) {
558  if (m_windowModel.isEmpty()) {
559  Q_EMIT closedAllWindows();
560  }
561  }
562 
563  DEBUG_MSG << " after " << toString() << " apps left " << m_windowModel.count();
564 }
565 
566 void TopLevelWindowModel::setInputMethodWindow(Window *window)
567 {
568  if (m_inputMethodWindow) {
569  qWarning("Multiple Input Method Surfaces created, removing the old one!");
570  delete m_inputMethodWindow;
571  }
572  m_inputMethodWindow = window;
573  Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
574  InputMethodManager::instance()->setWindow(window);
575 }
576 
577 void TopLevelWindowModel::removeInputMethodWindow()
578 {
579  if (m_inputMethodWindow) {
580  auto surface = m_inputMethodWindow->surface();
581  if (surface) {
582  m_allSurfaces.remove(surface);
583  }
584  if (m_focusedWindow == m_inputMethodWindow) {
585  setFocusedWindow(nullptr);
586  m_focusedWindowCleared = false;
587  }
588 
589  delete m_inputMethodWindow;
590  m_inputMethodWindow = nullptr;
591  Q_EMIT inputMethodSurfaceChanged(nullptr);
592  InputMethodManager::instance()->setWindow(nullptr);
593  }
594 }
595 
596 void TopLevelWindowModel::onSurfacesRaised(const QVector<lomiriapi::MirSurfaceInterface*> &surfaces)
597 {
598  DEBUG_MSG << "(" << surfaces << ")";
599  const int raiseCount = surfaces.size();
600  for (int i = 0; i < raiseCount; i++) {
601  int fromIndex = indexOf(surfaces[i]);
602  if (fromIndex != -1) {
603  move(fromIndex, 0);
604  }
605  }
606 }
607 
608 int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
609 {
610  return m_windowModel.count();
611 }
612 
613 QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
614 {
615  if (index.row() < 0 || index.row() >= m_windowModel.size())
616  return QVariant();
617 
618  if (role == WindowRole) {
619  Window *window = m_windowModel.at(index.row()).window;
620  return QVariant::fromValue(window);
621  } else if (role == ApplicationRole) {
622  return QVariant::fromValue(m_windowModel.at(index.row()).application);
623  } else {
624  return QVariant();
625  }
626 }
627 
628 QString TopLevelWindowModel::toString()
629 {
630  QString str;
631  for (int i = 0; i < m_windowModel.count(); ++i) {
632  auto item = m_windowModel.at(i);
633 
634  QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
635  .arg(QString::number(i),
636  item.application->appId(),
637  QString::number((qintptr)item.window->surface(), 16),
638  QString::number(item.window->id()));
639 
640  if (i > 0) {
641  str.append(",");
642  }
643  str.append(itemStr);
644  }
645  return str;
646 }
647 
648 int TopLevelWindowModel::indexOf(lomiriapi::MirSurfaceInterface *surface)
649 {
650  for (int i = 0; i < m_windowModel.count(); ++i) {
651  if (m_windowModel.at(i).window->surface() == surface) {
652  return i;
653  }
654  }
655  return -1;
656 }
657 
659 {
660  for (int i = 0; i < m_windowModel.count(); ++i) {
661  if (m_windowModel[i].window->id() == id) {
662  return i;
663  }
664  }
665  return -1;
666 }
667 
669 {
670  if (index >=0 && index < m_windowModel.count()) {
671  return m_windowModel[index].window;
672  } else {
673  return nullptr;
674  }
675 }
676 
677 lomiriapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
678 {
679  if (index >=0 && index < m_windowModel.count()) {
680  return m_windowModel[index].window->surface();
681  } else {
682  return nullptr;
683  }
684 }
685 
686 lomiriapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
687 {
688  if (index >=0 && index < m_windowModel.count()) {
689  return m_windowModel[index].application;
690  } else {
691  return nullptr;
692  }
693 }
694 
695 int TopLevelWindowModel::idAt(int index) const
696 {
697  if (index >=0 && index < m_windowModel.count()) {
698  return m_windowModel[index].window->id();
699  } else {
700  return 0;
701  }
702 }
703 
705 {
706  if (m_modelState == IdleState) {
707  DEBUG_MSG << "(id=" << id << ") - do it now.";
708  doRaiseId(id);
709  } else {
710  DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
711  // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
712  // if we perform yet another model change straight away.
713  //
714  // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
715  // the index is definitely within bounds.
716  QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
717  }
718 }
719 
720 void TopLevelWindowModel::doRaiseId(int id)
721 {
722  int fromIndex = indexForId(id);
723  // can't raise something that doesn't exist or that it's already on top
724  if (fromIndex != -1 && fromIndex != 0) {
725  auto surface = m_windowModel[fromIndex].window->surface();
726  if (surface && surface->live()) {
727  m_surfaceManager->raise(surface);
728  } else {
729  // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
730  // miral can do about it.
731  move(fromIndex, 0);
732  }
733  }
734 }
735 
736 void TopLevelWindowModel::setFocusedWindow(Window *window)
737 {
738  if (window != m_focusedWindow) {
739  DEBUG_MSG << "(" << window << ")";
740 
741  m_previousWindow = m_focusedWindow;
742 
743  m_focusedWindow = window;
744  Q_EMIT focusedWindowChanged(m_focusedWindow);
745 
746  if (m_previousWindow && m_previousWindow->focused() && !m_previousWindow->surface()) {
747  // do it ourselves. miral doesn't know about this window
748  m_previousWindow->setFocused(false);
749  }
750  }
751 
752  // Reset
753  m_pendingActivation = false;
754 }
755 
756 lomiriapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
757 {
758  return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
759 }
760 
762 {
763  return m_focusedWindow;
764 }
765 
766 void TopLevelWindowModel::move(int from, int to)
767 {
768  if (from == to) return;
769  DEBUG_MSG << " from=" << from << " to=" << to;
770 
771  if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
772  QModelIndex parent;
773  /* When moving an item down, the destination index needs to be incremented
774  by one, as explained in the documentation:
775  http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
776 
777  Q_ASSERT(m_modelState == IdleState);
778  m_modelState = MovingState;
779 
780  beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
781 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
782  const auto &window = m_windowModel.takeAt(from);
783  m_windowModel.insert(to, window);
784 #else
785  m_windowModel.move(from, to);
786 #endif
787  endMoveRows();
788 
789  Q_EMIT listChanged();
790  m_modelState = IdleState;
791 
792  DEBUG_MSG << " after " << toString();
793  }
794 }
795 void TopLevelWindowModel::onModificationsStarted()
796 {
797  m_surfaceManagerBusy = true;
798 }
799 
800 void TopLevelWindowModel::onModificationsEnded()
801 {
802  if (m_focusedWindowCleared) {
803  setFocusedWindow(nullptr);
804  }
805  // reset
806  m_focusedWindowCleared = false;
807  m_surfaceManagerBusy = false;
808 }
809 
810 void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
811 {
812  DEBUG_MSG << "(" << forbiddenId << ")";
813 
814  for (int i = 0; i < m_windowModel.count(); ++i) {
815  Window *window = m_windowModel[i].window;
816  if (window->id() != forbiddenId) {
817  window->activate();
818  break;
819  }
820  }
821 }
822 
823 void TopLevelWindowModel::refreshWindows()
824 {
825  DEBUG_MSG << "()";
826  clear();
827 
828  if (!m_workspace || !m_applicationManager || !m_surfaceManager) return;
829 
830  m_surfaceManager->forEachSurfaceInWorkspace(m_workspace->workspace(), [this](lomiri::shell::application::MirSurfaceInterface* surface) {
831  if (surface->parentSurface()) {
832  // Wrap it in a Window so that we keep focusedWindow() up to date.
833  Window *window = createWindow(surface);
834  connect(surface, &QObject::destroyed, window, [=](){
835  window->setSurface(nullptr);
836  window->deleteLater();
837  });
838  } else {
839  if (surface->type() == Mir::InputMethodType) {
840  setInputMethodWindow(createWindow(surface));
841  } else {
842  auto *application = m_applicationManager->findApplicationWithSurface(surface);
843  if (application) {
844  prependSurface(surface, application);
845  } else {
846  // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
847  // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
848  // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
849  Window *promptWindow = createWindow(surface);
850  connect(surface, &QObject::destroyed, promptWindow, [=](){
851  promptWindow->setSurface(nullptr);
852  promptWindow->deleteLater();
853  });
854  }
855  }
856  }
857  });
858 }
859 
860 void TopLevelWindowModel::clear()
861 {
862  DEBUG_MSG << "()";
863 
864  while(m_windowModel.count() > 0) {
865  ModelEntry entry = m_windowModel.takeAt(0);
866  disconnect(entry.window, 0, this, 0);
867  delete entry.window;
868  }
869  m_allSurfaces.clear();
870  setFocusedWindow(nullptr);
871  m_focusedWindowCleared = false;
872  m_previousWindow = nullptr;
873 }
874 
876 {
877  m_closingAllApps = true;
878  for (auto win : m_windowModel) {
879  win.window->close();
880  }
881 
882  // This is done after the for loop in the unlikely event that
883  // an app starts in between this
884  if (m_windowModel.isEmpty()) {
885  Q_EMIT closedAllWindows();
886  }
887 }
888 
890 {
891  return !m_nullWindow->focused();
892 }
893 
894 void TopLevelWindowModel::setRootFocus(bool focus)
895 {
896  DEBUG_MSG << "(" << focus << "), surfaceManagerBusy is " << m_surfaceManagerBusy;
897 
898  if (m_surfaceManagerBusy) {
899  // Something else is probably being focused already, let's not add to
900  // the noise.
901  return;
902  }
903 
904  if (focus) {
905  // Give focus back to previous focused window, only if null window is focused.
906  // If null window is not focused, a different app had taken the focus and we
907  // should repect that, or if a pendingActivation is going on.
908  if (m_previousWindow && !m_previousWindow->focused() && !m_pendingActivation &&
909  m_nullWindow == m_focusedWindow && m_previousWindow != m_nullWindow) {
910  m_previousWindow->activate();
911  } else if (!m_pendingActivation) {
912  // The previous window does not exist any more, focus top window.
913  activateTopMostWindowWithoutId(-1);
914  }
915  } else {
916  if (!m_nullWindow->focused()) {
917  m_nullWindow->activate();
918  }
919  }
920 }
921 
922 // Pending Activation will block refocus of previous focused window
923 // this is needed since surface activation with miral is async,
924 // and activation of placeholder is sync. This causes a race condition
925 // between placeholder and prev window. This results in prev window
926 // gets focused, as it will always be later than the placeholder.
928 {
929  m_pendingActivation = true;
930 }
TopLevelWindowModel::applicationAt
Q_INVOKABLE lomiri::shell::application::ApplicationInfoInterface * applicationAt(int index) const
Returns the application at the given index.
Definition: TopLevelWindowModel.cpp:686
TopLevelWindowModel::windowAt
Q_INVOKABLE Window * windowAt(int index) const
Returns the window at the given index.
Definition: TopLevelWindowModel.cpp:668
TopLevelWindowModel::listChanged
void listChanged()
Emitted when the list changes.
TopLevelWindowModel::surfaceAt
Q_INVOKABLE lomiri::shell::application::MirSurfaceInterface * surfaceAt(int index) const
Returns the surface at the given index.
Definition: TopLevelWindowModel.cpp:677
TopLevelWindowModel::indexForId
Q_INVOKABLE int indexForId(int id) const
Returns the index where the row with the given id is located.
Definition: TopLevelWindowModel.cpp:658
Window::surface
lomiri::shell::application::MirSurfaceInterface surface
Surface backing up this window It might be null if a surface hasn't been created yet (application is ...
Definition: Window.h:92
Window::id
int id
A unique identifier for this window. Useful for telling windows apart in a list model as they get mov...
Definition: Window.h:84
TopLevelWindowModel::inputMethodSurface
lomiri::shell::application::MirSurfaceInterface inputMethodSurface
The input method surface, if any.
Definition: TopLevelWindowModel.h:75
Window::focused
bool focused
Whether the surface is focused.
Definition: Window.h:71
TopLevelWindowModel::pendingActivation
Q_INVOKABLE void pendingActivation()
Sets pending activation flag.
Definition: TopLevelWindowModel.cpp:927
TopLevelWindowModel::focusedWindow
Window focusedWindow
The currently focused window, if any.
Definition: TopLevelWindowModel.h:80
Window
A slightly higher concept than MirSurface.
Definition: Window.h:47
Window::activate
void activate()
Focuses and raises the window.
Definition: Window.cpp:137
Window::state
Mir::State state
State of the surface.
Definition: Window.h:64
Window::focusRequested
void focusRequested()
Emitted when focus for this window is requested by an external party.
TopLevelWindowModel::raiseId
Q_INVOKABLE void raiseId(int id)
Raises the row with the given id to the top of the window stack (index == count-1)
Definition: TopLevelWindowModel.cpp:704
TopLevelWindowModel::closeAllWindows
Q_INVOKABLE void closeAllWindows()
Closes all windows, emits closedAllWindows when done.
Definition: TopLevelWindowModel.cpp:875
TopLevelWindowModel::idAt
Q_INVOKABLE int idAt(int index) const
Returns the unique id of the element at the given index.
Definition: TopLevelWindowModel.cpp:695
TopLevelWindowModel::rootFocus
bool rootFocus
Sets whether a user Window or "nothing" should be focused.
Definition: TopLevelWindowModel.h:107