]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadway-output.c
gdk: prevent NULL pointer access when debugging is enabled
[~andy/gtk] / gdk / broadway / broadway-output.c
1 #include <string.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <assert.h>
5 #include <errno.h>
6 #include <cairo.h>
7
8 #include "broadway-output.h"
9
10 /************************************************************************
11  *                Base64 functions                                      *
12  ************************************************************************/
13
14 static const char base64_alphabet[] =
15         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
16
17 #if 0 /* Unused for now */
18 static void
19 base64_uint8 (guint8 v, char *c)
20 {
21   c[0] = base64_alphabet[(v >> 0) & 0x3f];
22   c[1] = base64_alphabet[(v >> 6) & 0x3];
23 }
24 #endif
25
26 static void
27 base64_uint16 (guint32 v, char *c)
28 {
29   c[0] = base64_alphabet[(v >> 0) & 0x3f];
30   c[1] = base64_alphabet[(v >> 6) & 0x3f];
31   c[2] = base64_alphabet[(v >> 12) & 0xf];
32 }
33
34 #if 0
35 static void
36 base64_uint24 (guint32 v, char *c)
37 {
38   c[0] = base64_alphabet[(v >> 0) & 0x3f];
39   c[1] = base64_alphabet[(v >> 6) & 0x3f];
40   c[2] = base64_alphabet[(v >> 12) & 0x3f];
41   c[3] = base64_alphabet[(v >> 18) & 0x3f];
42 }
43 #endif
44
45 static void
46 base64_uint32 (guint32 v, char *c)
47 {
48   c[0] = base64_alphabet[(v >> 0) & 0x3f];
49   c[1] = base64_alphabet[(v >> 6) & 0x3f];
50   c[2] = base64_alphabet[(v >> 12) & 0x3f];
51   c[3] = base64_alphabet[(v >> 18) & 0x3f];
52   c[4] = base64_alphabet[(v >> 24) & 0x3f];
53   c[5] = base64_alphabet[(v >> 30) & 0x2];
54 }
55
56 /***********************************************************
57  *  conversion of raw image data to png data: uris         *
58  ***********************************************************/
59
60 static cairo_status_t
61 write_png_data (void              *closure,
62                 const unsigned char *data,
63                 unsigned int       data_len)
64 {
65   GString *buf = closure;
66
67   g_string_append_len (buf,  (char *)data, data_len);
68
69   return CAIRO_STATUS_SUCCESS;
70 }
71
72 static void
73 to_png_rgb (GString *buf, int w, int h, int byte_stride, guint32 *data)
74 {
75   cairo_surface_t *surface;
76
77   surface = cairo_image_surface_create_for_data ((guchar *)data,
78                                                  CAIRO_FORMAT_RGB24, w, h, byte_stride);
79
80   cairo_surface_write_to_png_stream (surface, write_png_data, buf);
81   cairo_surface_destroy (surface);
82 }
83
84 static void
85 to_png_rgba (GString *buf, int w, int h, int byte_stride, guint32 *data)
86 {
87   cairo_surface_t *surface;
88
89   surface = cairo_image_surface_create_for_data ((guchar *)data,
90                                                  CAIRO_FORMAT_ARGB32, w, h, byte_stride);
91
92   cairo_surface_write_to_png_stream (surface, write_png_data, buf);
93   cairo_surface_destroy (surface);
94 }
95
96 struct PngTarget {
97   GString *buf;
98   int state;
99   int save;
100 };
101
102 static cairo_status_t
103 write_png_url (void               *closure,
104                const unsigned char *data,
105                unsigned int        data_len)
106 {
107   struct PngTarget *target = closure;
108   gsize res, old_len;
109
110   old_len = target->buf->len;
111   g_string_set_size (target->buf,
112                      old_len + (data_len / 3 + 1) * 4 + 4);
113
114   res = g_base64_encode_step (data, data_len, FALSE,
115                               target->buf->str + old_len,
116                               &target->state, &target->save);
117
118   g_string_set_size (target->buf,  old_len + res);
119
120   return CAIRO_STATUS_SUCCESS;
121 }
122
123 static void
124 to_png_url_rgb (GString *buf, int w, int h, int byte_stride, guint32 *data)
125 {
126   cairo_surface_t *surface;
127   struct PngTarget target;
128   gsize res, old_len;
129
130   target.buf = buf;
131   target.state = 0;
132   target.save = 0;
133
134   g_string_append (buf, "data:image/png;base64,");
135
136   surface = cairo_image_surface_create_for_data ((guchar *)data,
137                                                  CAIRO_FORMAT_RGB24, w, h, byte_stride);
138
139   cairo_surface_write_to_png_stream (surface, write_png_url, &target);
140   cairo_surface_destroy (surface);
141
142   old_len = buf->len;
143
144   g_string_set_size (buf, old_len + 4);
145   res = g_base64_encode_close (FALSE,
146                                buf->str + old_len,
147                                &target.state, &target.save);
148   g_string_set_size (buf, old_len + res);
149 }
150
151 static void
152 to_png_url_rgba (GString *buf, int w, int h, int byte_stride, guint32 *data)
153 {
154   cairo_surface_t *surface;
155   struct PngTarget target;
156   gsize res, old_len;
157
158   target.buf = buf;
159   target.state = 0;
160   target.save = 0;
161
162   g_string_append (buf, "data:image/png;base64,");
163
164   surface = cairo_image_surface_create_for_data ((guchar *)data,
165                                                  CAIRO_FORMAT_ARGB32, w, h, byte_stride);
166
167   cairo_surface_write_to_png_stream (surface, write_png_url, &target);
168   cairo_surface_destroy (surface);
169
170   old_len = buf->len;
171
172   g_string_set_size (buf, old_len + 4);
173   res = g_base64_encode_close (FALSE,
174                                buf->str + old_len,
175                                &target.state, &target.save);
176   g_string_set_size (buf, old_len + res);
177 }
178
179 #if 0
180 static char *
181 to_png_a (int w, int h, int byte_stride, guint8 *data)
182 {
183   cairo_surface_t *surface;
184   struct PngTarget target;
185   gsize res, old_len;
186
187   target.url = g_string_new ("data:image/png;base64,");
188   target.state = 0;
189   target.save = 0;
190
191   surface = cairo_image_surface_create_for_data (data,
192                                                  CAIRO_FORMAT_A8, w, h, byte_stride);
193
194   cairo_surface_write_to_png_stream (surface, write_png_url, &target);
195
196   old_len = target.url->len;
197
198   g_string_set_size (target.url, old_len + 4);
199   res = g_base64_encode_close (FALSE,
200                                target.url->str + old_len,
201                                &target.state, &target.save);
202   g_string_set_size (target.url, old_len + res);
203
204   return g_string_free (target.url, FALSE);
205 }
206 #endif
207
208 /************************************************************************
209  *                Basic I/O primitives                                  *
210  ************************************************************************/
211
212 struct BroadwayOutput {
213   GOutputStream *out;
214   GString *buf;
215   int error;
216   guint32 serial;
217   gboolean proto_v7_plus;
218   gboolean binary;
219 };
220
221 static void
222 broadway_output_send_cmd (BroadwayOutput *output,
223                           gboolean fin, BroadwayWSOpCode code,
224                           const void *buf, gsize count)
225 {
226   gboolean mask = FALSE;
227   guchar header[16];
228   size_t p;
229
230   gboolean mid_header = count > 125 && count <= 65535;
231   gboolean long_header = count > 65535;
232
233   /* NB. big-endian spec => bit 0 == MSB */
234   header[0] = ( (fin ? 0x80 : 0) | (code & 0x0f) );
235   header[1] = ( (mask ? 0x80 : 0) |
236                 (mid_header ? 126 : long_header ? 127 : count) );
237   p = 2;
238   if (mid_header)
239     {
240       *(guint16 *)(header + p) = GUINT16_TO_BE( (guint16)count );
241       p += 2;
242     }
243   else if (long_header)
244     {
245       *(guint64 *)(header + p) = GUINT64_TO_BE( count );
246       p += 8;
247     }
248   // FIXME: if we are paranoid we should 'mask' the data
249   // FIXME: we should really emit these as a single write
250   g_output_stream_write_all (output->out, header, p, NULL, NULL, NULL);
251   g_output_stream_write_all (output->out, buf, count, NULL, NULL, NULL);
252 }
253
254 static void
255 broadway_output_send_cmd_pre_v7 (BroadwayOutput *output,
256                                  const void *buf, gsize count)
257 {
258   g_output_stream_write_all (output->out, "\0", 1, NULL, NULL, NULL);
259   g_output_stream_write_all (output->out, buf, count, NULL, NULL, NULL);
260   g_output_stream_write_all (output->out, "\xff", 1, NULL, NULL, NULL);
261 }
262
263 void broadway_output_pong (BroadwayOutput *output)
264 {
265   if (output->proto_v7_plus)
266     broadway_output_send_cmd (output, TRUE, BROADWAY_WS_CNX_PONG, NULL, 0);
267 }
268
269 int
270 broadway_output_flush (BroadwayOutput *output)
271 {
272   if (output->buf->len == 0)
273     return TRUE;
274
275   if (!output->proto_v7_plus)
276     broadway_output_send_cmd_pre_v7 (output, output->buf->str, output->buf->len);
277   else if (output->binary)
278     broadway_output_send_cmd (output, TRUE, BROADWAY_WS_BINARY,
279                               output->buf->str, output->buf->len);
280   else
281     broadway_output_send_cmd (output, TRUE, BROADWAY_WS_TEXT,
282                               output->buf->str, output->buf->len);
283
284   g_string_set_size (output->buf, 0);
285
286   return !output->error;
287
288 }
289
290 BroadwayOutput *
291 broadway_output_new (GOutputStream *out, guint32 serial,
292                      gboolean proto_v7_plus, gboolean binary)
293 {
294   BroadwayOutput *output;
295
296   output = g_new0 (BroadwayOutput, 1);
297
298   output->out = g_object_ref (out);
299   output->buf = g_string_new ("");
300   output->serial = serial;
301   output->proto_v7_plus = proto_v7_plus;
302   output->binary = binary;
303
304   return output;
305 }
306
307 void
308 broadway_output_free (BroadwayOutput *output)
309 {
310   g_object_unref (output->out);
311   free (output);
312 }
313
314 guint32
315 broadway_output_get_next_serial (BroadwayOutput *output)
316 {
317   return output->serial;
318 }
319
320
321 /************************************************************************
322  *                     Core rendering operations                        *
323  ************************************************************************/
324
325 static void
326 append_char (BroadwayOutput *output, char c)
327 {
328   g_string_append_c (output->buf, c);
329 }
330
331 static void
332 append_bool (BroadwayOutput *output, gboolean val)
333 {
334   if (output->binary)
335     g_string_append_c (output->buf, val ? 1: 0);
336   else
337     g_string_append_c (output->buf, val ? '1': '0');
338 }
339
340 static void
341 append_flags (BroadwayOutput *output, guint32 val)
342 {
343   if (output->binary)
344     g_string_append_c (output->buf, val);
345   else
346     g_string_append_c (output->buf, val + '0');
347 }
348
349
350 static void
351 append_uint16 (BroadwayOutput *output, guint32 v)
352 {
353   gsize old_len = output->buf->len;
354   guint8 *buf;
355
356   if (output->binary)
357     {
358       g_string_set_size (output->buf, old_len + 2);
359       buf = (guint8 *)output->buf->str + old_len;
360       buf[0] = (v >> 0) & 0xff;
361       buf[1] = (v >> 8) & 0xff;
362     }
363   else
364     {
365       g_string_set_size (output->buf, old_len + 3);
366       base64_uint16 (v, output->buf->str + old_len);
367     }
368 }
369
370 static void
371 append_uint32 (BroadwayOutput *output, guint32 v)
372 {
373   gsize old_len = output->buf->len;
374   guint8 *buf;
375
376   if (output->binary)
377     {
378       g_string_set_size (output->buf, old_len + 4);
379       buf = (guint8 *)output->buf->str + old_len;
380       buf[0] = (v >> 0) & 0xff;
381       buf[1] = (v >> 8) & 0xff;
382       buf[2] = (v >> 16) & 0xff;
383       buf[3] = (v >> 24) & 0xff;
384     }
385   else
386     {
387       g_string_set_size (output->buf, old_len + 6);
388       base64_uint32 (v, output->buf->str + old_len);
389     }
390 }
391
392 static void
393 overwrite_uint32 (BroadwayOutput *output, gsize pos, guint32 v)
394 {
395   if (output->binary)
396     {
397       guint8 *buf = (guint8 *)output->buf->str + pos;
398
399       buf[0] = (v >> 0) & 0xff;
400       buf[1] = (v >> 8) & 0xff;
401       buf[2] = (v >> 16) & 0xff;
402       buf[3] = (v >> 24) & 0xff;
403     }
404   else
405     {
406       base64_uint32 (v, output->buf->str + pos);
407     }
408 }
409
410
411 static void
412 write_header(BroadwayOutput *output, char op)
413 {
414   append_char (output, op);
415   append_uint32 (output, output->serial++);
416 }
417
418 void
419 broadway_output_copy_rectangles (BroadwayOutput *output,  int id,
420                                  BroadwayRect *rects, int n_rects,
421                                  int dx, int dy)
422 {
423   int i;
424
425   write_header (output, BROADWAY_OP_COPY_RECTANGLES);
426   append_uint16 (output, id);
427   append_uint16 (output, n_rects);
428   for (i = 0; i < n_rects; i++)
429     {
430       append_uint16 (output, rects[i].x);
431       append_uint16 (output, rects[i].y);
432       append_uint16 (output, rects[i].width);
433       append_uint16 (output, rects[i].height);
434     }
435   append_uint16 (output, dx);
436   append_uint16 (output, dy);
437 }
438
439 void
440 broadway_output_grab_pointer (BroadwayOutput *output,
441                               int id,
442                               gboolean owner_event)
443 {
444   write_header (output, BROADWAY_OP_GRAB_POINTER);
445   append_uint16 (output, id);
446   append_bool (output, owner_event);
447 }
448
449 guint32
450 broadway_output_ungrab_pointer (BroadwayOutput *output)
451 {
452   guint32 serial;
453
454   serial = output->serial;
455   write_header (output, BROADWAY_OP_UNGRAB_POINTER);
456
457   return serial;
458 }
459
460 void
461 broadway_output_new_surface(BroadwayOutput *output,
462                             int id, int x, int y, int w, int h,
463                             gboolean is_temp)
464 {
465   write_header (output, BROADWAY_OP_NEW_SURFACE);
466   append_uint16 (output, id);
467   append_uint16 (output, x);
468   append_uint16 (output, y);
469   append_uint16 (output, w);
470   append_uint16 (output, h);
471   append_bool (output, is_temp);
472 }
473
474 void
475 broadway_output_show_surface(BroadwayOutput *output,  int id)
476 {
477   write_header (output, BROADWAY_OP_SHOW_SURFACE);
478   append_uint16 (output, id);
479 }
480
481 void
482 broadway_output_hide_surface(BroadwayOutput *output,  int id)
483 {
484   write_header (output, BROADWAY_OP_HIDE_SURFACE);
485   append_uint16 (output, id);
486 }
487
488 void
489 broadway_output_destroy_surface(BroadwayOutput *output,  int id)
490 {
491   write_header (output, BROADWAY_OP_DESTROY_SURFACE);
492   append_uint16 (output, id);
493 }
494
495
496 void
497 broadway_output_move_resize_surface (BroadwayOutput *output,
498                                      int             id,
499                                      gboolean        has_pos,
500                                      int             x,
501                                      int             y,
502                                      gboolean        has_size,
503                                      int             w,
504                                      int             h)
505 {
506   int val;
507
508   if (!has_pos && !has_size)
509     return;
510
511   write_header (output, BROADWAY_OP_MOVE_RESIZE);
512   val = (!!has_pos) | ((!!has_size) << 1);
513   append_uint16 (output, id);
514   append_flags (output, val);
515   if (has_pos)
516     {
517       append_uint16 (output, x);
518       append_uint16 (output, y);
519     }
520   if (has_size)
521     {
522       append_uint16 (output, w);
523       append_uint16 (output, h);
524     }
525 }
526
527 void
528 broadway_output_set_transient_for (BroadwayOutput *output,
529                                    int             id,
530                                    int             parent_id)
531 {
532   write_header (output, BROADWAY_OP_SET_TRANSIENT_FOR);
533   append_uint16 (output, id);
534   append_uint16 (output, parent_id);
535 }
536
537
538 void
539 broadway_output_put_rgb (BroadwayOutput *output,  int id, int x, int y,
540                          int w, int h, int byte_stride, void *data)
541 {
542   gsize size_start, image_start, len;
543
544   write_header (output, BROADWAY_OP_PUT_RGB);
545
546   append_uint16 (output, id);
547   append_uint16 (output, x);
548   append_uint16 (output, y);
549
550   size_start = output->buf->len;
551   append_uint32 (output, 0);
552
553   image_start = output->buf->len;
554   if (output->binary)
555     to_png_rgb (output->buf, w, h, byte_stride, (guint32*)data);
556   else
557     to_png_url_rgb (output->buf, w, h, byte_stride, (guint32*)data);
558
559   len = output->buf->len - image_start;
560
561   overwrite_uint32 (output, size_start, len);
562 }
563
564 typedef struct  {
565   int x1, y1;
566   int x2, y2;
567 } BroadwayBox;
568
569 static int
570 is_any_x_set (unsigned char *data,
571               int box_x1, int box_x2,
572               int x1, int x2, int y, int *x_set,
573               int byte_stride)
574 {
575   int w ;
576   guint32 *ptr;
577
578   if (x1 < box_x1)
579     x1 = box_x1;
580
581   if (x2 > box_x2)
582     x2 = box_x2;
583
584   w = x2 - x1;
585   if (w > 0)
586     {
587       ptr = (guint32 *)(data + y * byte_stride + x1 * 4);
588       while (w-- > 0)
589         {
590           if (*ptr != 0)
591             {
592               if (x_set)
593                 *x_set = x1;
594               return 1;
595             }
596           ptr++;
597           x1++;
598         }
599     }
600   return 0;
601 }
602
603
604 #define EXTEND_X_FUZZ 10
605 #define EXTEND_Y_FUZZ 10
606
607 static int
608 extend_x_range (unsigned char *data,
609                 int box_x1, int box_y1,
610                 int box_x2, int box_y2,
611                 int *x1, int *x2, int y,
612                 int byte_stride)
613 {
614   int extended = 0;
615   int new_x;
616
617   while (is_any_x_set (data, box_x1, box_x2, *x1 - EXTEND_X_FUZZ, *x1, y, &new_x, byte_stride))
618     {
619       *x1 = new_x;
620       extended = 1;
621     }
622
623   while (is_any_x_set (data, box_x1, box_x2, *x2, *x2 + EXTEND_X_FUZZ, y, &new_x, byte_stride))
624     {
625       *x2 = new_x + 1;
626       extended = 1;
627     }
628
629   return extended;
630 }
631
632 static int
633 extend_y_range (unsigned char *data,
634                 int box_x1, int box_y1,
635                 int box_x2, int box_y2,
636                 int x1, int x2, int *y,
637                 int byte_stride)
638 {
639   int extended = 0;
640   int found_set;
641   int yy, y2;
642
643   while (*y < box_y2)
644     {
645       found_set = 0;
646
647       y2 = *y + EXTEND_Y_FUZZ;
648       if (y2 > box_y2)
649         y2 = box_y2;
650
651       for (yy = y2; yy > *y + 1; yy--)
652         {
653           if (is_any_x_set (data, box_x1, box_x2, x1, x2, yy - 1, NULL, byte_stride))
654             {
655               found_set = 1;
656               break;
657             }
658         }
659       if (!found_set)
660         break;
661       *y = yy;
662       extended = 1;
663     }
664
665   return extended;
666 }
667
668
669 static void
670 rgba_find_rects_extents (unsigned char *data,
671                          int box_x1, int box_y1,
672                          int box_x2, int box_y2,
673                          int x, int y,
674                          BroadwayBox *rect,
675                          int byte_stride)
676 {
677   int x1, x2, y1, y2, yy;
678   int extended;
679
680   x1 = x;
681   x2 = x + 1;
682   y1 = y;
683   y2 = y + 1;
684
685   do
686     {
687       /* Expand maximally for all known rows */
688       do
689         {
690           extended = 0;
691
692           for (yy = y1; yy < y2; yy++)
693             extended |= extend_x_range (data,
694                                         box_x1, box_y1,
695                                         box_x2, box_y2,
696                                         &x1, &x2, yy,
697                                         byte_stride);
698         }
699       while (extended);
700     }
701   while (extend_y_range(data,
702                         box_x1, box_y1,
703                         box_x2, box_y2,
704                         x1, x2, &y2,
705                         byte_stride));
706
707   rect->x1 = x1;
708   rect->x2 = x2;
709   rect->y1 = y1;
710   rect->y2 = y2;
711 }
712
713 static void
714 rgba_find_rects_sub (unsigned char *data,
715                      int box_x1, int box_y1,
716                      int box_x2, int box_y2,
717                      int byte_stride,
718                      BroadwayBox **rects,
719                      int *n_rects, int *alloc_rects)
720 {
721   guint32 *line;
722   BroadwayBox rect;
723   int x, y;
724
725   if (box_x1 == box_x2 || box_y1 == box_y2)
726     return;
727
728   for (y = box_y1; y < box_y2; y++)
729     {
730       line = (guint32 *)(data + y * byte_stride + box_x1 * 4);
731
732       for (x = box_x1; x < box_x2; x++)
733         {
734           if (*line != 0)
735             {
736               rgba_find_rects_extents (data,
737                                        box_x1, box_y1, box_x2, box_y2,
738                                        x, y, &rect, byte_stride);
739               if (*n_rects == *alloc_rects)
740                 {
741                   (*alloc_rects) *= 2;
742                   *rects = g_renew (BroadwayBox, *rects, *alloc_rects);
743                 }
744               (*rects)[*n_rects] = rect;
745               (*n_rects)++;
746               rgba_find_rects_sub (data,
747                                    box_x1, rect.y1,
748                                    rect.x1, rect.y2,
749                                    byte_stride,
750                                    rects, n_rects, alloc_rects);
751               rgba_find_rects_sub (data,
752                                    rect.x2, rect.y1,
753                                    box_x2, rect.y2,
754                                    byte_stride,
755                                    rects, n_rects, alloc_rects);
756               rgba_find_rects_sub (data,
757                                    box_x1, rect.y2,
758                                    box_x2, box_y2,
759                                    byte_stride,
760                                    rects, n_rects, alloc_rects);
761               return;
762             }
763           line++;
764         }
765     }
766 }
767
768 static BroadwayBox *
769 rgba_find_rects (unsigned char *data,
770                  int w, int h, int byte_stride,
771                  int *n_rects)
772 {
773   BroadwayBox *rects;
774   int alloc_rects;
775
776   alloc_rects = 20;
777   rects = g_new (BroadwayBox, alloc_rects);
778
779   *n_rects = 0;
780   rgba_find_rects_sub (data,
781                        0, 0, w, h, byte_stride,
782                        &rects, n_rects, &alloc_rects);
783
784   return rects;
785 }
786
787 void
788 broadway_output_put_rgba (BroadwayOutput *output,  int id, int x, int y,
789                           int w, int h, int byte_stride, void *data)
790 {
791   BroadwayBox *rects;
792   int i, n_rects;
793   gsize size_start, image_start, len;
794
795   rects = rgba_find_rects (data, w, h, byte_stride, &n_rects);
796
797   for (i = 0; i < n_rects; i++)
798     {
799       guint8 *subdata;
800
801       write_header (output, BROADWAY_OP_PUT_RGB);
802       append_uint16 (output, id);
803       append_uint16 (output, x + rects[i].x1);
804       append_uint16 (output, y + rects[i].y1);
805
806       size_start = output->buf->len;
807       append_uint32 (output, 0);
808
809       image_start = output->buf->len;
810
811       subdata = (guint8 *)data + rects[i].x1 * 4 + rects[i].y1 * byte_stride;
812       if (output->binary)
813         to_png_rgba (output->buf, rects[i].x2 - rects[i].x1,
814                      rects[i].y2 - rects[i].y1,
815                      byte_stride, (guint32*)subdata);
816       else
817         to_png_url_rgba (output->buf, rects[i].x2 - rects[i].x1,
818                          rects[i].y2 - rects[i].y1,
819                          byte_stride, (guint32*)subdata);
820
821       len = output->buf->len - image_start;
822
823       overwrite_uint32 (output, size_start, len);
824     }
825
826   free (rects);
827 }
828
829 void
830 broadway_output_surface_flush (BroadwayOutput *output,
831                                int             id)
832 {
833   write_header (output, BROADWAY_OP_FLUSH);
834   append_uint16 (output, id);
835 }