]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadway-server.c
broadway: Allow binding http server to an ip
[~andy/gtk] / gdk / broadway / broadway-server.c
1 #include "broadway-server.h"
2
3 #include "broadway-output.h"
4
5 #include <glib.h>
6 #include <glib/gprintf.h>
7 #include "gdktypes.h"
8 #include <stdlib.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <netinet/tcp.h>
16
17 typedef struct BroadwayInput BroadwayInput;
18 typedef struct BroadwayWindow BroadwayWindow;
19 struct _BroadwayServer {
20   GObject parent_instance;
21
22   char *address;
23   int port;
24   GSocketService *service;
25   BroadwayOutput *output;
26   guint32 id_counter;
27   guint32 saved_serial;
28   guint64 last_seen_time;
29   BroadwayInput *input;
30   GList *input_messages;
31   guint process_input_idle;
32
33   GHashTable *id_ht;
34   GList *toplevels;
35   BroadwayWindow *root;
36
37   guint32 screen_width;
38   guint32 screen_height;
39
40   gint32 mouse_in_toplevel_id;
41   int last_x, last_y; /* in root coords */
42   guint32 last_state;
43   gint32 real_mouse_in_toplevel_id; /* Not affected by grabs */
44
45   /* Explicit pointer grabs: */
46   gint32 pointer_grab_window_id; /* -1 => none */
47   gint32 pointer_grab_client_id; /* -1 => none */
48   guint32 pointer_grab_time;
49   gboolean pointer_grab_owner_events;
50
51   /* Future data, from the currently queued events */
52   int future_root_x;
53   int future_root_y;
54   guint32 future_state;
55   int future_mouse_in_toplevel;
56 };
57
58 struct _BroadwayServerClass
59 {
60   GObjectClass parent_class;
61 };
62
63 typedef struct HttpRequest {
64   BroadwayServer *server;
65   GSocketConnection *connection;
66   GDataInputStream *data;
67   GString *request;
68 }  HttpRequest;
69
70 struct BroadwayInput {
71   BroadwayServer *server;
72   GSocketConnection *connection;
73   GByteArray *buffer;
74   GSource *source;
75   gboolean seen_time;
76   gint64 time_base;
77   gboolean proto_v7_plus;
78   gboolean binary;
79 };
80
81 struct BroadwayWindow {
82   gint32 id;
83   gint32 x;
84   gint32 y;
85   gint32 width;
86   gint32 height;
87   gboolean is_temp;
88   gboolean last_synced;
89   gboolean visible;
90   gint32 transient_for;
91
92   cairo_surface_t *last_surface;
93 };
94
95 static void broadway_server_resync_windows (BroadwayServer *server);
96
97 G_DEFINE_TYPE (BroadwayServer, broadway_server, G_TYPE_OBJECT)
98
99 static void
100 broadway_server_init (BroadwayServer *server)
101 {
102   BroadwayWindow *root;
103
104   server->service = g_socket_service_new ();
105   server->pointer_grab_window_id = -1;
106   server->saved_serial = 1;
107   server->last_seen_time = 1;
108   server->id_ht = g_hash_table_new (NULL, NULL);
109   server->id_counter = 0;
110
111   root = g_new0 (BroadwayWindow, 1);
112   root->id = server->id_counter++;
113   root->width = 1024;
114   root->height = 768;
115   root->visible = TRUE;
116
117   server->root = root;
118
119   g_hash_table_insert (server->id_ht,
120                        GINT_TO_POINTER (root->id),
121                        root);
122 }
123
124 static void
125 broadway_server_finalize (GObject *object)
126 {
127   BroadwayServer *server = BROADWAY_SERVER (object);
128
129   g_free (server->address);
130
131   G_OBJECT_CLASS (broadway_server_parent_class)->finalize (object);
132 }
133
134 static void
135 broadway_server_class_init (BroadwayServerClass * class)
136 {
137   GObjectClass *object_class = G_OBJECT_CLASS (class);
138
139   object_class->finalize = broadway_server_finalize;
140 }
141
142 static void start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary);
143
144 static void
145 http_request_free (HttpRequest *request)
146 {
147   g_object_unref (request->connection);
148   g_object_unref (request->data);
149   g_string_free (request->request, TRUE);
150   g_free (request);
151 }
152
153 static void
154 broadway_input_free (BroadwayInput *input)
155 {
156   g_object_unref (input->connection);
157   g_byte_array_free (input->buffer, FALSE);
158   g_source_destroy (input->source);
159   g_free (input);
160 }
161
162 static void
163 update_event_state (BroadwayServer *server,
164                     BroadwayInputMsg *message)
165 {
166   BroadwayWindow *window;
167
168   switch (message->base.type) {
169   case BROADWAY_EVENT_ENTER:
170     server->last_x = message->pointer.root_x;
171     server->last_y = message->pointer.root_y;
172     server->last_state = message->pointer.state;
173     server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
174
175     /* TODO: Unset when it dies */
176     server->mouse_in_toplevel_id = message->pointer.event_window_id;
177     break;
178   case BROADWAY_EVENT_LEAVE:
179     server->last_x = message->pointer.root_x;
180     server->last_y = message->pointer.root_y;
181     server->last_state = message->pointer.state;
182     server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
183
184     server->mouse_in_toplevel_id = 0;
185     break;
186   case BROADWAY_EVENT_POINTER_MOVE:
187     server->last_x = message->pointer.root_x;
188     server->last_y = message->pointer.root_y;
189     server->last_state = message->pointer.state;
190     server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
191     break;
192   case BROADWAY_EVENT_BUTTON_PRESS:
193   case BROADWAY_EVENT_BUTTON_RELEASE:
194     server->last_x = message->pointer.root_x;
195     server->last_y = message->pointer.root_y;
196     server->last_state = message->pointer.state;
197     server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
198     break;
199   case BROADWAY_EVENT_SCROLL:
200     server->last_x = message->pointer.root_x;
201     server->last_y = message->pointer.root_y;
202     server->last_state = message->pointer.state;
203     server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
204     break;
205   case BROADWAY_EVENT_KEY_PRESS:
206   case BROADWAY_EVENT_KEY_RELEASE:
207     server->last_state = message->key.state;
208     break;
209   case BROADWAY_EVENT_GRAB_NOTIFY:
210   case BROADWAY_EVENT_UNGRAB_NOTIFY:
211     break;
212   case BROADWAY_EVENT_CONFIGURE_NOTIFY:
213     window = g_hash_table_lookup (server->id_ht,
214                                   GINT_TO_POINTER (message->configure_notify.id));
215     if (window != NULL)
216       {
217         window->x = message->configure_notify.x;
218         window->y = message->configure_notify.y;
219       }
220     break;
221   case BROADWAY_EVENT_DELETE_NOTIFY:
222     break;
223   case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
224     server->root->width = message->screen_resize_notify.width;
225     server->root->height = message->screen_resize_notify.height;
226     break;
227
228   default:
229     g_printerr ("update_event_state - Unknown input command %c\n", message->base.type);
230     break;
231   }
232 }
233
234 gboolean
235 broadway_server_lookahead_event (BroadwayServer  *server,
236                                  const char         *types)
237 {
238   BroadwayInputMsg *message;
239   GList *l;
240
241   for (l = server->input_messages; l != NULL; l = l->next)
242     {
243       message = l->data;
244       if (strchr (types, message->base.type) != NULL)
245         return TRUE;
246     }
247
248   return FALSE;
249 }
250
251 static gboolean
252 is_pointer_event (BroadwayInputMsg *message)
253 {
254   return
255     message->base.type == BROADWAY_EVENT_ENTER ||
256     message->base.type == BROADWAY_EVENT_LEAVE ||
257     message->base.type == BROADWAY_EVENT_POINTER_MOVE ||
258     message->base.type == BROADWAY_EVENT_BUTTON_PRESS ||
259     message->base.type == BROADWAY_EVENT_BUTTON_RELEASE ||
260     message->base.type == BROADWAY_EVENT_SCROLL ||
261     message->base.type == BROADWAY_EVENT_GRAB_NOTIFY ||
262     message->base.type == BROADWAY_EVENT_UNGRAB_NOTIFY;
263 }
264
265 static void
266 process_input_message (BroadwayServer *server,
267                        BroadwayInputMsg *message)
268 {
269   gint32 client;
270
271   update_event_state (server, message);
272   client = -1;
273   if (is_pointer_event (message) &&
274       server->pointer_grab_window_id != -1)
275     client = server->pointer_grab_client_id;
276
277   broadway_events_got_input (message, client);
278 }
279
280 static void
281 process_input_messages (BroadwayServer *server)
282 {
283   BroadwayInputMsg *message;
284
285   while (server->input_messages)
286     {
287       message = server->input_messages->data;
288       server->input_messages =
289         g_list_delete_link (server->input_messages,
290                             server->input_messages);
291
292       if (message->base.serial == 0)
293         {
294           /* This was sent before we got any requests, but we don't want the
295              daemon serials to go backwards, so we fix it up to be the last used
296              serial */
297           message->base.serial = server->saved_serial - 1;
298         }
299
300       process_input_message (server, message);
301       g_free (message);
302     }
303 }
304
305 static void
306 fake_configure_notify (BroadwayServer *server,
307                        BroadwayWindow *window)
308 {
309   BroadwayInputMsg ev = { {0} };
310
311   ev.base.type = BROADWAY_EVENT_CONFIGURE_NOTIFY;
312   ev.base.serial = server->saved_serial - 1;
313   ev.base.time = server->last_seen_time;
314   ev.configure_notify.id = window->id;
315   ev.configure_notify.x = window->x;
316   ev.configure_notify.y = window->y;
317   ev.configure_notify.width = window->width;
318   ev.configure_notify.height = window->height;
319
320   process_input_message (server, &ev);
321 }
322
323 static char *
324 parse_pointer_data (char *p, BroadwayInputPointerMsg *data)
325 {
326   data->mouse_window_id = strtol (p, &p, 10);
327   p++; /* Skip , */
328   data->event_window_id = strtol (p, &p, 10);
329   p++; /* Skip , */
330   data->root_x = strtol (p, &p, 10);
331   p++; /* Skip , */
332   data->root_y = strtol (p, &p, 10);
333   p++; /* Skip , */
334   data->win_x = strtol (p, &p, 10);
335   p++; /* Skip , */
336   data->win_y = strtol (p, &p, 10);
337   p++; /* Skip , */
338   data->state = strtol (p, &p, 10);
339
340   return p;
341 }
342
343 static void
344 update_future_pointer_info (BroadwayServer *server, BroadwayInputPointerMsg *data)
345 {
346   server->future_root_x = data->root_x;
347   server->future_root_y = data->root_y;
348   server->future_state = data->state;
349   server->future_mouse_in_toplevel = data->mouse_window_id;
350 }
351
352 static void
353 parse_input_message (BroadwayInput *input, const char *message)
354 {
355   BroadwayServer *server = input->server;
356   BroadwayInputMsg msg;
357   char *p;
358   gint64 time_;
359
360   memset (&msg, 0, sizeof (msg));
361
362   p = (char *)message;
363   msg.base.type = *p++;
364   msg.base.serial = (guint32)strtol (p, &p, 10);
365   p++; /* Skip , */
366   time_ = strtol(p, &p, 10);
367   p++; /* Skip , */
368
369   if (time_ == 0) {
370     time_ = server->last_seen_time;
371   } else {
372     if (!input->seen_time) {
373       input->seen_time = TRUE;
374       /* Calculate time base so that any following times are normalized to start
375          5 seconds after last_seen_time, to avoid issues that could appear when
376          a long hiatus due to a reconnect seems to be instant */
377       input->time_base = time_ - (server->last_seen_time + 5000);
378     }
379     time_ = time_ - input->time_base;
380   }
381
382   server->last_seen_time = time_;
383
384   msg.base.time = time_;
385
386   switch (msg.base.type) {
387   case BROADWAY_EVENT_ENTER:
388   case BROADWAY_EVENT_LEAVE:
389     p = parse_pointer_data (p, &msg.pointer);
390     update_future_pointer_info (server, &msg.pointer);
391     p++; /* Skip , */
392     msg.crossing.mode = strtol(p, &p, 10);
393     break;
394
395   case BROADWAY_EVENT_POINTER_MOVE: /* Mouse move */
396     p = parse_pointer_data (p, &msg.pointer);
397     update_future_pointer_info (server, &msg.pointer);
398     break;
399
400   case BROADWAY_EVENT_BUTTON_PRESS:
401   case BROADWAY_EVENT_BUTTON_RELEASE:
402     p = parse_pointer_data (p, &msg.pointer);
403     update_future_pointer_info (server, &msg.pointer);
404     p++; /* Skip , */
405     msg.button.button = strtol(p, &p, 10);
406     break;
407
408   case BROADWAY_EVENT_SCROLL:
409     p = parse_pointer_data (p, &msg.pointer);
410     update_future_pointer_info (server, &msg.pointer);
411     p++; /* Skip , */
412     msg.scroll.dir = strtol(p, &p, 10);
413     break;
414
415   case BROADWAY_EVENT_KEY_PRESS:
416   case BROADWAY_EVENT_KEY_RELEASE:
417     msg.key.mouse_window_id = strtol(p, &p, 10);
418     p++; /* Skip , */
419     msg.key.key = strtol(p, &p, 10);
420     p++; /* Skip , */
421     msg.key.state = strtol(p, &p, 10);
422     break;
423
424   case BROADWAY_EVENT_GRAB_NOTIFY:
425   case BROADWAY_EVENT_UNGRAB_NOTIFY:
426     msg.grab_reply.res = strtol(p, &p, 10);
427     break;
428
429   case BROADWAY_EVENT_CONFIGURE_NOTIFY:
430     msg.configure_notify.id = strtol(p, &p, 10);
431     p++; /* Skip , */
432     msg.configure_notify.x = strtol (p, &p, 10);
433     p++; /* Skip , */
434     msg.configure_notify.y = strtol (p, &p, 10);
435     p++; /* Skip , */
436     msg.configure_notify.width = strtol (p, &p, 10);
437     p++; /* Skip , */
438     msg.configure_notify.height = strtol (p, &p, 10);
439     break;
440
441   case BROADWAY_EVENT_DELETE_NOTIFY:
442     msg.delete_notify.id = strtol(p, &p, 10);
443     break;
444
445   case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
446     msg.screen_resize_notify.width = strtol (p, &p, 10);
447     p++; /* Skip , */
448     msg.screen_resize_notify.height = strtol (p, &p, 10);
449     break;
450
451   default:
452     g_printerr ("parse_input_message - Unknown input command %c (%s)\n", msg.base.type, message);
453     break;
454   }
455
456   server->input_messages = g_list_append (server->input_messages, g_memdup (&msg, sizeof (msg)));
457
458 }
459
460 static inline void
461 hex_dump (guchar *data, gsize len)
462 {
463 #ifdef DEBUG_WEBSOCKETS
464   gsize i, j;
465   for (j = 0; j < len + 15; j += 16)
466     {
467       fprintf (stderr, "0x%.4x  ", j);
468       for (i = 0; i < 16; i++)
469         {
470             if ((j + i) < len)
471               fprintf (stderr, "%.2x ", data[j+i]);
472             else
473               fprintf (stderr, "  ");
474             if (i == 8)
475               fprintf (stderr, " ");
476         }
477       fprintf (stderr, " | ");
478
479       for (i = 0; i < 16; i++)
480         if ((j + i) < len && g_ascii_isalnum(data[j+i]))
481           fprintf (stderr, "%c", data[j+i]);
482         else
483           fprintf (stderr, ".");
484       fprintf (stderr, "\n");
485     }
486 #endif
487 }
488
489 static void
490 parse_input (BroadwayInput *input)
491 {
492   BroadwayServer *server = input->server;
493
494   if (!input->buffer->len)
495     return;
496
497   if (input->proto_v7_plus)
498     {
499       hex_dump (input->buffer->data, input->buffer->len);
500
501       while (input->buffer->len > 2)
502         {
503           gsize len, payload_len;
504           BroadwayWSOpCode code;
505           gboolean is_mask, fin;
506           guchar *buf, *data, *mask;
507
508           buf = input->buffer->data;
509           len = input->buffer->len;
510
511 #ifdef DEBUG_WEBSOCKETS
512           g_print ("Parse input first byte 0x%2x 0x%2x\n", buf[0], buf[1]);
513 #endif
514
515           fin = buf[0] & 0x80;
516           code = buf[0] & 0x0f;
517           payload_len = buf[1] & 0x7f;
518           is_mask = buf[1] & 0x80;
519           data = buf + 2;
520
521           if (payload_len > 125)
522             {
523               if (len < 4)
524                 return;
525               payload_len = GUINT16_FROM_BE( *(guint16 *) data );
526               data += 2;
527             }
528           else if (payload_len > 126)
529             {
530               if (len < 10)
531                 return;
532               payload_len = GUINT64_FROM_BE( *(guint64 *) data );
533               data += 8;
534             }
535
536           mask = NULL;
537           if (is_mask)
538             {
539               if (data - buf + 4 > len)
540                 return;
541               mask = data;
542               data += 4;
543             }
544
545           if (data - buf + payload_len > len)
546             return; /* wait to accumulate more */
547
548           if (is_mask)
549             {
550               gsize i;
551               for (i = 0; i < payload_len; i++)
552                 data[i] ^= mask[i%4];
553             }
554
555           switch (code) {
556           case BROADWAY_WS_CNX_CLOSE:
557             break; /* hang around anyway */
558           case BROADWAY_WS_TEXT:
559             if (!fin)
560               {
561 #ifdef DEBUG_WEBSOCKETS
562                 g_warning ("can't yet accept fragmented input");
563 #endif
564               }
565             else
566               {
567                 char *terminated = g_strndup((char *)data, payload_len);
568                 parse_input_message (input, terminated);
569                 g_free (terminated);
570               }
571             break;
572           case BROADWAY_WS_CNX_PING:
573             broadway_output_pong (server->output);
574             break;
575           case BROADWAY_WS_CNX_PONG:
576             break; /* we never send pings, but tolerate pongs */
577           case BROADWAY_WS_BINARY:
578           case BROADWAY_WS_CONTINUATION:
579           default:
580             {
581               g_warning ("fragmented or unknown input code 0x%2x with fin set", code);
582               break;
583             }
584           }
585
586           g_byte_array_remove_range (input->buffer, 0, data - buf + payload_len);
587         }
588     }
589   else /* old style protocol */
590     {
591       char *buf, *ptr;
592       gsize len;
593
594       buf = (char *)input->buffer->data;
595       len = input->buffer->len;
596
597       if (buf[0] != 0)
598         {
599           server->input = NULL;
600           broadway_input_free (input);
601           return;
602         }
603
604       while ((ptr = memchr (buf, 0xff, len)) != NULL)
605         {
606           *ptr = 0;
607           ptr++;
608
609           parse_input_message (input, buf + 1);
610
611           len -= ptr - buf;
612           buf = ptr;
613
614           if (len > 0 && buf[0] != 0)
615             {
616               server->input = NULL;
617               broadway_input_free (input);
618               break;
619             }
620         }
621       g_byte_array_remove_range (input->buffer, 0, buf - (char *)input->buffer->data);
622     }
623 }
624
625
626 static gboolean
627 process_input_idle_cb (BroadwayServer *server)
628 {
629   server->process_input_idle = 0;
630   process_input_messages (server);
631   return G_SOURCE_REMOVE;
632 }
633
634 static void
635 queue_process_input_at_idle (BroadwayServer *server)
636 {
637   if (server->process_input_idle == 0)
638     server->process_input_idle =
639       g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)process_input_idle_cb, server, NULL);
640 }
641
642 static void
643 broadway_server_read_all_input_nonblocking (BroadwayServer *server)
644 {
645   GInputStream *in;
646   gssize res;
647   guint8 buffer[1024];
648   GError *error;
649   BroadwayInput *input;
650
651   if (server->input == NULL)
652     return;
653
654   input = server->input;
655
656   in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
657
658   error = NULL;
659   res = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (in),
660                                                   buffer, sizeof (buffer), NULL, &error);
661
662   if (res <= 0)
663     {
664       if (res < 0 &&
665           g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
666         {
667           g_error_free (error);
668           return;
669         }
670
671       server->input = NULL;
672       broadway_input_free (input);
673       if (res < 0)
674         {
675           g_printerr ("input error %s\n", error->message);
676           g_error_free (error);
677         }
678       return;
679     }
680
681   g_byte_array_append (input->buffer, buffer, res);
682
683   parse_input (input);
684 }
685
686 static void
687 broadway_server_consume_all_input (BroadwayServer *server)
688 {
689   broadway_server_read_all_input_nonblocking (server);
690
691   /* Since we're parsing input but not processing the resulting messages
692      we might not get a readable callback on the stream, so queue an idle to
693      process the messages */
694   queue_process_input_at_idle (server);
695 }
696
697
698 static gboolean
699 input_data_cb (GObject  *stream,
700                BroadwayInput *input)
701 {
702   BroadwayServer *server = input->server;
703
704   broadway_server_read_all_input_nonblocking (server);
705
706   process_input_messages (server);
707
708   return TRUE;
709 }
710
711 guint32
712 broadway_server_get_next_serial (BroadwayServer *server)
713 {
714   if (server->output)
715     return broadway_output_get_next_serial (server->output);
716
717   return server->saved_serial;
718 }
719
720 void
721 broadway_server_get_screen_size (BroadwayServer   *server,
722                                  guint32          *width,
723                                  guint32          *height)
724 {
725   *width = server->root->width;
726   *height = server->root->height;
727 }
728
729
730 void
731 broadway_server_flush (BroadwayServer *server)
732 {
733   if (server->output &&
734       !broadway_output_flush (server->output))
735     {
736       server->saved_serial = broadway_output_get_next_serial (server->output);
737       broadway_output_free (server->output);
738       server->output = NULL;
739     }
740 }
741
742 void
743 broadway_server_sync (BroadwayServer *server)
744 {
745   broadway_server_flush (server);
746 }
747
748
749 /* TODO: This is not used atm, is it needed? */
750 /* Note: This may be called while handling a message (i.e. sorta recursively) */
751 BroadwayInputMsg *
752 broadway_server_block_for_input (BroadwayServer *server, char op,
753                                  guint32 serial, gboolean remove_message)
754 {
755   BroadwayInputMsg *message;
756   gssize res;
757   guint8 buffer[1024];
758   BroadwayInput *input;
759   GInputStream *in;
760   GList *l;
761
762   broadway_server_flush (server);
763
764   if (server->input == NULL)
765     return NULL;
766
767   input = server->input;
768
769   while (TRUE) {
770     /* Check for existing reply in queue */
771
772     for (l = server->input_messages; l != NULL; l = l->next)
773       {
774         message = l->data;
775
776         if (message->base.type == op)
777           {
778             if (message->base.serial == serial)
779               {
780                 if (remove_message)
781                   server->input_messages =
782                     g_list_delete_link (server->input_messages, l);
783                 return message;
784               }
785           }
786       }
787
788     /* Not found, read more, blocking */
789
790     in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
791     res = g_input_stream_read (in, buffer, sizeof (buffer), NULL, NULL);
792     if (res <= 0)
793       return NULL;
794     g_byte_array_append (input->buffer, buffer, res);
795
796     parse_input (input);
797
798     /* Since we're parsing input but not processing the resulting messages
799        we might not get a readable callback on the stream, so queue an idle to
800        process the messages */
801     queue_process_input_at_idle (server);
802   }
803 }
804
805 static char *
806 parse_line (char *line, char *key)
807 {
808   char *p;
809
810   if (!g_str_has_prefix (line, key))
811     return NULL;
812   p = line + strlen (key);
813   if (*p != ':')
814     return NULL;
815   p++;
816   /* Skip optional initial space */
817   if (*p == ' ')
818     p++;
819   return p;
820 }
821 static void
822 send_error (HttpRequest *request,
823             int error_code,
824             const char *reason)
825 {
826   char *res;
827
828   res = g_strdup_printf ("HTTP/1.0 %d %s\r\n\r\n"
829                          "<html><head><title>%d %s</title></head>"
830                          "<body>%s</body></html>",
831                          error_code, reason,
832                          error_code, reason,
833                          reason);
834   /* TODO: This should really be async */
835   g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
836                              res, strlen (res), NULL, NULL, NULL);
837   g_free (res);
838   http_request_free (request);
839 }
840
841 /* magic from: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 */
842 #define SEC_WEB_SOCKET_KEY_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
843
844 /* 'x3JJHMbDL1EzLkh9GBhXDw==' generates 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=' */
845 static gchar *
846 generate_handshake_response_wsietf_v7 (const gchar *key)
847 {
848   gsize digest_len = 20;
849   guchar digest[digest_len];
850   GChecksum *checksum;
851
852   checksum = g_checksum_new (G_CHECKSUM_SHA1);
853   if (!checksum)
854     return NULL;
855
856   g_checksum_update (checksum, (guchar *)key, -1);
857   g_checksum_update (checksum, (guchar *)SEC_WEB_SOCKET_KEY_MAGIC, -1);
858
859   g_checksum_get_digest (checksum, digest, &digest_len);
860   g_checksum_free (checksum);
861
862   g_assert (digest_len == 20);
863
864   return g_base64_encode (digest, digest_len);
865 }
866
867 static void
868 start_input (HttpRequest *request, gboolean binary)
869 {
870   char **lines;
871   char *p;
872   int num_key1, num_key2;
873   guint64 key1, key2;
874   int num_space;
875   int i;
876   guint8 challenge[16];
877   char *res;
878   gsize len;
879   GChecksum *checksum;
880   char *origin, *host;
881   BroadwayServer *server;
882   BroadwayInput *input;
883   const void *data_buffer;
884   gsize data_buffer_size;
885   GInputStream *in;
886   char *key_v7;
887   gboolean proto_v7_plus;
888
889   server = request->server;
890
891 #ifdef DEBUG_WEBSOCKETS
892   g_print ("incoming request:\n%s\n", request->request->str);
893 #endif
894   lines = g_strsplit (request->request->str, "\n", 0);
895
896   num_key1 = 0;
897   num_key2 = 0;
898   key1 = 0;
899   key2 = 0;
900   key_v7 = NULL;
901   origin = NULL;
902   host = NULL;
903   for (i = 0; lines[i] != NULL; i++)
904     {
905       if ((p = parse_line (lines[i], "Sec-WebSocket-Key1")))
906         {
907           num_space = 0;
908           while (*p != 0)
909             {
910               if (g_ascii_isdigit (*p))
911                 key1 = key1 * 10 + g_ascii_digit_value (*p);
912               else if (*p == ' ')
913                 num_space++;
914
915               p++;
916             }
917           key1 /= num_space;
918           num_key1++;
919         }
920       else if ((p = parse_line (lines[i], "Sec-WebSocket-Key2")))
921         {
922           num_space = 0;
923           while (*p != 0)
924             {
925               if (g_ascii_isdigit (*p))
926                 key2 = key2 * 10 + g_ascii_digit_value (*p);
927               else if (*p == ' ')
928                 num_space++;
929
930               p++;
931             }
932           key2 /= num_space;
933           num_key2++;
934         }
935       else if ((p = parse_line (lines[i], "Sec-WebSocket-Key")))
936         {
937           key_v7 = p;
938         }
939       else if ((p = parse_line (lines[i], "Origin")))
940         {
941           origin = p;
942         }
943       else if ((p = parse_line (lines[i], "Host")))
944         {
945           host = p;
946         }
947       else if ((p = parse_line (lines[i], "Sec-WebSocket-Origin")))
948         {
949           origin = p;
950         }
951     }
952
953   if (origin == NULL || host == NULL)
954     {
955       g_strfreev (lines);
956       send_error (request, 400, "Bad websocket request");
957       return;
958     }
959
960   if (key_v7 != NULL)
961     {
962       char* accept = generate_handshake_response_wsietf_v7 (key_v7);
963       res = g_strdup_printf ("HTTP/1.1 101 Switching Protocols\r\n"
964                              "Upgrade: websocket\r\n"
965                              "Connection: Upgrade\r\n"
966                              "Sec-WebSocket-Accept: %s\r\n"
967                              "Sec-WebSocket-Origin: %s\r\n"
968                              "Sec-WebSocket-Location: ws://%s/socket\r\n"
969                              "Sec-WebSocket-Protocol: broadway\r\n"
970                              "\r\n", accept, origin, host);
971       g_free (accept);
972
973 #ifdef DEBUG_WEBSOCKETS
974       g_print ("v7 proto response:\n%s", res);
975 #endif
976
977       g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
978                                  res, strlen (res), NULL, NULL, NULL);
979       g_free (res);
980       proto_v7_plus = TRUE;
981     }
982   else
983     {
984       if (num_key1 != 1 || num_key2 != 1)
985         {
986           g_strfreev (lines);
987           send_error (request, 400, "Bad websocket request");
988           return;
989         }
990
991       challenge[0] = (key1 >> 24) & 0xff;
992       challenge[1] = (key1 >> 16) & 0xff;
993       challenge[2] = (key1 >>  8) & 0xff;
994       challenge[3] = (key1 >>  0) & 0xff;
995       challenge[4] = (key2 >> 24) & 0xff;
996       challenge[5] = (key2 >> 16) & 0xff;
997       challenge[6] = (key2 >>  8) & 0xff;
998       challenge[7] = (key2 >>  0) & 0xff;
999
1000       if (!g_input_stream_read_all (G_INPUT_STREAM (request->data), challenge+8, 8, NULL, NULL, NULL))
1001         {
1002           g_strfreev (lines);
1003           send_error (request, 400, "Bad websocket request");
1004           return;
1005         }
1006
1007       checksum = g_checksum_new (G_CHECKSUM_MD5);
1008       g_checksum_update (checksum, challenge, 16);
1009       len = 16;
1010       g_checksum_get_digest (checksum, challenge, &len);
1011       g_checksum_free (checksum);
1012
1013       res = g_strdup_printf ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
1014                              "Upgrade: WebSocket\r\n"
1015                              "Connection: Upgrade\r\n"
1016                              "Sec-WebSocket-Origin: %s\r\n"
1017                              "Sec-WebSocket-Location: ws://%s/socket\r\n"
1018                              "Sec-WebSocket-Protocol: broadway\r\n"
1019                              "\r\n",
1020                              origin, host);
1021
1022 #ifdef DEBUG_WEBSOCKETS
1023       g_print ("legacy response:\n%s", res);
1024 #endif
1025       g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1026                                  res, strlen (res), NULL, NULL, NULL);
1027       g_free (res);
1028       g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1029                                  challenge, 16, NULL, NULL, NULL);
1030       proto_v7_plus = FALSE;
1031     }
1032
1033
1034   if (server->input != NULL)
1035     {
1036       broadway_input_free (server->input);
1037       server->input = NULL;
1038     }
1039
1040   input = g_new0 (BroadwayInput, 1);
1041
1042   input->server = request->server;
1043   input->connection = g_object_ref (request->connection);
1044   input->proto_v7_plus = proto_v7_plus;
1045   input->binary = binary;
1046
1047   data_buffer = g_buffered_input_stream_peek_buffer (G_BUFFERED_INPUT_STREAM (request->data), &data_buffer_size);
1048   input->buffer = g_byte_array_sized_new (data_buffer_size);
1049   g_byte_array_append (input->buffer, data_buffer, data_buffer_size);
1050
1051   server->input = input;
1052
1053   start_output (request, proto_v7_plus, binary);
1054
1055   /* This will free and close the data input stream, but we got all the buffered content already */
1056   http_request_free (request);
1057
1058   in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
1059   input->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (in), NULL);
1060   g_source_set_callback (input->source, (GSourceFunc)input_data_cb, input, NULL);
1061   g_source_attach (input->source, NULL);
1062
1063   /* Process any data in the pipe already */
1064   parse_input (input);
1065   process_input_messages (server);
1066
1067   g_strfreev (lines);
1068 }
1069
1070 static void
1071 start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary)
1072 {
1073   GSocket *socket;
1074   BroadwayServer *server;
1075   int flag = 1;
1076
1077   socket = g_socket_connection_get_socket (request->connection);
1078   setsockopt(g_socket_get_fd (socket), IPPROTO_TCP,
1079              TCP_NODELAY, (char *) &flag, sizeof(int));
1080
1081   server = BROADWAY_SERVER (request->server);
1082
1083   if (server->output)
1084     {
1085       server->saved_serial = broadway_output_get_next_serial (server->output);
1086       broadway_output_free (server->output);
1087     }
1088
1089   server->output =
1090     broadway_output_new (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1091                          server->saved_serial, proto_v7_plus, binary);
1092
1093   broadway_server_resync_windows (server);
1094
1095   if (server->pointer_grab_window_id != -1)
1096     broadway_output_grab_pointer (server->output,
1097                                   server->pointer_grab_window_id,
1098                                   server->pointer_grab_owner_events);
1099 }
1100
1101 static void
1102 send_data (HttpRequest *request,
1103              const char *mimetype,
1104              const char *data, gsize len)
1105 {
1106   char *res;
1107
1108   res = g_strdup_printf ("HTTP/1.0 200 OK\r\n"
1109                          "Content-Type: %s\r\n"
1110                          "Content-Length: %"G_GSIZE_FORMAT"\r\n"
1111                          "\r\n",
1112                          mimetype, len);
1113   /* TODO: This should really be async */
1114   g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1115                              res, strlen (res), NULL, NULL, NULL);
1116   g_free (res);
1117   g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1118                              data, len, NULL, NULL, NULL);
1119   http_request_free (request);
1120 }
1121
1122 #include "clienthtml.h"
1123 #include "broadwayjs.h"
1124
1125 static void
1126 got_request (HttpRequest *request)
1127 {
1128   char *start, *escaped, *tmp, *version, *query;
1129
1130   if (!g_str_has_prefix (request->request->str, "GET "))
1131     {
1132       send_error (request, 501, "Only GET implemented");
1133       return;
1134     }
1135
1136   start = request->request->str + 4; /* Skip "GET " */
1137
1138   while (*start == ' ')
1139     start++;
1140
1141   for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1142     ;
1143   escaped = g_strndup (start, tmp - start);
1144   version = NULL;
1145   if (*tmp == ' ')
1146     {
1147       start = tmp;
1148       while (*start == ' ')
1149         start++;
1150       for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1151         ;
1152       version = g_strndup (start, tmp - start);
1153     }
1154
1155   query = strchr (escaped, '?');
1156   if (query)
1157     *query = 0;
1158
1159   if (strcmp (escaped, "/client.html") == 0 || strcmp (escaped, "/") == 0)
1160     send_data (request, "text/html", client_html, G_N_ELEMENTS(client_html) - 1);
1161   else if (strcmp (escaped, "/broadway.js") == 0)
1162     send_data (request, "text/javascript", broadway_js, G_N_ELEMENTS(broadway_js) - 1);
1163   else if (strcmp (escaped, "/socket") == 0)
1164     start_input (request, FALSE);
1165   else if (strcmp (escaped, "/socket-bin") == 0)
1166     start_input (request, TRUE);
1167   else
1168     send_error (request, 404, "File not found");
1169
1170   g_free (escaped);
1171   g_free (version);
1172 }
1173
1174 static void
1175 got_http_request_line (GInputStream *stream,
1176                        GAsyncResult *result,
1177                        HttpRequest *request)
1178 {
1179   char *line;
1180
1181   line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (stream), result, NULL, NULL);
1182   if (line == NULL)
1183     {
1184       http_request_free (request);
1185       g_printerr ("Error reading request lines\n");
1186       return;
1187     }
1188   if (strlen (line) == 0)
1189     got_request (request);
1190   else
1191     {
1192       /* Protect against overflow in request length */
1193       if (request->request->len > 1024 * 5)
1194         {
1195           send_error (request, 400, "Request too long");
1196         }
1197       else
1198         {
1199           g_string_append_printf (request->request, "%s\n", line);
1200           g_data_input_stream_read_line_async (request->data, 0, NULL,
1201                                                (GAsyncReadyCallback)got_http_request_line, request);
1202         }
1203     }
1204   g_free (line);
1205 }
1206
1207 static gboolean
1208 handle_incoming_connection (GSocketService    *service,
1209                             GSocketConnection *connection,
1210                             GObject           *source_object)
1211 {
1212   HttpRequest *request;
1213   GInputStream *in;
1214
1215   request = g_new0 (HttpRequest, 1);
1216   request->connection = g_object_ref (connection);
1217   request->server = BROADWAY_SERVER (source_object);
1218   request->request = g_string_new ("");
1219
1220   in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
1221
1222   request->data = g_data_input_stream_new (in);
1223   g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (request->data), FALSE);
1224   /* Be tolerant of input */
1225   g_data_input_stream_set_newline_type (request->data, G_DATA_STREAM_NEWLINE_TYPE_ANY);
1226
1227   g_data_input_stream_read_line_async (request->data, 0, NULL,
1228                                        (GAsyncReadyCallback)got_http_request_line, request);
1229   return TRUE;
1230 }
1231
1232 BroadwayServer *
1233 broadway_server_new (char *address, int port, GError **error)
1234 {
1235   BroadwayServer *server;
1236   GInetAddress *inet_address;
1237   GSocketAddress *socket_address;
1238
1239   server = g_object_new (BROADWAY_TYPE_SERVER, NULL);
1240   server->port = port;
1241   server->address = g_strdup (address);
1242
1243   if (address == NULL)
1244     {
1245       if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (server->service),
1246                                             server->port,
1247                                             G_OBJECT (server),
1248                                             error))
1249         {
1250           g_prefix_error (error, "Unable to listen to port %d: ", server->port);
1251           return NULL;
1252         }
1253     }
1254   else
1255     {
1256       inet_address = g_inet_address_new_from_string (address);
1257       if (inet_address == NULL)
1258         {
1259           g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid ip address %s: ", address);
1260           return NULL;
1261         }
1262       socket_address = g_inet_socket_address_new (inet_address, port);
1263       g_object_unref (inet_address);
1264       if (!g_socket_listener_add_address (G_SOCKET_LISTENER (server->service),
1265                                           socket_address,
1266                                           G_SOCKET_TYPE_STREAM,
1267                                           G_SOCKET_PROTOCOL_TCP,
1268                                           G_OBJECT (server),
1269                                           NULL,
1270                                           error))
1271         {
1272           g_prefix_error (error, "Unable to listen to %s:%d: ", server->address, server->port);
1273           g_object_unref (socket_address);
1274           return NULL;
1275         }
1276       g_object_unref (socket_address);
1277     }
1278
1279   g_signal_connect (server->service, "incoming",
1280                     G_CALLBACK (handle_incoming_connection), NULL);
1281   return server;
1282 }
1283
1284 guint32
1285 broadway_server_get_last_seen_time (BroadwayServer *server)
1286 {
1287   broadway_server_consume_all_input (server);
1288   return (guint32) server->last_seen_time;
1289 }
1290
1291 void
1292 broadway_server_query_mouse (BroadwayServer *server,
1293                              guint32            *toplevel,
1294                              gint32             *root_x,
1295                              gint32             *root_y,
1296                              guint32            *mask)
1297 {
1298   if (server->output)
1299     {
1300       broadway_server_consume_all_input (server);
1301       if (root_x)
1302         *root_x = server->future_root_x;
1303       if (root_y)
1304         *root_y = server->future_root_y;
1305       if (mask)
1306         *mask = server->future_state;
1307       if (toplevel)
1308         *toplevel = server->future_mouse_in_toplevel;
1309       return;
1310     }
1311
1312   /* Fallback when unconnected */
1313   if (root_x)
1314     *root_x = server->last_x;
1315   if (root_y)
1316     *root_y = server->last_y;
1317   if (mask)
1318     *mask = server->last_state;
1319   if (toplevel)
1320     *toplevel = server->mouse_in_toplevel_id;
1321 }
1322
1323 void
1324 broadway_server_destroy_window (BroadwayServer *server,
1325                                 gint id)
1326 {
1327   BroadwayWindow *window;
1328
1329   if (server->mouse_in_toplevel_id == id)
1330     {
1331       /* TODO: Send leave + enter event, update cursors, etc */
1332       server->mouse_in_toplevel_id = 0;
1333     }
1334
1335   if (server->pointer_grab_window_id == id)
1336     server->pointer_grab_window_id = -1;
1337
1338   if (server->output)
1339     broadway_output_destroy_surface (server->output,
1340                                      id);
1341
1342   window = g_hash_table_lookup (server->id_ht,
1343                                 GINT_TO_POINTER (id));
1344   if (window != NULL)
1345     {
1346       server->toplevels = g_list_remove (server->toplevels, window);
1347       g_hash_table_remove (server->id_ht,
1348                            GINT_TO_POINTER (id));
1349       g_free (window);
1350     }
1351 }
1352
1353 gboolean
1354 broadway_server_window_show (BroadwayServer *server,
1355                              gint id)
1356 {
1357   BroadwayWindow *window;
1358   gboolean sent = FALSE;
1359
1360   window = g_hash_table_lookup (server->id_ht,
1361                                 GINT_TO_POINTER (id));
1362   if (window == NULL)
1363     return FALSE;
1364
1365   window->visible = TRUE;
1366
1367   if (server->output)
1368     {
1369       broadway_output_show_surface (server->output, window->id);
1370       sent = TRUE;
1371     }
1372
1373   return sent;
1374 }
1375
1376 gboolean
1377 broadway_server_window_hide (BroadwayServer *server,
1378                              gint id)
1379 {
1380   BroadwayWindow *window;
1381   gboolean sent = FALSE;
1382
1383   window = g_hash_table_lookup (server->id_ht,
1384                                 GINT_TO_POINTER (id));
1385   if (window == NULL)
1386     return FALSE;
1387
1388   window->visible = FALSE;
1389
1390   if (server->mouse_in_toplevel_id == id)
1391     {
1392       /* TODO: Send leave + enter event, update cursors, etc */
1393       server->mouse_in_toplevel_id = 0;
1394     }
1395
1396   if (server->pointer_grab_window_id == id)
1397     server->pointer_grab_window_id = -1;
1398
1399   if (server->output)
1400     {
1401       broadway_output_hide_surface (server->output, window->id);
1402       sent = TRUE;
1403     }
1404   return sent;
1405 }
1406
1407 void
1408 broadway_server_window_set_transient_for (BroadwayServer *server,
1409                                           gint id, gint parent)
1410 {
1411   BroadwayWindow *window;
1412
1413   window = g_hash_table_lookup (server->id_ht,
1414                                 GINT_TO_POINTER (id));
1415   if (window == NULL)
1416     return;
1417
1418   window->transient_for = parent;
1419
1420   if (server->output)
1421     {
1422       broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1423       broadway_server_flush (server);
1424     }
1425 }
1426
1427 gboolean
1428 broadway_server_has_client (BroadwayServer *server)
1429 {
1430   return server->output != NULL;
1431 }
1432
1433 static void
1434 _cairo_region (cairo_t         *cr,
1435                const cairo_region_t *region)
1436 {
1437   cairo_rectangle_int_t box;
1438   gint n_boxes, i;
1439
1440   g_return_if_fail (cr != NULL);
1441   g_return_if_fail (region != NULL);
1442
1443   n_boxes = cairo_region_num_rectangles (region);
1444
1445   for (i = 0; i < n_boxes; i++)
1446     {
1447       cairo_region_get_rectangle (region, i, &box);
1448       cairo_rectangle (cr, box.x, box.y, box.width, box.height);
1449     }
1450 }
1451
1452
1453 static void
1454 copy_region (cairo_surface_t *surface,
1455              cairo_region_t *area,
1456              gint            dx,
1457              gint            dy)
1458 {
1459   cairo_t *cr;
1460
1461   cr = cairo_create (surface);
1462   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1463
1464   _cairo_region (cr, area);
1465   cairo_clip (cr);
1466
1467   /* NB: This is a self-copy and Cairo doesn't support that yet.
1468    * So we do a litle trick.
1469    */
1470   cairo_push_group (cr);
1471
1472   cairo_set_source_surface (cr, surface, dx, dy);
1473   cairo_paint (cr);
1474
1475   cairo_pop_group_to_source (cr);
1476   cairo_paint (cr);
1477
1478   cairo_destroy (cr);
1479 }
1480
1481 gboolean
1482 broadway_server_window_translate (BroadwayServer *server,
1483                                   gint id,
1484                                   cairo_region_t *area,
1485                                   gint            dx,
1486                                   gint            dy)
1487 {
1488   BroadwayWindow *window;
1489   gboolean sent = FALSE;
1490
1491   window = g_hash_table_lookup (server->id_ht,
1492                                 GINT_TO_POINTER (id));
1493   if (window == NULL)
1494     return FALSE;
1495
1496   if (window->last_synced &&
1497       server->output)
1498     {
1499       BroadwayRect *rects;
1500       cairo_rectangle_int_t rect;
1501       int i, n_rects;
1502
1503       copy_region (window->last_surface, area, dx, dy);
1504       n_rects = cairo_region_num_rectangles (area);
1505       rects = g_new (BroadwayRect, n_rects);
1506       for (i = 0; i < n_rects; i++)
1507         {
1508           cairo_region_get_rectangle (area, i, &rect);
1509           rects[i].x = rect.x;
1510           rects[i].y = rect.y;
1511           rects[i].width = rect.width;
1512           rects[i].height = rect.height;
1513         }
1514       broadway_output_copy_rectangles (server->output,
1515                                        window->id,
1516                                        rects, n_rects, dx, dy);
1517       g_free (rects);
1518       sent = TRUE;
1519     }
1520
1521   return sent;
1522 }
1523
1524 static void
1525 diff_surfaces (cairo_surface_t *surface,
1526                cairo_surface_t *old_surface)
1527 {
1528   guint8 *data, *old_data;
1529   guint32 *line, *old_line;
1530   int w, h, stride, old_stride;
1531   int x, y;
1532
1533   data = cairo_image_surface_get_data (surface);
1534   old_data = cairo_image_surface_get_data (old_surface);
1535
1536   w = cairo_image_surface_get_width (surface);
1537   h = cairo_image_surface_get_height (surface);
1538
1539   stride = cairo_image_surface_get_stride (surface);
1540   old_stride = cairo_image_surface_get_stride (old_surface);
1541
1542   for (y = 0; y < h; y++)
1543     {
1544       line = (guint32 *)data;
1545       old_line = (guint32 *)old_data;
1546
1547       for (x = 0; x < w; x++)
1548         {
1549           if ((*line & 0xffffff) == (*old_line & 0xffffff))
1550             *old_line = 0;
1551           else
1552             *old_line = *line | 0xff000000;
1553           line ++;
1554           old_line ++;
1555         }
1556
1557       data += stride;
1558       old_data += old_stride;
1559     }
1560 }
1561
1562 void
1563 broadway_server_window_update (BroadwayServer *server,
1564                                gint id,
1565                                cairo_surface_t *surface)
1566 {
1567   cairo_t *cr;
1568   BroadwayWindow *window;
1569
1570   if (surface == NULL)
1571     return;
1572
1573   window = g_hash_table_lookup (server->id_ht,
1574                                 GINT_TO_POINTER (id));
1575   if (window == NULL)
1576     return;
1577
1578   if (window->last_surface == NULL)
1579     window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1580                                                        window->width,
1581                                                        window->height);
1582
1583   if (server->output != NULL)
1584     {
1585       if (window->last_synced)
1586         {
1587           diff_surfaces (surface,
1588                          window->last_surface);
1589           broadway_output_put_rgba (server->output, window->id, 0, 0,
1590                                     cairo_image_surface_get_width (window->last_surface),
1591                                     cairo_image_surface_get_height (window->last_surface),
1592                                     cairo_image_surface_get_stride (window->last_surface),
1593                                     cairo_image_surface_get_data (window->last_surface));
1594         }
1595       else
1596         {
1597           window->last_synced = TRUE;
1598           broadway_output_put_rgb (server->output, window->id, 0, 0,
1599                                    cairo_image_surface_get_width (surface),
1600                                    cairo_image_surface_get_height (surface),
1601                                    cairo_image_surface_get_stride (surface),
1602                                    cairo_image_surface_get_data (surface));
1603         }
1604
1605       broadway_output_surface_flush (server->output, window->id);
1606     }
1607
1608   cr = cairo_create (window->last_surface);
1609   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1610   cairo_set_source_surface (cr, surface, 0, 0);
1611   cairo_paint (cr);
1612   cairo_destroy (cr);
1613 }
1614
1615 gboolean
1616 broadway_server_window_move_resize (BroadwayServer *server,
1617                                     gint id,
1618                                     gboolean with_move,
1619                                     int x,
1620                                     int y,
1621                                     int width,
1622                                     int height)
1623 {
1624   BroadwayWindow *window;
1625   gboolean with_resize;
1626   gboolean sent = FALSE;
1627   cairo_t *cr;
1628
1629   window = g_hash_table_lookup (server->id_ht,
1630                                 GINT_TO_POINTER (id));
1631   if (window == NULL)
1632     return FALSE;
1633
1634   with_resize = width != window->width || height != window->height;
1635   window->width = width;
1636   window->height = height;
1637
1638   if (with_resize && window->last_surface != NULL)
1639     {
1640       cairo_surface_t *old;
1641
1642       old = window->last_surface;
1643
1644       window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1645                                                          width, height);
1646
1647
1648       cr = cairo_create (window->last_surface);
1649       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1650       cairo_set_source_surface (cr, old, 0, 0);
1651       cairo_paint (cr);
1652       cairo_destroy (cr);
1653
1654       cairo_surface_destroy (old);
1655     }
1656
1657   if (server->output != NULL)
1658     {
1659       broadway_output_move_resize_surface (server->output,
1660                                            window->id,
1661                                            with_move, x, y,
1662                                            with_resize, window->width, window->height);
1663       sent = TRUE;
1664     }
1665   else
1666     {
1667       if (with_move)
1668         {
1669           window->x = x;
1670           window->y = y;
1671         }
1672
1673       fake_configure_notify (server, window);
1674     }
1675
1676   return sent;
1677 }
1678
1679 guint32
1680 broadway_server_grab_pointer (BroadwayServer *server,
1681                               gint client_id,
1682                               gint id,
1683                               gboolean owner_events,
1684                               guint32 event_mask,
1685                               guint32 time_)
1686 {
1687   if (server->pointer_grab_window_id != -1 &&
1688       time_ != 0 && server->pointer_grab_time > time_)
1689     return GDK_GRAB_ALREADY_GRABBED;
1690
1691   if (time_ == 0)
1692     time_ = server->last_seen_time;
1693
1694   server->pointer_grab_window_id = id;
1695   server->pointer_grab_client_id = client_id;
1696   server->pointer_grab_owner_events = owner_events;
1697   server->pointer_grab_time = time_;
1698
1699   if (server->output)
1700     {
1701       broadway_output_grab_pointer (server->output,
1702                                     id,
1703                                     owner_events);
1704       broadway_server_flush (server);
1705     }
1706
1707   /* TODO: What about toplevel grab events if we're not connected? */
1708
1709   return GDK_GRAB_SUCCESS;
1710 }
1711
1712 guint32
1713 broadway_server_ungrab_pointer (BroadwayServer *server,
1714                                 guint32    time_)
1715 {
1716   guint32 serial;
1717
1718   if (server->pointer_grab_window_id != -1 &&
1719       time_ != 0 && server->pointer_grab_time > time_)
1720     return 0;
1721
1722   /* TODO: What about toplevel grab events if we're not connected? */
1723
1724   if (server->output)
1725     {
1726       serial = broadway_output_ungrab_pointer (server->output);
1727       broadway_server_flush (server);
1728     }
1729   else
1730     {
1731       serial = server->saved_serial;
1732     }
1733
1734   server->pointer_grab_window_id = -1;
1735
1736   return serial;
1737 }
1738
1739 guint32
1740 broadway_server_new_window (BroadwayServer *server,
1741                             int x,
1742                             int y,
1743                             int width,
1744                             int height,
1745                             gboolean is_temp)
1746 {
1747   BroadwayWindow *window;
1748
1749   window = g_new0 (BroadwayWindow, 1);
1750   window->id = server->id_counter++;
1751   window->x = x;
1752   window->y = y;
1753   if (x == 0 && y == 0 && !is_temp)
1754     {
1755       /* TODO: Better way to know if we should pick default pos */
1756       window->x = 100;
1757       window->y = 100;
1758     }
1759   window->width = width;
1760   window->height = height;
1761   window->is_temp = is_temp;
1762
1763   g_hash_table_insert (server->id_ht,
1764                        GINT_TO_POINTER (window->id),
1765                        window);
1766
1767   server->toplevels = g_list_prepend (server->toplevels, window);
1768
1769   if (server->output)
1770     broadway_output_new_surface (server->output,
1771                                  window->id,
1772                                  window->x,
1773                                  window->y,
1774                                  window->width,
1775                                  window->height,
1776                                  window->is_temp);
1777   else
1778     fake_configure_notify (server, window);
1779
1780   return window->id;
1781 }
1782
1783 static void
1784 broadway_server_resync_windows (BroadwayServer *server)
1785 {
1786   GList *l;
1787
1788   if (server->output == NULL)
1789     return;
1790
1791   /* First create all windows */
1792   for (l = server->toplevels; l != NULL; l = l->next)
1793     {
1794       BroadwayWindow *window = l->data;
1795
1796       if (window->id == 0)
1797         continue; /* Skip root */
1798
1799       window->last_synced = FALSE;
1800       broadway_output_new_surface (server->output,
1801                                    window->id,
1802                                    window->x,
1803                                    window->y,
1804                                    window->width,
1805                                    window->height,
1806                                    window->is_temp);
1807     }
1808
1809   /* Then do everything that may reference other windows */
1810   for (l = server->toplevels; l != NULL; l = l->next)
1811     {
1812       BroadwayWindow *window = l->data;
1813
1814       if (window->id == 0)
1815         continue; /* Skip root */
1816
1817       if (window->transient_for != -1)
1818         broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1819       if (window->visible)
1820         {
1821           broadway_output_show_surface (server->output, window->id);
1822
1823           if (window->last_surface != NULL)
1824             {
1825               window->last_synced = TRUE;
1826               broadway_output_put_rgb (server->output, window->id, 0, 0,
1827                                        cairo_image_surface_get_width (window->last_surface),
1828                                        cairo_image_surface_get_height (window->last_surface),
1829                                        cairo_image_surface_get_stride (window->last_surface),
1830                                        cairo_image_surface_get_data (window->last_surface));
1831             }
1832           broadway_output_surface_flush (server->output, window->id);
1833         }
1834     }
1835
1836   broadway_server_flush (server);
1837 }