]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadwayd.c
broadway: Add TODO.broadway
[~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 typedef struct  {
21   guint32 id;
22   GSocketConnection *connection;
23   GBufferedInputStream *in;
24   guint32 last_seen_serial;
25   GList *windows;
26   guint disconnect_idle;
27 } BroadwayClient;
28
29 static void
30 client_free (BroadwayClient *client)
31 {
32   g_assert (client->windows == NULL);
33   g_assert (client->disconnect_idle == 0);
34   clients = g_list_remove (clients, client);
35   g_object_unref (client->connection);
36   g_object_unref (client->in);
37   g_free (client);
38 }
39
40 static void
41 client_disconnected (BroadwayClient *client)
42 {
43   GList *l;
44
45   if (client->disconnect_idle != 0)
46     {
47       g_source_remove (client->disconnect_idle);
48       client->disconnect_idle = 0;
49     }
50
51   for (l = client->windows; l != NULL; l = l->next)
52     broadway_server_destroy_window (server,
53                                     GPOINTER_TO_UINT (l->data));
54   g_list_free (client->windows);
55   client->windows = NULL;
56
57   broadway_server_flush (server);
58
59   client_free (client);
60 }
61
62 static gboolean
63 disconnect_idle_cb (BroadwayClient *client)
64 {
65   client->disconnect_idle = 0;
66   client_disconnected (client);
67   return G_SOURCE_REMOVE;
68 }
69
70 static void
71 client_disconnect_in_idle (BroadwayClient *client)
72 {
73   if (client->disconnect_idle == 0)
74     client->disconnect_idle =
75       g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)disconnect_idle_cb, client, NULL);
76 }
77
78 static void
79 send_reply (BroadwayClient *client,
80             BroadwayRequest *request,
81             BroadwayReply *reply,
82             gsize size,
83             guint32 type)
84 {
85   GOutputStream *output;
86
87   reply->base.size = size;
88   reply->base.last_serial = client->last_seen_serial;
89   reply->base.in_reply_to = request ? request->base.serial : 0;
90   reply->base.type = type;
91
92   output = g_io_stream_get_output_stream (G_IO_STREAM (client->connection));
93   if (!g_output_stream_write_all (output, reply, size, NULL, NULL, NULL))
94     {
95       g_printerr ("can't write to client");
96       client_disconnect_in_idle (client);
97     }
98 }
99
100 static cairo_region_t *
101 region_from_rects (BroadwayRect *rects, int n_rects)
102 {
103   cairo_region_t *region;
104   cairo_rectangle_int_t *cairo_rects;
105   int i;
106   
107   cairo_rects = g_new (cairo_rectangle_int_t, n_rects);
108   for (i = 0; i < n_rects; i++)
109     {
110       cairo_rects[i].x = rects[i].x;
111       cairo_rects[i].y = rects[i].y;
112       cairo_rects[i].width = rects[i].width;
113       cairo_rects[i].height = rects[i].height;
114     }
115   region = cairo_region_create_rectangles (cairo_rects, n_rects);
116   g_free (cairo_rects);
117   return region;
118 }
119
120 static const cairo_user_data_key_t shm_cairo_key;
121
122 typedef struct {
123   void *data;
124   gsize data_size;
125 } ShmSurfaceData;
126
127 static void
128 shm_data_unmap (void *_data)
129 {
130   ShmSurfaceData *data = _data;
131   munmap (data->data, data->data_size);
132   g_free (data);
133 }
134
135 cairo_surface_t *
136 open_surface (char *name, int width, int height)
137 {
138   ShmSurfaceData *data;
139   cairo_surface_t *surface;
140   gsize size;
141   void *ptr;
142   int fd;
143
144   size = width * height * sizeof (guint32);
145
146   fd = shm_open(name, O_RDONLY, 0600);
147   if (fd == -1)
148     {
149       perror ("Failed to shm_open");
150       return NULL;
151     }
152
153   ptr = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0); 
154   (void) close(fd);
155
156   if (ptr == NULL)
157     return NULL;
158
159   data = g_new0 (ShmSurfaceData, 1);
160
161   data->data = ptr;
162   data->data_size = size;
163
164   surface = cairo_image_surface_create_for_data ((guchar *)data->data,
165                                                  CAIRO_FORMAT_RGB24,
166                                                  width, height,
167                                                  width * sizeof (guint32));
168   g_assert (surface != NULL);
169   
170   cairo_surface_set_user_data (surface, &shm_cairo_key,
171                                data, shm_data_unmap);
172
173   return surface;
174 }
175
176 static void
177 client_handle_request (BroadwayClient *client,
178                        BroadwayRequest *request)
179 {
180   BroadwayReplyNewWindow reply_new_window;
181   BroadwayReplySync reply_sync;
182   BroadwayReplyQueryMouse reply_query_mouse;
183   BroadwayReplyGrabPointer reply_grab_pointer;
184   BroadwayReplyUngrabPointer reply_ungrab_pointer;
185   cairo_region_t *area;
186   cairo_surface_t *surface;
187
188   client->last_seen_serial = request->base.serial;
189
190   switch (request->base.type)
191     {
192     case BROADWAY_REQUEST_NEW_WINDOW:
193       reply_new_window.id =
194         broadway_server_new_window (server,
195                                     request->new_window.x,
196                                     request->new_window.y,
197                                     request->new_window.width,
198                                     request->new_window.height,
199                                     request->new_window.is_temp);
200       client->windows =
201         g_list_prepend (client->windows,
202                         GUINT_TO_POINTER (reply_new_window.id));
203       
204       send_reply (client, request, (BroadwayReply *)&reply_new_window, sizeof (reply_new_window),
205                   BROADWAY_REPLY_NEW_WINDOW);
206       break;
207     case BROADWAY_REQUEST_FLUSH:
208       broadway_server_flush (server);
209       break;
210     case BROADWAY_REQUEST_SYNC:
211       broadway_server_flush (server);
212       send_reply (client, request, (BroadwayReply *)&reply_sync, sizeof (reply_sync),
213                   BROADWAY_REPLY_SYNC);
214       break;
215     case BROADWAY_REQUEST_QUERY_MOUSE:
216       broadway_server_query_mouse (server,
217                                    &reply_query_mouse.toplevel,
218                                    &reply_query_mouse.root_x,
219                                    &reply_query_mouse.root_y,
220                                    &reply_query_mouse.mask);
221       send_reply (client, request, (BroadwayReply *)&reply_query_mouse, sizeof (reply_query_mouse),
222                   BROADWAY_REPLY_QUERY_MOUSE);
223       break;
224     case BROADWAY_REQUEST_DESTROY_WINDOW:
225       client->windows =
226         g_list_remove (client->windows,
227                        GUINT_TO_POINTER (request->destroy_window.id));
228       broadway_server_destroy_window (server, request->destroy_window.id);
229       break;
230     case BROADWAY_REQUEST_SHOW_WINDOW:
231       broadway_server_window_show (server, request->show_window.id);
232       break;
233     case BROADWAY_REQUEST_HIDE_WINDOW:
234       broadway_server_window_hide (server, request->hide_window.id);
235       break;
236     case BROADWAY_REQUEST_SET_TRANSIENT_FOR:
237       broadway_server_window_set_transient_for (server,
238                                                 request->set_transient_for.id,
239                                                 request->set_transient_for.parent);
240       break;
241     case BROADWAY_REQUEST_TRANSLATE:
242       area = region_from_rects (request->translate.rects,
243                                 request->translate.n_rects);
244       broadway_server_window_translate (server,
245                                         request->translate.id,
246                                         area,
247                                         request->translate.dx,
248                                         request->translate.dy);
249       cairo_region_destroy (area);
250       break;
251     case BROADWAY_REQUEST_UPDATE:
252       surface = open_surface (request->update.name,
253                               request->update.width,
254                               request->update.height);
255       if (surface != NULL)
256         {
257           broadway_server_window_update (server,
258                                          request->update.id,
259                                          surface);
260           cairo_surface_destroy (surface);
261         }
262       break;
263     case BROADWAY_REQUEST_MOVE_RESIZE:
264       if (!broadway_server_window_move_resize (server,
265                                                request->move_resize.id,
266                                                request->move_resize.x,
267                                                request->move_resize.y,
268                                                request->move_resize.width,
269                                                request->move_resize.height))
270         {
271           /* TODO: Send configure request */
272         }
273       break;
274     case BROADWAY_REQUEST_GRAB_POINTER:
275       reply_grab_pointer.status =
276         broadway_server_grab_pointer (server,
277                                       client->id,
278                                       request->grab_pointer.id,
279                                       request->grab_pointer.owner_events,
280                                       request->grab_pointer.event_mask,
281                                       request->grab_pointer.time_);
282       send_reply (client, request, (BroadwayReply *)&reply_grab_pointer, sizeof (reply_grab_pointer),
283                   BROADWAY_REPLY_GRAB_POINTER);
284       break;
285     case BROADWAY_REQUEST_UNGRAB_POINTER:
286       reply_ungrab_pointer.status =
287         broadway_server_ungrab_pointer (server,
288                                         request->ungrab_pointer.time_);
289       send_reply (client, request, (BroadwayReply *)&reply_ungrab_pointer, sizeof (reply_ungrab_pointer),
290                   BROADWAY_REPLY_UNGRAB_POINTER);
291       break;
292     default:
293       g_warning ("Unknown request of type %d\n", request->base.type);
294     }
295 }
296
297 static void
298 client_fill_cb (GObject *source_object,
299                 GAsyncResult *result,
300                 gpointer user_data)
301 {
302   BroadwayClient *client = user_data;
303   gssize res;
304
305   res = g_buffered_input_stream_fill_finish (client->in, result, NULL);
306   
307   if (res > 0)
308     {
309       guint32 size;
310       gsize count, remaining;
311       guint8 *buffer;
312       BroadwayRequest request;
313
314       buffer = (guint8 *)g_buffered_input_stream_peek_buffer (client->in, &count);
315
316       remaining = count;
317       while (remaining >= sizeof (guint32))
318         {
319           memcpy (&size, buffer, sizeof (guint32));
320           
321           if (size <= remaining)
322             {
323               g_assert (size >= sizeof (BroadwayRequestBase));
324               g_assert (size <= sizeof (BroadwayRequest));
325
326               memcpy (&request, buffer, size);
327               client_handle_request (client, &request);
328
329               remaining -= size;
330               buffer += size;
331             }
332         }
333       
334       /* This is guaranteed not to block */
335       g_input_stream_skip (G_INPUT_STREAM (client->in), count - remaining, NULL, NULL);
336       
337       g_buffered_input_stream_fill_async (client->in,
338                                           4*1024,
339                                           0,
340                                           NULL,
341                                           client_fill_cb, client);
342     }
343   else
344     {
345       client_disconnected (client);
346     }
347 }
348
349
350 static gboolean
351 incoming_client (GSocketService    *service,
352                  GSocketConnection *connection,
353                  GObject           *source_object)
354 {
355   BroadwayClient *client;
356   GInputStream *input;
357
358   client = g_new0 (BroadwayClient, 1);
359   client->id = client_id_count++;
360   client->connection = g_object_ref (connection);
361   
362   input = g_io_stream_get_input_stream (G_IO_STREAM (client->connection));
363   client->in = (GBufferedInputStream *)g_buffered_input_stream_new (input);
364
365   clients = g_list_prepend (clients, client);
366
367   g_buffered_input_stream_fill_async (client->in,
368                                       4*1024,
369                                       0,
370                                       NULL,
371                                       client_fill_cb, client);
372   
373   return TRUE;
374 }
375
376
377 int
378 main (int argc, char *argv[])
379 {
380   GError *error = NULL;
381   GOptionContext *context;
382   GMainLoop *loop;
383   GSocketAddress *address;
384   GSocketService *listener;
385   char *path, *base;
386   int http_port = 0;
387   int display = 1;
388   const GOptionEntry entries[] = {
389     { "port", 'p', 0, G_OPTION_ARG_INT, &http_port, "Httpd port", "PORT" },
390     { NULL }
391   };
392
393   context = g_option_context_new ("[:DISPLAY] - broadway display daemon");
394   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
395   if (!g_option_context_parse (context, &argc, &argv, &error))
396     {
397       g_printerr ("option parsing failed: %s\n", error->message);
398       exit (1);
399     }
400
401   if (argc > 1)
402     {
403       if (*argv[1] != ':')
404         {
405           g_printerr ("Usage broadwayd [:DISPLAY]\n");
406           exit (1);
407         }
408       display = strtol(argv[1]+1, NULL, 10);
409       if (display == 0)
410         {
411           g_printerr ("Failed to parse display num %s\n", argv[1]);
412           exit (1);
413         }
414     }
415
416   if (http_port == 0)
417     http_port = 8080 + (display - 1);
418
419   server = broadway_server_new (http_port, &error);
420   if (server == NULL)
421     {
422       g_printerr ("%s\n", error->message);
423       return 1;
424     }
425
426   base = g_strdup_printf ("broadway%d.socket", display);
427   path = g_build_filename (g_get_user_runtime_dir (), base, NULL);
428   g_free (base);
429   g_print ("Listening on %s\n", path);
430   address = g_unix_socket_address_new_with_type (path, -1,
431                                                  G_UNIX_SOCKET_ADDRESS_ABSTRACT);
432   g_free (path);
433
434   listener = g_socket_service_new ();
435   if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
436                                       address,
437                                       G_SOCKET_TYPE_STREAM,
438                                       G_SOCKET_PROTOCOL_DEFAULT,
439                                       G_OBJECT (server),
440                                       NULL,
441                                       &error))
442     {
443       g_printerr ("Can't listen: %s\n", error->message);
444       return 1;
445     }
446   g_object_unref (address);
447
448   g_signal_connect (listener, "incoming", G_CALLBACK (incoming_client), NULL);
449
450   g_socket_service_start (G_SOCKET_SERVICE (listener));
451
452   loop = g_main_loop_new (NULL, FALSE);
453   g_main_loop_run (loop);
454   
455   return 0;
456 }
457
458 static gsize
459 get_event_size (int type)
460 {
461   switch (type)
462     {
463     case BROADWAY_EVENT_ENTER:
464     case BROADWAY_EVENT_LEAVE:
465       return sizeof (BroadwayInputCrossingMsg);
466     case BROADWAY_EVENT_POINTER_MOVE:
467       return sizeof (BroadwayInputPointerMsg);
468     case BROADWAY_EVENT_BUTTON_PRESS:
469     case BROADWAY_EVENT_BUTTON_RELEASE:
470       return sizeof (BroadwayInputButtonMsg);
471     case BROADWAY_EVENT_SCROLL:
472       return sizeof (BroadwayInputScrollMsg);
473     case BROADWAY_EVENT_KEY_PRESS:
474     case BROADWAY_EVENT_KEY_RELEASE:
475       return  sizeof (BroadwayInputKeyMsg);
476     case BROADWAY_EVENT_GRAB_NOTIFY:
477     case BROADWAY_EVENT_UNGRAB_NOTIFY:
478       return sizeof (BroadwayInputGrabReply);
479     case BROADWAY_EVENT_CONFIGURE_NOTIFY:
480       return  sizeof (BroadwayInputConfigureNotify);
481     case BROADWAY_EVENT_DELETE_NOTIFY:
482       return sizeof (BroadwayInputDeleteNotify);
483     case BROADWAY_EVENT_SCREEN_SIZE_CHANGED:
484       return sizeof (BroadwayInputScreenResizeNotify);
485     default:
486       g_assert_not_reached ();
487     }
488   return 0;
489 }
490
491 void
492 broadway_events_got_input (BroadwayInputMsg *message,
493                            gint32 client_id)
494 {
495   GList *l;
496   BroadwayReplyEvent reply_event;
497   gsize size;
498
499   size = get_event_size (message->base.type);
500
501   reply_event.msg = *message;
502
503   for (l = clients; l != NULL; l = l->next)
504     {
505       BroadwayClient *client = l->data;
506
507       if (client_id == -1 ||
508           client->id == client_id)
509         {
510           send_reply (client, NULL, (BroadwayReply *)&reply_event,
511                       sizeof (BroadwayReplyBase) + size,
512                       BROADWAY_REPLY_EVENT);
513         }
514     }
515 }