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