]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadway-server.c
x11: Use _gdk_x11_get_xatom_for_display_printf()
[~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 (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                              "%s%s%s"
968                              "Sec-WebSocket-Location: ws://%s/socket\r\n"
969                              "Sec-WebSocket-Protocol: broadway\r\n"
970                              "\r\n", accept,
971                              origin?"Sec-WebSocket-Origin: ":"", origin?origin:"", origin?"\r\n":"",
972                              host);
973       g_free (accept);
974
975 #ifdef DEBUG_WEBSOCKETS
976       g_print ("v7 proto response:\n%s", res);
977 #endif
978
979       g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
980                                  res, strlen (res), NULL, NULL, NULL);
981       g_free (res);
982       proto_v7_plus = TRUE;
983     }
984   else
985     {
986       if (num_key1 != 1 || num_key2 != 1)
987         {
988           g_strfreev (lines);
989           send_error (request, 400, "Bad websocket request");
990           return;
991         }
992
993       challenge[0] = (key1 >> 24) & 0xff;
994       challenge[1] = (key1 >> 16) & 0xff;
995       challenge[2] = (key1 >>  8) & 0xff;
996       challenge[3] = (key1 >>  0) & 0xff;
997       challenge[4] = (key2 >> 24) & 0xff;
998       challenge[5] = (key2 >> 16) & 0xff;
999       challenge[6] = (key2 >>  8) & 0xff;
1000       challenge[7] = (key2 >>  0) & 0xff;
1001
1002       if (!g_input_stream_read_all (G_INPUT_STREAM (request->data), challenge+8, 8, NULL, NULL, NULL))
1003         {
1004           g_strfreev (lines);
1005           send_error (request, 400, "Bad websocket request");
1006           return;
1007         }
1008
1009       checksum = g_checksum_new (G_CHECKSUM_MD5);
1010       g_checksum_update (checksum, challenge, 16);
1011       len = 16;
1012       g_checksum_get_digest (checksum, challenge, &len);
1013       g_checksum_free (checksum);
1014
1015       res = g_strdup_printf ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
1016                              "Upgrade: WebSocket\r\n"
1017                              "Connection: Upgrade\r\n"
1018                              "%s%s%s"
1019                              "Sec-WebSocket-Location: ws://%s/socket\r\n"
1020                              "Sec-WebSocket-Protocol: broadway\r\n"
1021                              "\r\n",
1022                              origin?"Sec-WebSocket-Origin: ":"", origin?origin:"", origin?"\r\n":"",
1023                              host);
1024
1025 #ifdef DEBUG_WEBSOCKETS
1026       g_print ("legacy response:\n%s", res);
1027 #endif
1028       g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1029                                  res, strlen (res), NULL, NULL, NULL);
1030       g_free (res);
1031       g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1032                                  challenge, 16, NULL, NULL, NULL);
1033       proto_v7_plus = FALSE;
1034     }
1035
1036
1037   if (server->input != NULL)
1038     {
1039       broadway_input_free (server->input);
1040       server->input = NULL;
1041     }
1042
1043   input = g_new0 (BroadwayInput, 1);
1044
1045   input->server = request->server;
1046   input->connection = g_object_ref (request->connection);
1047   input->proto_v7_plus = proto_v7_plus;
1048   input->binary = binary;
1049
1050   data_buffer = g_buffered_input_stream_peek_buffer (G_BUFFERED_INPUT_STREAM (request->data), &data_buffer_size);
1051   input->buffer = g_byte_array_sized_new (data_buffer_size);
1052   g_byte_array_append (input->buffer, data_buffer, data_buffer_size);
1053
1054   server->input = input;
1055
1056   start_output (request, proto_v7_plus, binary);
1057
1058   /* This will free and close the data input stream, but we got all the buffered content already */
1059   http_request_free (request);
1060
1061   in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
1062   input->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (in), NULL);
1063   g_source_set_callback (input->source, (GSourceFunc)input_data_cb, input, NULL);
1064   g_source_attach (input->source, NULL);
1065
1066   /* Process any data in the pipe already */
1067   parse_input (input);
1068   process_input_messages (server);
1069
1070   g_strfreev (lines);
1071 }
1072
1073 static void
1074 start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary)
1075 {
1076   GSocket *socket;
1077   BroadwayServer *server;
1078   int flag = 1;
1079
1080   socket = g_socket_connection_get_socket (request->connection);
1081   setsockopt(g_socket_get_fd (socket), IPPROTO_TCP,
1082              TCP_NODELAY, (char *) &flag, sizeof(int));
1083
1084   server = BROADWAY_SERVER (request->server);
1085
1086   if (server->output)
1087     {
1088       server->saved_serial = broadway_output_get_next_serial (server->output);
1089       broadway_output_free (server->output);
1090     }
1091
1092   server->output =
1093     broadway_output_new (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1094                          server->saved_serial, proto_v7_plus, binary);
1095
1096   broadway_server_resync_windows (server);
1097
1098   if (server->pointer_grab_window_id != -1)
1099     broadway_output_grab_pointer (server->output,
1100                                   server->pointer_grab_window_id,
1101                                   server->pointer_grab_owner_events);
1102 }
1103
1104 static void
1105 send_data (HttpRequest *request,
1106              const char *mimetype,
1107              const char *data, gsize len)
1108 {
1109   char *res;
1110
1111   res = g_strdup_printf ("HTTP/1.0 200 OK\r\n"
1112                          "Content-Type: %s\r\n"
1113                          "Content-Length: %"G_GSIZE_FORMAT"\r\n"
1114                          "\r\n",
1115                          mimetype, len);
1116   /* TODO: This should really be async */
1117   g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1118                              res, strlen (res), NULL, NULL, NULL);
1119   g_free (res);
1120   g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1121                              data, len, NULL, NULL, NULL);
1122   http_request_free (request);
1123 }
1124
1125 #include "clienthtml.h"
1126 #include "broadwayjs.h"
1127
1128 static void
1129 got_request (HttpRequest *request)
1130 {
1131   char *start, *escaped, *tmp, *version, *query;
1132
1133   if (!g_str_has_prefix (request->request->str, "GET "))
1134     {
1135       send_error (request, 501, "Only GET implemented");
1136       return;
1137     }
1138
1139   start = request->request->str + 4; /* Skip "GET " */
1140
1141   while (*start == ' ')
1142     start++;
1143
1144   for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1145     ;
1146   escaped = g_strndup (start, tmp - start);
1147   version = NULL;
1148   if (*tmp == ' ')
1149     {
1150       start = tmp;
1151       while (*start == ' ')
1152         start++;
1153       for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1154         ;
1155       version = g_strndup (start, tmp - start);
1156     }
1157
1158   query = strchr (escaped, '?');
1159   if (query)
1160     *query = 0;
1161
1162   if (strcmp (escaped, "/client.html") == 0 || strcmp (escaped, "/") == 0)
1163     send_data (request, "text/html", client_html, G_N_ELEMENTS(client_html) - 1);
1164   else if (strcmp (escaped, "/broadway.js") == 0)
1165     send_data (request, "text/javascript", broadway_js, G_N_ELEMENTS(broadway_js) - 1);
1166   else if (strcmp (escaped, "/socket") == 0)
1167     start_input (request, FALSE);
1168   else if (strcmp (escaped, "/socket-bin") == 0)
1169     start_input (request, TRUE);
1170   else
1171     send_error (request, 404, "File not found");
1172
1173   g_free (escaped);
1174   g_free (version);
1175 }
1176
1177 static void
1178 got_http_request_line (GInputStream *stream,
1179                        GAsyncResult *result,
1180                        HttpRequest *request)
1181 {
1182   char *line;
1183
1184   line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (stream), result, NULL, NULL);
1185   if (line == NULL)
1186     {
1187       http_request_free (request);
1188       g_printerr ("Error reading request lines\n");
1189       return;
1190     }
1191   if (strlen (line) == 0)
1192     got_request (request);
1193   else
1194     {
1195       /* Protect against overflow in request length */
1196       if (request->request->len > 1024 * 5)
1197         {
1198           send_error (request, 400, "Request too long");
1199         }
1200       else
1201         {
1202           g_string_append_printf (request->request, "%s\n", line);
1203           g_data_input_stream_read_line_async (request->data, 0, NULL,
1204                                                (GAsyncReadyCallback)got_http_request_line, request);
1205         }
1206     }
1207   g_free (line);
1208 }
1209
1210 static gboolean
1211 handle_incoming_connection (GSocketService    *service,
1212                             GSocketConnection *connection,
1213                             GObject           *source_object)
1214 {
1215   HttpRequest *request;
1216   GInputStream *in;
1217
1218   request = g_new0 (HttpRequest, 1);
1219   request->connection = g_object_ref (connection);
1220   request->server = BROADWAY_SERVER (source_object);
1221   request->request = g_string_new ("");
1222
1223   in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
1224
1225   request->data = g_data_input_stream_new (in);
1226   g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (request->data), FALSE);
1227   /* Be tolerant of input */
1228   g_data_input_stream_set_newline_type (request->data, G_DATA_STREAM_NEWLINE_TYPE_ANY);
1229
1230   g_data_input_stream_read_line_async (request->data, 0, NULL,
1231                                        (GAsyncReadyCallback)got_http_request_line, request);
1232   return TRUE;
1233 }
1234
1235 BroadwayServer *
1236 broadway_server_new (char *address, int port, GError **error)
1237 {
1238   BroadwayServer *server;
1239   GInetAddress *inet_address;
1240   GSocketAddress *socket_address;
1241
1242   server = g_object_new (BROADWAY_TYPE_SERVER, NULL);
1243   server->port = port;
1244   server->address = g_strdup (address);
1245
1246   if (address == NULL)
1247     {
1248       if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (server->service),
1249                                             server->port,
1250                                             G_OBJECT (server),
1251                                             error))
1252         {
1253           g_prefix_error (error, "Unable to listen to port %d: ", server->port);
1254           return NULL;
1255         }
1256     }
1257   else
1258     {
1259       inet_address = g_inet_address_new_from_string (address);
1260       if (inet_address == NULL)
1261         {
1262           g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid ip address %s: ", address);
1263           return NULL;
1264         }
1265       socket_address = g_inet_socket_address_new (inet_address, port);
1266       g_object_unref (inet_address);
1267       if (!g_socket_listener_add_address (G_SOCKET_LISTENER (server->service),
1268                                           socket_address,
1269                                           G_SOCKET_TYPE_STREAM,
1270                                           G_SOCKET_PROTOCOL_TCP,
1271                                           G_OBJECT (server),
1272                                           NULL,
1273                                           error))
1274         {
1275           g_prefix_error (error, "Unable to listen to %s:%d: ", server->address, server->port);
1276           g_object_unref (socket_address);
1277           return NULL;
1278         }
1279       g_object_unref (socket_address);
1280     }
1281
1282   g_signal_connect (server->service, "incoming",
1283                     G_CALLBACK (handle_incoming_connection), NULL);
1284   return server;
1285 }
1286
1287 guint32
1288 broadway_server_get_last_seen_time (BroadwayServer *server)
1289 {
1290   broadway_server_consume_all_input (server);
1291   return (guint32) server->last_seen_time;
1292 }
1293
1294 void
1295 broadway_server_query_mouse (BroadwayServer *server,
1296                              guint32            *toplevel,
1297                              gint32             *root_x,
1298                              gint32             *root_y,
1299                              guint32            *mask)
1300 {
1301   if (server->output)
1302     {
1303       broadway_server_consume_all_input (server);
1304       if (root_x)
1305         *root_x = server->future_root_x;
1306       if (root_y)
1307         *root_y = server->future_root_y;
1308       if (mask)
1309         *mask = server->future_state;
1310       if (toplevel)
1311         *toplevel = server->future_mouse_in_toplevel;
1312       return;
1313     }
1314
1315   /* Fallback when unconnected */
1316   if (root_x)
1317     *root_x = server->last_x;
1318   if (root_y)
1319     *root_y = server->last_y;
1320   if (mask)
1321     *mask = server->last_state;
1322   if (toplevel)
1323     *toplevel = server->mouse_in_toplevel_id;
1324 }
1325
1326 void
1327 broadway_server_destroy_window (BroadwayServer *server,
1328                                 gint id)
1329 {
1330   BroadwayWindow *window;
1331
1332   if (server->mouse_in_toplevel_id == id)
1333     {
1334       /* TODO: Send leave + enter event, update cursors, etc */
1335       server->mouse_in_toplevel_id = 0;
1336     }
1337
1338   if (server->pointer_grab_window_id == id)
1339     server->pointer_grab_window_id = -1;
1340
1341   if (server->output)
1342     broadway_output_destroy_surface (server->output,
1343                                      id);
1344
1345   window = g_hash_table_lookup (server->id_ht,
1346                                 GINT_TO_POINTER (id));
1347   if (window != NULL)
1348     {
1349       server->toplevels = g_list_remove (server->toplevels, window);
1350       g_hash_table_remove (server->id_ht,
1351                            GINT_TO_POINTER (id));
1352       g_free (window);
1353     }
1354 }
1355
1356 gboolean
1357 broadway_server_window_show (BroadwayServer *server,
1358                              gint id)
1359 {
1360   BroadwayWindow *window;
1361   gboolean sent = FALSE;
1362
1363   window = g_hash_table_lookup (server->id_ht,
1364                                 GINT_TO_POINTER (id));
1365   if (window == NULL)
1366     return FALSE;
1367
1368   window->visible = TRUE;
1369
1370   if (server->output)
1371     {
1372       broadway_output_show_surface (server->output, window->id);
1373       sent = TRUE;
1374     }
1375
1376   return sent;
1377 }
1378
1379 gboolean
1380 broadway_server_window_hide (BroadwayServer *server,
1381                              gint id)
1382 {
1383   BroadwayWindow *window;
1384   gboolean sent = FALSE;
1385
1386   window = g_hash_table_lookup (server->id_ht,
1387                                 GINT_TO_POINTER (id));
1388   if (window == NULL)
1389     return FALSE;
1390
1391   window->visible = FALSE;
1392
1393   if (server->mouse_in_toplevel_id == id)
1394     {
1395       /* TODO: Send leave + enter event, update cursors, etc */
1396       server->mouse_in_toplevel_id = 0;
1397     }
1398
1399   if (server->pointer_grab_window_id == id)
1400     server->pointer_grab_window_id = -1;
1401
1402   if (server->output)
1403     {
1404       broadway_output_hide_surface (server->output, window->id);
1405       sent = TRUE;
1406     }
1407   return sent;
1408 }
1409
1410 void
1411 broadway_server_window_set_transient_for (BroadwayServer *server,
1412                                           gint id, gint parent)
1413 {
1414   BroadwayWindow *window;
1415
1416   window = g_hash_table_lookup (server->id_ht,
1417                                 GINT_TO_POINTER (id));
1418   if (window == NULL)
1419     return;
1420
1421   window->transient_for = parent;
1422
1423   if (server->output)
1424     {
1425       broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1426       broadway_server_flush (server);
1427     }
1428 }
1429
1430 gboolean
1431 broadway_server_has_client (BroadwayServer *server)
1432 {
1433   return server->output != NULL;
1434 }
1435
1436 static void
1437 _cairo_region (cairo_t         *cr,
1438                const cairo_region_t *region)
1439 {
1440   cairo_rectangle_int_t box;
1441   gint n_boxes, i;
1442
1443   g_return_if_fail (cr != NULL);
1444   g_return_if_fail (region != NULL);
1445
1446   n_boxes = cairo_region_num_rectangles (region);
1447
1448   for (i = 0; i < n_boxes; i++)
1449     {
1450       cairo_region_get_rectangle (region, i, &box);
1451       cairo_rectangle (cr, box.x, box.y, box.width, box.height);
1452     }
1453 }
1454
1455
1456 static void
1457 copy_region (cairo_surface_t *surface,
1458              cairo_region_t *area,
1459              gint            dx,
1460              gint            dy)
1461 {
1462   cairo_t *cr;
1463
1464   cr = cairo_create (surface);
1465   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1466
1467   _cairo_region (cr, area);
1468   cairo_clip (cr);
1469
1470   /* NB: This is a self-copy and Cairo doesn't support that yet.
1471    * So we do a litle trick.
1472    */
1473   cairo_push_group (cr);
1474
1475   cairo_set_source_surface (cr, surface, dx, dy);
1476   cairo_paint (cr);
1477
1478   cairo_pop_group_to_source (cr);
1479   cairo_paint (cr);
1480
1481   cairo_destroy (cr);
1482 }
1483
1484 gboolean
1485 broadway_server_window_translate (BroadwayServer *server,
1486                                   gint id,
1487                                   cairo_region_t *area,
1488                                   gint            dx,
1489                                   gint            dy)
1490 {
1491   BroadwayWindow *window;
1492   gboolean sent = FALSE;
1493
1494   window = g_hash_table_lookup (server->id_ht,
1495                                 GINT_TO_POINTER (id));
1496   if (window == NULL)
1497     return FALSE;
1498
1499   if (window->last_synced &&
1500       server->output)
1501     {
1502       BroadwayRect *rects;
1503       cairo_rectangle_int_t rect;
1504       int i, n_rects;
1505
1506       copy_region (window->last_surface, area, dx, dy);
1507       n_rects = cairo_region_num_rectangles (area);
1508       rects = g_new (BroadwayRect, n_rects);
1509       for (i = 0; i < n_rects; i++)
1510         {
1511           cairo_region_get_rectangle (area, i, &rect);
1512           rects[i].x = rect.x;
1513           rects[i].y = rect.y;
1514           rects[i].width = rect.width;
1515           rects[i].height = rect.height;
1516         }
1517       broadway_output_copy_rectangles (server->output,
1518                                        window->id,
1519                                        rects, n_rects, dx, dy);
1520       g_free (rects);
1521       sent = TRUE;
1522     }
1523
1524   return sent;
1525 }
1526
1527 static void
1528 diff_surfaces (cairo_surface_t *surface,
1529                cairo_surface_t *old_surface)
1530 {
1531   guint8 *data, *old_data;
1532   guint32 *line, *old_line;
1533   int w, h, stride, old_stride;
1534   int x, y;
1535
1536   data = cairo_image_surface_get_data (surface);
1537   old_data = cairo_image_surface_get_data (old_surface);
1538
1539   w = cairo_image_surface_get_width (surface);
1540   h = cairo_image_surface_get_height (surface);
1541
1542   stride = cairo_image_surface_get_stride (surface);
1543   old_stride = cairo_image_surface_get_stride (old_surface);
1544
1545   for (y = 0; y < h; y++)
1546     {
1547       line = (guint32 *)data;
1548       old_line = (guint32 *)old_data;
1549
1550       for (x = 0; x < w; x++)
1551         {
1552           if ((*line & 0xffffff) == (*old_line & 0xffffff))
1553             *old_line = 0;
1554           else
1555             *old_line = *line | 0xff000000;
1556           line ++;
1557           old_line ++;
1558         }
1559
1560       data += stride;
1561       old_data += old_stride;
1562     }
1563 }
1564
1565 void
1566 broadway_server_window_update (BroadwayServer *server,
1567                                gint id,
1568                                cairo_surface_t *surface)
1569 {
1570   cairo_t *cr;
1571   BroadwayWindow *window;
1572
1573   if (surface == NULL)
1574     return;
1575
1576   window = g_hash_table_lookup (server->id_ht,
1577                                 GINT_TO_POINTER (id));
1578   if (window == NULL)
1579     return;
1580
1581   if (window->last_surface == NULL)
1582     window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1583                                                        window->width,
1584                                                        window->height);
1585
1586   if (server->output != NULL)
1587     {
1588       if (window->last_synced)
1589         {
1590           diff_surfaces (surface,
1591                          window->last_surface);
1592           broadway_output_put_rgba (server->output, window->id, 0, 0,
1593                                     cairo_image_surface_get_width (window->last_surface),
1594                                     cairo_image_surface_get_height (window->last_surface),
1595                                     cairo_image_surface_get_stride (window->last_surface),
1596                                     cairo_image_surface_get_data (window->last_surface));
1597         }
1598       else
1599         {
1600           window->last_synced = TRUE;
1601           broadway_output_put_rgb (server->output, window->id, 0, 0,
1602                                    cairo_image_surface_get_width (surface),
1603                                    cairo_image_surface_get_height (surface),
1604                                    cairo_image_surface_get_stride (surface),
1605                                    cairo_image_surface_get_data (surface));
1606         }
1607
1608       broadway_output_surface_flush (server->output, window->id);
1609     }
1610
1611   cr = cairo_create (window->last_surface);
1612   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1613   cairo_set_source_surface (cr, surface, 0, 0);
1614   cairo_paint (cr);
1615   cairo_destroy (cr);
1616 }
1617
1618 gboolean
1619 broadway_server_window_move_resize (BroadwayServer *server,
1620                                     gint id,
1621                                     gboolean with_move,
1622                                     int x,
1623                                     int y,
1624                                     int width,
1625                                     int height)
1626 {
1627   BroadwayWindow *window;
1628   gboolean with_resize;
1629   gboolean sent = FALSE;
1630   cairo_t *cr;
1631
1632   window = g_hash_table_lookup (server->id_ht,
1633                                 GINT_TO_POINTER (id));
1634   if (window == NULL)
1635     return FALSE;
1636
1637   with_resize = width != window->width || height != window->height;
1638   window->width = width;
1639   window->height = height;
1640
1641   if (with_resize && window->last_surface != NULL)
1642     {
1643       cairo_surface_t *old;
1644
1645       old = window->last_surface;
1646
1647       window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1648                                                          width, height);
1649
1650
1651       cr = cairo_create (window->last_surface);
1652       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1653       cairo_set_source_surface (cr, old, 0, 0);
1654       cairo_paint (cr);
1655       cairo_destroy (cr);
1656
1657       cairo_surface_destroy (old);
1658     }
1659
1660   if (server->output != NULL)
1661     {
1662       broadway_output_move_resize_surface (server->output,
1663                                            window->id,
1664                                            with_move, x, y,
1665                                            with_resize, window->width, window->height);
1666       sent = TRUE;
1667     }
1668   else
1669     {
1670       if (with_move)
1671         {
1672           window->x = x;
1673           window->y = y;
1674         }
1675
1676       fake_configure_notify (server, window);
1677     }
1678
1679   return sent;
1680 }
1681
1682 guint32
1683 broadway_server_grab_pointer (BroadwayServer *server,
1684                               gint client_id,
1685                               gint id,
1686                               gboolean owner_events,
1687                               guint32 event_mask,
1688                               guint32 time_)
1689 {
1690   if (server->pointer_grab_window_id != -1 &&
1691       time_ != 0 && server->pointer_grab_time > time_)
1692     return GDK_GRAB_ALREADY_GRABBED;
1693
1694   if (time_ == 0)
1695     time_ = server->last_seen_time;
1696
1697   server->pointer_grab_window_id = id;
1698   server->pointer_grab_client_id = client_id;
1699   server->pointer_grab_owner_events = owner_events;
1700   server->pointer_grab_time = time_;
1701
1702   if (server->output)
1703     {
1704       broadway_output_grab_pointer (server->output,
1705                                     id,
1706                                     owner_events);
1707       broadway_server_flush (server);
1708     }
1709
1710   /* TODO: What about toplevel grab events if we're not connected? */
1711
1712   return GDK_GRAB_SUCCESS;
1713 }
1714
1715 guint32
1716 broadway_server_ungrab_pointer (BroadwayServer *server,
1717                                 guint32    time_)
1718 {
1719   guint32 serial;
1720
1721   if (server->pointer_grab_window_id != -1 &&
1722       time_ != 0 && server->pointer_grab_time > time_)
1723     return 0;
1724
1725   /* TODO: What about toplevel grab events if we're not connected? */
1726
1727   if (server->output)
1728     {
1729       serial = broadway_output_ungrab_pointer (server->output);
1730       broadway_server_flush (server);
1731     }
1732   else
1733     {
1734       serial = server->saved_serial;
1735     }
1736
1737   server->pointer_grab_window_id = -1;
1738
1739   return serial;
1740 }
1741
1742 guint32
1743 broadway_server_new_window (BroadwayServer *server,
1744                             int x,
1745                             int y,
1746                             int width,
1747                             int height,
1748                             gboolean is_temp)
1749 {
1750   BroadwayWindow *window;
1751
1752   window = g_new0 (BroadwayWindow, 1);
1753   window->id = server->id_counter++;
1754   window->x = x;
1755   window->y = y;
1756   if (x == 0 && y == 0 && !is_temp)
1757     {
1758       /* TODO: Better way to know if we should pick default pos */
1759       window->x = 100;
1760       window->y = 100;
1761     }
1762   window->width = width;
1763   window->height = height;
1764   window->is_temp = is_temp;
1765
1766   g_hash_table_insert (server->id_ht,
1767                        GINT_TO_POINTER (window->id),
1768                        window);
1769
1770   server->toplevels = g_list_prepend (server->toplevels, window);
1771
1772   if (server->output)
1773     broadway_output_new_surface (server->output,
1774                                  window->id,
1775                                  window->x,
1776                                  window->y,
1777                                  window->width,
1778                                  window->height,
1779                                  window->is_temp);
1780   else
1781     fake_configure_notify (server, window);
1782
1783   return window->id;
1784 }
1785
1786 static void
1787 broadway_server_resync_windows (BroadwayServer *server)
1788 {
1789   GList *l;
1790
1791   if (server->output == NULL)
1792     return;
1793
1794   /* First create all windows */
1795   for (l = server->toplevels; l != NULL; l = l->next)
1796     {
1797       BroadwayWindow *window = l->data;
1798
1799       if (window->id == 0)
1800         continue; /* Skip root */
1801
1802       window->last_synced = FALSE;
1803       broadway_output_new_surface (server->output,
1804                                    window->id,
1805                                    window->x,
1806                                    window->y,
1807                                    window->width,
1808                                    window->height,
1809                                    window->is_temp);
1810     }
1811
1812   /* Then do everything that may reference other windows */
1813   for (l = server->toplevels; l != NULL; l = l->next)
1814     {
1815       BroadwayWindow *window = l->data;
1816
1817       if (window->id == 0)
1818         continue; /* Skip root */
1819
1820       if (window->transient_for != -1)
1821         broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1822       if (window->visible)
1823         {
1824           broadway_output_show_surface (server->output, window->id);
1825
1826           if (window->last_surface != NULL)
1827             {
1828               window->last_synced = TRUE;
1829               broadway_output_put_rgb (server->output, window->id, 0, 0,
1830                                        cairo_image_surface_get_width (window->last_surface),
1831                                        cairo_image_surface_get_height (window->last_surface),
1832                                        cairo_image_surface_get_stride (window->last_surface),
1833                                        cairo_image_surface_get_data (window->last_surface));
1834             }
1835           broadway_output_surface_flush (server->output, window->id);
1836         }
1837     }
1838
1839   broadway_server_flush (server);
1840 }