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