7 #include <libwebsockets.h>
10 #include <sys/inotify.h>
11 #include <sys/types.h>
14 enum sockdrawer_protocols {
18 SOCKDRAWER_PROTOCOL_COUNT
21 #ifdef INSTALL_DATADIR
22 #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/sockdrawer"
24 #define LOCAL_RESOURCE_PATH "."
27 #define DEFAULTCONTENT LOCAL_RESOURCE_PATH"/index.html"
28 #define DEFAULTICON LOCAL_RESOURCE_PATH"/default.png"
30 #define APPDIR "/usr/share/applications"
32 struct libwebsocket_context *context;
34 E_DBus_Connection *bus;
36 json_t *app_list = NULL;
37 char *apps_directory = NULL;
39 char *content_directory = NULL;
41 json_t *event_queue = NULL;
43 char *voicecallmanager_path = NULL;
45 #define MAX_POLL_ELEMENTS 100
46 struct pollfd pollfds[100];
47 int count_pollfds = 0;
49 void handle_command(json_t *o);
50 static int callback_http(struct libwebsocket_context *context,
51 struct libwebsocket *wsi,
52 enum libwebsocket_callback_reasons reason, void *user,
53 void *in, size_t len);
54 void send_app_list(struct libwebsocket *socket);
55 static int callback_app_list(struct libwebsocket_context *context,
56 struct libwebsocket *wsi,
57 enum libwebsocket_callback_reasons reason,
58 void *user, void *in, size_t len);
59 static Eina_Bool get_websocket_event(void *data);
60 static int callback_dialer(struct libwebsocket_context *context,
61 struct libwebsocket *wsi,
62 enum libwebsocket_callback_reasons reason,
63 void *user, void *in, size_t len);
68 struct per_session_data__app_list {
71 struct per_session_data__dialer {
75 static struct libwebsocket_protocols protocols[] = {
84 sizeof(struct per_session_data__app_list),
89 sizeof(struct per_session_data__dialer),
96 /* libwebsocket callback for handling normal http request. We use this */
97 /* to implementa a rudamentary webserver. */
99 callback_http(struct libwebsocket_context *context,
100 struct libwebsocket *wsi,
101 enum libwebsocket_callback_reasons reason, void *user,
102 void *in, size_t len)
104 char client_name[128];
111 case LWS_CALLBACK_HTTP:
112 if (in && ((char *)in)[0] == '/' && !strstr(in, "..")) {
113 if (strstr(in, "/icon/") == in) {
114 /* virtual directory used for serving up system icons */
116 char *icon = (char *)(in + 6);
117 const char *path = efreet_icon_path_find(getenv("E_ICON_THEME"),
118 (const char *)icon, 128);
120 path = efreet_icon_path_find("default", icon, 128);
122 path = efreet_icon_path_find("hicolor", icon, 128);
124 if (icon[0] == '/' && !stat(icon, &info) && S_ISREG(info.st_mode))
127 fprintf(stderr, "Can not find icon: %s\n", icon);
134 libwebsockets_serve_http_file(wsi, path,
135 (const char *)efreet_mime_type_get((char *)path));
137 snprintf(filename, 256, "%s/%s", content_directory, (char *)in + 1);
138 if (!stat(filename, &info) && S_ISDIR(info.st_mode))
139 /* try serving up index.html */
140 snprintf(filename, 256, "%s/%s/index.html",
141 content_directory, (char *)in + 1);
143 if (!stat(filename, &info) && S_ISREG(info.st_mode)) {
144 libwebsockets_serve_http_file(wsi, filename,
145 (const char *)efreet_mime_type_get(filename));
146 printf("serving: %s\n", filename);
148 /* TODO: Serve up error html page */
149 fprintf(stderr, "Unable to find %s\n", filename);
153 fprintf(stderr, "Invalid request \'%s\'\n", (char *)in);
157 case LWS_CALLBACK_ADD_POLL_FD:
158 pollfds[count_pollfds].fd = (int)(long)user;
159 pollfds[count_pollfds].events = (int)len;
160 pollfds[count_pollfds++].revents = 0;
163 case LWS_CALLBACK_DEL_POLL_FD:
164 for (n = 0; n < count_pollfds; n++)
165 if (pollfds[n].fd == (int)(long)user)
166 while (n < count_pollfds) {
167 pollfds[n] = pollfds[n + 1];
173 case LWS_CALLBACK_SET_MODE_POLL_FD:
174 for (n = 0; n < count_pollfds; n++)
175 if (pollfds[n].fd == (int)(long)user)
176 pollfds[n].events |= (int)(long)len;
179 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
180 for (n = 0; n < count_pollfds; n++)
181 if (pollfds[n].fd == (int)(long)user)
182 pollfds[n].events &= ~(int)(long)len;
193 dial_reply(void *data, DBusMessage *reply, DBusError *error)
195 if (dbus_error_is_set(error)) {
196 fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
201 /* libwebsocket callback for the 'protocol-dialer' protocol */
203 callback_dialer(struct libwebsocket_context *context,
204 struct libwebsocket *wsi,
205 enum libwebsocket_callback_reasons reason,
206 void *user, void *in, size_t len)
212 case LWS_CALLBACK_BROADCAST:
214 * This is triggered on all open websocket connections
215 * when libwebsocket_broadcast is called. For this
216 * protocol we ignore the 'in' paramter and send out
217 * the pending event queue
220 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
222 LWS_SEND_BUFFER_POST_PADDING);
223 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
224 int n = sprintf((char *)p, "%s", (char *)in);
226 libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
232 case LWS_CALLBACK_ESTABLISHED:
233 if (voicecallmanager_path) {
234 /* inform any connections about the addition of the modem */
235 json_t *o = json_object();
237 json_object_set(o, "type", json_string("ModemAdded"));
238 json_object_set(o, "id", json_string(voicecallmanager_path));
239 char *dump = json_dumps(o, 0);
241 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
243 LWS_SEND_BUFFER_POST_PADDING);
244 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
245 int n = sprintf((char *)p, "%s", dump);
247 libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
252 case LWS_CALLBACK_RECEIVE:
254 * The other end of the websocket is sending us text
255 * so attempt to parse that text as a JSON object providing
256 * either a single command object or an array of command
259 if (in && (o = json_loads((char *)in, 0, NULL))) {
260 if (json_is_array(o)) {
261 for (i = 0; i < json_array_size(o); i++)
262 handle_command(json_array_get(o, i));
266 json_object_clear(o);
278 /* Write a serialized JSON object containing */
279 /* a list of objects each describing an application */
280 void send_app_list(struct libwebsocket *socket)
283 * WARNING: libwebsockets requires prefixing and post
284 * fixing the send buffer with enough space
285 * for the library to insert required websocket
286 * protocol information.
287 * Ignoring this will result in segfaults.
289 char *dump = json_dumps(app_list, 0);
290 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
292 LWS_SEND_BUFFER_POST_PADDING);
293 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
294 int n = sprintf((char *)p, "%s", dump);
296 libwebsocket_write(socket, p, n, LWS_WRITE_TEXT);
302 /* command object parser */
303 void handle_command(json_t *o)
305 const char *cmd = json_string_value(json_object_get(o, "cmd"));
310 if (strncmp(cmd, "launch", 6) == 0 && (tmp = json_object_get(o, "index"))) {
311 int index = json_integer_value(tmp);
312 json_t *app = json_array_get(app_list, index);
316 json_t *exec = json_object_get(app, "exec");
320 ecore_exe_run(json_string_value(exec), NULL);
321 } else if (strncmp(cmd, "makeCall", 8) == 0 &&
322 (tmp = json_object_get(o, "remotePartyId"))) {
323 const char *remotePartyId = json_string_value(tmp);
324 const char *hideCallerId = "";
327 msg = dbus_message_new_method_call(
329 voicecallmanager_path,
330 "org.ofono.VoiceCallManager",
333 dbus_message_append_args(msg,
334 DBUS_TYPE_STRING, &remotePartyId,
336 dbus_message_append_args(msg,
337 DBUS_TYPE_STRING, &hideCallerId,
339 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
340 } else if (strncmp(cmd, "end", 3) == 0) {
341 const char *path = json_string_value(json_object_get(o, "path"));
344 msg = dbus_message_new_method_call(
347 "org.ofono.VoiceCall",
350 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
355 /* libwebsocket callback for 'protocol-app-list' based websockets */
357 callback_app_list(struct libwebsocket_context *context,
358 struct libwebsocket *wsi,
359 enum libwebsocket_callback_reasons reason,
360 void *user, void *in, size_t len)
366 case LWS_CALLBACK_BROADCAST:
368 * This is triggered on all open websocket connections
369 * when libwebsocket_broadcast is called. For this
370 * protocol we only do one possible thing on broadcast
371 * so just ignore the incoming 'in' string and just
372 * send a new list of applications.
374 case LWS_CALLBACK_ESTABLISHED:
378 case LWS_CALLBACK_RECEIVE:
380 * The other end of the websocket is sending us text
381 * so attempt to parse that text as a JSON object providing
382 * either a single command object or an array of command
385 if (in && (o = json_loads((char *)in, 0, NULL))) {
386 if (json_is_array(o)) {
387 for (i = 0; i < json_array_size(o); i++)
388 handle_command(json_array_get(o, i));
392 json_object_clear(o);
404 void build_app_list(char *dir) {
406 const char *filename;
407 const Eina_File_Direct_Info *info;
409 json_array_clear(app_list);
412 it = eina_file_ls(dir);
413 EINA_ITERATOR_FOREACH(it, filename)
415 if (!eina_str_has_suffix(filename, ".desktop"))
418 Efreet_Desktop *desktop = efreet_desktop_get(filename);
419 if (!desktop || !desktop->name || !desktop->icon || !desktop->exec)
422 json_t *o = json_object();
423 json_object_set(o, "id", json_integer(index++));
424 json_object_set(o, "version", json_string("0.1"));
425 json_object_set(o, "show", json_boolean(1));
426 json_object_set(o, "name", json_string(desktop->name));
427 json_object_set(o, "iconPath", json_string(desktop->icon));
428 json_object_set(o, "exec", json_string(desktop->exec));
429 json_array_append(app_list, o);
431 eina_iterator_free(it);
434 #define BUFF_SIZE ((sizeof(struct inotify_event)+FILENAME_MAX)*1024)
435 static Eina_Bool poll_descriptors(void *data)
439 char buff[BUFF_SIZE] = {0};
441 n = poll(pollfds, count_pollfds, 100);
443 if (pollfds[0].revents) {
444 /* The first descriptor what inotify returns*/
445 len = read(pollfds[0].fd, buff, BUFF_SIZE);
447 struct inotify_event *pevent = (struct inotify_event *)&buff[i];
448 if (pevent->name && eina_str_has_suffix(pevent->name, ".desktop")) {
449 build_app_list(apps_directory);
451 /* Trigger the broadcast callback for all open connectinos */
452 libwebsockets_broadcast(&protocols[PROTOCOL_APP_LIST], "x", 2);
456 i += sizeof(struct inotify_event) + pevent->len;
459 if (count_pollfds > 1) {
460 for (n = 1; n < count_pollfds; n++)
461 if (pollfds[n].revents)
462 libwebsocket_service_fd(context, &pollfds[n]);
466 return ECORE_CALLBACK_RENEW;
469 void call_properties_changed_cb(void *data, DBusMessage *msg)
471 DBusMessageIter iter, value;
472 const char *property, *v;
477 dbus_message_iter_init(msg, &iter);
478 dbus_message_iter_get_basic(&iter, &property);
480 dbus_message_iter_next(&iter);
481 dbus_message_iter_recurse(&iter, &value);
483 dbus_message_iter_get_basic(&value, &v);
485 object = json_object();
486 json_object_set(object, "type", json_string("PropertyChanged"));
487 json_object_set(object, "id", json_string(dbus_message_get_path(msg)));
488 json_object_set(object, property, json_string(v));
489 dump = json_dumps(object, 0);
490 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
494 void modem_properties_changed_cb(void *data, DBusMessage *msg)
496 DBusMessageIter iter, values, entry;
497 const char *property, *v;
502 dbus_message_iter_init(msg, &iter);
503 dbus_message_iter_get_basic(&iter, &property);
505 if (!strcasecmp(property, "Interfaces")) {
506 dbus_message_iter_next(&iter);
507 dbus_message_iter_recurse(&iter, &values);
509 while (dbus_message_iter_get_arg_type(&values) == DBUS_TYPE_ARRAY) {
510 DBusMessageIter variant;
511 dbus_message_iter_recurse(&iter, &entry);
512 dbus_message_iter_recurse(&entry, &variant);
513 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_INVALID) {
514 dbus_message_iter_get_basic(&variant, &v);
515 if (!strcasecmp(v, "org.ofono.VoiceCallManager")) {
516 object = json_object();
517 json_object_set(object, "type", json_string("ModemAdded"));
518 json_object_set(object, "id", json_string(dbus_message_get_path(msg)));
519 dump = json_dumps(object, 0);
520 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
524 // If the modem has removed all interfaces then its no longer
525 // available for making calls
526 object = json_object();
527 json_object_set(object, "type", json_string("ModemRemoved"));
528 json_object_set(object, "id", json_string(dbus_message_get_path(msg)));
529 dump = json_dumps(object, 0);
530 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
533 dbus_message_iter_next(&values);
538 void call_added_cb(void *data, DBusMessage *msg)
540 DBusMessageIter iter, properties;
543 dbus_message_iter_init(msg, &iter);
544 dbus_message_iter_get_basic(&iter, &path);
546 dbus_message_iter_next(&iter);
547 dbus_message_iter_recurse(&iter, &properties);
549 json_t *object = json_object();
550 json_object_set(object, "id", json_string(path));
551 json_object_set(object, "type", json_string("CallAdded"));
553 while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
554 DBusMessageIter entry, value;
557 dbus_message_iter_recurse(&properties, &entry);
558 dbus_message_iter_get_basic(&entry, &key);
560 dbus_message_iter_next(&entry);
561 dbus_message_iter_recurse(&entry, &value);
563 dbus_message_iter_get_basic(&value, &v);
565 json_object_set(object, key, json_string(v));
567 dbus_message_iter_next(&properties);
570 e_dbus_signal_handler_add(bus,
573 "org.ofono.VoiceCall",
575 call_properties_changed_cb,
578 char *dump = json_dumps(object, 0);
579 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
583 void call_removed_cb(void *data, DBusMessage *msg)
585 DBusMessageIter iter;
588 dbus_message_iter_init(msg, &iter);
589 dbus_message_iter_get_basic(&iter, &path);
591 json_t *object = json_object();
592 json_object_set(object, "id", json_string(path));
593 json_object_set(object, "type", json_string("CallRemoved"));
594 char *dump = json_dumps(object, 0);
595 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
599 void modem_added_cb(void *data, DBusMessage *msg)
601 DBusMessageIter iter;
604 dbus_message_iter_init(msg, &iter);
605 dbus_message_iter_get_basic(&iter, &path);
607 json_t *object = json_object();
608 json_object_set(object, "type", json_string("ModemAdded"));
609 json_object_set(object, "id", json_string(path));
610 char *dump = json_dumps(object, 0);
611 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
615 void modem_removed_cb(void *data, DBusMessage *msg)
617 DBusMessageIter iter;
620 dbus_message_iter_init(msg, &iter);
621 dbus_message_iter_get_basic(&iter, &path);
623 json_t *object = json_object();
624 json_object_set(object, "type", json_string("ModemRemoved"));
625 json_object_set(object, "id", json_string(path));
626 char *dump = json_dumps(object, 0);
627 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
632 get_modems_reply(void *data, DBusMessage *reply, DBusError *error)
634 DBusMessageIter iter, entry;
635 if (dbus_error_is_set(error)) {
636 fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
640 dbus_message_iter_init(reply, &iter);
641 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
642 fprintf(stderr, "Unexpeced signature from ofono GetModems call\n");
646 dbus_message_iter_recurse(&iter, &entry);
648 while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) {
650 DBusMessageIter item, properties;
652 dbus_message_iter_recurse(&entry, &item);
653 dbus_message_iter_get_basic(&item, &path);
655 printf("registering %s\n", path);
656 e_dbus_signal_handler_add(bus, "org.ofono",
657 path, "org.ofono.Modem", "PropertyChanged",
658 modem_properties_changed_cb, (void *)strdup(path));
659 e_dbus_signal_handler_add(bus, "org.ofono",
660 path, "org.ofono.VoiceCallManager", "CallAdded",
661 call_added_cb, NULL);
662 e_dbus_signal_handler_add(bus, "org.ofono",
663 path, "org.ofono.VoiceCallManager",
665 call_removed_cb, NULL);
667 dbus_message_iter_next(&item);
668 dbus_message_iter_recurse(&item, &properties);
670 while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
672 DBusMessageIter interfaces, value, entry;
674 dbus_message_iter_recurse(&properties, &entry);
675 dbus_message_iter_get_basic(&entry, &key);
677 dbus_message_iter_next(&entry);
678 dbus_message_iter_recurse(&entry, &value);
680 if (!strcasecmp(key, "Interfaces")) {
681 dbus_message_iter_recurse(&value, &interfaces);
682 while (dbus_message_iter_get_arg_type(&interfaces) ==
686 dbus_message_iter_get_basic(&interfaces, &iface);
688 if (!strcasecmp(iface, "org.ofono.VoiceCallManager")) {
689 if (voicecallmanager_path)
690 free(voicecallmanager_path);
691 voicecallmanager_path = strdup(path);
693 /* inform any connections about the addition of the modem */
694 json_t *o = json_object();
695 json_object_set(o, "type", json_string("ModemAdded"));
696 json_object_set(o, "id", json_string(path));
697 char *dump = json_dumps(o, 0);
698 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
702 dbus_message_iter_next(&interfaces);
706 dbus_message_iter_next(&properties);
709 dbus_message_iter_next(&entry);
713 static struct option options[] = {
714 { "help", no_argument, NULL, 'h' },
715 { "port", required_argument, NULL, 'p' },
716 { "appsdir", required_argument, NULL, 'a' },
717 { "contentdir", required_argument, NULL, 'c' },
721 int main(int argc, char **argv)
733 ecore_app_args_set(argc, (const char **)argv);
735 /* parse command-line options */
737 n = getopt_long(argc, argv, "hp:d:", options, NULL);
742 apps_directory = strdup(optarg);
745 content_directory = strdup(optarg);
752 fprintf(stderr, "Usage: %s [--port=<p>] [--appsdir=<a>] [--contentdir=<c>]\n", argv[0]);
757 apps_directory = APPDIR;
758 if (!content_directory)
759 content_directory = LOCAL_RESOURCE_PATH;
761 /* Initialize and build up the application list */
762 app_list = json_array();
763 build_app_list(apps_directory);
765 bus = e_dbus_bus_get(DBUS_BUS_SYSTEM);
767 /* Start listening for incoming phone calls */
769 msg = dbus_message_new_method_call(
775 e_dbus_message_send(bus, msg, get_modems_reply, -1, NULL);
777 e_dbus_signal_handler_add(bus, "org.ofono", "/", "org.ofono.Manager", "ModemAdded",
778 modem_added_cb, NULL);
779 e_dbus_signal_handler_add(bus, "org.ofono", "/", "org.ofono.Manager", "ModemRemoved",
780 modem_removed_cb, NULL);
782 /* Listen for updates on application desktop files*/
784 inotify_add_watch(fd, APPDIR, IN_CREATE | IN_DELETE | IN_MODIFY);
786 pollfds[0].events = POLLIN;
787 pollfds[0].revents = 0;
790 /* Start listening for incoming websocket connections */
791 context = libwebsocket_create_context(port, NULL, protocols,
792 libwebsocket_internal_extensions,
793 NULL, NULL, -1, -1, opts);
794 if (context == NULL) {
795 fprintf(stderr, "libwebsocket init failed\n");
799 ecore_idler_add(poll_descriptors, NULL);
801 ecore_main_loop_begin();
804 libwebsocket_context_destroy(context);
809 /* Local Variables: */
811 /* c-basic-offset:4 */
812 /* indent-tabs-mode: nil */