grpc-proxy: Don't attempt to connect if there's no compositor running
[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 void
124 agl_shell_app_on_output(void *data, struct agl_shell *agl_shell,
125                 const char *app_id, const char *output_name)
126 {
127         (void) agl_shell;
128         (void) output_name;
129         (void) data;
130         (void) app_id;
131
132         LOG("got app_on_output event app_id %s on output\n", app_id, output_name);
133 }
134
135
136 static const struct agl_shell_listener shell_listener = {
137         agl_shell_bound_ok,
138         agl_shell_bound_fail,
139         agl_shell_app_state,
140         agl_shell_app_on_output,
141 };
142
143 static const struct agl_shell_listener shell_listener_init = {
144         agl_shell_bound_ok_init,
145         agl_shell_bound_fail_init,
146         nullptr,
147         nullptr,
148 };
149
150 static void
151 agl_shell_ext_doas_done(void *data, struct agl_shell_ext *agl_shell_ext, uint32_t status)
152 {
153         (void) agl_shell_ext;
154
155         struct shell_data *sh = static_cast<struct shell_data *>(data);
156         sh->wait_for_doas = false;
157
158         if (status == AGL_SHELL_EXT_DOAS_SHELL_CLIENT_STATUS_SUCCESS)
159                 sh->doas_ok = true;
160 }
161
162 static const struct agl_shell_ext_listener shell_ext_listener = {
163         agl_shell_ext_doas_done,
164 };
165
166 static void
167 display_handle_geometry(void *data, struct wl_output *wl_output,
168                 int x, int y, int physical_width, int physical_height,
169                 int subpixel, const char *make, const char *model, int transform)
170 {
171         (void) data;
172         (void) wl_output;
173         (void) x;
174         (void) y;
175         (void) physical_width;
176         (void) physical_height;
177         (void) subpixel;
178         (void) make;
179         (void) model;
180         (void) transform;
181 }
182
183 static void
184 display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
185                 int width, int height, int refresh)
186 {
187         (void) data;
188         (void) wl_output;
189         (void) flags;
190         (void) width;
191         (void) height;
192         (void) refresh;
193 }
194
195 static void
196 display_handle_done(void *data, struct wl_output *wl_output)
197 {
198         (void) data;
199         (void) wl_output;
200 }
201
202 static void
203 display_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
204 {
205         (void) data;
206         (void) wl_output;
207         (void) factor;
208 }
209
210
211 static void
212 display_handle_name(void *data, struct wl_output *wl_output, const char *name)
213 {
214         (void) wl_output;
215
216         struct window_output *woutput = static_cast<struct window_output *>(data);
217         woutput->name = strdup(name);
218 }
219
220 static void
221 display_handle_description(void *data, struct wl_output *wl_output, const char *description)
222 {
223         (void) data;
224         (void) wl_output;
225         (void) description;
226 }
227
228 static const struct wl_output_listener output_listener = {
229         display_handle_geometry,
230         display_handle_mode,
231         display_handle_done,
232         display_handle_scale,
233         display_handle_name,
234         display_handle_description,
235 };
236
237 static void
238 display_add_output(struct shell_data *sh, struct wl_registry *reg,
239                    uint32_t id, uint32_t version)
240 {
241         struct window_output *w_output;
242
243         w_output = new struct window_output;
244         w_output->shell_data = sh;
245
246         w_output->output =
247                 static_cast<struct wl_output *>(wl_registry_bind(reg, id,
248                                 &wl_output_interface,
249                                 std::min(version, static_cast<uint32_t>(4))));
250
251         wl_list_insert(&sh->output_list, &w_output->link);
252         wl_output_add_listener(w_output->output, &output_listener, w_output);
253 }
254
255 static void
256 destroy_output(struct window_output *w_output)
257 {
258         free(w_output->name);
259         wl_list_remove(&w_output->link);
260         free(w_output);
261 }
262
263 static void
264 global_add(void *data, struct wl_registry *reg, uint32_t id,
265                 const char *interface, uint32_t version)
266 {
267
268         struct shell_data *sh = static_cast<struct shell_data *>(data);
269
270         if (!sh)
271                 return;
272
273         if (strcmp(interface, agl_shell_interface.name) == 0) {
274                 // bind to at least v3 to get events
275                 sh->shell =
276                         static_cast<struct agl_shell *>(wl_registry_bind(reg, id,
277                                 &agl_shell_interface,
278                                 std::min(static_cast<uint32_t>(10), version)));
279                 agl_shell_add_listener(sh->shell, &shell_listener, data);
280                 sh->version = version;
281         } else if (strcmp(interface, "wl_output") == 0) {
282                 display_add_output(sh, reg, id, version);
283         }
284 }
285
286 // the purpose of this _init is to make sure we're not the first shell client
287 // running to allow the 'main' shell client take over.
288 static void
289 global_add_init(void *data, struct wl_registry *reg, uint32_t id,
290                 const char *interface, uint32_t version)
291 {
292
293         struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
294
295         if (!sh)
296                 return;
297
298         if (strcmp(interface, agl_shell_interface.name) == 0) {
299                 sh->shell =
300                         static_cast<struct agl_shell *>(wl_registry_bind(reg, id,
301                                 &agl_shell_interface,
302                                 std::min(static_cast<uint32_t>(10), version)));
303                 agl_shell_add_listener(sh->shell, &shell_listener_init, data);
304                 sh->version = version;
305         }
306 }
307
308 static void
309 global_remove(void *data, struct wl_registry *reg, uint32_t id)
310 {
311         /* Don't care */
312         (void) data;
313         (void) reg;
314         (void) id;
315 }
316
317 static void
318 global_add_ext(void *data, struct wl_registry *reg, uint32_t id,
319                 const char *interface, uint32_t version)
320 {
321         struct shell_data *sh = static_cast<struct shell_data *>(data);
322
323         if (!sh)
324                 return;
325
326         if (strcmp(interface, agl_shell_ext_interface.name) == 0) {
327                 sh->shell_ext =
328                         static_cast<struct agl_shell_ext *>(wl_registry_bind(reg, id,
329                                 &agl_shell_ext_interface,
330                                 std::min(static_cast<uint32_t>(1), version)));
331                 agl_shell_ext_add_listener(sh->shell_ext,
332                                            &shell_ext_listener, data);
333         }
334 }
335
336 static const struct wl_registry_listener registry_ext_listener = {
337         global_add_ext,
338         global_remove,
339 };
340
341 static const struct wl_registry_listener registry_listener = {
342         global_add,
343         global_remove,
344 };
345
346 static const struct wl_registry_listener registry_listener_init = {
347         global_add_init,
348         global_remove,
349 };
350
351 static void
352 register_shell_ext(struct wl_display *wl_display, struct shell_data *sh)
353 {
354         struct wl_registry *registry;
355
356         registry = wl_display_get_registry(wl_display);
357
358         wl_registry_add_listener(registry, &registry_ext_listener, sh);
359
360         wl_display_roundtrip(wl_display);
361         wl_registry_destroy(registry);
362 }
363
364 static void
365 register_shell(struct wl_display *wl_display, struct shell_data *sh)
366 {
367         struct wl_registry *registry;
368
369         wl_list_init(&sh->output_list);
370
371         registry = wl_display_get_registry(wl_display);
372
373         wl_registry_add_listener(registry, &registry_listener, sh);
374
375         wl_display_roundtrip(wl_display);
376         wl_registry_destroy(registry);
377 }
378
379 static int
380 __register_shell_init(void)
381 {
382         int ret = 0;
383         struct wl_registry *registry;
384         struct wl_display *wl_display;
385
386         struct shell_data_init *sh = new struct shell_data_init;
387
388         wl_display = wl_display_connect(NULL);
389         if (!wl_display) {
390                 goto err;
391         }
392         registry = wl_display_get_registry(wl_display);
393         sh->wait_for_bound = true;
394         sh->bound_fail = false;
395         sh->bound_ok = false;
396
397         wl_registry_add_listener(registry, &registry_listener_init, sh);
398         wl_display_roundtrip(wl_display);
399
400         if (!sh->shell || sh->version < 3) {
401                 ret = -1;
402                 goto err;
403         }
404
405         while (ret !=- 1 && sh->wait_for_bound) {
406                 ret = wl_display_dispatch(wl_display);
407
408                 if (sh->wait_for_bound)
409                         continue;
410         }
411
412         ret = sh->bound_fail;
413
414         agl_shell_destroy(sh->shell);
415         wl_display_flush(wl_display);
416 err:
417         wl_registry_destroy(registry);
418         wl_display_disconnect(wl_display);
419         delete sh;
420         return ret;
421 }
422
423 // we expect this client to be up & running *after* the shell client has
424 // already set-up panels/backgrounds.
425 // this means the very first try to bind to agl_shell we wait for
426 // 'bound_fail' event, which would tell us when it's ok to attempt to
427 // bind agl_shell_ext, call doas request, then attempt to bind (one
428 // more time) to agl_shell but this time wait for 'bound_ok' event.
429 void
430 register_shell_init(void)
431 {
432         struct timespec ts = {};
433
434         clock_gettime(CLOCK_MONOTONIC, &ts);
435
436         ts.tv_sec = 0;
437         ts.tv_nsec = 250 * 1000 * 1000; // 250 ms
438
439         // verify if 'bound_fail' was received
440         while (true) {
441
442                 int r = __register_shell_init();
443
444                 if (r < 0) {
445                         LOG("agl-shell extension not found or version too low\n");
446                         exit(EXIT_FAILURE);
447                 } else if (r == 1) {
448                         // we need to get a 'bound_fail' event, if we get a 'bound_ok'
449                         // it means we're the first shell to start so wait until the
450                         // shell client actually started
451                         LOG("Found another shell client running. "
452                              "Going further to bind to the agl_shell_ext interface\n");
453                         break;
454                 }
455
456                 LOG("No shell client detected running. Will wait until one starts up...\n");
457                 nanosleep(&ts, NULL);
458         }
459
460 }
461
462 static void
463 destroy_shell_data(struct shell_data *sh)
464 {
465         struct window_output *w_output, *w_output_next;
466
467         wl_list_for_each_safe(w_output, w_output_next, &sh->output_list, link)
468                 destroy_output(w_output);
469
470         wl_display_flush(sh->wl_display);
471         wl_display_disconnect(sh->wl_display);
472
473         delete sh;
474 }
475
476 static struct shell_data *
477 start_agl_shell_client(void)
478 {
479         int ret = 0;
480         struct wl_display *wl_display;
481
482         wl_display = wl_display_connect(NULL);
483
484         struct shell_data *sh = new struct shell_data;
485
486         if (!wl_display) {
487                 goto err;
488         }
489
490         sh->wl_display = wl_display;
491         sh->wait_for_doas = true;
492         sh->wait_for_bound = true;
493
494         register_shell_ext(wl_display, sh);
495
496         // check for agl_shell_ext
497         if (!sh->shell_ext) {
498                 LOG("Failed to bind to agl_shell_ext interface\n");
499                 goto err;
500         }
501
502         if (wl_list_empty(&sh->output_list)) {
503                 LOG("Failed get any outputs!\n");
504                 goto err;
505         }
506
507         agl_shell_ext_doas_shell_client(sh->shell_ext);
508         while (ret != -1 && sh->wait_for_doas) {
509                 ret = wl_display_dispatch(sh->wl_display);
510                 if (sh->wait_for_doas)
511                         continue;
512         }
513
514         if (!sh->doas_ok) {
515                 LOG("Failed to get doas_done event\n");
516                 goto err;
517         }
518
519         // bind to agl-shell
520         register_shell(wl_display, sh);
521         while (ret != -1 && sh->wait_for_bound) {
522                 ret = wl_display_dispatch(sh->wl_display);
523                 if (sh->wait_for_bound)
524                         continue;
525         }
526
527         // at this point, we can't do anything about it
528         if (!sh->bound_ok) {
529                 LOG("Failed to get bound_ok event!\n");
530                 goto err;
531         }
532
533         LOG("agl_shell/agl_shell_ext interface OK\n");
534
535         return sh;
536 err:
537         delete sh;
538         return nullptr;
539 }
540
541 static void
542 start_grpc_server(Shell *aglShell)
543 {
544         // instantiante the grpc server
545         std::string server_address(kDefaultGrpcServiceAddress);
546         GrpcServiceImpl service{aglShell};
547
548         grpc::EnableDefaultHealthCheckService(true);
549         grpc::reflection::InitProtoReflectionServerBuilderPlugin();
550
551         grpc::ServerBuilder builder;
552         builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
553         builder.RegisterService(&service);
554
555         std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
556         LOG("gRPC server listening on %s\n", server_address.c_str());
557
558         server->Wait();
559 }
560
561 int main(int argc, char **argv)
562 {
563         (void) argc;
564         (void) argv;
565         Shell *aglShell;
566         int ret = 0;
567
568         // this blocks until we detect that another shell client started
569         // running
570         register_shell_init();
571
572         struct shell_data *sh = start_agl_shell_client();
573         if (!sh) {
574                 LOG("Failed to initialize agl-shell/agl-shell-ext\n");
575                 exit(EXIT_FAILURE);
576         }
577
578         std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
579         aglShell = new Shell(agl_shell, sh);
580
581         std::thread thread(start_grpc_server, aglShell);
582
583         // serve wayland requests
584         while (running && ret != -1) {
585                 ret = wl_display_dispatch(sh->wl_display);
586         }
587
588         thread.join();
589         destroy_shell_data(sh);
590         return 0;
591 }