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