blob: 3e876743d704c2560e75e52cb39db4ea26eca061 [file] [log] [blame]
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -04001/*
2 * Copyright © 2013 Intel Corporation
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting documentation, and
8 * that the name of the copyright holders not be used in advertising or
9 * publicity pertaining to distribution of the software without specific,
10 * written prior permission. The copyright holders make no representations
11 * about the suitability of this software for any purpose. It is provided "as
12 * is" without express or implied warranty.
13 *
14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20 * OF THIS SOFTWARE.
21 */
22
23#include <stdint.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <cairo.h>
28#include <math.h>
29#include <assert.h>
30#include <pixman.h>
31#include <sys/epoll.h>
32#include <sys/socket.h>
33#include <unistd.h>
34
35#include <EGL/egl.h>
36#include <EGL/eglext.h>
37#include <GLES2/gl2.h>
38#include <GLES2/gl2ext.h>
39
40#include <cairo-gl.h>
41
42#include <wayland-client.h>
Kristian Høgsberg3c179332013-08-06 19:27:04 -070043#define WL_HIDE_DEPRECATED
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -040044#include <wayland-server.h>
45
46#include "window.h"
47
Kristian Høgsberg919cddb2013-07-08 19:03:57 -040048#define MIN(x,y) (((x) < (y)) ? (x) : (y))
49
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -040050struct nested {
51 struct display *display;
52 struct window *window;
53 struct widget *widget;
54 struct wl_display *child_display;
55 struct task child_task;
56
57 EGLDisplay egl_display;
58 struct program *texture_program;
59
60 struct wl_list surface_list;
61 struct wl_list frame_callback_list;
62};
63
64struct nested_region {
Kristian Høgsberg88dab172013-06-24 16:35:54 -040065 struct wl_resource *resource;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -040066 pixman_region32_t region;
67};
68
69struct nested_surface {
Kristian Høgsberg88dab172013-06-24 16:35:54 -040070 struct wl_resource *resource;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -040071 struct wl_resource *buffer_resource;
72 struct nested *nested;
73 EGLImageKHR *image;
74 GLuint texture;
75 struct wl_list link;
76 cairo_surface_t *cairo_surface;
77};
78
79struct nested_frame_callback {
Kristian Høgsberg88dab172013-06-24 16:35:54 -040080 struct wl_resource *resource;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -040081 struct wl_list link;
82};
83
84static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d;
85static PFNEGLCREATEIMAGEKHRPROC create_image;
86static PFNEGLDESTROYIMAGEKHRPROC destroy_image;
87static PFNEGLBINDWAYLANDDISPLAYWL bind_display;
88static PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display;
89static PFNEGLQUERYWAYLANDBUFFERWL query_buffer;
90
91static void
92frame_callback(void *data, struct wl_callback *callback, uint32_t time)
93{
94 struct nested *nested = data;
95 struct nested_frame_callback *nc, *next;
96
97 if (callback)
98 wl_callback_destroy(callback);
99
100 wl_list_for_each_safe(nc, next, &nested->frame_callback_list, link) {
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400101 wl_callback_send_done(nc->resource, time);
102 wl_resource_destroy(nc->resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400103 }
104 wl_list_init(&nested->frame_callback_list);
105
106 /* FIXME: toytoolkit need a pre-block handler where we can
107 * call this. */
108 wl_display_flush_clients(nested->child_display);
109}
110
111static const struct wl_callback_listener frame_listener = {
112 frame_callback
113};
114
115static void
116redraw_handler(struct widget *widget, void *data)
117{
118 struct nested *nested = data;
119 cairo_surface_t *surface;
120 cairo_t *cr;
121 struct rectangle allocation;
122 struct wl_callback *callback;
123 struct nested_surface *s;
124
125 widget_get_allocation(nested->widget, &allocation);
126
127 surface = window_get_surface(nested->window);
128
129 cr = cairo_create(surface);
130 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
131 cairo_rectangle(cr,
132 allocation.x,
133 allocation.y,
134 allocation.width,
135 allocation.height);
136 cairo_set_source_rgba(cr, 0, 0, 0, 0.8);
137 cairo_fill(cr);
138
139 wl_list_for_each(s, &nested->surface_list, link) {
Ander Conselvan de Oliveirad224bb92013-07-16 14:24:04 +0300140 display_acquire_window_surface(nested->display,
141 nested->window, NULL);
142
143 glBindTexture(GL_TEXTURE_2D, s->texture);
144 image_target_texture_2d(GL_TEXTURE_2D, s->image);
145
146 display_release_window_surface(nested->display,
147 nested->window);
148
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400149 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
150 cairo_set_source_surface(cr, s->cairo_surface,
151 allocation.x + 10,
152 allocation.y + 10);
153 cairo_rectangle(cr, allocation.x + 10,
154 allocation.y + 10,
155 allocation.width - 10,
156 allocation.height - 10);
157
158 cairo_fill(cr);
159 }
160
161 cairo_destroy(cr);
162
163 cairo_surface_destroy(surface);
164
165 callback = wl_surface_frame(window_get_wl_surface(nested->window));
166 wl_callback_add_listener(callback, &frame_listener, nested);
167}
168
169static void
170keyboard_focus_handler(struct window *window,
171 struct input *device, void *data)
172{
173 struct nested *nested = data;
174
175 window_schedule_redraw(nested->window);
176}
177
178static void
179handle_child_data(struct task *task, uint32_t events)
180{
181 struct nested *nested = container_of(task, struct nested, child_task);
182 struct wl_event_loop *loop;
183
184 loop = wl_display_get_event_loop(nested->child_display);
185
186 wl_event_loop_dispatch(loop, -1);
187 wl_display_flush_clients(nested->child_display);
188}
189
190struct nested_client {
191 struct wl_client *client;
192 pid_t pid;
193};
194
195static struct nested_client *
196launch_client(struct nested *nested, const char *path)
197{
198 int sv[2];
199 pid_t pid;
200 struct nested_client *client;
201
202 client = malloc(sizeof *client);
203 if (client == NULL)
204 return NULL;
205
206 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) {
207 fprintf(stderr, "launch_client: "
208 "socketpair failed while launching '%s': %m\n",
209 path);
Kristian Høgsberg67733942013-10-09 13:30:58 -0700210 free(client);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400211 return NULL;
212 }
213
214 pid = fork();
215 if (pid == -1) {
216 close(sv[0]);
217 close(sv[1]);
Kristian Høgsberg67733942013-10-09 13:30:58 -0700218 free(client);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400219 fprintf(stderr, "launch_client: "
220 "fork failed while launching '%s': %m\n", path);
221 return NULL;
222 }
223
224 if (pid == 0) {
225 int clientfd;
226 char s[32];
227
228 /* SOCK_CLOEXEC closes both ends, so we dup the fd to
229 * get a non-CLOEXEC fd to pass through exec. */
230 clientfd = dup(sv[1]);
231 if (clientfd == -1) {
232 fprintf(stderr, "compositor: dup failed: %m\n");
233 exit(-1);
234 }
235
236 snprintf(s, sizeof s, "%d", clientfd);
237 setenv("WAYLAND_SOCKET", s, 1);
238
239 execl(path, path, NULL);
240
241 fprintf(stderr, "compositor: executing '%s' failed: %m\n",
242 path);
243 exit(-1);
244 }
245
246 close(sv[1]);
247
248 client->client = wl_client_create(nested->child_display, sv[0]);
249 if (!client->client) {
250 close(sv[0]);
Kristian Høgsberg67733942013-10-09 13:30:58 -0700251 free(client);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400252 fprintf(stderr, "launch_client: "
253 "wl_client_create failed while launching '%s'.\n",
254 path);
255 return NULL;
256 }
257
258 client->pid = pid;
259
260 return client;
261}
262
263static void
264destroy_surface(struct wl_resource *resource)
265{
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400266 struct nested_surface *surface = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400267
268 free(surface);
269}
270
271static void
272surface_destroy(struct wl_client *client, struct wl_resource *resource)
273{
274 wl_resource_destroy(resource);
275}
276
277static void
278surface_attach(struct wl_client *client,
279 struct wl_resource *resource,
280 struct wl_resource *buffer_resource, int32_t sx, int32_t sy)
281{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400282 struct nested_surface *surface = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400283 struct nested *nested = surface->nested;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400284 EGLint format, width, height;
285 cairo_device_t *device;
286
287 if (surface->buffer_resource)
288 wl_buffer_send_release(surface->buffer_resource);
289
290 surface->buffer_resource = buffer_resource;
Kristian Høgsberg0d5fe3a2013-08-15 15:17:57 -0700291 if (!query_buffer(nested->egl_display, buffer_resource,
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400292 EGL_TEXTURE_FORMAT, &format)) {
293 fprintf(stderr, "attaching non-egl wl_buffer\n");
294 return;
295 }
296
297 if (surface->image != EGL_NO_IMAGE_KHR)
298 destroy_image(nested->egl_display, surface->image);
299 if (surface->cairo_surface)
300 cairo_surface_destroy(surface->cairo_surface);
301
302 switch (format) {
303 case EGL_TEXTURE_RGB:
304 case EGL_TEXTURE_RGBA:
305 break;
306 default:
307 fprintf(stderr, "unhandled format: %x\n", format);
308 return;
309 }
310
311 surface->image = create_image(nested->egl_display, NULL,
Kristian Høgsberg0d5fe3a2013-08-15 15:17:57 -0700312 EGL_WAYLAND_BUFFER_WL, buffer_resource,
313 NULL);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400314 if (surface->image == EGL_NO_IMAGE_KHR) {
315 fprintf(stderr, "failed to create img\n");
316 return;
317 }
318
Kristian Høgsberg0d5fe3a2013-08-15 15:17:57 -0700319 query_buffer(nested->egl_display, buffer_resource, EGL_WIDTH, &width);
320 query_buffer(nested->egl_display, buffer_resource, EGL_HEIGHT, &height);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400321
322 device = display_get_cairo_device(nested->display);
323 surface->cairo_surface =
324 cairo_gl_surface_create_for_texture(device,
325 CAIRO_CONTENT_COLOR_ALPHA,
326 surface->texture,
327 width, height);
328
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400329 window_schedule_redraw(nested->window);
330}
331
332static void
333surface_damage(struct wl_client *client,
334 struct wl_resource *resource,
335 int32_t x, int32_t y, int32_t width, int32_t height)
336{
337}
338
339static void
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400340destroy_frame_callback(struct wl_resource *resource)
341{
342 struct nested_frame_callback *callback = wl_resource_get_user_data(resource);
343
344 wl_list_remove(&callback->link);
345 free(callback);
346}
347
348static void
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400349surface_frame(struct wl_client *client,
350 struct wl_resource *resource, uint32_t id)
351{
352 struct nested_frame_callback *callback;
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400353 struct nested_surface *surface = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400354 struct nested *nested = surface->nested;
355
356 callback = malloc(sizeof *callback);
357 if (callback == NULL) {
358 wl_resource_post_no_memory(resource);
359 return;
360 }
361
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400362 callback->resource = wl_resource_create(client,
363 &wl_callback_interface, 1, id);
364 wl_resource_set_implementation(callback->resource, NULL, callback,
365 destroy_frame_callback);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400366
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400367 wl_list_insert(nested->frame_callback_list.prev, &callback->link);
368}
369
370static void
371surface_set_opaque_region(struct wl_client *client,
372 struct wl_resource *resource,
373 struct wl_resource *region_resource)
374{
375 fprintf(stderr, "surface_set_opaque_region\n");
376}
377
378static void
379surface_set_input_region(struct wl_client *client,
380 struct wl_resource *resource,
381 struct wl_resource *region_resource)
382{
383 fprintf(stderr, "surface_set_input_region\n");
384}
385
386static void
387surface_commit(struct wl_client *client, struct wl_resource *resource)
388{
389}
390
391static void
392surface_set_buffer_transform(struct wl_client *client,
393 struct wl_resource *resource, int transform)
394{
395 fprintf(stderr, "surface_set_buffer_transform\n");
396}
397
398static const struct wl_surface_interface surface_interface = {
399 surface_destroy,
400 surface_attach,
401 surface_damage,
402 surface_frame,
403 surface_set_opaque_region,
404 surface_set_input_region,
405 surface_commit,
406 surface_set_buffer_transform
407};
408
409static void
410compositor_create_surface(struct wl_client *client,
411 struct wl_resource *resource, uint32_t id)
412{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400413 struct nested *nested = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400414 struct nested_surface *surface;
415
Peter Huttererf3d62272013-08-08 11:57:05 +1000416 surface = zalloc(sizeof *surface);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400417 if (surface == NULL) {
418 wl_resource_post_no_memory(resource);
419 return;
420 }
421
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400422 surface->nested = nested;
423
424 display_acquire_window_surface(nested->display,
425 nested->window, NULL);
426
427 glGenTextures(1, &surface->texture);
428 glBindTexture(GL_TEXTURE_2D, surface->texture);
429 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
430 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
431 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
432 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
433
434 display_release_window_surface(nested->display, nested->window);
435
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400436 surface->resource =
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400437 wl_resource_create(client, &wl_surface_interface, 1, id);
438
439 wl_resource_set_implementation(surface->resource,
440 &surface_interface, surface,
441 destroy_surface);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400442
443 wl_list_insert(nested->surface_list.prev, &surface->link);
444}
445
446static void
447destroy_region(struct wl_resource *resource)
448{
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400449 struct nested_region *region = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400450
451 pixman_region32_fini(&region->region);
452 free(region);
453}
454
455static void
456region_destroy(struct wl_client *client, struct wl_resource *resource)
457{
458 wl_resource_destroy(resource);
459}
460
461static void
462region_add(struct wl_client *client, struct wl_resource *resource,
463 int32_t x, int32_t y, int32_t width, int32_t height)
464{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400465 struct nested_region *region = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400466
467 pixman_region32_union_rect(&region->region, &region->region,
468 x, y, width, height);
469}
470
471static void
472region_subtract(struct wl_client *client, struct wl_resource *resource,
473 int32_t x, int32_t y, int32_t width, int32_t height)
474{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400475 struct nested_region *region = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400476 pixman_region32_t rect;
477
478 pixman_region32_init_rect(&rect, x, y, width, height);
479 pixman_region32_subtract(&region->region, &region->region, &rect);
480 pixman_region32_fini(&rect);
481}
482
483static const struct wl_region_interface region_interface = {
484 region_destroy,
485 region_add,
486 region_subtract
487};
488
489static void
490compositor_create_region(struct wl_client *client,
491 struct wl_resource *resource, uint32_t id)
492{
493 struct nested_region *region;
494
495 region = malloc(sizeof *region);
496 if (region == NULL) {
497 wl_resource_post_no_memory(resource);
498 return;
499 }
500
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400501 pixman_region32_init(&region->region);
502
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400503 region->resource =
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400504 wl_resource_create(client, &wl_region_interface, 1, id);
505 wl_resource_set_implementation(region->resource, &region_interface,
506 region, destroy_region);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400507}
508
509static const struct wl_compositor_interface compositor_interface = {
510 compositor_create_surface,
511 compositor_create_region
512};
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400513
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400514static void
515compositor_bind(struct wl_client *client,
516 void *data, uint32_t version, uint32_t id)
517{
518 struct nested *nested = data;
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400519 struct wl_resource *resource;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400520
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400521 resource = wl_resource_create(client, &wl_compositor_interface,
522 MIN(version, 3), id);
523 wl_resource_set_implementation(resource, &compositor_interface,
524 nested, NULL);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400525}
526
527static int
528nested_init_compositor(struct nested *nested)
529{
530 const char *extensions;
531 struct wl_event_loop *loop;
532 int fd, ret;
533
534 wl_list_init(&nested->surface_list);
535 wl_list_init(&nested->frame_callback_list);
536 nested->child_display = wl_display_create();
537 loop = wl_display_get_event_loop(nested->child_display);
538 fd = wl_event_loop_get_fd(loop);
539 nested->child_task.run = handle_child_data;
540 display_watch_fd(nested->display, fd,
541 EPOLLIN, &nested->child_task);
542
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400543 if (!wl_global_create(nested->child_display,
544 &wl_compositor_interface, 1,
545 nested, compositor_bind))
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400546 return -1;
547
548 wl_display_init_shm(nested->child_display);
549
550 nested->egl_display = display_get_egl_display(nested->display);
551 extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS);
552 if (strstr(extensions, "EGL_WL_bind_wayland_display") == NULL) {
553 fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n");
554 return -1;
555 }
556
557 bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL");
558 unbind_display = (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL");
559 create_image = (void *) eglGetProcAddress("eglCreateImageKHR");
560 destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR");
561 query_buffer = (void *) eglGetProcAddress("eglQueryWaylandBufferWL");
562 image_target_texture_2d =
563 (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES");
564
565 ret = bind_display(nested->egl_display, nested->child_display);
566 if (!ret) {
567 fprintf(stderr, "failed to bind wl_display\n");
568 return -1;
569 }
570
571 return 0;
572}
573
574static struct nested *
575nested_create(struct display *display)
576{
577 struct nested *nested;
578
Peter Huttererf3d62272013-08-08 11:57:05 +1000579 nested = zalloc(sizeof *nested);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400580 if (nested == NULL)
581 return nested;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400582
583 nested->window = window_create(display);
584 nested->widget = frame_create(nested->window, nested);
585 window_set_title(nested->window, "Wayland Nested");
586 nested->display = display;
587
588 window_set_user_data(nested->window, nested);
589 widget_set_redraw_handler(nested->widget, redraw_handler);
590 window_set_keyboard_focus_handler(nested->window,
591 keyboard_focus_handler);
592
593 nested_init_compositor(nested);
594
595 widget_schedule_resize(nested->widget, 400, 400);
596
597 return nested;
598}
599
600static void
601nested_destroy(struct nested *nested)
602{
603 widget_destroy(nested->widget);
604 window_destroy(nested->window);
605 free(nested);
606}
607
608int
609main(int argc, char *argv[])
610{
611 struct display *display;
612 struct nested *nested;
613
614 display = display_create(&argc, argv);
615 if (display == NULL) {
616 fprintf(stderr, "failed to create display: %m\n");
617 return -1;
618 }
619
620 nested = nested_create(display);
621
Kristian Høgsbergcb61dcf2013-08-07 09:50:12 -0700622 launch_client(nested, "weston-nested-client");
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400623
624 display_run(display);
625
626 nested_destroy(nested);
627 display_destroy(display);
628
629 return 0;
630}