2 * Copyright (C) 2013-2016 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17#include "ApplicationManager.h"
18#include "ApplicationInfo.h"
19#include "MirSurface.h"
25#include <QGuiApplication>
28#include <QQmlComponent>
31#include <QtDBus/QtDBus>
33#define APPLICATIONMANAGER_DEBUG 0
35#if APPLICATIONMANAGER_DEBUG
36#define DEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << __func__ << " " << params
38#define DEBUG_MSG(params) ((void)0)
41namespace unityapi = unity::shell::application;
43ApplicationManager *ApplicationManager::the_application_manager = nullptr;
45ApplicationManager *ApplicationManager::singleton()
47 if (!the_application_manager) {
48 the_application_manager = new ApplicationManager();
50 // Emit signal to notify Upstart that Mir is ready to receive client connections
51 // see http://upstart.ubuntu.com/cookbook/#expect-stop
52 if (qgetenv("UNITY_MIR_EMITS_SIGSTOP") == "1") {
56 return the_application_manager;
59ApplicationManager::ApplicationManager(QObject *parent)
60 : ApplicationManagerInterface(parent)
62 buildListOfAvailableApplications();
64 // polling to find out when the toplevel window has been created as there's
65 // no signal telling us that
66 connect(&m_windowCreatedTimer, &QTimer::timeout,
67 this, &ApplicationManager::onWindowCreatedTimerTimeout);
68 m_windowCreatedTimer.setSingleShot(false);
69 m_windowCreatedTimer.start(200);
71 connect(MirFocusController::instance(), &MirFocusController::focusedSurfaceChanged,
72 this, &ApplicationManager::updateFocusedApplication, Qt::QueuedConnection);
75ApplicationManager::~ApplicationManager()
79void ApplicationManager::onWindowCreatedTimerTimeout()
81 if (QGuiApplication::topLevelWindows().count() > 0) {
82 m_windowCreatedTimer.stop();
87void ApplicationManager::onWindowCreated()
89 startApplication("unity8-dash");
90 focusApplication("unity8-dash");
93int ApplicationManager::rowCount(const QModelIndex& parent) const {
94 return !parent.isValid() ? m_runningApplications.size() : 0;
97QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
98 if (index.row() < 0 || index.row() >= m_runningApplications.size())
101 auto app = m_runningApplications.at(index.row());
108 return app->comment();
116 return app->focused();
118 return app->isTouchApp();
119 case RoleExemptFromLifecycle:
120 return app->exemptFromLifecycle();
121 case RoleApplication:
122 return QVariant::fromValue(static_cast<unityapi::ApplicationInfoInterface*>(app));
128ApplicationInfo *ApplicationManager::get(int row) const {
129 if (row < 0 || row >= m_runningApplications.size())
131 return m_runningApplications.at(row);
134ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const {
135 for (ApplicationInfo *app : m_runningApplications) {
136 if (app->appId() == appId) {
143QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
145 for (int i = 0; i < m_runningApplications.size(); ++i) {
146 if (m_runningApplications.at(i) == application) {
151 return QModelIndex();
154bool ApplicationManager::add(ApplicationInfo *application) {
155 if (!application || m_runningApplications.contains(application)) {
158 DEBUG_MSG(application->appId());
160 application->setState(ApplicationInfo::Starting);
162 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
163 m_runningApplications.append(application);
165 connect(application, &ApplicationInfo::focusedChanged, this, [application, this]() {
166 QModelIndex appIndex = findIndex(application);
167 if (!appIndex.isValid()) return;
168 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
170 connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
171 QModelIndex appIndex = findIndex(application);
172 if (!appIndex.isValid()) return;
173 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
175 connect(application, &ApplicationInfo::stageChanged, this, [application, this]() {
176 QModelIndex appIndex = findIndex(application);
177 if (!appIndex.isValid()) return;
178 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleStage);
181 connect(application, &ApplicationInfo::closed, this, [application, this]() {
182 this->remove(application);
184 connect(application, &ApplicationInfo::focusRequested, this, [application, this]() {
185 Q_EMIT this->focusRequested(application->appId());
189 Q_EMIT countChanged();
190 if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
195void ApplicationManager::remove(ApplicationInfo *application) {
196 int i = m_runningApplications.indexOf(application);
198 DEBUG_MSG(application->appId());
199 beginRemoveRows(QModelIndex(), i, i);
200 m_runningApplications.removeAt(i);
202 Q_EMIT countChanged();
203 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
205 application->disconnect(this);
208void ApplicationManager::move(int from, int to) {
209 if (from == to) return;
211 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
213 /* When moving an item down, the destination index needs to be incremented
214 * by one, as explained in the documentation:
215 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
216 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
217 m_runningApplications.move(from, to);
222ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
223 const QStringList &arguments)
228 ApplicationInfo *application = findApplication(appId);
230 // the requested app is already running
233 application = add(appId);
236 // most likely not among the available ones
240 Q_EMIT application->focusRequested(); // we assume that an application that's starting up wants focus
245ApplicationInfo* ApplicationManager::add(QString appId)
247 ApplicationInfo *application = nullptr;
249 for (ApplicationInfo *availableApp : m_availableApplications) {
250 if (availableApp->appId() == appId) {
251 application = availableApp;
257 if (!add(application)) {
258 application = nullptr;
265bool ApplicationManager::stopApplication(const QString &appId)
268 ApplicationInfo *application = findApplication(appId);
269 if (application == nullptr)
272 if (application->appId() == focusedApplicationId()) {
273 unfocusCurrentApplication();
275 application->close();
279QString ApplicationManager::focusedApplicationId() const {
280 for (ApplicationInfo *app : m_runningApplications) {
281 if (app->focused()) {
288bool ApplicationManager::focusApplication(const QString &appId)
290 ApplicationInfo *application = findApplication(appId);
291 if (application == nullptr)
294 application->setFocused(true);
299bool ApplicationManager::requestFocusApplication(const QString &appId)
301 ApplicationInfo *application = findApplication(appId);
302 if (application == nullptr)
305 application->requestFocus();
310void ApplicationManager::unfocusCurrentApplication()
312 for (ApplicationInfo *app : m_runningApplications) {
313 if (app->focused()) {
314 app->setFocused(false);
319void ApplicationManager::buildListOfAvailableApplications()
323 Be careful when changing application properties here as some qmltests
324 rely on them being the way it's specified here (e.g. that camera-app
325 is fullscreen, that twitter-webapp can rotate in all directions, etc)
328 ApplicationInfo *application;
330 application = new ApplicationInfo(this);
331 application->setAppId("unity8-dash");
332 application->setName("Unity 8 Mock Dash");
333 application->setScreenshotId("unity8-dash");
334 application->setSupportedOrientations(Qt::PrimaryOrientation);
335 m_availableApplications.append(application);
337 application = new ApplicationInfo(this);
338 application->setAppId("dialer-app");
339 application->setName("Dialer");
340 application->setScreenshotId("dialer");
341 application->setIconId("dialer-app");
342 application->setSupportedOrientations(Qt::PortraitOrientation
343 | Qt::InvertedPortraitOrientation);
344 m_availableApplications.append(application);
346 application = new ApplicationInfo(this);
347 application->setAppId("camera-app");
348 application->setName("Camera");
349 application->setScreenshotId("camera");
350 application->setIconId("camera");
351 application->setFullscreen(true);
352 application->setSupportedOrientations(Qt::PortraitOrientation
353 | Qt::LandscapeOrientation
354 | Qt::InvertedPortraitOrientation
355 | Qt::InvertedLandscapeOrientation);
356 application->setRotatesWindowContents(true);
357 m_availableApplications.append(application);
359 application = new ApplicationInfo(this);
360 application->setAppId("gallery-app");
361 application->setName("Gallery");
362 application->setScreenshotId("gallery");
363 application->setIconId("gallery");
364 application->setShellChrome(Mir::LowChrome);
365 application->setStage(ApplicationInfo::MainStage);
366 m_availableApplications.append(application);
368 application = new ApplicationInfo(this);
369 application->setAppId("facebook-webapp");
370 application->setName("Facebook");
371 application->setScreenshotId("facebook");
372 application->setIconId("facebook");
373 m_availableApplications.append(application);
375 application = new ApplicationInfo(this);
376 application->setAppId("webbrowser-app");
377 application->setShellChrome(Mir::LowChrome);
378 application->setName("Browser");
379 application->setScreenshotId("browser");
380 application->setIconId("browser");
381 m_availableApplications.append(application);
383 application = new ApplicationInfo(this);
384 application->setAppId("twitter-webapp");
385 application->setName("Twitter");
386 application->setScreenshotId("twitter");
387 application->setIconId("twitter");
388 m_availableApplications.append(application);
390 application = new ApplicationInfo(this);
391 application->setAppId("map");
392 application->setName("Map");
393 application->setIconId("map");
394 application->setScreenshotId("map");
395 m_availableApplications.append(application);
397 application = new ApplicationInfo(this);
398 application->setAppId("gmail-webapp");
399 application->setName("GMail");
400 application->setIconId("gmail");
401 application->setScreenshotId("gmail-webapp.svg");
402 application->setStage(ApplicationInfo::MainStage);
403 application->setSupportedOrientations(Qt::PortraitOrientation
404 | Qt::LandscapeOrientation
405 | Qt::InvertedPortraitOrientation
406 | Qt::InvertedLandscapeOrientation);
407 m_availableApplications.append(application);
409 application = new ApplicationInfo(this);
410 application->setAppId("music-app");
411 application->setName("Music");
412 application->setIconId("soundcloud");
413 application->setScreenshotId("music");
414 application->setStage(ApplicationInfo::MainStage);
415 application->setSupportedOrientations(Qt::PortraitOrientation
416 | Qt::LandscapeOrientation
417 | Qt::InvertedPortraitOrientation
418 | Qt::InvertedLandscapeOrientation);
419 m_availableApplications.append(application);
421 application = new ApplicationInfo(this);
422 application->setAppId("ubuntu-weather-app");
423 application->setName("Weather");
424 application->setIconId("weather");
425 application->setScreenshotId("ubuntu-weather-app.svg");
426 application->setSupportedOrientations(Qt::LandscapeOrientation
427 | Qt::InvertedLandscapeOrientation);
428 m_availableApplications.append(application);
430 application = new ApplicationInfo(this);
431 application->setAppId("notes-app");
432 application->setName("Notepad");
433 application->setIconId("notepad");
434 m_availableApplications.append(application);
436 application = new ApplicationInfo(this);
437 application->setAppId("calendar-app");
438 application->setName("Calendar");
439 application->setIconId("calendar");
440 m_availableApplications.append(application);
442 application = new ApplicationInfo(this);
443 application->setAppId("evernote");
444 application->setName("Evernote");
445 application->setIconId("evernote");
446 m_availableApplications.append(application);
448 application = new ApplicationInfo(this);
449 application->setAppId("pinterest");
450 application->setName("Pinterest");
451 application->setIconId("pinterest");
452 m_availableApplications.append(application);
454 application = new ApplicationInfo(this);
455 application->setAppId("soundcloud");
456 application->setName("SoundCloud");
457 application->setIconId("soundcloud");
458 m_availableApplications.append(application);
460 application = new ApplicationInfo(this);
461 application->setAppId("wikipedia");
462 application->setName("Wikipedia");
463 application->setIconId("wikipedia");
464 m_availableApplications.append(application);
466 application = new ApplicationInfo(this);
467 application->setAppId("youtube");
468 application->setName("YouTube");
469 application->setIconId("youtube");
470 m_availableApplications.append(application);
472 application = new ApplicationInfo(this);
473 application->setAppId("libreoffice");
474 application->setName("LibreOffice");
475 application->setIconId("libreoffice");
476 application->setScreenshotId("libreoffice");
477 application->setIsTouchApp(false);
478 m_availableApplications.append(application);
482QStringList ApplicationManager::availableApplications()
485 Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
486 appIds << app->appId();
491bool ApplicationManager::isEmpty() const
493 return m_runningApplications.isEmpty();
496void ApplicationManager::updateFocusedApplication()
498 ApplicationInfo *focusedApplication = nullptr;
499 ApplicationInfo *previouslyFocusedApplication = nullptr;
501 MirSurface *surface = static_cast<MirSurface*>(MirFocusController::instance()->focusedSurface());
503 focusedApplication = findApplication(surface);
506 surface = static_cast<MirSurface*>(MirFocusController::instance()->previouslyFocusedSurface());
508 previouslyFocusedApplication = findApplication(surface);
511 if (focusedApplication != previouslyFocusedApplication) {
512 if (focusedApplication) {
513 DEBUG_MSG("focused " << focusedApplication->appId());
514 Q_EMIT focusedApplication->focusedChanged(true);
515 this->move(this->m_runningApplications.indexOf(focusedApplication), 0);
517 if (previouslyFocusedApplication) {
518 DEBUG_MSG("unfocused " << previouslyFocusedApplication->appId());
519 Q_EMIT previouslyFocusedApplication->focusedChanged(false);
521 Q_EMIT focusedApplicationIdChanged();
525ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
527 for (ApplicationInfo *app : m_runningApplications) {
528 auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
529 if (surfaceList->contains(surface)) {
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list