Unity 8
ShellApplication.cpp
1 /*
2  * Copyright (C) 2015 Canonical, Ltd.
3  *
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.
7  *
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.
12  *
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/>.
15  */
16 
17 #include "ShellApplication.h"
18 
19 // Qt
20 #include <QLibrary>
21 #include <QScreen>
22 
23 #include <libintl.h>
24 
25 // libandroid-properties
26 #include <hybris/properties/properties.h>
27 
28 // local
29 #include <paths.h>
30 #include "CachingNetworkManagerFactory.h"
31 #include "UnityCommandLineParser.h"
32 
33 ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
34  : QGuiApplication(argc, argv)
35 {
36 
37  setApplicationName(QStringLiteral("unity8"));
38 
39  connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
40 
41  setupQmlEngine(isMirServer);
42 
43  UnityCommandLineParser parser(*this);
44 
45  if (!parser.deviceName().isEmpty()) {
46  m_deviceName = parser.deviceName();
47  } else {
48  char buffer[200];
49  property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
50  m_deviceName = QString(buffer);
51  }
52  m_qmlArgs.setDeviceName(m_deviceName);
53 
54  m_qmlArgs.setMode(parser.mode());
55 
56  // The testability driver is only loaded by QApplication but not by QGuiApplication.
57  // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
58  if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
59  QLibrary testLib(QStringLiteral("qttestability"));
60  if (testLib.load()) {
61  typedef void (*TasInitialize)(void);
62  TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
63  if (initFunction) {
64  initFunction();
65  } else {
66  qCritical("Library qttestability resolve failed!");
67  }
68  } else {
69  qCritical("Library qttestability load failed!");
70  }
71  }
72 
73  bindtextdomain("unity8", translationDirectory().toUtf8().data());
74  textdomain("unity8");
75 
76  m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
77 
78  if (parser.windowGeometry().isValid()) {
79  m_shellView->setWidth(parser.windowGeometry().width());
80  m_shellView->setHeight(parser.windowGeometry().height());
81  }
82 
83  if (parser.hasFrameless()) {
84  m_shellView->setFlags(Qt::FramelessWindowHint);
85  }
86 
87 
88  #ifdef UNITY8_ENABLE_TOUCH_EMULATION
89  // You will need this if you want to interact with touch-only components using a mouse
90  // Needed only when manually testing on a desktop.
91  if (parser.hasMouseToTouch()) {
92  m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
93  }
94  #endif
95 
96 
97  // Some hard-coded policy for now.
98  // NB: We don't support more than two screens at the moment
99  //
100  // TODO: Support an arbitrary number of screens and different policies
101  // (eg cloned desktop, several desktops, etc)
102  if (isMirServer && screens().count() == 2) {
103  m_shellView->setScreen(screens().at(1));
104  m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
105 
106  m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
107  m_secondaryWindow->setScreen(screens().at(0));
108  // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
109  m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
110  m_secondaryWindow->setVisible(true);
111  }
112 
113  if (isMirServer || parser.hasFullscreen()) {
114  m_shellView->showFullScreen();
115  } else {
116  m_shellView->show();
117  }
118 }
119 
120 ShellApplication::~ShellApplication()
121 {
122  destroyResources();
123 }
124 
125 void ShellApplication::destroyResources()
126 {
127  // Deletion order is important. Don't use QScopedPointers and the like
128  // Otherwise the process will hang on shutdown (bug somewhere I guess).
129  delete m_shellView;
130  m_shellView = nullptr;
131 
132  delete m_secondaryWindow;
133  m_secondaryWindow = nullptr;
134 
135  #ifdef UNITY8_ENABLE_TOUCH_EMULATION
136  delete m_mouseTouchAdaptor;
137  m_mouseTouchAdaptor = nullptr;
138  #endif
139 
140  delete m_qmlEngine;
141  m_qmlEngine = nullptr;
142 }
143 
144 void ShellApplication::setupQmlEngine(bool isMirServer)
145 {
146  m_qmlEngine = new QQmlEngine(this);
147 
148  m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
149 
150  prependImportPaths(m_qmlEngine, ::overrideImportPaths());
151  if (!isMirServer) {
152  prependImportPaths(m_qmlEngine, ::nonMirImportPaths());
153  }
154  appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
155 
156  m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
157 
158  QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
159 }
160 
161 void ShellApplication::onScreenAdded(QScreen * /*screen*/)
162 {
163  // TODO: Support an arbitrary number of screens and different policies
164  // (eg cloned desktop, several desktops, etc)
165  if (screens().count() == 2) {
166  m_shellView->setScreen(screens().at(1));
167  m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
168  // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
169  // its backing QPlatformWindow recreated). So lets refocus it.
170  m_shellView->requestActivate();
171  // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
172  // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
173  m_shellView->setVisible(true);
174 
175  m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
176  m_secondaryWindow->setScreen(screens().at(0));
177 
178  // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
179  m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
180  m_secondaryWindow->setVisible(true);
181  }
182 }
183 
184 void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
185 {
186  // TODO: Support an arbitrary number of screens and different policies
187  // (eg cloned desktop, several desktops, etc)
188  if (screen == m_shellView->screen()) {
189  const QList<QScreen *> allScreens = screens();
190  Q_ASSERT(allScreens.count() > 1);
191  Q_ASSERT(allScreens.at(0) != screen);
192  Q_ASSERT(m_secondaryWindow);
193  delete m_secondaryWindow;
194  m_secondaryWindow = nullptr;
195  m_shellView->setScreen(allScreens.first());
196  m_qmlArgs.setDeviceName(m_deviceName);
197  // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
198  // its backing QPlatformWindow recreated). So lets refocus it.
199  m_shellView->requestActivate();
200  // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
201  // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
202  m_shellView->setVisible(true);
203  }
204 }