tinyds: Handle ds_seat
[platform/core/uifw/libds-tizen.git] / src / examples / tinyds.c
1 #include "pixman-helper.h"
2
3 #include <assert.h>
4 #include <stdbool.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <signal.h>
8 #include <time.h>
9
10 #include <drm_fourcc.h>
11 #include <pixman.h>
12 #include <wayland-server.h>
13 #include <libds/log.h>
14 #include <libds/backend.h>
15 #include <libds/allocator/shm.h>
16 #include <libds/backend/wayland.h>
17 #include <libds/swapchain.h>
18 #include <libds/compositor.h>
19 #include <libds/xdg_shell.h>
20 #include <libds/input_device.h>
21 #include <libds/keyboard.h>
22 #include <libds/touch.h>
23 #include <libds/pointer.h>
24 #include <libds/seat.h>
25
26 #define TINYDS_UNUSED   __attribute__((unused))
27
28 #define OUTPUT_WIDTH   1280
29 #define OUTPUT_HEIGHT  720
30
31 struct tinyds_server;
32
33 struct tinyds_pointer
34 {
35     struct ds_input_device *dev;
36     struct tinyds_server *server;
37
38     struct tinyds_view *focused_view;
39
40     struct wl_listener destroy;
41     struct wl_listener motion;
42     struct wl_listener motion_absolute;
43     struct wl_listener button;
44     struct wl_listener frame;
45 };
46
47 struct tinyds_keyboard
48 {
49     struct ds_input_device *dev;
50     struct tinyds_server *server;
51
52     struct wl_listener destroy;
53     struct wl_listener key;
54 };
55
56 struct tinyds_touch
57 {
58     struct ds_input_device *dev;
59     struct tinyds_server *server;
60
61     struct wl_listener destroy;
62     struct wl_listener down;
63     struct wl_listener up;
64     struct wl_listener motion;
65 };
66
67 struct tinyds_output
68 {
69     struct tinyds_server *server;
70     struct ds_output *ds_output;
71     struct ds_allocator *allocator;
72     struct ds_swapchain *swapchain;
73
74     struct wl_listener output_destroy;
75     struct wl_listener output_frame;
76
77     int width, height;
78
79     bool drawable;
80     bool damaged;
81 };
82
83 struct tinyds_server
84 {
85     struct wl_display *display;
86
87     struct ds_backend *backend;
88     struct ds_compositor *compositor;
89     struct ds_xdg_shell *xdg_shell;
90     struct ds_seat *seat;
91
92     struct tinyds_output output;
93
94     struct wl_list views;
95
96     struct wl_listener new_output;
97     struct wl_listener new_input;
98     struct wl_listener new_xdg_surface;
99 };
100
101 struct tinyds_view
102 {
103     struct tinyds_server *server;
104
105     struct ds_xdg_surface *xdg_surface;
106
107     struct wl_listener xdg_surface_map;
108     struct wl_listener xdg_surface_unmap;
109     struct wl_listener xdg_surface_destroy;
110     struct wl_listener surface_commit;
111     struct wl_list link; // tinyds_server::views
112
113     int x, y;
114     bool mapped;
115 };
116
117 static bool server_init(struct tinyds_server *server,
118         struct wl_display *display);
119 static void server_fini(struct tinyds_server *server);
120 static void server_add_view(struct tinyds_server *server,
121         struct ds_xdg_surface *xdg_surface);
122 static struct tinyds_view *server_view_at(struct tinyds_server *server,
123         double lx, double ly, double *sx, double *sy);
124 static bool output_init(struct tinyds_output *output,
125         struct tinyds_server *server, struct ds_output *ds_output,
126         int width, int height);
127 static void output_fini(struct tinyds_output *output);
128 static void output_damage(struct tinyds_output *output);
129 static void output_redraw(struct tinyds_output *output);
130 static void view_destroy(struct tinyds_view *view);
131 static void view_composite(struct tinyds_view *view,
132         pixman_image_t *dst_image);
133
134 int
135 main(void)
136 {
137     struct tinyds_server server;
138     struct wl_display *display;
139     const char *socket;
140
141     ds_log_init(DS_DBG, NULL);
142
143     display = wl_display_create();
144     assert(display);
145
146     assert(server_init(&server, display) == true);
147
148     socket = wl_display_add_socket_auto(display);
149     assert(socket);
150
151     ds_backend_start(server.backend);
152
153     output_damage(&server.output);
154     output_redraw(&server.output);
155
156     setenv("WAYLAND_DISPLAY", socket, true);
157
158     ds_inf("Running Wayland compositor on WAYLAND_DISPLAY=%s", socket);
159
160     wl_display_run(server.display);
161
162     server_fini(&server);
163     wl_display_destroy(display);
164
165     return 0;
166 }
167
168 static struct ds_backend *
169 create_wl_backend(struct wl_display *display)
170 {
171     if (!getenv("WAYLAND_DISPLAY") && !getenv("WAYLAND_SOCKET"))
172         return NULL;
173
174     return ds_wl_backend_create(display, NULL);
175 }
176
177 static void
178 server_handle_new_output(struct wl_listener *listener, void *data)
179 {
180     struct tinyds_server *server;
181     struct ds_output *ds_output = data;
182
183     server = wl_container_of(listener, server, new_output);
184
185     assert(output_init(&server->output, server, ds_output,
186             OUTPUT_WIDTH, OUTPUT_HEIGHT) == true);
187 }
188
189 static void
190 keyboard_handle_device_destroy(struct wl_listener *listener, void *data)
191 {
192     struct tinyds_keyboard *kbd;
193
194     kbd = wl_container_of(listener, kbd, destroy);
195
196     ds_inf("Keyboard(%p) destroyed", kbd);
197
198     wl_list_remove(&kbd->destroy.link);
199     wl_list_remove(&kbd->key.link);
200
201     free(kbd);
202 }
203
204 static bool
205 server_handle_keybinding(struct tinyds_server *server, xkb_keysym_t sym)
206 {
207     switch (sym) {
208         case XKB_KEY_BackSpace:
209             wl_display_terminate(server->display);
210             break;
211         default:
212             return false;
213     }
214
215     return true;
216 }
217
218 static void
219 keyboard_handle_key(struct wl_listener *listener, void *data)
220 {
221     struct tinyds_keyboard *kbd;
222     struct ds_event_keyboard_key *event = data;
223     struct ds_keyboard *ds_keyboard;
224     struct xkb_state *xkb_state;
225     const xkb_keysym_t *syms;
226     uint32_t modifiers;
227     int nsyms;
228
229     kbd = wl_container_of(listener, kbd, key);
230
231     ds_keyboard = ds_input_device_get_keyboard(kbd->dev);
232
233     modifiers = ds_keyboard_get_modifiers(ds_keyboard);
234     if ((modifiers & DS_MODIFIER_CTRL) &&
235             (modifiers & DS_MODIFIER_ALT) &&
236             (modifiers & DS_MODIFIER_SHIFT) &&
237             event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
238         xkb_state = ds_keyboard_get_xkb_state(ds_keyboard);
239         if (xkb_state) {
240             nsyms = xkb_state_key_get_syms(xkb_state, event->keycode + 8,
241                     &syms);
242             for (int i = 0; i < nsyms; i++) {
243                 server_handle_keybinding(kbd->server, syms[i]);
244             }
245         }
246     }
247 }
248
249 static void
250 server_add_keyboard(struct tinyds_server *server, struct ds_input_device *dev)
251 {
252     struct tinyds_keyboard *kbd;
253
254     kbd = calloc(1, sizeof *kbd);
255     assert(kbd);
256
257     kbd->dev = dev;
258     kbd->server = server;
259
260     kbd->destroy.notify = keyboard_handle_device_destroy;
261     ds_input_device_add_destroy_listener(dev, &kbd->destroy);
262
263     kbd->key.notify = keyboard_handle_key;
264     ds_keyboard_add_key_listener(ds_input_device_get_keyboard(dev), &kbd->key);
265
266     ds_inf("Keyboard(%p) added", kbd);
267 }
268
269 static void
270 touch_handle_device_destroy(struct wl_listener *listener, void *data)
271 {
272     struct tinyds_touch *touch;
273
274     touch = wl_container_of(listener, touch, destroy);
275
276     ds_inf("Touch(%p) destroyed", touch);
277
278     wl_list_remove(&touch->destroy.link);
279     wl_list_remove(&touch->down.link);
280     wl_list_remove(&touch->up.link);
281     wl_list_remove(&touch->motion.link);
282
283     free(touch);
284 }
285
286 static void
287 touch_handle_down(struct wl_listener *listener, void *data)
288 {
289     ds_inf("Touch device(%p): down", data);
290 }
291
292 static void
293 touch_handle_up(struct wl_listener *listener, void *data)
294 {
295     ds_inf("Touch device(%p): up", data);
296 }
297
298 static void
299 touch_handle_motion(struct wl_listener *listener, void *data)
300 {
301     ds_inf("Touch device(%p): motion", data);
302 }
303
304 static void
305 server_add_touch(struct tinyds_server *server, struct ds_input_device *dev)
306 {
307     struct tinyds_touch *touch;
308
309     touch = calloc(1, sizeof *touch);
310     assert(touch);
311
312     touch->dev = dev;
313     touch->server = server;
314
315     touch->destroy.notify = touch_handle_device_destroy;
316     ds_input_device_add_destroy_listener(dev, &touch->destroy);
317
318     touch->down.notify = touch_handle_down;
319     ds_touch_add_down_listener(ds_input_device_get_touch(dev), &touch->down);
320
321     touch->up.notify = touch_handle_up;
322     ds_touch_add_up_listener(ds_input_device_get_touch(dev), &touch->up);
323
324     touch->motion.notify = touch_handle_motion;
325     ds_touch_add_motion_listener(ds_input_device_get_touch(dev), &touch->motion);
326
327     ds_inf("Touch(%p) added", touch);
328 }
329
330 static void
331 pointer_handle_device_destroy(struct wl_listener *listener, void *data)
332 {
333     struct tinyds_pointer *pointer;
334
335     pointer = wl_container_of(listener, pointer, destroy);
336
337     ds_inf("Pointer(%p) destroyed", pointer);
338
339     wl_list_remove(&pointer->destroy.link);
340     wl_list_remove(&pointer->motion.link);
341     wl_list_remove(&pointer->motion_absolute.link);
342     wl_list_remove(&pointer->button.link);
343     wl_list_remove(&pointer->frame.link);
344
345     free(pointer);
346 }
347
348 static void
349 pointer_handle_motion(struct wl_listener *listener, void *data)
350 {
351     struct tinyds_pointer *pointer;
352
353     pointer = wl_container_of(listener, pointer, motion);
354
355     ds_inf("Pointer(%p) motion", pointer);
356 }
357
358 static void
359 pointer_handle_motion_absolute(struct wl_listener *listener, void *data)
360 {
361     struct tinyds_pointer *pointer;
362     struct ds_event_pointer_motion_absolute *event = data;
363     struct tinyds_view *view;
364     double ox, oy, sx, sy;
365
366     pointer = wl_container_of(listener, pointer, motion_absolute);
367
368     ds_inf("Pointer(%p) motion absolute: (x %f y %f) time(%d)",
369             pointer, event->x, event->y, event->time_msec);
370
371     ox = event->x * OUTPUT_WIDTH;
372     oy = event->y * OUTPUT_HEIGHT;
373     view = server_view_at(pointer->server, ox, oy, &sx, &sy);
374
375     if (pointer->focused_view != view) {
376         if (pointer->focused_view) {
377             ds_inf("Clear pointer focus from view(%p)", pointer->focused_view);
378             ds_seat_pointer_notify_clear_focus(pointer->server->seat);
379             pointer->focused_view = NULL;
380         }
381
382         if (view) {
383             ds_inf("Set pointer focus to view(%p)", view);
384             ds_seat_pointer_notify_enter(pointer->server->seat,
385                     ds_xdg_surface_get_surface(view->xdg_surface), sx, sy);
386             pointer->focused_view = view;
387         }
388     }
389
390     if (view) {
391         ds_seat_pointer_notify_motion(pointer->server->seat,
392                 event->time_msec, sx, sy);
393     }
394 }
395
396 static void
397 pointer_handle_button(struct wl_listener *listener, void *data)
398 {
399     struct tinyds_pointer *pointer;
400     struct ds_event_pointer_button *event = data;
401
402     pointer = wl_container_of(listener, pointer, button);
403
404     ds_inf("Pointer(%p) button(%d): state(%s) time(%d)",
405             pointer, event->button,
406             (event->state == DS_BUTTON_PRESSED) ? "Pressed" : "Released",
407             event->time_msec);
408 }
409
410 static void
411 pointer_handle_frame(struct wl_listener *listener, void *data)
412 {
413     struct tinyds_pointer *pointer;
414
415     pointer = wl_container_of(listener, pointer, frame);
416
417     ds_inf("Pointer(%p) frame", pointer);
418     ds_seat_pointer_notify_frame(pointer->server->seat);
419 }
420
421 static void
422 server_add_pointer(struct tinyds_server *server, struct ds_input_device *dev)
423 {
424     struct tinyds_pointer *pointer;
425
426     pointer = calloc(1, sizeof *pointer);
427     assert(pointer);
428
429     pointer->dev = dev;
430     pointer->server = server;
431
432     pointer->destroy.notify = pointer_handle_device_destroy;
433     ds_input_device_add_destroy_listener(dev, &pointer->destroy);
434
435     pointer->motion.notify = pointer_handle_motion;
436     ds_pointer_add_motion_listener(ds_input_device_get_pointer(dev),
437             &pointer->motion);
438
439     pointer->motion_absolute.notify = pointer_handle_motion_absolute;
440     ds_pointer_add_motion_absolute_listener(ds_input_device_get_pointer(dev),
441             &pointer->motion_absolute);
442
443     pointer->button.notify = pointer_handle_button;
444     ds_pointer_add_button_listener(ds_input_device_get_pointer(dev),
445             &pointer->button);
446
447     pointer->frame.notify = pointer_handle_frame;
448     ds_pointer_add_frame_listener(ds_input_device_get_pointer(dev),
449             &pointer->frame);
450
451     ds_inf("Pointer(%p) added", pointer);
452 }
453
454 static void
455 server_handle_new_input(struct wl_listener *listener, void *data)
456 {
457     struct tinyds_server *server;
458     struct ds_input_device *dev = data;
459     enum ds_input_device_type dev_type;
460
461     server = wl_container_of(listener, server, new_input);
462
463     dev_type = ds_input_device_get_type(dev);
464     switch (dev_type) {
465         case DS_INPUT_DEVICE_KEYBOARD:
466             server_add_keyboard(server, dev);
467             break;
468         case DS_INPUT_DEVICE_TOUCH:
469             server_add_touch(server, dev);
470             break;
471         case DS_INPUT_DEVICE_POINTER:
472             server_add_pointer(server, dev);
473             ds_seat_set_capabilities(server->seat, WL_SEAT_CAPABILITY_POINTER);
474             break;
475         default:
476             ds_err("Unknown type(%d) of ds_input_device", dev_type);
477             break;
478     }
479 }
480
481 static void
482 view_handle_xdg_surface_map(struct wl_listener *listener,
483         void *data TINYDS_UNUSED)
484 {
485     struct tinyds_view *view;
486
487     view = wl_container_of(listener, view, xdg_surface_map);
488     view->mapped = true;
489 }
490
491 static void
492 view_handle_xdg_surface_unmap(struct wl_listener *listener,
493         void *data TINYDS_UNUSED)
494 {
495     struct tinyds_view *view;
496
497     view = wl_container_of(listener, view, xdg_surface_unmap);
498     view->mapped = false;
499 }
500
501 static void
502 view_handle_xdg_surface_destroy(struct wl_listener *listener,
503         void *data TINYDS_UNUSED) 
504 {
505     struct tinyds_view *view;
506
507     view = wl_container_of(listener, view, xdg_surface_destroy);
508
509     output_damage(&view->server->output);
510     output_redraw(&view->server->output);
511
512     view_destroy(view);
513 }
514
515 static void
516 view_handle_surface_commit(struct wl_listener *listener,
517         void *data TINYDS_UNUSED)
518 {
519     struct tinyds_view *view;
520
521     view = wl_container_of(listener, view, surface_commit);
522
523     output_damage(&view->server->output);
524     output_redraw(&view->server->output);
525 }
526
527 static void
528 server_new_xdg_surface(struct wl_listener *listener, void *data)
529 {
530     struct tinyds_server *server;
531
532     server = wl_container_of(listener, server, new_xdg_surface);
533
534     server_add_view(server, (struct ds_xdg_surface *)data);
535 }
536
537 static bool
538 server_init(struct tinyds_server *server, struct wl_display *display)
539 {
540     server->display = display;
541
542     wl_list_init(&server->views);
543
544     if (wl_display_init_shm(display) != 0)
545         return false;
546
547     server->backend = create_wl_backend(display);
548     if (!server->backend)
549         return false;
550
551     ds_wl_backend_create_output(server->backend);
552
553     server->new_input.notify = server_handle_new_input;
554     ds_backend_add_new_input_listener(server->backend, &server->new_input);
555
556     server->new_output.notify = server_handle_new_output;
557     ds_backend_add_new_output_listener(server->backend, &server->new_output);
558
559     server->compositor = ds_compositor_create(display);
560     if (!server->compositor)
561         goto err;
562
563     server->xdg_shell = ds_xdg_shell_create(display);
564     if (!server->xdg_shell)
565         goto err;
566
567     server->new_xdg_surface.notify = server_new_xdg_surface;
568     ds_xdg_shell_add_new_surface_listener(server->xdg_shell,
569             &server->new_xdg_surface);
570
571     server->seat = ds_seat_create(display, "seat0" /* arbitrary name */);
572     if (!server->seat)
573         goto err;
574
575     return true;
576
577 err:
578     ds_backend_destroy(server->backend);
579
580     return false;
581 }
582
583 static void
584 server_fini(struct tinyds_server *server)
585 {
586     struct tinyds_view *view, *tmp;
587
588     wl_list_for_each_safe(view, tmp, &server->views, link)
589         view_destroy(view);
590
591     output_fini(&server->output);
592
593     wl_list_remove(&server->new_xdg_surface.link);
594 }
595
596 static void
597 output_handle_destroy(struct wl_listener *listener, void *data TINYDS_UNUSED)
598 {
599     struct tinyds_output *output =
600         wl_container_of(listener, output, output_destroy);
601
602     wl_list_remove(&output->output_destroy.link);
603     wl_list_remove(&output->output_frame.link);
604     output->ds_output = NULL;
605
606     wl_display_terminate(output->server->display);
607 }
608
609 static void
610 output_handle_frame(struct wl_listener *listener, void *data TINYDS_UNUSED)
611 {
612     struct tinyds_output *output =
613         wl_container_of(listener, output, output_frame);
614
615     output->drawable = true;
616     output_redraw(output);
617 }
618
619 static bool
620 output_init(struct tinyds_output *output, struct tinyds_server *server,
621         struct ds_output *ds_output, int width, int height)
622 {
623     output->server = server;
624     output->ds_output = ds_output;
625     output->width = width;
626     output->height = height;
627     output->drawable = true;
628
629     ds_output_set_custom_mode(ds_output, OUTPUT_WIDTH, OUTPUT_HEIGHT, 0);
630
631     output->allocator = ds_shm_allocator_create();
632     if (!output->allocator)
633         return false;
634
635     output->swapchain = ds_swapchain_create(output->allocator,
636             width, height, DRM_FORMAT_XRGB8888);
637     if (!output->swapchain)
638         goto err_swapchain;
639
640     output->output_destroy.notify = output_handle_destroy;
641     ds_output_add_destroy_listener(output->ds_output, &output->output_destroy);
642
643     output->output_frame.notify = output_handle_frame;
644     ds_output_add_frame_listener(output->ds_output, &output->output_frame);
645
646     return true;
647
648 err_swapchain:
649     ds_allocator_destroy(output->allocator);
650
651     return false;
652 }
653
654 static void
655 output_fini(struct tinyds_output *output)
656 {
657     ds_output_destroy(output->ds_output);
658     ds_swapchain_destroy(output->swapchain);
659     ds_allocator_destroy(output->allocator);
660 }
661
662 static void
663 output_damage(struct tinyds_output *output)
664 {
665     output->damaged = true;
666 }
667
668 static void
669 output_redraw(struct tinyds_output *output)
670 {
671     struct ds_buffer *output_buffer;
672     pixman_image_t *output_image;
673     struct tinyds_view *view;
674
675     if (!output->drawable || !output->damaged)
676         return;
677
678     output_buffer = ds_swapchain_acquire(output->swapchain, NULL);
679     if (!output_buffer)
680         return;
681
682     output_image = pixman_image_from_buffer(output_buffer,
683             DS_BUFFER_DATA_PTR_ACCESS_WRITE);
684     if (!output_image)
685         goto out;
686
687     pixman_image_fill_color(output_image, 80, 80, 80);
688
689     wl_list_for_each(view, &output->server->views, link) {
690         if (!view->mapped)
691             continue;
692         view_composite(view, output_image);
693     }
694     pixman_image_unref(output_image);
695
696     ds_output_attach_buffer(output->ds_output, output_buffer);
697     ds_output_commit(output->ds_output);
698
699     output->drawable = false;
700     output->damaged = false;
701
702 out:
703     ds_buffer_unlock(output_buffer);
704 }
705
706 static void
707 server_add_view(struct tinyds_server *server, struct ds_xdg_surface *xdg_surface)
708 {
709     struct tinyds_view *view;
710
711     view = calloc(1, sizeof *view);
712     view->server = server;
713     view->xdg_surface = xdg_surface;
714
715     view->xdg_surface_map.notify = view_handle_xdg_surface_map;
716     ds_xdg_surface_add_map_listener(xdg_surface,
717             &view->xdg_surface_map);
718
719     view->xdg_surface_unmap.notify = view_handle_xdg_surface_unmap;
720     ds_xdg_surface_add_unmap_listener(xdg_surface,
721             &view->xdg_surface_unmap);
722
723     view->xdg_surface_destroy.notify = view_handle_xdg_surface_destroy;
724     ds_xdg_surface_add_destroy_listener(xdg_surface,
725             &view->xdg_surface_destroy);
726
727     view->surface_commit.notify = view_handle_surface_commit;
728     ds_surface_add_commit_listener(
729             ds_xdg_surface_get_surface(xdg_surface),
730             &view->surface_commit);
731
732     wl_list_insert(server->views.prev, &view->link);
733
734     ds_inf("View(%p) added", view);
735 }
736
737 static struct tinyds_view *
738 server_view_at(struct tinyds_server *server, double lx, double ly,
739         double *sx, double *sy)
740 {
741     struct tinyds_view *view;
742     struct ds_surface *surface;
743     struct ds_buffer *buffer;
744     int x, y, w = 0, h = 0;
745
746     wl_list_for_each(view, &server->views, link) {
747         surface = ds_xdg_surface_get_surface(view->xdg_surface);
748         buffer = ds_surface_get_buffer(surface);
749         ds_buffer_get_size(buffer, &w, &h);
750
751         x = view->x;
752         y = view->y;
753
754         if (lx >= x && lx <= w && ly >= y && ly <= h) {
755             *sx = lx - x;
756             *sy = ly - y;
757
758             return view;
759         }
760     }
761
762     return NULL;
763 }
764
765 static void
766 view_destroy(struct tinyds_view *view)
767 {
768     ds_inf("View(%p) destroyed", view);
769
770     wl_list_remove(&view->xdg_surface_destroy.link);
771     wl_list_remove(&view->xdg_surface_map.link);
772     wl_list_remove(&view->xdg_surface_unmap.link);
773     wl_list_remove(&view->surface_commit.link);
774     wl_list_remove(&view->link);
775     free(view);
776 }
777
778 static void
779 view_send_frame_done(struct tinyds_view *view)
780 {
781     struct timespec now;
782
783     clock_gettime(CLOCK_MONOTONIC, &now);
784     ds_surface_send_frame_done(ds_xdg_surface_get_surface(view->xdg_surface),
785             &now);
786 }
787
788 static void
789 view_composite(struct tinyds_view *view, pixman_image_t *dst_image)
790 {
791     struct ds_buffer *buffer;
792     pixman_image_t *src_image;
793
794     buffer = ds_surface_get_buffer(
795             ds_xdg_surface_get_surface(view->xdg_surface));
796     if (!buffer)
797         return;
798
799     src_image = pixman_image_from_buffer(buffer,
800             DS_BUFFER_DATA_PTR_ACCESS_READ);
801     pixman_image_composite32(PIXMAN_OP_OVER,
802             src_image,
803             NULL,
804             dst_image,
805             0, 0, 0, 0, 0, 0,
806             pixman_image_get_width(src_image),
807             pixman_image_get_height(src_image));
808     pixman_image_unref(src_image);
809
810     view_send_frame_done(view);
811 }