f378c2d6bf57dec0f4b12c33d8f609c9829e4b66
[profile/ivi/sockdrawer.git] / main.c
1 #include <Ecore.h>
2 #include <E_DBus.h>
3 #include <Efreet.h>
4 #include <Eina.h>
5 #include <getopt.h>
6 #include <jansson.h>
7 #include <libwebsockets.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include <sys/inotify.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13
14 enum sockdrawer_protocols {
15     PROTOCOL_HTTP = 0,
16     PROTOCOL_APP_LIST,
17     PROTOCOL_DIALER,
18     SOCKDRAWER_PROTOCOL_COUNT
19 };
20
21 #ifdef INSTALL_DATADIR
22 #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/sockdrawer"
23 #else
24 #define LOCAL_RESOURCE_PATH "."
25 #endif
26
27 #define DEFAULTCONTENT LOCAL_RESOURCE_PATH"/index.html"
28 #define DEFAULTICON LOCAL_RESOURCE_PATH"/default.png"
29
30 #define APPDIR "/usr/share/applications"
31
32 struct libwebsocket_context *context;
33
34 E_DBus_Connection *bus;
35
36 json_t *app_list = NULL;
37 char *apps_directory = NULL;
38
39 json_t *event_queue = NULL;
40
41 char *voicecallmanager_path = NULL;
42
43 #define MAX_POLL_ELEMENTS 100
44 struct pollfd pollfds[100];
45 int count_pollfds = 0;
46
47 void handle_command(json_t *o);
48 static int callback_http(struct libwebsocket_context *context, 
49                          struct libwebsocket *wsi,
50                          enum libwebsocket_callback_reasons reason, void *user, 
51                          void *in, size_t len);
52 void send_app_list(struct libwebsocket *socket);
53 static int callback_app_list(struct libwebsocket_context *context,
54                              struct libwebsocket *wsi,
55                              enum libwebsocket_callback_reasons reason,
56                              void *user, void *in, size_t len);
57 static Eina_Bool get_websocket_event(void *data);
58 static int callback_dialer(struct libwebsocket_context *context,
59                            struct libwebsocket *wsi,
60                            enum libwebsocket_callback_reasons reason,
61                            void *user, void *in, size_t len);
62
63 /*
64  * Per instance data
65  */
66 struct per_session_data__app_list {
67     int placeholder;
68 };
69 struct per_session_data__dialer {
70     int placeholder;
71 };
72
73 static struct libwebsocket_protocols protocols[] = {
74     {
75         "http-only",
76         callback_http,
77         0
78     },
79    {
80         "app-list-protocol",
81         callback_app_list,
82         sizeof(struct per_session_data__app_list),
83     },
84    {
85         "dialer-protocol",
86         callback_dialer,
87         sizeof(struct per_session_data__dialer),
88     },
89     {
90         NULL, NULL, 0
91     }
92 };
93
94 /* libwebsocket callback for handling normal http request. We use this */
95 /* to implementa a rudamentary webserver. */
96 static int 
97 callback_http(struct libwebsocket_context *context, 
98               struct libwebsocket *wsi,
99               enum libwebsocket_callback_reasons reason, void *user, 
100               void *in, size_t len)
101 {
102     char client_name[128];
103     char client_ip[128];
104     char filename[256];
105     int n;
106     struct stat info;
107
108     switch (reason) {
109     case LWS_CALLBACK_HTTP:
110         if (in && ((char *)in)[0] == '/' && !strstr(in, "..")) {
111             if (strstr(in, "/icon/") == in) {
112                 /* virtual directory used for serving up system icons */
113
114                 char *icon = (char *)(in + 6);
115                 const char *path = efreet_icon_path_find(getenv("E_ICON_THEME"), 
116                                                          (const char *)icon, 128);
117                 if (!path) {
118                     path = efreet_icon_path_find("default", icon, 128);
119                     if (!path) {
120                         path = efreet_icon_path_find("hicolor", icon, 128);
121                         if (!path) {
122                             if (icon[0] == '/' && !stat(icon, &info) && S_ISREG(info.st_mode))
123                                 path = icon;
124                             else {
125                                 fprintf(stderr, "Can not find icon: %s\n", icon);
126                                 path = DEFAULTICON;
127                             }
128                         }
129                     }
130                 }
131
132                 libwebsockets_serve_http_file(wsi, path, 
133                                               (const char *)efreet_mime_type_get((char *)path));
134             } else {
135                 snprintf(filename, 256, "%s/%s", LOCAL_RESOURCE_PATH, (char *)in + 1);
136                 if (strlen((char *)in) == 1)
137                     /* serve up default content */
138                     libwebsockets_serve_http_file(wsi, DEFAULTCONTENT, "text/html");
139                 else if (!stat(filename, &info) && S_ISREG(info.st_mode))
140                     libwebsockets_serve_http_file(wsi, filename, 
141                                                   (const char *)efreet_mime_type_get((char *)in));
142                 else
143                     fprintf(stderr, "Failed to find content: %s\n", (char *)in);
144             }
145         } else {
146             fprintf(stderr, "Invalid request \'%s\'\n", (char *)in);
147         }
148         break;
149
150     case LWS_CALLBACK_ADD_POLL_FD:
151         pollfds[count_pollfds].fd = (int)(long)user;
152         pollfds[count_pollfds].events = (int)len;
153         pollfds[count_pollfds++].revents = 0;
154         break;
155
156     case LWS_CALLBACK_DEL_POLL_FD:
157         for (n = 0; n < count_pollfds; n++)
158             if (pollfds[n].fd == (int)(long)user)
159                 while (n < count_pollfds) {
160                     pollfds[n] = pollfds[n + 1];
161                     n++;
162                 }
163         count_pollfds--;
164         break;
165
166     case LWS_CALLBACK_SET_MODE_POLL_FD:
167         for (n = 0; n < count_pollfds; n++)
168             if (pollfds[n].fd == (int)(long)user)
169                 pollfds[n].events |= (int)(long)len;
170         break;
171
172     case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
173         for (n = 0; n < count_pollfds; n++)
174             if (pollfds[n].fd == (int)(long)user)
175                 pollfds[n].events &= ~(int)(long)len;
176         break;
177
178     default:
179         break;
180     }
181     
182     return 0;
183 }
184
185 void
186 dial_reply(void *data, DBusMessage *reply, DBusError *error)
187 {
188     if (dbus_error_is_set(error)) {
189         fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
190         return;
191     }
192 }
193
194
195 /* libwebsocket callback for 'protocol-app-list' based websockets */
196 static int
197 callback_dialer(struct libwebsocket_context *context,
198                 struct libwebsocket *wsi,
199                 enum libwebsocket_callback_reasons reason,
200                 void *user, void *in, size_t len)
201 {
202     json_t *o = NULL;
203     int i;
204
205     switch (reason) {
206     case LWS_CALLBACK_BROADCAST:
207         /* 
208          * This is triggered on all open websocket connections
209          * when libwebsocket_broadcast is called. For this 
210          * protocol we ignore the 'in' paramter and send out
211          * the pending event queue
212          */
213     case LWS_CALLBACK_ESTABLISHED:
214         if (in) {
215             unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING + 
216                                       strlen((char *)in) + 
217                                       LWS_SEND_BUFFER_POST_PADDING);
218             unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
219             int n = sprintf((char *)p, "%s", (char *)in);
220
221             libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
222
223             free(b);
224         }
225         break;
226
227     case LWS_CALLBACK_RECEIVE:
228         /* 
229          * The other end of the websocket is sending us text
230          * so attempt to parse that text as a JSON object providing
231          * either a single command object or an array of command
232          * objects.
233          */
234         if (in && (o = json_loads((char *)in, 0, NULL))) {
235             if (json_is_array(o)) {
236                 for (i = 0; i < json_array_size(o); i++)
237                     handle_command(json_array_get(o, i));
238                 json_array_clear(o);
239             } else {
240                 handle_command(o);
241                 json_object_clear(o);
242             }
243         }
244         break;
245
246     default:
247         break;
248     }
249
250     return 0;
251 }
252
253 /* Write a serialized JSON object containing */
254 /* a list of objects each describing an application */
255 void send_app_list(struct libwebsocket *socket) 
256 {
257     /* 
258      * WARNING: libwebsockets requires prefixing and post
259      *          fixing the send buffer with enough space
260      *          for the library to insert required websocket
261      *          protocol information.
262      *          Ignoring this will result in segfaults.
263      */
264     char *dump = json_dumps(app_list, 0);
265     unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING + 
266                               strlen(dump) + 
267                               LWS_SEND_BUFFER_POST_PADDING);
268     unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
269     int n = sprintf((char *)p, "%s", dump);
270
271     libwebsocket_write(socket, p, n, LWS_WRITE_TEXT);
272
273     free(dump);
274     free(b);
275 }
276
277 /* command object parser */
278 void handle_command(json_t *o)
279 {
280     const char *cmd = json_string_value(json_object_get(o, "cmd"));
281     if (!cmd)
282         return;
283
284     json_t *tmp;
285     if (strncmp(cmd, "launch", 6) == 0 && (tmp = json_object_get(o, "index"))) {
286         int index = json_integer_value(tmp);
287         json_t *app = json_array_get(app_list, index);
288         if (!app)
289             return;
290
291         json_t *exec = json_object_get(app, "exec");
292         if (!exec)
293             return;
294         
295         ecore_exe_run(json_string_value(exec), NULL);
296     } else if (strncmp(cmd, "makeCall", 8) == 0 && 
297                (tmp = json_object_get(o, "remotePartyId"))) {
298         const char *remotePartyId = json_string_value(tmp);
299         const char *hideCallerId = "";
300
301         DBusMessage *msg;
302         msg = dbus_message_new_method_call(
303                                            "org.ofono",
304                                            voicecallmanager_path,
305                                            "org.ofono.VoiceCallManager",
306                                            "Dial"
307                                            );
308         dbus_message_append_args(msg, 
309                                  DBUS_TYPE_STRING, &remotePartyId, 
310                                  DBUS_TYPE_INVALID);
311         dbus_message_append_args(msg, 
312                                  DBUS_TYPE_STRING, &hideCallerId,
313                                  DBUS_TYPE_INVALID);
314         e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
315     } else if (strncmp(cmd, "end", 3) == 0) {
316         DBusMessage *msg;
317         msg = dbus_message_new_method_call(
318                                            "org.ofono",
319                                            voicecallmanager_path,
320                                            "org.ofono.VoiceCallManager",
321                                            "HangupAll"
322                                            );
323         e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
324
325     }
326 }
327
328 /* libwebsocket callback for 'protocol-app-list' based websockets */
329 static int
330 callback_app_list(struct libwebsocket_context *context,
331                   struct libwebsocket *wsi,
332                   enum libwebsocket_callback_reasons reason,
333                   void *user, void *in, size_t len)
334 {
335     json_t *o = NULL;
336     int i;
337
338     switch (reason) {
339     case LWS_CALLBACK_BROADCAST:
340         /* 
341          * This is triggered on all open websocket connections
342          * when libwebsocket_broadcast is called. For this 
343          * protocol we only do one possible thing on broadcast
344          * so just ignore the incoming 'in' string and just
345          * send a new list of applications.
346          */
347     case LWS_CALLBACK_ESTABLISHED:
348         send_app_list(wsi);
349         break;
350
351     case LWS_CALLBACK_RECEIVE:
352         /* 
353          * The other end of the websocket is sending us text
354          * so attempt to parse that text as a JSON object providing
355          * either a single command object or an array of command
356          * objects.
357          */
358         if (in && (o = json_loads((char *)in, 0, NULL))) {
359             if (json_is_array(o)) {
360                 for (i = 0; i < json_array_size(o); i++)
361                     handle_command(json_array_get(o, i));
362                 json_array_clear(o);
363             } else {
364                 handle_command(o);
365                 json_object_clear(o);
366             }
367         }
368         break;
369
370     default:
371         break;
372     }
373
374     return 0;
375 }
376
377 void build_app_list(char *dir) {
378     Eina_Iterator *it;
379     const char *filename;
380     const Eina_File_Direct_Info *info;
381
382     json_array_clear(app_list);
383
384     int index = 0;
385     it = eina_file_ls(dir);
386     EINA_ITERATOR_FOREACH(it, filename)
387         {
388             if (!eina_str_has_suffix(filename, ".desktop"))
389                 continue;
390
391             Efreet_Desktop *desktop = efreet_desktop_get(filename);
392             if (!desktop || !desktop->name || !desktop->icon || !desktop->exec)
393                 continue;
394
395             json_t *o = json_object();
396             json_object_set(o, "id", json_integer(index++));
397             json_object_set(o, "version", json_string("0.1"));
398             json_object_set(o, "show", json_boolean(1));
399             json_object_set(o, "name", json_string(desktop->name));
400             json_object_set(o, "iconPath", json_string(desktop->icon));
401             json_object_set(o, "exec", json_string(desktop->exec));
402             json_array_append(app_list, o);
403         }
404     eina_iterator_free(it);
405 }
406
407 #define BUFF_SIZE ((sizeof(struct inotify_event)+FILENAME_MAX)*1024)
408 static Eina_Bool poll_descriptors(void *data)
409 {
410     int n;
411     ssize_t len, i = 0;
412     char buff[BUFF_SIZE] = {0};
413
414     n = poll(pollfds, count_pollfds, 100);
415     if (n > -1) {
416         if (pollfds[0].revents) {
417             /* The first descriptor what inotify returns*/
418             len = read(pollfds[0].fd, buff, BUFF_SIZE);
419             while (i < len) {
420                 struct inotify_event *pevent = (struct inotify_event *)&buff[i];
421                 if (pevent->name && eina_str_has_suffix(pevent->name, ".desktop")) {
422                     build_app_list(apps_directory);
423
424                     /* Trigger the broadcast callback for all open connectinos */
425                     libwebsockets_broadcast(&protocols[PROTOCOL_APP_LIST], "x", 2);
426                     break;
427                 }
428
429                 i += sizeof(struct inotify_event) + pevent->len;
430             }
431         }
432         if (count_pollfds > 1) {
433             for (n = 1; n < count_pollfds; n++)
434                 if (pollfds[n].revents)
435                     libwebsocket_service_fd(context, &pollfds[n]);
436         }
437     }
438
439     return ECORE_CALLBACK_RENEW;
440 }
441
442 void call_properties_changed_cb(void *data, DBusMessage *msg)
443 {
444     DBusMessageIter iter, value;
445     const char *property, *v;
446     char *dump;
447     int n;
448     json_t *object;
449
450     dbus_message_iter_init(msg, &iter);
451     dbus_message_iter_get_basic(&iter, &property);
452
453     dbus_message_iter_next(&iter);
454     dbus_message_iter_recurse(&iter, &value);
455
456     dbus_message_iter_get_basic(&value, &v);
457
458     object = json_object();
459     json_object_set(object, property, json_string(v));
460     dump = json_dumps(object, 0);
461     libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
462     free(dump);
463 }
464
465 void call_added_cb(void *data, DBusMessage *msg)
466 {
467     DBusMessageIter iter, properties;
468     const char *path;
469
470     json_t *object = json_object();
471
472     dbus_message_iter_init(msg, &iter);
473     dbus_message_iter_get_basic(&iter, &path);
474
475     dbus_message_iter_next(&iter);
476     dbus_message_iter_recurse(&iter, &properties);
477
478     while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
479         DBusMessageIter entry, value;
480         const char *key, *v;
481
482         dbus_message_iter_recurse(&properties, &entry);
483         dbus_message_iter_get_basic(&entry, &key);
484
485         dbus_message_iter_next(&entry);
486         dbus_message_iter_recurse(&entry, &value);
487         
488         dbus_message_iter_get_basic(&value, &v);
489
490         json_object_set(object, key, json_string(v));
491
492         dbus_message_iter_next(&properties);
493     }
494
495     e_dbus_signal_handler_add(bus, 
496                               "org.ofono", 
497                               path,
498                               "org.ofono.VoiceCall", 
499                               "PropertyChanged", 
500                               call_properties_changed_cb, 
501                               NULL);
502
503     char *dump = json_dumps(object, 0);
504     libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
505     free(dump);
506 }
507
508 void call_removed_cb(void *data, DBusMessage *msg)
509 {
510     json_t *object = json_object();
511     json_object_set(object, "type", json_string("CallRemoved"));
512     char *dump = json_dumps(object, 0);
513     libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
514     free(dump);
515 }
516
517 void
518 get_modems_reply(void *data, DBusMessage *reply, DBusError *error)
519 {
520     DBusMessageIter iter, entry;
521     if (dbus_error_is_set(error)) {
522         fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
523         return;
524     }
525
526     dbus_message_iter_init(reply, &iter);
527     if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
528         fprintf(stderr, "Unexpeced signature from ofono GetModems call\n");
529         return;
530     }
531
532     dbus_message_iter_recurse(&iter, &entry);
533     
534     while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) {
535         const char *path;
536         DBusMessageIter item, properties;
537
538         dbus_message_iter_recurse(&entry, &item);
539         dbus_message_iter_get_basic(&item, &path);
540
541         dbus_message_iter_next(&item);
542         dbus_message_iter_recurse(&item, &properties);
543
544         while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
545             const char *key;
546             DBusMessageIter interfaces, value, entry;
547
548             dbus_message_iter_recurse(&properties, &entry);
549             dbus_message_iter_get_basic(&entry, &key);
550
551             dbus_message_iter_next(&entry);
552             dbus_message_iter_recurse(&entry, &value);
553             
554             if (!strcasecmp(key, "Interfaces")) {
555                 dbus_message_iter_recurse(&value, &interfaces);
556                 while (dbus_message_iter_get_arg_type(&interfaces) == 
557                        DBUS_TYPE_STRING) {
558                     const char *iface;
559                     
560                     dbus_message_iter_get_basic(&interfaces, &iface);
561                     
562                     if (!strcasecmp(iface, "org.ofono.VoiceCallManager")) {
563                         if (voicecallmanager_path)
564                             free(voicecallmanager_path);
565                         voicecallmanager_path = strdup(path);
566
567                         e_dbus_signal_handler_add(bus, "org.ofono", 
568                                                   path, iface, "CallAdded", 
569                                                   call_added_cb, NULL);
570                         e_dbus_signal_handler_add(bus, "org.ofono", 
571                                                   path, iface, 
572                                                   "CallRemoved", 
573                                                   call_removed_cb, NULL);
574                     }
575
576                     dbus_message_iter_next(&interfaces);
577                 }
578             }
579
580             dbus_message_iter_next(&properties);
581         }
582
583         dbus_message_iter_next(&entry);
584     }
585 }
586
587
588 static struct option options[] = {
589     { "help",      no_argument,         NULL, 'h' },
590     { "port",      required_argument,   NULL, 'p' },
591     { "directory", required_argument,   NULL, 'd' },
592     { NULL, 0, 0, 0 }
593 };
594
595 int main(int argc, char **argv)
596 {
597     int fd;
598     int n = 0;
599     int port = 7681;
600     int opts = 0;
601
602     eina_init();
603     ecore_init();
604     e_dbus_init();
605     efreet_init();
606     efreet_mime_init();
607     ecore_app_args_set(argc, (const char **)argv);
608
609     /* parse command-line options */
610     while (n >= 0) {
611         n = getopt_long(argc, argv, "hp:d:", options, NULL);
612         if (n < 0)
613             continue;
614         switch (n) {
615         case 'd':
616             apps_directory = strdup(optarg);
617             break;
618         case 'p':
619             port = atoi(optarg);
620             break;
621         case 'h':
622             fprintf(stderr, "Usage: %s [--port=<p>] [--directory=<d>]\n", argv[0]);
623             exit(-1);
624         }
625     }
626     if (!apps_directory)
627         apps_directory = APPDIR;
628     
629     /* Initialize and build up the application list */
630     app_list = json_array();
631     build_app_list(apps_directory);
632
633     bus = e_dbus_bus_get(DBUS_BUS_SYSTEM);
634
635     /* Start listening for incoming phone calls */
636     DBusMessage *msg;
637     msg = dbus_message_new_method_call(
638                                        "org.ofono",
639                                        "/",
640                                        "org.ofono.Manager",
641                                        "GetModems"
642                                        );
643     e_dbus_message_send(bus, msg, get_modems_reply, -1, NULL);
644
645
646     /* Listen for updates on application desktop files*/
647     fd = inotify_init();
648     inotify_add_watch(fd, APPDIR, IN_CREATE | IN_DELETE | IN_MODIFY);
649     pollfds[0].fd = fd;
650     pollfds[0].events = POLLIN;
651     pollfds[0].revents = 0;
652     count_pollfds = 1;
653
654     /* Start listening for incoming websocket connections */
655     context = libwebsocket_create_context(port, NULL, protocols,
656                                           libwebsocket_internal_extensions,
657                                           NULL, NULL, -1, -1, opts);
658     if (context == NULL) {
659         fprintf(stderr, "libwebsocket init failed\n");
660         return -1;
661     }
662
663     ecore_idler_add(poll_descriptors, NULL);
664     
665     ecore_main_loop_begin();
666     
667     /* cleanup */
668     libwebsocket_context_destroy(context);
669     ecore_shutdown();
670     return 0;
671 }
672
673 /* Local Variables:      */
674 /* mode:c                */
675 /* c-basic-offset:4      */
676 /* indent-tabs-mode: nil */
677 /* End:                  */