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")))
953 if (origin == NULL || host == NULL)
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"
967 "Sec-WebSocket-Origin: %s\r\n"
968 "Sec-WebSocket-Location: ws://%s/socket\r\n"
969 "Sec-WebSocket-Protocol: broadway\r\n"
970 "\r\n", accept, origin, host);
973 #ifdef DEBUG_WEBSOCKETS
974 g_print ("v7 proto response:\n%s", res);
977 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
978 res, strlen (res), NULL, NULL, NULL);
980 proto_v7_plus = TRUE;
984 if (num_key1 != 1 || num_key2 != 1)
987 send_error (request, 400, "Bad websocket request");
991 challenge[0] = (key1 >> 24) & 0xff;
992 challenge[1] = (key1 >> 16) & 0xff;
993 challenge[2] = (key1 >> 8) & 0xff;
994 challenge[3] = (key1 >> 0) & 0xff;
995 challenge[4] = (key2 >> 24) & 0xff;
996 challenge[5] = (key2 >> 16) & 0xff;
997 challenge[6] = (key2 >> 8) & 0xff;
998 challenge[7] = (key2 >> 0) & 0xff;
1000 if (!g_input_stream_read_all (G_INPUT_STREAM (request->data), challenge+8, 8, NULL, NULL, NULL))
1003 send_error (request, 400, "Bad websocket request");
1007 checksum = g_checksum_new (G_CHECKSUM_MD5);
1008 g_checksum_update (checksum, challenge, 16);
1010 g_checksum_get_digest (checksum, challenge, &len);
1011 g_checksum_free (checksum);
1013 res = g_strdup_printf ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
1014 "Upgrade: WebSocket\r\n"
1015 "Connection: Upgrade\r\n"
1016 "Sec-WebSocket-Origin: %s\r\n"
1017 "Sec-WebSocket-Location: ws://%s/socket\r\n"
1018 "Sec-WebSocket-Protocol: broadway\r\n"
1022 #ifdef DEBUG_WEBSOCKETS
1023 g_print ("legacy response:\n%s", res);
1025 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1026 res, strlen (res), NULL, NULL, NULL);
1028 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1029 challenge, 16, NULL, NULL, NULL);
1030 proto_v7_plus = FALSE;
1034 if (server->input != NULL)
1036 broadway_input_free (server->input);
1037 server->input = NULL;
1040 input = g_new0 (BroadwayInput, 1);
1042 input->server = request->server;
1043 input->connection = g_object_ref (request->connection);
1044 input->proto_v7_plus = proto_v7_plus;
1045 input->binary = binary;
1047 data_buffer = g_buffered_input_stream_peek_buffer (G_BUFFERED_INPUT_STREAM (request->data), &data_buffer_size);
1048 input->buffer = g_byte_array_sized_new (data_buffer_size);
1049 g_byte_array_append (input->buffer, data_buffer, data_buffer_size);
1051 server->input = input;
1053 start_output (request, proto_v7_plus, binary);
1055 /* This will free and close the data input stream, but we got all the buffered content already */
1056 http_request_free (request);
1058 in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
1059 input->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (in), NULL);
1060 g_source_set_callback (input->source, (GSourceFunc)input_data_cb, input, NULL);
1061 g_source_attach (input->source, NULL);
1063 /* Process any data in the pipe already */
1064 parse_input (input);
1065 process_input_messages (server);
1071 start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary)
1074 BroadwayServer *server;
1077 socket = g_socket_connection_get_socket (request->connection);
1078 setsockopt(g_socket_get_fd (socket), IPPROTO_TCP,
1079 TCP_NODELAY, (char *) &flag, sizeof(int));
1081 server = BROADWAY_SERVER (request->server);
1085 server->saved_serial = broadway_output_get_next_serial (server->output);
1086 broadway_output_free (server->output);
1090 broadway_output_new (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1091 server->saved_serial, proto_v7_plus, binary);
1093 broadway_server_resync_windows (server);
1095 if (server->pointer_grab_window_id != -1)
1096 broadway_output_grab_pointer (server->output,
1097 server->pointer_grab_window_id,
1098 server->pointer_grab_owner_events);
1102 send_data (HttpRequest *request,
1103 const char *mimetype,
1104 const char *data, gsize len)
1108 res = g_strdup_printf ("HTTP/1.0 200 OK\r\n"
1109 "Content-Type: %s\r\n"
1110 "Content-Length: %"G_GSIZE_FORMAT"\r\n"
1113 /* TODO: This should really be async */
1114 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1115 res, strlen (res), NULL, NULL, NULL);
1117 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1118 data, len, NULL, NULL, NULL);
1119 http_request_free (request);
1122 #include "clienthtml.h"
1123 #include "broadwayjs.h"
1126 got_request (HttpRequest *request)
1128 char *start, *escaped, *tmp, *version, *query;
1130 if (!g_str_has_prefix (request->request->str, "GET "))
1132 send_error (request, 501, "Only GET implemented");
1136 start = request->request->str + 4; /* Skip "GET " */
1138 while (*start == ' ')
1141 for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1143 escaped = g_strndup (start, tmp - start);
1148 while (*start == ' ')
1150 for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1152 version = g_strndup (start, tmp - start);
1155 query = strchr (escaped, '?');
1159 if (strcmp (escaped, "/client.html") == 0 || strcmp (escaped, "/") == 0)
1160 send_data (request, "text/html", client_html, G_N_ELEMENTS(client_html) - 1);
1161 else if (strcmp (escaped, "/broadway.js") == 0)
1162 send_data (request, "text/javascript", broadway_js, G_N_ELEMENTS(broadway_js) - 1);
1163 else if (strcmp (escaped, "/socket") == 0)
1164 start_input (request, FALSE);
1165 else if (strcmp (escaped, "/socket-bin") == 0)
1166 start_input (request, TRUE);
1168 send_error (request, 404, "File not found");
1175 got_http_request_line (GInputStream *stream,
1176 GAsyncResult *result,
1177 HttpRequest *request)
1181 line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (stream), result, NULL, NULL);
1184 http_request_free (request);
1185 g_printerr ("Error reading request lines\n");
1188 if (strlen (line) == 0)
1189 got_request (request);
1192 /* Protect against overflow in request length */
1193 if (request->request->len > 1024 * 5)
1195 send_error (request, 400, "Request too long");
1199 g_string_append_printf (request->request, "%s\n", line);
1200 g_data_input_stream_read_line_async (request->data, 0, NULL,
1201 (GAsyncReadyCallback)got_http_request_line, request);
1208 handle_incoming_connection (GSocketService *service,
1209 GSocketConnection *connection,
1210 GObject *source_object)
1212 HttpRequest *request;
1215 request = g_new0 (HttpRequest, 1);
1216 request->connection = g_object_ref (connection);
1217 request->server = BROADWAY_SERVER (source_object);
1218 request->request = g_string_new ("");
1220 in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
1222 request->data = g_data_input_stream_new (in);
1223 g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (request->data), FALSE);
1224 /* Be tolerant of input */
1225 g_data_input_stream_set_newline_type (request->data, G_DATA_STREAM_NEWLINE_TYPE_ANY);
1227 g_data_input_stream_read_line_async (request->data, 0, NULL,
1228 (GAsyncReadyCallback)got_http_request_line, request);
1233 broadway_server_new (char *address, int port, GError **error)
1235 BroadwayServer *server;
1236 GInetAddress *inet_address;
1237 GSocketAddress *socket_address;
1239 server = g_object_new (BROADWAY_TYPE_SERVER, NULL);
1240 server->port = port;
1241 server->address = g_strdup (address);
1243 if (address == NULL)
1245 if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (server->service),
1250 g_prefix_error (error, "Unable to listen to port %d: ", server->port);
1256 inet_address = g_inet_address_new_from_string (address);
1257 if (inet_address == NULL)
1259 g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid ip address %s: ", address);
1262 socket_address = g_inet_socket_address_new (inet_address, port);
1263 g_object_unref (inet_address);
1264 if (!g_socket_listener_add_address (G_SOCKET_LISTENER (server->service),
1266 G_SOCKET_TYPE_STREAM,
1267 G_SOCKET_PROTOCOL_TCP,
1272 g_prefix_error (error, "Unable to listen to %s:%d: ", server->address, server->port);
1273 g_object_unref (socket_address);
1276 g_object_unref (socket_address);
1279 g_signal_connect (server->service, "incoming",
1280 G_CALLBACK (handle_incoming_connection), NULL);
1285 broadway_server_get_last_seen_time (BroadwayServer *server)
1287 broadway_server_consume_all_input (server);
1288 return (guint32) server->last_seen_time;
1292 broadway_server_query_mouse (BroadwayServer *server,
1300 broadway_server_consume_all_input (server);
1302 *root_x = server->future_root_x;
1304 *root_y = server->future_root_y;
1306 *mask = server->future_state;
1308 *toplevel = server->future_mouse_in_toplevel;
1312 /* Fallback when unconnected */
1314 *root_x = server->last_x;
1316 *root_y = server->last_y;
1318 *mask = server->last_state;
1320 *toplevel = server->mouse_in_toplevel_id;
1324 broadway_server_destroy_window (BroadwayServer *server,
1327 BroadwayWindow *window;
1329 if (server->mouse_in_toplevel_id == id)
1331 /* TODO: Send leave + enter event, update cursors, etc */
1332 server->mouse_in_toplevel_id = 0;
1335 if (server->pointer_grab_window_id == id)
1336 server->pointer_grab_window_id = -1;
1339 broadway_output_destroy_surface (server->output,
1342 window = g_hash_table_lookup (server->id_ht,
1343 GINT_TO_POINTER (id));
1346 server->toplevels = g_list_remove (server->toplevels, window);
1347 g_hash_table_remove (server->id_ht,
1348 GINT_TO_POINTER (id));
1354 broadway_server_window_show (BroadwayServer *server,
1357 BroadwayWindow *window;
1358 gboolean sent = FALSE;
1360 window = g_hash_table_lookup (server->id_ht,
1361 GINT_TO_POINTER (id));
1365 window->visible = TRUE;
1369 broadway_output_show_surface (server->output, window->id);
1377 broadway_server_window_hide (BroadwayServer *server,
1380 BroadwayWindow *window;
1381 gboolean sent = FALSE;
1383 window = g_hash_table_lookup (server->id_ht,
1384 GINT_TO_POINTER (id));
1388 window->visible = FALSE;
1390 if (server->mouse_in_toplevel_id == id)
1392 /* TODO: Send leave + enter event, update cursors, etc */
1393 server->mouse_in_toplevel_id = 0;
1396 if (server->pointer_grab_window_id == id)
1397 server->pointer_grab_window_id = -1;
1401 broadway_output_hide_surface (server->output, window->id);
1408 broadway_server_window_set_transient_for (BroadwayServer *server,
1409 gint id, gint parent)
1411 BroadwayWindow *window;
1413 window = g_hash_table_lookup (server->id_ht,
1414 GINT_TO_POINTER (id));
1418 window->transient_for = parent;
1422 broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1423 broadway_server_flush (server);
1428 broadway_server_has_client (BroadwayServer *server)
1430 return server->output != NULL;
1434 _cairo_region (cairo_t *cr,
1435 const cairo_region_t *region)
1437 cairo_rectangle_int_t box;
1440 g_return_if_fail (cr != NULL);
1441 g_return_if_fail (region != NULL);
1443 n_boxes = cairo_region_num_rectangles (region);
1445 for (i = 0; i < n_boxes; i++)
1447 cairo_region_get_rectangle (region, i, &box);
1448 cairo_rectangle (cr, box.x, box.y, box.width, box.height);
1454 copy_region (cairo_surface_t *surface,
1455 cairo_region_t *area,
1461 cr = cairo_create (surface);
1462 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1464 _cairo_region (cr, area);
1467 /* NB: This is a self-copy and Cairo doesn't support that yet.
1468 * So we do a litle trick.
1470 cairo_push_group (cr);
1472 cairo_set_source_surface (cr, surface, dx, dy);
1475 cairo_pop_group_to_source (cr);
1482 broadway_server_window_translate (BroadwayServer *server,
1484 cairo_region_t *area,
1488 BroadwayWindow *window;
1489 gboolean sent = FALSE;
1491 window = g_hash_table_lookup (server->id_ht,
1492 GINT_TO_POINTER (id));
1496 if (window->last_synced &&
1499 BroadwayRect *rects;
1500 cairo_rectangle_int_t rect;
1503 copy_region (window->last_surface, area, dx, dy);
1504 n_rects = cairo_region_num_rectangles (area);
1505 rects = g_new (BroadwayRect, n_rects);
1506 for (i = 0; i < n_rects; i++)
1508 cairo_region_get_rectangle (area, i, &rect);
1509 rects[i].x = rect.x;
1510 rects[i].y = rect.y;
1511 rects[i].width = rect.width;
1512 rects[i].height = rect.height;
1514 broadway_output_copy_rectangles (server->output,
1516 rects, n_rects, dx, dy);
1525 diff_surfaces (cairo_surface_t *surface,
1526 cairo_surface_t *old_surface)
1528 guint8 *data, *old_data;
1529 guint32 *line, *old_line;
1530 int w, h, stride, old_stride;
1533 data = cairo_image_surface_get_data (surface);
1534 old_data = cairo_image_surface_get_data (old_surface);
1536 w = cairo_image_surface_get_width (surface);
1537 h = cairo_image_surface_get_height (surface);
1539 stride = cairo_image_surface_get_stride (surface);
1540 old_stride = cairo_image_surface_get_stride (old_surface);
1542 for (y = 0; y < h; y++)
1544 line = (guint32 *)data;
1545 old_line = (guint32 *)old_data;
1547 for (x = 0; x < w; x++)
1549 if ((*line & 0xffffff) == (*old_line & 0xffffff))
1552 *old_line = *line | 0xff000000;
1558 old_data += old_stride;
1563 broadway_server_window_update (BroadwayServer *server,
1565 cairo_surface_t *surface)
1568 BroadwayWindow *window;
1570 if (surface == NULL)
1573 window = g_hash_table_lookup (server->id_ht,
1574 GINT_TO_POINTER (id));
1578 if (window->last_surface == NULL)
1579 window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1583 if (server->output != NULL)
1585 if (window->last_synced)
1587 diff_surfaces (surface,
1588 window->last_surface);
1589 broadway_output_put_rgba (server->output, window->id, 0, 0,
1590 cairo_image_surface_get_width (window->last_surface),
1591 cairo_image_surface_get_height (window->last_surface),
1592 cairo_image_surface_get_stride (window->last_surface),
1593 cairo_image_surface_get_data (window->last_surface));
1597 window->last_synced = TRUE;
1598 broadway_output_put_rgb (server->output, window->id, 0, 0,
1599 cairo_image_surface_get_width (surface),
1600 cairo_image_surface_get_height (surface),
1601 cairo_image_surface_get_stride (surface),
1602 cairo_image_surface_get_data (surface));
1605 broadway_output_surface_flush (server->output, window->id);
1608 cr = cairo_create (window->last_surface);
1609 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1610 cairo_set_source_surface (cr, surface, 0, 0);
1616 broadway_server_window_move_resize (BroadwayServer *server,
1624 BroadwayWindow *window;
1625 gboolean with_resize;
1626 gboolean sent = FALSE;
1629 window = g_hash_table_lookup (server->id_ht,
1630 GINT_TO_POINTER (id));
1634 with_resize = width != window->width || height != window->height;
1635 window->width = width;
1636 window->height = height;
1638 if (with_resize && window->last_surface != NULL)
1640 cairo_surface_t *old;
1642 old = window->last_surface;
1644 window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1648 cr = cairo_create (window->last_surface);
1649 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1650 cairo_set_source_surface (cr, old, 0, 0);
1654 cairo_surface_destroy (old);
1657 if (server->output != NULL)
1659 broadway_output_move_resize_surface (server->output,
1662 with_resize, window->width, window->height);
1673 fake_configure_notify (server, window);
1680 broadway_server_grab_pointer (BroadwayServer *server,
1683 gboolean owner_events,
1687 if (server->pointer_grab_window_id != -1 &&
1688 time_ != 0 && server->pointer_grab_time > time_)
1689 return GDK_GRAB_ALREADY_GRABBED;
1692 time_ = server->last_seen_time;
1694 server->pointer_grab_window_id = id;
1695 server->pointer_grab_client_id = client_id;
1696 server->pointer_grab_owner_events = owner_events;
1697 server->pointer_grab_time = time_;
1701 broadway_output_grab_pointer (server->output,
1704 broadway_server_flush (server);
1707 /* TODO: What about toplevel grab events if we're not connected? */
1709 return GDK_GRAB_SUCCESS;
1713 broadway_server_ungrab_pointer (BroadwayServer *server,
1718 if (server->pointer_grab_window_id != -1 &&
1719 time_ != 0 && server->pointer_grab_time > time_)
1722 /* TODO: What about toplevel grab events if we're not connected? */
1726 serial = broadway_output_ungrab_pointer (server->output);
1727 broadway_server_flush (server);
1731 serial = server->saved_serial;
1734 server->pointer_grab_window_id = -1;
1740 broadway_server_new_window (BroadwayServer *server,
1747 BroadwayWindow *window;
1749 window = g_new0 (BroadwayWindow, 1);
1750 window->id = server->id_counter++;
1753 if (x == 0 && y == 0 && !is_temp)
1755 /* TODO: Better way to know if we should pick default pos */
1759 window->width = width;
1760 window->height = height;
1761 window->is_temp = is_temp;
1763 g_hash_table_insert (server->id_ht,
1764 GINT_TO_POINTER (window->id),
1767 server->toplevels = g_list_prepend (server->toplevels, window);
1770 broadway_output_new_surface (server->output,
1778 fake_configure_notify (server, window);
1784 broadway_server_resync_windows (BroadwayServer *server)
1788 if (server->output == NULL)
1791 /* First create all windows */
1792 for (l = server->toplevels; l != NULL; l = l->next)
1794 BroadwayWindow *window = l->data;
1796 if (window->id == 0)
1797 continue; /* Skip root */
1799 window->last_synced = FALSE;
1800 broadway_output_new_surface (server->output,
1809 /* Then do everything that may reference other windows */
1810 for (l = server->toplevels; l != NULL; l = l->next)
1812 BroadwayWindow *window = l->data;
1814 if (window->id == 0)
1815 continue; /* Skip root */
1817 if (window->transient_for != -1)
1818 broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1819 if (window->visible)
1821 broadway_output_show_surface (server->output, window->id);
1823 if (window->last_surface != NULL)
1825 window->last_synced = TRUE;
1826 broadway_output_put_rgb (server->output, window->id, 0, 0,
1827 cairo_image_surface_get_width (window->last_surface),
1828 cairo_image_surface_get_height (window->last_surface),
1829 cairo_image_surface_get_stride (window->last_surface),
1830 cairo_image_surface_get_data (window->last_surface));
1832 broadway_output_surface_flush (server->output, window->id);
1836 broadway_server_flush (server);