small code cleanup
[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 /* libwebsocket callback for the 'protocol-dialer' protocol */
195 static int
196 callback_dialer(struct libwebsocket_context *context,
197                 struct libwebsocket *wsi,
198                 enum libwebsocket_callback_reasons reason,
199                 void *user, void *in, size_t len)
200 {
201     json_t *o = NULL;
202     int i;
203
204     switch (reason) {
205     case LWS_CALLBACK_BROADCAST:
206         /* 
207          * This is triggered on all open websocket connections
208          * when libwebsocket_broadcast is called. For this 
209          * protocol we ignore the 'in' paramter and send out
210          * the pending event queue
211          */
212         if (in) {
213             unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING + 
214                                       strlen((char *)in) + 
215                                       LWS_SEND_BUFFER_POST_PADDING);
216             unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
217             int n = sprintf((char *)p, "%s", (char *)in);
218
219             libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
220
221             free(b);
222         }
223         break;
224
225     case LWS_CALLBACK_RECEIVE:
226         /* 
227          * The other end of the websocket is sending us text
228          * so attempt to parse that text as a JSON object providing
229          * either a single command object or an array of command
230          * objects.
231          */
232         if (in && (o = json_loads((char *)in, 0, NULL))) {
233             if (json_is_array(o)) {
234                 for (i = 0; i < json_array_size(o); i++)
235                     handle_command(json_array_get(o, i));
236                 json_array_clear(o);
237             } else {
238                 handle_command(o);
239                 json_object_clear(o);
240             }
241         }
242         break;
243
244     default:
245         break;
246     }
247
248     return 0;
249 }
250
251 /* Write a serialized JSON object containing */
252 /* a list of objects each describing an application */
253 void send_app_list(struct libwebsocket *socket) 
254 {
255     /* 
256      * WARNING: libwebsockets requires prefixing and post
257      *          fixing the send buffer with enough space
258      *          for the library to insert required websocket
259      *          protocol information.
260      *          Ignoring this will result in segfaults.
261      */
262     char *dump = json_dumps(app_list, 0);
263     unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING + 
264                               strlen(dump) + 
265                               LWS_SEND_BUFFER_POST_PADDING);
266     unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
267     int n = sprintf((char *)p, "%s", dump);
268
269     libwebsocket_write(socket, p, n, LWS_WRITE_TEXT);
270
271     free(dump);
272     free(b);
273 }
274
275 /* command object parser */
276 void handle_command(json_t *o)
277 {
278     const char *cmd = json_string_value(json_object_get(o, "cmd"));
279     if (!cmd)
280         return;
281
282     json_t *tmp;
283     if (strncmp(cmd, "launch", 6) == 0 && (tmp = json_object_get(o, "index"))) {
284         int index = json_integer_value(tmp);
285         json_t *app = json_array_get(app_list, index);
286         if (!app)
287             return;
288
289         json_t *exec = json_object_get(app, "exec");
290         if (!exec)
291             return;
292         
293         ecore_exe_run(json_string_value(exec), NULL);
294     } else if (strncmp(cmd, "makeCall", 8) == 0 && 
295                (tmp = json_object_get(o, "remotePartyId"))) {
296         const char *remotePartyId = json_string_value(tmp);
297         const char *hideCallerId = "";
298
299         DBusMessage *msg;
300         msg = dbus_message_new_method_call(
301                                            "org.ofono",
302                                            voicecallmanager_path,
303                                            "org.ofono.VoiceCallManager",
304                                            "Dial"
305                                            );
306         dbus_message_append_args(msg, 
307                                  DBUS_TYPE_STRING, &remotePartyId, 
308                                  DBUS_TYPE_INVALID);
309         dbus_message_append_args(msg, 
310                                  DBUS_TYPE_STRING, &hideCallerId,
311                                  DBUS_TYPE_INVALID);
312         e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
313     } else if (strncmp(cmd, "end", 3) == 0) {
314         DBusMessage *msg;
315         msg = dbus_message_new_method_call(
316                                            "org.ofono",
317                                            voicecallmanager_path,
318                                            "org.ofono.VoiceCallManager",
319                                            "HangupAll"
320                                            );
321         e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
322
323     }
324 }
325
326 /* libwebsocket callback for 'protocol-app-list' based websockets */
327 static int
328 callback_app_list(struct libwebsocket_context *context,
329                   struct libwebsocket *wsi,
330                   enum libwebsocket_callback_reasons reason,
331                   void *user, void *in, size_t len)
332 {
333     json_t *o = NULL;
334     int i;
335
336     switch (reason) {
337     case LWS_CALLBACK_BROADCAST:
338         /* 
339          * This is triggered on all open websocket connections
340          * when libwebsocket_broadcast is called. For this 
341          * protocol we only do one possible thing on broadcast
342          * so just ignore the incoming 'in' string and just
343          * send a new list of applications.
344          */
345     case LWS_CALLBACK_ESTABLISHED:
346         send_app_list(wsi);
347         break;
348
349     case LWS_CALLBACK_RECEIVE:
350         /* 
351          * The other end of the websocket is sending us text
352          * so attempt to parse that text as a JSON object providing
353          * either a single command object or an array of command
354          * objects.
355          */
356         if (in && (o = json_loads((char *)in, 0, NULL))) {
357             if (json_is_array(o)) {
358                 for (i = 0; i < json_array_size(o); i++)
359                     handle_command(json_array_get(o, i));
360                 json_array_clear(o);
361             } else {
362                 handle_command(o);
363                 json_object_clear(o);
364             }
365         }
366         break;
367
368     default:
369         break;
370     }
371
372     return 0;
373 }
374
375 void build_app_list(char *dir) {
376     Eina_Iterator *it;
377     const char *filename;
378     const Eina_File_Direct_Info *info;
379
380     json_array_clear(app_list);
381
382     int index = 0;
383     it = eina_file_ls(dir);
384     EINA_ITERATOR_FOREACH(it, filename)
385         {
386             if (!eina_str_has_suffix(filename, ".desktop"))
387                 continue;
388
389             Efreet_Desktop *desktop = efreet_desktop_get(filename);
390             if (!desktop || !desktop->name || !desktop->icon || !desktop->exec)
391                 continue;
392
393             json_t *o = json_object();
394             json_object_set(o, "id", json_integer(index++));
395             json_object_set(o, "version", json_string("0.1"));
396             json_object_set(o, "show", json_boolean(1));
397             json_object_set(o, "name", json_string(desktop->name));
398             json_object_set(o, "iconPath", json_string(desktop->icon));
399             json_object_set(o, "exec", json_string(desktop->exec));
400             json_array_append(app_list, o);
401         }
402     eina_iterator_free(it);
403 }
404
405 #define BUFF_SIZE ((sizeof(struct inotify_event)+FILENAME_MAX)*1024)
406 static Eina_Bool poll_descriptors(void *data)
407 {
408     int n;
409     ssize_t len, i = 0;
410     char buff[BUFF_SIZE] = {0};
411
412     n = poll(pollfds, count_pollfds, 100);
413     if (n > -1) {
414         if (pollfds[0].revents) {
415             /* The first descriptor what inotify returns*/
416             len = read(pollfds[0].fd, buff, BUFF_SIZE);
417             while (i < len) {
418                 struct inotify_event *pevent = (struct inotify_event *)&buff[i];
419                 if (pevent->name && eina_str_has_suffix(pevent->name, ".desktop")) {
420                     build_app_list(apps_directory);
421
422                     /* Trigger the broadcast callback for all open connectinos */
423                     libwebsockets_broadcast(&protocols[PROTOCOL_APP_LIST], "x", 2);
424                     break;
425                 }
426
427                 i += sizeof(struct inotify_event) + pevent->len;
428             }
429         }
430         if (count_pollfds > 1) {
431             for (n = 1; n < count_pollfds; n++)
432                 if (pollfds[n].revents)
433                     libwebsocket_service_fd(context, &pollfds[n]);
434         }
435     }
436
437     return ECORE_CALLBACK_RENEW;
438 }
439
440 void call_properties_changed_cb(void *data, DBusMessage *msg)
441 {
442     DBusMessageIter iter, value;
443     const char *property, *v;
444     char *dump;
445     int n;
446     json_t *object;
447
448     dbus_message_iter_init(msg, &iter);
449     dbus_message_iter_get_basic(&iter, &property);
450
451     dbus_message_iter_next(&iter);
452     dbus_message_iter_recurse(&iter, &value);
453
454     dbus_message_iter_get_basic(&value, &v);
455
456     object = json_object();
457     json_object_set(object, property, json_string(v));
458     dump = json_dumps(object, 0);
459     libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
460     free(dump);
461 }
462
463 void call_added_cb(void *data, DBusMessage *msg)
464 {
465     DBusMessageIter iter, properties;
466     const char *path;
467
468     json_t *object = json_object();
469
470     dbus_message_iter_init(msg, &iter);
471     dbus_message_iter_get_basic(&iter, &path);
472
473     dbus_message_iter_next(&iter);
474     dbus_message_iter_recurse(&iter, &properties);
475
476     while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
477         DBusMessageIter entry, value;
478         const char *key, *v;
479
480         dbus_message_iter_recurse(&properties, &entry);
481         dbus_message_iter_get_basic(&entry, &key);
482
483         dbus_message_iter_next(&entry);
484         dbus_message_iter_recurse(&entry, &value);
485         
486         dbus_message_iter_get_basic(&value, &v);
487
488         json_object_set(object, key, json_string(v));
489
490         dbus_message_iter_next(&properties);
491     }
492
493     e_dbus_signal_handler_add(bus, 
494                               "org.ofono", 
495                               path,
496                               "org.ofono.VoiceCall", 
497                               "PropertyChanged", 
498                               call_properties_changed_cb, 
499                               NULL);
500
501     char *dump = json_dumps(object, 0);
502     libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
503     free(dump);
504 }
505
506 void call_removed_cb(void *data, DBusMessage *msg)
507 {
508     json_t *object = json_object();
509     json_object_set(object, "type", json_string("CallRemoved"));
510     char *dump = json_dumps(object, 0);
511     libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
512     free(dump);
513 }
514
515 void
516 get_modems_reply(void *data, DBusMessage *reply, DBusError *error)
517 {
518     DBusMessageIter iter, entry;
519     if (dbus_error_is_set(error)) {
520         fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
521         return;
522     }
523
524     dbus_message_iter_init(reply, &iter);
525     if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
526         fprintf(stderr, "Unexpeced signature from ofono GetModems call\n");
527         return;
528     }
529
530     dbus_message_iter_recurse(&iter, &entry);
531     
532     while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) {
533         const char *path;
534         DBusMessageIter item, properties;
535
536         dbus_message_iter_recurse(&entry, &item);
537         dbus_message_iter_get_basic(&item, &path);
538
539         dbus_message_iter_next(&item);
540         dbus_message_iter_recurse(&item, &properties);
541
542         while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
543             const char *key;
544             DBusMessageIter interfaces, value, entry;
545
546             dbus_message_iter_recurse(&properties, &entry);
547             dbus_message_iter_get_basic(&entry, &key);
548
549             dbus_message_iter_next(&entry);
550             dbus_message_iter_recurse(&entry, &value);
551             
552             if (!strcasecmp(key, "Interfaces")) {
553                 dbus_message_iter_recurse(&value, &interfaces);
554                 while (dbus_message_iter_get_arg_type(&interfaces) == 
555                        DBUS_TYPE_STRING) {
556                     const char *iface;
557                     
558                     dbus_message_iter_get_basic(&interfaces, &iface);
559                     
560                     if (!strcasecmp(iface, "org.ofono.VoiceCallManager")) {
561                         if (voicecallmanager_path)
562                             free(voicecallmanager_path);
563                         voicecallmanager_path = strdup(path);
564
565                         e_dbus_signal_handler_add(bus, "org.ofono", 
566                                                   path, iface, "CallAdded", 
567                                                   call_added_cb, NULL);
568                         e_dbus_signal_handler_add(bus, "org.ofono", 
569                                                   path, iface, 
570                                                   "CallRemoved", 
571                                                   call_removed_cb, NULL);
572                     }
573
574                     dbus_message_iter_next(&interfaces);
575                 }
576             }
577
578             dbus_message_iter_next(&properties);
579         }
580
581         dbus_message_iter_next(&entry);
582     }
583 }
584
585 static struct option options[] = {
586     { "help",      no_argument,         NULL, 'h' },
587     { "port",      required_argument,   NULL, 'p' },
588     { "directory", required_argument,   NULL, 'd' },
589     { NULL, 0, 0, 0 }
590 };
591
592 int main(int argc, char **argv)
593 {
594     int fd;
595     int n = 0;
596     int port = 7681;
597     int opts = 0;
598
599     eina_init();
600     ecore_init();
601     e_dbus_init();
602     efreet_init();
603     efreet_mime_init();
604     ecore_app_args_set(argc, (const char **)argv);
605
606     /* parse command-line options */
607     while (n >= 0) {
608         n = getopt_long(argc, argv, "hp:d:", options, NULL);
609         if (n < 0)
610             continue;
611         switch (n) {
612         case 'd':
613             apps_directory = strdup(optarg);
614             break;
615         case 'p':
616             port = atoi(optarg);
617             break;
618         case 'h':
619             fprintf(stderr, "Usage: %s [--port=<p>] [--directory=<d>]\n", argv[0]);
620             exit(-1);
621         }
622     }
623     if (!apps_directory)
624         apps_directory = APPDIR;
625     
626     /* Initialize and build up the application list */
627     app_list = json_array();
628     build_app_list(apps_directory);
629
630     bus = e_dbus_bus_get(DBUS_BUS_SYSTEM);
631
632     /* Start listening for incoming phone calls */
633     DBusMessage *msg;
634     msg = dbus_message_new_method_call(
635                                        "org.ofono",
636                                        "/",
637                                        "org.ofono.Manager",
638                                        "GetModems"
639                                        );
640     e_dbus_message_send(bus, msg, get_modems_reply, -1, NULL);
641
642     /* Listen for updates on application desktop files*/
643     fd = inotify_init();
644     inotify_add_watch(fd, APPDIR, IN_CREATE | IN_DELETE | IN_MODIFY);
645     pollfds[0].fd = fd;
646     pollfds[0].events = POLLIN;
647     pollfds[0].revents = 0;
648     count_pollfds = 1;
649
650     /* Start listening for incoming websocket connections */
651     context = libwebsocket_create_context(port, NULL, protocols,
652                                           libwebsocket_internal_extensions,
653                                           NULL, NULL, -1, -1, opts);
654     if (context == NULL) {
655         fprintf(stderr, "libwebsocket init failed\n");
656         return -1;
657     }
658
659     ecore_idler_add(poll_descriptors, NULL);
660     
661     ecore_main_loop_begin();
662     
663     /* cleanup */
664     libwebsocket_context_destroy(context);
665     ecore_shutdown();
666     return 0;
667 }
668
669 /* Local Variables:      */
670 /* mode:c                */
671 /* c-basic-offset:4      */
672 /* indent-tabs-mode: nil */
673 /* End:                  */