protocol/grpc-proxy: Add deactivate_app request
[src/agl-compositor.git] / grpc-proxy / main-grpc.cpp
1 /*
2  * Copyright © 2022 Collabora, Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the
13  * next paragraph) shall be included in all copies or substantial
14  * portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25
26 #include <cstdio>
27 #include <ctime>
28 #include <algorithm>
29 #include <queue>
30 #include <thread>
31 #include <mutex>
32 #include <condition_variable>
33
34 #include "shell.h"
35 #include "log.h"
36 #include "main-grpc.h"
37 #include "grpc-async-cb.h"
38
39 struct shell_data_init {
40         struct agl_shell *shell;
41         bool wait_for_bound;
42         bool bound_ok;
43         bool bound_fail;
44         int version;
45 };
46
47 static int running = 1;
48
49 static void
50 agl_shell_bound_ok_init(void *data, struct agl_shell *agl_shell)
51 {
52         (void) agl_shell;
53
54         struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
55         sh->wait_for_bound = false;
56
57         sh->bound_ok = true;
58 }
59
60 static void
61 agl_shell_bound_fail_init(void *data, struct agl_shell *agl_shell)
62 {
63         (void) agl_shell;
64
65         struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
66         sh->wait_for_bound = false;
67
68         sh->bound_fail = true;
69 }
70
71 static void
72 agl_shell_bound_ok(void *data, struct agl_shell *agl_shell)
73 {
74         (void) agl_shell;
75
76         struct shell_data *sh = static_cast<struct shell_data *>(data);
77         sh->wait_for_bound = false;
78
79         sh->bound_ok = true;
80 }
81
82 static void
83 agl_shell_bound_fail(void *data, struct agl_shell *agl_shell)
84 {
85         (void) agl_shell;
86
87         struct shell_data *sh = static_cast<struct shell_data *>(data);
88         sh->wait_for_bound = false;
89
90         sh->bound_ok = false;
91 }
92
93 static void
94 agl_shell_app_state(void *data, struct agl_shell *agl_shell,
95                 const char *app_id, uint32_t state)
96 {
97         (void) agl_shell;
98         struct shell_data *sh = static_cast<struct shell_data *>(data);
99         LOG("got app_state event app_id %s,  state %d\n", app_id, state);
100
101         if (sh->server_context_list.empty())
102                 return;
103
104         ::agl_shell_ipc::AppStateResponse app;
105
106         sh->current_app_state.set_app_id(std::string(app_id));
107         sh->current_app_state.set_state(state);
108
109         auto start = sh->server_context_list.begin();
110         while (start != sh->server_context_list.end()) {
111                 // hold on if we're still detecting another in-flight writting
112                 if (start->second->Writting()) {
113                         LOG("skip writing to lister %p\n", static_cast<void *>(start->second));
114                         continue;
115                 }
116
117                 LOG("writing to lister %p\n", static_cast<void *>(start->second));
118                 start->second->NextWrite();
119                 start++;
120         }
121 }
122
123 static const struct agl_shell_listener shell_listener = {
124         agl_shell_bound_ok,
125         agl_shell_bound_fail,
126         agl_shell_app_state,
127 };
128
129 static const struct agl_shell_listener shell_listener_init = {
130         agl_shell_bound_ok_init,
131         agl_shell_bound_fail_init,
132         nullptr,
133 };
134
135 static void
136 agl_shell_ext_doas_done(void *data, struct agl_shell_ext *agl_shell_ext, uint32_t status)
137 {
138         (void) agl_shell_ext;
139
140         struct shell_data *sh = static_cast<struct shell_data *>(data);
141         sh->wait_for_doas = false;
142
143         if (status == AGL_SHELL_EXT_DOAS_SHELL_CLIENT_STATUS_SUCCESS)
144                 sh->doas_ok = true;
145 }
146
147 static const struct agl_shell_ext_listener shell_ext_listener = {
148         agl_shell_ext_doas_done,
149 };
150
151 static void
152 display_handle_geometry(void *data, struct wl_output *wl_output,
153                 int x, int y, int physical_width, int physical_height,
154                 int subpixel, const char *make, const char *model, int transform)
155 {
156         (void) data;
157         (void) wl_output;
158         (void) x;
159         (void) y;
160         (void) physical_width;
161         (void) physical_height;
162         (void) subpixel;
163         (void) make;
164         (void) model;
165         (void) transform;
166 }
167
168 static void
169 display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
170                 int width, int height, int refresh)
171 {
172         (void) data;
173         (void) wl_output;
174         (void) flags;
175         (void) width;
176         (void) height;
177         (void) refresh;
178 }
179
180 static void
181 display_handle_done(void *data, struct wl_output *wl_output)
182 {
183         (void) data;
184         (void) wl_output;
185 }
186
187 static void
188 display_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
189 {
190         (void) data;
191         (void) wl_output;
192         (void) factor;
193 }
194
195
196 static void
197 display_handle_name(void *data, struct wl_output *wl_output, const char *name)
198 {
199         (void) wl_output;
200
201         struct window_output *woutput = static_cast<struct window_output *>(data);
202         woutput->name = strdup(name);
203 }
204
205 static void
206 display_handle_description(void *data, struct wl_output *wl_output, const char *description)
207 {
208         (void) data;
209         (void) wl_output;
210         (void) description;
211 }
212
213 static const struct wl_output_listener output_listener = {
214         display_handle_geometry,
215         display_handle_mode,
216         display_handle_done,
217         display_handle_scale,
218         display_handle_name,
219         display_handle_description,
220 };
221
222 static void
223 display_add_output(struct shell_data *sh, struct wl_registry *reg,
224                    uint32_t id, uint32_t version)
225 {
226         struct window_output *w_output;
227
228         w_output = new struct window_output;
229         w_output->shell_data = sh;
230
231         w_output->output =
232                 static_cast<struct wl_output *>(wl_registry_bind(reg, id,
233                                 &wl_output_interface,
234                                 std::min(version, static_cast<uint32_t>(4))));
235
236         wl_list_insert(&sh->output_list, &w_output->link);
237         wl_output_add_listener(w_output->output, &output_listener, w_output);
238 }
239
240 static void
241 destroy_output(struct window_output *w_output)
242 {
243         free(w_output->name);
244         wl_list_remove(&w_output->link);
245         free(w_output);
246 }
247
248 static void
249 global_add(void *data, struct wl_registry *reg, uint32_t id,
250                 const char *interface, uint32_t version)
251 {
252
253         struct shell_data *sh = static_cast<struct shell_data *>(data);
254
255         if (!sh)
256                 return;
257
258         if (strcmp(interface, agl_shell_interface.name) == 0) {
259                 // bind to at least v3 to get events
260                 sh->shell =
261                         static_cast<struct agl_shell *>(wl_registry_bind(reg, id,
262                                 &agl_shell_interface,
263                                 std::min(static_cast<uint32_t>(5), version)));
264                 agl_shell_add_listener(sh->shell, &shell_listener, data);
265                 sh->version = version;
266         } else if (strcmp(interface, "wl_output") == 0) {
267                 display_add_output(sh, reg, id, version);
268         }
269 }
270
271 // the purpose of this _init is to make sure we're not the first shell client
272 // running to allow the 'main' shell client take over.
273 static void
274 global_add_init(void *data, struct wl_registry *reg, uint32_t id,
275                 const char *interface, uint32_t version)
276 {
277
278         struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
279
280         if (!sh)
281                 return;
282
283         if (strcmp(interface, agl_shell_interface.name) == 0) {
284                 sh->shell =
285                         static_cast<struct agl_shell *>(wl_registry_bind(reg, id,
286                                 &agl_shell_interface,
287                                 std::min(static_cast<uint32_t>(5), version)));
288                 agl_shell_add_listener(sh->shell, &shell_listener_init, data);
289                 sh->version = version;
290         }
291 }
292
293 static void
294 global_remove(void *data, struct wl_registry *reg, uint32_t id)
295 {
296         /* Don't care */
297         (void) data;
298         (void) reg;
299         (void) id;
300 }
301
302 static void
303 global_add_ext(void *data, struct wl_registry *reg, uint32_t id,
304                 const char *interface, uint32_t version)
305 {
306         struct shell_data *sh = static_cast<struct shell_data *>(data);
307
308         if (!sh)
309                 return;
310
311         if (strcmp(interface, agl_shell_ext_interface.name) == 0) {
312                 sh->shell_ext =
313                         static_cast<struct agl_shell_ext *>(wl_registry_bind(reg, id,
314                                 &agl_shell_ext_interface,
315                                 std::min(static_cast<uint32_t>(1), version)));
316                 agl_shell_ext_add_listener(sh->shell_ext,
317                                            &shell_ext_listener, data);
318         }
319 }
320
321 static const struct wl_registry_listener registry_ext_listener = {
322         global_add_ext,
323         global_remove,
324 };
325
326 static const struct wl_registry_listener registry_listener = {
327         global_add,
328         global_remove,
329 };
330
331 static const struct wl_registry_listener registry_listener_init = {
332         global_add_init,
333         global_remove,
334 };
335
336 static void
337 register_shell_ext(struct wl_display *wl_display, struct shell_data *sh)
338 {
339         struct wl_registry *registry;
340
341         registry = wl_display_get_registry(wl_display);
342
343         wl_registry_add_listener(registry, &registry_ext_listener, sh);
344
345         wl_display_roundtrip(wl_display);
346         wl_registry_destroy(registry);
347 }
348
349 static void
350 register_shell(struct wl_display *wl_display, struct shell_data *sh)
351 {
352         struct wl_registry *registry;
353
354         wl_list_init(&sh->output_list);
355
356         registry = wl_display_get_registry(wl_display);
357
358         wl_registry_add_listener(registry, &registry_listener, sh);
359
360         wl_display_roundtrip(wl_display);
361         wl_registry_destroy(registry);
362 }
363
364 static int
365 __register_shell_init(void)
366 {
367         int ret = 0;
368         struct wl_registry *registry;
369         struct wl_display *wl_display;
370
371         struct shell_data_init *sh = new struct shell_data_init;
372
373         wl_display = wl_display_connect(NULL);
374         registry = wl_display_get_registry(wl_display);
375         sh->wait_for_bound = true;
376         sh->bound_fail = false;
377         sh->bound_ok = false;
378
379         wl_registry_add_listener(registry, &registry_listener_init, sh);
380         wl_display_roundtrip(wl_display);
381
382         if (!sh->shell || sh->version < 3) {
383                 ret = -1;
384                 goto err;
385         }
386
387         while (ret !=- 1 && sh->wait_for_bound) {
388                 ret = wl_display_dispatch(wl_display);
389
390                 if (sh->wait_for_bound)
391                         continue;
392         }
393
394         ret = sh->bound_fail;
395
396         agl_shell_destroy(sh->shell);
397         wl_display_flush(wl_display);
398 err:
399         wl_registry_destroy(registry);
400         wl_display_disconnect(wl_display);
401         delete sh;
402         return ret;
403 }
404
405 // we expect this client to be up & running *after* the shell client has
406 // already set-up panels/backgrounds.
407 // this means the very first try to bind to agl_shell we wait for
408 // 'bound_fail' event, which would tell us when it's ok to attempt to
409 // bind agl_shell_ext, call doas request, then attempt to bind (one
410 // more time) to agl_shell but this time wait for 'bound_ok' event.
411 void
412 register_shell_init(void)
413 {
414         struct timespec ts = {};
415
416         clock_gettime(CLOCK_MONOTONIC, &ts);
417
418         ts.tv_sec = 0;
419         ts.tv_nsec = 250 * 1000 * 1000; // 250 ms
420
421         // verify if 'bound_fail' was received
422         while (true) {
423
424                 int r = __register_shell_init();
425
426                 if (r < 0) {
427                         LOG("agl-shell extension not found or version too low\n");
428                         exit(EXIT_FAILURE);
429                 } else if (r == 1) {
430                         // we need to get a 'bound_fail' event, if we get a 'bound_ok'
431                         // it means we're the first shell to start so wait until the
432                         // shell client actually started
433                         LOG("Found another shell client running. "
434                              "Going further to bind to the agl_shell_ext interface\n");
435                         break;
436                 }
437
438                 LOG("No shell client detected running. Will wait until one starts up...\n");
439                 nanosleep(&ts, NULL);
440         }
441
442 }
443
444 static void
445 destroy_shell_data(struct shell_data *sh)
446 {
447         struct window_output *w_output, *w_output_next;
448
449         wl_list_for_each_safe(w_output, w_output_next, &sh->output_list, link)
450                 destroy_output(w_output);
451
452         wl_display_flush(sh->wl_display);
453         wl_display_disconnect(sh->wl_display);
454
455         delete sh;
456 }
457
458 static struct shell_data *
459 start_agl_shell_client(void)
460 {
461         int ret = 0;
462         struct wl_display *wl_display;
463
464         wl_display = wl_display_connect(NULL);
465
466         struct shell_data *sh = new struct shell_data;
467
468         sh->wl_display = wl_display;
469         sh->wait_for_doas = true;
470         sh->wait_for_bound = true;
471
472         register_shell_ext(wl_display, sh);
473
474         // check for agl_shell_ext
475         if (!sh->shell_ext) {
476                 LOG("Failed to bind to agl_shell_ext interface\n");
477                 goto err;
478         }
479
480         if (wl_list_empty(&sh->output_list)) {
481                 LOG("Failed get any outputs!\n");
482                 goto err;
483         }
484
485         agl_shell_ext_doas_shell_client(sh->shell_ext);
486         while (ret != -1 && sh->wait_for_doas) {
487                 ret = wl_display_dispatch(sh->wl_display);
488                 if (sh->wait_for_doas)
489                         continue;
490         }
491
492         if (!sh->doas_ok) {
493                 LOG("Failed to get doas_done event\n");
494                 goto err;
495         }
496
497         // bind to agl-shell
498         register_shell(wl_display, sh);
499         while (ret != -1 && sh->wait_for_bound) {
500                 ret = wl_display_dispatch(sh->wl_display);
501                 if (sh->wait_for_bound)
502                         continue;
503         }
504
505         // at this point, we can't do anything about it
506         if (!sh->bound_ok) {
507                 LOG("Failed to get bound_ok event!\n");
508                 goto err;
509         }
510
511         LOG("agl_shell/agl_shell_ext interface OK\n");
512
513         return sh;
514 err:
515         delete sh;
516         return nullptr;
517 }
518
519 static void
520 start_grpc_server(Shell *aglShell)
521 {
522         // instantiante the grpc server
523         std::string server_address(kDefaultGrpcServiceAddress);
524         GrpcServiceImpl service{aglShell};
525
526         grpc::EnableDefaultHealthCheckService(true);
527         grpc::reflection::InitProtoReflectionServerBuilderPlugin();
528
529         grpc::ServerBuilder builder;
530         builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
531         builder.RegisterService(&service);
532
533         std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
534         LOG("gRPC server listening on %s\n", server_address.c_str());
535
536         server->Wait();
537 }
538
539 int main(int argc, char **argv)
540 {
541         (void) argc;
542         (void) argv;
543         Shell *aglShell;
544         int ret = 0;
545
546         // this blocks until we detect that another shell client started
547         // running
548         register_shell_init();
549
550         struct shell_data *sh = start_agl_shell_client();
551         if (!sh) {
552                 LOG("Failed to initialize agl-shell/agl-shell-ext\n");
553                 exit(EXIT_FAILURE);
554         }
555
556         std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
557         aglShell = new Shell(agl_shell, sh);
558
559         std::thread thread(start_grpc_server, aglShell);
560
561         // serve wayland requests
562         while (running && ret != -1) {
563                 ret = wl_display_dispatch(sh->wl_display);
564         }
565
566         destroy_shell_data(sh);
567         return 0;
568 }