main-grpc: Fix iterator going invalid at destruction time
[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 <chrono>
33 #include <condition_variable>
34
35 #include "shell.h"
36 #include "log.h"
37 #include "main-grpc.h"
38 #include "grpc-async-cb.h"
39
40 using namespace std::chrono_literals;
41
42 static int running = 1;
43
44 static void
45 agl_shell_bound_ok(void *data, struct agl_shell *agl_shell)
46 {
47         (void) agl_shell;
48
49         struct shell_data *sh = static_cast<struct shell_data *>(data);
50         sh->wait_for_bound = false;
51
52         LOG("bound_ok event!\n");
53         sh->bound_ok = true;
54 }
55
56 static void
57 agl_shell_bound_fail(void *data, struct agl_shell *agl_shell)
58 {
59         (void) agl_shell;
60
61         struct shell_data *sh = static_cast<struct shell_data *>(data);
62         sh->wait_for_bound = false;
63
64         LOG("bound_fail event!\n");
65         sh->bound_ok = false;
66         sh->bound_fail = true;
67 }
68
69 static void
70 agl_shell_app_state(void *data, struct agl_shell *agl_shell,
71                 const char *app_id, uint32_t state)
72 {
73         (void) agl_shell;
74         struct shell_data *sh = static_cast<struct shell_data *>(data);
75         LOG("got app_state event app_id %s,  state %d\n", app_id, state);
76
77         if (sh->server_context_list.empty())
78                 return;
79
80         ::agl_shell_ipc::AppStateResponse app;
81
82         sh->current_app_state.set_app_id(std::string(app_id));
83         sh->current_app_state.set_state(state);
84
85         auto start = sh->server_context_list.begin();
86         while (start != sh->server_context_list.end()) {
87                 // hold on if we're still detecting another in-flight writting
88                 if (start->second->Writting()) {
89                         LOG("skip writing to lister %p\n", static_cast<void *>(start->second));
90                         continue;
91                 }
92
93                 LOG("writing to lister %p\n", static_cast<void *>(start->second));
94                 start->second->NextWrite();
95                 start++;
96         }
97 }
98
99 static void
100 agl_shell_app_on_output(void *data, struct agl_shell *agl_shell,
101                 const char *app_id, const char *output_name)
102 {
103         (void) agl_shell;
104         (void) output_name;
105         (void) data;
106         (void) app_id;
107
108         LOG("got app_on_output event app_id %s on output %s\n", app_id, output_name);
109 }
110
111
112 static const struct agl_shell_listener shell_listener = {
113         agl_shell_bound_ok,
114         agl_shell_bound_fail,
115         agl_shell_app_state,
116         agl_shell_app_on_output,
117 };
118
119 static void
120 agl_shell_ext_doas_done(void *data, struct agl_shell_ext *agl_shell_ext, uint32_t status)
121 {
122         (void) agl_shell_ext;
123
124         struct shell_data *sh = static_cast<struct shell_data *>(data);
125         sh->wait_for_doas = false;
126
127         if (status == AGL_SHELL_EXT_DOAS_SHELL_CLIENT_STATUS_SUCCESS) {
128                 LOG("got doas_ok true!\n");
129                 sh->doas_ok = true;
130         }
131 }
132
133 static const struct agl_shell_ext_listener shell_ext_listener = {
134         agl_shell_ext_doas_done,
135 };
136
137 static void
138 display_handle_geometry(void *data, struct wl_output *wl_output,
139                 int x, int y, int physical_width, int physical_height,
140                 int subpixel, const char *make, const char *model, int transform)
141 {
142         (void) data;
143         (void) wl_output;
144         (void) x;
145         (void) y;
146         (void) physical_width;
147         (void) physical_height;
148         (void) subpixel;
149         (void) make;
150         (void) model;
151         (void) transform;
152 }
153
154 static void
155 display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
156                 int width, int height, int refresh)
157 {
158         (void) data;
159         (void) wl_output;
160         (void) flags;
161         (void) width;
162         (void) height;
163         (void) refresh;
164 }
165
166 static void
167 display_handle_done(void *data, struct wl_output *wl_output)
168 {
169         (void) data;
170         (void) wl_output;
171 }
172
173 static void
174 display_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
175 {
176         (void) data;
177         (void) wl_output;
178         (void) factor;
179 }
180
181
182 static void
183 display_handle_name(void *data, struct wl_output *wl_output, const char *name)
184 {
185         (void) wl_output;
186
187         struct window_output *woutput = static_cast<struct window_output *>(data);
188         woutput->name = strdup(name);
189 }
190
191 static void
192 display_handle_description(void *data, struct wl_output *wl_output, const char *description)
193 {
194         (void) data;
195         (void) wl_output;
196         (void) description;
197 }
198
199 static const struct wl_output_listener output_listener = {
200         display_handle_geometry,
201         display_handle_mode,
202         display_handle_done,
203         display_handle_scale,
204         display_handle_name,
205         display_handle_description,
206 };
207
208 static void
209 display_add_output(struct shell_data *sh, struct wl_registry *reg,
210                    uint32_t id, uint32_t version)
211 {
212         struct window_output *w_output;
213
214         w_output = new struct window_output;
215         w_output->shell_data = sh;
216
217         w_output->output =
218                 static_cast<struct wl_output *>(wl_registry_bind(reg, id,
219                                 &wl_output_interface,
220                                 std::min(version, static_cast<uint32_t>(4))));
221
222         wl_list_insert(&sh->output_list, &w_output->link);
223         wl_output_add_listener(w_output->output, &output_listener, w_output);
224 }
225
226 static void
227 destroy_output(struct window_output *w_output)
228 {
229         free(w_output->name);
230         wl_list_remove(&w_output->link);
231         free(w_output);
232 }
233
234 static void
235 global_add(void *data, struct wl_registry *reg, uint32_t id,
236                 const char *interface, uint32_t version)
237 {
238
239         struct shell_data *sh = static_cast<struct shell_data *>(data);
240
241         if (!sh)
242                 return;
243
244         struct global_data gb;
245
246         gb.version = version;
247         gb.id = id;
248         gb.interface_name = std::string(interface);
249         sh->globals.push_back(gb);
250
251         if (strcmp(interface, agl_shell_interface.name) == 0) {
252                 // nothing here, we're just going to bind a bit later after we
253                 // got doas_ok event
254         } else if (strcmp(interface, "wl_output") == 0) {
255                 display_add_output(sh, reg, id, version);
256         } else if (strcmp(interface, agl_shell_ext_interface.name) == 0) {
257                 sh->shell_ext =
258                         static_cast<struct agl_shell_ext *>(wl_registry_bind(reg, id,
259                                 &agl_shell_ext_interface,
260                                 std::min(static_cast<uint32_t>(1), version)));
261                 agl_shell_ext_add_listener(sh->shell_ext,
262                                            &shell_ext_listener, data);
263         }
264 }
265
266 static void
267 global_remove(void *data, struct wl_registry *reg, uint32_t id)
268 {
269         struct shell_data *sh = static_cast<struct shell_data *>(data);
270         /* Don't care */
271         (void) reg;
272         (void) id;
273
274         for (std::list<global_data>::iterator it = sh->globals.begin();
275                         it != sh->globals.end(); it = sh->globals.erase(it))
276                 ;
277 }
278
279 static const struct wl_registry_listener registry_listener = {
280         global_add,
281         global_remove,
282 };
283
284
285 // we expect this client to be up & running *after* the shell client has
286 // already set-up panels/backgrounds.
287 //
288 // this means we need to wait for doas_done event with doas_shell_client_status
289 // set to sucess.
290 struct shell_data *
291 register_shell_ext(void)
292 {
293         // try first to bind to agl_shell_ext
294         int ret = 0;
295         struct wl_registry *registry;
296
297         struct shell_data *sh = new struct shell_data;
298
299         sh->wl_display = wl_display_connect(NULL);
300         if (!sh->wl_display) {
301                 goto err;
302         }
303
304         registry = wl_display_get_registry(sh->wl_display);
305
306         sh->wait_for_bound = true;
307         sh->wait_for_doas = true;
308
309         sh->bound_fail = false;
310         sh->bound_ok = false;
311
312         sh->doas_ok = false;
313         wl_list_init(&sh->output_list);
314
315         wl_registry_add_listener(registry, &registry_listener, sh);
316         wl_display_roundtrip(sh->wl_display);
317
318         if (!sh->shell_ext) {
319                 LOG("agl_shell_ext interface was not found!\n");
320                 goto err;
321         }
322
323         do {
324                 // this should loop until we get back an doas_ok event
325                 agl_shell_ext_doas_shell_client(sh->shell_ext);
326
327                 while (ret !=- 1 && sh->wait_for_doas) {
328                         ret = wl_display_dispatch(sh->wl_display);
329
330                         if (sh->wait_for_doas)
331                                 continue;
332                 }
333
334                 if (sh->doas_ok) {
335                         break;
336                 }
337
338                 std::this_thread::sleep_for(250ms);
339                 sh->wait_for_doas = true;
340         } while (!sh->doas_ok);
341
342         if (!sh->doas_ok) {
343                 LOG("agl_shell_ext: failed to get doas_ok status\n");
344                 goto err;
345         }
346
347
348         // search for the globals to get id and version
349         for (std::list<global_data>::iterator it = sh->globals.begin();
350                         it != sh->globals.end(); it++) {
351                 if (it->interface_name == "agl_shell") {
352                         sh->shell =
353                                 static_cast<struct agl_shell *>(wl_registry_bind(registry, it->id,
354                                         &agl_shell_interface, std::min(static_cast<uint32_t>(11),
355                                                 it->version))
356                                 );
357                         agl_shell_add_listener(sh->shell, &shell_listener, sh);
358                         break;
359                 }
360         }
361
362         if (!sh->shell) {
363                 LOG("agl_shell was not advertised!\n");
364                 goto err;
365         }
366
367         // wait to bound now
368         while (ret != -1 && sh->wait_for_bound) {
369                 ret = wl_display_dispatch(sh->wl_display);
370
371                 if (sh->wait_for_bound)
372                         continue;
373         }
374
375         // at this point, we can't do anything about it
376         if (!sh->bound_ok) {
377                 LOG("Failed to get bound_ok event!\n");
378                 goto err;
379         }
380
381         LOG("agl_shell/agl_shell_ext interfaces OK\n");
382         return sh;
383 err:
384         LOG("agl_shell/agl_shell_ext interfaces NOK\n");
385         return NULL;
386 }
387
388 static void
389 destroy_shell_data(struct shell_data *sh)
390 {
391         struct window_output *w_output, *w_output_next;
392
393         wl_list_for_each_safe(w_output, w_output_next, &sh->output_list, link)
394                 destroy_output(w_output);
395
396        for (std::list<global_data>::iterator it = sh->globals.begin();
397                        it != sh->globals.end(); it = sh->globals.erase(it))
398                ;
399
400         wl_display_flush(sh->wl_display);
401         wl_display_disconnect(sh->wl_display);
402
403         delete sh;
404 }
405
406 static void
407 start_grpc_server(std::shared_ptr<grpc::Server> server)
408 {
409         LOG("gRPC server listening\n");
410         server->Wait();
411 }
412
413 int main(int argc, char **argv)
414 {
415         (void) argc;
416         (void) argv;
417         Shell *aglShell = nullptr;
418         int ret = 0;
419
420         // instantiante the grpc server
421         std::string server_address(kDefaultGrpcServiceAddress);
422         GrpcServiceImpl service{aglShell};
423
424         grpc::EnableDefaultHealthCheckService(true);
425         grpc::reflection::InitProtoReflectionServerBuilderPlugin();
426
427         grpc::ServerBuilder builder;
428         builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
429         builder.RegisterService(&service);
430
431         std::shared_ptr<grpc::Server> server(builder.BuildAndStart());
432         std::thread thread(start_grpc_server, server);
433
434         // this blocks until we detect that another shell client started
435         // running
436         struct shell_data *sh = register_shell_ext();
437         if (!sh) {
438                 LOG("Failed to get register ag_shell_ext\n");
439                 thread.join();
440                 exit(EXIT_FAILURE);
441         }
442
443         std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
444         aglShell = new Shell(agl_shell, sh);
445
446         // now that we have aglShell, set it to the gRPC proxy as well
447         service.setAglShell(aglShell);
448
449         // serve wayland requests
450         while (running && ret != -1) {
451                 ret = wl_display_dispatch(sh->wl_display);
452         }
453
454         thread.join();
455         destroy_shell_data(sh);
456         return 0;
457 }