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);
65 void set_powered_cb(void *data, DBusMessage *reply, DBusError *error);
66 static Eina_Bool power_on_modem(void *data);
67 void setup_new_modem(DBusMessageIter entry);
69 void initialize_ofono(void);
74 struct per_session_data__app_list {
77 struct per_session_data__dialer {
81 static struct libwebsocket_protocols protocols[] = {
90 sizeof(struct per_session_data__app_list),
95 sizeof(struct per_session_data__dialer),
102 /* libwebsocket callback for handling normal http request. We use this */
103 /* to implementa a rudamentary webserver. */
105 callback_http(struct libwebsocket_context *context,
106 struct libwebsocket *wsi,
107 enum libwebsocket_callback_reasons reason, void *user,
108 void *in, size_t len)
110 char client_name[128];
117 case LWS_CALLBACK_HTTP:
118 if (in && ((char *)in)[0] == '/' && !strstr(in, "..")) {
119 if (strstr(in, "/icon/") == in) {
120 /* virtual directory used for serving up system icons */
122 char *icon = (char *)(in + 6);
123 const char *path = efreet_icon_path_find(getenv("E_ICON_THEME"),
124 (const char *)icon, 128);
126 path = efreet_icon_path_find("default", icon, 128);
128 path = efreet_icon_path_find("hicolor", icon, 128);
130 if (icon[0] == '/' && !stat(icon, &info) && S_ISREG(info.st_mode))
133 fprintf(stderr, "Can not find icon: %s\n", icon);
140 libwebsockets_serve_http_file(wsi, path,
141 (const char *)efreet_mime_type_get((char *)path));
143 snprintf(filename, 256, "%s/%s", content_directory, (char *)in + 1);
144 if (!stat(filename, &info) && S_ISDIR(info.st_mode))
145 /* try serving up index.html */
146 snprintf(filename, 256, "%s/%s/index.html",
147 content_directory, (char *)in + 1);
149 if (!stat(filename, &info) && S_ISREG(info.st_mode)) {
150 libwebsockets_serve_http_file(wsi, filename,
151 (const char *)efreet_mime_type_get(filename));
153 /* TODO: Serve up error html page */
154 fprintf(stderr, "Unable to find %s\n", filename);
158 fprintf(stderr, "Invalid request \'%s\'\n", (char *)in);
162 case LWS_CALLBACK_ADD_POLL_FD:
163 pollfds[count_pollfds].fd = (int)(long)user;
164 pollfds[count_pollfds].events = (int)len;
165 pollfds[count_pollfds++].revents = 0;
168 case LWS_CALLBACK_DEL_POLL_FD:
169 for (n = 0; n < count_pollfds; n++)
170 if (pollfds[n].fd == (int)(long)user)
171 while (n < count_pollfds) {
172 pollfds[n] = pollfds[n + 1];
178 case LWS_CALLBACK_SET_MODE_POLL_FD:
179 for (n = 0; n < count_pollfds; n++)
180 if (pollfds[n].fd == (int)(long)user)
181 pollfds[n].events |= (int)(long)len;
184 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
185 for (n = 0; n < count_pollfds; n++)
186 if (pollfds[n].fd == (int)(long)user)
187 pollfds[n].events &= ~(int)(long)len;
198 dial_reply(void *data, DBusMessage *reply, DBusError *error)
200 if (dbus_error_is_set(error)) {
201 fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
206 /* libwebsocket callback for the 'protocol-dialer' protocol */
208 callback_dialer(struct libwebsocket_context *context,
209 struct libwebsocket *wsi,
210 enum libwebsocket_callback_reasons reason,
211 void *user, void *in, size_t len)
217 case LWS_CALLBACK_BROADCAST:
219 * This is triggered on all open websocket connections
220 * when libwebsocket_broadcast is called. For this
221 * protocol we ignore the 'in' paramter and send out
222 * the pending event queue
225 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
227 LWS_SEND_BUFFER_POST_PADDING);
228 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
229 int n = sprintf((char *)p, "%s", (char *)in);
231 libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
237 case LWS_CALLBACK_ESTABLISHED:
239 if (voicecallmanager_path) {
240 /* inform any connections about the addition of the modem */
241 json_t *o = json_object();
243 json_object_set(o, "type", json_string("ModemAdded"));
244 json_object_set(o, "id", json_string(voicecallmanager_path));
245 char *dump = json_dumps(o, 0);
247 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
249 LWS_SEND_BUFFER_POST_PADDING);
250 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
251 int n = sprintf((char *)p, "%s", dump);
253 libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
258 case LWS_CALLBACK_RECEIVE:
260 * The other end of the websocket is sending us text
261 * so attempt to parse that text as a JSON object providing
262 * either a single command object or an array of command
265 if (in && (o = json_loads((char *)in, 0, NULL))) {
266 if (json_is_array(o)) {
267 for (i = 0; i < json_array_size(o); i++)
268 handle_command(json_array_get(o, i));
272 json_object_clear(o);
284 /* Write a serialized JSON object containing */
285 /* a list of objects each describing an application */
286 void send_app_list(struct libwebsocket *socket)
289 * WARNING: libwebsockets requires prefixing and post
290 * fixing the send buffer with enough space
291 * for the library to insert required websocket
292 * protocol information.
293 * Ignoring this will result in segfaults.
295 char *dump = json_dumps(app_list, 0);
296 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
298 LWS_SEND_BUFFER_POST_PADDING);
299 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
300 int n = sprintf((char *)p, "%s", dump);
302 libwebsocket_write(socket, p, n, LWS_WRITE_TEXT);
308 /* command object parser */
309 void handle_command(json_t *o)
311 const char *cmd = json_string_value(json_object_get(o, "cmd"));
316 if (strncmp(cmd, "launch", 6) == 0 && (tmp = json_object_get(o, "index"))) {
317 int index = json_integer_value(tmp);
318 json_t *app = json_array_get(app_list, index);
322 json_t *exec = json_object_get(app, "exec");
326 ecore_exe_run(json_string_value(exec), NULL);
327 } else if (strncmp(cmd, "makeCall", 8) == 0 &&
328 (tmp = json_object_get(o, "remotePartyId"))) {
329 const char *remotePartyId = json_string_value(tmp);
330 const char *hideCallerId = "";
333 msg = dbus_message_new_method_call(
335 voicecallmanager_path,
336 "org.ofono.VoiceCallManager",
339 dbus_message_append_args(msg,
340 DBUS_TYPE_STRING, &remotePartyId,
342 dbus_message_append_args(msg,
343 DBUS_TYPE_STRING, &hideCallerId,
345 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
346 } else if (strncmp(cmd, "end", 3) == 0) {
347 const char *path = json_string_value(json_object_get(o, "path"));
350 msg = dbus_message_new_method_call(
353 "org.ofono.VoiceCall",
356 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
358 } else if (strncmp(cmd, "accept", 6) == 0) {
359 const char *path = json_string_value(json_object_get(o, "path"));
362 msg = dbus_message_new_method_call(
365 "org.ofono.VoiceCall",
368 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
372 /* libwebsocket callback for 'protocol-app-list' based websockets */
374 callback_app_list(struct libwebsocket_context *context,
375 struct libwebsocket *wsi,
376 enum libwebsocket_callback_reasons reason,
377 void *user, void *in, size_t len)
383 case LWS_CALLBACK_BROADCAST:
385 * This is triggered on all open websocket connections
386 * when libwebsocket_broadcast is called. For this
387 * protocol we only do one possible thing on broadcast
388 * so just ignore the incoming 'in' string and just
389 * send a new list of applications.
391 case LWS_CALLBACK_ESTABLISHED:
395 case LWS_CALLBACK_RECEIVE:
397 * The other end of the websocket is sending us text
398 * so attempt to parse that text as a JSON object providing
399 * either a single command object or an array of command
402 if (in && (o = json_loads((char *)in, 0, NULL))) {
403 if (json_is_array(o)) {
404 for (i = 0; i < json_array_size(o); i++)
405 handle_command(json_array_get(o, i));
409 json_object_clear(o);
421 void build_app_list(char *dir) {
423 const char *filename;
424 const Eina_File_Direct_Info *info;
426 json_array_clear(app_list);
429 it = eina_file_ls(dir);
430 EINA_ITERATOR_FOREACH(it, filename)
432 if (!eina_str_has_suffix(filename, ".desktop"))
435 Efreet_Desktop *desktop = efreet_desktop_get(filename);
436 if (!desktop || !desktop->name || !desktop->icon || !desktop->exec)
439 json_t *o = json_object();
440 json_object_set(o, "id", json_integer(index++));
441 json_object_set(o, "version", json_string("0.1"));
442 json_object_set(o, "show", json_boolean(1));
443 json_object_set(o, "name", json_string(desktop->name));
444 json_object_set(o, "iconPath", json_string(desktop->icon));
445 json_object_set(o, "exec", json_string(desktop->exec));
446 json_array_append(app_list, o);
448 eina_iterator_free(it);
451 #define BUFF_SIZE ((sizeof(struct inotify_event)+FILENAME_MAX)*1024)
452 static Eina_Bool poll_descriptors(void *data)
456 char buff[BUFF_SIZE] = {0};
458 n = poll(pollfds, count_pollfds, 100);
460 if (pollfds[0].revents) {
461 /* The first descriptor what inotify returns*/
462 len = read(pollfds[0].fd, buff, BUFF_SIZE);
464 struct inotify_event *pevent = (struct inotify_event *)&buff[i];
465 if (pevent->name && eina_str_has_suffix(pevent->name, ".desktop")) {
466 build_app_list(apps_directory);
468 /* Trigger the broadcast callback for all open connectinos */
469 libwebsockets_broadcast(&protocols[PROTOCOL_APP_LIST], "x", 2);
473 i += sizeof(struct inotify_event) + pevent->len;
476 if (count_pollfds > 1) {
477 for (n = 1; n < count_pollfds; n++)
478 if (pollfds[n].revents)
479 libwebsocket_service_fd(context, &pollfds[n]);
483 return ECORE_CALLBACK_RENEW;
486 void call_properties_changed_cb(void *data, DBusMessage *msg)
488 DBusMessageIter iter, value;
489 const char *property, *v;
494 dbus_message_iter_init(msg, &iter);
495 dbus_message_iter_get_basic(&iter, &property);
497 dbus_message_iter_next(&iter);
498 dbus_message_iter_recurse(&iter, &value);
500 dbus_message_iter_get_basic(&value, &v);
502 object = json_object();
503 json_object_set(object, "type", json_string("PropertyChanged"));
504 json_object_set(object, "id", json_string(dbus_message_get_path(msg)));
505 json_object_set(object, property, json_string(v));
506 dump = json_dumps(object, 0);
507 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
511 void modem_properties_changed_cb(void *data, DBusMessage *msg)
513 DBusMessageIter iter, values, entry;
514 const char *property, *v;
519 dbus_message_iter_init(msg, &iter);
520 dbus_message_iter_get_basic(&iter, &property);
522 if (!strcasecmp(property, "Interfaces")) {
523 dbus_message_iter_next(&iter);
524 dbus_message_iter_recurse(&iter, &values);
526 while (dbus_message_iter_get_arg_type(&values) == DBUS_TYPE_ARRAY) {
527 DBusMessageIter variant;
528 dbus_message_iter_recurse(&iter, &entry);
529 dbus_message_iter_recurse(&entry, &variant);
530 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_INVALID) {
531 dbus_message_iter_get_basic(&variant, &v);
532 if (!strcasecmp(v, "org.ofono.VoiceCallManager")) {
533 object = json_object();
534 json_object_set(object, "type", json_string("ModemAdded"));
535 json_object_set(object, "id", json_string(dbus_message_get_path(msg)));
536 dump = json_dumps(object, 0);
537 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
541 const char *path = strdup(dbus_message_get_path(msg));
543 // If the modem has removed all interfaces then its no longer
544 // available for making calls
545 object = json_object();
546 json_object_set(object, "type", json_string("ModemRemoved"));
547 json_object_set(object, "id", json_string(path));
548 dump = json_dumps(object, 0);
549 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
552 // Start polling to re-enable the modem
553 power_on_modem((void *)path);
555 dbus_message_iter_next(&values);
560 void call_added_cb(void *data, DBusMessage *msg)
562 DBusMessageIter iter, properties;
565 dbus_message_iter_init(msg, &iter);
566 dbus_message_iter_get_basic(&iter, &path);
568 dbus_message_iter_next(&iter);
569 dbus_message_iter_recurse(&iter, &properties);
571 json_t *object = json_object();
572 json_object_set(object, "id", json_string(path));
573 json_object_set(object, "type", json_string("CallAdded"));
575 while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
576 DBusMessageIter entry, value;
579 dbus_message_iter_recurse(&properties, &entry);
580 dbus_message_iter_get_basic(&entry, &key);
582 dbus_message_iter_next(&entry);
583 dbus_message_iter_recurse(&entry, &value);
585 dbus_message_iter_get_basic(&value, &v);
587 json_object_set(object, key, json_string(v));
589 dbus_message_iter_next(&properties);
592 e_dbus_signal_handler_add(bus,
595 "org.ofono.VoiceCall",
597 call_properties_changed_cb,
600 char *dump = json_dumps(object, 0);
601 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
605 void call_removed_cb(void *data, DBusMessage *msg)
607 DBusMessageIter iter;
610 dbus_message_iter_init(msg, &iter);
611 dbus_message_iter_get_basic(&iter, &path);
613 json_t *object = json_object();
614 json_object_set(object, "id", json_string(path));
615 json_object_set(object, "type", json_string("CallRemoved"));
616 char *dump = json_dumps(object, 0);
617 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
621 void modem_added_cb(void *data, DBusMessage *msg)
624 * TODO: Verify the modem is powered on before sending a ModemAdded
625 * event, and if the modem is not powered then go ahead and
626 * ask ofono to power on the modem
628 DBusMessageIter iter;
631 dbus_message_iter_init(msg, &iter);
632 dbus_message_iter_get_basic(&iter, &path);
634 json_t *object = json_object();
635 json_object_set(object, "type", json_string("ModemAdded"));
636 json_object_set(object, "id", json_string(path));
637 char *dump = json_dumps(object, 0);
638 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
642 void modem_removed_cb(void *data, DBusMessage *msg)
644 DBusMessageIter entry;
645 dbus_message_iter_init(msg, &entry);
646 setup_new_modem(entry);
651 power_on_modem(void *data)
653 const char *path = (const char *)data;
655 DBusMessageIter iter, sub;
656 msg = dbus_message_new_method_call(
662 const char *property = "Powered";
665 sig[0] = DBUS_TYPE_BOOLEAN;
668 dbus_message_append_args(msg,
669 DBUS_TYPE_STRING, &property,
672 dbus_message_iter_init_append(msg, &iter);
673 if (dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, sig, &sub)) {
674 dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &p);
675 dbus_message_iter_close_container(&iter, &sub);
677 e_dbus_message_send(bus, msg, set_powered_cb, -1, (void *)path);
680 return ECORE_CALLBACK_DONE;
684 set_powered_cb(void *data, DBusMessage *reply, DBusError *error)
686 if (dbus_error_is_set(error)) {
688 * Attempting to power on a modem when the associated phone
689 * is out of range will result in this call failing. Since we
690 * expect a users phone to automatically be enabled when the
691 * user comes within range then we start a periodic attempt
694 const char *path = (const char *)data;
695 ecore_poller_add(ECORE_POLLER_CORE, 4, power_on_modem, (void *)path);
700 void setup_new_modem(DBusMessageIter entry)
702 while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) {
704 DBusMessageIter item, properties;
706 dbus_message_iter_recurse(&entry, &item);
707 dbus_message_iter_get_basic(&item, &path);
709 printf("registering %s\n", path);
710 e_dbus_signal_handler_add(bus, "org.ofono",
711 path, "org.ofono.Modem", "PropertyChanged",
712 modem_properties_changed_cb, (void *)strdup(path));
713 e_dbus_signal_handler_add(bus, "org.ofono",
714 path, "org.ofono.VoiceCallManager", "CallAdded",
715 call_added_cb, NULL);
716 e_dbus_signal_handler_add(bus, "org.ofono",
717 path, "org.ofono.VoiceCallManager",
719 call_removed_cb, NULL);
721 dbus_message_iter_next(&item);
722 dbus_message_iter_recurse(&item, &properties);
726 while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
728 DBusMessageIter interfaces, value, entry;
730 dbus_message_iter_recurse(&properties, &entry);
731 dbus_message_iter_get_basic(&entry, &key);
733 dbus_message_iter_next(&entry);
734 dbus_message_iter_recurse(&entry, &value);
736 if (!strcasecmp(key, "Interfaces")) {
737 dbus_message_iter_recurse(&value, &interfaces);
738 while (dbus_message_iter_get_arg_type(&interfaces) ==
742 dbus_message_iter_get_basic(&interfaces, &iface);
744 if (!strcasecmp(iface, "org.ofono.VoiceCallManager")) {
746 if (voicecallmanager_path)
747 free(voicecallmanager_path);
748 voicecallmanager_path = strdup(path);
750 /* inform any connections about the addition of the modem */
751 json_t *o = json_object();
752 json_object_set(o, "type", json_string("ModemAdded"));
753 json_object_set(o, "id", json_string(path));
754 char *dump = json_dumps(o, 0);
755 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
759 dbus_message_iter_next(&interfaces);
763 dbus_message_iter_next(&properties);
767 power_on_modem(strdup(path));
770 dbus_message_iter_next(&entry);
775 get_modems_reply(void *data, DBusMessage *reply, DBusError *error)
777 DBusMessageIter iter, entry;
778 if (dbus_error_is_set(error)) {
779 fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
783 dbus_message_iter_init(reply, &iter);
784 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
785 fprintf(stderr, "Unexpeced signature from ofono GetModems call\n");
789 dbus_message_iter_recurse(&iter, &entry);
791 setup_new_modem(entry);
794 void initialize_ofono(void)
796 static int ofono_initialized = 0;
798 if (ofono_initialized > 0)
802 fprintf(stderr, "Initializing phone stack\n");
804 /* Start listening for incoming phone calls */
806 msg = dbus_message_new_method_call(
812 e_dbus_message_send(bus, msg, get_modems_reply, -1, NULL);
814 e_dbus_signal_handler_add(bus, "org.ofono", "/", "org.ofono.Manager", "ModemAdded",
815 modem_added_cb, NULL);
816 e_dbus_signal_handler_add(bus, "org.ofono", "/", "org.ofono.Manager", "ModemRemoved",
817 modem_removed_cb, NULL);
820 static struct option options[] = {
821 { "help", no_argument, NULL, 'h' },
822 { "port", required_argument, NULL, 'p' },
823 { "appsdir", required_argument, NULL, 'a' },
824 { "contentdir", required_argument, NULL, 'c' },
828 int main(int argc, char **argv)
840 ecore_app_args_set(argc, (const char **)argv);
842 /* parse command-line options */
844 n = getopt_long(argc, argv, "hp:d:", options, NULL);
849 apps_directory = strdup(optarg);
852 content_directory = strdup(optarg);
859 fprintf(stderr, "Usage: %s [--port=<p>] [--appsdir=<a>] [--contentdir=<c>]\n", argv[0]);
864 apps_directory = APPDIR;
865 if (!content_directory)
866 content_directory = LOCAL_RESOURCE_PATH;
868 /* Initialize and build up the application list */
869 app_list = json_array();
870 build_app_list(apps_directory);
872 bus = e_dbus_bus_get(DBUS_BUS_SYSTEM);
874 /* Listen for updates on application desktop files*/
876 inotify_add_watch(fd, APPDIR, IN_CREATE | IN_DELETE | IN_MODIFY);
878 pollfds[0].events = POLLIN;
879 pollfds[0].revents = 0;
882 /* Start listening for incoming websocket connections */
883 context = libwebsocket_create_context(port, NULL, protocols,
884 libwebsocket_internal_extensions,
885 NULL, NULL, -1, -1, opts);
886 if (context == NULL) {
887 fprintf(stderr, "libwebsocket init failed\n");
891 ecore_idler_add(poll_descriptors, NULL);
893 /* configure poller for periodically attempting */
894 /* to enabled modems */
895 ecore_poller_poll_interval_set(ECORE_POLLER_CORE, 1.0);
897 ecore_main_loop_begin();
900 libwebsocket_context_destroy(context);
905 /* Local Variables: */
907 /* c-basic-offset:4 */
908 /* indent-tabs-mode: nil */