11 #include <gio/gunixsocketaddress.h>
13 #include "broadway-server.h"
15 BroadwayServer *server;
18 static guint32 client_id_count = 1;
22 * Broadway tracks serials for all clients primarily to get the right behaviour wrt
23 * grabs. Each request the client sends gets an increasing per-client serial number, starting
24 * at 1. Thus, the client can now when a mouse event is seen whether the mouse event was
25 * sent before or after the server saw the grab request from the client (as this affects how
26 * the event is handled).
28 * There is only a single stream of increasing serials sent from the daemon to the web browser
29 * though, called "daemon serials", so we need to map back from the daemon serials to the client
30 * serials when we send an event to a client. So, each client keeps track of the mappings
31 * between its serials and daemon serials for any outstanding requests.
33 * There is some additional complexity in that there may be multiple consecutive web browser
34 * sessions, so we need to keep track of the last daemon serial used inbetween each web client
35 * connection so that the daemon serials can be strictly increasing.
39 guint32 client_serial;
40 guint32 daemon_serial;
41 } BroadwaySerialMapping;
45 GSocketConnection *connection;
46 GBufferedInputStream *in;
47 GSList *serial_mappings;
49 guint disconnect_idle;
53 client_free (BroadwayClient *client)
55 g_assert (client->windows == NULL);
56 g_assert (client->disconnect_idle == 0);
57 clients = g_list_remove (clients, client);
58 g_object_unref (client->connection);
59 g_object_unref (client->in);
60 g_slist_free_full (client->serial_mappings, g_free);
65 client_disconnected (BroadwayClient *client)
69 if (client->disconnect_idle != 0)
71 g_source_remove (client->disconnect_idle);
72 client->disconnect_idle = 0;
75 for (l = client->windows; l != NULL; l = l->next)
76 broadway_server_destroy_window (server,
77 GPOINTER_TO_UINT (l->data));
78 g_list_free (client->windows);
79 client->windows = NULL;
81 broadway_server_flush (server);
87 disconnect_idle_cb (BroadwayClient *client)
89 client->disconnect_idle = 0;
90 client_disconnected (client);
91 return G_SOURCE_REMOVE;
95 client_disconnect_in_idle (BroadwayClient *client)
97 if (client->disconnect_idle == 0)
98 client->disconnect_idle =
99 g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)disconnect_idle_cb, client, NULL);
103 send_reply (BroadwayClient *client,
104 BroadwayRequest *request,
105 BroadwayReply *reply,
109 GOutputStream *output;
111 reply->base.size = size;
112 reply->base.in_reply_to = request ? request->base.serial : 0;
113 reply->base.type = type;
115 output = g_io_stream_get_output_stream (G_IO_STREAM (client->connection));
116 if (!g_output_stream_write_all (output, reply, size, NULL, NULL, NULL))
118 g_printerr ("can't write to client");
119 client_disconnect_in_idle (client);
123 static cairo_region_t *
124 region_from_rects (BroadwayRect *rects, int n_rects)
126 cairo_region_t *region;
127 cairo_rectangle_int_t *cairo_rects;
130 cairo_rects = g_new (cairo_rectangle_int_t, n_rects);
131 for (i = 0; i < n_rects; i++)
133 cairo_rects[i].x = rects[i].x;
134 cairo_rects[i].y = rects[i].y;
135 cairo_rects[i].width = rects[i].width;
136 cairo_rects[i].height = rects[i].height;
138 region = cairo_region_create_rectangles (cairo_rects, n_rects);
139 g_free (cairo_rects);
143 static const cairo_user_data_key_t shm_cairo_key;
151 shm_data_unmap (void *_data)
153 ShmSurfaceData *data = _data;
154 munmap (data->data, data->data_size);
159 open_surface (char *name, int width, int height)
161 ShmSurfaceData *data;
162 cairo_surface_t *surface;
167 size = width * height * sizeof (guint32);
169 fd = shm_open(name, O_RDONLY, 0600);
172 perror ("Failed to shm_open");
176 ptr = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
182 data = g_new0 (ShmSurfaceData, 1);
185 data->data_size = size;
187 surface = cairo_image_surface_create_for_data ((guchar *)data->data,
190 width * sizeof (guint32));
191 g_assert (surface != NULL);
193 cairo_surface_set_user_data (surface, &shm_cairo_key,
194 data, shm_data_unmap);
200 add_client_serial_mapping (BroadwayClient *client,
201 guint32 client_serial,
202 guint32 daemon_serial)
204 BroadwaySerialMapping *map;
207 last = g_slist_last (client->serial_mappings);
213 /* If we have no web client, don't grow forever */
214 if (map->daemon_serial == daemon_serial)
216 map->client_serial = client_serial;
221 map = g_new0 (BroadwaySerialMapping, 1);
222 map->client_serial = client_serial;
223 map->daemon_serial = daemon_serial;
224 client->serial_mappings = g_slist_append (client->serial_mappings, map);
227 /* Returns the latest seen client serial at the time we sent
228 a daemon request to the browser with a specific daemon serial */
230 get_client_serial (BroadwayClient *client, guint32 daemon_serial)
232 BroadwaySerialMapping *map;
234 guint32 client_serial = 0;
237 for (l = client->serial_mappings; l != NULL; l = l->next)
241 if (map->daemon_serial <= daemon_serial)
244 client_serial = map->client_serial;
250 /* Remove mappings before the found one, they will never more be used */
251 while (found != NULL &&
252 client->serial_mappings != found)
254 g_free (client->serial_mappings->data);
255 client->serial_mappings =
256 g_slist_delete_link (client->serial_mappings, client->serial_mappings);
259 return client_serial;
264 client_handle_request (BroadwayClient *client,
265 BroadwayRequest *request)
267 BroadwayReplyNewWindow reply_new_window;
268 BroadwayReplySync reply_sync;
269 BroadwayReplyQueryMouse reply_query_mouse;
270 BroadwayReplyGrabPointer reply_grab_pointer;
271 BroadwayReplyUngrabPointer reply_ungrab_pointer;
272 cairo_region_t *area;
273 cairo_surface_t *surface;
274 guint32 before_serial, now_serial;
276 before_serial = broadway_server_get_next_serial (server);
278 switch (request->base.type)
280 case BROADWAY_REQUEST_NEW_WINDOW:
281 reply_new_window.id =
282 broadway_server_new_window (server,
283 request->new_window.x,
284 request->new_window.y,
285 request->new_window.width,
286 request->new_window.height,
287 request->new_window.is_temp);
289 g_list_prepend (client->windows,
290 GUINT_TO_POINTER (reply_new_window.id));
292 send_reply (client, request, (BroadwayReply *)&reply_new_window, sizeof (reply_new_window),
293 BROADWAY_REPLY_NEW_WINDOW);
295 case BROADWAY_REQUEST_FLUSH:
296 broadway_server_flush (server);
298 case BROADWAY_REQUEST_SYNC:
299 broadway_server_flush (server);
300 send_reply (client, request, (BroadwayReply *)&reply_sync, sizeof (reply_sync),
301 BROADWAY_REPLY_SYNC);
303 case BROADWAY_REQUEST_QUERY_MOUSE:
304 broadway_server_query_mouse (server,
305 &reply_query_mouse.toplevel,
306 &reply_query_mouse.root_x,
307 &reply_query_mouse.root_y,
308 &reply_query_mouse.mask);
309 send_reply (client, request, (BroadwayReply *)&reply_query_mouse, sizeof (reply_query_mouse),
310 BROADWAY_REPLY_QUERY_MOUSE);
312 case BROADWAY_REQUEST_DESTROY_WINDOW:
314 g_list_remove (client->windows,
315 GUINT_TO_POINTER (request->destroy_window.id));
316 broadway_server_destroy_window (server, request->destroy_window.id);
318 case BROADWAY_REQUEST_SHOW_WINDOW:
319 broadway_server_window_show (server, request->show_window.id);
321 case BROADWAY_REQUEST_HIDE_WINDOW:
322 broadway_server_window_hide (server, request->hide_window.id);
324 case BROADWAY_REQUEST_SET_TRANSIENT_FOR:
325 broadway_server_window_set_transient_for (server,
326 request->set_transient_for.id,
327 request->set_transient_for.parent);
329 case BROADWAY_REQUEST_TRANSLATE:
330 area = region_from_rects (request->translate.rects,
331 request->translate.n_rects);
332 broadway_server_window_translate (server,
333 request->translate.id,
335 request->translate.dx,
336 request->translate.dy);
337 cairo_region_destroy (area);
339 case BROADWAY_REQUEST_UPDATE:
340 surface = open_surface (request->update.name,
341 request->update.width,
342 request->update.height);
345 broadway_server_window_update (server,
348 cairo_surface_destroy (surface);
351 case BROADWAY_REQUEST_MOVE_RESIZE:
352 broadway_server_window_move_resize (server,
353 request->move_resize.id,
354 request->move_resize.with_move,
355 request->move_resize.x,
356 request->move_resize.y,
357 request->move_resize.width,
358 request->move_resize.height);
360 case BROADWAY_REQUEST_GRAB_POINTER:
361 reply_grab_pointer.status =
362 broadway_server_grab_pointer (server,
364 request->grab_pointer.id,
365 request->grab_pointer.owner_events,
366 request->grab_pointer.event_mask,
367 request->grab_pointer.time_);
368 send_reply (client, request, (BroadwayReply *)&reply_grab_pointer, sizeof (reply_grab_pointer),
369 BROADWAY_REPLY_GRAB_POINTER);
371 case BROADWAY_REQUEST_UNGRAB_POINTER:
372 reply_ungrab_pointer.status =
373 broadway_server_ungrab_pointer (server,
374 request->ungrab_pointer.time_);
375 send_reply (client, request, (BroadwayReply *)&reply_ungrab_pointer, sizeof (reply_ungrab_pointer),
376 BROADWAY_REPLY_UNGRAB_POINTER);
379 g_warning ("Unknown request of type %d\n", request->base.type);
383 now_serial = broadway_server_get_next_serial (server);
385 /* If we sent a new output request, map that this client serial to that, otherwise
386 update old mapping for previously sent daemon serial */
387 if (now_serial != before_serial)
388 add_client_serial_mapping (client,
389 request->base.serial,
392 add_client_serial_mapping (client,
393 request->base.serial,
398 client_fill_cb (GObject *source_object,
399 GAsyncResult *result,
402 BroadwayClient *client = user_data;
405 res = g_buffered_input_stream_fill_finish (client->in, result, NULL);
410 gsize count, remaining;
412 BroadwayRequest request;
414 buffer = (guint8 *)g_buffered_input_stream_peek_buffer (client->in, &count);
417 while (remaining >= sizeof (guint32))
419 memcpy (&size, buffer, sizeof (guint32));
421 if (size <= remaining)
423 g_assert (size >= sizeof (BroadwayRequestBase));
424 g_assert (size <= sizeof (BroadwayRequest));
426 memcpy (&request, buffer, size);
427 client_handle_request (client, &request);
434 /* This is guaranteed not to block */
435 g_input_stream_skip (G_INPUT_STREAM (client->in), count - remaining, NULL, NULL);
437 g_buffered_input_stream_fill_async (client->in,
441 client_fill_cb, client);
445 client_disconnected (client);
451 incoming_client (GSocketService *service,
452 GSocketConnection *connection,
453 GObject *source_object)
455 BroadwayClient *client;
457 BroadwayInputMsg ev = { {0} };
459 client = g_new0 (BroadwayClient, 1);
460 client->id = client_id_count++;
461 client->connection = g_object_ref (connection);
463 input = g_io_stream_get_input_stream (G_IO_STREAM (client->connection));
464 client->in = (GBufferedInputStream *)g_buffered_input_stream_new (input);
466 clients = g_list_prepend (clients, client);
468 g_buffered_input_stream_fill_async (client->in,
472 client_fill_cb, client);
474 /* Send initial resize notify */
475 ev.base.type = BROADWAY_EVENT_SCREEN_SIZE_CHANGED;
476 ev.base.serial = broadway_server_get_next_serial (server) - 1;
477 ev.base.time = broadway_server_get_last_seen_time (server);
478 broadway_server_get_screen_size (server,
479 &ev.screen_resize_notify.width,
480 &ev.screen_resize_notify.height);
482 broadway_events_got_input (&ev,
490 main (int argc, char *argv[])
492 GError *error = NULL;
493 GOptionContext *context;
495 GSocketAddress *address;
496 GSocketService *listener;
498 char *http_address = NULL;
501 const GOptionEntry entries[] = {
502 { "port", 'p', 0, G_OPTION_ARG_INT, &http_port, "Httpd port", "PORT" },
503 { "address", 'a', 0, G_OPTION_ARG_STRING, &http_address, "Ip address to bind to ", "ADDRESS" },
507 context = g_option_context_new ("[:DISPLAY] - broadway display daemon");
508 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
509 if (!g_option_context_parse (context, &argc, &argv, &error))
511 g_printerr ("option parsing failed: %s\n", error->message);
519 g_printerr ("Usage broadwayd [:DISPLAY]\n");
522 display = strtol(argv[1]+1, NULL, 10);
525 g_printerr ("Failed to parse display num %s\n", argv[1]);
531 http_port = 8080 + (display - 1);
533 server = broadway_server_new (http_address, http_port, &error);
536 g_printerr ("%s\n", error->message);
540 base = g_strdup_printf ("broadway%d.socket", display);
541 path = g_build_filename (g_get_user_runtime_dir (), base, NULL);
543 g_print ("Listening on %s\n", path);
544 address = g_unix_socket_address_new_with_type (path, -1,
545 G_UNIX_SOCKET_ADDRESS_ABSTRACT);
548 listener = g_socket_service_new ();
549 if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
551 G_SOCKET_TYPE_STREAM,
552 G_SOCKET_PROTOCOL_DEFAULT,
557 g_printerr ("Can't listen: %s\n", error->message);
560 g_object_unref (address);
562 g_signal_connect (listener, "incoming", G_CALLBACK (incoming_client), NULL);
564 g_socket_service_start (G_SOCKET_SERVICE (listener));
566 loop = g_main_loop_new (NULL, FALSE);
567 g_main_loop_run (loop);
573 get_event_size (int type)
577 case BROADWAY_EVENT_ENTER:
578 case BROADWAY_EVENT_LEAVE:
579 return sizeof (BroadwayInputCrossingMsg);
580 case BROADWAY_EVENT_POINTER_MOVE:
581 return sizeof (BroadwayInputPointerMsg);
582 case BROADWAY_EVENT_BUTTON_PRESS:
583 case BROADWAY_EVENT_BUTTON_RELEASE:
584 return sizeof (BroadwayInputButtonMsg);
585 case BROADWAY_EVENT_SCROLL:
586 return sizeof (BroadwayInputScrollMsg);
587 case BROADWAY_EVENT_KEY_PRESS:
588 case BROADWAY_EVENT_KEY_RELEASE:
589 return sizeof (BroadwayInputKeyMsg);
590 case BROADWAY_EVENT_GRAB_NOTIFY:
591 case BROADWAY_EVENT_UNGRAB_NOTIFY:
592 return sizeof (BroadwayInputGrabReply);
593 case BROADWAY_EVENT_CONFIGURE_NOTIFY:
594 return sizeof (BroadwayInputConfigureNotify);
595 case BROADWAY_EVENT_DELETE_NOTIFY:
596 return sizeof (BroadwayInputDeleteNotify);
597 case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
598 return sizeof (BroadwayInputScreenResizeNotify);
600 g_assert_not_reached ();
606 broadway_events_got_input (BroadwayInputMsg *message,
610 BroadwayReplyEvent reply_event;
612 guint32 daemon_serial;
614 size = get_event_size (message->base.type);
615 g_assert (sizeof (BroadwayReplyBase) + size <= sizeof (BroadwayReplyEvent));
617 memset (&reply_event, 0, sizeof (BroadwayReplyEvent));
618 daemon_serial = message->base.serial;
620 memcpy (&reply_event.msg, message, size);
622 for (l = clients; l != NULL; l = l->next)
624 BroadwayClient *client = l->data;
626 if (client_id == -1 ||
627 client->id == client_id)
629 reply_event.msg.base.serial = get_client_serial (client, daemon_serial);
631 send_reply (client, NULL, (BroadwayReply *)&reply_event,
632 G_STRUCT_OFFSET (BroadwayReplyEvent, msg) + size,
633 BROADWAY_REPLY_EVENT);