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
33 #include <condition_variable>
37 #include "main-grpc.h"
38 #include "grpc-async-cb.h"
40 using namespace std::chrono_literals;
42 static int running = 1;
45 agl_shell_bound_ok(void *data, struct agl_shell *agl_shell)
49 struct shell_data *sh = static_cast<struct shell_data *>(data);
50 sh->wait_for_bound = false;
52 LOG("bound_ok event!\n");
57 agl_shell_bound_fail(void *data, struct agl_shell *agl_shell)
61 struct shell_data *sh = static_cast<struct shell_data *>(data);
62 sh->wait_for_bound = false;
64 LOG("bound_fail event!\n");
66 sh->bound_fail = true;
70 agl_shell_app_state(void *data, struct agl_shell *agl_shell,
71 const char *app_id, uint32_t state)
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);
77 if (sh->server_context_list.empty())
80 ::agl_shell_ipc::AppStateResponse app;
82 sh->current_app_state.set_app_id(std::string(app_id));
83 sh->current_app_state.set_state(state);
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));
93 LOG("writing to lister %p\n", static_cast<void *>(start->second));
94 start->second->NextWrite();
100 agl_shell_app_on_output(void *data, struct agl_shell *agl_shell,
101 const char *app_id, const char *output_name)
108 LOG("got app_on_output event app_id %s on output %s\n", app_id, output_name);
112 static const struct agl_shell_listener shell_listener = {
114 agl_shell_bound_fail,
116 agl_shell_app_on_output,
120 agl_shell_ext_doas_done(void *data, struct agl_shell_ext *agl_shell_ext, uint32_t status)
122 (void) agl_shell_ext;
124 struct shell_data *sh = static_cast<struct shell_data *>(data);
125 sh->wait_for_doas = false;
127 if (status == AGL_SHELL_EXT_DOAS_SHELL_CLIENT_STATUS_SUCCESS) {
128 LOG("got doas_ok true!\n");
133 static const struct agl_shell_ext_listener shell_ext_listener = {
134 agl_shell_ext_doas_done,
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)
146 (void) physical_width;
147 (void) physical_height;
155 display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
156 int width, int height, int refresh)
167 display_handle_done(void *data, struct wl_output *wl_output)
174 display_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
183 display_handle_name(void *data, struct wl_output *wl_output, const char *name)
187 struct window_output *woutput = static_cast<struct window_output *>(data);
188 woutput->name = strdup(name);
192 display_handle_description(void *data, struct wl_output *wl_output, const char *description)
199 static const struct wl_output_listener output_listener = {
200 display_handle_geometry,
203 display_handle_scale,
205 display_handle_description,
209 display_add_output(struct shell_data *sh, struct wl_registry *reg,
210 uint32_t id, uint32_t version)
212 struct window_output *w_output;
214 w_output = new struct window_output;
215 w_output->shell_data = sh;
218 static_cast<struct wl_output *>(wl_registry_bind(reg, id,
219 &wl_output_interface,
220 std::min(version, static_cast<uint32_t>(4))));
222 wl_list_insert(&sh->output_list, &w_output->link);
223 wl_output_add_listener(w_output->output, &output_listener, w_output);
227 destroy_output(struct window_output *w_output)
229 free(w_output->name);
230 wl_list_remove(&w_output->link);
235 global_add(void *data, struct wl_registry *reg, uint32_t id,
236 const char *interface, uint32_t version)
239 struct shell_data *sh = static_cast<struct shell_data *>(data);
244 struct global_data gb;
246 gb.version = version;
248 gb.interface_name = std::string(interface);
249 sh->globals.push_back(gb);
251 if (strcmp(interface, agl_shell_interface.name) == 0) {
252 // nothing here, we're just going to bind a bit later after we
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) {
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);
267 global_remove(void *data, struct wl_registry *reg, uint32_t id)
269 struct shell_data *sh = static_cast<struct shell_data *>(data);
274 for (std::list<global_data>::iterator it = sh->globals.begin();
275 it != sh->globals.end(); it++) {
276 sh->globals.erase(it);
280 static const struct wl_registry_listener registry_listener = {
286 // we expect this client to be up & running *after* the shell client has
287 // already set-up panels/backgrounds.
289 // this means we need to wait for doas_done event with doas_shell_client_status
292 register_shell_ext(void)
294 // try first to bind to agl_shell_ext
296 struct wl_registry *registry;
298 struct shell_data *sh = new struct shell_data;
300 sh->wl_display = wl_display_connect(NULL);
301 if (!sh->wl_display) {
305 registry = wl_display_get_registry(sh->wl_display);
307 sh->wait_for_bound = true;
308 sh->wait_for_doas = true;
310 sh->bound_fail = false;
311 sh->bound_ok = false;
314 wl_list_init(&sh->output_list);
316 wl_registry_add_listener(registry, ®istry_listener, sh);
317 wl_display_roundtrip(sh->wl_display);
319 if (!sh->shell_ext) {
320 LOG("agl_shell_ext interface was not found!\n");
325 // this should loop until we get back an doas_ok event
326 agl_shell_ext_doas_shell_client(sh->shell_ext);
328 while (ret !=- 1 && sh->wait_for_doas) {
329 ret = wl_display_dispatch(sh->wl_display);
331 if (sh->wait_for_doas)
339 std::this_thread::sleep_for(250ms);
340 sh->wait_for_doas = true;
341 } while (!sh->doas_ok);
344 LOG("agl_shell_ext: failed to get doas_ok status\n");
349 // search for the globals to get id and version
350 for (std::list<global_data>::iterator it = sh->globals.begin();
351 it != sh->globals.end(); it++) {
352 if (it->interface_name == "agl_shell") {
354 static_cast<struct agl_shell *>(wl_registry_bind(registry, it->id,
355 &agl_shell_interface, std::min(static_cast<uint32_t>(11),
358 agl_shell_add_listener(sh->shell, &shell_listener, sh);
364 LOG("agl_shell was not advertised!\n");
369 while (ret != -1 && sh->wait_for_bound) {
370 ret = wl_display_dispatch(sh->wl_display);
372 if (sh->wait_for_bound)
376 // at this point, we can't do anything about it
378 LOG("Failed to get bound_ok event!\n");
382 LOG("agl_shell/agl_shell_ext interfaces OK\n");
385 LOG("agl_shell/agl_shell_ext interfaces NOK\n");
390 destroy_shell_data(struct shell_data *sh)
392 struct window_output *w_output, *w_output_next;
394 wl_list_for_each_safe(w_output, w_output_next, &sh->output_list, link)
395 destroy_output(w_output);
397 for (std::list<global_data>::iterator it = sh->globals.begin();
398 it != sh->globals.end(); it++) {
399 sh->globals.erase(it);
402 wl_display_flush(sh->wl_display);
403 wl_display_disconnect(sh->wl_display);
409 start_grpc_server(std::shared_ptr<grpc::Server> server)
411 LOG("gRPC server listening\n");
415 int main(int argc, char **argv)
419 Shell *aglShell = nullptr;
422 // instantiante the grpc server
423 std::string server_address(kDefaultGrpcServiceAddress);
424 GrpcServiceImpl service{aglShell};
426 grpc::EnableDefaultHealthCheckService(true);
427 grpc::reflection::InitProtoReflectionServerBuilderPlugin();
429 grpc::ServerBuilder builder;
430 builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
431 builder.RegisterService(&service);
433 std::shared_ptr<grpc::Server> server(builder.BuildAndStart());
434 std::thread thread(start_grpc_server, server);
436 // this blocks until we detect that another shell client started
438 struct shell_data *sh = register_shell_ext();
440 LOG("Failed to get register ag_shell_ext\n");
445 std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
446 aglShell = new Shell(agl_shell, sh);
448 // now that we have aglShell, set it to the gRPC proxy as well
449 service.setAglShell(aglShell);
451 // serve wayland requests
452 while (running && ret != -1) {
453 ret = wl_display_dispatch(sh->wl_display);
457 destroy_shell_data(sh);