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