1 #include "config.h"
2 
3 #include "LauncherMainWindow.hxx"
4 
5 // Qt headers
6 #include <QMessageBox>
7 #include <QSettings>
8 #include <QDebug>
9 #include <QMenu>
10 #include <QMenuBar>
11 
12 #include <QOpenGLContext>
13 #include <QQmlComponent>
14 #include <QQmlContext>
15 #include <QQmlEngine>
16 #include <QQmlError>
17 #include <QQmlFileSelector>
18 #include <QQuickItem>
19 
20 #include "version.h"
21 
22 // launcher headers
23 #include "AddOnsController.hxx"
24 #include "AircraftModel.hxx"
25 #include "DefaultAircraftLocator.hxx"
26 #include "LaunchConfig.hxx"
27 #include "LauncherController.hxx"
28 #include "LauncherNotificationsController.hxx"
29 #include "LauncherPackageDelegate.hxx"
30 #include "LocalAircraftCache.hxx"
31 #include "LocationController.hxx"
32 #include "QtLauncher.hxx"
33 #include "UpdateChecker.hxx"
34 #include "GettingStartedTip.hxx"
35 
36 #include <Main/sentryIntegration.hxx>
37 
38 //////////////////////////////////////////////////////////////////////////////
39 
LauncherMainWindow(bool inSimMode)40 LauncherMainWindow::LauncherMainWindow(bool inSimMode) : QQuickView()
41 {
42     setTitle("FlightGear " FLIGHTGEAR_VERSION);
43 
44     m_controller = new LauncherController(this, this);
45     m_controller->initQML();
46 
47     // use a direct connection to be notified synchronously when the render thread
48     // starts OpenGL. We use this to log the OpenGL information from the
49     // context at that time, for tracing purposes.
50     connect(this, &QQuickWindow::sceneGraphInitialized,
51             this, &LauncherMainWindow::renderTheadSceneGraphInitialized,
52             Qt::DirectConnection);
53 
54     if (!inSimMode) {
55 #if defined(Q_OS_MAC)
56         QMenuBar* mb = new QMenuBar();
57 
58         QMenu* fileMenu = mb->addMenu(tr("File"));
59         QAction* openAction = new QAction(tr("Open saved configuration..."));
60         openAction->setMenuRole(QAction::NoRole);
61         connect(openAction, &QAction::triggered,
62                 m_controller, &LauncherController::openConfig);
63 
64         QAction* saveAction = new QAction(tr("Save configuration as..."));
65         saveAction->setMenuRole(QAction::NoRole);
66         connect(saveAction, &QAction::triggered,
67                 m_controller, &LauncherController::saveConfigAs);
68 
69         fileMenu->addAction(openAction);
70         fileMenu->addAction(saveAction);
71 
72         QMenu* toolsMenu = mb->addMenu(tr("Tools"));
73         QAction* restoreDefaultsAction = new QAction(tr("Restore defaults..."));
74         restoreDefaultsAction->setMenuRole(QAction::NoRole);
75         connect(restoreDefaultsAction, &QAction::triggered,
76                 m_controller, &LauncherController::requestRestoreDefaults);
77 
78         QAction* changeDataAction = new QAction(tr("Select data files location..."));
79         changeDataAction->setMenuRole(QAction::NoRole);
80         connect(changeDataAction, &QAction::triggered,
81                 m_controller, &LauncherController::requestChangeDataPath);
82 
83         QAction* viewCommandLineAction = new QAction(tr("View command-line"));
84         connect(viewCommandLineAction, &QAction::triggered,
85                 m_controller, &LauncherController::viewCommandLine);
86 
87         toolsMenu->addAction(restoreDefaultsAction);
88         toolsMenu->addAction(changeDataAction);
89         toolsMenu->addAction(viewCommandLineAction);
90 #endif
91 
92         QAction* qa = new QAction(this);
93         qa->setMenuRole(QAction::QuitRole); // will be addeed accordingly
94         qa->setShortcut(QKeySequence("Ctrl+Q"));
95         connect(qa, &QAction::triggered, m_controller, &LauncherController::quit);
96     }
97 
98     const bool haveQQC2 = checkQQC2Availability();
99 
100     connect(this, &QQuickView::statusChanged, this, &LauncherMainWindow::onQuickStatusChanged);
101 
102     m_controller->initialRestoreSettings();
103 
104     ////////////
105 #if defined(Q_OS_WIN)
106     const QString osName("win");
107 #elif defined(Q_OS_MAC)
108     const QString osName("mac");
109 #else
110     const QString osName("unix");
111 #endif
112 
113 
114     setResizeMode(QQuickView::SizeRootObjectToView);
115     engine()->addImportPath("qrc:///");
116 
117     // allow selecting different QML files based on the Qt version we are
118     // compiled against
119     auto selector = new QQmlFileSelector(engine(), this);
120 #if QT_VERSION >= 0x050600
121     selector->setExtraSelectors({"qt56"});
122 #endif
123 
124 #if QT_VERSION >= 0x050700
125     if (haveQQC2) {
126       selector->setExtraSelectors({"qt56", "qt57"});
127     }
128 #endif
129 
130 
131     QQmlContext* ctx = rootContext();
132     ctx->setContextProperty("_launcher", m_controller);
133     ctx->setContextProperty("_config", m_controller->config());
134     ctx->setContextProperty("_location", m_controller->location());
135     ctx->setContextProperty("_osName", osName);
136 
137     auto updater = new UpdateChecker(this);
138     ctx->setContextProperty("_updates", updater);
139 
140     auto packageDelegate = new LauncherPackageDelegate(this);
141     ctx->setContextProperty("_packages", packageDelegate);
142 
143     auto notifications = new LauncherNotificationsController{this, engine()};
144     ctx->setContextProperty("_notifications", notifications);
145 
146     if (!inSimMode) {
147         auto addOnsCtl = new AddOnsController(this, m_controller->config());
148         ctx->setContextProperty("_addOns", addOnsCtl);
149     }
150 
151 #if defined(ENABLE_COMPOSITOR)
152     ctx->setContextProperty("_haveCompositor", true);
153 #else
154     ctx->setContextProperty("_haveCompositor", false);
155 #endif
156 
157     auto weatherScenariosModel = new flightgear::WeatherScenariosModel(this);
158     ctx->setContextProperty("_weatherScenarios", weatherScenariosModel);
159 
160     setSource(QUrl("qrc:///qml/Launcher.qml"));
161 }
162 
checkQQC2Availability()163 bool LauncherMainWindow::checkQQC2Availability()
164 {
165   QQmlComponent comp(engine());
166   comp.setData(R"(
167                import QtQuick.Controls 2.0
168                ScrollBar {
169                }
170                )", {});
171   if (comp.isError()) {
172     return false;
173   }
174 
175 
176   auto item = comp.create();
177   const bool haveQQC2 = (item != nullptr);
178   if (item)
179     item->deleteLater();
180   return haveQQC2;
181 }
182 
onQuickStatusChanged(QQuickView::Status status)183 void LauncherMainWindow::onQuickStatusChanged(QQuickView::Status status)
184 {
185     if (status == QQuickView::Error) {
186         QString errorString;
187 
188         Q_FOREACH (auto err, errors()) {
189             errorString.append("\n" + err.toString());
190         }
191 
192         QMessageBox::critical(nullptr, "UI loading failures.",
193                               tr("Problems occurred loading the user interface. This is usually due to missing modules on your system. "
194                                  "Please report this error to the FlightGear developer list or forum, and take care to mention your system "
195                                  "distribution, etc. Please also include the information provided below.\n") +
196                                   errorString);
197     }
198 }
199 
~LauncherMainWindow()200 LauncherMainWindow::~LauncherMainWindow()
201 {
202 }
203 
event(QEvent * event)204 bool LauncherMainWindow::event(QEvent *event)
205 {
206     if (event->type() == QEvent::Close) {
207         m_controller->saveSettings();
208     }
209     return QQuickView::event(event);
210 }
211 
execInApp()212 bool LauncherMainWindow::execInApp()
213 {
214 	m_controller->setInAppMode();
215 
216     show();
217 
218     while (m_controller->keepRunningInAppMode()) {
219         qApp->processEvents();
220     }
221 
222     return m_controller->inAppResult();
223 }
224 
225 // this slot runs in the context of the render thread. Don't modify
226 // any non-protected state here. We're using it soley to grab the current
227 // QOpenGLContext and thus the driver/vendor info
renderTheadSceneGraphInitialized()228 void LauncherMainWindow::renderTheadSceneGraphInitialized()
229 {
230     auto qContext = QOpenGLContext::currentContext();
231     if (!qContext) {
232         qWarning() << Q_FUNC_INFO << "No current OpenGL context";
233         return;
234     }
235 
236     std::string renderer = (char*)glGetString(GL_RENDERER);
237     // capture this to help with debugging this crash:
238     // https://sentry.io/share/issue/f98e38dceb4241dbaeed944d6ce4d746/
239     // https://bugreports.qt.io/browse/QTBUG-69703
240     flightgear::addSentryTag("qt-gl-vendor", (char*)glGetString(GL_VENDOR));
241     flightgear::addSentryTag("qt-gl-renderer", renderer.c_str());
242     flightgear::addSentryTag("qt-gl-version", (char*)glGetString(GL_VERSION));
243     flightgear::addSentryTag("qt-glsl-version", (char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
244 
245     const char* gltype[] = {"Desktop", "GLES 2", "GLES 1"};
246     flightgear::addSentryTag("qt-gl-module-type", gltype[QOpenGLContext::openGLModuleType()]);
247 
248     // if necessary, borrow more code from:
249     // https://code.qt.io/cgit/qt/qtbase.git/tree/examples/opengl/contextinfo/widget.cpp?h=5.15#n358
250     // to expand what this reports
251 }
252