]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadwayd.c
win32: Fix build
[~andy/gtk] / gdk / broadway / broadwayd.c
1 #include "config.h"
2 #include <string.h>
3 #include <sys/mman.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8
9 #include <glib.h>
10 #include <gio/gio.h>
11 #include <gio/gunixsocketaddress.h>
12
13 #include "broadway-server.h"
14
15 BroadwayServer *server;
16 GList *clients;
17
18 static guint32 client_id_count = 1;
19
20 /* Serials:
21  *
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).
27  *
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.
32  *
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.
36  */
37
38 typedef struct {
39   guint32 client_serial;
40   guint32 daemon_serial;
41 } BroadwaySerialMapping;
42
43 typedef struct  {
44   guint32 id;
45   GSocketConnection *connection;
46   GBufferedInputStream *in;
47   GSList *serial_mappings;
48   GList *windows;
49   guint disconnect_idle;
50 } BroadwayClient;
51
52 static void
53 client_free (BroadwayClient *client)
54 {
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);
61   g_free (client);
62 }
63
64 static void
65 client_disconnected (BroadwayClient *client)
66 {
67   GList *l;
68
69   if (client->disconnect_idle != 0)
70     {
71       g_source_remove (client->disconnect_idle);
72       client->disconnect_idle = 0;
73     }
74
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;
80
81   broadway_server_flush (server);
82
83   client_free (client);
84 }
85
86 static gboolean
87 disconnect_idle_cb (BroadwayClient *client)
88 {
89   client->disconnect_idle = 0;
90   client_disconnected (client);
91   return G_SOURCE_REMOVE;
92 }
93
94 static void
95 client_disconnect_in_idle (BroadwayClient *client)
96 {
97   if (client->disconnect_idle == 0)
98     client->disconnect_idle =
99       g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)disconnect_idle_cb, client, NULL);
100 }
101
102 static void
103 send_reply (BroadwayClient *client,
104             BroadwayRequest *request,
105             BroadwayReply *reply,
106             gsize size,
107             guint32 type)
108 {
109   GOutputStream *output;
110
111   reply->base.size = size;
112   reply->base.in_reply_to = request ? request->base.serial : 0;
113   reply->base.type = type;
114
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))
117     {
118       g_printerr ("can't write to client");
119       client_disconnect_in_idle (client);
120     }
121 }
122
123 static cairo_region_t *
124 region_from_rects (BroadwayRect *rects, int n_rects)
125 {
126   cairo_region_t *region;
127   cairo_rectangle_int_t *cairo_rects;
128   int i;
129   
130   cairo_rects = g_new (cairo_rectangle_int_t, n_rects);
131   for (i = 0; i < n_rects; i++)
132     {
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;
137     }
138   region = cairo_region_create_rectangles (cairo_rects, n_rects);
139   g_free (cairo_rects);
140   return region;
141 }
142
143 static const cairo_user_data_key_t shm_cairo_key;
144
145 typedef struct {
146   void *data;
147   gsize data_size;
148 } ShmSurfaceData;
149
150 static void
151 shm_data_unmap (void *_data)
152 {
153   ShmSurfaceData *data = _data;
154   munmap (data->data, data->data_size);
155   g_free (data);
156 }
157
158 cairo_surface_t *
159 open_surface (char *name, int width, int height)
160 {
161   ShmSurfaceData *data;
162   cairo_surface_t *surface;
163   gsize size;
164   void *ptr;
165   int fd;
166
167   size = width * height * sizeof (guint32);
168
169   fd = shm_open(name, O_RDONLY, 0600);
170   if (fd == -1)
171     {
172       perror ("Failed to shm_open");
173       return NULL;
174     }
175
176   ptr = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0); 
177   (void) close(fd);
178
179   if (ptr == NULL)
180     return NULL;
181
182   data = g_new0 (ShmSurfaceData, 1);
183
184   data->data = ptr;
185   data->data_size = size;
186
187   surface = cairo_image_surface_create_for_data ((guchar *)data->data,
188                                                  CAIRO_FORMAT_RGB24,
189                                                  width, height,
190                                                  width * sizeof (guint32));
191   g_assert (surface != NULL);
192   
193   cairo_surface_set_user_data (surface, &shm_cairo_key,
194                                data, shm_data_unmap);
195
196   return surface;
197 }
198
199 void
200 add_client_serial_mapping (BroadwayClient *client,
201                            guint32 client_serial,
202                            guint32 daemon_serial)
203 {
204   BroadwaySerialMapping *map;
205   GSList *last;
206
207   last = g_slist_last (client->serial_mappings);
208
209   if (last != NULL)
210     {
211       map = last->data;
212
213       /* If we have no web client, don't grow forever */
214       if (map->daemon_serial == daemon_serial)
215         {
216           map->client_serial = client_serial;
217           return;
218         }
219     }
220
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);
225 }
226
227 /* Returns the latest seen client serial at the time we sent
228    a daemon request to the browser with a specific daemon serial */
229 guint32
230 get_client_serial (BroadwayClient *client, guint32 daemon_serial)
231 {
232   BroadwaySerialMapping *map;
233   GSList *l, *found;
234   guint32 client_serial = 0;
235
236   found = NULL;
237   for (l = client->serial_mappings;  l != NULL; l = l->next)
238     {
239       map = l->data;
240
241       if (map->daemon_serial <= daemon_serial)
242         {
243           found = l;
244           client_serial = map->client_serial;
245         }
246       else
247         break;
248     }
249
250   /* Remove mappings before the found one, they will never more be used */
251   while (found != NULL &&
252          client->serial_mappings != found)
253     {
254       g_free (client->serial_mappings->data);
255       client->serial_mappings =
256         g_slist_delete_link (client->serial_mappings, client->serial_mappings);
257     }
258
259   return client_serial;
260 }
261
262
263 static void
264 client_handle_request (BroadwayClient *client,
265                        BroadwayRequest *request)
266 {
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;
275
276   before_serial = broadway_server_get_next_serial (server);
277
278   switch (request->base.type)
279     {
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);
288       client->windows =
289         g_list_prepend (client->windows,
290                         GUINT_TO_POINTER (reply_new_window.id));
291
292       send_reply (client, request, (BroadwayReply *)&reply_new_window, sizeof (reply_new_window),
293                   BROADWAY_REPLY_NEW_WINDOW);
294       break;
295     case BROADWAY_REQUEST_FLUSH:
296       broadway_server_flush (server);
297       break;
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);
302       break;
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);
311       break;
312     case BROADWAY_REQUEST_DESTROY_WINDOW:
313       client->windows =
314         g_list_remove (client->windows,
315                        GUINT_TO_POINTER (request->destroy_window.id));
316       broadway_server_destroy_window (server, request->destroy_window.id);
317       break;
318     case BROADWAY_REQUEST_SHOW_WINDOW:
319       broadway_server_window_show (server, request->show_window.id);
320       break;
321     case BROADWAY_REQUEST_HIDE_WINDOW:
322       broadway_server_window_hide (server, request->hide_window.id);
323       break;
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);
328       break;
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,
334                                         area,
335                                         request->translate.dx,
336                                         request->translate.dy);
337       cairo_region_destroy (area);
338       break;
339     case BROADWAY_REQUEST_UPDATE:
340       surface = open_surface (request->update.name,
341                               request->update.width,
342                               request->update.height);
343       if (surface != NULL)
344         {
345           broadway_server_window_update (server,
346                                          request->update.id,
347                                          surface);
348           cairo_surface_destroy (surface);
349         }
350       break;
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);
359       break;
360     case BROADWAY_REQUEST_GRAB_POINTER:
361       reply_grab_pointer.status =
362         broadway_server_grab_pointer (server,
363                                       client->id,
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);
370       break;
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);
377       break;
378     default:
379       g_warning ("Unknown request of type %d\n", request->base.type);
380     }
381
382
383   now_serial = broadway_server_get_next_serial (server);
384
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,
390                                before_serial);
391   else
392     add_client_serial_mapping (client,
393                                request->base.serial,
394                                before_serial - 1);
395 }
396
397 static void
398 client_fill_cb (GObject *source_object,
399                 GAsyncResult *result,
400                 gpointer user_data)
401 {
402   BroadwayClient *client = user_data;
403   gssize res;
404
405   res = g_buffered_input_stream_fill_finish (client->in, result, NULL);
406   
407   if (res > 0)
408     {
409       guint32 size;
410       gsize count, remaining;
411       guint8 *buffer;
412       BroadwayRequest request;
413
414       buffer = (guint8 *)g_buffered_input_stream_peek_buffer (client->in, &count);
415
416       remaining = count;
417       while (remaining >= sizeof (guint32))
418         {
419           memcpy (&size, buffer, sizeof (guint32));
420           
421           if (size <= remaining)
422             {
423               g_assert (size >= sizeof (BroadwayRequestBase));
424               g_assert (size <= sizeof (BroadwayRequest));
425
426               memcpy (&request, buffer, size);
427               client_handle_request (client, &request);
428
429               remaining -= size;
430               buffer += size;
431             }
432         }
433       
434       /* This is guaranteed not to block */
435       g_input_stream_skip (G_INPUT_STREAM (client->in), count - remaining, NULL, NULL);
436       
437       g_buffered_input_stream_fill_async (client->in,
438                                           4*1024,
439                                           0,
440                                           NULL,
441                                           client_fill_cb, client);
442     }
443   else
444     {
445       client_disconnected (client);
446     }
447 }
448
449
450 static gboolean
451 incoming_client (GSocketService    *service,
452                  GSocketConnection *connection,
453                  GObject           *source_object)
454 {
455   BroadwayClient *client;
456   GInputStream *input;
457   BroadwayInputMsg ev = { {0} };
458
459   client = g_new0 (BroadwayClient, 1);
460   client->id = client_id_count++;
461   client->connection = g_object_ref (connection);
462
463   input = g_io_stream_get_input_stream (G_IO_STREAM (client->connection));
464   client->in = (GBufferedInputStream *)g_buffered_input_stream_new (input);
465
466   clients = g_list_prepend (clients, client);
467
468   g_buffered_input_stream_fill_async (client->in,
469                                       4*1024,
470                                       0,
471                                       NULL,
472                                       client_fill_cb, client);
473
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);
481
482   broadway_events_got_input (&ev,
483                              client->id);
484
485   return TRUE;
486 }
487
488
489 int
490 main (int argc, char *argv[])
491 {
492   GError *error = NULL;
493   GOptionContext *context;
494   GMainLoop *loop;
495   GSocketAddress *address;
496   GSocketService *listener;
497   char *path, *base;
498   char *http_address = NULL;
499   int http_port = 0;
500   int display = 1;
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" },
504     { NULL }
505   };
506
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))
510     {
511       g_printerr ("option parsing failed: %s\n", error->message);
512       exit (1);
513     }
514
515   if (argc > 1)
516     {
517       if (*argv[1] != ':')
518         {
519           g_printerr ("Usage broadwayd [:DISPLAY]\n");
520           exit (1);
521         }
522       display = strtol(argv[1]+1, NULL, 10);
523       if (display == 0)
524         {
525           g_printerr ("Failed to parse display num %s\n", argv[1]);
526           exit (1);
527         }
528     }
529
530   if (http_port == 0)
531     http_port = 8080 + (display - 1);
532
533   server = broadway_server_new (http_address, http_port, &error);
534   if (server == NULL)
535     {
536       g_printerr ("%s\n", error->message);
537       return 1;
538     }
539
540   base = g_strdup_printf ("broadway%d.socket", display);
541   path = g_build_filename (g_get_user_runtime_dir (), base, NULL);
542   g_free (base);
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);
546   g_free (path);
547
548   listener = g_socket_service_new ();
549   if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
550                                       address,
551                                       G_SOCKET_TYPE_STREAM,
552                                       G_SOCKET_PROTOCOL_DEFAULT,
553                                       G_OBJECT (server),
554                                       NULL,
555                                       &error))
556     {
557       g_printerr ("Can't listen: %s\n", error->message);
558       return 1;
559     }
560   g_object_unref (address);
561
562   g_signal_connect (listener, "incoming", G_CALLBACK (incoming_client), NULL);
563
564   g_socket_service_start (G_SOCKET_SERVICE (listener));
565
566   loop = g_main_loop_new (NULL, FALSE);
567   g_main_loop_run (loop);
568   
569   return 0;
570 }
571
572 static gsize
573 get_event_size (int type)
574 {
575   switch (type)
576     {
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);
599     default:
600       g_assert_not_reached ();
601     }
602   return 0;
603 }
604
605 void
606 broadway_events_got_input (BroadwayInputMsg *message,
607                            gint32 client_id)
608 {
609   GList *l;
610   BroadwayReplyEvent reply_event;
611   gsize size;
612   guint32 daemon_serial;
613
614   size = get_event_size (message->base.type);
615   g_assert (sizeof (BroadwayReplyBase) + size <= sizeof (BroadwayReplyEvent));
616
617   memset (&reply_event, 0, sizeof (BroadwayReplyEvent));
618   daemon_serial = message->base.serial;
619
620   memcpy (&reply_event.msg, message, size);
621
622   for (l = clients; l != NULL; l = l->next)
623     {
624       BroadwayClient *client = l->data;
625
626       if (client_id == -1 ||
627           client->id == client_id)
628         {
629           reply_event.msg.base.serial = get_client_serial (client, daemon_serial);
630
631           send_reply (client, NULL, (BroadwayReply *)&reply_event,
632                       G_STRUCT_OFFSET (BroadwayReplyEvent, msg) + size,
633                       BROADWAY_REPLY_EVENT);
634         }
635     }
636 }