blob: 508523406073a322b9f37f82274a0be9f7409a96 [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øgsberge8344e32013-10-09 13:34:35 -0700291 if (!query_buffer(nested->egl_display, (void *) 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øgsberge8344e32013-10-09 13:34:35 -0700319 query_buffer(nested->egl_display,
320 (void *) buffer_resource, EGL_WIDTH, &width);
321 query_buffer(nested->egl_display,
322 (void *) buffer_resource, EGL_HEIGHT, &height);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400323
324 device = display_get_cairo_device(nested->display);
325 surface->cairo_surface =
326 cairo_gl_surface_create_for_texture(device,
327 CAIRO_CONTENT_COLOR_ALPHA,
328 surface->texture,
329 width, height);
330
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400331 window_schedule_redraw(nested->window);
332}
333
334static void
335surface_damage(struct wl_client *client,
336 struct wl_resource *resource,
337 int32_t x, int32_t y, int32_t width, int32_t height)
338{
339}
340
341static void
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400342destroy_frame_callback(struct wl_resource *resource)
343{
344 struct nested_frame_callback *callback = wl_resource_get_user_data(resource);
345
346 wl_list_remove(&callback->link);
347 free(callback);
348}
349
350static void
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400351surface_frame(struct wl_client *client,
352 struct wl_resource *resource, uint32_t id)
353{
354 struct nested_frame_callback *callback;
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400355 struct nested_surface *surface = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400356 struct nested *nested = surface->nested;
357
358 callback = malloc(sizeof *callback);
359 if (callback == NULL) {
360 wl_resource_post_no_memory(resource);
361 return;
362 }
363
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400364 callback->resource = wl_resource_create(client,
365 &wl_callback_interface, 1, id);
366 wl_resource_set_implementation(callback->resource, NULL, callback,
367 destroy_frame_callback);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400368
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400369 wl_list_insert(nested->frame_callback_list.prev, &callback->link);
370}
371
372static void
373surface_set_opaque_region(struct wl_client *client,
374 struct wl_resource *resource,
375 struct wl_resource *region_resource)
376{
377 fprintf(stderr, "surface_set_opaque_region\n");
378}
379
380static void
381surface_set_input_region(struct wl_client *client,
382 struct wl_resource *resource,
383 struct wl_resource *region_resource)
384{
385 fprintf(stderr, "surface_set_input_region\n");
386}
387
388static void
389surface_commit(struct wl_client *client, struct wl_resource *resource)
390{
391}
392
393static void
394surface_set_buffer_transform(struct wl_client *client,
395 struct wl_resource *resource, int transform)
396{
397 fprintf(stderr, "surface_set_buffer_transform\n");
398}
399
400static const struct wl_surface_interface surface_interface = {
401 surface_destroy,
402 surface_attach,
403 surface_damage,
404 surface_frame,
405 surface_set_opaque_region,
406 surface_set_input_region,
407 surface_commit,
408 surface_set_buffer_transform
409};
410
411static void
412compositor_create_surface(struct wl_client *client,
413 struct wl_resource *resource, uint32_t id)
414{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400415 struct nested *nested = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400416 struct nested_surface *surface;
417
Peter Huttererf3d62272013-08-08 11:57:05 +1000418 surface = zalloc(sizeof *surface);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400419 if (surface == NULL) {
420 wl_resource_post_no_memory(resource);
421 return;
422 }
423
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400424 surface->nested = nested;
425
426 display_acquire_window_surface(nested->display,
427 nested->window, NULL);
428
429 glGenTextures(1, &surface->texture);
430 glBindTexture(GL_TEXTURE_2D, surface->texture);
431 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
432 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
433 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
434 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
435
436 display_release_window_surface(nested->display, nested->window);
437
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400438 surface->resource =
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400439 wl_resource_create(client, &wl_surface_interface, 1, id);
440
441 wl_resource_set_implementation(surface->resource,
442 &surface_interface, surface,
443 destroy_surface);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400444
445 wl_list_insert(nested->surface_list.prev, &surface->link);
446}
447
448static void
449destroy_region(struct wl_resource *resource)
450{
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400451 struct nested_region *region = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400452
453 pixman_region32_fini(&region->region);
454 free(region);
455}
456
457static void
458region_destroy(struct wl_client *client, struct wl_resource *resource)
459{
460 wl_resource_destroy(resource);
461}
462
463static void
464region_add(struct wl_client *client, struct wl_resource *resource,
465 int32_t x, int32_t y, int32_t width, int32_t height)
466{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400467 struct nested_region *region = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400468
469 pixman_region32_union_rect(&region->region, &region->region,
470 x, y, width, height);
471}
472
473static void
474region_subtract(struct wl_client *client, struct wl_resource *resource,
475 int32_t x, int32_t y, int32_t width, int32_t height)
476{
Kristian Høgsberg1d7d8f02013-06-25 16:15:27 -0400477 struct nested_region *region = wl_resource_get_user_data(resource);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400478 pixman_region32_t rect;
479
480 pixman_region32_init_rect(&rect, x, y, width, height);
481 pixman_region32_subtract(&region->region, &region->region, &rect);
482 pixman_region32_fini(&rect);
483}
484
485static const struct wl_region_interface region_interface = {
486 region_destroy,
487 region_add,
488 region_subtract
489};
490
491static void
492compositor_create_region(struct wl_client *client,
493 struct wl_resource *resource, uint32_t id)
494{
495 struct nested_region *region;
496
497 region = malloc(sizeof *region);
498 if (region == NULL) {
499 wl_resource_post_no_memory(resource);
500 return;
501 }
502
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400503 pixman_region32_init(&region->region);
504
Kristian Høgsberg88dab172013-06-24 16:35:54 -0400505 region->resource =
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400506 wl_resource_create(client, &wl_region_interface, 1, id);
507 wl_resource_set_implementation(region->resource, &region_interface,
508 region, destroy_region);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400509}
510
511static const struct wl_compositor_interface compositor_interface = {
512 compositor_create_surface,
513 compositor_create_region
514};
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400515
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400516static void
517compositor_bind(struct wl_client *client,
518 void *data, uint32_t version, uint32_t id)
519{
520 struct nested *nested = data;
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400521 struct wl_resource *resource;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400522
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400523 resource = wl_resource_create(client, &wl_compositor_interface,
524 MIN(version, 3), id);
525 wl_resource_set_implementation(resource, &compositor_interface,
526 nested, NULL);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400527}
528
529static int
530nested_init_compositor(struct nested *nested)
531{
532 const char *extensions;
533 struct wl_event_loop *loop;
534 int fd, ret;
535
536 wl_list_init(&nested->surface_list);
537 wl_list_init(&nested->frame_callback_list);
538 nested->child_display = wl_display_create();
539 loop = wl_display_get_event_loop(nested->child_display);
540 fd = wl_event_loop_get_fd(loop);
541 nested->child_task.run = handle_child_data;
542 display_watch_fd(nested->display, fd,
543 EPOLLIN, &nested->child_task);
544
Kristian Høgsberg919cddb2013-07-08 19:03:57 -0400545 if (!wl_global_create(nested->child_display,
546 &wl_compositor_interface, 1,
547 nested, compositor_bind))
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400548 return -1;
549
550 wl_display_init_shm(nested->child_display);
551
552 nested->egl_display = display_get_egl_display(nested->display);
553 extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS);
554 if (strstr(extensions, "EGL_WL_bind_wayland_display") == NULL) {
555 fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n");
556 return -1;
557 }
558
559 bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL");
560 unbind_display = (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL");
561 create_image = (void *) eglGetProcAddress("eglCreateImageKHR");
562 destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR");
563 query_buffer = (void *) eglGetProcAddress("eglQueryWaylandBufferWL");
564 image_target_texture_2d =
565 (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES");
566
567 ret = bind_display(nested->egl_display, nested->child_display);
568 if (!ret) {
569 fprintf(stderr, "failed to bind wl_display\n");
570 return -1;
571 }
572
573 return 0;
574}
575
576static struct nested *
577nested_create(struct display *display)
578{
579 struct nested *nested;
580
Peter Huttererf3d62272013-08-08 11:57:05 +1000581 nested = zalloc(sizeof *nested);
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400582 if (nested == NULL)
583 return nested;
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400584
585 nested->window = window_create(display);
586 nested->widget = frame_create(nested->window, nested);
587 window_set_title(nested->window, "Wayland Nested");
588 nested->display = display;
589
590 window_set_user_data(nested->window, nested);
591 widget_set_redraw_handler(nested->widget, redraw_handler);
592 window_set_keyboard_focus_handler(nested->window,
593 keyboard_focus_handler);
594
595 nested_init_compositor(nested);
596
597 widget_schedule_resize(nested->widget, 400, 400);
598
599 return nested;
600}
601
602static void
603nested_destroy(struct nested *nested)
604{
605 widget_destroy(nested->widget);
606 window_destroy(nested->window);
607 free(nested);
608}
609
610int
611main(int argc, char *argv[])
612{
613 struct display *display;
614 struct nested *nested;
615
616 display = display_create(&argc, argv);
617 if (display == NULL) {
618 fprintf(stderr, "failed to create display: %m\n");
619 return -1;
620 }
621
622 nested = nested_create(display);
623
Kristian Høgsbergcb61dcf2013-08-07 09:50:12 -0700624 launch_client(nested, "weston-nested-client");
Kristian Høgsberg1cc5ac32013-04-11 21:47:41 -0400625
626 display_run(display);
627
628 nested_destroy(nested);
629 display_destroy(display);
630
631 return 0;
632}