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