d2967ca264edfc352be12d4415fca42833af1324
[apps/homescreen.git] / homescreen / src / main.cpp
1 // SPDX-License-Identifier: Apache-2.0
2 /*
3  * Copyright (C) 2016, 2017 Mentor Graphics Development (Deutschland) GmbH
4  * Copyright (c) 2017, 2018 TOYOTA MOTOR CORPORATION
5  * Copyright (c) 2022 Konsulko Group
6  */
7
8 #include <QGuiApplication>
9 #include <QCommandLineParser>
10 #include <QtCore/QUrlQuery>
11 #include <QtGui/QGuiApplication>
12 #include <QtQml/QQmlApplicationEngine>
13 #include <QtQml/QQmlContext>
14 #include <QtQml/QQmlComponent>
15 #include <QtQml/qqml.h>
16 #include <QQuickWindow>
17 #include <QTimer>
18
19 #include <weather.h>
20 #include <bluetooth.h>
21
22 #include "applicationlauncher.h"
23 #include "statusbarmodel.h"
24 #include "mastervolume.h"
25 #include "homescreenhandler.h"
26 #include "hmi-debug.h"
27
28 // meson will define these
29 #include QT_QPA_HEADER
30 #include <wayland-client.h>
31
32 #include "agl-shell-client-protocol.h"
33 #include "shell.h"
34
35 #ifndef MIN
36 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
37 #endif
38
39 QScreen *
40 find_screen(const char *screen_name)
41 {
42         QList<QScreen *> screens = qApp->screens();
43         QScreen *found = nullptr;
44         QString qstr_name = QString::fromUtf8(screen_name, -1);
45
46         for (QScreen *xscreen : screens) {
47                 if (qstr_name == xscreen->name()) {
48                         found = xscreen;
49                         break;
50                 }
51         }
52
53         return found;
54 }
55
56 struct shell_data {
57         struct agl_shell *shell;
58         HomescreenHandler *homescreenHandler;
59         bool wait_for_bound;
60         bool bound_ok;
61         int ver;
62 };
63
64 static void
65 agl_shell_bound_ok(void *data, struct agl_shell *agl_shell)
66 {
67         struct shell_data *shell_data = static_cast<struct shell_data *>(data);
68         shell_data->wait_for_bound = false;
69
70         shell_data->bound_ok = true;
71 }
72
73 static void
74 agl_shell_bound_fail(void *data, struct agl_shell *agl_shell)
75 {
76         struct shell_data *shell_data = static_cast<struct shell_data *>(data);
77         shell_data->wait_for_bound = false;
78
79         shell_data->bound_ok = false;
80 }
81
82 static void
83 agl_shell_app_state(void *data, struct agl_shell *agl_shell,
84                 const char *app_id, uint32_t state)
85 {
86         struct shell_data *shell_data = static_cast<struct shell_data *>(data);
87         HomescreenHandler *homescreenHandler = shell_data->homescreenHandler;
88
89         if (!homescreenHandler)
90                 return;
91
92         switch (state) {
93         case AGL_SHELL_APP_STATE_STARTED:
94                 qDebug() << "Got AGL_SHELL_APP_STATE_STARTED for app_id " << app_id;
95                 homescreenHandler->processAppStatusEvent(app_id, "started");
96                 break;
97         case AGL_SHELL_APP_STATE_TERMINATED:
98                 qDebug() << "Got AGL_SHELL_APP_STATE_TERMINATED for app_id " << app_id;
99                 // handled by HomescreenHandler::processAppStatusEvent
100                 break;
101         case AGL_SHELL_APP_STATE_ACTIVATED:
102                 qDebug() << "Got AGL_SHELL_APP_STATE_ACTIVATED for app_id " << app_id;
103                 homescreenHandler->addAppToStack(app_id);
104                 break;
105         default:
106                 break;
107         }
108 }
109
110 static void
111 agl_shell_app_on_output(void *data, struct agl_shell *agl_shell,
112                 const char *app_id, const char *output_name)
113 {
114         struct shell_data *shell_data = static_cast<struct shell_data *>(data);
115         HomescreenHandler *homescreenHandler = shell_data->homescreenHandler;
116
117         if (!homescreenHandler)
118                 return;
119
120         std::pair new_pending_app = std::pair(QString(app_id),
121                                               QString(output_name));
122         homescreenHandler->pending_app_list.push_back(new_pending_app);
123 }
124
125
126 #ifdef AGL_SHELL_BOUND_OK_SINCE_VERSION
127 static const struct agl_shell_listener shell_listener = {
128         agl_shell_bound_ok,
129         agl_shell_bound_fail,
130         agl_shell_app_state,
131         agl_shell_app_on_output,
132 };
133 #endif
134
135 static void
136 global_add(void *data, struct wl_registry *reg, uint32_t name,
137            const char *interface, uint32_t ver)
138 {
139         struct shell_data *shell_data = static_cast<struct shell_data *>(data);
140
141         if (!shell_data)
142                 return;
143
144         if (strcmp(interface, agl_shell_interface.name) == 0) {
145                 if (ver >= 2) {
146                         shell_data->shell =
147                                 static_cast<struct agl_shell *>(
148                                         wl_registry_bind(reg, name, &agl_shell_interface, MIN(8, ver)));
149 #ifdef AGL_SHELL_BOUND_OK_SINCE_VERSION
150                         agl_shell_add_listener(shell_data->shell, &shell_listener, data);
151 #endif
152                 } else {
153                         shell_data->shell =
154                                 static_cast<struct agl_shell *>(
155                                         wl_registry_bind(reg, name, &agl_shell_interface, 1));
156                 }
157                 shell_data->ver = ver;
158
159         }
160 }
161
162 static void
163 global_remove(void *data, struct wl_registry *reg, uint32_t id)
164 {
165         /* Don't care */
166         (void) data;
167         (void) reg;
168         (void) id;
169 }
170
171 static const struct wl_registry_listener registry_listener = {
172         global_add,
173         global_remove,
174 };
175
176 static struct wl_surface *
177 getWlSurface(QPlatformNativeInterface *native, QWindow *window)
178 {
179         void *surf = native->nativeResourceForWindow("surface", window);
180         return static_cast<struct ::wl_surface *>(surf);
181 }
182
183 static struct wl_output *
184 getWlOutput(QPlatformNativeInterface *native, QScreen *screen)
185 {
186         void *output = native->nativeResourceForScreen("output", screen);
187         return static_cast<struct ::wl_output*>(output);
188 }
189
190 static struct wl_display *
191 getWlDisplay(QPlatformNativeInterface *native)
192 {
193        return static_cast<struct wl_display *>(
194                native->nativeResourceForIntegration("display")
195        );
196 }
197
198
199 static void
200 register_agl_shell(QPlatformNativeInterface *native, struct shell_data *shell_data)
201 {
202         struct wl_display *wl;
203         struct wl_registry *registry;
204
205         wl = getWlDisplay(native);
206         registry = wl_display_get_registry(wl);
207
208         wl_registry_add_listener(registry, &registry_listener, shell_data);
209
210         /* Roundtrip to get all globals advertised by the compositor */
211         wl_display_roundtrip(wl);
212         wl_registry_destroy(registry);
213 }
214
215 static struct wl_surface *
216 create_component(QPlatformNativeInterface *native, QQmlComponent *comp,
217                  QScreen *screen, QObject **qobj)
218 {
219         QObject *obj = comp->create();
220         obj->setParent(screen);
221
222         QWindow *win = qobject_cast<QWindow *>(obj);
223         *qobj = obj;
224
225         return getWlSurface(native, win);
226 }
227
228
229 static void
230 load_agl_shell(QPlatformNativeInterface *native, QQmlApplicationEngine *engine,
231                struct agl_shell *agl_shell, QScreen *screen)
232 {
233         struct wl_surface *bg;
234         struct wl_output *output;
235         int32_t x, y;
236         int32_t width, height;
237         QObject *qobj_bg;
238         QSize size = screen->size();
239
240         // this incorporates the panels directly, but in doing so, it
241         // would also need to specify an activation area the same area
242         // in order to void overlapping any new activation window
243         QQmlComponent bg_comp(engine, QUrl("qrc:/background_with_panels.qml"));
244         qInfo() << bg_comp.errors();
245
246         bg = create_component(native, &bg_comp, screen, &qobj_bg);
247
248         output = getWlOutput(native, screen);
249
250         qDebug() << "Normal mode - with single surface";
251         qDebug() << "Setting homescreen to screen  " << screen->name();
252         agl_shell_set_background(agl_shell, bg, output);
253
254         // 216 is the width size of the panel
255         x = 0;
256         y = 216;
257
258         width  = size.width();
259         height = size.height() - (2 * y);
260
261         qDebug() << "Using custom rectangle " << width << "x" << height
262                 << "+" << x << "x" << y << " for activation";
263         qDebug() << "Panels should be embedded the background surface";
264
265 #ifdef AGL_SHELL_SET_ACTIVATE_REGION_SINCE_VERSION
266         agl_shell_set_activate_region(agl_shell, output,
267                                       x, y, width, height);
268 #endif
269 }
270
271 static void
272 load_agl_shell_for_ci(QPlatformNativeInterface *native,
273                       QQmlApplicationEngine *engine,
274                       struct agl_shell *agl_shell, QScreen *screen)
275 {
276         struct wl_surface *bg, *top, *bottom;
277         struct wl_output *output;
278         QObject *qobj_bg, *qobj_top, *qobj_bottom;
279
280         QQmlComponent bg_comp(engine, QUrl("qrc:/background_demo.qml"));
281         qInfo() << bg_comp.errors();
282
283         QQmlComponent top_comp(engine, QUrl("qrc:/toppanel_demo.qml"));
284         qInfo() << top_comp.errors();
285
286         QQmlComponent bot_comp(engine, QUrl("qrc:/bottompanel_demo.qml"));
287         qInfo() << bot_comp.errors();
288
289         top = create_component(native, &top_comp, screen, &qobj_top);
290         bottom = create_component(native, &bot_comp, screen, &qobj_bottom);
291         bg = create_component(native, &bg_comp, screen, &qobj_bg);
292
293         /* engine.rootObjects() works only if we had a load() */
294         StatusBarModel *statusBar = qobj_top->findChild<StatusBarModel *>("statusBar");
295         if (statusBar) {
296                 qDebug() << "got statusBar objectname, doing init()";
297                 statusBar->init(engine->rootContext());
298         }
299
300         output = getWlOutput(native, screen);
301
302         qDebug() << "Setting homescreen to screen  " << screen->name();
303
304         agl_shell_set_background(agl_shell, bg, output);
305         agl_shell_set_panel(agl_shell, top, output, AGL_SHELL_EDGE_TOP);
306         agl_shell_set_panel(agl_shell, bottom, output, AGL_SHELL_EDGE_BOTTOM);
307
308         qDebug() << "CI mode - with multiple surfaces";
309 }
310
311 static void
312 load_agl_shell_app(QPlatformNativeInterface *native, QQmlApplicationEngine *engine,
313                    struct agl_shell *agl_shell, const char *screen_name, bool is_demo)
314 {
315         QScreen *screen = nullptr;
316
317         if (!screen_name)
318                 screen = qApp->primaryScreen();
319         else
320                 screen = find_screen(screen_name);
321
322         if (!screen) {
323                 qDebug() << "No outputs present in the system.";
324                 return;
325         }
326
327         if (is_demo) {
328                 load_agl_shell_for_ci(native, engine, agl_shell, screen);
329         } else {
330                 load_agl_shell(native, engine, agl_shell, screen);
331         }
332
333         /* Delay the ready signal until after Qt has done all of its own setup
334          * in a.exec() */
335         QTimer::singleShot(500, [agl_shell](){
336                 qDebug() << "sending ready to compositor";
337                 agl_shell_ready(agl_shell);
338         });
339 }
340
341 int main(int argc, char *argv[])
342 {
343         setenv("QT_QPA_PLATFORM", "wayland", 1);
344         setenv("QT_QUICK_CONTROLS_STYLE", "AGL", 1);
345
346         QGuiApplication app(argc, argv);
347         const char *screen_name;
348         bool is_demo_val = false;
349         bool is_embedded_panels = false;
350         int ret = 0;
351         struct shell_data shell_data = { nullptr, nullptr, true, false, 0 };
352
353         QPlatformNativeInterface *native = qApp->platformNativeInterface();
354         screen_name = getenv("HOMESCREEN_START_SCREEN");
355
356         const char *is_demo = getenv("HOMESCREEN_DEMO_CI");
357         if (is_demo && strcmp(is_demo, "1") == 0)
358                 is_demo_val = true;
359
360         const char *embedded_panels = getenv("HOMESCREEN_EMBEDDED_PANELS");
361         if (embedded_panels && strcmp(embedded_panels, "1") == 0)
362                 is_embedded_panels = true;
363
364         QCoreApplication::setOrganizationDomain("LinuxFoundation");
365         QCoreApplication::setOrganizationName("AutomotiveGradeLinux");
366         QCoreApplication::setApplicationName("HomeScreen");
367         QCoreApplication::setApplicationVersion("0.7.0");
368
369         // we need to have an app_id
370         app.setDesktopFileName("homescreen");
371
372         register_agl_shell(native, &shell_data);
373         if (!shell_data.shell) {
374                 fprintf(stderr, "agl_shell extension is not advertised. "
375                         "Are you sure that agl-compositor is running?\n");
376                 exit(EXIT_FAILURE);
377         }
378
379         qDebug() << "agl-shell interface is at version " << shell_data.ver;
380         if (shell_data.ver >= 2) {
381                 while (ret != -1 && shell_data.wait_for_bound) {
382                         ret = wl_display_dispatch(getWlDisplay(native));
383
384                         if (shell_data.wait_for_bound)
385                                 continue;
386                 }
387
388                 if (!shell_data.bound_ok) {
389                         qInfo() << "agl_shell extension already in use by other shell client.";
390                         exit(EXIT_FAILURE);
391                 }
392         }
393
394
395         std::shared_ptr<struct agl_shell> agl_shell{shell_data.shell, agl_shell_destroy};
396         Shell *aglShell = new Shell(agl_shell, &app);
397
398         // Import C++ class to QML
399         qmlRegisterType<StatusBarModel>("HomeScreen", 1, 0, "StatusBarModel");
400         qmlRegisterType<MasterVolume>("MasterVolume", 1, 0, "MasterVolume");
401
402         ApplicationLauncher *launcher = new ApplicationLauncher();
403         launcher->setCurrent(QStringLiteral("launcher"));
404
405         HomescreenHandler* homescreenHandler = new HomescreenHandler(aglShell, launcher);
406         shell_data.homescreenHandler = homescreenHandler;
407
408         QQmlApplicationEngine engine;
409         QQmlContext *context = engine.rootContext();
410
411         context->setContextProperty("homescreenHandler", homescreenHandler);
412         context->setContextProperty("launcher", launcher);
413         context->setContextProperty("weather", new Weather());
414         context->setContextProperty("bluetooth", new Bluetooth(false, context));
415
416         // We add it here even if we don't use it
417         context->setContextProperty("shell", aglShell);
418
419         load_agl_shell_app(native, &engine, shell_data.shell,
420                            screen_name, is_demo_val);
421
422         return app.exec();
423 }