1 #include "gdkbroadway-server.h"
3 #include "broadway-output.h"
4 #include "gdkprivate-broadway.h"
7 #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;
19 struct _GdkBroadwayServer {
20 GObject parent_instance;
23 GSocketService *service;
24 BroadwayOutput *output;
27 guint64 last_seen_time;
29 GList *input_messages;
30 guint process_input_idle;
35 gint32 mouse_in_toplevel_id;
36 int last_x, last_y; /* in root coords */
38 gint32 real_mouse_in_toplevel_id; /* Not affected by grabs */
40 /* Explicit pointer grabs: */
41 gint32 pointer_grab_window_id; /* -1 => none */
42 guint32 pointer_grab_time;
43 gboolean pointer_grab_owner_events;
45 /* Future data, from the currently queued events */
49 int future_mouse_in_toplevel;
52 struct _GdkBroadwayServerClass
54 GObjectClass parent_class;
57 typedef struct HttpRequest {
58 GdkBroadwayServer *server;
59 GSocketConnection *connection;
60 GDataInputStream *data;
64 struct BroadwayInput {
65 GdkBroadwayServer *server;
66 GSocketConnection *connection;
71 gboolean proto_v7_plus;
86 cairo_surface_t *last_surface;
89 static void _gdk_broadway_server_resync_windows (GdkBroadwayServer *server);
91 G_DEFINE_TYPE (GdkBroadwayServer, gdk_broadway_server, G_TYPE_OBJECT)
94 gdk_broadway_server_init (GdkBroadwayServer *server)
98 server->service = g_socket_service_new ();
99 server->pointer_grab_window_id = -1;
100 server->saved_serial = 1;
101 server->last_seen_time = 1;
102 server->id_ht = g_hash_table_new (NULL, NULL);
103 server->id_counter = 0;
105 root = g_new0 (BroadwayWindow, 1);
106 root->id = server->id_counter++;
109 root->visible = TRUE;
111 g_hash_table_insert (server->id_ht,
112 GINT_TO_POINTER (root->id),
117 gdk_broadway_server_finalize (GObject *object)
119 G_OBJECT_CLASS (gdk_broadway_server_parent_class)->finalize (object);
123 gdk_broadway_server_class_init (GdkBroadwayServerClass * class)
125 GObjectClass *object_class = G_OBJECT_CLASS (class);
127 object_class->finalize = gdk_broadway_server_finalize;
130 static void start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary);
133 http_request_free (HttpRequest *request)
135 g_object_unref (request->connection);
136 g_object_unref (request->data);
137 g_string_free (request->request, TRUE);
142 broadway_input_free (BroadwayInput *input)
144 g_object_unref (input->connection);
145 g_byte_array_free (input->buffer, FALSE);
146 g_source_destroy (input->source);
151 update_event_state (GdkBroadwayServer *server,
152 BroadwayInputMsg *message)
154 switch (message->base.type) {
155 case BROADWAY_EVENT_ENTER:
156 server->last_x = message->pointer.root_x;
157 server->last_y = message->pointer.root_y;
158 server->last_state = message->pointer.state;
159 server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
161 /* TODO: Unset when it dies */
162 server->mouse_in_toplevel_id = message->pointer.event_window_id;
164 case BROADWAY_EVENT_LEAVE:
165 server->last_x = message->pointer.root_x;
166 server->last_y = message->pointer.root_y;
167 server->last_state = message->pointer.state;
168 server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
170 server->mouse_in_toplevel_id = 0;
172 case BROADWAY_EVENT_POINTER_MOVE:
173 server->last_x = message->pointer.root_x;
174 server->last_y = message->pointer.root_y;
175 server->last_state = message->pointer.state;
176 server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
178 case BROADWAY_EVENT_BUTTON_PRESS:
179 case BROADWAY_EVENT_BUTTON_RELEASE:
180 server->last_x = message->pointer.root_x;
181 server->last_y = message->pointer.root_y;
182 server->last_state = message->pointer.state;
183 server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
185 case BROADWAY_EVENT_SCROLL:
186 server->last_x = message->pointer.root_x;
187 server->last_y = message->pointer.root_y;
188 server->last_state = message->pointer.state;
189 server->real_mouse_in_toplevel_id = message->pointer.mouse_window_id;
191 case BROADWAY_EVENT_KEY_PRESS:
192 case BROADWAY_EVENT_KEY_RELEASE:
193 server->last_state = message->key.state;
195 case BROADWAY_EVENT_GRAB_NOTIFY:
196 case BROADWAY_EVENT_UNGRAB_NOTIFY:
198 case BROADWAY_EVENT_CONFIGURE_NOTIFY:
200 case BROADWAY_EVENT_DELETE_NOTIFY:
202 case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
206 g_printerr ("update_event_state - Unknown input command %c\n", message->base.type);
212 _gdk_broadway_server_lookahead_event (GdkBroadwayServer *server,
215 BroadwayInputMsg *message;
218 for (l = server->input_messages; l != NULL; l = l->next)
221 if (strchr (types, message->base.type) != NULL)
229 process_input_messages (GdkBroadwayServer *server)
231 BroadwayInputMsg *message;
233 while (server->input_messages)
235 message = server->input_messages->data;
236 server->input_messages =
237 g_list_delete_link (server->input_messages,
238 server->input_messages);
241 update_event_state (server, message);
242 _gdk_broadway_events_got_input (message);
248 parse_pointer_data (char *p, BroadwayInputPointerMsg *data)
250 data->mouse_window_id = strtol (p, &p, 10);
252 data->event_window_id = strtol (p, &p, 10);
254 data->root_x = strtol (p, &p, 10);
256 data->root_y = strtol (p, &p, 10);
258 data->win_x = strtol (p, &p, 10);
260 data->win_y = strtol (p, &p, 10);
262 data->state = strtol (p, &p, 10);
268 update_future_pointer_info (GdkBroadwayServer *server, BroadwayInputPointerMsg *data)
270 server->future_root_x = data->root_x;
271 server->future_root_y = data->root_y;
272 server->future_state = data->state;
273 server->future_mouse_in_toplevel = data->mouse_window_id;
277 parse_input_message (BroadwayInput *input, const char *message)
279 GdkBroadwayServer *server = input->server;
280 BroadwayInputMsg msg;
285 msg.base.type = *p++;
286 msg.base.serial = (guint32)strtol (p, &p, 10);
288 time_ = strtol(p, &p, 10);
292 time_ = server->last_seen_time;
294 if (!input->seen_time) {
295 input->seen_time = TRUE;
296 /* Calculate time base so that any following times are normalized to start
297 5 seconds after last_seen_time, to avoid issues that could appear when
298 a long hiatus due to a reconnect seems to be instant */
299 input->time_base = time_ - (server->last_seen_time + 5000);
301 time_ = time_ - input->time_base;
304 server->last_seen_time = time_;
306 msg.base.time = time_;
308 switch (msg.base.type) {
309 case BROADWAY_EVENT_ENTER:
310 case BROADWAY_EVENT_LEAVE:
311 p = parse_pointer_data (p, &msg.pointer);
312 update_future_pointer_info (server, &msg.pointer);
314 msg.crossing.mode = strtol(p, &p, 10);
317 case BROADWAY_EVENT_POINTER_MOVE: /* Mouse move */
318 p = parse_pointer_data (p, &msg.pointer);
319 update_future_pointer_info (server, &msg.pointer);
322 case BROADWAY_EVENT_BUTTON_PRESS:
323 case BROADWAY_EVENT_BUTTON_RELEASE:
324 p = parse_pointer_data (p, &msg.pointer);
325 update_future_pointer_info (server, &msg.pointer);
327 msg.button.button = strtol(p, &p, 10);
330 case BROADWAY_EVENT_SCROLL:
331 p = parse_pointer_data (p, &msg.pointer);
332 update_future_pointer_info (server, &msg.pointer);
334 msg.scroll.dir = strtol(p, &p, 10);
337 case BROADWAY_EVENT_KEY_PRESS:
338 case BROADWAY_EVENT_KEY_RELEASE:
339 msg.key.mouse_window_id = strtol(p, &p, 10);
341 msg.key.key = strtol(p, &p, 10);
343 msg.key.state = strtol(p, &p, 10);
346 case BROADWAY_EVENT_GRAB_NOTIFY:
347 case BROADWAY_EVENT_UNGRAB_NOTIFY:
348 msg.grab_reply.res = strtol(p, &p, 10);
351 case BROADWAY_EVENT_CONFIGURE_NOTIFY:
352 msg.configure_notify.id = strtol(p, &p, 10);
354 msg.configure_notify.x = strtol (p, &p, 10);
356 msg.configure_notify.y = strtol (p, &p, 10);
358 msg.configure_notify.width = strtol (p, &p, 10);
360 msg.configure_notify.height = strtol (p, &p, 10);
363 case BROADWAY_EVENT_DELETE_NOTIFY:
364 msg.delete_notify.id = strtol(p, &p, 10);
367 case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
368 msg.screen_resize_notify.width = strtol (p, &p, 10);
370 msg.screen_resize_notify.height = strtol (p, &p, 10);
374 g_printerr ("parse_input_message - Unknown input command %c (%s)\n", msg.base.type, message);
378 server->input_messages = g_list_append (server->input_messages, g_memdup (&msg, sizeof (msg)));
383 hex_dump (guchar *data, gsize len)
385 #ifdef DEBUG_WEBSOCKETS
387 for (j = 0; j < len + 15; j += 16)
389 fprintf (stderr, "0x%.4x ", j);
390 for (i = 0; i < 16; i++)
393 fprintf (stderr, "%.2x ", data[j+i]);
395 fprintf (stderr, " ");
397 fprintf (stderr, " ");
399 fprintf (stderr, " | ");
401 for (i = 0; i < 16; i++)
402 if ((j + i) < len && g_ascii_isalnum(data[j+i]))
403 fprintf (stderr, "%c", data[j+i]);
405 fprintf (stderr, ".");
406 fprintf (stderr, "\n");
412 parse_input (BroadwayInput *input)
414 GdkBroadwayServer *server = input->server;
416 if (!input->buffer->len)
419 if (input->proto_v7_plus)
421 hex_dump (input->buffer->data, input->buffer->len);
423 while (input->buffer->len > 2)
425 gsize len, payload_len;
426 BroadwayWSOpCode code;
427 gboolean is_mask, fin;
428 guchar *buf, *data, *mask;
430 buf = input->buffer->data;
431 len = input->buffer->len;
433 #ifdef DEBUG_WEBSOCKETS
434 g_print ("Parse input first byte 0x%2x 0x%2x\n", buf[0], buf[1]);
438 code = buf[0] & 0x0f;
439 payload_len = buf[1] & 0x7f;
440 is_mask = buf[1] & 0x80;
443 if (payload_len > 125)
447 payload_len = GUINT16_FROM_BE( *(guint16 *) data );
450 else if (payload_len > 126)
454 payload_len = GUINT64_FROM_BE( *(guint64 *) data );
461 if (data - buf + 4 > len)
467 if (data - buf + payload_len > len)
468 return; /* wait to accumulate more */
473 for (i = 0; i < payload_len; i++)
474 data[i] ^= mask[i%4];
478 case BROADWAY_WS_CNX_CLOSE:
479 break; /* hang around anyway */
480 case BROADWAY_WS_TEXT:
483 #ifdef DEBUG_WEBSOCKETS
484 g_warning ("can't yet accept fragmented input");
489 char *terminated = g_strndup((char *)data, payload_len);
490 parse_input_message (input, terminated);
494 case BROADWAY_WS_CNX_PING:
495 broadway_output_pong (server->output);
497 case BROADWAY_WS_CNX_PONG:
498 break; /* we never send pings, but tolerate pongs */
499 case BROADWAY_WS_BINARY:
500 case BROADWAY_WS_CONTINUATION:
503 g_warning ("fragmented or unknown input code 0x%2x with fin set", code);
508 g_byte_array_remove_range (input->buffer, 0, data - buf + payload_len);
511 else /* old style protocol */
516 buf = (char *)input->buffer->data;
517 len = input->buffer->len;
521 server->input = NULL;
522 broadway_input_free (input);
526 while ((ptr = memchr (buf, 0xff, len)) != NULL)
531 parse_input_message (input, buf + 1);
536 if (len > 0 && buf[0] != 0)
538 server->input = NULL;
539 broadway_input_free (input);
543 g_byte_array_remove_range (input->buffer, 0, buf - (char *)input->buffer->data);
549 process_input_idle_cb (GdkBroadwayServer *server)
551 server->process_input_idle = 0;
552 process_input_messages (server);
553 return G_SOURCE_REMOVE;
557 queue_process_input_at_idle (GdkBroadwayServer *server)
559 if (server->process_input_idle == 0)
560 server->process_input_idle =
561 g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)process_input_idle_cb, server, NULL);
565 _gdk_broadway_server_read_all_input_nonblocking (GdkBroadwayServer *server)
571 BroadwayInput *input;
573 if (server->input == NULL)
576 input = server->input;
578 in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
581 res = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (in),
582 buffer, sizeof (buffer), NULL, &error);
587 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
589 g_error_free (error);
593 server->input = NULL;
594 broadway_input_free (input);
597 g_printerr ("input error %s\n", error->message);
598 g_error_free (error);
603 g_byte_array_append (input->buffer, buffer, res);
609 _gdk_broadway_server_consume_all_input (GdkBroadwayServer *server)
611 _gdk_broadway_server_read_all_input_nonblocking (server);
613 /* Since we're parsing input but not processing the resulting messages
614 we might not get a readable callback on the stream, so queue an idle to
615 process the messages */
616 queue_process_input_at_idle (server);
621 input_data_cb (GObject *stream,
622 BroadwayInput *input)
624 GdkBroadwayServer *server = input->server;
626 _gdk_broadway_server_read_all_input_nonblocking (server);
628 process_input_messages (server);
634 _gdk_broadway_server_get_next_serial (GdkBroadwayServer *server)
637 return broadway_output_get_next_serial (server->output);
639 return server->saved_serial;
643 _gdk_broadway_server_flush (GdkBroadwayServer *server)
645 if (server->output &&
646 !broadway_output_flush (server->output))
648 server->saved_serial = broadway_output_get_next_serial (server->output);
649 broadway_output_free (server->output);
650 server->output = NULL;
655 _gdk_broadway_server_sync (GdkBroadwayServer *server)
657 _gdk_broadway_server_flush (server);
661 /* TODO: This is not used atm, is it needed? */
662 /* Note: This may be called while handling a message (i.e. sorta recursively) */
664 _gdk_broadway_server_block_for_input (GdkBroadwayServer *server, char op,
665 guint32 serial, gboolean remove_message)
667 BroadwayInputMsg *message;
670 BroadwayInput *input;
674 _gdk_broadway_server_flush (server);
676 if (server->input == NULL)
679 input = server->input;
682 /* Check for existing reply in queue */
684 for (l = server->input_messages; l != NULL; l = l->next)
688 if (message->base.type == op)
690 if (message->base.serial == serial)
693 server->input_messages =
694 g_list_delete_link (server->input_messages, l);
700 /* Not found, read more, blocking */
702 in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
703 res = g_input_stream_read (in, buffer, sizeof (buffer), NULL, NULL);
706 g_byte_array_append (input->buffer, buffer, res);
710 /* Since we're parsing input but not processing the resulting messages
711 we might not get a readable callback on the stream, so queue an idle to
712 process the messages */
713 queue_process_input_at_idle (server);
718 parse_line (char *line, char *key)
722 if (!g_str_has_prefix (line, key))
724 p = line + strlen (key);
728 /* Skip optional initial space */
734 send_error (HttpRequest *request,
740 res = g_strdup_printf ("HTTP/1.0 %d %s\r\n\r\n"
741 "<html><head><title>%d %s</title></head>"
742 "<body>%s</body></html>",
746 /* TODO: This should really be async */
747 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
748 res, strlen (res), NULL, NULL, NULL);
750 http_request_free (request);
753 /* magic from: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 */
754 #define SEC_WEB_SOCKET_KEY_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
756 /* 'x3JJHMbDL1EzLkh9GBhXDw==' generates 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=' */
758 generate_handshake_response_wsietf_v7 (const gchar *key)
760 gsize digest_len = 20;
761 guchar digest[digest_len];
764 checksum = g_checksum_new (G_CHECKSUM_SHA1);
768 g_checksum_update (checksum, (guchar *)key, -1);
769 g_checksum_update (checksum, (guchar *)SEC_WEB_SOCKET_KEY_MAGIC, -1);
771 g_checksum_get_digest (checksum, digest, &digest_len);
772 g_checksum_free (checksum);
774 g_assert (digest_len == 20);
776 return g_base64_encode (digest, digest_len);
780 start_input (HttpRequest *request, gboolean binary)
784 int num_key1, num_key2;
788 guint8 challenge[16];
793 GdkBroadwayServer *server;
794 BroadwayInput *input;
795 const void *data_buffer;
796 gsize data_buffer_size;
799 gboolean proto_v7_plus;
801 server = GDK_BROADWAY_SERVER (request->server);
803 #ifdef DEBUG_WEBSOCKETS
804 g_print ("incoming request:\n%s\n", request->request->str);
806 lines = g_strsplit (request->request->str, "\n", 0);
815 for (i = 0; lines[i] != NULL; i++)
817 if ((p = parse_line (lines[i], "Sec-WebSocket-Key1")))
822 if (g_ascii_isdigit (*p))
823 key1 = key1 * 10 + g_ascii_digit_value (*p);
832 else if ((p = parse_line (lines[i], "Sec-WebSocket-Key2")))
837 if (g_ascii_isdigit (*p))
838 key2 = key2 * 10 + g_ascii_digit_value (*p);
847 else if ((p = parse_line (lines[i], "Sec-WebSocket-Key")))
851 else if ((p = parse_line (lines[i], "Origin")))
855 else if ((p = parse_line (lines[i], "Host")))
859 else if ((p = parse_line (lines[i], "Sec-WebSocket-Origin")))
865 if (origin == NULL || host == NULL)
868 send_error (request, 400, "Bad websocket request");
874 char* accept = generate_handshake_response_wsietf_v7 (key_v7);
875 res = g_strdup_printf ("HTTP/1.1 101 Switching Protocols\r\n"
876 "Upgrade: websocket\r\n"
877 "Connection: Upgrade\r\n"
878 "Sec-WebSocket-Accept: %s\r\n"
879 "Sec-WebSocket-Origin: %s\r\n"
880 "Sec-WebSocket-Location: ws://%s/socket\r\n"
881 "Sec-WebSocket-Protocol: broadway\r\n"
882 "\r\n", accept, origin, host);
885 #ifdef DEBUG_WEBSOCKETS
886 g_print ("v7 proto response:\n%s", res);
889 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
890 res, strlen (res), NULL, NULL, NULL);
892 proto_v7_plus = TRUE;
896 if (num_key1 != 1 || num_key2 != 1)
899 send_error (request, 400, "Bad websocket request");
903 challenge[0] = (key1 >> 24) & 0xff;
904 challenge[1] = (key1 >> 16) & 0xff;
905 challenge[2] = (key1 >> 8) & 0xff;
906 challenge[3] = (key1 >> 0) & 0xff;
907 challenge[4] = (key2 >> 24) & 0xff;
908 challenge[5] = (key2 >> 16) & 0xff;
909 challenge[6] = (key2 >> 8) & 0xff;
910 challenge[7] = (key2 >> 0) & 0xff;
912 if (!g_input_stream_read_all (G_INPUT_STREAM (request->data), challenge+8, 8, NULL, NULL, NULL))
915 send_error (request, 400, "Bad websocket request");
919 checksum = g_checksum_new (G_CHECKSUM_MD5);
920 g_checksum_update (checksum, challenge, 16);
922 g_checksum_get_digest (checksum, challenge, &len);
923 g_checksum_free (checksum);
925 res = g_strdup_printf ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
926 "Upgrade: WebSocket\r\n"
927 "Connection: Upgrade\r\n"
928 "Sec-WebSocket-Origin: %s\r\n"
929 "Sec-WebSocket-Location: ws://%s/socket\r\n"
930 "Sec-WebSocket-Protocol: broadway\r\n"
934 #ifdef DEBUG_WEBSOCKETS
935 g_print ("legacy response:\n%s", res);
937 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
938 res, strlen (res), NULL, NULL, NULL);
940 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
941 challenge, 16, NULL, NULL, NULL);
942 proto_v7_plus = FALSE;
946 if (server->input != NULL)
948 broadway_input_free (server->input);
949 server->input = NULL;
952 input = g_new0 (BroadwayInput, 1);
954 input->server = request->server;
955 input->connection = g_object_ref (request->connection);
956 input->proto_v7_plus = proto_v7_plus;
957 input->binary = binary;
959 data_buffer = g_buffered_input_stream_peek_buffer (G_BUFFERED_INPUT_STREAM (request->data), &data_buffer_size);
960 input->buffer = g_byte_array_sized_new (data_buffer_size);
961 g_byte_array_append (input->buffer, data_buffer, data_buffer_size);
963 server->input = input;
965 start_output (request, proto_v7_plus, binary);
967 /* This will free and close the data input stream, but we got all the buffered content already */
968 http_request_free (request);
970 in = g_io_stream_get_input_stream (G_IO_STREAM (input->connection));
971 input->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (in), NULL);
972 g_source_set_callback (input->source, (GSourceFunc)input_data_cb, input, NULL);
973 g_source_attach (input->source, NULL);
975 /* Process any data in the pipe already */
977 process_input_messages (server);
983 start_output (HttpRequest *request, gboolean proto_v7_plus, gboolean binary)
986 GdkBroadwayServer *server;
989 socket = g_socket_connection_get_socket (request->connection);
990 setsockopt(g_socket_get_fd (socket), IPPROTO_TCP,
991 TCP_NODELAY, (char *) &flag, sizeof(int));
993 server = GDK_BROADWAY_SERVER (request->server);
997 server->saved_serial = broadway_output_get_next_serial (server->output);
998 broadway_output_free (server->output);
1002 broadway_output_new (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1003 server->saved_serial, proto_v7_plus, binary);
1005 _gdk_broadway_server_resync_windows (server);
1007 if (server->pointer_grab_window_id != -1)
1008 broadway_output_grab_pointer (server->output,
1009 server->pointer_grab_window_id,
1010 server->pointer_grab_owner_events);
1014 send_data (HttpRequest *request,
1015 const char *mimetype,
1016 const char *data, gsize len)
1020 res = g_strdup_printf ("HTTP/1.0 200 OK\r\n"
1021 "Content-Type: %s\r\n"
1022 "Content-Length: %"G_GSIZE_FORMAT"\r\n"
1025 /* TODO: This should really be async */
1026 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1027 res, strlen (res), NULL, NULL, NULL);
1029 g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (request->connection)),
1030 data, len, NULL, NULL, NULL);
1031 http_request_free (request);
1034 #include "clienthtml.h"
1035 #include "broadwayjs.h"
1038 got_request (HttpRequest *request)
1040 char *start, *escaped, *tmp, *version, *query;
1042 if (!g_str_has_prefix (request->request->str, "GET "))
1044 send_error (request, 501, "Only GET implemented");
1048 start = request->request->str + 4; /* Skip "GET " */
1050 while (*start == ' ')
1053 for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1055 escaped = g_strndup (start, tmp - start);
1060 while (*start == ' ')
1062 for (tmp = start; *tmp != 0 && *tmp != ' ' && *tmp != '\n'; tmp++)
1064 version = g_strndup (start, tmp - start);
1067 query = strchr (escaped, '?');
1071 if (strcmp (escaped, "/client.html") == 0 || strcmp (escaped, "/") == 0)
1072 send_data (request, "text/html", client_html, G_N_ELEMENTS(client_html) - 1);
1073 else if (strcmp (escaped, "/broadway.js") == 0)
1074 send_data (request, "text/javascript", broadway_js, G_N_ELEMENTS(broadway_js) - 1);
1075 else if (strcmp (escaped, "/socket") == 0)
1076 start_input (request, FALSE);
1077 else if (strcmp (escaped, "/socket-bin") == 0)
1078 start_input (request, TRUE);
1080 send_error (request, 404, "File not found");
1087 got_http_request_line (GInputStream *stream,
1088 GAsyncResult *result,
1089 HttpRequest *request)
1093 line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (stream), result, NULL, NULL);
1096 http_request_free (request);
1097 g_printerr ("Error reading request lines\n");
1100 if (strlen (line) == 0)
1101 got_request (request);
1104 /* Protect against overflow in request length */
1105 if (request->request->len > 1024 * 5)
1107 send_error (request, 400, "Request too long");
1111 g_string_append_printf (request->request, "%s\n", line);
1112 g_data_input_stream_read_line_async (request->data, 0, NULL,
1113 (GAsyncReadyCallback)got_http_request_line, request);
1120 handle_incoming_connection (GSocketService *service,
1121 GSocketConnection *connection,
1122 GObject *source_object)
1124 HttpRequest *request;
1127 request = g_new0 (HttpRequest, 1);
1128 request->connection = g_object_ref (connection);
1129 request->server = GDK_BROADWAY_SERVER (source_object);
1130 request->request = g_string_new ("");
1132 in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
1134 request->data = g_data_input_stream_new (in);
1135 g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (request->data), FALSE);
1136 /* Be tolerant of input */
1137 g_data_input_stream_set_newline_type (request->data, G_DATA_STREAM_NEWLINE_TYPE_ANY);
1139 g_data_input_stream_read_line_async (request->data, 0, NULL,
1140 (GAsyncReadyCallback)got_http_request_line, request);
1145 _gdk_broadway_server_new (int port, GError **error)
1147 GdkBroadwayServer *server;
1149 server = g_object_new (GDK_TYPE_BROADWAY_SERVER, NULL);
1150 server->port = port;
1152 if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (server->service),
1157 g_prefix_error (error, "Unable to listen to port %d: ", server->port);
1161 g_signal_connect (server->service, "incoming",
1162 G_CALLBACK (handle_incoming_connection), NULL);
1167 _gdk_broadway_server_get_last_seen_time (GdkBroadwayServer *server)
1169 _gdk_broadway_server_consume_all_input (server);
1170 return (guint32) server->last_seen_time;
1174 _gdk_broadway_server_query_mouse (GdkBroadwayServer *server,
1182 _gdk_broadway_server_consume_all_input (server);
1184 *root_x = server->future_root_x;
1186 *root_y = server->future_root_y;
1188 *mask = server->future_state;
1190 *toplevel = server->future_mouse_in_toplevel;
1194 /* Fallback when unconnected */
1196 *root_x = server->last_x;
1198 *root_y = server->last_y;
1200 *mask = server->last_state;
1202 *toplevel = server->mouse_in_toplevel_id;
1206 _gdk_broadway_server_destroy_window (GdkBroadwayServer *server,
1209 BroadwayWindow *window;
1211 if (server->mouse_in_toplevel_id == id)
1213 /* TODO: Send leave + enter event, update cursors, etc */
1214 server->mouse_in_toplevel_id = 0;
1217 if (server->pointer_grab_window_id == id)
1218 server->pointer_grab_window_id = -1;
1221 broadway_output_destroy_surface (server->output,
1224 window = g_hash_table_lookup (server->id_ht,
1225 GINT_TO_POINTER (id));
1228 server->toplevels = g_list_remove (server->toplevels, window);
1229 g_hash_table_remove (server->id_ht,
1230 GINT_TO_POINTER (id));
1236 _gdk_broadway_server_window_show (GdkBroadwayServer *server,
1239 BroadwayWindow *window;
1240 gboolean sent = FALSE;
1242 window = g_hash_table_lookup (server->id_ht,
1243 GINT_TO_POINTER (id));
1247 window->visible = TRUE;
1251 broadway_output_show_surface (server->output, window->id);
1259 _gdk_broadway_server_window_hide (GdkBroadwayServer *server,
1262 BroadwayWindow *window;
1263 gboolean sent = FALSE;
1265 window = g_hash_table_lookup (server->id_ht,
1266 GINT_TO_POINTER (id));
1270 window->visible = FALSE;
1272 if (server->mouse_in_toplevel_id == id)
1274 /* TODO: Send leave + enter event, update cursors, etc */
1275 server->mouse_in_toplevel_id = 0;
1280 broadway_output_hide_surface (server->output, window->id);
1287 _gdk_broadway_server_window_set_transient_for (GdkBroadwayServer *server,
1288 gint id, gint parent)
1290 BroadwayWindow *window;
1292 window = g_hash_table_lookup (server->id_ht,
1293 GINT_TO_POINTER (id));
1297 window->transient_for = parent;
1301 broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1302 _gdk_broadway_server_flush (server);
1307 _gdk_broadway_server_has_client (GdkBroadwayServer *server)
1309 return server->output != NULL;
1313 _cairo_region (cairo_t *cr,
1314 const cairo_region_t *region)
1316 cairo_rectangle_int_t box;
1319 g_return_if_fail (cr != NULL);
1320 g_return_if_fail (region != NULL);
1322 n_boxes = cairo_region_num_rectangles (region);
1324 for (i = 0; i < n_boxes; i++)
1326 cairo_region_get_rectangle (region, i, &box);
1327 cairo_rectangle (cr, box.x, box.y, box.width, box.height);
1333 copy_region (cairo_surface_t *surface,
1334 cairo_region_t *area,
1340 cr = cairo_create (surface);
1341 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1343 _cairo_region (cr, area);
1346 /* NB: This is a self-copy and Cairo doesn't support that yet.
1347 * So we do a litle trick.
1349 cairo_push_group (cr);
1351 cairo_set_source_surface (cr, surface, dx, dy);
1354 cairo_pop_group_to_source (cr);
1361 _gdk_broadway_server_window_translate (GdkBroadwayServer *server,
1363 cairo_region_t *area,
1367 BroadwayWindow *window;
1368 gboolean sent = FALSE;
1370 window = g_hash_table_lookup (server->id_ht,
1371 GINT_TO_POINTER (id));
1375 if (window->last_synced &&
1378 BroadwayRect *rects;
1379 cairo_rectangle_int_t rect;
1382 copy_region (window->last_surface, area, dx, dy);
1383 n_rects = cairo_region_num_rectangles (area);
1384 rects = g_new (BroadwayRect, n_rects);
1385 for (i = 0; i < n_rects; i++)
1387 cairo_region_get_rectangle (area, i, &rect);
1388 rects[i].x = rect.x;
1389 rects[i].y = rect.y;
1390 rects[i].width = rect.width;
1391 rects[i].height = rect.height;
1393 broadway_output_copy_rectangles (server->output,
1395 rects, n_rects, dx, dy);
1404 diff_surfaces (cairo_surface_t *surface,
1405 cairo_surface_t *old_surface)
1407 guint8 *data, *old_data;
1408 guint32 *line, *old_line;
1409 int w, h, stride, old_stride;
1412 data = cairo_image_surface_get_data (surface);
1413 old_data = cairo_image_surface_get_data (old_surface);
1415 w = cairo_image_surface_get_width (surface);
1416 h = cairo_image_surface_get_height (surface);
1418 stride = cairo_image_surface_get_stride (surface);
1419 old_stride = cairo_image_surface_get_stride (old_surface);
1421 for (y = 0; y < h; y++)
1423 line = (guint32 *)data;
1424 old_line = (guint32 *)old_data;
1426 for (x = 0; x < w; x++)
1428 if ((*line & 0xffffff) == (*old_line & 0xffffff))
1431 *old_line = *line | 0xff000000;
1437 old_data += old_stride;
1442 _gdk_broadway_server_window_update (GdkBroadwayServer *server,
1444 cairo_surface_t *surface)
1447 BroadwayWindow *window;
1449 if (surface == NULL)
1452 window = g_hash_table_lookup (server->id_ht,
1453 GINT_TO_POINTER (id));
1457 if (window->last_surface == NULL)
1458 window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1462 if (server->output != NULL)
1464 if (window->last_synced)
1466 diff_surfaces (surface,
1467 window->last_surface);
1468 broadway_output_put_rgba (server->output, window->id, 0, 0,
1469 cairo_image_surface_get_width (window->last_surface),
1470 cairo_image_surface_get_height (window->last_surface),
1471 cairo_image_surface_get_stride (window->last_surface),
1472 cairo_image_surface_get_data (window->last_surface));
1476 window->last_synced = TRUE;
1477 broadway_output_put_rgb (server->output, window->id, 0, 0,
1478 cairo_image_surface_get_width (surface),
1479 cairo_image_surface_get_height (surface),
1480 cairo_image_surface_get_stride (surface),
1481 cairo_image_surface_get_data (surface));
1484 broadway_output_surface_flush (server->output, window->id);
1487 cr = cairo_create (window->last_surface);
1488 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1489 cairo_set_source_surface (cr, surface, 0, 0);
1495 _gdk_broadway_server_window_move_resize (GdkBroadwayServer *server,
1502 BroadwayWindow *window;
1503 gboolean with_move, with_resize;
1504 gboolean sent = FALSE;
1507 window = g_hash_table_lookup (server->id_ht,
1508 GINT_TO_POINTER (id));
1512 with_move = x != window->x || y != window->y;
1513 with_resize = width != window->width || height != window->height;
1516 window->width = width;
1517 window->height = height;
1519 if (with_resize && window->last_surface != NULL)
1521 cairo_surface_t *old;
1523 old = window->last_surface;
1525 window->last_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
1529 cr = cairo_create (window->last_surface);
1530 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1531 cairo_set_source_surface (cr, old, 0, 0);
1535 cairo_surface_destroy (old);
1538 if (server->output != NULL)
1540 broadway_output_move_resize_surface (server->output,
1542 with_move, window->x, window->y,
1543 with_resize, window->width, window->height);
1551 _gdk_broadway_server_grab_pointer (GdkBroadwayServer *server,
1553 gboolean owner_events,
1557 if (server->pointer_grab_window_id != -1 &&
1558 time_ != 0 && server->pointer_grab_time > time_)
1559 return GDK_GRAB_ALREADY_GRABBED;
1562 time_ = server->last_seen_time;
1564 server->pointer_grab_window_id = id;
1565 server->pointer_grab_owner_events = owner_events;
1566 server->pointer_grab_time = time_;
1570 broadway_output_grab_pointer (server->output,
1573 _gdk_broadway_server_flush (server);
1576 /* TODO: What about toplevel grab events if we're not connected? */
1578 return GDK_GRAB_SUCCESS;
1582 _gdk_broadway_server_ungrab_pointer (GdkBroadwayServer *server,
1587 if (server->pointer_grab_window_id != -1 &&
1588 time_ != 0 && server->pointer_grab_time > time_)
1591 /* TODO: What about toplevel grab events if we're not connected? */
1595 serial = broadway_output_ungrab_pointer (server->output);
1596 _gdk_broadway_server_flush (server);
1600 serial = server->saved_serial;
1603 server->pointer_grab_window_id = -1;
1609 _gdk_broadway_server_new_window (GdkBroadwayServer *server,
1616 BroadwayWindow *window;
1618 window = g_new0 (BroadwayWindow, 1);
1619 window->id = server->id_counter++;
1622 window->width = width;
1623 window->height = height;
1624 window->is_temp = is_temp;
1626 g_hash_table_insert (server->id_ht,
1627 GINT_TO_POINTER (window->id),
1630 server->toplevels = g_list_prepend (server->toplevels, window);
1633 broadway_output_new_surface (server->output,
1645 _gdk_broadway_server_resync_windows (GdkBroadwayServer *server)
1649 if (server->output == NULL)
1652 /* First create all windows */
1653 for (l = server->toplevels; l != NULL; l = l->next)
1655 BroadwayWindow *window = l->data;
1657 if (window->id == 0)
1658 continue; /* Skip root */
1660 window->last_synced = FALSE;
1661 broadway_output_new_surface (server->output,
1670 /* Then do everything that may reference other windows */
1671 for (l = server->toplevels; l != NULL; l = l->next)
1673 BroadwayWindow *window = l->data;
1675 if (window->id == 0)
1676 continue; /* Skip root */
1678 if (window->transient_for != -1)
1679 broadway_output_set_transient_for (server->output, window->id, window->transient_for);
1680 if (window->visible)
1682 broadway_output_show_surface (server->output, window->id);
1684 if (window->last_surface != NULL)
1686 window->last_synced = TRUE;
1687 broadway_output_put_rgb (server->output, window->id, 0, 0,
1688 cairo_image_surface_get_width (window->last_surface),
1689 cairo_image_surface_get_height (window->last_surface),
1690 cairo_image_surface_get_stride (window->last_surface),
1691 cairo_image_surface_get_data (window->last_surface));
1693 broadway_output_surface_flush (server->output, window->id);
1697 _gdk_broadway_server_flush (server);