2 * Copyright © 2022 Collabora, Ltd.
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:
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.
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
32 #include <condition_variable>
36 #include "main-grpc.h"
37 #include "grpc-async-cb.h"
39 struct shell_data_init {
40 struct agl_shell *shell;
47 static int running = 1;
50 agl_shell_bound_ok_init(void *data, struct agl_shell *agl_shell)
54 struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
55 sh->wait_for_bound = false;
61 agl_shell_bound_fail_init(void *data, struct agl_shell *agl_shell)
65 struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
66 sh->wait_for_bound = false;
68 sh->bound_fail = true;
72 agl_shell_bound_ok(void *data, struct agl_shell *agl_shell)
76 struct shell_data *sh = static_cast<struct shell_data *>(data);
77 sh->wait_for_bound = false;
83 agl_shell_bound_fail(void *data, struct agl_shell *agl_shell)
87 struct shell_data *sh = static_cast<struct shell_data *>(data);
88 sh->wait_for_bound = false;
94 agl_shell_app_state(void *data, struct agl_shell *agl_shell,
95 const char *app_id, uint32_t state)
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);
101 if (sh->server_context_list.empty())
104 ::agl_shell_ipc::AppStateResponse app;
106 sh->current_app_state.set_app_id(std::string(app_id));
107 sh->current_app_state.set_state(state);
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));
117 LOG("writing to lister %p\n", static_cast<void *>(start->second));
118 start->second->NextWrite();
124 agl_shell_app_on_output(void *data, struct agl_shell *agl_shell,
125 const char *app_id, const char *output_name)
132 LOG("got app_on_output event app_id %s on output %s\n", app_id, output_name);
136 static const struct agl_shell_listener shell_listener = {
138 agl_shell_bound_fail,
140 agl_shell_app_on_output,
143 static const struct agl_shell_listener shell_listener_init = {
144 agl_shell_bound_ok_init,
145 agl_shell_bound_fail_init,
151 agl_shell_ext_doas_done(void *data, struct agl_shell_ext *agl_shell_ext, uint32_t status)
153 (void) agl_shell_ext;
155 struct shell_data *sh = static_cast<struct shell_data *>(data);
156 sh->wait_for_doas = false;
158 if (status == AGL_SHELL_EXT_DOAS_SHELL_CLIENT_STATUS_SUCCESS)
162 static const struct agl_shell_ext_listener shell_ext_listener = {
163 agl_shell_ext_doas_done,
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)
175 (void) physical_width;
176 (void) physical_height;
184 display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
185 int width, int height, int refresh)
196 display_handle_done(void *data, struct wl_output *wl_output)
203 display_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
212 display_handle_name(void *data, struct wl_output *wl_output, const char *name)
216 struct window_output *woutput = static_cast<struct window_output *>(data);
217 woutput->name = strdup(name);
221 display_handle_description(void *data, struct wl_output *wl_output, const char *description)
228 static const struct wl_output_listener output_listener = {
229 display_handle_geometry,
232 display_handle_scale,
234 display_handle_description,
238 display_add_output(struct shell_data *sh, struct wl_registry *reg,
239 uint32_t id, uint32_t version)
241 struct window_output *w_output;
243 w_output = new struct window_output;
244 w_output->shell_data = sh;
247 static_cast<struct wl_output *>(wl_registry_bind(reg, id,
248 &wl_output_interface,
249 std::min(version, static_cast<uint32_t>(4))));
251 wl_list_insert(&sh->output_list, &w_output->link);
252 wl_output_add_listener(w_output->output, &output_listener, w_output);
256 destroy_output(struct window_output *w_output)
258 free(w_output->name);
259 wl_list_remove(&w_output->link);
264 global_add(void *data, struct wl_registry *reg, uint32_t id,
265 const char *interface, uint32_t version)
268 struct shell_data *sh = static_cast<struct shell_data *>(data);
273 if (strcmp(interface, agl_shell_interface.name) == 0) {
274 // bind to at least v3 to get events
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);
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.
289 global_add_init(void *data, struct wl_registry *reg, uint32_t id,
290 const char *interface, uint32_t version)
293 struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
298 if (strcmp(interface, agl_shell_interface.name) == 0) {
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;
309 global_remove(void *data, struct wl_registry *reg, uint32_t id)
318 global_add_ext(void *data, struct wl_registry *reg, uint32_t id,
319 const char *interface, uint32_t version)
321 struct shell_data *sh = static_cast<struct shell_data *>(data);
326 if (strcmp(interface, agl_shell_ext_interface.name) == 0) {
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);
336 static const struct wl_registry_listener registry_ext_listener = {
341 static const struct wl_registry_listener registry_listener = {
346 static const struct wl_registry_listener registry_listener_init = {
352 register_shell_ext(struct wl_display *wl_display, struct shell_data *sh)
354 struct wl_registry *registry;
356 registry = wl_display_get_registry(wl_display);
358 wl_registry_add_listener(registry, ®istry_ext_listener, sh);
360 wl_display_roundtrip(wl_display);
361 wl_registry_destroy(registry);
365 register_shell(struct wl_display *wl_display, struct shell_data *sh)
367 struct wl_registry *registry;
369 wl_list_init(&sh->output_list);
371 registry = wl_display_get_registry(wl_display);
373 wl_registry_add_listener(registry, ®istry_listener, sh);
375 wl_display_roundtrip(wl_display);
376 wl_registry_destroy(registry);
380 __register_shell_init(void)
383 struct wl_registry *registry;
384 struct wl_display *wl_display;
386 struct shell_data_init *sh = new struct shell_data_init;
388 wl_display = wl_display_connect(NULL);
391 goto err_failed_display;
393 registry = wl_display_get_registry(wl_display);
394 sh->wait_for_bound = true;
395 sh->bound_fail = false;
396 sh->bound_ok = false;
398 wl_registry_add_listener(registry, ®istry_listener_init, sh);
399 wl_display_roundtrip(wl_display);
401 if (!sh->shell || sh->version < 3) {
406 while (ret !=- 1 && sh->wait_for_bound) {
407 ret = wl_display_dispatch(wl_display);
409 if (sh->wait_for_bound)
413 ret = sh->bound_fail;
415 agl_shell_destroy(sh->shell);
416 wl_display_flush(wl_display);
418 wl_registry_destroy(registry);
419 wl_display_disconnect(wl_display);
426 // we expect this client to be up & running *after* the shell client has
427 // already set-up panels/backgrounds.
428 // this means the very first try to bind to agl_shell we wait for
429 // 'bound_fail' event, which would tell us when it's ok to attempt to
430 // bind agl_shell_ext, call doas request, then attempt to bind (one
431 // more time) to agl_shell but this time wait for 'bound_ok' event.
433 register_shell_init(void)
435 struct timespec ts = {};
437 clock_gettime(CLOCK_MONOTONIC, &ts);
440 ts.tv_nsec = 250 * 1000 * 1000; // 250 ms
442 // verify if 'bound_fail' was received
445 int r = __register_shell_init();
448 LOG("agl-shell extension not found or version too low\n");
451 // we need to get a 'bound_fail' event, if we get a 'bound_ok'
452 // it means we're the first shell to start so wait until the
453 // shell client actually started
454 LOG("Found another shell client running. "
455 "Going further to bind to the agl_shell_ext interface\n");
459 LOG("No shell client detected running. Will wait until one starts up...\n");
460 nanosleep(&ts, NULL);
466 destroy_shell_data(struct shell_data *sh)
468 struct window_output *w_output, *w_output_next;
470 wl_list_for_each_safe(w_output, w_output_next, &sh->output_list, link)
471 destroy_output(w_output);
473 wl_display_flush(sh->wl_display);
474 wl_display_disconnect(sh->wl_display);
479 static struct shell_data *
480 start_agl_shell_client(void)
483 struct wl_display *wl_display;
485 wl_display = wl_display_connect(NULL);
487 struct shell_data *sh = new struct shell_data;
493 sh->wl_display = wl_display;
494 sh->wait_for_doas = true;
495 sh->wait_for_bound = true;
497 register_shell_ext(wl_display, sh);
499 // check for agl_shell_ext
500 if (!sh->shell_ext) {
501 LOG("Failed to bind to agl_shell_ext interface\n");
505 if (wl_list_empty(&sh->output_list)) {
506 LOG("Failed get any outputs!\n");
510 agl_shell_ext_doas_shell_client(sh->shell_ext);
511 while (ret != -1 && sh->wait_for_doas) {
512 ret = wl_display_dispatch(sh->wl_display);
513 if (sh->wait_for_doas)
518 LOG("Failed to get doas_done event\n");
523 register_shell(wl_display, sh);
524 while (ret != -1 && sh->wait_for_bound) {
525 ret = wl_display_dispatch(sh->wl_display);
526 if (sh->wait_for_bound)
530 // at this point, we can't do anything about it
532 LOG("Failed to get bound_ok event!\n");
536 LOG("agl_shell/agl_shell_ext interface OK\n");
545 start_grpc_server(std::shared_ptr<grpc::Server> server)
547 LOG("gRPC server listening\n");
551 int main(int argc, char **argv)
555 Shell *aglShell = nullptr;
558 // instantiante the grpc server
559 std::string server_address(kDefaultGrpcServiceAddress);
560 GrpcServiceImpl service{aglShell};
562 grpc::EnableDefaultHealthCheckService(true);
563 grpc::reflection::InitProtoReflectionServerBuilderPlugin();
565 grpc::ServerBuilder builder;
566 builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
567 builder.RegisterService(&service);
569 std::shared_ptr<grpc::Server> server(builder.BuildAndStart());
570 std::thread thread(start_grpc_server, server);
572 // this blocks until we detect that another shell client started
574 register_shell_init();
576 struct shell_data *sh = start_agl_shell_client();
578 LOG("Failed to initialize agl-shell/agl-shell-ext\n");
582 std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
583 aglShell = new Shell(agl_shell, sh);
585 // now that we have aglShell, set it to the gRPC proxy as well
586 service.setAglShell(aglShell);
588 // serve wayland requests
589 while (running && ret != -1) {
590 ret = wl_display_dispatch(sh->wl_display);
594 destroy_shell_data(sh);