1 #include "broadway-server.h"
3 #include "broadway-output.h"
6 #include <glib/gprintf.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <netinet/tcp.h>
17 typedef struct BroadwayInput BroadwayInput;
18 typedef struct BroadwayWindow BroadwayWindow;
19 struct _BroadwayServer {
20 GObject parent_instance;
24 GSocketService *service;
25 BroadwayOutput *output;
28 guint64 last_seen_time;
30 GList *input_messages;
31 guint process_input_idle;
38 guint32 screen_height;
40 gint32 mouse_in_toplevel_id;
41 int last_x, last_y; /* in root coords */
43 gint32 real_mouse_in_toplevel_id; /* Not affected by grabs */
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;
51 /* Future data, from the currently queued events */
55 int future_mouse_in_toplevel;
58 struct _BroadwayServerClass
60 GObjectClass parent_class;
63 typedef struct HttpRequest {
64 BroadwayServer *server;
65 GSocketConnection *connection;
66 GDataInputStream *data;
70 struct BroadwayInput {
71 BroadwayServer *server;
72 GSocketConnection *connection;
77 gboolean proto_v7_plus;
81 struct BroadwayWindow {
92 cairo_surface_t *last_surface;
95 static void broadway_server_resync_windows (BroadwayServer *server);
97 G_DEFINE_TYPE (BroadwayServer, broadway_server, G_TYPE_OBJECT)
100 broadway_server_init (BroadwayServer *server)
102 BroadwayWindow *root;
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;
111 root = g_new0 (BroadwayWindow, 1);
112 root->id = server->id_counter++;
115 root->visible = TRUE;
119 g_hash_table_insert (server->id_ht,
120 GINT_TO_POINTER (root->id),
125 broadway_server_finalize (GObject *object)
127 BroadwayServer *server = BROADWAY_SERVER (object);
129 g_free (server->address);
131 G_OBJECT_CLASS (broadway_server_parent_class)->finalize (object);
135 broadway_server_class_init (BroadwayServerClass * class)
137 GObjectClass *object_class = G_OBJECT_CLASS (class);
139 object_class->finalize = broadway_server_finalize;
142 static void start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary);
145 http_request_free (HttpRequest *request)
147 g_object_unref (request->connection);
148 g_object_unref (request->data);
149 g_string_free (request->request, TRUE);
154 broadway_input_free (BroadwayInput *input)
156 g_object_unref (input->connection);
157 g_byte_array_free (input->buffer, FALSE);
158 g_source_destroy (input->source);
163 update_event_state (BroadwayServer *server,
164 BroadwayInputMsg *message)
166 BroadwayWindow *window;
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;
175 /* TODO: Unset when it dies */
176 server->mouse_in_toplevel_id = message->pointer.event_window_id;
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;
184 server->mouse_in_toplevel_id = 0;
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;
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;
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;
205 case BROADWAY_EVENT_KEY_PRESS:
206 case BROADWAY_EVENT_KEY_RELEASE:
207 server->last_state = message->key.state;
209 case BROADWAY_EVENT_GRAB_NOTIFY:
210 case BROADWAY_EVENT_UNGRAB_NOTIFY:
212 case BROADWAY_EVENT_CONFIGURE_NOTIFY:
213 window = g_hash_table_lookup (server->id_ht,
214 GINT_TO_POINTER (message->configure_notify.id));
217 window->x = message->configure_notify.x;
218 window->y = message->configure_notify.y;
221 case BROADWAY_EVENT_DELETE_NOTIFY:
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;
229 g_printerr ("update_event_state - Unknown input command %c\n", message->base.type);
235 broadway_server_lookahead_event (BroadwayServer *server,
238 BroadwayInputMsg *message;
241 for (l = server->input_messages; l != NULL; l = l->next)
244 if (strchr (types, message->base.type) != NULL)
252 is_pointer_event (BroadwayInputMsg *message)
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;
266 process_input_message (BroadwayServer *server,
267 BroadwayInputMsg *message)
271 update_event_state (server, message);
273 if (is_pointer_event (message) &&
274 server->pointer_grab_window_id != -1)
275 client = server->pointer_grab_client_id;
277 broadway_events_got_input (message, client);
281 process_input_messages (BroadwayServer *server)
283 BroadwayInputMsg *message;
285 while (server->input_messages)
287 message = server->input_messages->data;
288 server->input_messages =
289 g_list_delete_link (server->input_messages,
290 server->input_messages);
292 if (message->base.serial == 0)
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
297 message->base.serial = server->saved_serial - 1;
300 process_input_message (server, message);
306 fake_configure_notify (BroadwayServer *server,
307 BroadwayWindow *window)
309 BroadwayInputMsg ev = { {0} };
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;
320 process_input_message (server, &ev);
324 parse_pointer_data (char *p, BroadwayInputPointerMsg *data)
326 data->mouse_window_id = strtol (p, &p, 10);
328 data->event_window_id = strtol (p, &p, 10);
330 data->root_x = strtol (p, &p, 10);
332 data->root_y = strtol (p, &p, 10);
334 data->win_x = strtol (p, &p, 10);
336 data->win_y = strtol (p, &p, 10);
338 data->state = strtol (p, &p, 10);
344 update_future_pointer_info (BroadwayServer *server, BroadwayInputPointerMsg *data)
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;
353 parse_input_message (BroadwayInput *input, const char *message)
355 BroadwayServer *server = input->server;
356 BroadwayInputMsg msg;
360 memset (&msg, 0, sizeof (msg));
363 msg.base.type = *p++;
364 msg.base.serial = (guint32)strtol (p, &p, 10);
366 time_ = strtol(p, &p, 10);
370 time_ = server->last_seen_time;
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);
379 time_ = time_ - input->time_base;
382 server->last_seen_time = time_;
384 msg.base.time = time_;
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);
392 msg.crossing.mode = strtol(p, &p, 10);
395 case BROADWAY_EVENT_POINTER_MOVE: /* Mouse move */
396 p = parse_pointer_data (p, &msg.pointer);
397 update_future_pointer_info (server, &msg.pointer);
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);
405 msg.button.button = strtol(p, &p, 10);
408 case BROADWAY_EVENT_SCROLL:
409 p = parse_pointer_data (p, &msg.pointer);
410 update_future_pointer_info (server, &msg.pointer);
412 msg.scroll.dir = strtol(p, &p, 10);
415 case BROADWAY_EVENT_KEY_PRESS:
416 case BROADWAY_EVENT_KEY_RELEASE:
417 msg.key.mouse_window_id = strtol(p, &p, 10);
419 msg.key.key = strtol(p, &p, 10);
421 msg.key.state = strtol(p, &p, 10);
424 case BROADWAY_EVENT_GRAB_NOTIFY:
425 case BROADWAY_EVENT_UNGRAB_NOTIFY:
426 msg.grab_reply.res = strtol(p, &p, 10);
429 case BROADWAY_EVENT_CONFIGURE_NOTIFY:
430 msg.configure_notify.id = strtol(p, &p, 10);
432 msg.configure_notify.x = strtol (p, &p, 10);
434 msg.configure_notify.y = strtol (p, &p, 10);
436 msg.configure_notify.width = strtol (p, &p, 10);
438 msg.configure_notify.height = strtol (p, &p, 10);
441 case BROADWAY_EVENT_DELETE_NOTIFY:
442 msg.delete_notify.id = strtol(p, &p, 10);
445 case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
446 msg.screen_resize_notify.width = strtol (p, &p, 10);
448 msg.screen_resize_notify.height = strtol (p, &p, 10);
452 g_printerr ("parse_input_message - Unknown input command %c (%s)\n", msg.base.type, message);
456 server->input_messages = g_list_append (server->input_messages, g_memdup (&msg, sizeof (msg)));
461 hex_dump (guchar *data, gsize len)
463 #ifdef DEBUG_WEBSOCKETS
465 for (j = 0; j < len + 15; j += 16)
467 fprintf (stderr, "0x%.4x ", j);
468 for (i = 0; i < 16; i++)
471 fprintf (stderr, "%.2x ", data[j+i]);
473 fprintf (stderr, " ");
475 fprintf (stderr, " ");
477 fprintf (stderr, " | ");
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]);
483 fprintf (stderr, ".");
484 fprintf (stderr, "\n");
490 parse_input (BroadwayInput *input)
492 BroadwayServer *server = input->server;
494 if (!input->buffer->len)
497 if (input->proto_v7_plus)
499 hex_dump (input->buffer->data, input->buffer->len);
501 while (input->buffer->len > 2)
503 gsize len, payload_len;
504 BroadwayWSOpCode code;
505 gboolean is_mask, fin;
506 guchar *buf, *data, *mask;
508 buf = input->buffer->data;
509 len = input->buffer->len;
511 #ifdef DEBUG_WEBSOCKETS
512 g_print ("Parse input first byte 0x%2x 0x%2x\n", buf[0], buf[1]);
516 code = buf[0] & 0x0f;
517 payload_len = buf[1] & 0x7f;
518 is_mask = buf[1] & 0x80;
521 if (payload_len > 125)
525 payload_len = GUINT16_FROM_BE( *(guint16 *) data );
528 else if (payload_len > 126)
532 payload_len = GUINT64_FROM_BE( *(guint64 *) data );
539 if (data - buf + 4 > len)
545 if (data - buf + payload_len > len)
546 return; /* wait to accumulate more */
551 for (i = 0; i < payload_len; i++)
552 data[i] ^= mask[i%4];
556 case BROADWAY_WS_CNX_CLOSE:
557 break; /* hang around anyway */
558 case BROADWAY_WS_TEXT:
561 #ifdef DEBUG_WEBSOCKETS
562 g_warning ("can't yet accept fragmented input");
567 char *terminated = g_strndup((char *)data, payload_len);
568 parse_input_message (input, terminated);
572 case BROADWAY_WS_CNX_PING:
573 broadway_output_pong (server->output);
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:
581 g_warning ("fragmented or unknown input code 0x%2x with fin set", code);
586 g_byte_array_remove_range (input->buffer, 0, data - buf + payload_len);
589 else /* old style protocol */
594 buf = (char *)input->buffer->data;
595 len = input->buffer->len;
599 server->input = NULL;
600 broadway_input_free (input);
604 while ((ptr = memchr (buf, 0xff, len)) != NULL)
609 parse_input_message (input, buf + 1);
614 if (len > 0 && buf[0] != 0)
616 server->input = NULL;
617 broadway_input_free (input);
621 g_byte_array_remove_range (input->buffer, 0, buf - (char *)input->buffer->data);
627 process_input_idle_cb (BroadwayServer *server)
629 server->process_input_idle = 0;
630 process_input_messages (server);
631 return G_SOURCE_REMOVE;
635 queue_process_input_at_idle (BroadwayServer *server)
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);
643 broadway_server_read_all_input_nonblocking (BroadwayServer *server)
649 BroadwayInput *input;
651 if (server->input == NULL)
654 input = server->input;
656 in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
659 res = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (in),
660 buffer, sizeof (buffer), NULL, &error);
665 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
667 g_error_free (error);
671 server->input = NULL;
672 broadway_input_free (input);
675 g_printerr ("input error %s\n", error->message);
676 g_error_free (error);
681 g_byte_array_append (input->buffer, buffer, res);
687 broadway_server_consume_all_input (BroadwayServer *server)
689 broadway_server_read_all_input_nonblocking (server);
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);
699 input_data_cb (GObject *stream,
700 BroadwayInput *input)
702 BroadwayServer *server = input->server;
704 broadway_server_read_all_input_nonblocking (server);
706 process_input_messages (server);
712 broadway_server_get_next_serial (BroadwayServer *server)
715 return broadway_output_get_next_serial (server->output);
717 return server->saved_serial;
721 broadway_server_get_screen_size (BroadwayServer *server,
725 *width = server->root->width;
726 *height = server->root->height;
731 broadway_server_flush (BroadwayServer *server)
733 if (server->output &&
734 !broadway_output_flush (server->output))
736 server->saved_serial = broadway_output_get_next_serial (server->output);
737 broadway_output_free (server->output);
738 server->output = NULL;
743 broadway_server_sync (BroadwayServer *server)
745 broadway_server_flush (server);
749 /* TODO: This is not used atm, is it needed? */
750 /* Note: This may be called while handling a message (i.e. sorta recursively) */
752 broadway_server_block_for_input (BroadwayServer *server, char op,
753 guint32 serial, gboolean remove_message)
755 BroadwayInputMsg *message;
758 BroadwayInput *input;
762 broadway_server_flush (server);
764 if (server->input == NULL)
767 input = server->input;
770 /* Check for existing reply in queue */
772 for (l = server->input_messages; l != NULL; l = l->next)
776 if (message->base.type == op)
778 if (message->base.serial == serial)
781 server->input_messages =
782 g_list_delete_link (server->input_messages, l);
788 /* Not found, read more, blocking */
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);
794 g_byte_array_append (input->buffer, buffer, res);
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);
806 parse_line (char *line, char *key)
810 if (!g_str_has_prefix (line, key))
812 p = line + strlen (key);
816 /* Skip optional initial space */
822 send_error (HttpRequest *request,
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>",
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);
838 http_request_free (request);
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"
844 /* 'x3JJHMbDL1EzLkh9GBhXDw==' generates 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=' */
846 generate_handshake_response_wsietf_v7 (const gchar *key)
848 gsize digest_len = 20;
849 guchar digest[digest_len];
852 checksum = g_checksum_new (G_CHECKSUM_SHA1);
856 g_checksum_update (checksum, (guchar *)key, -1);
857 g_checksum_update (checksum, (guchar *)SEC_WEB_SOCKET_KEY_MAGIC, -1);
859 g_checksum_get_digest (checksum, digest, &digest_len);
860 g_checksum_free (checksum);
862 g_assert (digest_len == 20);
864 return g_base64_encode (digest, digest_len);
868 start_input (HttpRequest *request, gboolean binary)
872 int num_key1, num_key2;
876 guint8 challenge[16];
881 BroadwayServer *server;
882 BroadwayInput *input;
883 const void *data_buffer;
884 gsize data_buffer_size;
887 gboolean proto_v7_plus;
889 server = request->server;
891 #ifdef DEBUG_WEBSOCKETS
892 g_print ("incoming request:\n%s\n", request->request->str);
894 lines = g_strsplit (request->request->str, "\n", 0);
903 for (i = 0; lines[i] != NULL; i++)
905 if ((p = parse_line (lines[i], "Sec-WebSocket-Key1")))
910 if (g_ascii_isdigit (*p))
911 key1 = key1 * 10 + g_ascii_digit_value (*p);
920 else if ((p = parse_line (lines[i], "Sec-WebSocket-Key2")))
925 if (g_ascii_isdigit (*p))
926 key2 = key2 * 10 + g_ascii_digit_value (*p);
935 else if ((p = parse_line (lines[i], "Sec-WebSocket-Key")))
939 else if ((p = parse_line (lines[i], "Origin")))
943 else if ((p = parse_line (lines[i], "Host")))
947 else if ((p = parse_line (lines[i], "Sec-WebSocket-Origin")))
956 send_error (request, 400, "Bad websocket request");
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"
968 "Sec-WebSocket-Location: ws://%s/socket\r\n"
969 "Sec-WebSocket-Protocol: broadway\r\n"
971 origin?"Sec-WebSocket-Origin: ":"", origin?origin:"", origin?"\r\n":"",
975 #ifdef DEBUG_WEBSOCKETS
976 g_print ("v7 proto response:\n%s", res);
979 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
980 res, strlen (res), NULL, NULL, NULL);
982 proto_v7_plus = TRUE;
986 if (num_key1 != 1 || num_key2 != 1)
989 send_error (request, 400, "Bad websocket request");
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;
1002 if (!g_input_stream_read_all (G_INPUT_STREAM (request->data), challenge+8, 8, NULL, NULL, NULL))
1005 send_error (request, 400, "Bad websocket request");
1009 checksum = g_checksum_new (G_CHECKSUM_MD5);
1010 g_checksum_update (checksum, challenge, 16);
1012 g_checksum_get_digest (checksum, challenge, &len);
1013 g_checksum_free (checksum);
1015 res = g_strdup_printf ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
1016 "Upgrade: WebSocket\r\n"
1017 "Connection: Upgrade\r\n"
1019 "Sec-WebSocket-Location: ws://%s/socket\r\n"
1020 "Sec-WebSocket-Protocol: broadway\r\n"
1022 origin?"Sec-WebSocket-Origin: ":"", origin?origin:"", origin?"\r\n":"",
1025 #ifdef DEBUG_WEBSOCKETS
1026 g_print ("legacy response:\n%s", res);
1028 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1029 res, strlen (res), NULL, NULL, NULL);
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;
1037 if (server->input != NULL)
1039 broadway_input_free (server->input);
1040 server->input = NULL;
1043 input = g_new0 (BroadwayInput, 1);
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;
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);
1054 server->input = input;
1056 start_output (request, proto_v7_plus, binary);
1058 /* This will free and close the data input stream, but we got all the buffered content already */
1059 http_request_free (request);
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);
1066 /* Process any data in the pipe already */
1067 parse_input (input);
1068 process_input_messages (server);
1074 start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary)
1077 BroadwayServer *server;
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));
1084 server = BROADWAY_SERVER (request->server);
1088 server->saved_serial = broadway_output_get_next_serial (server->output);
1089 broadway_output_free (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);
1096 broadway_server_resync_windows (server);
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);
1105 send_data (HttpRequest *request,
1106 const char *mimetype,
1107 const char *data, gsize len)
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"
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);
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);
1125 #include "clienthtml.h"
1126 #include "broadwayjs.h"
1129 got_request (HttpRequest *request)
1131 char *start, *escaped, *tmp, *version, *query;
1133 if (!g_str_has_prefix (request->request->str, "GET "))
1135 send_error (request, 501, "Only GET implemented");
1139 start = request->request->str + 4; /* Skip "GET " */
1141 while (*start == ' ')
1144 for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1146 escaped = g_strndup (start, tmp - start);
1151 while (*start == ' ')
1153 for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1155 version = g_strndup (start, tmp - start);
1158 query = strchr (escaped, '?');
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);
1171 send_error (request, 404, "File not found");
1178 got_http_request_line (GInputStream *stream,
1179 GAsyncResult *result,
1180 HttpRequest *request)
1184 line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (stream), result, NULL, NULL);
1187 http_request_free (request);
1188 g_printerr ("Error reading request lines\n");
1191 if (strlen (line) == 0)
1192 got_request (request);
1195 /* Protect against overflow in request length */
1196 if (request->request->len > 1024 * 5)
1198 send_error (request, 400, "Request too long");
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);
1211 handle_incoming_connection (GSocketService *service,
1212 GSocketConnection *connection,
1213 GObject *source_object)
1215 HttpRequest *request;
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 ("");
1223 in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
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);
1230 g_data_input_stream_read_line_async (request->data, 0, NULL,
1231 (GAsyncReadyCallback)got_http_request_line, request);
1236 broadway_server_new (char *address, int port, GError **error)
1238 BroadwayServer *server;
1239 GInetAddress *inet_address;
1240 GSocketAddress *socket_address;
1242 server = g_object_new (BROADWAY_TYPE_SERVER, NULL);
1243 server->port = port;
1244 server->address = g_strdup (address);
1246 if (address == NULL)
1248 if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (server->service),
1253 g_prefix_error (error, "Unable to listen to port %d: ", server->port);
1259 inet_address = g_inet_address_new_from_string (address);
1260 if (inet_address == NULL)
1262 g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid ip address %s: ", address);
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),
1269 G_SOCKET_TYPE_STREAM,
1270 G_SOCKET_PROTOCOL_TCP,
1275 g_prefix_error (error, "Unable to listen to %s:%d: ", server->address, server->port);
1276 g_object_unref (socket_address);
1279 g_object_unref (socket_address);
1282 g_signal_connect (server->service, "incoming",
1283 G_CALLBACK (handle_incoming_connection), NULL);
1288 broadway_server_get_last_seen_time (BroadwayServer *server)
1290 broadway_server_consume_all_input (server);
1291 return (guint32) server->last_seen_time;
1295 broadway_server_query_mouse (BroadwayServer *server,
1303 broadway_server_consume_all_input (server);
1305 *root_x = server->future_root_x;
1307 *root_y = server->future_root_y;
1309 *mask = server->future_state;
1311 *toplevel = server->future_mouse_in_toplevel;
1315 /* Fallback when unconnected */
1317 *root_x = server->last_x;
1319 *root_y = server->last_y;
1321 *mask = server->last_state;
1323 *toplevel = server->mouse_in_toplevel_id;
1327 broadway_server_destroy_window (BroadwayServer *server,
1330 BroadwayWindow *window;
1332 if (server->mouse_in_toplevel_id == id)
1334 /* TODO: Send leave + enter event, update cursors, etc */
1335 server->mouse_in_toplevel_id = 0;
1338 if (server->pointer_grab_window_id == id)
1339 server->pointer_grab_window_id = -1;
1342 broadway_output_destroy_surface (server->output,
1345 window = g_hash_table_lookup (server->id_ht,
1346 GINT_TO_POINTER (id));
1349 server->toplevels = g_list_remove (server->toplevels, window);
1350 g_hash_table_remove (server->id_ht,
1351 GINT_TO_POINTER (id));
1357 broadway_server_window_show (BroadwayServer *server,
1360 BroadwayWindow *window;
1361 gboolean sent = FALSE;
1363 window = g_hash_table_lookup (server->id_ht,
1364 GINT_TO_POINTER (id));
1368 window->visible = TRUE;
1372 broadway_output_show_surface (server->output, window->id);
1380 broadway_server_window_hide (BroadwayServer *server,
1383 BroadwayWindow *window;
1384 gboolean sent = FALSE;
1386 window = g_hash_table_lookup (server->id_ht,
1387 GINT_TO_POINTER (id));
1391 window->visible = FALSE;
1393 if (server->mouse_in_toplevel_id == id)
1395 /* TODO: Send leave + enter event, update cursors, etc */
1396 server->mouse_in_toplevel_id = 0;
1399 if (server->pointer_grab_window_id == id)
1400 server->pointer_grab_window_id = -1;
1404 broadway_output_hide_surface (server->output, window->id);
1411 broadway_server_window_set_transient_for (BroadwayServer *server,
1412 gint id, gint parent)
1414 BroadwayWindow *window;
1416 window = g_hash_table_lookup (server->id_ht,
1417 GINT_TO_POINTER (id));
1421 window->transient_for = parent;
1425 broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1426 broadway_server_flush (server);
1431 broadway_server_has_client (BroadwayServer *server)
1433 return server->output != NULL;
1437 _cairo_region (cairo_t *cr,
1438 const cairo_region_t *region)
1440 cairo_rectangle_int_t box;
1443 g_return_if_fail (cr != NULL);
1444 g_return_if_fail (region != NULL);
1446 n_boxes = cairo_region_num_rectangles (region);
1448 for (i = 0; i < n_boxes; i++)
1450 cairo_region_get_rectangle (region, i, &box);
1451 cairo_rectangle (cr, box.x, box.y, box.width, box.height);
1457 copy_region (cairo_surface_t *surface,
1458 cairo_region_t *area,
1464 cr = cairo_create (surface);
1465 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1467 _cairo_region (cr, area);
1470 /* NB: This is a self-copy and Cairo doesn't support that yet.
1471 * So we do a litle trick.
1473 cairo_push_group (cr);
1475 cairo_set_source_surface (cr, surface, dx, dy);
1478 cairo_pop_group_to_source (cr);
1485 broadway_server_window_translate (BroadwayServer *server,
1487 cairo_region_t *area,
1491 BroadwayWindow *window;
1492 gboolean sent = FALSE;
1494 window = g_hash_table_lookup (server->id_ht,
1495 GINT_TO_POINTER (id));
1499 if (window->last_synced &&
1502 BroadwayRect *rects;
1503 cairo_rectangle_int_t rect;
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++)
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;
1517 broadway_output_copy_rectangles (server->output,
1519 rects, n_rects, dx, dy);
1528 diff_surfaces (cairo_surface_t *surface,
1529 cairo_surface_t *old_surface)
1531 guint8 *data, *old_data;
1532 guint32 *line, *old_line;
1533 int w, h, stride, old_stride;
1536 data = cairo_image_surface_get_data (surface);
1537 old_data = cairo_image_surface_get_data (old_surface);
1539 w = cairo_image_surface_get_width (surface);
1540 h = cairo_image_surface_get_height (surface);
1542 stride = cairo_image_surface_get_stride (surface);
1543 old_stride = cairo_image_surface_get_stride (old_surface);
1545 for (y = 0; y < h; y++)
1547 line = (guint32 *)data;
1548 old_line = (guint32 *)old_data;
1550 for (x = 0; x < w; x++)
1552 if ((*line & 0xffffff) == (*old_line & 0xffffff))
1555 *old_line = *line | 0xff000000;
1561 old_data += old_stride;
1566 broadway_server_window_update (BroadwayServer *server,
1568 cairo_surface_t *surface)
1571 BroadwayWindow *window;
1573 if (surface == NULL)
1576 window = g_hash_table_lookup (server->id_ht,
1577 GINT_TO_POINTER (id));
1581 if (window->last_surface == NULL)
1582 window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1586 if (server->output != NULL)
1588 if (window->last_synced)
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));
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));
1608 broadway_output_surface_flush (server->output, window->id);
1611 cr = cairo_create (window->last_surface);
1612 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1613 cairo_set_source_surface (cr, surface, 0, 0);
1619 broadway_server_window_move_resize (BroadwayServer *server,
1627 BroadwayWindow *window;
1628 gboolean with_resize;
1629 gboolean sent = FALSE;
1632 window = g_hash_table_lookup (server->id_ht,
1633 GINT_TO_POINTER (id));
1637 with_resize = width != window->width || height != window->height;
1638 window->width = width;
1639 window->height = height;
1641 if (with_resize && window->last_surface != NULL)
1643 cairo_surface_t *old;
1645 old = window->last_surface;
1647 window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1651 cr = cairo_create (window->last_surface);
1652 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1653 cairo_set_source_surface (cr, old, 0, 0);
1657 cairo_surface_destroy (old);
1660 if (server->output != NULL)
1662 broadway_output_move_resize_surface (server->output,
1665 with_resize, window->width, window->height);
1676 fake_configure_notify (server, window);
1683 broadway_server_grab_pointer (BroadwayServer *server,
1686 gboolean owner_events,
1690 if (server->pointer_grab_window_id != -1 &&
1691 time_ != 0 && server->pointer_grab_time > time_)
1692 return GDK_GRAB_ALREADY_GRABBED;
1695 time_ = server->last_seen_time;
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_;
1704 broadway_output_grab_pointer (server->output,
1707 broadway_server_flush (server);
1710 /* TODO: What about toplevel grab events if we're not connected? */
1712 return GDK_GRAB_SUCCESS;
1716 broadway_server_ungrab_pointer (BroadwayServer *server,
1721 if (server->pointer_grab_window_id != -1 &&
1722 time_ != 0 && server->pointer_grab_time > time_)
1725 /* TODO: What about toplevel grab events if we're not connected? */
1729 serial = broadway_output_ungrab_pointer (server->output);
1730 broadway_server_flush (server);
1734 serial = server->saved_serial;
1737 server->pointer_grab_window_id = -1;
1743 broadway_server_new_window (BroadwayServer *server,
1750 BroadwayWindow *window;
1752 window = g_new0 (BroadwayWindow, 1);
1753 window->id = server->id_counter++;
1756 if (x == 0 && y == 0 && !is_temp)
1758 /* TODO: Better way to know if we should pick default pos */
1762 window->width = width;
1763 window->height = height;
1764 window->is_temp = is_temp;
1766 g_hash_table_insert (server->id_ht,
1767 GINT_TO_POINTER (window->id),
1770 server->toplevels = g_list_prepend (server->toplevels, window);
1773 broadway_output_new_surface (server->output,
1781 fake_configure_notify (server, window);
1787 broadway_server_resync_windows (BroadwayServer *server)
1791 if (server->output == NULL)
1794 /* First create all windows */
1795 for (l = server->toplevels; l != NULL; l = l->next)
1797 BroadwayWindow *window = l->data;
1799 if (window->id == 0)
1800 continue; /* Skip root */
1802 window->last_synced = FALSE;
1803 broadway_output_new_surface (server->output,
1812 /* Then do everything that may reference other windows */
1813 for (l = server->toplevels; l != NULL; l = l->next)
1815 BroadwayWindow *window = l->data;
1817 if (window->id == 0)
1818 continue; /* Skip root */
1820 if (window->transient_for != -1)
1821 broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1822 if (window->visible)
1824 broadway_output_show_surface (server->output, window->id);
1826 if (window->last_surface != NULL)
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));
1835 broadway_output_surface_flush (server->output, window->id);
1839 broadway_server_flush (server);