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