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 json_t *event_queue = NULL;
41 char *voicecallmanager_path = NULL;
43 #define MAX_POLL_ELEMENTS 100
44 struct pollfd pollfds[100];
45 int count_pollfds = 0;
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);
66 struct per_session_data__app_list {
69 struct per_session_data__dialer {
73 static struct libwebsocket_protocols protocols[] = {
82 sizeof(struct per_session_data__app_list),
87 sizeof(struct per_session_data__dialer),
94 /* libwebsocket callback for handling normal http request. We use this */
95 /* to implementa a rudamentary webserver. */
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)
102 char client_name[128];
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 */
114 char *icon = (char *)(in + 6);
115 const char *path = efreet_icon_path_find(getenv("E_ICON_THEME"),
116 (const char *)icon, 128);
118 path = efreet_icon_path_find("default", icon, 128);
120 path = efreet_icon_path_find("hicolor", icon, 128);
122 if (icon[0] == '/' && !stat(icon, &info) && S_ISREG(info.st_mode))
125 fprintf(stderr, "Can not find icon: %s\n", icon);
132 libwebsockets_serve_http_file(wsi, path,
133 (const char *)efreet_mime_type_get((char *)path));
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));
143 fprintf(stderr, "Failed to find content: %s\n", (char *)in);
146 fprintf(stderr, "Invalid request \'%s\'\n", (char *)in);
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;
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];
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;
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;
186 dial_reply(void *data, DBusMessage *reply, DBusError *error)
188 if (dbus_error_is_set(error)) {
189 fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
194 /* libwebsocket callback for the 'protocol-dialer' protocol */
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)
205 case LWS_CALLBACK_BROADCAST:
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
213 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
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);
219 libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
225 case LWS_CALLBACK_ESTABLISHED:
226 if (voicecallmanager_path) {
227 /* inform any connections about the addition of the modem */
228 json_t *o = json_object();
229 json_object_set(o, "ModemAdded", json_string(voicecallmanager_path));
230 char *dump = json_dumps(o, 0);
232 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
234 LWS_SEND_BUFFER_POST_PADDING);
235 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
236 int n = sprintf((char *)p, "%s", dump);
238 libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
243 case LWS_CALLBACK_RECEIVE:
245 * The other end of the websocket is sending us text
246 * so attempt to parse that text as a JSON object providing
247 * either a single command object or an array of command
250 if (in && (o = json_loads((char *)in, 0, NULL))) {
251 if (json_is_array(o)) {
252 for (i = 0; i < json_array_size(o); i++)
253 handle_command(json_array_get(o, i));
257 json_object_clear(o);
269 /* Write a serialized JSON object containing */
270 /* a list of objects each describing an application */
271 void send_app_list(struct libwebsocket *socket)
274 * WARNING: libwebsockets requires prefixing and post
275 * fixing the send buffer with enough space
276 * for the library to insert required websocket
277 * protocol information.
278 * Ignoring this will result in segfaults.
280 char *dump = json_dumps(app_list, 0);
281 unsigned char *b = malloc(LWS_SEND_BUFFER_PRE_PADDING +
283 LWS_SEND_BUFFER_POST_PADDING);
284 unsigned char *p = b + LWS_SEND_BUFFER_PRE_PADDING;
285 int n = sprintf((char *)p, "%s", dump);
287 libwebsocket_write(socket, p, n, LWS_WRITE_TEXT);
293 /* command object parser */
294 void handle_command(json_t *o)
296 const char *cmd = json_string_value(json_object_get(o, "cmd"));
301 if (strncmp(cmd, "launch", 6) == 0 && (tmp = json_object_get(o, "index"))) {
302 int index = json_integer_value(tmp);
303 json_t *app = json_array_get(app_list, index);
307 json_t *exec = json_object_get(app, "exec");
311 ecore_exe_run(json_string_value(exec), NULL);
312 } else if (strncmp(cmd, "makeCall", 8) == 0 &&
313 (tmp = json_object_get(o, "remotePartyId"))) {
314 const char *remotePartyId = json_string_value(tmp);
315 const char *hideCallerId = "";
318 msg = dbus_message_new_method_call(
320 voicecallmanager_path,
321 "org.ofono.VoiceCallManager",
324 dbus_message_append_args(msg,
325 DBUS_TYPE_STRING, &remotePartyId,
327 dbus_message_append_args(msg,
328 DBUS_TYPE_STRING, &hideCallerId,
330 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
331 } else if (strncmp(cmd, "end", 3) == 0) {
333 msg = dbus_message_new_method_call(
335 voicecallmanager_path,
336 "org.ofono.VoiceCallManager",
339 e_dbus_message_send(bus, msg, dial_reply, -1, NULL);
344 /* libwebsocket callback for 'protocol-app-list' based websockets */
346 callback_app_list(struct libwebsocket_context *context,
347 struct libwebsocket *wsi,
348 enum libwebsocket_callback_reasons reason,
349 void *user, void *in, size_t len)
355 case LWS_CALLBACK_BROADCAST:
357 * This is triggered on all open websocket connections
358 * when libwebsocket_broadcast is called. For this
359 * protocol we only do one possible thing on broadcast
360 * so just ignore the incoming 'in' string and just
361 * send a new list of applications.
363 case LWS_CALLBACK_ESTABLISHED:
367 case LWS_CALLBACK_RECEIVE:
369 * The other end of the websocket is sending us text
370 * so attempt to parse that text as a JSON object providing
371 * either a single command object or an array of command
374 if (in && (o = json_loads((char *)in, 0, NULL))) {
375 if (json_is_array(o)) {
376 for (i = 0; i < json_array_size(o); i++)
377 handle_command(json_array_get(o, i));
381 json_object_clear(o);
393 void build_app_list(char *dir) {
395 const char *filename;
396 const Eina_File_Direct_Info *info;
398 json_array_clear(app_list);
401 it = eina_file_ls(dir);
402 EINA_ITERATOR_FOREACH(it, filename)
404 if (!eina_str_has_suffix(filename, ".desktop"))
407 Efreet_Desktop *desktop = efreet_desktop_get(filename);
408 if (!desktop || !desktop->name || !desktop->icon || !desktop->exec)
411 json_t *o = json_object();
412 json_object_set(o, "id", json_integer(index++));
413 json_object_set(o, "version", json_string("0.1"));
414 json_object_set(o, "show", json_boolean(1));
415 json_object_set(o, "name", json_string(desktop->name));
416 json_object_set(o, "iconPath", json_string(desktop->icon));
417 json_object_set(o, "exec", json_string(desktop->exec));
418 json_array_append(app_list, o);
420 eina_iterator_free(it);
423 #define BUFF_SIZE ((sizeof(struct inotify_event)+FILENAME_MAX)*1024)
424 static Eina_Bool poll_descriptors(void *data)
428 char buff[BUFF_SIZE] = {0};
430 n = poll(pollfds, count_pollfds, 100);
432 if (pollfds[0].revents) {
433 /* The first descriptor what inotify returns*/
434 len = read(pollfds[0].fd, buff, BUFF_SIZE);
436 struct inotify_event *pevent = (struct inotify_event *)&buff[i];
437 if (pevent->name && eina_str_has_suffix(pevent->name, ".desktop")) {
438 build_app_list(apps_directory);
440 /* Trigger the broadcast callback for all open connectinos */
441 libwebsockets_broadcast(&protocols[PROTOCOL_APP_LIST], "x", 2);
445 i += sizeof(struct inotify_event) + pevent->len;
448 if (count_pollfds > 1) {
449 for (n = 1; n < count_pollfds; n++)
450 if (pollfds[n].revents)
451 libwebsocket_service_fd(context, &pollfds[n]);
455 return ECORE_CALLBACK_RENEW;
458 void call_properties_changed_cb(void *data, DBusMessage *msg)
460 DBusMessageIter iter, value;
461 const char *property, *v;
466 dbus_message_iter_init(msg, &iter);
467 dbus_message_iter_get_basic(&iter, &property);
469 dbus_message_iter_next(&iter);
470 dbus_message_iter_recurse(&iter, &value);
472 dbus_message_iter_get_basic(&value, &v);
474 object = json_object();
475 json_object_set(object, property, json_string(v));
476 dump = json_dumps(object, 0);
477 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
481 void modem_properties_changed_cb(void *data, DBusMessage *msg)
483 DBusMessageIter iter, values, entry;
484 const char *property, *v;
489 dbus_message_iter_init(msg, &iter);
490 dbus_message_iter_get_basic(&iter, &property);
492 if (!strcasecmp(property, "Interfaces")) {
493 dbus_message_iter_next(&iter);
494 dbus_message_iter_recurse(&iter, &values);
496 while (dbus_message_iter_get_arg_type(&values) == DBUS_TYPE_ARRAY) {
497 DBusMessageIter variant;
498 dbus_message_iter_recurse(&iter, &entry);
499 dbus_message_iter_recurse(&entry, &variant);
500 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_INVALID) {
501 dbus_message_iter_get_basic(&variant, &v);
502 if (!strcasecmp(v, "org.ofono.VoiceCallManager")) {
503 object = json_object();
504 json_object_set(object, "ModemAdded", json_string(dbus_message_get_path(msg)));
505 dump = json_dumps(object, 0);
506 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
510 // If the modem has removed all interfaces then its no longer
511 // available for making calls
512 object = json_object();
513 json_object_set(object, "ModemRemoved", json_string(dbus_message_get_path(msg)));
514 dump = json_dumps(object, 0);
515 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
518 dbus_message_iter_next(&values);
523 void call_added_cb(void *data, DBusMessage *msg)
525 DBusMessageIter iter, properties;
528 json_t *object = json_object();
530 dbus_message_iter_init(msg, &iter);
531 dbus_message_iter_get_basic(&iter, &path);
533 dbus_message_iter_next(&iter);
534 dbus_message_iter_recurse(&iter, &properties);
536 while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
537 DBusMessageIter entry, value;
540 dbus_message_iter_recurse(&properties, &entry);
541 dbus_message_iter_get_basic(&entry, &key);
543 dbus_message_iter_next(&entry);
544 dbus_message_iter_recurse(&entry, &value);
546 dbus_message_iter_get_basic(&value, &v);
548 json_object_set(object, key, json_string(v));
550 dbus_message_iter_next(&properties);
553 e_dbus_signal_handler_add(bus,
556 "org.ofono.VoiceCall",
558 call_properties_changed_cb,
561 char *dump = json_dumps(object, 0);
562 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
566 void call_removed_cb(void *data, DBusMessage *msg)
568 json_t *object = json_object();
569 json_object_set(object, "type", json_string("CallRemoved"));
570 char *dump = json_dumps(object, 0);
571 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
575 void modem_added_cb(void *data, DBusMessage *msg)
577 DBusMessageIter iter;
580 dbus_message_iter_init(msg, &iter);
581 dbus_message_iter_get_basic(&iter, &path);
583 json_t *object = json_object();
584 json_object_set(object, "ModemAdded", json_string(path));
585 char *dump = json_dumps(object, 0);
586 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
590 void modem_removed_cb(void *data, DBusMessage *msg)
592 DBusMessageIter iter;
595 dbus_message_iter_init(msg, &iter);
596 dbus_message_iter_get_basic(&iter, &path);
598 json_t *object = json_object();
599 json_object_set(object, "ModemRemoved", json_string(path));
600 char *dump = json_dumps(object, 0);
601 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
606 get_modems_reply(void *data, DBusMessage *reply, DBusError *error)
608 DBusMessageIter iter, entry;
609 if (dbus_error_is_set(error)) {
610 fprintf(stderr, "Error: %s - %s\n", error->name, error->message);
614 dbus_message_iter_init(reply, &iter);
615 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
616 fprintf(stderr, "Unexpeced signature from ofono GetModems call\n");
620 dbus_message_iter_recurse(&iter, &entry);
622 while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRUCT) {
624 DBusMessageIter item, properties;
626 dbus_message_iter_recurse(&entry, &item);
627 dbus_message_iter_get_basic(&item, &path);
629 printf("registering %s\n", path);
630 e_dbus_signal_handler_add(bus, "org.ofono",
631 path, "org.ofono.Modem", "PropertyChanged",
632 modem_properties_changed_cb, (void *)strdup(path));
633 e_dbus_signal_handler_add(bus, "org.ofono",
634 path, "org.ofono.VoiceCallManager", "CallAdded",
635 call_added_cb, NULL);
636 e_dbus_signal_handler_add(bus, "org.ofono",
637 path, "org.ofono.VoiceCallManager",
639 call_removed_cb, NULL);
641 dbus_message_iter_next(&item);
642 dbus_message_iter_recurse(&item, &properties);
644 while (dbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_DICT_ENTRY) {
646 DBusMessageIter interfaces, value, entry;
648 dbus_message_iter_recurse(&properties, &entry);
649 dbus_message_iter_get_basic(&entry, &key);
651 dbus_message_iter_next(&entry);
652 dbus_message_iter_recurse(&entry, &value);
654 if (!strcasecmp(key, "Interfaces")) {
655 dbus_message_iter_recurse(&value, &interfaces);
656 while (dbus_message_iter_get_arg_type(&interfaces) ==
660 dbus_message_iter_get_basic(&interfaces, &iface);
662 if (!strcasecmp(iface, "org.ofono.VoiceCallManager")) {
663 if (voicecallmanager_path)
664 free(voicecallmanager_path);
665 voicecallmanager_path = strdup(path);
667 /* inform any connections about the addition of the modem */
668 json_t *o = json_object();
669 json_object_set(o, "ModemAdded", json_string(path));
670 char *dump = json_dumps(o, 0);
671 libwebsockets_broadcast(&protocols[PROTOCOL_DIALER], dump, strlen(dump));
675 dbus_message_iter_next(&interfaces);
679 dbus_message_iter_next(&properties);
682 dbus_message_iter_next(&entry);
686 static struct option options[] = {
687 { "help", no_argument, NULL, 'h' },
688 { "port", required_argument, NULL, 'p' },
689 { "directory", required_argument, NULL, 'd' },
693 int main(int argc, char **argv)
705 ecore_app_args_set(argc, (const char **)argv);
707 /* parse command-line options */
709 n = getopt_long(argc, argv, "hp:d:", options, NULL);
714 apps_directory = strdup(optarg);
720 fprintf(stderr, "Usage: %s [--port=<p>] [--directory=<d>]\n", argv[0]);
725 apps_directory = APPDIR;
727 /* Initialize and build up the application list */
728 app_list = json_array();
729 build_app_list(apps_directory);
731 bus = e_dbus_bus_get(DBUS_BUS_SYSTEM);
733 /* Start listening for incoming phone calls */
735 msg = dbus_message_new_method_call(
741 e_dbus_message_send(bus, msg, get_modems_reply, -1, NULL);
743 e_dbus_signal_handler_add(bus, "org.ofono", "/", "org.ofono.Manager", "ModemAdded",
744 modem_added_cb, NULL);
745 e_dbus_signal_handler_add(bus, "org.ofono", "/", "org.ofono.Manager", "ModemRemoved",
746 modem_removed_cb, NULL);
748 /* Listen for updates on application desktop files*/
750 inotify_add_watch(fd, APPDIR, IN_CREATE | IN_DELETE | IN_MODIFY);
752 pollfds[0].events = POLLIN;
753 pollfds[0].revents = 0;
756 /* Start listening for incoming websocket connections */
757 context = libwebsocket_create_context(port, NULL, protocols,
758 libwebsocket_internal_extensions,
759 NULL, NULL, -1, -1, opts);
760 if (context == NULL) {
761 fprintf(stderr, "libwebsocket init failed\n");
765 ecore_idler_add(poll_descriptors, NULL);
767 ecore_main_loop_begin();
770 libwebsocket_context_destroy(context);
775 /* Local Variables: */
777 /* c-basic-offset:4 */
778 /* indent-tabs-mode: nil */