summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app_tracker.c343
-rw-r--r--src/dbus_gesture_adapter.c215
-rw-r--r--src/elm_access_adapter.c65
-rw-r--r--src/flat_navi.c693
-rw-r--r--src/keyboard_tracker.c179
-rw-r--r--src/main.c272
-rw-r--r--src/navigator.c2292
-rw-r--r--src/pivot_chooser.c142
-rw-r--r--src/screen_reader.c87
-rw-r--r--src/screen_reader_gestures.c1126
-rw-r--r--src/screen_reader_haptic.c98
-rw-r--r--src/screen_reader_spi.c329
-rw-r--r--src/screen_reader_switch.c126
-rw-r--r--src/screen_reader_system.c670
-rw-r--r--src/screen_reader_tts.c347
-rw-r--r--src/screen_reader_vconf.c119
-rw-r--r--src/smart_notification.c192
-rw-r--r--src/window_tracker.c130
18 files changed, 7425 insertions, 0 deletions
diff --git a/src/app_tracker.c b/src/app_tracker.c
new file mode 100644
index 0000000..b88389f
--- /dev/null
+++ b/src/app_tracker.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "app_tracker.h"
+#include "screen_reader_tts.h"
+#include "logger.h"
+
+typedef struct {
+ AtspiAccessible *base_root;
+ AtspiAccessible *root;
+ GList *callbacks;
+ guint timer;
+} SubTreeRootData;
+
+typedef struct {
+ AppTrackerEventCB func;
+ AtspiAccessible *root;
+ void *user_data;
+} EventCallbackData;
+
+#define APP_TRACKER_INVACTIVITY_TIMEOUT 200
+
+static int _init_count;
+static GList *_roots;
+static AtspiEventListener *_listener;
+static AppTrackerEventCB _new_obj_highlighted_callback;
+
+static int _is_descendant(AtspiAccessible * ancestor, AtspiAccessible * descendant)
+{
+ return 1;
+
+#if 0
+ do {
+ if (ancestor == descendant)
+ return 1;
+ }
+ while ((descendant = atspi_accessible_get_parent(descendant, NULL)) != NULL);
+
+ return 0;
+#endif
+}
+
+static Eina_Bool _object_has_modal_state(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+
+ if (atspi_state_set_contains(ss, ATSPI_STATE_MODAL))
+ ret = EINA_TRUE;
+ g_object_unref(ss);
+ return ret;
+}
+
+static Eina_Bool _object_has_showing_state(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+
+ if (atspi_state_set_contains(ss, ATSPI_STATE_SHOWING))
+ ret = EINA_TRUE;
+ g_object_unref(ss);
+ return ret;
+}
+
+static Eina_Bool _object_has_highlighted_state(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+
+ if (atspi_state_set_contains(ss, ATSPI_STATE_HIGHLIGHTED))
+ ret = EINA_TRUE;
+ g_object_unref(ss);
+ return ret;
+}
+
+static void _subtree_callbacks_call(SubTreeRootData * std)
+{
+ DEBUG("START");
+ GList *l;
+ EventCallbackData *ecd;
+
+ for (l = std->callbacks; l != NULL; l = l->next) {
+ ecd = l->data;
+ ecd->func(std->root, ecd->user_data);
+ }
+ DEBUG("END");
+}
+
+static gboolean _on_timeout_cb(gpointer user_data)
+{
+ DEBUG("START");
+ SubTreeRootData *std = user_data;
+
+ _subtree_callbacks_call(std);
+
+ std->timer = 0;
+ DEBUG("END");
+ return FALSE;
+}
+
+static void _print_event_object_info(const AtspiEvent * event)
+{
+ gchar *name = atspi_accessible_get_name(event->source, NULL), *role = atspi_accessible_get_role_name(event->source, NULL);
+
+ DEBUG("signal:%s, name: %s, role: %s, detail1:%i, detail2: %i", event->type, role, name, event->detail1, event->detail2);
+ g_free(name);
+ g_free(role);
+}
+
+static void _on_atspi_event_cb(const AtspiEvent * event)
+{
+ GList *l;
+ SubTreeRootData *std;
+
+ if (!event)
+ return;
+ if (!event->source) {
+ ERROR("empty event source");
+ return;
+ }
+
+ if ((atspi_accessible_get_role(event->source, NULL) == ATSPI_ROLE_DESKTOP_FRAME)) {
+ return;
+ }
+
+ _print_event_object_info(event);
+
+ if (!strcmp(event->type, "object:property-change:accessible-name") && _object_has_highlighted_state(event->source)) {
+ gchar *name = atspi_accessible_get_name(event->source, NULL);
+ DEBUG("New name for object, read:%s", name);
+ tts_speak (name, EINA_TRUE);
+ g_free(name);
+ return;
+ }
+ AtspiAccessible *new_highlighted_obj = NULL;
+
+ if (!strcmp(event->type, "object:state-changed:highlighted"))
+ new_highlighted_obj = event->source;
+ else if (!strcmp(event->type, "object:active-descendant-changed"))
+ new_highlighted_obj = atspi_accessible_get_child_at_index(event->source, event->detail1, NULL);
+
+ if (new_highlighted_obj && _new_obj_highlighted_callback && _object_has_highlighted_state(new_highlighted_obj)) {
+ DEBUG("HIGHLIGHTED OBJECT IS ABOUT TO CHANGE");
+ _new_obj_highlighted_callback(new_highlighted_obj, NULL);
+ g_object_unref(new_highlighted_obj);
+ new_highlighted_obj = NULL;
+ }
+
+ if (!strcmp("object:state-changed:showing", event->type) ||
+ !strcmp("object:state-changed:visible", event->type) ||
+ !strcmp("object:state-changed:defunct", event->type)) {
+ for (l = _roots; l != NULL; l = l->next) {
+ std = l->data;
+
+ if (!_object_has_showing_state(std->root) && std->base_root) {
+ std->root = std->base_root;
+ std->base_root = NULL;
+ }
+
+ if (_is_descendant(std->root, event->source)) {
+ if (std->timer)
+ g_source_remove(std->timer);
+ DEBUG("Before Checking if modal is showing");
+ if (_object_has_modal_state(event->source)) {
+ DEBUG("Object is modal");
+ std->base_root = std->root;
+ std->root = event->source;
+ }
+ std->timer = g_timeout_add(APP_TRACKER_INVACTIVITY_TIMEOUT, _on_timeout_cb, std);
+ }
+ }
+ }
+}
+
+static int _app_tracker_init_internal(void)
+{
+ DEBUG("START");
+ _new_obj_highlighted_callback = NULL;
+ _listener = atspi_event_listener_new_simple(_on_atspi_event_cb, NULL);
+
+ atspi_event_listener_register(_listener, "object:state-changed:showing", NULL);
+ atspi_event_listener_register(_listener, "object:state-changed:visible", NULL);
+ atspi_event_listener_register(_listener, "object:state-changed:defunct", NULL);
+ atspi_event_listener_register(_listener, "object:state-changed:highlighted", NULL);
+ atspi_event_listener_register(_listener, "object:bounds-changed", NULL);
+ atspi_event_listener_register(_listener, "object:visible-data-changed", NULL);
+ atspi_event_listener_register(_listener, "object:active-descendant-changed", NULL);
+ atspi_event_listener_register(_listener, "object:property-change", NULL);
+
+ return 0;
+}
+
+static void _free_callbacks(gpointer data)
+{
+ g_free(data);
+}
+
+static void _free_rootdata(gpointer data)
+{
+ SubTreeRootData *std = data;
+ g_list_free_full(std->callbacks, _free_callbacks);
+ if (std->timer)
+ g_source_remove(std->timer);
+ g_free(std);
+}
+
+static void _app_tracker_shutdown_internal(void)
+{
+ atspi_event_listener_deregister(_listener, "object:state-changed:showing", NULL);
+ atspi_event_listener_deregister(_listener, "object:state-changed:visible", NULL);
+ atspi_event_listener_deregister(_listener, "object:state-changed:highlighted", NULL);
+ atspi_event_listener_deregister(_listener, "object:bounds-changed", NULL);
+ atspi_event_listener_deregister(_listener, "object:state-changed:defunct", NULL);
+ atspi_event_listener_deregister(_listener, "object:visible-data-changed", NULL);
+ atspi_event_listener_deregister(_listener, "object:active-descendant-changed", NULL);
+ atspi_event_listener_deregister(_listener, "object:property-change", NULL);
+
+ g_object_unref(_listener);
+ _listener = NULL;
+ _new_obj_highlighted_callback = NULL;
+ g_list_free_full(_roots, _free_rootdata);
+ _roots = NULL;
+}
+
+int app_tracker_init(void)
+{
+ DEBUG("START");
+ if (!_init_count)
+ if (_app_tracker_init_internal())
+ return -1;
+ return ++_init_count;
+}
+
+void app_tracker_shutdown(void)
+{
+ if (_init_count == 1)
+ _app_tracker_shutdown_internal();
+ if (--_init_count < 0)
+ _init_count = 0;
+}
+
+void app_tracker_callback_register(AtspiAccessible * app, AppTrackerEventCB cb, void *user_data)
+{
+ DEBUG("START");
+ SubTreeRootData *rd = NULL;
+ EventCallbackData *cd;
+ GList *l;
+
+ if (!_init_count || !cb)
+ return;
+
+ for (l = _roots; l != NULL; l = l->next) {
+ rd = l->data;
+ if (((SubTreeRootData *) l->data)->root == app) {
+ rd = l->data;
+ break;
+ }
+ }
+
+ if (!rd) {
+ rd = g_new(SubTreeRootData, 1);
+ rd->root = app;
+ rd->base_root = NULL;
+ rd->callbacks = NULL;
+ rd->timer = 0;
+ _roots = g_list_append(_roots, rd);
+ }
+
+ cd = g_new(EventCallbackData, 1);
+ cd->func = cb;
+ cd->user_data = user_data;
+
+ rd->callbacks = g_list_append(rd->callbacks, cd);
+ DEBUG("END");
+}
+
+void app_tracker_new_obj_highlighted_callback_register(AppTrackerEventCB cb)
+{
+ _new_obj_highlighted_callback = cb;
+}
+
+void app_tracker_callback_unregister(AtspiAccessible * app, AppTrackerEventCB cb, void *user_data)
+{
+ DEBUG("START");
+ GList *l;
+ EventCallbackData *ecd;
+ SubTreeRootData *std = NULL;
+
+ for (l = _roots; l != NULL; l = l->next) {
+ if (((SubTreeRootData *) l->data)->root == app || ((SubTreeRootData *) l->data)->base_root == app) {
+ std = l->data;
+ break;
+ }
+ }
+
+ if (!std)
+ return;
+
+ for (l = std->callbacks; l != NULL; l = l->next) {
+ ecd = l->data;
+ if ((ecd->func == cb) && (ecd->user_data == user_data)) {
+ std->callbacks = g_list_delete_link(std->callbacks, l);
+ break;
+ }
+ }
+
+ if (!std->callbacks) {
+ if (std->timer)
+ g_source_remove(std->timer);
+ _roots = g_list_remove(_roots, std);
+ g_free(std);
+ }
+}
+
+void app_tracker_new_obj_highlighted_callback_unregister(AppTrackerEventCB cb)
+{
+ _new_obj_highlighted_callback = NULL;
+}
diff --git a/src/dbus_gesture_adapter.c b/src/dbus_gesture_adapter.c
new file mode 100644
index 0000000..e4427ad
--- /dev/null
+++ b/src/dbus_gesture_adapter.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dbus_gesture_adapter.h"
+#include "logger.h"
+
+#include <Eldbus.h>
+
+#define E_A11Y_SERVICE_BUS_NAME "com.samsung.EModule"
+#define E_A11Y_SERVICE_NAVI_IFC_NAME "com.samsung.GestureNavigation"
+#define E_A11Y_SERVICE_NAVI_OBJ_PATH "/com/samsung/GestureNavigation"
+
+static Eldbus_Connection *conn;
+static Eldbus_Service_Interface *service;
+
+enum _Signals {
+ GESTURE_DETECTED = 0,
+};
+
+static const Eldbus_Signal _signals[] = {
+ [GESTURE_DETECTED] = {"GestureDetected", ELDBUS_ARGS({"siiiiu", NULL}), 0},
+ {NULL, ELDBUS_ARGS({NULL, NULL}), 0}
+};
+
+static const Eldbus_Service_Interface_Desc desc = {
+ E_A11Y_SERVICE_NAVI_IFC_NAME, NULL, _signals, NULL, NULL, NULL
+};
+
+void _on_get_a11y_address(void *data, const Eldbus_Message * msg, Eldbus_Pending * pending)
+{
+ const char *sock_addr;
+ const char *errname, *errmsg;
+ Eldbus_Connection *session = data;
+
+ if (eldbus_message_error_get(msg, &errname, &errmsg)) {
+ ERROR("GetAddress failed: %s %s", errname, errmsg);
+ return;
+ }
+
+ if (!eldbus_message_arguments_get(msg, "s", &sock_addr) || !sock_addr) {
+ ERROR("Could not get A11Y Bus socket address.");
+ goto end;
+ }
+
+ if (!(conn = eldbus_address_connection_get(sock_addr))) {
+ ERROR("Failed to connect to %s", sock_addr);
+ goto end;
+ }
+
+ if (!(service = eldbus_service_interface_register(conn, E_A11Y_SERVICE_NAVI_OBJ_PATH, &desc))) {
+ ERROR("Failed to register %s interface", E_A11Y_SERVICE_NAVI_IFC_NAME);
+ eldbus_connection_unref(conn);
+ conn = NULL;
+ goto end;
+ }
+
+ eldbus_name_request(conn, E_A11Y_SERVICE_BUS_NAME, ELDBUS_NAME_REQUEST_FLAG_DO_NOT_QUEUE, NULL, NULL);
+
+ end:
+ eldbus_connection_unref(session);
+}
+
+void dbus_gesture_adapter_init(void)
+{
+ Eldbus_Connection *session;
+ Eldbus_Message *msg;
+
+ eldbus_init();
+
+ if (!(session = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION))) {
+ ERROR("Unable to get session bus");
+ return;
+ }
+
+ if (!(msg = eldbus_message_method_call_new("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress"))) {
+ ERROR("DBus message allocation failed");
+ goto fail_msg;
+ }
+
+ if (!eldbus_connection_send(session, msg, _on_get_a11y_address, session, -1)) {
+ ERROR("Message send failed");
+ goto fail_send;
+ }
+
+ return;
+
+ fail_send:
+ eldbus_message_unref(msg);
+ fail_msg:
+ eldbus_connection_unref(session);
+}
+
+void dbus_gesture_adapter_shutdown(void)
+{
+ if (service)
+ eldbus_service_object_unregister(service);
+ if (conn)
+ eldbus_connection_unref(conn);
+
+ conn = NULL;
+ service = NULL;
+
+ eldbus_shutdown();
+}
+
+static const char *_gesture_enum_to_string(Gesture g)
+{
+ switch (g) {
+ case ONE_FINGER_HOVER:
+ return "OneFingerHover";
+ case TWO_FINGERS_HOVER:
+ return "TwoFingersHover";
+ case THREE_FINGERS_HOVER:
+ return "ThreeFingersHover";
+ case ONE_FINGER_FLICK_LEFT:
+ return "OneFingerFlickLeft";
+ case ONE_FINGER_FLICK_RIGHT:
+ return "OneFingerFlickRight";
+ case ONE_FINGER_FLICK_UP:
+ return "OneFingerFlickUp";
+ case ONE_FINGER_FLICK_DOWN:
+ return "OneFingerFlickDown";
+ case TWO_FINGERS_FLICK_UP:
+ return "TwoFingersFlickUp";
+ case TWO_FINGERS_FLICK_DOWN:
+ return "TwoFingersFlickDown";
+ case TWO_FINGERS_FLICK_LEFT:
+ return "TwoFingersFlickLeft";
+ case TWO_FINGERS_FLICK_RIGHT:
+ return "TwoFingersFlickRight";
+ case THREE_FINGERS_FLICK_LEFT:
+ return "ThreeFingersFlickLeft";
+ case THREE_FINGERS_FLICK_RIGHT:
+ return "ThreeFingersFlickRight";
+ case THREE_FINGERS_FLICK_UP:
+ return "ThreeFingersFlickUp";
+ case THREE_FINGERS_FLICK_DOWN:
+ return "ThreeFingersFlickDown";
+ case ONE_FINGER_SINGLE_TAP:
+ return "OneFingerSingleTap";
+ case ONE_FINGER_DOUBLE_TAP:
+ return "OneFingerDoubleTap";
+ case ONE_FINGER_TRIPLE_TAP:
+ return "OneFingerTripleTap";
+ case TWO_FINGERS_SINGLE_TAP:
+ return "TwoFingersSingleTap";
+ case TWO_FINGERS_DOUBLE_TAP:
+ return "TwoFingersDoubleTap";
+ case TWO_FINGERS_TRIPLE_TAP:
+ return "TwoFingersTripleTap";
+ case THREE_FINGERS_SINGLE_TAP:
+ return "ThreeFingersSingleTap";
+ case THREE_FINGERS_DOUBLE_TAP:
+ return "ThreeFingersDoubleTap";
+ case THREE_FINGERS_TRIPLE_TAP:
+ return "ThreeFingersTripleTap";
+ case ONE_FINGER_FLICK_LEFT_RETURN:
+ return "OneFingerFlickLeftReturn";
+ case ONE_FINGER_FLICK_RIGHT_RETURN:
+ return "OneFingerFlickRightReturn";
+ case ONE_FINGER_FLICK_UP_RETURN:
+ return "OneFingerFlickUpReturn";
+ case ONE_FINGER_FLICK_DOWN_RETURN:
+ return "OneFingerFlickDownReturn";
+ case TWO_FINGERS_FLICK_LEFT_RETURN:
+ return "TwoFingersFlickLeftReturn";
+ case TWO_FINGERS_FLICK_RIGHT_RETURN:
+ return "TwoFingersFlickRightReturn";
+ case TWO_FINGERS_FLICK_UP_RETURN:
+ return "TwoFingersFlickUpReturn";
+ case TWO_FINGERS_FLICK_DOWN_RETURN:
+ return "TwoFingersFlickDownReturn";
+ case THREE_FINGERS_FLICK_LEFT_RETURN:
+ return "ThreeFingersFlickLeftReturn";
+ case THREE_FINGERS_FLICK_RIGHT_RETURN:
+ return "ThreeFingersFlickRightReturn";
+ case THREE_FINGERS_FLICK_UP_RETURN:
+ return "ThreeFingersFlickUpReturn";
+ case THREE_FINGERS_FLICK_DOWN_RETURN:
+ return "ThreeFingersFlickDownReturn";
+ default:
+ ERROR("unhandled gesture enum");
+ return NULL;
+ }
+}
+
+void dbus_gesture_adapter_emit(const Gesture_Info * info)
+{
+ const char *name;
+
+ if (!service)
+ return;
+
+ name = _gesture_enum_to_string(info->type);
+ if (!name)
+ return;
+
+ if (!eldbus_service_signal_emit(service, GESTURE_DETECTED, name, info->x_beg, info->y_beg, info->x_end, info->y_end, info->state)) {
+ ERROR("Unable to send GestureDetected signal");
+ } else
+ DEBUG("Successfullt send GestureDetected singal");
+}
diff --git a/src/elm_access_adapter.c b/src/elm_access_adapter.c
new file mode 100644
index 0000000..fc46227
--- /dev/null
+++ b/src/elm_access_adapter.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "elm_access_adapter.h"
+#include "logger.h"
+
+static void _get_root_coords(Ecore_X_Window win, int *x, int *y)
+{
+ Ecore_X_Window root = ecore_x_window_root_first_get();
+ Ecore_X_Window parent = ecore_x_window_parent_get(win);
+ int wx, wy;
+
+ if (x)
+ *x = 0;
+ if (y)
+ *y = 0;
+
+ while (parent && (root != parent)) {
+ ecore_x_window_geometry_get(parent, &wx, &wy, NULL, NULL);
+ if (x)
+ *x += wx;
+ if (y)
+ *y += wy;
+ parent = ecore_x_window_parent_get(parent);
+ }
+}
+
+static void _send_ecore_x_client_msg(Ecore_X_Window win, int x, int y, Eina_Bool activate)
+{
+ int x_win, y_win;
+ long type;
+ _get_root_coords(win, &x_win, &y_win);
+ DEBUG("Window screen size:%d %d", x_win, y_win);
+ DEBUG("activate keyboard: %d %d", x, y);
+
+ if (activate)
+ type = ECORE_X_ATOM_E_ILLUME_ACCESS_ACTION_ACTIVATE;
+ else
+ type = ECORE_X_ATOM_E_ILLUME_ACCESS_ACTION_READ;
+
+ ecore_x_client_message32_send(win, ECORE_X_ATOM_E_ILLUME_ACCESS_CONTROL, ECORE_X_EVENT_MASK_WINDOW_CONFIGURE, win, type, x - x_win, y - y_win, 0);
+}
+
+void elm_access_adaptor_emit_activate(Ecore_X_Window win, int x, int y)
+{
+ _send_ecore_x_client_msg(win, x, y, EINA_TRUE);
+}
+
+void elm_access_adaptor_emit_read(Ecore_X_Window win, int x, int y)
+{
+ _send_ecore_x_client_msg(win, x, y, EINA_FALSE);
+}
diff --git a/src/flat_navi.c b/src/flat_navi.c
new file mode 100644
index 0000000..72aac40
--- /dev/null
+++ b/src/flat_navi.c
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flat_navi.h"
+#include "logger.h"
+
+struct _FlatNaviContext {
+ AtspiAccessible *root;
+ AtspiAccessible *current;
+ AtspiAccessible *first;
+ AtspiAccessible *last;
+};
+
+static const AtspiStateType required_states[] = {
+ ATSPI_STATE_SHOWING,
+ ATSPI_STATE_VISIBLE,
+ ATSPI_STATE_FOCUSABLE,
+ ATSPI_STATE_LAST_DEFINED
+};
+
+static const AtspiRole interesting_roles[] = {
+ ATSPI_ROLE_CALENDAR,
+ ATSPI_ROLE_CHECK_BOX,
+ ATSPI_ROLE_COLOR_CHOOSER,
+ ATSPI_ROLE_COMBO_BOX,
+ ATSPI_ROLE_DATE_EDITOR,
+ ATSPI_ROLE_DIALOG,
+ ATSPI_ROLE_FILE_CHOOSER,
+ ATSPI_ROLE_FILLER,
+ ATSPI_ROLE_FONT_CHOOSER,
+ ATSPI_ROLE_GLASS_PANE,
+ ATSPI_ROLE_HEADER,
+ ATSPI_ROLE_HEADING,
+ ATSPI_ROLE_ICON,
+ ATSPI_ROLE_ENTRY,
+ ATSPI_ROLE_LABEL,
+ ATSPI_ROLE_LINK,
+ ATSPI_ROLE_LIST_ITEM,
+ ATSPI_ROLE_MENU_ITEM,
+ ATSPI_ROLE_PANEL,
+ ATSPI_ROLE_PARAGRAPH,
+ ATSPI_ROLE_PASSWORD_TEXT,
+ ATSPI_ROLE_POPUP_MENU,
+ ATSPI_ROLE_PUSH_BUTTON,
+ ATSPI_ROLE_PROGRESS_BAR,
+ ATSPI_ROLE_RADIO_BUTTON,
+ ATSPI_ROLE_RADIO_MENU_ITEM,
+ ATSPI_ROLE_SLIDER,
+ ATSPI_ROLE_SPIN_BUTTON,
+ ATSPI_ROLE_TABLE_CELL,
+ ATSPI_ROLE_TEXT,
+ ATSPI_ROLE_TOGGLE_BUTTON,
+ ATSPI_ROLE_TOOL_TIP,
+ ATSPI_ROLE_TREE_ITEM,
+ ATSPI_ROLE_LAST_DEFINED
+};
+
+static Eina_Bool _has_escape_action(AtspiAccessible * obj)
+{
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiAction *action = NULL;
+
+ action = atspi_accessible_get_action_iface(obj);
+ if (action) {
+ int i = 0;
+ for (; i < atspi_action_get_n_actions(action, NULL); i++) {
+ gchar *action_name = atspi_action_get_action_name(action, i, NULL);
+ Eina_Bool equal = !strcmp(action_name, "escape");
+ g_free(action_name);
+ if (equal) {
+ ret = EINA_TRUE;
+ break;
+ }
+ }
+ g_object_unref(action);
+ }
+ DEBUG("Obj %s %s escape action", atspi_accessible_get_role_name(obj, NULL), ret ? "has" : "doesn't have");
+ return ret;
+}
+
+static Eina_Bool _is_collapsed(AtspiStateSet * ss)
+{
+ if (!ss)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+ if (atspi_state_set_contains(ss, ATSPI_STATE_EXPANDABLE) && !atspi_state_set_contains(ss, ATSPI_STATE_EXPANDED))
+ ret = EINA_TRUE;
+
+ return ret;
+}
+
+static Eina_Bool _object_is_item(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+ AtspiRole role = atspi_accessible_get_role(obj, NULL);
+ if (role == ATSPI_ROLE_LIST_ITEM || role == ATSPI_ROLE_MENU_ITEM)
+ ret = EINA_TRUE;
+ DEBUG("IS ITEM %d", ret);
+ return ret;
+}
+
+static AtspiAccessible *_get_object_in_relation(AtspiAccessible * source, AtspiRelationType search_type)
+{
+ GArray *relations;
+ AtspiAccessible *ret = NULL;
+ AtspiRelation *relation;
+ AtspiRelationType type;
+ int i;
+ if (source) {
+ DEBUG("CHECKING RELATIONS");
+ relations = atspi_accessible_get_relation_set(source, NULL);
+ if (relations) {
+ for (i = 0; i < relations->len; i++) {
+ DEBUG("ALL RELATIONS FOUND: %d", relations->len);
+ relation = g_array_index(relations, AtspiRelation *, i);
+ type = atspi_relation_get_relation_type(relation);
+ DEBUG("RELATION: %d", type);
+
+ if (type == search_type) {
+ ret = atspi_relation_get_target(relation, 0);
+ DEBUG("SEARCHED RELATION FOUND");
+ break;
+ }
+ }
+ g_array_free(relations, TRUE);
+ }
+ }
+ return ret;
+}
+
+static Eina_Bool _accept_object(AtspiAccessible * obj)
+{
+ DEBUG("START");
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+ gchar *name = NULL;
+ gchar *desc = NULL;
+ AtspiAction *action = NULL;
+ AtspiEditableText *etext = NULL;
+ AtspiText *text = NULL;
+ AtspiValue *value = NULL;
+ AtspiStateSet *ss = NULL;
+ AtspiComponent *component;
+ AtspiRect *extent;
+
+ AtspiRole r = atspi_accessible_get_role(obj, NULL);
+
+ switch (r) {
+ case ATSPI_ROLE_APPLICATION:
+ case ATSPI_ROLE_FILLER:
+ case ATSPI_ROLE_SCROLL_PANE:
+ case ATSPI_ROLE_SPLIT_PANE:
+ case ATSPI_ROLE_WINDOW:
+ case ATSPI_ROLE_IMAGE:
+ case ATSPI_ROLE_LIST:
+ case ATSPI_ROLE_PAGE_TAB_LIST:
+ case ATSPI_ROLE_TOOL_BAR:
+ case ATSPI_ROLE_REDUNDANT_OBJECT:
+ return EINA_FALSE;
+ case ATSPI_ROLE_DIALOG:
+ if (!_has_escape_action(obj))
+ return EINA_FALSE;
+ break;
+ default:
+ break;
+ }
+
+ // When given accessibility object is controlled by other object we consider
+ // it as not "user-presentable" on and skip it in navigation tree
+ AtspiAccessible *relation = _get_object_in_relation(obj, ATSPI_RELATION_CONTROLLED_BY);
+ if (relation)
+ {
+ g_object_unref(relation);
+ return EINA_FALSE;
+ }
+
+ ss = atspi_accessible_get_state_set(obj);
+ if (ss) {
+ if (_object_is_item(obj)) {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ if (parent) {
+ AtspiStateSet *pss = atspi_accessible_get_state_set(parent);
+ g_object_unref(parent);
+ if (pss) {
+ ret = atspi_state_set_contains(pss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(pss, ATSPI_STATE_VISIBLE) && !_is_collapsed(pss);
+ DEBUG("ITEM HAS SHOWING && VISIBLE && NOT COLLAPSED PARENT %d", ret);
+ g_object_unref(pss);
+ g_object_unref(ss);
+ return ret;
+ }
+ }
+ } else {
+ /* Extent of candidate object could be 0 */
+ component = atspi_accessible_get_component_iface(obj);
+ extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
+ g_object_unref(component);
+
+ if (extent->width <= 0 || extent->height <= 0) {
+ g_free(extent);
+ g_object_unref(ss);
+ return EINA_FALSE;
+ }
+ g_free(extent);
+
+ ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(ss, ATSPI_STATE_VISIBLE);
+ }
+ g_object_unref(ss);
+ }
+ if (!ret) {
+ return EINA_FALSE;
+ }
+
+ name = atspi_accessible_get_name(obj, NULL);
+
+ ret = EINA_FALSE;
+ if (name) {
+ if (strncmp(name, "\0", 1)) {
+ DEBUG("Has name:[%s]", name);
+ ret = EINA_TRUE;
+ }
+ g_free(name);
+ }
+ if (!ret) {
+ desc = atspi_accessible_get_description(obj, NULL);
+ if (desc) {
+ if (strncmp(desc, "\0", 1)) {
+ DEBUG("Has description:[%s]", desc);
+ ret = EINA_TRUE;
+ }
+ g_free(desc);
+ }
+ }
+ if (!ret) {
+ action = atspi_accessible_get_action_iface(obj);
+ if (action) {
+ DEBUG("Has action interface");
+ ret = EINA_TRUE;
+ g_object_unref(action);
+ }
+ }
+ if (!ret) {
+ value = atspi_accessible_get_value_iface(obj);
+ if (value) {
+ DEBUG("Has value interface");
+ ret = EINA_TRUE;
+ g_object_unref(value);
+ }
+ }
+ if (!ret) {
+ etext = atspi_accessible_get_editable_text_iface(obj);
+ if (etext) {
+ DEBUG("Has editable text interface");
+ ret = EINA_TRUE;
+ g_object_unref(etext);
+ }
+ }
+ if (!ret) {
+ text = atspi_accessible_get_text_iface(obj);
+ if (text) {
+ DEBUG("Has text interface");
+ ret = EINA_TRUE;
+ g_object_unref(text);
+ }
+ }
+
+ DEBUG("END:%d", ret);
+ return ret;
+}
+
+#ifdef SCREEN_READER_FLAT_NAVI_TEST_DUMMY_IMPLEMENTATION
+Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
+{
+ return EINA_FALSE;
+}
+#else
+
+int _object_has_modal_state(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+
+ if (atspi_state_set_contains(ss, ATSPI_STATE_MODAL))
+ ret = EINA_TRUE;
+ g_object_unref(ss);
+ return ret;
+}
+
+Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
+{
+ if (!ctx || !target)
+ return EINA_FALSE;
+
+ if (!ctx->root) {
+ DEBUG("NO top window");
+ return EINA_FALSE;
+ }
+
+ AtspiAccessible *current_obj = flat_navi_context_current_get(ctx);
+
+ Eina_Bool ret = EINA_FALSE;
+ GError *error = NULL;
+
+ AtspiAccessible *obj = g_object_ref(ctx->root);
+ AtspiAccessible *youngest_ancestor_in_context = (_accept_object(obj) ? g_object_ref(obj) : NULL);
+ AtspiComponent *component;
+ Eina_Bool look_for_next_descendant = EINA_TRUE;
+
+ while (look_for_next_descendant) {
+ component = atspi_accessible_get_component_iface(obj);
+
+ g_object_unref(obj);
+ obj = component ? atspi_component_get_accessible_at_point(component, x_cord, y_cord, ATSPI_COORD_TYPE_WINDOW, &error) : NULL;
+ g_clear_object(&component);
+
+ if (error) {
+ DEBUG("Got error from atspi_component_get_accessible_at_point, domain: %i, code: %i, message: %s", error->domain, error->code, error->message);
+ g_clear_error(&error);
+ g_clear_object(&obj);
+ if (youngest_ancestor_in_context)
+ g_clear_object(&youngest_ancestor_in_context);
+ look_for_next_descendant = EINA_FALSE;
+ } else if (obj) {
+ DEBUG("Found object %s, role %s", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL));
+ if (_accept_object(obj)) {
+ DEBUG("Object %s with role %s fulfills highlight conditions", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL));
+ if (youngest_ancestor_in_context)
+ g_object_unref(youngest_ancestor_in_context);
+ youngest_ancestor_in_context = g_object_ref(obj);
+ }
+ } else {
+ g_clear_object(&obj);
+ look_for_next_descendant = EINA_FALSE;
+ }
+ }
+
+ if (youngest_ancestor_in_context && !_object_has_modal_state(youngest_ancestor_in_context)) {
+ if (youngest_ancestor_in_context == current_obj || flat_navi_context_current_set(ctx, youngest_ancestor_in_context)) {
+ DEBUG("Setting highlight to object %s with role %s", atspi_accessible_get_name(youngest_ancestor_in_context, NULL), atspi_accessible_get_role_name(youngest_ancestor_in_context, NULL));
+ *target = youngest_ancestor_in_context;
+ ret = EINA_TRUE;
+ }
+ } else
+ DEBUG("NO widget under (%d, %d) found or the same widget under hover", x_cord, y_cord);
+ DEBUG("END");
+ return ret;
+}
+#endif
+
+AtspiAccessible *_get_child(AtspiAccessible * obj, int i)
+{
+ DEBUG("START:%d", i);
+ if (i < 0) {
+ DEBUG("END");
+ return NULL;
+ }
+ if (!obj) {
+ DEBUG("END");
+ return NULL;
+ }
+ int cc = atspi_accessible_get_child_count(obj, NULL);
+ if (cc == 0 || i >= cc) {
+ DEBUG("END");
+ return NULL;
+ }
+ return atspi_accessible_get_child_at_index(obj, i, NULL);
+}
+
+static Eina_Bool _has_next_sibling(AtspiAccessible * obj, int next_sibling_idx_modifier)
+{
+ Eina_Bool ret = EINA_FALSE;
+ if (!obj) return ret;
+
+ int idx = atspi_accessible_get_index_in_parent(obj, NULL);
+ if (idx >= 0) {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ int cc = atspi_accessible_get_child_count(parent, NULL);
+ g_object_unref(parent);
+ if ((next_sibling_idx_modifier > 0 && idx < cc - 1) || (next_sibling_idx_modifier < 0 && idx > 0)) {
+ ret = EINA_TRUE;
+ }
+ }
+ return ret;
+}
+
+AtspiAccessible *_directional_depth_first_search(AtspiAccessible * root, AtspiAccessible * start, int next_sibling_idx_modifier, Eina_Bool(*stop_condition) (AtspiAccessible *))
+{
+ Eina_Bool start_is_not_defunct = EINA_FALSE;
+ AtspiStateSet *ss;
+
+ if (start) {
+ AtspiStateSet *ss = atspi_accessible_get_state_set(start);
+ start_is_not_defunct = !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
+ g_object_unref(ss);
+ if (!start_is_not_defunct)
+ DEBUG("Start is defunct!");
+ }
+
+ AtspiAccessible *node = (start && start_is_not_defunct)
+ ? g_object_ref(start)
+ : (root ? g_object_ref(root) : NULL);
+
+ if (!node)
+ return NULL;
+
+ AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0)
+ ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO)
+ : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
+
+ Eina_Bool relation_mode = EINA_FALSE;
+ if (next_related_in_direction) {
+ relation_mode = EINA_TRUE;
+ g_object_unref(next_related_in_direction);
+ }
+
+ while (node) {
+ AtspiAccessible *prev_related_in_direction = (next_sibling_idx_modifier > 0)
+ ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM)
+ : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO);
+
+ if (node != start && (relation_mode || !prev_related_in_direction) && stop_condition(node)) {
+ g_object_unref(prev_related_in_direction);
+ return node;
+ }
+
+ AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0)
+ ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO)
+ : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
+
+ DEBUG("RELATION MODE: %d", relation_mode);
+ if (!prev_related_in_direction)
+ DEBUG("PREV IN RELATION NULL");
+ if (!next_related_in_direction)
+ DEBUG("NEXT IN RELATION NULL");
+
+ if ((!relation_mode && !prev_related_in_direction && next_related_in_direction) || (relation_mode && next_related_in_direction)) {
+ DEBUG("APPLICABLE FOR RELATION NAVIG");
+ g_object_unref(prev_related_in_direction);
+ relation_mode = EINA_TRUE;
+ g_object_unref(node);
+ node = next_related_in_direction;
+ } else {
+ g_object_unref(prev_related_in_direction);
+ g_object_unref(next_related_in_direction);
+ relation_mode = EINA_FALSE;
+ int cc = atspi_accessible_get_child_count(node, NULL);
+ ss = atspi_accessible_get_state_set(node);
+
+ if (cc > 0 && atspi_state_set_contains(ss, ATSPI_STATE_SHOWING)) // walk down
+ {
+ int idx = next_sibling_idx_modifier > 0 ? 0 : cc - 1;
+ g_object_unref(node);
+ node = atspi_accessible_get_child_at_index(node, idx, NULL);
+ DEBUG("DFS DOWN");
+ } else {
+ while (!_has_next_sibling(node, next_sibling_idx_modifier) || node == root) // no next sibling
+ {
+ DEBUG("DFS NO SIBLING");
+ if (!node || node == root) {
+ DEBUG("DFS END");
+ g_object_unref(node);
+ g_object_unref(ss);
+ return NULL;
+ }
+ g_object_unref(node);
+ node = atspi_accessible_get_parent(node, NULL); // walk up...
+ DEBUG("DFS UP");
+ }
+ int idx = atspi_accessible_get_index_in_parent(node, NULL);
+ g_object_unref(node);
+ node = atspi_accessible_get_child_at_index(atspi_accessible_get_parent(node, NULL), idx + next_sibling_idx_modifier, NULL); //... and next
+ DEBUG("DFS NEXT %d", idx + next_sibling_idx_modifier);
+ }
+ g_object_unref(ss);
+ }
+ }
+ DEBUG("DFS END");
+ return NULL;
+}
+
+AtspiAccessible *_first(FlatNaviContext * ctx)
+{
+ DEBUG("START");
+ return _directional_depth_first_search(ctx->root, NULL, 1, &_accept_object);
+}
+
+AtspiAccessible *_last(FlatNaviContext * ctx)
+{
+ DEBUG("START");
+ return _directional_depth_first_search(ctx->root, NULL, -1, &_accept_object);
+}
+
+AtspiAccessible *_next(FlatNaviContext * ctx)
+{
+ DEBUG("START");
+ AtspiAccessible *root = ctx->root;
+ AtspiAccessible *current = ctx->current;
+ AtspiAccessible *ret = NULL;
+
+ ret = _directional_depth_first_search(root, current, 1, &_accept_object);
+
+ if (current && !ret) {
+ DEBUG("DFS SECOND PASS");
+ ret = _directional_depth_first_search(root, NULL, 1, &_accept_object);
+ }
+ return ret;
+}
+
+AtspiAccessible *_prev(FlatNaviContext * ctx)
+{
+ DEBUG("START\n");
+ AtspiAccessible *root = ctx->root;
+ AtspiAccessible *current = ctx->current;
+ AtspiAccessible *ret = NULL;
+
+ ret = _directional_depth_first_search(root, current, -1, &_accept_object);
+ if (current && !ret) {
+ DEBUG("DFS SECOND PASS");
+ ret = _directional_depth_first_search(root, NULL, -1, &_accept_object);
+ }
+ return ret;
+}
+
+int flat_navi_context_current_children_count_visible_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return -1;
+ int count = 0;
+ /*
+ AtspiAccessible *obj = NULL;
+ AtspiStateSet *ss = NULL;
+
+ Eina_List *l, *l2, *line;
+ AtspiAccessible *current = flat_navi_context_current_get(ctx);
+ AtspiAccessible *parent = atspi_accessible_get_parent (current, NULL);
+
+ EINA_LIST_FOREACH(ctx->lines, l, line)
+ {
+ EINA_LIST_FOREACH(line, l2, obj)
+ {
+ ss = atspi_accessible_get_state_set(obj);
+ if (atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && parent == atspi_accessible_get_parent(obj, NULL))
+ count++;
+ g_object_unref(ss);
+ }
+ }
+ */
+ return count;
+}
+
+FlatNaviContext *flat_navi_context_create(AtspiAccessible * root)
+{
+ DEBUG("START");
+ if (!root)
+ return NULL;
+ FlatNaviContext *ret;
+ ret = calloc(1, sizeof(FlatNaviContext));
+ if (!ret)
+ return NULL;
+
+ ret->root = root;
+ ret->current = _first(ret);
+ return ret;
+}
+
+void flat_navi_context_free(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return;
+ free(ctx);
+}
+
+AtspiAccessible *flat_navi_context_root_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return ctx->root;
+}
+
+const AtspiAccessible *flat_navi_context_first_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return _first(ctx);
+}
+
+const AtspiAccessible *flat_navi_context_last_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return _last(ctx);
+}
+
+AtspiAccessible *flat_navi_context_current_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return ctx->current;
+}
+
+Eina_Bool flat_navi_context_current_set(FlatNaviContext * ctx, AtspiAccessible * target)
+{
+ if (!ctx || !target)
+ return EINA_FALSE;
+
+ ctx->current = target;
+
+ return EINA_TRUE;
+}
+
+AtspiAccessible *flat_navi_context_next(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _next(ctx);
+ ctx->current = ret;
+
+ return ret;
+
+}
+
+AtspiAccessible *flat_navi_context_prev(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _prev(ctx);
+ ctx->current = ret;
+
+ return ret;
+}
+
+AtspiAccessible *flat_navi_context_first(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _first(ctx);
+ ctx->current = ret;
+
+ return ret;
+}
+
+AtspiAccessible *flat_navi_context_last(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _last(ctx);
+ ctx->current = ret;
+
+ return ret;
+}
+
+Eina_Bool flat_navi_is_valid(FlatNaviContext * context, AtspiAccessible * new_root)
+{
+ Eina_Bool ret = EINA_FALSE;
+ if (!context || !context->current || context->root != new_root)
+ return ret;
+ AtspiStateSet *ss = atspi_accessible_get_state_set(context->current);
+
+ ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
+ g_object_unref(ss);
+ return ret;
+}
diff --git a/src/keyboard_tracker.c b/src/keyboard_tracker.c
new file mode 100644
index 0000000..70f62d9
--- /dev/null
+++ b/src/keyboard_tracker.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include <atspi/atspi.h>
+#include <Ecore.h>
+#include <Ecore_X.h>
+#include "keyboard_tracker.h"
+#include "logger.h"
+
+static AtspiDeviceListener *listener;
+static Keyboard_Tracker_Cb user_cb;
+static void *user_data;
+static Ecore_Event_Handler *root_xwindow_property_changed_hld = NULL;
+static Ecore_Event_Handler *active_xwindow_property_changed_hld = NULL;
+
+static void _check_keyboard_state(Ecore_X_Window keyboard_win)
+{
+ Ecore_X_Virtual_Keyboard_State keyboard_state;
+ static Ecore_X_Virtual_Keyboard_State last_keyboard_state = ECORE_X_VIRTUAL_KEYBOARD_STATE_OFF;
+
+ if (!keyboard_win)
+ {
+ return;
+ }
+
+ keyboard_state = ecore_x_e_virtual_keyboard_state_get(keyboard_win);
+ if (keyboard_state == last_keyboard_state)
+ {
+ return;
+ }
+
+ if (keyboard_state == ECORE_X_VIRTUAL_KEYBOARD_STATE_ON)
+ {
+ tts_speak (_("IDS_VISUAL_KEYBOARD_ENABLED"), EINA_FALSE);
+ last_keyboard_state = keyboard_state;
+ }
+ else if (keyboard_state == ECORE_X_VIRTUAL_KEYBOARD_STATE_OFF)
+ {
+ tts_speak (_("IDS_VISUAL_KEYBOARD_DISABLED"), EINA_FALSE);
+ last_keyboard_state = keyboard_state;
+ }
+}
+
+static Eina_Bool _active_xwindow_property_changed_cb(void *data, int type, void *event)
+{
+ Ecore_X_Event_Window_Property *wp;
+ wp = (Ecore_X_Event_Window_Property *)event;
+
+ if (!wp)
+ {
+ return EINA_FALSE;
+ }
+
+ if (wp->atom == ECORE_X_ATOM_E_VIRTUAL_KEYBOARD_STATE)
+ {
+ DEBUG("keyboard state event");
+ _check_keyboard_state(wp->win);
+ }
+
+ return EINA_TRUE;
+}
+
+void active_xwindow_property_tracker_register()
+{
+ Ecore_X_Window active_window = 0;
+ ecore_x_window_prop_xid_get(ecore_x_window_root_first_get(), ECORE_X_ATOM_NET_ACTIVE_WINDOW, ECORE_X_ATOM_WINDOW, &active_window, 1);
+ if (active_window)
+ {
+ ecore_x_event_mask_set(active_window, ECORE_X_EVENT_MASK_WINDOW_PROPERTY);
+ active_xwindow_property_changed_hld = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, _active_xwindow_property_changed_cb, NULL);
+ }
+}
+
+void active_xwindow_property_tracker_unregister()
+{
+ if (active_xwindow_property_changed_hld)
+ {
+ ecore_event_handler_del(active_xwindow_property_changed_hld);
+ active_xwindow_property_changed_hld = NULL;
+ }
+}
+
+static Eina_Bool _root_xwindow_property_changed_cb(void *data, int type, void *event)
+{
+ Ecore_X_Event_Window_Property *wp;
+ wp = (Ecore_X_Event_Window_Property *)event;
+
+ if (!wp)
+ {
+ return EINA_FALSE;
+ }
+
+ if (wp->atom == ECORE_X_ATOM_NET_ACTIVE_WINDOW)
+ {
+ DEBUG("active window change");
+ active_xwindow_property_tracker_unregister();
+ active_xwindow_property_tracker_register();
+ }
+
+ return EINA_TRUE;
+}
+
+void root_xwindow_property_tracker_register()
+{
+ Ecore_X_Window root_window;
+
+ root_window = ecore_x_window_root_first_get();
+ if (root_window)
+ {
+ ecore_x_event_mask_set(root_window, ECORE_X_EVENT_MASK_WINDOW_PROPERTY);
+ root_xwindow_property_changed_hld = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, _root_xwindow_property_changed_cb, NULL);
+ }
+}
+
+void root_xwindow_property_tracker_unregister()
+{
+ if (root_xwindow_property_changed_hld)
+ {
+ ecore_event_handler_del(root_xwindow_property_changed_hld);
+ root_xwindow_property_changed_hld = NULL;
+ }
+}
+
+static gboolean device_cb(const AtspiDeviceEvent * stroke, void *data)
+{
+ Key k;
+ if (!strcmp(stroke->event_string, "KP_Up"))
+ k = KEY_UP;
+ else if (!strcmp(stroke->event_string, "KP_Down"))
+ k = KEY_DOWN;
+ else if (!strcmp(stroke->event_string, "KP_Left"))
+ k = KEY_LEFT;
+ else if (!strcmp(stroke->event_string, "KP_Right"))
+ k = KEY_RIGHT;
+ else
+ return FALSE;
+
+ if (user_cb)
+ user_cb(user_data, k);
+
+ return TRUE;
+}
+
+void keyboard_tracker_init(void)
+{
+ listener = atspi_device_listener_new(device_cb, NULL, NULL);
+ atspi_register_keystroke_listener(listener, NULL, 0, ATSPI_KEY_PRESSED, ATSPI_KEYLISTENER_SYNCHRONOUS | ATSPI_KEYLISTENER_CANCONSUME, NULL);
+ active_xwindow_property_tracker_register();
+ root_xwindow_property_tracker_register();
+ DEBUG("keyboard tracker init");
+}
+
+void keyboard_tracker_register(Keyboard_Tracker_Cb cb, void *data)
+{
+ user_cb = cb;
+ user_data = data;
+}
+
+void keyboard_tracker_shutdown(void)
+{
+ atspi_deregister_keystroke_listener(listener, NULL, 0, ATSPI_KEY_PRESSED, NULL);
+ root_xwindow_property_tracker_unregister();
+ active_xwindow_property_tracker_unregister();
+ DEBUG("keyboard tracker shutdown");
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2b20ab7
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <time.h>
+#include <signal.h>
+#include <err.h>
+#include <execinfo.h>
+#include <appcore-efl.h>
+#include <Elementary.h>
+#include <eldbus-1/Eldbus.h>
+#include <service_app.h>
+#include <vconf.h>
+#include "navigator.h"
+#include "window_tracker.h"
+#include "logger.h"
+#include "screen_reader.h"
+#include "screen_reader_gestures.h"
+#include "screen_reader_switch.h"
+
+#define MAX_STACK_FRAMES 64
+static void *stack_traces[MAX_STACK_FRAMES];
+
+void posix_print_stack_trace(FILE * log_file)
+{
+ int i, trace_size = 0;
+ char **messages = (char **)NULL;
+
+ trace_size = backtrace(stack_traces, MAX_STACK_FRAMES);
+ messages = backtrace_symbols(stack_traces, trace_size);
+
+ /* skip the first couple stack frames (as they are this function and
+ our handler) and also skip the last frame as it's (always?) junk. */
+ // for (i = 3; i < (trace_size - 1); ++i)
+ for (i = 0; i < trace_size; ++i) // we'll use this for now so you can see what's going on
+ {
+ fprintf(log_file, " BACKTRACE LINE %i: %s\n", i, messages[i]);
+ }
+ if (messages) {
+ free(messages);
+ }
+}
+
+void print_warning(int sig, siginfo_t * siginfo, FILE * log_file)
+{
+ switch (sig) {
+ case SIGSEGV:
+ fputs("Caught SIGSEGV: Segmentation Fault\n", log_file);
+ break;
+ case SIGINT:
+ fputs("Caught SIGINT: Interactive attention signal, (usually ctrl+c)\n", log_file);
+ break;
+ case SIGFPE:
+ switch (siginfo->si_code) {
+ case FPE_INTDIV:
+ fputs("Caught SIGFPE: (integer divide by zero)\n", log_file);
+ break;
+ case FPE_INTOVF:
+ fputs("Caught SIGFPE: (integer overflow)\n", log_file);
+ break;
+ case FPE_FLTDIV:
+ fputs("Caught SIGFPE: (floating-point divide by zero)\n", log_file);
+ break;
+ case FPE_FLTOVF:
+ fputs("Caught SIGFPE: (floating-point overflow)\n", log_file);
+ break;
+ case FPE_FLTUND:
+ fputs("Caught SIGFPE: (floating-point underflow)\n", log_file);
+ break;
+ case FPE_FLTRES:
+ fputs("Caught SIGFPE: (floating-point inexact result)\n", log_file);
+ break;
+ case FPE_FLTINV:
+ fputs("Caught SIGFPE: (floating-point invalid operation)\n", log_file);
+ break;
+ case FPE_FLTSUB:
+ fputs("Caught SIGFPE: (subscript out of range)\n", log_file);
+ break;
+ default:
+ fputs("Caught SIGFPE: Arithmetic Exception\n", log_file);
+ break;
+ }
+ break;
+ case SIGILL:
+ switch (siginfo->si_code) {
+ case ILL_ILLOPC:
+ fputs("Caught SIGILL: (illegal opcode)\n", log_file);
+ break;
+ case ILL_ILLOPN:
+ fputs("Caught SIGILL: (illegal operand)\n", log_file);
+ break;
+ case ILL_ILLADR:
+ fputs("Caught SIGILL: (illegal addressing mode)\n", log_file);
+ break;
+ case ILL_ILLTRP:
+ fputs("Caught SIGILL: (illegal trap)\n", log_file);
+ break;
+ case ILL_PRVOPC:
+ fputs("Caught SIGILL: (privileged opcode)\n", log_file);
+ break;
+ case ILL_PRVREG:
+ fputs("Caught SIGILL: (privileged register)\n", log_file);
+ break;
+ case ILL_COPROC:
+ fputs("Caught SIGILL: (coprocessor error)\n", log_file);
+ break;
+ case ILL_BADSTK:
+ fputs("Caught SIGILL: (internal stack error)\n", log_file);
+ break;
+ default:
+ fputs("Caught SIGILL: Illegal Instruction\n", log_file);
+ break;
+ }
+ break;
+ case SIGTERM:
+ fputs("Caught SIGTERM: a termination request was sent to the program\n", log_file);
+ break;
+ case SIGABRT:
+ fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", log_file);
+ break;
+ default:
+ break;
+ }
+}
+
+void posix_signal_handler(int sig, siginfo_t * siginfo, void *context)
+{
+ char file_name[256];
+ struct tm *timeinfo;
+ time_t rawtime = time(NULL);
+ timeinfo = localtime(&rawtime);
+ if (timeinfo)
+ snprintf(file_name, sizeof(file_name), "/tmp/screen_reader_crash_stacktrace_%i%i%i_%i:%i:%i_pid_%i.log", timeinfo->tm_year, timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, getpid());
+ else
+ snprintf(file_name, sizeof(file_name), "/tmp/screen_reader_crash_stacktrace_pid_%i.log", getpid());
+
+ FILE *log_file = fopen(file_name, "w");
+ if (log_file) {
+ (void)context;
+ print_warning(sig, siginfo, stderr);
+
+ /* check file if it is symbolic link */
+ struct stat lstat_info;
+ if (lstat(file_name, &lstat_info) != -1) {
+ print_warning(sig, siginfo, log_file);
+ posix_print_stack_trace(log_file);
+ }
+
+ fclose(log_file);
+ log_file = NULL;
+ }
+
+ _Exit(1);
+}
+
+static uint8_t alternate_stack[SIGSTKSZ];
+void set_signal_handler()
+{
+ /* setup alternate stack */
+ {
+ stack_t ss = { };
+ /* malloc is usually used here, I'm not 100% sure my static allocation
+ is valid but it seems to work just fine. */
+ ss.ss_sp = (void *)alternate_stack;
+ ss.ss_size = SIGSTKSZ;
+ ss.ss_flags = 0;
+
+ if (sigaltstack(&ss, NULL) != 0) {
+ err(1, "sigaltstack");
+ }
+ }
+
+ /* register our signal handlers */
+ {
+ struct sigaction sig_action = { };
+ sig_action.sa_sigaction = posix_signal_handler;
+ sigemptyset(&sig_action.sa_mask);
+
+ sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+
+ if (sigaction(SIGSEGV, &sig_action, NULL) != 0) {
+ err(1, "sigaction");
+ }
+ if (sigaction(SIGFPE, &sig_action, NULL) != 0) {
+ err(1, "sigaction");
+ }
+ if (sigaction(SIGINT, &sig_action, NULL) != 0) {
+ err(1, "sigaction");
+ }
+ if (sigaction(SIGILL, &sig_action, NULL) != 0) {
+ err(1, "sigaction");
+ }
+ if (sigaction(SIGTERM, &sig_action, NULL) != 0) {
+ err(1, "sigaction");
+ }
+ if (sigaction(SIGABRT, &sig_action, NULL) != 0) {
+ err(1, "sigaction");
+ }
+ }
+}
+
+static bool app_create(void *data)
+{
+ if (vconf_set_bool(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS, 1))
+ ERROR("Can't set value of %s vconf key to 1", VCONFKEY_SETAPPL_ACCESSIBILITY_TTS);
+ DEBUG("atspi_init");
+ atspi_init();
+ DEBUG("logger_init");
+ DEBUG("screen_reader_create_service");
+ screen_reader_create_service(data);
+#ifndef SCREEN_READER_TV
+ DEBUG("screen_reader_gestures_init");
+ screen_reader_gestures_init();
+ DEBUG("navigator_init");
+ navigator_init();
+#endif
+ DEBUG("screen_reader_switch_enabled_set");
+ screen_reader_switch_enabled_set(EINA_TRUE);
+ return true;
+}
+
+static void app_terminate(void *data)
+{
+ DEBUG("screen reader terminating");
+#ifndef SCREEN_READER_TV
+ DEBUG("terminate navigator");
+ navigator_shutdown();
+ DEBUG("terminate gestures");
+ screen_reader_gestures_shutdown();
+#endif
+ DEBUG("terminate service");
+ screen_reader_terminate_service(data);
+ DEBUG("clear ScreenReaderEnabled property");
+ screen_reader_switch_enabled_set(EINA_FALSE);
+ DEBUG("screen reader terminated");
+ if (vconf_set_bool(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS, 0))
+ ERROR("Can't set value of %s vconf key to 0", VCONFKEY_SETAPPL_ACCESSIBILITY_TTS);
+ DEBUG("libatspi terminated");
+ atspi_exit();
+}
+
+static void app_control(app_control_h app_control, void *data)
+{
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ set_signal_handler();
+ unsetenv("ELM_ATSPI_MODE");
+
+
+ service_app_lifecycle_callback_s event_callback;
+
+ event_callback.create = app_create;
+ event_callback.terminate = app_terminate;
+ event_callback.app_control = app_control;
+
+ return service_app_main(argc, argv, &event_callback, get_pointer_to_service_data_struct());
+}
diff --git a/src/navigator.c b/src/navigator.c
new file mode 100644
index 0000000..c965cd8
--- /dev/null
+++ b/src/navigator.c
@@ -0,0 +1,2292 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <Ecore_X.h>
+#include <Ecore.h>
+#include <math.h>
+#include <atspi/atspi.h>
+#include "logger.h"
+#include "navigator.h"
+#include "window_tracker.h"
+#include "keyboard_tracker.h"
+#include "pivot_chooser.h"
+#include "flat_navi.h"
+#include "app_tracker.h"
+#include "smart_notification.h"
+#include "screen_reader_system.h"
+#include "screen_reader_haptic.h"
+#include "screen_reader_tts.h"
+#include "screen_reader_gestures.h"
+#include "dbus_gesture_adapter.h"
+#include "elm_access_adapter.h"
+
+#define QUICKPANEL_DOWN TRUE
+#define QUICKPANEL_UP FALSE
+
+#define DISTANCE_NB 8
+#define MENU_ITEM_TAB_INDEX_SIZE 16
+#define HOVERSEL_TRAIT_SIZE 200
+#define TTS_MAX_TEXT_SIZE 2000
+#define GESTURE_LIMIT 10
+
+//Timeout in ms which will be used as interval for handling ongoing
+//hoved gesture updates. It is introduced to improve performance.
+//Even if user makes many mouse move events within hover gesture
+//only 5 highlight updates a second will be performed. Else we will
+//highly pollute dbus bus and and decrease highlight performance.
+#define ONGOING_HOVER_GESTURE_INTERPRETATION_INTERVAL 200
+
+#define DEBUG_MODE
+
+#define GERROR_CHECK(error)\
+ if (error)\
+ {\
+ ERROR("Error_log:%s",error->message);\
+ g_error_free(error);\
+ error = NULL;\
+ }
+
+static void on_window_activate(void *data, AtspiAccessible * window);
+
+typedef struct {
+ int x, y;
+} last_focus_t;
+
+static last_focus_t gesture_start_p = { -1, -1 };
+static last_focus_t last_focus = { -1, -1 };
+
+static AtspiAccessible *current_obj;
+static AtspiComponent *current_comp = NULL;
+static AtspiAccessible *top_window;
+static FlatNaviContext *context;
+static bool prepared = false;
+static int counter = 0;
+int _last_hover_event_time = -1;
+
+static struct {
+ AtspiAccessible *focused_object;
+ bool auto_review_on;
+} s_auto_review = {
+.focused_object = NULL,.auto_review_on = false};
+
+char *state_to_char(AtspiStateType state)
+{
+ switch (state) {
+ case ATSPI_STATE_INVALID:
+ return strdup("ATSPI_STATE_INVALID");
+ case ATSPI_STATE_ACTIVE:
+ return strdup("ATSPI_STATE_ACTIVE");
+ case ATSPI_STATE_ARMED:
+ return strdup("ATSPI_STATE_ARMED");
+ case ATSPI_STATE_BUSY:
+ return strdup("ATSPI_STATE_BUSY");
+ case ATSPI_STATE_CHECKED:
+ return strdup("ATSPI_STATE_CHECKED");
+ case ATSPI_STATE_COLLAPSED:
+ return strdup("ATSPI_STATE_COLLAPSED");
+ case ATSPI_STATE_DEFUNCT:
+ return strdup("ATSPI_STATE_DEFUNCT");
+ case ATSPI_STATE_EDITABLE:
+ return strdup("ATSPI_STATE_EDITABLE");
+ case ATSPI_STATE_ENABLED:
+ return strdup("ATSPI_STATE_ENABLED");
+ case ATSPI_STATE_EXPANDABLE:
+ return strdup("ATSPI_STATE_EXPANDABLE");
+ case ATSPI_STATE_EXPANDED:
+ return strdup("ATSPI_STATE_EXPANDED");
+ case ATSPI_STATE_FOCUSABLE:
+ return strdup("ATSPI_STATE_FOCUSABLE");
+ case ATSPI_STATE_FOCUSED:
+ return strdup("ATSPI_STATE_FOCUSED");
+ case ATSPI_STATE_HAS_TOOLTIP:
+ return strdup("ATSPI_STATE_HAS_TOOLTIP");
+ case ATSPI_STATE_HORIZONTAL:
+ return strdup("ATSPI_STATE_HORIZONTAL");
+ case ATSPI_STATE_ICONIFIED:
+ return strdup("ATSPI_STATE_ICONIFIED");
+ case ATSPI_STATE_MULTI_LINE:
+ return strdup("ATSPI_STATE_MULTI_LINE");
+ case ATSPI_STATE_MULTISELECTABLE:
+ return strdup("ATSPI_STATE_MULTISELECTABLE");
+ case ATSPI_STATE_OPAQUE:
+ return strdup("ATSPI_STATE_OPAQUE");
+ case ATSPI_STATE_PRESSED:
+ return strdup("ATSPI_STATE_PRESSED");
+ case ATSPI_STATE_RESIZABLE:
+ return strdup("ATSPI_STATE_RESIZABLE");
+ case ATSPI_STATE_SELECTABLE:
+ return strdup("ATSPI_STATE_SELECTABLE");
+ case ATSPI_STATE_SELECTED:
+ return strdup("ATSPI_STATE_SELECTED");
+ case ATSPI_STATE_SENSITIVE:
+ return strdup("ATSPI_STATE_SENSITIVE");
+ case ATSPI_STATE_SHOWING:
+ return strdup("ATSPI_STATE_SHOWING");
+ case ATSPI_STATE_SINGLE_LINE:
+ return strdup("ATSPI_STATE_SINGLE_LINE");
+ case ATSPI_STATE_STALE:
+ return strdup("ATSPI_STATE_STALE");
+ case ATSPI_STATE_TRANSIENT:
+ return strdup("ATSPI_STATE_TRANSIENT");
+ case ATSPI_STATE_VERTICAL:
+ return strdup("ATSPI_STATE_VERTICAL");
+ case ATSPI_STATE_VISIBLE:
+ return strdup("ATSPI_STATE_VISIBLE");
+ case ATSPI_STATE_MANAGES_DESCENDANTS:
+ return strdup("ATSPI_STATE_MANAGES_DESCENDANTS");
+ case ATSPI_STATE_INDETERMINATE:
+ return strdup("ATSPI_STATE_INDETERMINATE");
+ case ATSPI_STATE_REQUIRED:
+ return strdup("ATSPI_STATE_REQUIRED");
+ case ATSPI_STATE_TRUNCATED:
+ return strdup("ATSPI_STATE_TRUNCATED");
+ case ATSPI_STATE_ANIMATED:
+ return strdup("ATSPI_STATE_ANIMATED");
+ case ATSPI_STATE_INVALID_ENTRY:
+ return strdup("ATSPI_STATE_INVALID_ENTRY");
+ case ATSPI_STATE_SUPPORTS_AUTOCOMPLETION:
+ return strdup("ATSPI_STATE_SUPPORTS_AUTOCOMPLETION");
+ case ATSPI_STATE_SELECTABLE_TEXT:
+ return strdup("ATSPI_STATE_SELECTABLE_TEXT");
+ case ATSPI_STATE_IS_DEFAULT:
+ return strdup("ATSPI_STATE_IS_DEFAULT");
+ case ATSPI_STATE_VISITED:
+ return strdup("ATSPI_STATE_VISITED");
+ case ATSPI_STATE_CHECKABLE:
+ return strdup("ATSPI_STATE_CHECKABLE");
+ case ATSPI_STATE_HAS_POPUP:
+ return strdup("ATSPI_STATE_HAS_POPUP");
+ case ATSPI_STATE_READ_ONLY:
+ return strdup("ATSPI_STATE_READ_ONLY");
+ case ATSPI_STATE_LAST_DEFINED:
+ return strdup("ATSPI_STATE_LAST_DEFINED");
+ case ATSPI_STATE_MODAL:
+ return strdup("ATSPI_STATE_MODAL");
+ case ATSPI_STATE_HIGHLIGHTED:
+ return strdup("ATSPI_STATE_HIGHLIGHTED");
+ case ATSPI_STATE_HIGHLIGHTABLE:
+ return strdup("ATSPI_STATE_HIGHLIGHTABLE");
+ default:
+ return strdup("\0");
+ }
+
+}
+
+static void display_info_about_object(AtspiAccessible * obj, bool display_parent_info)
+{
+ if(!obj)
+ {
+ return;
+ }
+
+ DEBUG("START");
+ DEBUG("------------------------");
+ gchar *name = atspi_accessible_get_name(obj, NULL);
+ gchar *role = atspi_accessible_get_localized_role_name(obj, NULL);
+ gchar *description = atspi_accessible_get_description(obj, NULL);
+ char *state_name = NULL;
+ AtspiStateSet *st = atspi_accessible_get_state_set(obj);
+ GArray *states = atspi_state_set_get_states(st);
+ AtspiComponent *comp = atspi_accessible_get_component_iface(obj);
+ AtspiValue *value = atspi_accessible_get_value_iface(obj);
+ AtspiRect *rect_screen = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_SCREEN, NULL);
+ AtspiRect *rect_win = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_WINDOW, NULL);
+
+ if(display_parent_info) {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ display_info_about_object(parent, false);
+ g_object_unref(parent);
+ }
+
+ DEBUG("NAME:%s", name);
+ DEBUG("ROLE:%s", role);
+ DEBUG("DESCRIPTION:%s", description);
+ DEBUG("CHILDS:%d", atspi_accessible_get_child_count(obj, NULL));
+ DEBUG("HIGHLIGHT_INDEX:%d", atspi_component_get_highlight_index(comp, NULL));
+ DEBUG("INDEX IN PARENT:%d", atspi_accessible_get_index_in_parent(obj, NULL));
+ if (value) {
+ DEBUG("VALUE:%f", atspi_value_get_current_value(value, NULL));
+ DEBUG("VALUE MAX:%f", atspi_value_get_maximum_value(value, NULL));
+ DEBUG("VALUE MIN:%f", atspi_value_get_minimum_value(value, NULL));
+ g_object_unref(value);
+ }
+ DEBUG("STATES:");
+ int a;
+ AtspiStateType stat;
+ for (a = 0; states && (a < states->len); ++a) {
+ stat = g_array_index(states, AtspiStateType, a);
+ state_name = state_to_char(stat);
+ DEBUG(" %s", state_name);
+ free(state_name);
+ }
+ g_array_free(states, 0);
+ DEBUG("LOCALE:%s", atspi_accessible_get_object_locale(obj, NULL));
+ DEBUG("SIZE ON SCREEN, width:%d, height:%d", rect_screen->width, rect_screen->height);
+ DEBUG("POSITION ON SCREEN: x:%d y:%d", rect_screen->x, rect_screen->y);
+ DEBUG("SIZE ON WIN, width:%d, height:%d", rect_win->width, rect_win->height);
+ DEBUG("POSITION ON WIN: x:%d y:%d", rect_win->x, rect_win->y);
+ DEBUG("INTERFACES:");
+ GArray *ifaces = atspi_accessible_get_interfaces(obj);
+ for (a = 0; ifaces && (a < ifaces->len); ++a) {
+ gchar * interface_name = g_array_index(ifaces, gchar *, a);
+ DEBUG(" %s", interface_name);
+ g_free(interface_name);
+ }
+ if (ifaces)
+ g_array_free(ifaces, FALSE);
+
+ DEBUG("------------------------");
+ DEBUG("END");
+ g_free(name);
+ g_free(role);
+ g_free(description);
+}
+
+char *generate_description_for_subtrees(AtspiAccessible * obj)
+{
+ DEBUG("START");
+
+ if (!obj)
+ return strdup("");
+ return strdup("");
+ /*
+ AtspiRole role;
+ int child_count;
+ int i;
+ char *name = NULL;
+ char *below = NULL;
+ char ret[TTS_MAX_TEXT_SIZE] = "\0";
+ AtspiAccessible *child = NULL;
+
+ int child_count = atspi_accessible_get_child_count(obj, NULL);
+
+ role = atspi_accessible_get_role(obj, NULL);
+
+ // Do not generate that for popups
+ if (role == ATSPI_ROLE_POPUP_MENU || role == ATSPI_ROLE_DIALOG)
+ return strdup("");
+
+ child_count = atspi_accessible_get_child_count(obj, NULL);
+
+ DEBUG("There is %d children inside this object", child_count);
+ if (!child_count)
+ return strdup("");
+
+ for (i=0; i < child_count; i++)
+ {
+ child = atspi_accessible_get_child_at_index(obj, i, NULL);
+ name = atspi_accessible_get_name(child, NULL);
+ DEBUG("%d child name:%s", i, name);
+ if (name && strncmp(name, "\0", 1))
+ {
+ strncat(ret, name, sizeof(ret) - strlen(ret) - 1);
+ }
+ strncat(ret, " ", sizeof(ret) - strlen(ret) - 1);
+ below = generate_description_for_subtrees(child);
+ DEBUG("%s from below", below);
+ if (strncmp(below, "\0", 1))
+ {
+ strncat(ret, below, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ g_object_unref(child);
+ free(below);
+ free(name);
+ }
+ return strdup(ret);
+ */
+}
+
+static int _check_list_children_count(AtspiAccessible * obj)
+{
+ int list_count = 0;
+ int i;
+ AtspiAccessible *child = NULL;
+
+ if (!obj)
+ return 0;
+
+ if (atspi_accessible_get_role(obj, NULL) == ATSPI_ROLE_LIST) {
+ int children_count = atspi_accessible_get_child_count(obj, NULL);
+
+ for (i = 0; i < children_count; i++) {
+ child = atspi_accessible_get_child_at_index(obj, i, NULL);
+ if (atspi_accessible_get_role(child, NULL) == ATSPI_ROLE_LIST_ITEM)
+ list_count++;
+ g_object_unref(child);
+ }
+ }
+
+ return list_count;
+}
+
+static int _find_popup_list_children_count(AtspiAccessible * obj)
+{
+ int list_items_count = 0;
+ int children_count = atspi_accessible_get_child_count(obj, NULL);
+ int i;
+ AtspiAccessible *child = NULL;
+
+ list_items_count = _check_list_children_count(obj);
+ if (list_items_count > 0)
+ return list_items_count;
+
+ for (i = 0; i < children_count; i++) {
+ child = atspi_accessible_get_child_at_index(obj, i, NULL);
+ list_items_count = _find_popup_list_children_count(child);
+ if (list_items_count > 0)
+ return list_items_count;
+ g_object_unref(child);
+ }
+
+ return 0;
+}
+
+static bool _widget_has_state(AtspiAccessible * obj, AtspiStateType type)
+{
+ Eina_Bool ret = EINA_FALSE;
+ AtspiStateSet *st = atspi_accessible_get_state_set(obj);
+ if (atspi_state_set_contains(st, type))
+ ret = EINA_TRUE;
+ g_object_unref(st);
+ return ret;
+}
+
+int get_accuracy(double val, int max_accuracy)
+{
+ char val_str[HOVERSEL_TRAIT_SIZE] = "";
+ int position;
+ int accuracy;
+
+ snprintf(val_str, HOVERSEL_TRAIT_SIZE, "%.*f", max_accuracy, val);
+ accuracy = max_accuracy;
+ position = strlen(val_str) - 1;
+ while ( position > 0 && val_str[position] == '0' ) {
+ --position;
+ --accuracy;
+ }
+ return accuracy;
+}
+
+void add_slider_description(char *dest, uint dest_size, AtspiAccessible *obj)
+{
+ gchar *role_name;
+ AtspiValue *value_iface;
+ double val;
+ double min_val;
+ double max_val;
+ char trait[HOVERSEL_TRAIT_SIZE] = "";
+ int accuracy;
+
+ role_name = atspi_accessible_get_localized_role_name(obj, NULL);
+ if (role_name) {
+ strncat(dest, role_name, dest_size - strlen(dest) - 1);
+ g_free(role_name);
+ }
+
+ value_iface = atspi_accessible_get_value_iface(obj);
+ if (!value_iface) {
+ return;
+ }
+
+ accuracy = get_accuracy( atspi_value_get_minimum_increment(value_iface, NULL), 3 );
+ val = atspi_value_get_current_value(value_iface, NULL);
+ max_val = atspi_value_get_maximum_value(value_iface, NULL);
+ min_val = atspi_value_get_minimum_value(value_iface, NULL);
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_SLIDER_VALUE"), accuracy, min_val, accuracy, max_val, accuracy, val);
+ strncat(dest, trait, dest_size - strlen(dest) - 1);
+
+ if (_widget_has_state(obj, ATSPI_STATE_ENABLED)) {
+ strncat(dest, _("IDS_TRAIT_SLIDER_SWIPE_COMMUNICATE"), dest_size - strlen(dest) - 1);
+ }
+ g_object_unref(value_iface);
+}
+
+char *generate_trait(AtspiAccessible * obj)
+{
+ if (!obj)
+ return strdup("");
+
+ AtspiRole role = atspi_accessible_get_role(obj, NULL);
+ AtspiStateSet *state_set = atspi_accessible_get_state_set(obj);
+ char ret[TTS_MAX_TEXT_SIZE] = "\0";
+ switch (role) {
+ case ATSPI_ROLE_ENTRY: {
+ gchar *role_name = atspi_accessible_get_localized_role_name(obj, NULL);
+ if (role_name) {
+ strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ if (atspi_state_set_contains(state_set, ATSPI_STATE_FOCUSED))
+ strncat(ret, _("IDS_TRAIT_TEXT_EDIT_FOCUSED"), sizeof(ret) - strlen(ret) - 1);
+ else
+ strncat(ret, _("IDS_TRAIT_TEXT_EDIT"), sizeof(ret) - strlen(ret) - 1);
+ g_free(role_name);
+ }
+ break;
+ }
+ case ATSPI_ROLE_MENU_ITEM: {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ int children_count = atspi_accessible_get_child_count(parent, NULL);
+ int index = atspi_accessible_get_index_in_parent(obj, NULL);
+ char tab_index[MENU_ITEM_TAB_INDEX_SIZE];
+ snprintf(tab_index, MENU_ITEM_TAB_INDEX_SIZE, _("IDS_TRAIT_MENU_ITEM_TAB_INDEX"), index + 1, children_count);
+ strncat(ret, tab_index, sizeof(ret) - strlen(ret) - 1);
+ g_object_unref(parent);
+ break;
+ }
+ case ATSPI_ROLE_POPUP_MENU: {
+ int children_count = atspi_accessible_get_child_count(obj, NULL);
+ char trait[HOVERSEL_TRAIT_SIZE];
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_CTX_POPUP"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_SHOWING"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, " ", sizeof(ret) - strlen(ret) - 1);
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, "%d", children_count);
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, " ", sizeof(ret) - strlen(ret) - 1);
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_ITEMS"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_POPUP_CLOSE"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ break;
+ }
+ case ATSPI_ROLE_DIALOG: {
+ int children_count = _find_popup_list_children_count(obj);
+ char trait[HOVERSEL_TRAIT_SIZE];
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_POPUP"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+
+ if (children_count > 0) {
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_SHOWING"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, " ", sizeof(ret) - strlen(ret) - 1);
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, "%d", children_count);
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, " ", sizeof(ret) - strlen(ret) - 1);
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_ITEMS"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ }
+
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_POPUP_CLOSE"));
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ break;
+ }
+ case ATSPI_ROLE_GLASS_PANE: {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ int children_count = atspi_accessible_get_child_count(parent, NULL);
+ char trait[HOVERSEL_TRAIT_SIZE];
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_PD_HOVERSEL"), children_count);
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ g_object_unref(parent);
+ break;
+ }
+ case ATSPI_ROLE_LIST_ITEM: {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ AtspiRole parent_role = atspi_accessible_get_role(parent, NULL);
+
+ if(parent_role == ATSPI_ROLE_TREE_TABLE) {
+
+ AtspiStateSet *state_set = atspi_accessible_get_state_set(obj);
+ gboolean is_selected = atspi_state_set_contains(state_set, ATSPI_STATE_SELECTED);
+ g_object_unref(state_set);
+
+ if(is_selected) {
+ strncat(ret, _("IDS_TRAIT_ITEM_SELECTED"), sizeof(ret) - strlen(ret) - 1);
+ }
+
+ AtspiStateSet *parent_state_set = atspi_accessible_get_state_set(parent);
+ bool is_parent_multiselectable = atspi_state_set_contains(parent_state_set, ATSPI_STATE_MULTISELECTABLE);
+
+ g_object_unref(parent_state_set);
+ g_object_unref(parent);
+
+ if(is_parent_multiselectable) {
+
+ char buf[200];
+
+ AtspiSelection *parent_selection = atspi_accessible_get_selection(parent);
+ int selected_children_count = atspi_selection_get_n_selected_children(parent_selection, NULL);
+
+ if(is_selected) {
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ }
+
+ snprintf(buf, 200, _("IDS_TRAIT_ITEM_SELECTED_COUNT"), selected_children_count);
+ strncat(ret, buf, sizeof(ret) - strlen(ret) - 1);
+
+ g_object_unref(parent_selection);
+ }
+
+ } else if (atspi_state_set_contains(state_set, ATSPI_STATE_EXPANDABLE)) {
+ if (atspi_state_set_contains(state_set, ATSPI_STATE_EXPANDED)) {
+ strncat(ret, _("IDS_TRAIT_GROUP_INDEX_EXPANDED"), sizeof(ret) - strlen(ret) - 1);
+ } else {
+ strncat(ret, _("IDS_TRAIT_GROUP_INDEX_COLLAPSED"), sizeof(ret) - strlen(ret) - 1);
+ }
+ }
+ g_object_unref(parent);
+ break;
+ }
+ case ATSPI_ROLE_CHECK_BOX:
+ case ATSPI_ROLE_RADIO_BUTTON: {
+ if (atspi_state_set_contains(state_set, ATSPI_STATE_CHECKED)) {
+ strncat(ret, _("IDS_TRAIT_CHECK_BOX_SELECTED"), sizeof(ret) - strlen(ret) - 1);
+ } else {
+ strncat(ret, _("IDS_TRAIT_CHECK_BOX_NOT_SELECTED"), sizeof(ret) - strlen(ret) - 1);
+ }
+
+ if (role == ATSPI_ROLE_RADIO_BUTTON) {
+ /* Say role name ("radio button"), but only if it's not a color chooser */
+ AtspiAccessible *parent;
+ AtspiRole parent_role;
+ parent = atspi_accessible_get_parent(obj, NULL);
+ parent_role = atspi_accessible_get_role(parent, NULL);
+ if (parent_role != ATSPI_ROLE_COLOR_CHOOSER) {
+ gchar *role_name;
+ role_name = atspi_accessible_get_localized_role_name(obj, NULL);
+ if (role_name) {
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1);
+ g_free(role_name);
+ }
+ }
+ g_object_unref(parent);
+ }
+ break;
+ }
+ case ATSPI_ROLE_PUSH_BUTTON: {
+ strncat(ret, _("IDS_TRAIT_PUSH_BUTTON"), sizeof(ret) - strlen(ret) - 1);
+ break;
+ }
+ case ATSPI_ROLE_PROGRESS_BAR: {
+ AtspiValue *value = atspi_accessible_get_value_iface(obj);
+ if (value) {
+ double val = atspi_value_get_current_value(value, NULL);
+ char trait[HOVERSEL_TRAIT_SIZE];
+ if (val > 0) {
+ snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_PD_PROGRESSBAR_PERCENT"), val * 100);
+ strncat(ret, trait, sizeof(ret) - strlen(ret) - 1);
+ } else {
+ strncat(ret, _("IDS_TRAIT_PD_PROGRESSBAR"), sizeof(ret) - strlen(ret) - 1);
+ }
+ g_object_unref(value);
+ }
+ break;
+ }
+ case ATSPI_ROLE_TOGGLE_BUTTON: {
+ strncat(ret, _("IDS_TRAIT_TOGGLE_BUTTON"), sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ if (atspi_state_set_contains(state_set, ATSPI_STATE_CHECKED)) {
+ strncat(ret, _("IDS_TRAIT_TOGGLE_BUTTON_ON"), sizeof(ret) - strlen(ret) - 1);
+ } else {
+ strncat(ret, _("IDS_TRAIT_TOGGLE_BUTTON_OFF"), sizeof(ret) - strlen(ret) - 1);
+ }
+ break;
+ }
+ case ATSPI_ROLE_SLIDER: {
+ add_slider_description(ret, sizeof(ret), obj);
+ break;
+ }
+ case ATSPI_ROLE_HEADING:
+ case ATSPI_ROLE_GROUPING: {
+ break;
+ }
+ default: {
+ gchar *role_name = atspi_accessible_get_localized_role_name(obj, NULL);
+ if (role_name) {
+ strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1);
+ g_free(role_name);
+ }
+ }
+ }
+
+ if (state_set)
+ g_object_unref(state_set);
+
+ return strdup(ret);
+}
+
+char *generate_text_for_relation_objects(AtspiAccessible * obj, AtspiRelationType search, char *(*text_generate_cb)(AtspiAccessible *obj))
+{
+ GError *err = NULL;
+ GArray *relations;
+ AtspiRelation *relation;
+ AtspiRelationType type;
+ Eina_Strbuf *buf;
+ int i, j;
+ char *ret = NULL;
+
+ if (!obj || !text_generate_cb) return NULL;
+
+ relations = atspi_accessible_get_relation_set(obj, &err);
+ if (err || !relations)
+ {
+ if (err) g_error_free(err);
+ return NULL;
+ }
+
+ buf = eina_strbuf_new();
+
+ for (i = 0; i < relations->len; i++)
+ {
+ relation = g_array_index(relations, AtspiRelation *, i);
+ type = atspi_relation_get_relation_type(relation);
+ if (type == search)
+ {
+ for (j = 0; j < atspi_relation_get_n_targets(relation); j++)
+ {
+ AtspiAccessible *target = atspi_relation_get_target(relation, j);
+ char *text = text_generate_cb(target);
+ if (j == 0)
+ eina_strbuf_append_printf(buf, "%s", text);
+ else
+ eina_strbuf_append_printf(buf, ", %s", text);
+ g_object_unref(target);
+ free(text);
+ }
+ }
+ g_object_unref(relation);
+ }
+ g_array_free(relations, TRUE);
+ ret = eina_strbuf_string_steal(buf);
+ eina_strbuf_free(buf);
+
+ return ret;
+}
+
+static char *generate_description_from_relation_object(AtspiAccessible *obj)
+{
+ GError *err = NULL;
+ char *ret = generate_trait(obj);
+ char *desc = atspi_accessible_get_description(obj, &err);
+
+ if (err)
+ {
+ g_error_free(err);
+ g_free(desc);
+ return ret;
+ }
+
+ if (desc) {
+ if (desc[0] != '\0') {
+ char *tmp = ret;
+ if (asprintf(&ret, "%s, %s", desc, ret) < 0)
+ ERROR("asprintf failed.");
+ free(tmp);
+ }
+ g_free(desc);
+ }
+
+
+ return ret;
+}
+
+static char *generate_name_from_relation_object(AtspiAccessible *obj)
+{
+ GError *err = NULL;
+ char *name = atspi_accessible_get_name(obj, &err);
+
+ if(err)
+ {
+ g_error_free(err);
+ g_free(name);
+ return NULL;
+ }
+
+ return name;
+}
+
+static char *generate_what_to_read(AtspiAccessible * obj)
+{
+ char *name;
+ char *names = NULL;
+ char *description;
+ char *role_name;
+ char *other;
+ char *text = NULL;
+ char ret[TTS_MAX_TEXT_SIZE] = "\0";
+ char *description_from_relation;
+ char *name_from_relation;
+
+ description = atspi_accessible_get_description(obj, NULL);
+ name = atspi_accessible_get_name(obj, NULL);
+ role_name = generate_trait(obj);
+ other = generate_description_for_subtrees(obj);
+ description_from_relation = generate_text_for_relation_objects(obj, ATSPI_RELATION_DESCRIBED_BY, generate_description_from_relation_object);
+ name_from_relation = generate_text_for_relation_objects(obj, ATSPI_RELATION_LABELLED_BY, generate_name_from_relation_object);
+ AtspiText *iface_text = atspi_accessible_get_text_iface(obj);
+ if (iface_text) {
+ text = atspi_text_get_text(iface_text, 0, atspi_text_get_character_count(iface_text, NULL), NULL);
+ g_object_unref(iface_text);
+ }
+
+ DEBUG("->->->->->-> WIDGET GAINED HIGHLIGHT: %s <-<-<-<-<-<-<-", name);
+ DEBUG("->->->->->-> FROM SUBTREE HAS NAME: %s <-<-<-<-<-<-<-", other);
+
+ display_info_about_object(obj, false);
+
+ if (name && strncmp(name, "\0", 1))
+ names = strdup(name);
+ else if (other && strncmp(other, "\0", 1))
+ names = strdup(other);
+
+ if (text) {
+ strncat(ret, text, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ DEBUG("Text:%s", text);
+
+ if (names && strlen(names) > 0) {
+ if (strlen(ret) > 0)
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, names, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ if (name_from_relation && strlen(name_from_relation) > 0) {
+ if(strlen(ret) > 0)
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, name_from_relation, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ if (role_name && strlen(role_name) > 0) {
+ if (strlen(ret) > 0)
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ if (description && strlen(description) > 0) {
+ if (strlen(ret) > 0)
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, description, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ if (description_from_relation && (description_from_relation[0] != '\n')) {
+ if (strlen(ret) > 0)
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, description_from_relation, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ free(text);
+ free(name);
+ free(names);
+ free(name_from_relation);
+ free(description);
+ free(role_name);
+ free(other);
+ free(description_from_relation);
+
+ return strdup(ret);
+}
+
+static void _current_highlight_object_set(AtspiAccessible * obj)
+{
+ DEBUG("START");
+ GError *err = NULL;
+ gchar *role = NULL;
+
+ if (!obj) {
+ DEBUG("Clearing highlight object");
+ current_obj = NULL;
+ if (current_comp) {
+ atspi_component_clear_highlight(current_comp, &err);
+ g_object_ref(current_comp);
+ current_comp = NULL;
+ }
+
+ return;
+ }
+ if (current_obj == obj) {
+ DEBUG("Object already highlighted");
+ DEBUG("Object name:%s", atspi_accessible_get_name(obj, NULL));
+ return;
+ }
+ if (obj && ATSPI_IS_COMPONENT(obj)) {
+ DEBUG("OBJ WITH COMPONENT");
+ AtspiComponent *comp = atspi_accessible_get_component_iface(obj);
+ if (!comp) {
+ GError *err = NULL;
+ role = atspi_accessible_get_role_name(obj, &err);
+ ERROR("AtspiComponent *comp NULL, [%s]", role);
+ GERROR_CHECK(err);
+ g_free(role);
+ return;
+ }
+ if (current_comp) {
+ atspi_component_clear_highlight(current_comp, &err);
+ }
+ atspi_component_grab_highlight(comp, &err);
+ current_comp = comp;
+ GERROR_CHECK(err)
+
+ Eina_Bool is_paused = tts_pause_get();
+ if (is_paused) {
+ tts_stop_set();
+ tts_pause_set(EINA_FALSE);
+ }
+ current_obj = obj;
+ char *text_to_speak = NULL;
+ text_to_speak = generate_what_to_read(obj);
+ DEBUG("SPEAK:%s", text_to_speak);
+
+ tts_speak(text_to_speak, EINA_TRUE);
+ g_free(text_to_speak);
+ } else
+ DEBUG("Unable to highlight on object");
+ DEBUG("END");
+}
+
+void test_debug(AtspiAccessible * current_widget)
+{
+ int jdx;
+ int count_child;
+ gchar *role;
+ GError *err = NULL;
+ AtspiAccessible *child_iter = NULL;
+ AtspiAccessible *parent = atspi_accessible_get_parent(current_widget, &err);
+ GERROR_CHECK(err)
+
+ if (!parent)
+ return;
+ count_child = atspi_accessible_get_child_count(parent, &err);
+ GERROR_CHECK(err)
+ DEBUG("Total childs in parent: %d\n", count_child);
+ if (!count_child)
+ {
+ g_object_unref(parent);
+ return;
+ }
+
+ for (jdx = 0; jdx < count_child; jdx++) {
+ child_iter = atspi_accessible_get_child_at_index(parent, jdx, &err);
+ GERROR_CHECK(err)
+
+ if (current_widget == child_iter) {
+ role = atspi_accessible_get_role_name(parent, &err);
+ DEBUG("Childen found in parent: %s at index: %d\n", role, jdx);
+ } else {
+ role = atspi_accessible_get_role_name(parent, &err);
+ DEBUG("Childen not found in parent: %s at index: %d\n", role, jdx);
+ }
+ g_free(role);
+ GERROR_CHECK(err)
+ }
+ g_object_unref(parent);
+}
+
+static void _focus_widget(Gesture_Info * info)
+{
+ DEBUG("START");
+
+ if ((last_focus.x == info->x_beg) && (last_focus.y == info->y_beg))
+ return;
+
+ AtspiAccessible *obj = NULL;
+ if (flat_navi_context_current_at_x_y_set(context, info->x_beg, info->y_beg, &obj)) {
+ last_focus.x = info->x_beg;
+ last_focus.y = info->y_beg;
+ _current_highlight_object_set(obj);
+ }
+
+ DEBUG("END");
+}
+
+static void _focus_next(void)
+{
+ DEBUG("START");
+ AtspiAccessible *obj;
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+
+ obj = flat_navi_context_next(context);
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Next widget not found. Abort");
+ DEBUG("END");
+}
+
+static void _focus_next_visible(void)
+{
+ DEBUG("START");
+ AtspiAccessible *obj;
+ AtspiStateSet *ss = NULL;
+ Eina_Bool visible = EINA_FALSE;
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+
+ do {
+ obj = flat_navi_context_next(context);
+ // try 'cycle' objects in context
+ if (obj) {
+ ss = atspi_accessible_get_state_set(obj);
+ visible = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING);
+ g_object_unref(ss);
+ }
+ }
+ while (obj && !visible);
+
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Next widget not found. Abort");
+ DEBUG("END");
+}
+
+static void _focus_prev_visible(void)
+{
+ AtspiAccessible *obj;
+ AtspiStateSet *ss = NULL;
+ Eina_Bool visible = EINA_FALSE;
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+ do {
+ obj = flat_navi_context_prev(context);
+ // try 'cycle' objects in context
+ if (obj) {
+ ss = atspi_accessible_get_state_set(obj);
+ visible = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING);
+ g_object_unref(ss);
+ }
+ }
+ while (obj && !visible);
+
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Previous widget not found. Abort");
+}
+
+static void _focus_prev(void)
+{
+ AtspiAccessible *obj;
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+
+ obj = flat_navi_context_prev(context);
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Previous widget not found. Abort");
+}
+
+static void _caret_move_beg(void)
+{
+ AtspiAccessible *current_widget = NULL;
+ AtspiText *text_interface;
+ gboolean ret;
+ GError *err = NULL;
+
+ if (!current_obj)
+ return;
+
+ current_widget = current_obj;
+
+ text_interface = atspi_accessible_get_text_iface(current_widget);
+ if (text_interface) {
+ ret = atspi_text_set_caret_offset(text_interface, 0, &err);
+ GERROR_CHECK(err)
+ if (ret) {
+ DEBUG("Caret position increment done");
+ gchar *text = atspi_text_get_text(text_interface, 0, 1, NULL);
+ DEBUG("SPEAK:%s", text);
+ tts_speak(text, EINA_TRUE);
+ tts_speak(_("IDS_TEXT_BEGIN"), EINA_FALSE);
+ g_free(text);
+ } else {
+ ERROR("Caret position increment error");
+ }
+ g_object_unref(text_interface);
+ } else
+ ERROR("No text interface supported!");
+}
+
+static void _caret_move_end(void)
+{
+ AtspiAccessible *current_widget = NULL;
+ AtspiText *text_interface;
+ gboolean ret;
+ GError *err = NULL;
+
+ if (!current_obj)
+ return;
+
+ current_widget = current_obj;
+
+ text_interface = atspi_accessible_get_text_iface(current_widget);
+ if (text_interface) {
+ int len = atspi_text_get_character_count(text_interface, NULL);
+ ret = atspi_text_set_caret_offset(text_interface, len, &err);
+ if (ret) {
+ DEBUG("Caret position increment done");
+ DEBUG("SPEAK:%s", _("IDS_TEXT_END"));
+ tts_speak(_("IDS_TEXT_END"), EINA_TRUE);
+ } else
+ ERROR("Caret position to end error");
+ g_object_unref(text_interface);
+ }
+}
+
+static void _caret_move_forward(void)
+{
+ AtspiAccessible *current_widget = NULL;
+ AtspiText *text_interface;
+ gint current_offset;
+ gboolean ret;
+ int offset_pos;
+ gchar *text;
+ GError *err = NULL;
+ if (!current_obj)
+ return;
+
+ current_widget = current_obj;
+
+ text_interface = atspi_accessible_get_text_iface(current_widget);
+ if (text_interface) {
+ current_offset = atspi_text_get_caret_offset(text_interface, &err);
+ GERROR_CHECK(err)
+ ret = atspi_text_set_caret_offset(text_interface, current_offset + 1, &err);
+ GERROR_CHECK(err)
+ if (ret) {
+ offset_pos = atspi_text_get_caret_offset(text_interface, NULL);
+ text = atspi_text_get_text(text_interface, offset_pos, offset_pos + 1, NULL);
+ DEBUG("Caret position increment done");
+ DEBUG("Current caret position:%d", offset_pos);
+ DEBUG("Current caret offset:%d", current_offset);
+ if (offset_pos == atspi_text_get_character_count(text_interface, NULL)) {
+ DEBUG("SPEAK:%s", _("IDS_TEXT_END"));
+ tts_speak(_("IDS_TEXT_END"), EINA_FALSE);
+ } else {
+ DEBUG("SPEAK:%s", text);
+ tts_speak(text, EINA_TRUE);
+ }
+ g_free(text);
+ } else {
+ ERROR("Caret position increment error");
+ }
+ g_object_unref(text_interface);
+ } else
+ ERROR("No text interface supported!");
+ return;
+
+}
+
+static void _caret_move_backward(void)
+{
+ AtspiAccessible *current_widget = NULL;
+ AtspiText *text_interface;
+ gint current_offset;
+ int offset_pos;
+ gchar *text;
+ GError *err = NULL;
+ gboolean ret;
+
+ if (!current_obj)
+ return;
+
+ current_widget = current_obj;
+
+ GERROR_CHECK(err)
+
+ text_interface = atspi_accessible_get_text_iface(current_widget);
+ if (text_interface) {
+ current_offset = atspi_text_get_caret_offset(text_interface, &err);
+ GERROR_CHECK(err)
+ ret = atspi_text_set_caret_offset(text_interface, current_offset - 1, &err);
+ GERROR_CHECK(err)
+ if (ret) {
+ offset_pos = atspi_text_get_caret_offset(text_interface, NULL);
+ text = atspi_text_get_text(text_interface, offset_pos, offset_pos + 1, NULL);
+ DEBUG("Caret position decrement done");
+ DEBUG("Current caret position:%d", offset_pos);
+ DEBUG("SPEAK:%s", text);
+ tts_speak(text, EINA_TRUE);
+ g_free(text);
+ if (offset_pos == 0) {
+ DEBUG("SPEAK:%s", _("IDS_TEXT_BEGIN"));
+ tts_speak(_("IDS_TEXT_BEGIN"), EINA_FALSE);
+ }
+ } else {
+ ERROR("Caret position decrement error");
+ }
+ g_object_unref(text_interface);
+ } else
+ ERROR("No text interface supported!");
+ return;
+}
+
+static void _read_value(AtspiValue * value)
+{
+ if (!value)
+ return;
+
+ gdouble current_val = atspi_value_get_current_value(value, NULL);
+ gdouble max_val = atspi_value_get_maximum_value(value, NULL);
+ gdouble min_val = atspi_value_get_minimum_value(value, NULL);
+
+ int proc = (current_val / fabs(max_val - min_val)) * 100;
+
+ char buf[256] = "\0";
+ snprintf(buf, sizeof(buf), "%d percent", proc);
+ DEBUG("has value %s", buf);
+ tts_speak(buf, EINA_TRUE);
+}
+
+static void _value_inc(void)
+{
+ AtspiAccessible *current_widget = NULL;
+ GError *err = NULL;
+
+ if (!current_obj)
+ return;
+
+ current_widget = current_obj;
+
+ AtspiValue *value_interface = atspi_accessible_get_value_iface(current_widget);
+ if (value_interface) {
+ DEBUG("Value interface supported!\n");
+ gdouble current_val = atspi_value_get_current_value(value_interface, &err);
+ GERROR_CHECK(err)
+ DEBUG("Current value: %f\n ", (double)current_val);
+ gdouble minimum_inc = atspi_value_get_minimum_increment(value_interface, &err);
+ DEBUG("Minimum increment: %f\n ", (double)minimum_inc);
+ GERROR_CHECK(err)
+ atspi_value_set_current_value(value_interface, current_val + minimum_inc, &err);
+ GERROR_CHECK(err)
+ _read_value(value_interface);
+ g_object_unref(value_interface);
+ return;
+ }
+ ERROR("No value interface supported!\n");
+}
+
+static void _value_dec(void)
+{
+ AtspiAccessible *current_widget = NULL;
+ GError *err = NULL;
+
+ if (!current_obj)
+ return;
+ current_widget = current_obj;
+
+ AtspiValue *value_interface = atspi_accessible_get_value_iface(current_widget);
+ if (value_interface) {
+ DEBUG("Value interface supported!\n");
+ gdouble current_val = atspi_value_get_current_value(value_interface, &err);
+ GERROR_CHECK(err)
+ DEBUG("Current value: %f\n ", (double)current_val);
+ gdouble minimum_inc = atspi_value_get_minimum_increment(value_interface, &err);
+ GERROR_CHECK(err)
+ DEBUG("Minimum increment: %f\n ", (double)minimum_inc);
+ atspi_value_set_current_value(value_interface, current_val - minimum_inc, &err);
+ GERROR_CHECK(err)
+ _read_value(value_interface);
+ g_object_unref(value_interface);
+ return;
+ }
+ ERROR("No value interface supported!\n");
+}
+
+static void _activate_widget(void)
+{
+ //activate the widget
+ //only if activate mean click
+ //special behavior for entry, caret should move from first/last last/first
+ DEBUG("START");
+ AtspiAccessible *current_widget = NULL;
+ AtspiComponent *focus_component = NULL;
+ AtspiAccessible *parent = NULL;
+ AtspiStateSet *ss = NULL;
+ AtspiSelection *selection = NULL;
+ AtspiAction *action;
+ AtspiEditableText *edit = NULL;
+
+ GError *err = NULL;
+ gchar *actionName = NULL;
+ gint number = 0;
+ gint i = 0;
+ gint index = 0;
+ Eina_Bool activate_found = EINA_FALSE;
+ AtspiRole role = ATSPI_ROLE_INVALID;
+
+ if (!current_obj)
+ return;
+
+ if (!_widget_has_state(current_obj, ATSPI_STATE_ENABLED)) {
+ DEBUG("Widget is disabled so cannot be activated");
+ return;
+ }
+
+ current_widget = current_obj;
+
+ role = atspi_accessible_get_role(current_widget, NULL);
+ if (role == ATSPI_ROLE_SLIDER) {
+ return;
+ }
+
+ display_info_about_object(current_widget, false);
+
+ edit = atspi_accessible_get_editable_text_iface(current_widget);
+ if (edit) {
+ DEBUG("Activated object has editable Interface");
+ focus_component = atspi_accessible_get_component_iface(current_widget);
+ if (focus_component) {
+ if (atspi_component_grab_focus(focus_component, &err) == TRUE) {
+ GERROR_CHECK(err)
+
+ DEBUG("Entry activated\n");
+
+ char *text_to_speak = NULL;
+ text_to_speak = generate_what_to_read(current_widget);
+
+ DEBUG("SPEAK:%s", text_to_speak);
+
+ tts_speak(text_to_speak, EINA_TRUE);
+ g_free(text_to_speak);
+ g_object_unref(focus_component);
+ }
+ }
+ g_object_unref(edit);
+ return;
+ }
+
+ action = atspi_accessible_get_action_iface(current_widget);
+ if (action) {
+ number = atspi_action_get_n_actions(action, &err);
+ DEBUG("Number of available action = %d\n", number);
+ GERROR_CHECK(err)
+ activate_found = EINA_FALSE;
+ while (i < number && !activate_found) {
+ actionName = atspi_action_get_name(action, i, &err);
+ if (actionName && !strcmp("activate", actionName)) {
+ DEBUG("There is activate action");
+ activate_found = EINA_TRUE;
+ } else {
+ i++;
+ }
+ g_free(actionName);
+ }
+ if (activate_found) {
+ DEBUG("PERFORMING ATSPI ACTION NO.%d", i);
+ atspi_action_do_action(action, i, &err);
+ } else if (number > 0) {
+ DEBUG("PERFORMING ATSPI DEFAULT ACTION");
+ atspi_action_do_action(action, 0, &err);
+ } else
+ ERROR("There is no actions inside Action interface");
+ if (action)
+ g_object_unref(action);
+ GERROR_CHECK(err)
+ return;
+ }
+
+ ss = atspi_accessible_get_state_set(current_widget);
+ if (atspi_state_set_contains(ss, ATSPI_STATE_SELECTABLE) == EINA_TRUE) {
+ DEBUG("OBJECT IS SELECTABLE");
+ parent = atspi_accessible_get_parent(current_widget, NULL);
+ if (parent) {
+ index = atspi_accessible_get_index_in_parent(current_widget, NULL);
+ selection = atspi_accessible_get_selection_iface(parent);
+ if (selection) {
+ if(atspi_state_set_contains(ss, ATSPI_STATE_SELECTED)) {
+ atspi_selection_deselect_child (selection, index, NULL);
+
+ } else {
+ DEBUG("SELECT CHILD NO:%d\n", index);
+ atspi_selection_select_child(selection, index, NULL);
+ }
+
+ g_object_unref(selection);
+ g_object_unref(parent);
+ g_object_unref(ss);
+ return;
+ } else
+ ERROR("no selection iterface in parent");
+
+ g_object_unref(parent);
+ }
+ }
+ g_object_unref(ss);
+
+}
+
+static void _quickpanel_change_state(gboolean quickpanel_switch)
+{
+ DEBUG("START");
+ Ecore_X_Window xwin = 0;
+
+ if (quickpanel_switch)
+ DEBUG("QUICKPANEL STATE ON");
+ else
+ DEBUG("QUICKPANEL STATE OFF");
+
+ Ecore_X_Illume_Quickpanel_State state;
+
+ ecore_x_window_prop_xid_get(ecore_x_window_root_first_get(), ECORE_X_ATOM_NET_ACTIVE_WINDOW, ECORE_X_ATOM_WINDOW, &xwin, 1);
+
+ state = quickpanel_switch ? ECORE_X_ILLUME_QUICKPANEL_STATE_ON : ECORE_X_ILLUME_QUICKPANEL_STATE_OFF;
+
+ ecore_x_e_illume_quickpanel_state_set(xwin, state);
+
+ ecore_x_e_illume_quickpanel_state_send(ecore_x_e_illume_zone_get(xwin), state);
+
+ ecore_main_loop_iterate();
+}
+
+/**
+ * @brief Gets 'deepest' Scrollable accessible containing (x,y) point
+ */
+/*
+static AtspiScrollable*
+_find_scrollable_ancestor_at_xy(int x, int y)
+{
+ AtspiAccessible *ret = NULL;
+ AtspiRect *rect;
+ GError *err = NULL;
+
+ if (!top_window || !ATSPI_IS_COMPONENT(top_window))
+ {
+ DEBUG("No active window detected or no AtspiComponent interface available");
+ return NULL;
+ }
+
+ rect = atspi_component_get_extents(ATSPI_COMPONENT(top_window), ATSPI_COORD_TYPE_SCREEN, &err);
+ GERROR_CHECK(err)
+ if (!rect)
+ {
+ ERROR("Unable to fetch window screen coordinates");
+ return NULL;
+ }
+
+ // Scroll must originate within window borders
+ if ((x < rect->x) || (x > rect->x + rect->width) ||
+ (y < rect->y) || (y > rect->y + rect->height))
+ {
+ DEBUG("Scroll don't start within active window borders");
+ g_free(rect);
+ return NULL;
+ }
+
+ ret = atspi_component_get_accessible_at_point(ATSPI_COMPONENT(top_window), x, y, ATSPI_COORD_TYPE_SCREEN, &err);
+ GERROR_CHECK(err)
+ if (!ret)
+ {
+ ERROR("Unable to get accessible objct at (%d, %d) screen coordinates.", x, y);
+ return NULL;
+ }
+gchar *name;
+gchar *role;
+ // find accessible object with Scrollable interface
+ while (ret && (ret != top_window))
+ {
+ name = atspi_accessible_get_name(ret, &err);
+ GERROR_CHECK(err)
+ role = atspi_accessible_get_role_name(ret, &err);
+ GERROR_CHECK(err)
+ DEBUG("Testing for scrollability: %s %s",
+ name, role);
+ if (atspi_accessible_get_scrollable(ret))
+ {
+ DEBUG("Scrollable widget found at (%d, %d), name: %s, role: %s", x, y,
+ name ,role);
+ g_free(name);
+ g_free(role);
+ return ATSPI_SCROLLABLE(ret);
+ }
+ g_free(name);
+ g_free(role);
+ ret = atspi_accessible_get_parent(ret, &err);
+ if (err)
+ {
+ ERROR("Unable to fetch AT-SPI parent");
+ GERROR_CHECK(err)
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static void _widget_scroll_begin(Gesture_Info *gi)
+{
+ GError *err = NULL;
+
+ if (scrolled_obj)
+ {
+ ERROR("Scrolling context active when initializing new scrolling context! This should never happen.");
+ ERROR("Force reset of current scrolling context...");
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_END, gi->x_begin, gi->y_begin, &err);
+ if (err)
+ {
+ ERROR("Failed to reset scroll context.");
+ GERROR_CHECK(err)
+ scrolled_obj = NULL;
+ }
+ }
+
+ scrolled_obj = _find_scrollable_ancestor_at_xy(gi->x_begin, gi->y_begin);
+
+ if (!scrolled_obj)
+ {
+ DEBUG("No scrollable widget found at (%d, %d) coordinates", gi->x_begin, gi->y_begin);
+ return;
+ }
+
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_START, gi->x_begin, gi->y_begin, &err);
+ if (err)
+ {
+ ERROR("Failed to initialize scroll operation");
+ GERROR_CHECK(err)
+ scrolled_obj = NULL;
+ }
+}
+
+static void _widget_scroll_continue(Gesture_Info *gi)
+{
+ GError *err = NULL;
+ if (!scrolled_obj)
+ {
+ DEBUG("Scrolling context not initialized!");
+ return;
+ }
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_CONTINUE, gi->x_begin, gi->y_begin, &err);
+ GERROR_CHECK(err)
+}
+
+static void _widget_scroll_end(Gesture_Info *gi)
+{
+ GError *err = NULL;
+ if (!scrolled_obj)
+ {
+ ERROR("Scrolling context not initialized!");
+ return;
+ }
+
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_END, gi->x_begin, gi->y_begin, &err);
+ scrolled_obj = NULL;
+ GERROR_CHECK(err)
+}
+*/
+
+static void _widget_scroll(Gesture_Info * gi)
+{
+ DEBUG("Recognized gesture state: %d", gi->state);
+
+ if (gi->state == 0) {
+ DEBUG("save coordinates %d %d", gesture_start_p.x, gesture_start_p.y);
+ gesture_start_p.x = gi->x_beg;
+ gesture_start_p.y = gi->y_beg;
+ }
+
+ if (gi->state != 2) {
+ DEBUG("Scroll not finished yet");
+ return;
+ }
+
+ AtspiAccessible *obj = NULL;
+ obj = flat_navi_context_current_get(context);
+ if (!obj) {
+ ERROR("No context");
+ return;
+ }
+
+ AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+ if (!ss) {
+ ERROR("no stetes");
+ return;
+ }
+
+ if (!atspi_state_set_contains(ss, ATSPI_STATE_SHOWING)) {
+ DEBUG("current context do not have visible state, swith to next/prev");
+ if (gesture_start_p.y > gi->y_end || gesture_start_p.x > gi->x_end) {
+ DEBUG("NEXT");
+ _focus_next_visible();
+ } else if (gesture_start_p.y < gi->y_end || gesture_start_p.x < gi->x_end) {
+ DEBUG("PREVIOUS");
+ _focus_prev_visible();
+ }
+ }
+ DEBUG("end");
+ g_object_unref(ss);
+ g_object_unref(obj);
+}
+
+static void _read_quickpanel(void)
+{
+ DEBUG("START");
+
+ device_time_get();
+ device_battery_get();
+ device_bluetooth_get();
+ device_signal_strenght_get();
+
+ device_date_get();
+ device_missed_events_get();
+ DEBUG("END");
+}
+
+static void _set_pause(void)
+{
+ DEBUG("START");
+
+ Eina_Bool res = EINA_FALSE;
+ bool pause = tts_pause_get();
+ res = tts_pause_set(!pause);
+ if (!res) {
+ ERROR("Failed to set pause state");
+ }
+
+ DEBUG("END");
+}
+
+void auto_review_highlight_set(void)
+{
+ AtspiAccessible *obj = flat_navi_context_next(context);
+
+ DEBUG("START");
+
+ if (!obj) {
+ DEBUG("obj == NULL");
+ s_auto_review.auto_review_on = false;
+ return;
+ } else if (obj == flat_navi_context_last_get(context)) {
+ DEBUG("obj == flat_navi_context_last_get()");
+ s_auto_review.auto_review_on = false;
+ }
+
+ _current_highlight_object_set(obj);
+
+ DEBUG("END");
+}
+
+void auto_review_highlight_top(void)
+{
+ DEBUG("START");
+ char *text_to_speak = NULL;
+ AtspiAccessible *obj = flat_navi_context_current_get(context);
+ AtspiAccessible *first = flat_navi_context_first(context);
+
+ if (first != obj) {
+ _current_highlight_object_set(first);
+ } else {
+ text_to_speak = generate_what_to_read(obj);
+ DEBUG("Text to speak: %s", text_to_speak);
+ tts_speak(text_to_speak, EINA_TRUE);
+ free(text_to_speak);
+ }
+
+ DEBUG("END");
+}
+
+static void _on_auto_review_stop(void)
+{
+ DEBUG("START");
+ s_auto_review.auto_review_on = false;
+ DEBUG("END");
+}
+
+static void _on_utterance(void)
+{
+ DEBUG("START");
+ DEBUG("s_auto_review.auto_review_on == %d", s_auto_review.auto_review_on);
+
+ if (s_auto_review.auto_review_on) {
+ auto_review_highlight_set();
+ }
+ DEBUG("END");
+}
+
+static void _review_from_current(void)
+{
+ DEBUG("START");
+
+ s_auto_review.focused_object = flat_navi_context_current_get(context);
+ s_auto_review.auto_review_on = true;
+ auto_review_highlight_set();
+
+ DEBUG("END");
+}
+
+static void _review_from_top()
+{
+ DEBUG("START");
+
+ s_auto_review.focused_object = flat_navi_context_current_get(context);
+ s_auto_review.auto_review_on = true;
+ auto_review_highlight_top();
+
+ DEBUG("END");
+}
+
+static void _direct_scroll_back(void)
+{
+ DEBUG("ONE_FINGER_FLICK_LEFT_RETURN");
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+
+ AtspiAccessible *obj = NULL;
+ AtspiAccessible *current = NULL;
+ AtspiAccessible *parent = NULL;
+ AtspiRole role;
+
+ current = flat_navi_context_current_get(context);
+ parent = atspi_accessible_get_parent(current, NULL);
+ role = atspi_accessible_get_role(parent, NULL);
+
+ if (role != ATSPI_ROLE_LIST) {
+ DEBUG("That operation can be done only on list, it is:%s", atspi_accessible_get_role_name(parent, NULL));
+ g_object_unref(parent);
+ g_object_unref(current);
+ return;
+ }
+
+ int index = atspi_accessible_get_index_in_parent(current, NULL);
+ int children_count = atspi_accessible_get_child_count(parent, NULL);
+
+ if (children_count <= 0) {
+ ERROR("NO visible element on list");
+ g_object_unref(parent);
+ g_object_unref(current);
+ return;
+ }
+
+ DEBUG("start from element with index:%d/%d", index, children_count);
+
+ if (index <= 0) {
+ DEBUG("first element");
+ obj = atspi_accessible_get_child_at_index(parent, 0, NULL);
+ smart_notification(FOCUS_CHAIN_END_NOTIFICATION_EVENT, 0, 0);
+ }
+
+ else {
+ DEBUG("go back to %d element", index);
+ obj = atspi_accessible_get_child_at_index(parent, index, NULL);
+ }
+
+ if (obj) {
+ DEBUG("Will set highlight and context");
+ if (flat_navi_context_current_set(context, obj)) {
+ DEBUG("current obj set");
+ }
+ _current_highlight_object_set(obj);
+ }
+ g_object_unref(parent);
+ g_object_unref(current);
+}
+
+static void _direct_scroll_forward(void)
+{
+ DEBUG("ONE_FINGER_FLICK_RIGHT_RETURN");
+
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+
+ AtspiAccessible *obj = NULL;
+ AtspiAccessible *current = NULL;
+ AtspiAccessible *parent = NULL;
+ AtspiRole role;
+
+ current = flat_navi_context_current_get(context);
+ parent = atspi_accessible_get_parent(current, NULL);
+ role = atspi_accessible_get_role(parent, NULL);
+
+ if (role != ATSPI_ROLE_LIST) {
+ DEBUG("That operation can be done only on list, it is:%s", atspi_accessible_get_role_name(parent, NULL));
+ g_object_unref(parent);
+ g_object_unref(current);
+ return;
+ }
+
+ int index = atspi_accessible_get_index_in_parent(current, NULL);
+ int children_count = atspi_accessible_get_child_count(parent, NULL);
+
+ if (children_count <= 0) {
+ ERROR("NO visible element on list");
+ g_object_unref(parent);
+ g_object_unref(current);
+ return;
+ }
+
+ DEBUG("start from element with index:%d/%d", index, children_count);
+
+ if (index >= children_count) {
+ DEBUG("last element");
+ obj = atspi_accessible_get_child_at_index(parent, children_count - 1, NULL);
+ smart_notification(FOCUS_CHAIN_END_NOTIFICATION_EVENT, 0, 0);
+ }
+
+ else {
+ DEBUG("go back to %d element", index);
+ obj = atspi_accessible_get_child_at_index(parent, index, NULL);
+ }
+
+ if (obj) {
+ DEBUG("Will set highlight and context");
+ if (flat_navi_context_current_set(context, obj)) {
+ DEBUG("current obj set");
+ }
+ _current_highlight_object_set(obj);
+ }
+ g_object_unref(parent);
+ g_object_unref(current);
+}
+
+static void _direct_scroll_to_first(void)
+{
+ DEBUG("ONE_FINGER_FLICK_UP_RETURN");
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+ AtspiAccessible *obj = flat_navi_context_first(context);
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("First widget not found. Abort");
+ DEBUG("END");
+}
+
+static void _direct_scroll_to_last(void)
+{
+ DEBUG("ONE_FINGER_FLICK_DOWN_RETURN");
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+ AtspiAccessible *obj = flat_navi_context_last(context);
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Last widget not found. Abort");
+ DEBUG("END");
+}
+
+static Eina_Bool _has_value(void)
+{
+ DEBUG("START");
+ AtspiAccessible *obj = NULL;
+
+ if (!current_obj)
+ return EINA_FALSE;
+
+ obj = current_obj;
+
+ if (!obj)
+ return EINA_FALSE;
+
+ AtspiValue *value = atspi_accessible_get_value_iface(obj);
+
+ if (value) {
+ g_object_unref(value);
+ return EINA_TRUE;
+ }
+
+ return EINA_FALSE;
+}
+
+static Eina_Bool _is_enabled(void)
+{
+ if (!current_obj) {
+ return EINA_FALSE;
+ }
+
+ return _widget_has_state(current_obj, ATSPI_STATE_ENABLED);
+}
+
+static Eina_Bool _is_active_entry(void)
+{
+ DEBUG("START");
+
+ if (!context) {
+ ERROR("No navigation context created");
+ return EINA_FALSE;
+ }
+ AtspiAccessible *obj = NULL;
+ AtspiRole role;
+ obj = flat_navi_context_current_get(context);
+
+ if (!obj)
+ return EINA_FALSE;
+
+ role = atspi_accessible_get_role(obj, NULL);
+ if (role == ATSPI_ROLE_ENTRY) {
+ AtspiStateSet *state_set = atspi_accessible_get_state_set(obj);
+ if (atspi_state_set_contains(state_set, ATSPI_STATE_FOCUSED)) {
+ g_object_unref(state_set);
+ return EINA_TRUE;
+ }
+ g_object_unref(state_set);
+ return EINA_FALSE;
+ }
+
+ DEBUG("END");
+ return EINA_FALSE;
+}
+
+static Eina_Bool _is_slider(AtspiAccessible * obj)
+{
+ DEBUG("START");
+
+ if (!obj)
+ return EINA_FALSE;
+
+ AtspiRole role;
+
+ role = atspi_accessible_get_role(obj, NULL);
+ if (role == ATSPI_ROLE_SLIDER) {
+ return EINA_TRUE;
+ }
+ return EINA_FALSE;
+}
+
+static void _move_slider(Gesture_Info * gi)
+{
+ DEBUG("ONE FINGER DOUBLE TAP AND HOLD");
+
+ if (!context) {
+ ERROR("No navigation context created");
+ return;
+ }
+
+ AtspiAccessible *obj = NULL;
+ AtspiValue *value = NULL;
+ AtspiComponent *comp = NULL;
+ AtspiRect *rect = NULL;
+ int click_point_x = 0;
+ int click_point_y = 0;
+
+ obj = current_obj;
+
+ if (!obj) {
+ DEBUG("no object");
+ prepared = false;
+ return;
+ }
+
+ if (!_is_slider(obj)) {
+ DEBUG("Object is not a slider");
+ prepared = false;
+ return;
+ }
+
+ if (!_widget_has_state(obj, ATSPI_STATE_ENABLED)) {
+ DEBUG("Slider is disabled");
+ prepared = false;
+ return;
+ }
+
+ if (gi->state == 0) {
+ comp = atspi_accessible_get_component_iface(obj);
+ if (!comp) {
+ ERROR("that slider do not have component interface");
+ prepared = false;
+ return;
+ }
+
+ rect = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_SCREEN, NULL);
+
+ DEBUG("Current object is in:%d %d", rect->x, rect->y);
+ DEBUG("Current object has size:%d %d", rect->width, rect->height);
+
+ click_point_x = rect->x + rect->width / 2;
+ click_point_y = rect->y + rect->height / 2;
+ DEBUG("Click on point %d %d", click_point_x, click_point_y);
+ start_scroll(click_point_x, click_point_y);
+ }
+
+ if (gi->state == 1) {
+ counter++;
+ DEBUG("SCROLLING but not meet counter:%d", counter);
+ if (counter >= GESTURE_LIMIT) {
+ counter = 0;
+ DEBUG("Scroll on point %d %d", gi->x_end, gi->y_end);
+ continue_scroll(gi->x_end, gi->y_end);
+ }
+ }
+
+ if (gi->state == 2) {
+ DEBUG("state == 2");
+ end_scroll(gi->x_end, gi->y_end);
+ prepared = false;
+ value = atspi_accessible_get_value_iface(obj);
+ if (value) {
+ _read_value(value);
+ g_object_unref(value);
+ } else {
+ ERROR("There is not value interface in slider");
+ }
+ }
+ DEBUG("END");
+}
+
+AtspiAction *_get_main_window(void)
+{
+ AtspiAccessible *win = flat_navi_context_root_get(context);
+ if (!win) {
+ ERROR("win == NULL");
+ return NULL;
+ }
+
+ AtspiAction *action = atspi_accessible_get_action_iface(win);
+ if (!action) {
+ ERROR("action == NULL");
+ return NULL;
+ }
+
+ return action;
+}
+
+static int _find_action_index(AtspiAction * action, char *action_name_to_find)
+{
+ int action_num = atspi_action_get_n_actions(action, NULL);
+ char *action_name = NULL;
+
+ int i = 0;
+ for (i = 0; i < action_num; ++i) {
+ action_name = atspi_action_get_action_name(action, i, NULL);
+
+ if (!strcmp(action_name_to_find, action_name)) {
+ return i;
+ }
+ }
+
+ return -i;
+}
+
+static void _start_stop_signal_send(void)
+{
+ int action_index = -1;
+ char *action_name = "pause_play";
+ AtspiAction *action = _get_main_window();
+ if (!action) {
+ ERROR("Could not get the action inteface");
+ }
+
+ if (!action) {
+ ERROR("action == NULL");
+ return;
+ }
+
+ action_index = _find_action_index(action, action_name);
+ if (action_index < 0) {
+ ERROR("Pause_play action not found");
+ return;
+ }
+
+ DEBUG("ACTION: %s has index: %d", action_name, action_index);
+ atspi_action_do_action(action, action_index, NULL);
+}
+
+static void on_gesture_detected(void *data, Gesture_Info * info)
+{
+ Ecore_X_Window keyboard_win;
+ _on_auto_review_stop();
+
+ if (info->type == ONE_FINGER_SINGLE_TAP && info->state == 3) {
+ DEBUG("One finger single tap aborted");
+ prepared = true;
+ }
+
+ switch (info->type) {
+ case ONE_FINGER_HOVER:
+ if (prepared) {
+ DEBUG("Prepare to move slider");
+ _move_slider(info);
+ } else {
+ if (_last_hover_event_time < 0)
+ _last_hover_event_time = info->event_time;
+ //info->event_time and _last_hover_event_time contain timestamp in ms.
+ //RETURN so we do not handle all incoming event
+ if ((info->event_time - _last_hover_event_time) < ONGOING_HOVER_GESTURE_INTERPRETATION_INTERVAL && info->state == 1)
+ return;
+ _last_hover_event_time = info->state != 1 ? -1 : info->event_time;
+#ifdef ELM_ACCESS_KEYBOARD
+ keyboard_win = top_window_get(info->x_end, info->y_end);
+ if (keyboard_win && ecore_x_e_virtual_keyboard_get(keyboard_win)) {
+ elm_access_adaptor_emit_read(keyboard_win, info->x_end, info->y_end);
+ break;
+ }
+#endif
+ _focus_widget(info);
+ }
+ break;
+ case TWO_FINGERS_HOVER:
+ _widget_scroll(info);
+ break;
+ case ONE_FINGER_FLICK_LEFT:
+ _focus_prev();
+ break;
+ case ONE_FINGER_FLICK_RIGHT:
+ _focus_next();
+ break;
+ case ONE_FINGER_FLICK_UP:
+ if (_is_active_entry())
+ _caret_move_backward();
+ else if (_has_value() && _is_enabled())
+ _value_inc();
+ else
+ _focus_prev();
+ break;
+ case ONE_FINGER_FLICK_DOWN:
+ if (_is_active_entry())
+ _caret_move_forward();
+ else if (_has_value() && _is_enabled())
+ _value_dec();
+ else
+ _focus_next();
+ break;
+ case ONE_FINGER_SINGLE_TAP:
+#ifdef ELM_ACCESS_KEYBOARD
+ keyboard_win = top_window_get(info->x_end, info->y_end);
+ if (keyboard_win && ecore_x_e_virtual_keyboard_get(keyboard_win)) {
+ elm_access_adaptor_emit_read(keyboard_win, info->x_end, info->y_end);
+ break;
+ }
+#endif
+ if (!prepared)
+ _focus_widget(info);
+ break;
+ case ONE_FINGER_DOUBLE_TAP:
+#ifdef ELM_ACCESS_KEYBOARD
+ keyboard_win = top_window_get(info->x_end, info->y_end);
+ if (keyboard_win && ecore_x_e_virtual_keyboard_get(keyboard_win)) {
+ elm_access_adaptor_emit_activate(keyboard_win, info->x_end, info->y_end);
+ break;
+ }
+#endif
+ _activate_widget();
+ break;
+ case TWO_FINGERS_SINGLE_TAP:
+ _set_pause();
+ break;
+ case TWO_FINGERS_DOUBLE_TAP:
+ _start_stop_signal_send();
+ break;
+ case TWO_FINGERS_TRIPLE_TAP:
+#ifndef SCREEN_READER_TV
+ _read_quickpanel();
+#endif
+ break;
+ case THREE_FINGERS_SINGLE_TAP:
+ _review_from_top();
+ break;
+ case THREE_FINGERS_DOUBLE_TAP:
+ _review_from_current();
+ break;
+ case THREE_FINGERS_FLICK_DOWN:
+ _quickpanel_change_state(QUICKPANEL_DOWN);
+ break;
+ case THREE_FINGERS_FLICK_UP:
+ _quickpanel_change_state(QUICKPANEL_UP);
+ break;
+ case ONE_FINGER_FLICK_LEFT_RETURN:
+ _direct_scroll_back();
+ break;
+ case ONE_FINGER_FLICK_RIGHT_RETURN:
+ _direct_scroll_forward();
+ break;
+ case ONE_FINGER_FLICK_UP_RETURN:
+ if (_is_active_entry())
+ _caret_move_beg();
+ else
+ _direct_scroll_to_first();
+ break;
+ case ONE_FINGER_FLICK_DOWN_RETURN:
+ if (_is_active_entry())
+ _caret_move_end();
+ else
+ _direct_scroll_to_last();
+ break;
+ default:
+ DEBUG("Gesture type %d not handled in switch", info->type);
+ }
+
+ dbus_gesture_adapter_emit(info);
+}
+
+static void _view_content_changed(AtspiAccessible * root, void *user_data)
+{
+ if (flat_navi_is_valid(context, root))
+ return;
+ if (!_widget_has_state(root, ATSPI_STATE_SHOWING))
+ return;
+ flat_navi_context_free(context);
+ context = flat_navi_context_create(root);
+ _current_highlight_object_set(flat_navi_context_current_get(context));
+}
+
+static void _new_highlighted_obj_changed(AtspiAccessible * new_highlighted_obj, void *user_data)
+{
+ DEBUG("context: %p, current: %p, new_highlighted_obj: %p", context, flat_navi_context_current_get(context), new_highlighted_obj);
+ if (context && flat_navi_context_current_get(context) != new_highlighted_obj) {
+ flat_navi_context_current_set(context, g_object_ref(new_highlighted_obj));
+ }
+}
+
+void clear(gpointer d)
+{
+ AtspiAccessible **data = d;
+ AtspiAccessible *obj = *data;
+ g_object_unref(obj);
+}
+
+static AtspiAccessible *_get_modal_descendant(AtspiAccessible * root)
+{
+ GError *err = NULL;
+ AtspiStateSet *states = atspi_state_set_new(NULL);
+ atspi_state_set_add(states, ATSPI_STATE_MODAL);
+ atspi_state_set_add(states, ATSPI_STATE_SHOWING);
+ atspi_state_set_add(states, ATSPI_STATE_VISIBLE);
+ DEBUG("GET MODAL: STATE SET PREPARED");
+ AtspiMatchRule *rule = atspi_match_rule_new(states,
+ ATSPI_Collection_MATCH_ALL,
+ NULL,
+ ATSPI_Collection_MATCH_INVALID,
+ NULL,
+ ATSPI_Collection_MATCH_INVALID,
+ NULL,
+ ATSPI_Collection_MATCH_INVALID,
+ FALSE);
+ DEBUG("GET MODAL: MATCHING RULE PREPARED");
+ AtspiAccessible *ret = NULL;
+ AtspiCollection *col_iface = atspi_accessible_get_collection_iface(root);
+ GArray *result = atspi_collection_get_matches(col_iface,
+ rule,
+ ATSPI_Collection_SORT_ORDER_CANONICAL,
+ 1,
+ TRUE,
+ &err);
+ GERROR_CHECK(err);
+ DEBUG("GET MODAL: QUERY PERFORMED");
+ g_object_unref(states);
+ g_object_unref(rule);
+ g_object_unref(col_iface);
+ if (result && result->len > 0) {
+ DEBUG("GET MODAL: MODAL FOUND");
+ g_array_set_clear_func(result, clear);
+ ret = g_object_ref(g_array_index(result, AtspiAccessible *, 0));
+ g_array_free(result, TRUE);
+ }
+ return ret;
+}
+
+static void on_window_activate(void *data, AtspiAccessible * window)
+{
+ DEBUG("START");
+
+ if (top_window)
+ app_tracker_callback_unregister(top_window, _view_content_changed, NULL);
+
+ if (window) {
+ DEBUG("Window name: %s", atspi_accessible_get_name(window, NULL));
+ // TODO: modal descendant of window should be used (if exists) otherwise window
+ AtspiAccessible *modal_descendant = _get_modal_descendant(window);
+ app_tracker_callback_register(modal_descendant ? modal_descendant : window, _view_content_changed, NULL);
+ _view_content_changed(modal_descendant ? modal_descendant : window, NULL);
+ g_object_unref(modal_descendant);
+ } else {
+ flat_navi_context_free(context);
+ ERROR("No top window found!");
+ }
+ top_window = window;
+ DEBUG("END");
+}
+
+void kb_tracker(void *data, Key k)
+{
+ switch (k) {
+ case KEY_LEFT:
+ _focus_prev();
+ break;
+ case KEY_RIGHT:
+ _focus_next();
+ break;
+ default:
+ DEBUG("Key %d not supported \n", k);
+ }
+}
+
+void navigator_init(void)
+{
+ DEBUG("START");
+
+ set_utterance_cb(_on_utterance);
+
+ screen_reader_gestures_tracker_register(on_gesture_detected, NULL);
+ // register on active_window
+ dbus_gesture_adapter_init();
+ app_tracker_init();
+ app_tracker_new_obj_highlighted_callback_register(_new_highlighted_obj_changed);
+ window_tracker_init();
+ window_tracker_register(on_window_activate, NULL);
+ window_tracker_active_window_request();
+ smart_notification_init();
+#ifndef SCREEN_READER_TV
+ system_notifications_init();
+#endif
+ keyboard_tracker_init();
+ keyboard_tracker_register(kb_tracker, NULL);
+}
+
+void navigator_shutdown(void)
+{
+ GError *err = NULL;
+ if (current_obj) {
+ AtspiComponent *comp = atspi_accessible_get_component_iface(current_obj);
+ if (comp) {
+ atspi_component_clear_highlight(comp, &err);
+ GERROR_CHECK(err);
+ }
+ }
+ if (context) {
+ flat_navi_context_free(context);
+ context = NULL;
+ }
+ dbus_gesture_adapter_shutdown();
+ app_tracker_shutdown();
+ window_tracker_shutdown();
+ smart_notification_shutdown();
+#ifndef SCREEN_READER_TV
+ system_notifications_shutdown();
+#endif
+ keyboard_tracker_shutdown();
+}
diff --git a/src/pivot_chooser.c b/src/pivot_chooser.c
new file mode 100644
index 0000000..481663b
--- /dev/null
+++ b/src/pivot_chooser.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atspi/atspi.h>
+#include "logger.h"
+#include <Eina.h>
+
+/**
+ * @brief Finds first leaf in object hierarchy with given states,
+ * starting from object given as parent.
+ *
+ * This heuristic assumes that focused element have focused
+ * parent widgets.
+ */
+static AtspiAccessible *_pivot_with_state_top_down_find(AtspiAccessible * parent, AtspiStateType type)
+{
+ AtspiAccessible *ret = NULL;
+ AtspiStateSet *states;
+ int i;
+
+ states = atspi_accessible_get_state_set(parent);
+ if (!states || atspi_state_set_contains(states, type)) {
+ int n = atspi_accessible_get_child_count(parent, NULL);
+ if (n == 0)
+ ret = parent;
+ for (i = 0; i < n; i++) {
+ AtspiAccessible *child = atspi_accessible_get_child_at_index(parent, i, NULL);
+ if (!child)
+ continue;
+
+ ret = _pivot_with_state_top_down_find(child, type);
+
+ g_object_unref(child);
+
+ if (ret)
+ break;
+ }
+ }
+
+ g_object_unref(states);
+
+ return ret;
+}
+
+/**
+ * @brief Finds first leaf descendant of given object with state @p type
+ */
+static AtspiAccessible *_pivot_with_state_flat_find(AtspiAccessible * parent, AtspiStateType type)
+{
+ Eina_List *candidates = NULL, *queue = NULL;
+
+ // ref object to keep same ref count
+ g_object_ref(parent);
+ queue = eina_list_append(queue, parent);
+
+ while (queue) {
+ AtspiAccessible *obj = eina_list_data_get(queue);
+ queue = eina_list_remove_list(queue, queue);
+
+ int n = atspi_accessible_get_child_count(obj, NULL);
+ if (n == 0)
+ candidates = eina_list_append(candidates, obj);
+ else {
+ int i;
+ for (i = 0; i < n; i++) {
+ AtspiAccessible *child = atspi_accessible_get_child_at_index(obj, i, NULL);
+ if (child)
+ queue = eina_list_append(queue, child);
+ }
+ g_object_unref(obj);
+ }
+ }
+
+ // FIXME sort by (x,y) first ??
+ while (candidates) {
+ AtspiAccessible *obj = eina_list_data_get(candidates);
+ candidates = eina_list_remove_list(candidates, candidates);
+
+ AtspiStateSet *states = atspi_accessible_get_state_set(obj);
+ if (states && atspi_state_set_contains(states, type)) {
+ g_object_unref(states);
+ g_object_unref(obj);
+ eina_list_free(candidates);
+
+ return obj;
+ }
+
+ g_object_unref(states);
+ g_object_unref(obj);
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Purpose of this methods is to find first visible object in
+ * hierarchy
+ */
+AtspiAccessible *pivot_chooser_pivot_get(AtspiAccessible * win)
+{
+ AtspiAccessible *ret;
+
+ if (atspi_accessible_get_role(win, NULL) != ATSPI_ROLE_WINDOW) {
+ ERROR("Pivot search entry point must be a Window!");
+ return NULL;
+ }
+
+ DEBUG("Finding SHOWING widget using top-down method.");
+ ret = _pivot_with_state_top_down_find(win, ATSPI_STATE_SHOWING);
+ if (ret)
+ return ret;
+
+ DEBUG("Finding SHOWING widget using top-down method.");
+ ret = _pivot_with_state_flat_find(win, ATSPI_STATE_SHOWING);
+ if (ret)
+ return ret;
+
+ DEBUG("Finding FOCUSED widget using top-down method.");
+ ret = _pivot_with_state_top_down_find(win, ATSPI_STATE_FOCUSED);
+ if (ret)
+ return ret;
+
+ DEBUG("Finding FOCUSED widget using flat search method.");
+ ret = _pivot_with_state_flat_find(win, ATSPI_STATE_FOCUSED);
+ if (ret)
+ return ret;
+
+ return NULL;
+}
diff --git a/src/screen_reader.c b/src/screen_reader.c
new file mode 100644
index 0000000..e465e1b
--- /dev/null
+++ b/src/screen_reader.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "screen_reader.h"
+#include "screen_reader_tts.h"
+#include "screen_reader_vconf.h"
+#include "screen_reader_spi.h"
+#include <vconf.h>
+#include "logger.h"
+
+#ifdef RUN_IPC_TEST_SUIT
+#include "test_suite/test_suite.h"
+#endif
+
+#define BUF_SIZE 1024
+
+Service_Data service_data = {
+ //Set by vconf
+ .run_service = 1,
+#ifdef SCREEN_READER_TV
+ .tracking_signal_name = FOCUS_CHANGED_SIG,
+#else
+ .tracking_signal_name = HIGHLIGHT_CHANGED_SIG,
+#endif
+
+ //Set by tts
+ .tts = NULL,
+ .available_languages = NULL,
+
+ //Actions to do when tts state is 'ready'
+ .update_language_list = false,
+
+ .text_to_say_info = NULL
+};
+
+Service_Data *get_pointer_to_service_data_struct()
+{
+ return &service_data;
+}
+
+int screen_reader_create_service(void *data)
+{
+ Service_Data *service_data = data;
+
+ vconf_init(service_data);
+ tts_init(service_data);
+
+#ifdef SCREEN_READER_TV
+ spi_init(service_data);
+#endif
+
+ /* XML TEST */
+#ifdef RUN_IPC_TEST_SUIT
+ run_xml_tests();
+ test_suite_init();
+#endif
+
+ return 0;
+}
+
+int screen_reader_terminate_service(void *data)
+{
+ DEBUG("Service Terminate Callback \n");
+
+ Service_Data *service_data = data;
+
+ tts_stop(service_data->tts);
+ tts_unprepare(service_data->tts);
+ tts_destroy(service_data->tts);
+ service_data->text_from_dbus = NULL;
+ service_data->current_value = NULL;
+
+ return 0;
+}
diff --git a/src/screen_reader_gestures.c b/src/screen_reader_gestures.c
new file mode 100644
index 0000000..c55d606
--- /dev/null
+++ b/src/screen_reader_gestures.c
@@ -0,0 +1,1126 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "screen_reader_gestures.h"
+#include "logger.h"
+
+#include <Ecore.h>
+#include <Ecore_Input.h>
+#include <Ecore_X.h>
+
+static GestureCB _global_cb;
+static void *_global_data;
+static Ecore_Window win;
+static Ecore_Event_Handler *property_changed_hld;
+
+struct _Gestures_Config {
+ // minimal required length of flick gesture (in pixels)
+ int one_finger_flick_min_length;
+ // maximal time of gesture
+ int one_finger_flick_max_time;
+ // timeout period to activate hover gesture (first longpress timeout)
+ double one_finger_hover_longpress_timeout;
+ // to activate flick gesture by 2 fingers (it is hotfix - gestures need serious refactoring)
+ int two_finger_flick_to_scroll_timeout;
+ // after mowing this pixels flick two finger flick to scroll gesture is started
+ int two_finger_flick_to_scroll_min_length;
+ // tap timeout - maximal ammount of time allowed between seqiential taps
+ double one_finger_tap_timeout;
+ // tap radius(in pixels)
+ int one_finger_tap_radius;
+};
+typedef struct _Gestures_Config Gestures_Config;
+
+typedef enum {
+ FLICK_DIRECTION_UNDEFINED,
+ FLICK_DIRECTION_DOWN,
+ FLICK_DIRECTION_UP,
+ FLICK_DIRECTION_LEFT,
+ FLICK_DIRECTION_RIGHT,
+ FLICK_DIRECTION_DOWN_RETURN,
+ FLICK_DIRECTION_UP_RETURN,
+ FLICK_DIRECTION_LEFT_RETURN,
+ FLICK_DIRECTION_RIGHT_RETURN,
+} flick_direction_e;
+
+typedef enum {
+ GESTURE_NOT_STARTED = 0, // Gesture is ready to start
+ GESTURE_ONGOING, // Gesture in progress.
+ GESTURE_FINISHED, // Gesture finished - should be emited
+ GESTURE_ABORTED // Gesture aborted
+} gesture_state_e;
+
+typedef enum {
+ ONE_FINGER_GESTURE = 1,
+ TWO_FINGERS_GESTURE,
+ THREE_FINGERS_GESTURE
+} gesture_type_e;
+
+struct _Cover {
+ Ecore_X_Window win; /**< Input window covering given zone */
+ unsigned int n_taps; /**< Number of fingers touching screen */
+ unsigned int event_time;
+
+ struct {
+ gesture_state_e state; // current state of gesture
+ unsigned int timestamp[3]; // time of gesture;
+ int finger[3]; // finger number which initiates gesture
+ int x_org[3], y_org[3]; // coorinates of finger down event
+ int x_end[3], y_end[3]; // coorinates of finger up event
+ flick_direction_e dir; // direction of flick
+ int n_fingers; // number of fingers in gesture
+ int n_fingers_left; // number of fingers in gesture
+ // still touching screen
+ Eina_Bool finger_out[3]; // finger is out of the finger boundary
+ Eina_Bool return_flick[3];
+ Eina_Bool flick_to_scroll;
+ int flick_to_scroll_last_x;
+ int flick_to_scroll_last_y;
+ } flick_gesture;
+
+ struct {
+ gesture_state_e state; // currest gesture state
+ int x[2], y[2];
+ int n_fingers;
+ int finger[2];
+ unsigned int timestamp; // time of gesture;
+ unsigned int last_emission_time; // last time of gesture emission
+ Ecore_Timer *timer;
+ Eina_Bool longpressed;
+ } hover_gesture;
+
+ struct {
+ Eina_Bool started; // indicates if taps recognition process has started
+ Eina_Bool pressed; // indicates if finger is down
+ int n_taps; // number of taps captures in sequence
+ int finger[3]; // device id of finget
+ Ecore_Timer *timer; // sequence expiration timer
+ int x_org[3], y_org[3]; // coordinates of first tap
+ gesture_type_e tap_type;
+ } tap_gesture_data;
+};
+typedef struct _Cover Cover;
+
+Gestures_Config *_e_mod_config;
+static Ecore_X_Window scrolled_win;
+static int rx, ry;
+static Eina_List *handlers;
+static Cover *cov;
+static int win_angle;
+
+static void _hover_event_emit(Cover * cov, int state);
+static unsigned int _win_angle_get(void);
+
+void __transform_coordinates(int *ax, int *ay)
+{
+ Ecore_X_Window root;
+ int w;
+ int h;
+ int tmp;
+
+ win_angle = _win_angle_get();
+
+ switch (win_angle) {
+ case 90:
+ root = ecore_x_window_root_first_get();
+ ecore_x_window_geometry_get(root, NULL, NULL, &w, &h);
+ tmp = *ax;
+ *ax = h - *ay;
+ *ay = tmp;
+ break;
+ case 270:
+ root = ecore_x_window_root_first_get();
+ ecore_x_window_geometry_get(root, NULL, NULL, &w, &h);
+ tmp = *ax;
+ *ax = *ay;
+ *ay = w - tmp;
+ break;
+ }
+}
+
+static void _event_emit(Gesture g, int x, int y, int x_e, int y_e, int state, int event_time)
+{
+ Gesture_Info *info = calloc(sizeof(Gesture_Info), 1);
+ EINA_SAFETY_ON_NULL_RETURN(info);
+
+ __transform_coordinates(&x, &y);
+ __transform_coordinates(&x_e, &y_e);
+
+ info->type = g;
+ info->x_beg = x;
+ info->x_end = x_e;
+ info->y_beg = y;
+ info->y_end = y_e;
+ info->state = state;
+ info->event_time = event_time;
+
+ if (_global_cb)
+ _global_cb(_global_data, info);
+ free(info);
+}
+
+static void _flick_gesture_mouse_down(Ecore_Event_Mouse_Button * ev, Cover * cov)
+{
+ if (cov->flick_gesture.state == GESTURE_NOT_STARTED) {
+ cov->flick_gesture.state = GESTURE_ONGOING;
+ cov->flick_gesture.finger[0] = ev->multi.device;
+ cov->flick_gesture.x_org[0] = ev->root.x;
+ cov->flick_gesture.y_org[0] = ev->root.y;
+ cov->flick_gesture.timestamp[0] = ev->timestamp;
+ cov->flick_gesture.flick_to_scroll = EINA_FALSE;
+ cov->flick_gesture.n_fingers = 1;
+ cov->flick_gesture.n_fingers_left = 1;
+ cov->flick_gesture.dir = FLICK_DIRECTION_UNDEFINED;
+ cov->flick_gesture.finger_out[0] = EINA_FALSE;
+ cov->flick_gesture.return_flick[0] = EINA_FALSE;
+ } else if (cov->flick_gesture.state == GESTURE_ONGOING) {
+ // abort gesture if too many fingers touched screen
+ if ((cov->n_taps > 3) || (cov->flick_gesture.n_fingers > 2)) {
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ return;
+ }
+
+ cov->flick_gesture.x_org[cov->flick_gesture.n_fingers] = ev->root.x;
+ cov->flick_gesture.y_org[cov->flick_gesture.n_fingers] = ev->root.y;
+ cov->flick_gesture.timestamp[cov->flick_gesture.n_fingers] = ev->timestamp;
+ cov->flick_gesture.finger[cov->flick_gesture.n_fingers] = ev->multi.device;
+ cov->flick_gesture.n_fingers++;
+ cov->flick_gesture.n_fingers_left++;
+ if (cov->flick_gesture.n_fingers < 3) { /* n_fingers == 3 makes out of bounds write */
+ cov->flick_gesture.finger_out[cov->flick_gesture.n_fingers] = EINA_FALSE;
+ cov->flick_gesture.return_flick[cov->flick_gesture.n_fingers] = EINA_FALSE;
+ }
+ }
+}
+
+static Eina_Bool _flick_gesture_time_check(unsigned int event_time, unsigned int gesture_time)
+{
+ DEBUG("Flick time: %d", event_time - gesture_time);
+ if ((event_time - gesture_time) < _e_mod_config->one_finger_flick_max_time * 2) //Double time because of the possible of return flick
+ return EINA_TRUE;
+ else
+ return EINA_FALSE;
+}
+
+static Eina_Bool _flick_gesture_length_check(int x, int y, int x_org, int y_org)
+{
+ int dx = x - x_org;
+ int dy = y - y_org;
+
+ if ((dx * dx + dy * dy) > (_e_mod_config->one_finger_flick_min_length * _e_mod_config->one_finger_flick_min_length))
+ return EINA_TRUE;
+ else
+ return EINA_FALSE;
+}
+
+static flick_direction_e _flick_gesture_direction_get(int x, int y, int x_org, int y_org)
+{
+ int dx = x - x_org;
+ int dy = y - y_org;
+ int tmp;
+
+ switch (win_angle) {
+ case 90:
+ tmp = dx;
+ dx = -dy;
+ dy = tmp;
+ break;
+ case 270:
+ tmp = dx;
+ dx = dy;
+ dy = -tmp;
+ break;
+ }
+
+ if ((dy < 0) && (abs(dx) < -dy))
+ return FLICK_DIRECTION_UP;
+ if ((dy > 0) && (abs(dx) < dy))
+ return FLICK_DIRECTION_DOWN;
+ if ((dx > 0) && (dx > abs(dy)))
+ return FLICK_DIRECTION_RIGHT;
+ if ((dx < 0) && (-dx > abs(dy)))
+ return FLICK_DIRECTION_LEFT;
+
+ return FLICK_DIRECTION_UNDEFINED;
+}
+
+static void _flick_event_emit(Cover * cov)
+{
+ int ax, ay, axe, aye, i, type = -1;
+ ax = ay = axe = aye = 0;
+
+ for (i = 0; i < cov->flick_gesture.n_fingers; i++) {
+ ax += cov->flick_gesture.x_org[i];
+ ay += cov->flick_gesture.y_org[i];
+ axe += cov->flick_gesture.x_end[i];
+ aye += cov->flick_gesture.y_end[i];
+ }
+
+ ax /= cov->flick_gesture.n_fingers;
+ ay /= cov->flick_gesture.n_fingers;
+ axe /= cov->flick_gesture.n_fingers;
+ aye /= cov->flick_gesture.n_fingers;
+
+ if (cov->flick_gesture.dir == FLICK_DIRECTION_LEFT) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_LEFT;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_LEFT;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_LEFT;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_RIGHT) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_RIGHT;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_RIGHT;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_RIGHT;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_UP) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_UP;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_UP;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_UP;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_DOWN) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_DOWN;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_DOWN;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_DOWN;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_DOWN_RETURN) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_DOWN_RETURN;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_DOWN_RETURN;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_DOWN_RETURN;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_UP_RETURN) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_UP_RETURN;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_UP_RETURN;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_UP_RETURN;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_LEFT_RETURN) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_LEFT_RETURN;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_LEFT_RETURN;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_LEFT_RETURN;
+ } else if (cov->flick_gesture.dir == FLICK_DIRECTION_RIGHT_RETURN) {
+ if (cov->flick_gesture.n_fingers == 1)
+ type = ONE_FINGER_FLICK_RIGHT_RETURN;
+ if (cov->flick_gesture.n_fingers == 2)
+ type = TWO_FINGERS_FLICK_RIGHT_RETURN;
+ if (cov->flick_gesture.n_fingers == 3)
+ type = THREE_FINGERS_FLICK_RIGHT_RETURN;
+ }
+ DEBUG("FLICK GESTURE: N: %d F: %d", cov->flick_gesture.n_fingers, cov->flick_gesture.dir);
+ _event_emit(type, ax, ay, axe, aye, 2, cov->event_time);
+}
+
+static void _flick_gesture_mouse_up(Ecore_Event_Mouse_Button * ev, Cover * cov)
+{
+ if (cov->flick_gesture.state == GESTURE_ONGOING) {
+ int i;
+ // check if fingers match
+ for (i = 0; i < cov->flick_gesture.n_fingers; i++) {
+ if (cov->flick_gesture.finger[i] == ev->multi.device)
+ break;
+ }
+ if (i == cov->flick_gesture.n_fingers) {
+ DEBUG("Finger id not recognized. Gesture aborted.");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ goto end;
+ }
+ if (cov->flick_gesture.flick_to_scroll)
+ {
+ if (ev->multi.device == 1) {
+ //if it is second finger then update x and y,
+ //We use last x and y coordinates in end_scroll.
+ //So if the first finger is up before
+ //the second one we will use latest x and y of second finger
+ //because second was the finger that scroll follows.
+ //Else we can encounter that delta between last continue_scroll
+ //coordinates and end_scroll coordinates will be high.
+ cov->flick_gesture.flick_to_scroll_last_x = ev->x;
+ cov->flick_gesture.flick_to_scroll_last_y = ev->y;
+ }
+
+ DEBUG("Flick gesture was interpreted as scroll so we aborting it.");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ goto end;
+ }
+ // check if flick for given finger is valid
+ if (!_flick_gesture_time_check(ev->timestamp, cov->flick_gesture.timestamp[i])) {
+ DEBUG("finger flick gesture timeout expired. Gesture aborted.");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ goto end;
+ }
+ // check minimal flick length
+ if (!_flick_gesture_length_check(ev->root.x, ev->root.y, cov->flick_gesture.x_org[i], cov->flick_gesture.y_org[i])) {
+ if (!cov->flick_gesture.finger_out[i]) {
+ DEBUG("Minimal gesture length not reached and no return flick. Gesture aborted.");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ goto end;
+ }
+ cov->flick_gesture.return_flick[i] = EINA_TRUE;
+ }
+
+ flick_direction_e s = cov->flick_gesture.return_flick[i] ? cov->flick_gesture.dir : _flick_gesture_direction_get(ev->root.x, ev->root.y,
+ cov->flick_gesture.x_org[i],
+ cov->flick_gesture.y_org[i]);
+
+ cov->flick_gesture.n_fingers_left--;
+
+ if ((cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir > FLICK_DIRECTION_RIGHT)
+ && cov->flick_gesture.return_flick[i] == EINA_FALSE) {
+ DEBUG("Flick gesture");
+ cov->flick_gesture.dir = s;
+ }
+ // gesture is valid only if all flicks are in same direction
+ if (cov->flick_gesture.dir != s) {
+ DEBUG("Flick in different direction. Gesture aborted.");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ goto end;
+ }
+
+ cov->flick_gesture.x_end[i] = ev->root.x;
+ cov->flick_gesture.y_end[i] = ev->root.y;
+
+ if (!cov->flick_gesture.n_fingers_left) {
+ _flick_event_emit(cov);
+ cov->flick_gesture.state = GESTURE_NOT_STARTED;
+ }
+ }
+
+ end:
+ // if no finger is touching a screen, gesture will be reseted.
+ if (cov->flick_gesture.state == GESTURE_ABORTED) {
+ if (cov->flick_gesture.flick_to_scroll) {
+ end_scroll(cov->flick_gesture.flick_to_scroll_last_x, cov->flick_gesture.flick_to_scroll_last_y);
+ cov->flick_gesture.flick_to_scroll = EINA_FALSE;
+ }
+ if (cov->n_taps == 0)
+ cov->flick_gesture.state = GESTURE_NOT_STARTED;
+ }
+}
+
+static Eina_Bool _flick_to_scroll_gesture_conditions_met(Ecore_Event_Mouse_Move * ev, int gesture_timestamp, int dx, int dy)
+{
+ if (ev->timestamp - gesture_timestamp > _e_mod_config->two_finger_flick_to_scroll_timeout)
+ if (abs(dx) > _e_mod_config->two_finger_flick_to_scroll_min_length || abs(dy) > _e_mod_config->two_finger_flick_to_scroll_min_length)
+ return EINA_TRUE;
+
+ return EINA_FALSE;
+}
+
+static void _flick_gesture_mouse_move(Ecore_Event_Mouse_Move * ev, Cover * cov)
+{
+ if (cov->flick_gesture.state == GESTURE_ONGOING) {
+ int i;
+ for (i = 0; i < cov->flick_gesture.n_fingers; ++i) {
+ if (cov->flick_gesture.finger[i] == ev->multi.device)
+ break;
+ }
+ if (i == cov->flick_gesture.n_fingers) {
+ if (cov->flick_gesture.n_fingers >= 3) //that is because of the EFL bug. Mouse move event before mouse down(!)
+ {
+ ERROR("Finger id not recognized. Gesture aborted.");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ return;
+ }
+ }
+
+ int dx = ev->root.x - cov->flick_gesture.x_org[i];
+ int dy = ev->root.y - cov->flick_gesture.y_org[i];
+ int tmp;
+
+ switch (win_angle) {
+ case 90:
+ tmp = dx;
+ dx = -dy;
+ dy = tmp;
+ break;
+ case 270:
+ tmp = dx;
+ dx = dy;
+ dy = -tmp;
+ break;
+ }
+ if (i == 1) {
+ if (cov->flick_gesture.flick_to_scroll || _flick_to_scroll_gesture_conditions_met(ev, cov->flick_gesture.timestamp[i], dx, dy)) {
+ if (!cov->flick_gesture.flick_to_scroll) {
+ start_scroll(ev->x, ev->y);
+ cov->flick_gesture.flick_to_scroll = EINA_TRUE;
+ } else {
+ continue_scroll(ev->x, ev->y);
+ }
+ cov->flick_gesture.flick_to_scroll_last_x = ev->x;
+ cov->flick_gesture.flick_to_scroll_last_y = ev->y;
+ return;
+ }
+ }
+
+ if (!cov->flick_gesture.finger_out[i]) {
+ if (abs(dx) > _e_mod_config->one_finger_flick_min_length) {
+ cov->flick_gesture.finger_out[i] = EINA_TRUE;
+ if (dx > 0) {
+ if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_RIGHT_RETURN) {
+ cov->flick_gesture.dir = FLICK_DIRECTION_RIGHT_RETURN;
+ } else {
+ ERROR("Invalid direction, abort");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ }
+ } else {
+ if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_LEFT_RETURN) {
+ cov->flick_gesture.dir = FLICK_DIRECTION_LEFT_RETURN;
+ } else {
+ ERROR("Invalid direction, abort");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ }
+ }
+ return;
+ }
+
+ else if (abs(dy) > _e_mod_config->one_finger_flick_min_length) {
+ cov->flick_gesture.finger_out[i] = EINA_TRUE;
+ if (dy > 0) {
+ if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_DOWN_RETURN) {
+ cov->flick_gesture.dir = FLICK_DIRECTION_DOWN_RETURN;
+ } else {
+ ERROR("Invalid direction, abort");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ }
+ } else {
+ if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_UP_RETURN) {
+ cov->flick_gesture.dir = FLICK_DIRECTION_UP_RETURN;
+ } else {
+ ERROR("Invalid direction, abort");
+ cov->flick_gesture.state = GESTURE_ABORTED;
+ }
+ }
+ return;
+ }
+ }
+ }
+ return;
+}
+
+static Eina_Bool _on_hover_timeout(void *data)
+{
+ Cover *cov = data;
+ DEBUG("Hover timer expierd");
+
+ cov->hover_gesture.longpressed = EINA_TRUE;
+ cov->hover_gesture.timer = NULL;
+
+ if (cov->hover_gesture.last_emission_time == -1) {
+ _hover_event_emit(cov, 0);
+ cov->hover_gesture.last_emission_time = cov->event_time;
+ }
+ return EINA_FALSE;
+}
+
+static void _hover_gesture_timer_reset(Cover * cov, double time)
+{
+ DEBUG("Hover timer reset");
+ cov->hover_gesture.longpressed = EINA_FALSE;
+ if (cov->hover_gesture.timer) {
+ ecore_timer_reset(cov->hover_gesture.timer);
+ return;
+ }
+ cov->hover_gesture.timer = ecore_timer_add(time, _on_hover_timeout, cov);
+}
+
+static void _hover_gesture_mouse_down(Ecore_Event_Mouse_Button * ev, Cover * cov)
+{
+ if (cov->hover_gesture.state == GESTURE_NOT_STARTED && cov->n_taps == 1) {
+ cov->hover_gesture.state = GESTURE_ONGOING;
+ cov->hover_gesture.timestamp = ev->timestamp;
+ cov->hover_gesture.last_emission_time = -1;
+ cov->hover_gesture.x[0] = ev->root.x;
+ cov->hover_gesture.y[0] = ev->root.y;
+ cov->hover_gesture.finger[0] = ev->multi.device;
+ cov->hover_gesture.n_fingers = 1;
+ _hover_gesture_timer_reset(cov, _e_mod_config->one_finger_hover_longpress_timeout);
+ }
+ if (cov->hover_gesture.state == GESTURE_ONGOING && cov->n_taps == 2) {
+ if (cov->hover_gesture.longpressed) {
+ _hover_event_emit(cov, 2);
+ goto abort;
+ }
+ cov->hover_gesture.timestamp = -1;
+ cov->hover_gesture.last_emission_time = -1;
+ cov->hover_gesture.x[1] = ev->root.x;
+ cov->hover_gesture.y[1] = ev->root.y;
+ cov->hover_gesture.finger[1] = ev->multi.device;
+ cov->hover_gesture.n_fingers = 2;
+ _hover_gesture_timer_reset(cov, _e_mod_config->one_finger_hover_longpress_timeout);
+ }
+ // abort gesture if more then 2 fingers touched screen
+ if ((cov->hover_gesture.state == GESTURE_ONGOING) && cov->n_taps > 2) {
+ DEBUG("More then 2 finged. Abort hover gesture");
+ _hover_event_emit(cov, 2);
+ goto abort;
+ }
+ return;
+
+ abort:
+ cov->hover_gesture.state = GESTURE_ABORTED;
+ if (cov->hover_gesture.timer)
+ ecore_timer_del(cov->hover_gesture.timer);
+ cov->hover_gesture.timer = NULL;
+}
+
+static void _hover_gesture_mouse_up(Ecore_Event_Mouse_Button * ev, Cover * cov)
+{
+ int i;
+ if (cov->hover_gesture.state == GESTURE_ONGOING) {
+
+ for (i = 0; i < cov->hover_gesture.n_fingers; i++) {
+ if (cov->hover_gesture.finger[i] == ev->multi.device)
+ break;
+ }
+ if (i == cov->hover_gesture.n_fingers) {
+ DEBUG("Invalid finger id: %d", ev->multi.device);
+ return;
+ } else {
+ cov->hover_gesture.state = GESTURE_ABORTED;
+ if (cov->hover_gesture.timer)
+ ecore_timer_del(cov->hover_gesture.timer);
+ cov->hover_gesture.timer = NULL;
+ // aditionally emit event to complete sequence
+ if (cov->hover_gesture.longpressed)
+ _hover_event_emit(cov, 2);
+ }
+ }
+ // reset gesture only if user released all his fingers
+ if (cov->n_taps == 0)
+ cov->hover_gesture.state = GESTURE_NOT_STARTED;
+}
+
+static void _get_root_coords(Ecore_X_Window win, int *x, int *y)
+{
+ Ecore_X_Window root = ecore_x_window_root_first_get();
+ Ecore_X_Window parent = ecore_x_window_parent_get(win);
+ int wx, wy;
+
+ if (x)
+ *x = 0;
+ if (y)
+ *y = 0;
+
+ while (parent && (root != parent)) {
+ ecore_x_window_geometry_get(parent, &wx, &wy, NULL, NULL);
+ if (x)
+ *x += wx;
+ if (y)
+ *y += wy;
+ parent = ecore_x_window_parent_get(parent);
+ }
+}
+
+Ecore_X_Window top_window_get(int x, int y)
+{
+ Ecore_X_Window wins[1] = { win };
+ Ecore_X_Window under = ecore_x_window_at_xy_with_skip_get(x, y, wins, sizeof(wins) / sizeof(wins[0]));
+ if (under) {
+ _get_root_coords(under, &rx, &ry);
+ DEBUG("Recieved window with coords:%d %d", rx, ry);
+ return under;
+ }
+ return 0;
+}
+
+void start_scroll(int x, int y)
+{
+ Ecore_X_Window wins[1] = { win };
+ Ecore_X_Window under = ecore_x_window_at_xy_with_skip_get(x, y, wins, sizeof(wins) / sizeof(wins[0]));
+ _get_root_coords(under, &rx, &ry);
+ ecore_x_mouse_in_send(under, x - rx, y - ry);
+ ecore_x_window_focus(under);
+ ecore_x_mouse_down_send(under, x - rx, y - ry, 1);
+ scrolled_win = under;
+}
+
+void continue_scroll(int x, int y)
+{
+ ecore_x_mouse_move_send(scrolled_win, x - rx, y - ry);
+}
+
+void end_scroll(int x, int y)
+{
+ ecore_x_mouse_up_send(scrolled_win, x - rx, y - ry, 1);
+ ecore_x_mouse_out_send(scrolled_win, x - rx, y - ry);
+}
+
+static unsigned int _win_angle_get(void)
+{
+ Ecore_X_Window root, first_root;
+ int ret;
+ int count;
+ int angle = 0;
+ unsigned char *prop_data = NULL;
+
+ first_root = ecore_x_window_root_first_get();
+ root = ecore_x_window_root_get(first_root);
+ ret = ecore_x_window_prop_property_get(root, ECORE_X_ATOM_E_ILLUME_ROTATE_ROOT_ANGLE, ECORE_X_ATOM_CARDINAL, 32, &prop_data, &count);
+
+ if (ret && prop_data)
+ memcpy(&angle, prop_data, sizeof(int));
+
+ if (prop_data)
+ free(prop_data);
+
+ return angle;
+}
+
+static void _hover_event_emit(Cover * cov, int state)
+{
+ int ax = 0, ay = 0, j;
+
+ for (j = 0; j < cov->hover_gesture.n_fingers; j++) {
+ ax += cov->hover_gesture.x[j];
+ ay += cov->hover_gesture.y[j];
+ }
+
+ ax /= cov->hover_gesture.n_fingers;
+ ay /= cov->hover_gesture.n_fingers;
+
+ switch (cov->hover_gesture.n_fingers) {
+ case 1:
+ INFO("ONE FINGER HOVER");
+ _event_emit(ONE_FINGER_HOVER, ax, ay, ax, ay, state, cov->event_time);
+ break;
+ default:
+ break;
+ }
+}
+
+static void _hover_gesture_mouse_move(Ecore_Event_Mouse_Move * ev, Cover * cov)
+{
+ if (cov->hover_gesture.state == GESTURE_ONGOING) {
+ // check fingers
+ int i;
+ if (!cov->hover_gesture.longpressed)
+ return;
+
+ for (i = 0; i < cov->hover_gesture.n_fingers; i++) {
+ if (cov->hover_gesture.finger[i] == ev->multi.device)
+ break;
+ }
+ if (i == cov->hover_gesture.n_fingers) {
+ DEBUG("Invalid finger id: %d", ev->multi.device);
+ return;
+ }
+ cov->hover_gesture.x[i] = ev->root.x;
+ cov->hover_gesture.y[i] = ev->root.y;
+ _hover_event_emit(cov, 1);
+ }
+}
+
+static void _tap_event_emit(Cover * cov, int state)
+{
+ switch (cov->tap_gesture_data.n_taps) {
+ case 1:
+ if (cov->tap_gesture_data.tap_type == ONE_FINGER_GESTURE) {
+ DEBUG("ONE_FINGER_SINGLE_TAP");
+ _event_emit(ONE_FINGER_SINGLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], state, cov->event_time);
+ } else if (cov->tap_gesture_data.tap_type == TWO_FINGERS_GESTURE) {
+ DEBUG("TWO_FINGERS_SINGLE_TAP");
+ _event_emit(TWO_FINGERS_SINGLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[1], cov->tap_gesture_data.y_org[1], state, cov->event_time);
+ } else if (cov->tap_gesture_data.tap_type == THREE_FINGERS_GESTURE) {
+ DEBUG("THREE_FINGERS_SINGLE_TAP");
+ _event_emit(THREE_FINGERS_SINGLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[2], cov->tap_gesture_data.y_org[2], state, cov->event_time);
+ } else {
+ ERROR("Unknown tap");
+ }
+ break;
+ case 2:
+ if (cov->tap_gesture_data.tap_type == ONE_FINGER_GESTURE) {
+ DEBUG("ONE_FINGER_DOUBLE_TAP");
+ _event_emit(ONE_FINGER_DOUBLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], state, cov->event_time);
+ } else if (cov->tap_gesture_data.tap_type == TWO_FINGERS_GESTURE) {
+ DEBUG("TWO_FINGERS_DOUBLE_TAP");
+ _event_emit(TWO_FINGERS_DOUBLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[1], cov->tap_gesture_data.y_org[1], state, cov->event_time);
+ } else if (cov->tap_gesture_data.tap_type == THREE_FINGERS_GESTURE) {
+ DEBUG("THREE_FINGERS_DOUBLE_TAP");
+ _event_emit(THREE_FINGERS_DOUBLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[2], cov->tap_gesture_data.y_org[2], state, cov->event_time);
+ } else {
+ ERROR("Unknown tap");
+ }
+ break;
+ case 3:
+ if (cov->tap_gesture_data.tap_type == ONE_FINGER_GESTURE) {
+ DEBUG("ONE_FINGER_TRIPLE_TAP");
+ _event_emit(ONE_FINGER_TRIPLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], state, cov->event_time);
+ } else if (cov->tap_gesture_data.tap_type == TWO_FINGERS_GESTURE) {
+ DEBUG("TWO_FINGERS_TRIPLE_TAP");
+ _event_emit(TWO_FINGERS_TRIPLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[1], cov->tap_gesture_data.y_org[1], state, cov->event_time);
+ } else if (cov->tap_gesture_data.tap_type == THREE_FINGERS_GESTURE) {
+ DEBUG("THREE_FINGERS_TRIPLE_TAP");
+ _event_emit(THREE_FINGERS_TRIPLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[2], cov->tap_gesture_data.y_org[2], state, cov->event_time);
+ } else {
+ ERROR("Unknown tap");
+ }
+ break;
+ default:
+ ERROR("Unknown tap");
+ break;
+ }
+}
+
+static Eina_Bool _on_tap_timer_expire(void *data)
+{
+ Cover *cov = data;
+ DEBUG("Timer expired");
+
+ if (cov->tap_gesture_data.started && !cov->tap_gesture_data.pressed)
+ _tap_event_emit(cov, 2);
+ else
+ _tap_event_emit(cov, 3);
+
+ // finish gesture
+ cov->tap_gesture_data.started = EINA_FALSE;
+ cov->tap_gesture_data.timer = NULL;
+ cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE;
+ cov->tap_gesture_data.finger[0] = -1;
+ cov->tap_gesture_data.finger[1] = -1;
+ cov->tap_gesture_data.finger[2] = -1;
+
+ return EINA_FALSE;
+}
+
+static int _tap_gesture_finger_check(Cover * cov, int x, int y)
+{
+ int dx = x - cov->tap_gesture_data.x_org[0];
+ int dy = y - cov->tap_gesture_data.y_org[0];
+
+ if (cov->tap_gesture_data.finger[0] != -1 && (dx * dx + dy * dy < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius)) {
+ return 0;
+ }
+
+ dx = x - cov->tap_gesture_data.x_org[1];
+ dy = y - cov->tap_gesture_data.y_org[1];
+ if (cov->tap_gesture_data.finger[1] != -1 && (dx * dx + dy * dy < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius)) {
+ return 1;
+ }
+
+ dx = x - cov->tap_gesture_data.x_org[2];
+ dy = y - cov->tap_gesture_data.y_org[2];
+ if (cov->tap_gesture_data.finger[2] != -1 && (dx * dx + dy * dy < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius)) {
+ return 2;
+ }
+
+ return -1;
+}
+
+static void _tap_gestures_mouse_down(Ecore_Event_Mouse_Button * ev, Cover * cov)
+{
+ if (cov->n_taps > 4) {
+ ERROR("Too many fingers");
+ return;
+ }
+
+ cov->tap_gesture_data.pressed = EINA_TRUE;
+
+ if (cov->tap_gesture_data.started == EINA_FALSE) {
+ DEBUG("First finger down");
+ cov->tap_gesture_data.started = EINA_TRUE;
+ cov->tap_gesture_data.finger[0] = ev->multi.device;
+ cov->tap_gesture_data.x_org[0] = ev->root.x;
+ cov->tap_gesture_data.y_org[0] = ev->root.y;
+ cov->tap_gesture_data.finger[1] = -1;
+ cov->tap_gesture_data.finger[2] = -1;
+ cov->tap_gesture_data.n_taps = 0;
+ cov->tap_gesture_data.timer = ecore_timer_add(_e_mod_config->one_finger_tap_timeout, _on_tap_timer_expire, cov);
+ cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE;
+ }
+
+ else {
+ if (ev->multi.device == cov->tap_gesture_data.finger[0]) {
+ DEBUG("First finger down");
+
+ if (_tap_gesture_finger_check(cov, ev->root.x, ev->root.y) == -1) {
+ ERROR("Abort gesture");
+ cov->tap_gesture_data.started = EINA_FALSE;
+ ecore_timer_del(cov->tap_gesture_data.timer);
+ cov->tap_gesture_data.timer = NULL;
+ cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE;
+ cov->tap_gesture_data.finger[0] = -1;
+ cov->tap_gesture_data.finger[1] = -1;
+ cov->tap_gesture_data.finger[2] = -1;
+ _tap_gestures_mouse_down(ev, cov);
+ return;
+ }
+
+ cov->tap_gesture_data.x_org[0] = ev->root.x;
+ cov->tap_gesture_data.y_org[0] = ev->root.y;
+ } else if (cov->tap_gesture_data.finger[1] == -1 || cov->tap_gesture_data.finger[1] == ev->multi.device) {
+ DEBUG("Second finger down");
+ cov->tap_gesture_data.finger[1] = ev->multi.device;
+
+ cov->tap_gesture_data.x_org[1] = ev->root.x;
+ cov->tap_gesture_data.y_org[1] = ev->root.y;
+ if (cov->tap_gesture_data.tap_type < TWO_FINGERS_GESTURE)
+ cov->tap_gesture_data.tap_type = TWO_FINGERS_GESTURE;
+ } else if (cov->tap_gesture_data.finger[2] == -1 || cov->tap_gesture_data.finger[2] == ev->multi.device) {
+ DEBUG("Third finger down");
+ cov->tap_gesture_data.finger[2] = ev->multi.device;
+
+ cov->tap_gesture_data.x_org[2] = ev->root.x;
+ cov->tap_gesture_data.y_org[2] = ev->root.y;
+ if (cov->tap_gesture_data.tap_type < THREE_FINGERS_GESTURE)
+ cov->tap_gesture_data.tap_type = THREE_FINGERS_GESTURE;
+ } else {
+ ERROR("Unknown finger down");
+ }
+ ecore_timer_reset(cov->tap_gesture_data.timer);
+ }
+}
+
+static void _tap_gestures_mouse_up(Ecore_Event_Mouse_Button * ev, Cover * cov)
+{
+ if (cov->tap_gesture_data.timer) {
+ cov->tap_gesture_data.pressed = EINA_FALSE;
+
+ if (ev->multi.device == cov->tap_gesture_data.finger[0]) {
+ DEBUG("First finger up");
+
+ int dx = ev->root.x - cov->tap_gesture_data.x_org[0];
+ int dy = ev->root.y - cov->tap_gesture_data.y_org[0];
+
+ if ((dx * dx + dy * dy) < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) {
+ if (cov->n_taps == 0) {
+ cov->tap_gesture_data.n_taps++;
+ }
+ } else {
+ ERROR("Abort gesture");
+ cov->tap_gesture_data.started = EINA_FALSE;
+ }
+ } else if (ev->multi.device == cov->tap_gesture_data.finger[1]) {
+ DEBUG("Second finger up");
+
+ int dx = ev->root.x - cov->tap_gesture_data.x_org[1];
+ int dy = ev->root.y - cov->tap_gesture_data.y_org[1];
+
+ if ((dx * dx + dy * dy) < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) {
+ if (cov->n_taps == 0) {
+ cov->tap_gesture_data.n_taps++;
+ }
+ } else {
+ ERROR("Abort gesture");
+ cov->tap_gesture_data.started = EINA_FALSE;
+ }
+ } else if (ev->multi.device == cov->tap_gesture_data.finger[2]) {
+ DEBUG("Third finger up");
+
+ int dx = ev->root.x - cov->tap_gesture_data.x_org[2];
+ int dy = ev->root.y - cov->tap_gesture_data.y_org[2];
+
+ if ((dx * dx + dy * dy) < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) {
+ if (cov->n_taps == 0) {
+ cov->tap_gesture_data.n_taps++;
+ }
+ } else {
+ ERROR("Abort gesture");
+ cov->tap_gesture_data.started = EINA_FALSE;
+ }
+ } else {
+ ERROR("Unknown finger up, abort gesture");
+ cov->tap_gesture_data.started = EINA_FALSE;
+ }
+ }
+}
+
+static void _tap_gestures_move(Ecore_Event_Mouse_Move * ev, Cover * cov)
+{
+ int i;
+ for (i = 0; i < sizeof(cov->tap_gesture_data.finger) / sizeof(cov->tap_gesture_data.finger[0]); i++) {
+ if (ev->multi.device == cov->tap_gesture_data.finger[i]) {
+ int dx = ev->root.x - cov->tap_gesture_data.x_org[i];
+ int dy = ev->root.y - cov->tap_gesture_data.y_org[i];
+
+ if ((dx * dx + dy * dy) > _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) {
+ DEBUG("abort tap gesutre");
+ cov->tap_gesture_data.started = EINA_FALSE;
+ ecore_timer_del(cov->tap_gesture_data.timer);
+ cov->tap_gesture_data.timer = NULL;
+ cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE;
+ cov->tap_gesture_data.finger[0] = -1;
+ cov->tap_gesture_data.finger[1] = -1;
+ cov->tap_gesture_data.finger[2] = -1;
+ }
+ break;
+ }
+ }
+}
+
+static Eina_Bool _cb_mouse_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
+{
+ Ecore_Event_Mouse_Button *ev = event;
+
+ cov->n_taps++;
+ cov->event_time = ev->timestamp;
+
+ DEBUG("mouse down: multi.device: %d, taps: %d", ev->multi.device, cov->n_taps);
+
+ win_angle = _win_angle_get();
+
+ _flick_gesture_mouse_down(ev, cov);
+ _hover_gesture_mouse_down(ev, cov);
+ _tap_gestures_mouse_down(ev, cov);
+
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool _cb_mouse_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
+{
+ Ecore_Event_Mouse_Button *ev = event;
+
+ cov->n_taps--;
+ cov->event_time = ev->timestamp;
+
+ DEBUG("mouse up, multi.device: %d, taps: %d", ev->multi.device, cov->n_taps);
+
+ _flick_gesture_mouse_up(ev, cov);
+ _hover_gesture_mouse_up(ev, cov);
+ _tap_gestures_mouse_up(ev, cov);
+
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool _cb_mouse_move(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
+{
+ Ecore_Event_Mouse_Move *ev = event;
+
+ cov->event_time = ev->timestamp;
+
+ _flick_gesture_mouse_move(ev, cov);
+ _hover_gesture_mouse_move(ev, cov);
+ _tap_gestures_move(ev, cov);
+
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool _gesture_input_win_create(void)
+{
+ int w, h;
+
+ if (!win) {
+ Ecore_Window root = ecore_x_window_root_first_get();
+ if (!root)
+ return EINA_FALSE;
+ ecore_x_window_geometry_get(root, NULL, NULL, &w, &h);
+ win = ecore_x_window_input_new(root, 0, 0, w, h);
+ }
+ if (!win)
+ return EINA_FALSE;
+
+ ecore_x_input_multi_select(win);
+ ecore_x_window_show(win);
+ ecore_x_window_raise(win);
+
+ // restet gestures
+ memset(cov, 0x0, sizeof(Cover));
+
+ return EINA_TRUE;
+}
+
+static Eina_Bool _win_property_changed(void *data, int type, void *event)
+{
+ Ecore_X_Event_Window_Property *wp = event;
+
+ if (wp->atom != ECORE_X_ATOM_NET_CLIENT_LIST_STACKING)
+ return EINA_TRUE;
+
+ _gesture_input_win_create();
+
+ return EINA_TRUE;
+}
+
+static Eina_Bool _gestures_input_window_init(void)
+{
+ Ecore_Window root = ecore_x_window_root_first_get();
+ if (!root) {
+ ERROR("No root window found. Is Window manager running?");
+ return EINA_FALSE;
+ }
+ ecore_x_event_mask_set(root, ECORE_X_EVENT_MASK_WINDOW_PROPERTY);
+ property_changed_hld = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, _win_property_changed, NULL);
+
+ return _gesture_input_win_create();
+}
+
+static void _gestures_input_widnow_shutdown(void)
+{
+ ecore_event_handler_del(property_changed_hld);
+ if (win)
+ ecore_x_window_free(win);
+ win = 0;
+}
+
+Eina_Bool screen_reader_gestures_init(void)
+{
+ ecore_init();
+ ecore_x_init(NULL);
+
+ cov = calloc(sizeof(Cover), 1);
+
+ if (!_gestures_input_window_init()) {
+ free(cov);
+ return EINA_FALSE;
+ }
+
+ _e_mod_config = calloc(sizeof(Gestures_Config), 1);
+ _e_mod_config->one_finger_flick_min_length = 100;
+ _e_mod_config->one_finger_flick_max_time = 400;
+ _e_mod_config->two_finger_flick_to_scroll_timeout = 100;
+ _e_mod_config->two_finger_flick_to_scroll_min_length = 50;
+ _e_mod_config->one_finger_hover_longpress_timeout = 0.81;
+ _e_mod_config->one_finger_tap_timeout = 0.4;
+ _e_mod_config->one_finger_tap_radius = 100;
+
+ handlers = eina_list_append(NULL, ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE, _cb_mouse_move, NULL));
+ handlers = eina_list_append(handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, _cb_mouse_up, NULL));
+ handlers = eina_list_append(handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, _cb_mouse_down, NULL));
+
+ return EINA_TRUE;
+}
+
+void screen_reader_gestures_shutdown(void)
+{
+ Ecore_Event_Handler *hdlr;
+ EINA_LIST_FREE(handlers, hdlr) {
+ ecore_event_handler_del(hdlr);
+ }
+ _gestures_input_widnow_shutdown();
+
+ ecore_x_shutdown();
+ ecore_shutdown();
+ free(_e_mod_config);
+ free(cov);
+}
+
+void screen_reader_gestures_tracker_register(GestureCB cb, void *data)
+{
+ _global_cb = cb;
+ _global_data = data;
+}
diff --git a/src/screen_reader_haptic.c b/src/screen_reader_haptic.c
new file mode 100644
index 0000000..f099cb4
--- /dev/null
+++ b/src/screen_reader_haptic.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <device/haptic.h>
+#include "logger.h"
+#include "smart_notification.h"
+
+static haptic_device_h handle;
+static haptic_effect_h effect_handle;
+
+#define RED "\x1B[31m"
+#define RESET "\033[0m"
+
+/**
+ * @brief Initializer for haptic module
+ *
+ */
+void haptic_module_init(void)
+{
+ int num;
+
+ if (!device_haptic_get_count(&num)) {
+ DEBUG(RED "Haptic device received!" RESET);
+ } else {
+ ERROR("Cannot receive haptic device count");
+ return;
+ }
+
+ if (!device_haptic_open(0, &handle)) {
+ DEBUG(RED "Device connected!" RESET);
+ } else {
+ ERROR("Cannot open haptic device");
+ }
+}
+
+/**
+ * @brief Disconnect haptic handle
+ *
+ */
+void haptic_module_disconnect(void)
+{
+ if (!handle) {
+ ERROR("Haptic handle lost");
+ return;
+ }
+ if (!device_haptic_close(handle)) {
+ DEBUG("Haptic disconnected");
+ } else {
+ ERROR("Haptic close error");
+ }
+}
+
+/**
+ * @brief Start vibrations
+ *
+ */
+void haptic_vibrate_start(void)
+{
+ if (!handle) {
+ ERROR("Haptic handle lost");
+ return;
+ }
+ if (!device_haptic_vibrate(handle, 1000, 100, &effect_handle)) {
+ DEBUG(RED "Vibrations started!" RESET);
+ } else {
+ ERROR("Cannot start vibration");
+ }
+}
+
+/**
+ * @brief Stop vibrations
+ *
+ */
+void haptic_vibrate_stop(void)
+{
+ if (!handle) {
+ ERROR("Haptic handle lost");
+ return;
+ }
+ if (!device_haptic_stop(handle, &effect_handle)) {
+ ERROR("Vibrations stopped!");
+ } else {
+ DEBUG(RED "Cannot stop vibration" RESET);
+ }
+}
diff --git a/src/screen_reader_spi.c b/src/screen_reader_spi.c
new file mode 100644
index 0000000..10645de
--- /dev/null
+++ b/src/screen_reader_spi.c
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include "screen_reader_spi.h"
+#include "screen_reader_tts.h"
+#include "logger.h"
+#ifdef RUN_IPC_TEST_SUIT
+#include "test_suite/test_suite.h"
+#endif
+
+#define EPS 0.000000001
+
+/** @brief Service_Data used as screen reader internal data struct*/
+static Service_Data *service_data;
+
+typedef struct {
+ char *key;
+ char *val;
+} Attr;
+
+/**
+ * @brief Debug function. Print current toolkit version/event
+ * type/event source/event detail1/event detail2
+ *
+ * @param AtspiEvent instance
+ *
+ */
+static void display_info(const AtspiEvent * event)
+{
+ AtspiAccessible *source = event->source;
+ gchar *name = atspi_accessible_get_name(source, NULL);
+ gchar *role = atspi_accessible_get_localized_role_name(source, NULL);
+ gchar *toolkit = atspi_accessible_get_toolkit_name(source, NULL);
+
+ DEBUG("--------------------------------------------------------");
+ DEBUG("Toolkit: %s; Event_type: %s; (%d, %d)", toolkit, event->type, event->detail1, event->detail2);
+ DEBUG("Name: %s; Role: %s", name, role);
+ DEBUG("--------------------------------------------------------");
+}
+
+Eina_Bool double_click_timer_cb(void *data)
+{
+ Service_Data *sd = data;
+ sd->clicked_widget = NULL;
+
+ return EINA_FALSE;
+}
+
+bool allow_recursive_name(AtspiAccessible * obj)
+{
+ AtspiRole r = atspi_accessible_get_role(obj, NULL);
+ if (r == ATSPI_ROLE_FILLER)
+ return true;
+ return false;
+}
+
+char *generate_description_for_subtree(AtspiAccessible * obj)
+{
+ DEBUG("START");
+ if (!allow_recursive_name(obj))
+ return strdup("");
+
+ if (!obj)
+ return strdup("");
+ int child_count = atspi_accessible_get_child_count(obj, NULL);
+
+ DEBUG("There is %d children inside this filler", child_count);
+ if (!child_count)
+ return strdup("");
+
+ int i;
+ char *name = NULL;
+ char *below = NULL;
+ char ret[256] = "\0";
+ AtspiAccessible *child = NULL;
+ for (i = 0; i < child_count; i++) {
+ child = atspi_accessible_get_child_at_index(obj, i, NULL);
+ name = atspi_accessible_get_name(child, NULL);
+ DEBUG("%d child name:%s", i, name);
+ if (name && strncmp(name, "\0", 1)) {
+ strncat(ret, name, sizeof(ret) - strlen(ret) - 1);
+ }
+ strncat(ret, " ", sizeof(ret) - strlen(ret) - 1);
+ below = generate_description_for_subtree(child);
+ if (strncmp(below, "\0", 1)) {
+ strncat(ret, below, sizeof(ret) - strlen(ret) - 1);
+ }
+ g_object_unref(child);
+ free(below);
+ free(name);
+ }
+ return strdup(ret);
+}
+
+static char *spi_on_state_changed_get_text(AtspiEvent * event, void *user_data)
+{
+ Service_Data *sd = (Service_Data *) user_data;
+ char *name;
+ char *names = NULL;
+ char *description;
+ char *role_name;
+ char *other;
+ char ret[256] = "\0";
+ sd->currently_focused = event->source;
+
+ description = atspi_accessible_get_description(sd->currently_focused, NULL);
+ name = atspi_accessible_get_name(sd->currently_focused, NULL);
+ role_name = atspi_accessible_get_localized_role_name(sd->currently_focused, NULL);
+ other = generate_description_for_subtree(sd->currently_focused);
+
+ DEBUG("->->->->->-> WIDGET GAINED HIGHLIGHT: %s <-<-<-<-<-<-<-", name);
+ DEBUG("->->->->->-> FROM SUBTREE HAS NAME: %s <-<-<-<-<-<-<-", other);
+
+ if (name && strncmp(name, "\0", 1))
+ names = strdup(name);
+ else if (other && strncmp(other, "\0", 1))
+ names = strdup(other);
+
+ if (names) {
+ strncat(ret, names, sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ }
+
+ if (role_name)
+ strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1);
+
+ if (description) {
+ if (strncmp(description, "\0", 1))
+ strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1);
+ strncat(ret, description, sizeof(ret) - strlen(ret) - 1);
+ }
+
+ free(name);
+ free(names);
+ free(description);
+ free(role_name);
+ free(other);
+
+ return strdup(ret);
+}
+
+static char *spi_on_caret_move_get_text(AtspiEvent * event, void *user_data)
+{
+ Service_Data *sd = (Service_Data *) user_data;
+ sd->currently_focused = event->source;
+ char *return_text;
+
+ AtspiText *text_interface = atspi_accessible_get_text_iface(sd->currently_focused);
+ if (text_interface) {
+ DEBUG("->->->->->-> WIDGET CARET MOVED: %s <-<-<-<-<-<-<-", atspi_accessible_get_name(sd->currently_focused, NULL));
+
+ int char_count = (int)atspi_text_get_character_count(text_interface, NULL);
+ int caret_pos = atspi_text_get_caret_offset(text_interface, NULL);
+ if (!caret_pos) {
+ DEBUG("MIN POSITION REACHED");
+ if (asprintf(&return_text, "%s %s", (char *)atspi_text_get_text(text_interface, caret_pos, caret_pos + 1, NULL), _("IDS_REACHED_MIN_POS")) < 0) {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ } else if (char_count == caret_pos) {
+ DEBUG("MAX POSITION REACHED");
+ if (asprintf(&return_text, "%s %s", (char *)atspi_text_get_text(text_interface, caret_pos, caret_pos + 1, NULL), _("IDS_REACHED_MAX_POS")) < 0) {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ } else {
+ if (asprintf(&return_text, "%s", (char *)atspi_text_get_text(text_interface, caret_pos, caret_pos + 1, NULL)) < 0) {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ }
+ } else {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ return return_text;
+}
+
+static char *spi_on_value_changed_get_text(AtspiEvent * event, void *user_data)
+{
+ Service_Data *sd = (Service_Data *) user_data;
+ char *text_to_read = NULL;
+
+ sd->currently_focused = event->source;
+
+ AtspiValue *value_interface = atspi_accessible_get_value_iface(sd->currently_focused);
+ if (value_interface) {
+ DEBUG("->->->->->-> WIDGET VALUE CHANGED: %s <-<-<-<-<-<-<-", atspi_accessible_get_name(sd->currently_focused, NULL));
+
+ double current_temp_value = (double)atspi_value_get_current_value(value_interface, NULL);
+ if (abs(current_temp_value - atspi_value_get_maximum_value(value_interface, NULL)) < EPS) {
+ DEBUG("MAX VALUE REACHED");
+ if (asprintf(&text_to_read, "%.2f %s", current_temp_value, _("IDS_REACHED_MAX_VAL")) < 0) {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ } else if (abs(current_temp_value - atspi_value_get_minimum_value(value_interface, NULL)) < EPS) {
+ DEBUG("MIN VALUE REACHED");
+ if (asprintf(&text_to_read, "%.2f %s", current_temp_value, _("IDS_REACHED_MIN_VAL")) < 0) {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ } else {
+ if (asprintf(&text_to_read, "%.2f", current_temp_value) < 0) {
+ ERROR(MEMORY_ERROR);
+ return NULL;
+ }
+ }
+ }
+
+ return text_to_read;
+}
+
+char *spi_event_get_text_to_read(AtspiEvent * event, void *user_data)
+{
+ DEBUG("START");
+ Service_Data *sd = (Service_Data *) user_data;
+ char *text_to_read;
+
+ DEBUG("TRACK SIGNAL:%s", sd->tracking_signal_name);
+ DEBUG("WENT EVENT:%s", event->type);
+
+ if (!sd->tracking_signal_name) {
+ ERROR("Invalid tracking signal name");
+ return NULL;
+ }
+
+ if (!strncmp(event->type, sd->tracking_signal_name, strlen(event->type)) && event->detail1 == 1) {
+ text_to_read = spi_on_state_changed_get_text(event, user_data);
+ } else if (!strncmp(event->type, CARET_MOVED_SIG, strlen(event->type))) {
+ text_to_read = spi_on_caret_move_get_text(event, user_data);
+ } else if (!strncmp(event->type, VALUE_CHANGED_SIG, strlen(event->type))) {
+ text_to_read = spi_on_value_changed_get_text(event, user_data);
+ } else {
+ ERROR("Unknown event type");
+ return NULL;
+ }
+
+ return text_to_read;
+}
+
+void spi_event_listener_cb(AtspiEvent * event, void *user_data)
+{
+ DEBUG("START");
+ display_info(event);
+
+ if (!user_data) {
+ ERROR("Invalid parameter");
+ return;
+ }
+
+ char *text_to_read = spi_event_get_text_to_read(event, user_data);
+ if (!text_to_read) {
+ ERROR("Can not prepare text to read");
+ return;
+ }
+ DEBUG("SPEAK: %s", text_to_read);
+ tts_speak(text_to_read, EINA_TRUE);
+
+ free(text_to_read);
+ DEBUG("END");
+}
+
+/**
+ * @brief Initializer for screen-reader atspi listeners
+ *
+ * @param user_data screen-reader internal data
+ *
+**/
+void spi_init(Service_Data * sd)
+{
+ if (!sd) {
+ ERROR("Invalid parameter");
+ return;
+ }
+ DEBUG("--------------------- SPI_init START ---------------------");
+ service_data = sd;
+
+ DEBUG(">>> Creating listeners <<<");
+
+ sd->spi_listener = atspi_event_listener_new(spi_event_listener_cb, service_data, NULL);
+ if (sd->spi_listener == NULL) {
+ DEBUG("FAILED TO CREATE spi state changed listener");
+ }
+ // ---------------------------------------------------------------------------------------------------
+
+ DEBUG("TRACKING SIGNAL:%s", sd->tracking_signal_name);
+
+ gboolean ret1 = atspi_event_listener_register(sd->spi_listener, sd->tracking_signal_name, NULL);
+ if (ret1 == false) {
+ DEBUG("FAILED TO REGISTER spi focus/highlight listener");
+ }
+ GError *error = NULL;
+ gboolean ret2 = atspi_event_listener_register(sd->spi_listener, CARET_MOVED_SIG, &error);
+ if (ret2 == false) {
+ DEBUG("FAILED TO REGISTER spi caret moved listener: %s", error ? error->message : "no error message");
+ if (error)
+ g_clear_error(&error);
+ }
+
+ gboolean ret3 = atspi_event_listener_register(sd->spi_listener, VALUE_CHANGED_SIG, &error);
+ if (ret3 == false) {
+ DEBUG("FAILED TO REGISTER spi value changed listener: %s", error ? error->message : "no error message");
+ if (error)
+ g_clear_error(&error);
+ }
+
+ if (ret1 == true && ret2 == true && ret3 == true) {
+ DEBUG("spi listener REGISTERED");
+ }
+
+ DEBUG("---------------------- SPI_init END ----------------------\n\n");
+}
diff --git a/src/screen_reader_switch.c b/src/screen_reader_switch.c
new file mode 100644
index 0000000..651d49f
--- /dev/null
+++ b/src/screen_reader_switch.c
@@ -0,0 +1,126 @@
+#include "logger.h"
+#include <Eldbus.h>
+
+Eina_Bool screen_reader_switch_enabled_get(Eina_Bool * value)
+{
+ Eldbus_Connection *conn;
+ Eldbus_Object *dobj;
+ Eldbus_Proxy *proxy;
+ Eldbus_Message *req, *reply;
+ const char *errname = NULL, *errmsg = NULL;
+ Eina_Bool ret = EINA_FALSE;
+ Eldbus_Message_Iter *iter;
+
+ eldbus_init();
+
+ if (!(conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION))) {
+ ERROR("Connection to session bus failed");
+ return EINA_FALSE;
+ }
+ if (!(dobj = eldbus_object_get(conn, "org.a11y.Bus", "/org/a11y/bus"))) {
+ ERROR("Failed to create eldbus object for /org/a11y/bus");
+ goto fail_obj;
+ }
+ if (!(proxy = eldbus_proxy_get(dobj, "org.freedesktop.DBus.Properties"))) {
+ ERROR("Failed to create proxy object for 'org.freedesktop.DBus.Properties'");
+ goto fail_proxy;
+ }
+ if (!(req = eldbus_proxy_method_call_new(proxy, "Get"))) {
+ ERROR("Failed to create method call on org.freedesktop.DBus.Properties.Get");
+ goto fail_proxy;
+ }
+ eldbus_message_ref(req);
+
+ if (!eldbus_message_arguments_append(req, "ss", "org.a11y.Status", "ScreenReaderEnabled")) {
+ ERROR("Failed to append message args");
+ goto fail_msg;
+ }
+
+ reply = eldbus_proxy_send_and_block(proxy, req, 100);
+ if (!reply || eldbus_message_error_get(reply, &errname, &errmsg)) {
+ ERROR("Unable to call method org.freedesktop.DBus.Properties.Get: %s %s", errname, errmsg);
+ goto fail_msg;
+ }
+
+ if (!eldbus_message_arguments_get(reply, "v", &iter)) {
+ ERROR("Invalid answer signature");
+ goto fail_msg;
+ } else {
+ if (!eldbus_message_iter_arguments_get(iter, "b", value)) {
+ ERROR("Invalid variant signature");
+ } else
+ ret = EINA_TRUE;
+ }
+
+ fail_msg:
+ eldbus_message_unref(req);
+ fail_proxy:
+ eldbus_object_unref(dobj);
+ fail_obj:
+ eldbus_connection_unref(conn);
+
+ eldbus_shutdown();
+
+ return ret;
+}
+
+Eina_Bool screen_reader_switch_enabled_set(Eina_Bool value)
+{
+ Eldbus_Connection *conn;
+ Eldbus_Object *dobj;
+ Eldbus_Proxy *proxy;
+ Eldbus_Message *req, *reply;
+ const char *errname = NULL, *errmsg = NULL;
+ Eina_Bool ret = EINA_FALSE;
+ Eldbus_Message_Iter *iter;
+
+ eldbus_init();
+
+ if (!(conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION))) {
+ ERROR("Connection to session bus failed");
+ return EINA_FALSE;
+ }
+ if (!(dobj = eldbus_object_get(conn, "org.a11y.Bus", "/org/a11y/bus"))) {
+ ERROR("Failed to create eldbus object");
+ goto fail_obj;
+ }
+ if (!(proxy = eldbus_proxy_get(dobj, "org.freedesktop.DBus.Properties"))) {
+ ERROR("Failed to create proxy object for 'org.freedesktop.DBus.Properties'");
+ goto fail_proxy;
+ }
+ if (!(req = eldbus_proxy_method_call_new(proxy, "Set"))) {
+ ERROR("Failed to create method call on org.freedesktop.DBus.Properties.Set");
+ goto fail_proxy;
+ }
+ eldbus_message_ref(req);
+
+ if (!eldbus_message_arguments_append(req, "ss", "org.a11y.Status", "ScreenReaderEnabled")) {
+ ERROR("Failed to append message args");
+ goto fail_msg;
+ }
+ if (!(iter = eldbus_message_iter_container_new(eldbus_message_iter_get(req), 'v', "b"))) {
+ ERROR("Unable to create variant iterator");
+ goto fail_msg;
+ }
+ if (!eldbus_message_iter_arguments_append(iter, "b", value)) {
+ ERROR("Unable to append to variant iterator");
+ goto fail_msg;
+ }
+ if (!eldbus_message_iter_container_close(eldbus_message_iter_get(req), iter)) {
+ ERROR("Failed to close variant iterator");
+ goto fail_msg;
+ }
+ eldbus_proxy_send(proxy, req, NULL, NULL, -1.0);
+ ret = EINA_TRUE;
+
+ fail_msg:
+ eldbus_message_unref(req);
+ fail_proxy:
+ eldbus_object_unref(dobj);
+ fail_obj:
+ eldbus_connection_unref(conn);
+
+ eldbus_shutdown();
+
+ return ret;
+}
diff --git a/src/screen_reader_system.c b/src/screen_reader_system.c
new file mode 100644
index 0000000..b3a457b
--- /dev/null
+++ b/src/screen_reader_system.c
@@ -0,0 +1,670 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCREEN_READER_TV
+
+#define _GNU_SOURCE
+
+#include <device/battery.h>
+#include <device/display.h>
+#include <device/callback.h>
+#include <bluetooth.h>
+#include <tapi_common.h>
+#include <TelNetwork.h>
+#include <vconf.h>
+#include <wifi.h>
+#include <notification.h>
+#include <notification_list.h>
+
+#include "screen_reader.h"
+#include "screen_reader_tts.h"
+#include "smart_notification.h"
+#include "logger.h"
+
+#define MAX_SIM_COUNT 2
+#define DATE_TIME_BUFFER_SIZE 26
+
+TapiHandle *tapi_handle[MAX_SIM_COUNT + 1] = { 0, };
+
+static void device_system_cb(device_callback_e type, void *value, void *user_data);
+
+static void tapi_init(void)
+{
+ int i = 0;
+ char **cp_list = tel_get_cp_name_list();
+
+ if (!cp_list) {
+ ERROR("cp name list is null");
+ return;
+ }
+
+ DEBUG("TAPI INIT");
+ for (i = 0; cp_list[i]; ++i) {
+ tapi_handle[i] = tel_init(cp_list[i]);
+ DEBUG("CP_LIST %d = %s", i, cp_list[i]);
+ }
+
+}
+
+/**
+ * @brief Initializer for smart notifications
+ *
+ */
+void system_notifications_init(void)
+{
+ DEBUG("******************** START ********************");
+ int ret = -1;
+
+ // BATTERY LOW/FULL
+ device_add_callback(DEVICE_CALLBACK_BATTERY_LEVEL, device_system_cb, NULL);
+ // BATTERY CHARGING/NOT-CHARGING
+ device_add_callback(DEVICE_CALLBACK_BATTERY_CHARGING, device_system_cb, NULL);
+ // SCREEN OFF/ON
+ device_add_callback(DEVICE_CALLBACK_DISPLAY_STATE, device_system_cb, NULL);
+
+ ret = bt_initialize();
+ if (ret != BT_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ }
+
+ ret = wifi_initialize();
+ if (ret != WIFI_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ }
+
+ tapi_init();
+
+ DEBUG(" ********************* END ********************* ");
+}
+
+/**
+ * @brief Initializer for smart notifications
+ *
+ */
+void system_notifications_shutdown(void)
+{
+ int ret = -1;
+
+ // BATTERY LOW/FULL
+ device_remove_callback(DEVICE_CALLBACK_BATTERY_LEVEL, device_system_cb);
+ // BATTERY CHARGING/NOT-CHARGING
+ device_remove_callback(DEVICE_CALLBACK_BATTERY_CHARGING, device_system_cb);
+ // SCREEN OFF/ON
+ device_remove_callback(DEVICE_CALLBACK_DISPLAY_STATE, device_system_cb);
+
+ ret = bt_deinitialize();
+ if (ret != BT_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ }
+
+ ret = wifi_deinitialize();
+ if (ret != WIFI_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ return;
+ }
+}
+
+/**
+ * @brief Device system callback handler
+ *
+ * @param type Device callback type
+ * @param value UNUSED
+ * @param user_data UNUSED
+ */
+static void device_system_cb(device_callback_e type, void *value, void *user_data)
+{
+ if (type == DEVICE_CALLBACK_BATTERY_LEVEL) {
+ device_battery_level_e status;
+ if (device_battery_get_level_status(&status)) {
+ ERROR("Cannot get battery level status");
+ return;
+ }
+
+ if (status == DEVICE_BATTERY_LEVEL_LOW) {
+ tts_speak(_("IDS_SYSTEM_BATTERY_LOW"), EINA_TRUE);
+ } else if (status == DEVICE_BATTERY_LEVEL_CRITICAL) {
+ tts_speak(_("IDS_SYSTEM_BATTERY_CRITICAL"), EINA_TRUE);
+ } else if (status == DEVICE_BATTERY_LEVEL_FULL) {
+ tts_speak(_("IDS_SYSTEM_BATTERY_FULL"), EINA_TRUE);
+ }
+ } else if (type == DEVICE_CALLBACK_BATTERY_CHARGING) {
+ bool charging;
+ if (device_battery_is_charging(&charging)) {
+ ERROR("Cannot check if battery is charging");
+ return;
+ }
+
+ if (!charging)
+ tts_speak(_("IDS_SYSTEM_NOT_CHARGING"), EINA_FALSE);
+ } else if (type == DEVICE_CALLBACK_DISPLAY_STATE) {
+ display_state_e state;
+ if (device_display_get_state(&state)) {
+ ERROR("Cannot check if battery is charging");
+ return;
+ }
+
+ if (state == DISPLAY_STATE_NORMAL) {
+ tts_speak(_("IDS_SYSTEM_SCREEN_ON"), EINA_FALSE);
+ } else if (state == DISPLAY_STATE_SCREEN_OFF) {
+ tts_speak(_("IDS_SYSTEM_SCREEN_OFF"), EINA_FALSE);
+ }
+ }
+}
+
+// ******************************** Indicator info ********************************** //
+
+static int _read_text_get(char *key)
+{
+ int read_text = 0;
+ int ret = -1;
+
+ ret = vconf_get_bool(key, &read_text);
+ if (ret != 0) {
+ ERROR("ret == %d", ret);
+ return true;
+ }
+
+ return read_text;
+}
+
+void device_time_get(void)
+{
+ char buffer[DATE_TIME_BUFFER_SIZE];
+ int disp_12_24 = VCONFKEY_TIME_FORMAT_12;
+ int ret = -1;
+ time_t rawtime = 0;
+ struct tm *timeinfo = NULL;
+
+ if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_TIME)) {
+ return;
+ }
+
+ time(&rawtime);
+ timeinfo = localtime(&rawtime);
+ if (!timeinfo) {
+ ERROR("localtime returns NULL");
+ return;
+ }
+
+ ret = vconf_get_int(VCONFKEY_REGIONFORMAT_TIME1224, &disp_12_24);
+ if (ret != 0) {
+ ERROR("ret == %d", ret);
+ }
+
+ if (disp_12_24 == VCONFKEY_TIME_FORMAT_24) {
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "Current time: %H %M", timeinfo);
+ } else {
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "Current time: %I %M %p", timeinfo);
+ }
+
+ DEBUG("Text to say: %s", buffer);
+ tts_speak(buffer, EINA_FALSE);
+}
+
+char *device_error_to_string(int e)
+{
+ switch (e) {
+ case DEVICE_ERROR_NONE:
+ return "DEVICE_ERROR_NONE";
+ break;
+
+ case DEVICE_ERROR_OPERATION_FAILED:
+ return "DEVICE_ERROR_OPERATION_FAILED";
+ break;
+
+ case DEVICE_ERROR_PERMISSION_DENIED:
+ return "DEVICE_ERROR_PERMISSION_DENIED";
+ break;
+
+ case DEVICE_ERROR_INVALID_PARAMETER:
+ return "DEVICE_ERROR_INVALID_PARAMETER";
+ break;
+
+ case DEVICE_ERROR_ALREADY_IN_PROGRESS:
+ return "DEVICE_ERROR_ALREADY_IN_PROGRESS";
+ break;
+
+ case DEVICE_ERROR_NOT_SUPPORTED:
+ return "DEVICE_ERROR_NOT_SUPPORTED";
+ break;
+
+ case DEVICE_ERROR_RESOURCE_BUSY:
+ return "DEVICE_ERROR_RESOURCE_BUSY";
+ break;
+
+ case DEVICE_ERROR_NOT_INITIALIZED:
+ return "DEVICE_ERROR_NOT_INITIALIZED";
+ break;
+
+ default:
+ return _("IDS_SYSTEM_NETWORK_SERVICE_UNKNOWN");
+ break;
+ }
+}
+
+void device_battery_get(void)
+{
+ char *buffer = NULL;
+ char *charging_text = NULL;
+ int percent;
+ bool is_charging = false;
+ int ret = -1;
+
+ if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_BATTERY)) {
+ return;
+ }
+
+ ret = device_battery_is_charging(&is_charging);
+ if (ret != DEVICE_ERROR_NONE) {
+ ERROR("ret == %s", device_error_to_string(ret));
+ }
+
+ if (is_charging) {
+ charging_text = _("IDS_SYSTEM_BATTERY_INFO_CHARGING");
+ } else {
+ charging_text = "";
+ }
+
+ ret = device_battery_get_percent(&percent);
+ if (ret != DEVICE_ERROR_NONE) {
+ ERROR("ret == %s", device_error_to_string(ret));
+ return;
+ }
+
+ if (percent == 100) {
+ ret = asprintf(&buffer, "%s %s", charging_text, _("IDS_SYSTEM_BATTERY_FULLY_CHARGED_STR"));
+ if (ret == 0) {
+ free(buffer);
+ ERROR("Buffer length == 0");
+ return;
+ } else if (ret < 0) {
+ ERROR("Buffer == NULL");
+ return;
+ }
+ } else {
+ ret = asprintf(&buffer, "%s %d %% %s", charging_text, percent, _("IDS_SYSTEM_BATTERY_INFO_BATTERY_STR"));
+ if (ret == 0) {
+ free(buffer);
+ ERROR("Buffer length == 0");
+ return;
+ } else if(ret < 0) {
+ ERROR("Buffer == NULL");
+ return;
+ }
+ }
+
+ if (!buffer) {
+ ERROR("buf == NULL");
+ return;
+ }
+
+ DEBUG("Text to say: %s", buffer);
+ tts_speak(buffer, EINA_FALSE);
+ free(buffer);
+}
+
+static void _signal_strength_sim_get(void)
+{
+ int i = 0;
+ int val = 0;
+ int ret = -1;
+ int sim_card_count = 0;
+ Eina_Strbuf *str_buf = NULL;
+ char *buffer = NULL;
+ int service_type = TAPI_NETWORK_SERVICE_TYPE_UNKNOWN;
+ char *service_type_text = NULL;
+
+ str_buf = eina_strbuf_new();
+
+ for (i = 0; tapi_handle[i]; ++i) {
+ ++sim_card_count;
+ }
+
+ for (i = 0; tapi_handle[i]; ++i) {
+ ret = tel_get_property_int(tapi_handle[i], TAPI_PROP_NETWORK_SIGNALSTRENGTH_LEVEL, &val);
+ if (ret != TAPI_API_SUCCESS) {
+ ERROR("Can not get %s", TAPI_PROP_NETWORK_SIGNALSTRENGTH_LEVEL);
+ val = 0;
+ }
+
+ if (sim_card_count > 1)
+ eina_strbuf_append_printf(str_buf, "%s %d %s %d; ", _("IDS_SYSTEM_SIGNAL_SIMCARD"), i + 1, _("IDS_SYSTEM_SIGNAL_STRENGTH"), val);
+ else
+ eina_strbuf_append_printf(str_buf, "%s %d; ", _("IDS_SYSTEM_SIGNAL_STRENGTH"), val);
+ DEBUG("sim: %d TAPI_PROP_NETWORK_SIGNALSTRENGTH_LEVEL %d", i, val);
+
+ ret = tel_get_property_int(tapi_handle[i], TAPI_PROP_NETWORK_SERVICE_TYPE, &service_type);
+ if (ret != TAPI_API_SUCCESS) {
+ ERROR("Can not get %s", TAPI_PROP_NETWORK_SERVICE_TYPE);
+ }
+
+ switch (service_type) {
+ case TAPI_NETWORK_SERVICE_TYPE_UNKNOWN:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_UNKNOWN");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_NO_SERVICE:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_NO_SERVICE");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_EMERGENCY:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_EMERGENCY");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_SEARCH:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_SEARCHING");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_2G:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_2G");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_2_5G:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_25G");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_2_5G_EDGE:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_EDGE");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_3G:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_3G");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_HSDPA:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_HSDPA");
+ break;
+
+ case TAPI_NETWORK_SERVICE_TYPE_LTE:
+ service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_LTE");
+ break;
+ }
+
+ eina_strbuf_append_printf(str_buf, " Service type: %s.", service_type_text);
+ }
+
+ buffer = eina_strbuf_string_steal(str_buf);
+
+ DEBUG("Text to say: %s", buffer);
+ tts_speak(buffer, EINA_FALSE);
+
+ eina_strbuf_string_free(str_buf);
+ free(buffer);
+}
+
+static void _signal_strength_wifi_get(void)
+{
+ int val = 0;
+ int ret = -1;
+ char *buffer = NULL;
+ char *wifi_text = NULL;
+ bool wifi_activated = false;
+ wifi_ap_h ap = NULL;
+
+ ret = wifi_is_activated(&wifi_activated);
+ if (ret != WIFI_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ return;
+ }
+
+ if (wifi_activated) {
+ ret = wifi_get_connected_ap(&ap);
+ if (ret != WIFI_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ return;
+ }
+
+ if (!ap) {
+ DEBUG("Text to say: %s %s", _("IDS_SYSTEM_NETWORK_TYPE_WIFI"), "Not connected");
+
+ ret = asprintf(&buffer, " %s, %s", _("IDS_SYSTEM_NETWORK_TYPE_WIFI"), "Not connected");
+ if (ret == 0) {
+ free(buffer);
+ ERROR("Buffer length == 0");
+ return;
+ } else if (ret < 0) {
+ ERROR("Buffer == NULL");
+ return;
+ }
+
+ tts_speak(buffer, EINA_FALSE);
+ free(buffer);
+ return;
+ }
+
+ ret = wifi_ap_get_rssi(ap, &val);
+ if (ret != WIFI_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ wifi_ap_destroy(ap);
+ return;
+ }
+
+ switch (val) {
+ case 0:
+ wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_NO_SIGNAL");
+ break;
+
+ case 1:
+ wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_POOR");
+ break;
+
+ case 2:
+ wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_WEAK");
+ break;
+
+ case 3:
+ wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_MEDIUM");
+ break;
+
+ case 4:
+ wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_GOOD");
+ break;
+ }
+
+ if (!asprintf(&buffer, " %s, %s", _("IDS_SYSTEM_NETWORK_TYPE_WIFI"), wifi_text)) {
+ ERROR("buffer length == 0");
+ wifi_ap_destroy(ap);
+ return;
+ }
+
+ DEBUG("Text to say: %s", buffer);
+ tts_speak(buffer, EINA_FALSE);
+ free(buffer);
+ wifi_ap_destroy(ap);
+ }
+}
+
+void device_signal_strenght_get(void)
+{
+ if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_SIGNAL_STRENGHT)) {
+ return;
+ }
+ _signal_strength_sim_get();
+ _signal_strength_wifi_get();
+}
+
+void device_missed_events_get(void)
+{
+ notification_list_h list = NULL;
+ notification_list_h elem = NULL;
+ notification_h noti = NULL;
+ int ret = -1;
+ char *noti_count_text = NULL;
+ int noti_count = 0;
+ int current_noti_count = 0;
+ char *buffer = NULL;
+
+ if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_MISSED_EVENTS)) {
+ return;
+ }
+
+ ret = notification_get_list(NOTIFICATION_TYPE_NONE, -1, &list);
+ if (ret != NOTIFICATION_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ return;
+ }
+
+ elem = notification_list_get_head(list);
+
+ while (elem) {
+ noti = notification_list_get_data(elem);
+ notification_get_text(noti, NOTIFICATION_TEXT_TYPE_EVENT_COUNT, &noti_count_text);
+
+ if (noti_count_text) {
+ current_noti_count = atoi(noti_count_text);
+ if (current_noti_count > 0) {
+ noti_count += current_noti_count;
+ } else {
+ noti_count++;
+ }
+ } else {
+ noti_count++;
+ }
+
+ elem = notification_list_get_next(elem);
+ }
+
+ if (noti_count == 0) {
+ tts_speak(_("IDS_SYSTEM_NOTIFICATIONS_UNREAD_0"), EINA_FALSE);
+ } else if (noti_count == 1) {
+ tts_speak(_("IDS_SYSTEM_NOTIFICATIONS_UNREAD_1"), EINA_FALSE);
+ } else {
+ DEBUG("%d %s", noti_count, _("IDS_SYSTEM_NOTIFICATIONS_UNREAD_MANY"));
+
+ if (asprintf(&buffer, "%d %s", noti_count, _("IDS_SYSTEM_NOTIFICATIONS_UNREAD_MANY"))) {
+ ERROR("buffer length equals 0");
+ }
+
+ tts_speak(buffer, EINA_FALSE);
+ free(buffer);
+ }
+
+ ret = notification_free_list(list);
+ if (ret != NOTIFICATION_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ }
+}
+
+void device_date_get(void)
+{
+ char buffer[DATE_TIME_BUFFER_SIZE];
+ int date_format = SETTING_DATE_FORMAT_DD_MM_YYYY;
+ int ret = -1;
+ time_t rawtime = 0;
+ struct tm *timeinfo = NULL;
+
+ if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_DATE)) {
+ return;
+ }
+
+ time(&rawtime);
+ timeinfo = localtime(&rawtime);
+ if (!timeinfo) {
+ ERROR("localtime returns NULL");
+ return;
+ }
+
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "%Y:%m:%d %H:%M:%S", timeinfo);
+
+ ret = vconf_get_int(VCONFKEY_SETAPPL_DATE_FORMAT_INT, &date_format);
+ if (ret != 0) {
+ ERROR("ret == %d", ret);
+ }
+
+ switch (date_format) {
+ case SETTING_DATE_FORMAT_DD_MM_YYYY:
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "%d %B %Y", timeinfo);
+ break;
+
+ case SETTING_DATE_FORMAT_MM_DD_YYYY:
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "%B %d %Y", timeinfo);
+ break;
+
+ case SETTING_DATE_FORMAT_YYYY_MM_DD:
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "%Y %B %d", timeinfo);
+ break;
+
+ case SETTING_DATE_FORMAT_YYYY_DD_MM:
+ strftime(buffer, DATE_TIME_BUFFER_SIZE, "%Y %d %B", timeinfo);
+ break;
+ }
+
+ DEBUG("Text to say: %s", buffer);
+ tts_speak(buffer, EINA_FALSE);
+}
+
+static bool bonded_device_count_cb(bt_device_info_s * device_info, void *user_data)
+{
+ int *device_count = (int *)user_data;
+
+ (*device_count)++;
+
+ return true;
+}
+
+static bool bonded_device_get_cb(bt_device_info_s * device_info, void *user_data)
+{
+ char **device_name = (char **)user_data;
+
+ if (asprintf(device_name, "%s connected", device_info->remote_name)) {
+ ERROR("buffer length == 0");
+ }
+
+ return false;
+}
+
+void device_bluetooth_get(void)
+{
+ char *buffer = NULL;
+ int device_count = 0;
+
+ if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_BLUETOOTH)) {
+ return;
+ }
+
+ bt_adapter_state_e adapter_state = BT_ADAPTER_DISABLED;
+ int ret = bt_adapter_get_state(&adapter_state);
+ if (ret != BT_ERROR_NONE) {
+ ERROR("ret == %d", ret);
+ return;
+ }
+
+ if (adapter_state == BT_ADAPTER_DISABLED) {
+ DEBUG("Text to say: %s", _("IDS_SYSTEM_BT_BLUETOOTH_OFF"));
+ tts_speak(_("IDS_SYSTEM_BT_BLUETOOTH_OFF"), EINA_FALSE);
+ return;
+ } else {
+ bt_adapter_foreach_bonded_device(bonded_device_count_cb, (void *)&device_count);
+
+ if (device_count == 0) {
+ if (!asprintf(&buffer, _("IDS_SYSTEM_BT_NO_DEVICES_CONNECTED"))) {
+ ERROR("buffer length == 0");
+ }
+ } else if (device_count == 1) {
+ bt_adapter_foreach_bonded_device(bonded_device_get_cb, &buffer);
+ } else {
+ if (asprintf(&buffer, "%d %s", device_count, _("IDS_SYSTEM_BT_DEVICES_CONNECTED_COUNT"))) {
+ ERROR("buffer length == 0");
+ }
+ }
+
+ DEBUG("Text to say: %s", buffer);
+ tts_speak(buffer, EINA_FALSE);
+ free(buffer);
+ }
+}
+
+#endif
diff --git a/src/screen_reader_tts.c b/src/screen_reader_tts.c
new file mode 100644
index 0000000..68c82ce
--- /dev/null
+++ b/src/screen_reader_tts.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+
+#include <Ecore.h>
+#include "screen_reader_tts.h"
+#include "screen_reader_vconf.h"
+#include "logger.h"
+
+// ---------------------------- DEBUG HELPERS ------------------------------
+
+#define FLUSH_LIMIT 1
+
+static int last_utt_id;
+static Eina_Bool pause_state = EINA_FALSE;
+static Eina_Bool flush_flag = EINA_FALSE;
+static Eina_Strbuf *txt_keep_buff = NULL;
+
+static void (*on_utterance_end) (void);
+
+static void _text_keep(const char *txt)
+{
+ if (!txt_keep_buff)
+ return;
+ if (eina_strbuf_length_get(txt_keep_buff) > 0)
+ eina_strbuf_append(txt_keep_buff, ", ");
+ eina_strbuf_append(txt_keep_buff, txt);
+}
+
+static char *get_tts_error(int r)
+{
+ switch (r) {
+ case TTS_ERROR_NONE:
+ {
+ return "no error";
+ }
+ case TTS_ERROR_INVALID_PARAMETER:
+ {
+ return "inv param";
+ }
+ case TTS_ERROR_OUT_OF_MEMORY:
+ {
+ return "out of memory";
+ }
+ case TTS_ERROR_OPERATION_FAILED:
+ {
+ return "oper failed";
+ }
+ case TTS_ERROR_INVALID_STATE:
+ {
+ return "inv state";
+ }
+ default:
+ {
+ return "uknown error";
+ }
+ }
+}
+
+static char *get_tts_state(tts_state_e r)
+{
+ switch (r) {
+ case TTS_STATE_CREATED:
+ {
+ return "created";
+ }
+ case TTS_STATE_READY:
+ {
+ return "ready";
+ }
+ case TTS_STATE_PLAYING:
+ {
+ return "playing";
+ }
+ case TTS_STATE_PAUSED:
+ {
+ return "pause";
+ }
+ default:
+ {
+ return "uknown state";
+ }
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+
+void set_utterance_cb(void (*uter_cb) (void))
+{
+ on_utterance_end = uter_cb;
+}
+
+bool get_supported_voices_cb(tts_h tts, const char *language, int voice_type, void *user_data)
+{
+ DEBUG("LANG: %s; TYPE: %d", language, voice_type);
+
+ Service_Data *sd = user_data;
+ Voice_Info *vi = calloc(1, sizeof(Voice_Info));
+ if (!vi) {
+ ERROR(MEMORY_ERROR);
+ return ECORE_CALLBACK_CANCEL;
+ }
+
+ if (asprintf(&vi->language, "%s", language) < 0) {
+ free(vi);
+ ERROR(MEMORY_ERROR);
+ return ECORE_CALLBACK_CANCEL;
+ }
+
+ vi->voice_type = voice_type;
+
+ sd->available_languages = eina_list_append(sd->available_languages, vi);
+
+ return ECORE_CALLBACK_RENEW;
+}
+
+static void __tts_test_utt_started_cb(tts_h tts, int utt_id, void *user_data)
+{
+ DEBUG("Utterance started : utt id(%d) \n", utt_id);
+ return;
+}
+
+static void __tts_test_utt_completed_cb(tts_h tts, int utt_id, void *user_data)
+{
+ DEBUG("Utterance completed : utt id(%d) \n", utt_id);
+ if (last_utt_id - utt_id > FLUSH_LIMIT)
+ flush_flag = EINA_TRUE;
+ else {
+ if (flush_flag)
+ flush_flag = EINA_FALSE;
+ }
+
+#ifndef SCREEN_READER_TV
+ if (last_utt_id == utt_id) {
+ DEBUG("LAST UTTERANCE");
+ pause_state = EINA_FALSE;
+ on_utterance_end();
+ }
+#endif
+
+ return;
+}
+
+bool tts_init(void *data)
+{
+ DEBUG("--------------------- TTS_init START ---------------------");
+ Service_Data *sd = data;
+
+ int r = tts_create(&sd->tts);
+ DEBUG("Create tts %d (%s)", r, get_tts_error(r));
+
+ r = tts_set_mode(sd->tts, TTS_MODE_SCREEN_READER);
+ DEBUG("Set tts mode SR %d (%s)", r, get_tts_error(r));
+
+ r = tts_prepare(sd->tts);
+ DEBUG("Prepare tts %d (%s)", r, get_tts_error(r));
+
+ tts_set_state_changed_cb(sd->tts, state_changed_cb, sd);
+
+ tts_set_utterance_started_cb(sd->tts, __tts_test_utt_started_cb, sd);
+ tts_set_utterance_completed_cb(sd->tts, __tts_test_utt_completed_cb, sd);
+
+ DEBUG("---------------------- TTS_init END ----------------------\n\n");
+ txt_keep_buff = eina_strbuf_new();
+ return true;
+}
+
+Eina_Bool tts_pause_get(void)
+{
+ DEBUG("PAUSE STATE: %d", pause_state);
+ return pause_state;
+}
+
+void tts_stop_set(void)
+{
+ Service_Data *sd = get_pointer_to_service_data_struct();
+ tts_stop(sd->tts);
+}
+
+Eina_Bool tts_pause_set(Eina_Bool pause_switch)
+{
+ Service_Data *sd = get_pointer_to_service_data_struct();
+ if (!sd)
+ return EINA_FALSE;
+
+ if (pause_switch) {
+ pause_state = EINA_TRUE;
+
+ if (tts_pause(sd->tts)) {
+ pause_state = EINA_FALSE;
+ return EINA_FALSE;
+ }
+ } else if (!pause_switch) {
+ pause_state = EINA_FALSE;
+
+ if (tts_play(sd->tts)) {
+ pause_state = EINA_TRUE;
+ return EINA_FALSE;
+ }
+ }
+ return EINA_TRUE;
+}
+
+void tts_speak(char *text_to_speak, Eina_Bool flush_switch)
+{
+ int ret = 0;
+ Service_Data *sd = get_pointer_to_service_data_struct();
+ int speak_id;
+
+ if (!sd)
+ return;
+ tts_state_e state;
+ tts_get_state(sd->tts, &state);
+
+ if (state != TTS_STATE_PLAYING && state != TTS_STATE_PAUSED && state != TTS_STATE_READY) {
+ if (text_to_speak)
+ _text_keep(text_to_speak);
+ return;
+ }
+
+ if (flush_flag || flush_switch) {
+ if (state == TTS_STATE_PLAYING || state == TTS_STATE_PAUSED) {
+ ret = tts_stop(sd->tts);
+ if (TTS_ERROR_NONE != ret) {
+ DEBUG("Fail to stop TTS: resultl(%d)", ret);
+ }
+ }
+ }
+
+ DEBUG("tts_speak\n");
+ DEBUG("text to say:%s\n", text_to_speak);
+ if (!text_to_speak)
+ return;
+ if (!text_to_speak[0])
+ return;
+
+ if ((ret = tts_add_text(sd->tts, text_to_speak, NULL, TTS_VOICE_TYPE_AUTO, TTS_SPEED_AUTO, &speak_id))) {
+ switch (ret) {
+ case TTS_ERROR_INVALID_PARAMETER:
+ DEBUG("FAILED tts_add_text: error: TTS_ERROR_INVALID_PARAMETER");
+ break;
+ case TTS_ERROR_INVALID_STATE:
+ DEBUG("FAILED tts_add_text: error: TTS_ERROR_INVALID_STATE, tts_state: %d", state);
+ break;
+ case TTS_ERROR_INVALID_VOICE:
+ DEBUG("FAILED tts_add_text: error: TTS_ERROR_INVALID_VOICE");
+ break;
+ case TTS_ERROR_OPERATION_FAILED:
+ DEBUG("FAILED tts_add_text: error: TTS_ERROR_OPERATION_FAILED");
+ break;
+ case TTS_ERROR_NOT_SUPPORTED:
+ DEBUG("FAILED tts_add_text: error: TTS_ERROR_NOT_SUPPORTED");
+ break;
+ default:
+ DEBUG("FAILED tts_add_text: error: not recognized");
+ }
+ return;
+ }
+
+ DEBUG("added id to:%d\n", speak_id);
+ last_utt_id = speak_id;
+ tts_play(sd->tts);
+}
+
+Eina_Bool update_supported_voices(void *data)
+{
+ DEBUG("START");
+ tts_state_e state;
+
+ Service_Data *sd = data;
+
+ int res = tts_get_state(sd->tts, &state);
+
+ if (res != TTS_ERROR_NONE) {
+ DEBUG("CANNOT RETRIVE STATE");
+ return EINA_FALSE;
+ }
+
+ if (state == TTS_STATE_READY) {
+ tts_foreach_supported_voices(sd->tts, get_supported_voices_cb, sd);
+ } else {
+ sd->update_language_list = EINA_TRUE;
+ }
+
+ DEBUG("END");
+ return EINA_TRUE;
+}
+
+void state_changed_cb(tts_h tts, tts_state_e previous, tts_state_e current, void *user_data)
+{
+ if (pause_state) {
+ DEBUG("TTS is currently paused. Resume to start reading");
+ return;
+ }
+
+ DEBUG("++++++++++++++++state_changed_cb\n++++++++++++++++++");
+ DEBUG("current state:%s and previous state:%s\n", get_tts_state(current), get_tts_state(previous));
+ Service_Data *sd = user_data;
+
+ if (TTS_STATE_CREATED == previous && TTS_STATE_READY == current) {
+
+ update_supported_voices(sd);
+
+ char *txt;
+
+ if (!txt_keep_buff)
+ return;
+ if (!eina_strbuf_length_get(txt_keep_buff))
+ return;
+
+ txt = eina_strbuf_string_steal(txt_keep_buff);
+ eina_strbuf_free(txt_keep_buff);
+ txt_keep_buff = NULL;
+ tts_speak(txt, EINA_FALSE);
+ free(txt);
+ }
+}
+
+void spi_stop(void *data)
+{
+ if (!data) {
+ ERROR("Invalid parameter");
+ return;
+ }
+
+ Service_Data *sd = data;
+ sd->update_language_list = false;
+ free((char *)sd->text_from_dbus);
+ free(sd->current_value);
+ sd->text_from_dbus = NULL;
+ sd->current_value = NULL;
+ tts_stop(sd->tts);
+}
diff --git a/src/screen_reader_vconf.c b/src/screen_reader_vconf.c
new file mode 100644
index 0000000..b2daef2
--- /dev/null
+++ b/src/screen_reader_vconf.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <Elementary.h>
+#include <vconf.h>
+#include <service_app.h>
+#include "screen_reader_vconf.h"
+#include "screen_reader_spi.h"
+#include "logger.h"
+
+#ifdef RUN_IPC_TEST_SUIT
+#include "test_suite/test_suite.h"
+#endif
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "SCREEN READER VCONF"
+
+keylist_t *keys = NULL;
+
+// ------------------------------ vconf callbacks----------------------
+
+void app_termination_cb(keynode_t * node, void *user_data)
+{
+ DEBUG("START");
+ DEBUG("Application terminate %d", !node->value.i);
+
+ Service_Data *service_data = user_data;
+ service_data->run_service = node->value.i;
+
+ if (service_data->run_service == 0) {
+ service_app_exit();
+ }
+
+ DEBUG("END");
+}
+
+void display_language_cb(keynode_t * node, void *user_data)
+{
+ DEBUG("START");
+ DEBUG("Trying to set LC_MESSAGES to: %s", node->value.s);
+
+ Service_Data *sd = user_data;
+ snprintf(sd->display_language, LANGUAGE_NAME_SIZE, "%s", node->value.s);
+ //to make gettext work
+ setenv("LC_MESSAGES", sd->display_language, 1);
+
+ DEBUG("END");
+}
+
+// --------------------------------------------------------------------
+
+int get_key_values(Service_Data * sd)
+{
+ DEBUG("START");
+ int to_ret = 0;
+
+ char *display_language = vconf_get_str("db/menu_widget/language");
+ if (display_language) {
+ snprintf(sd->display_language, LANGUAGE_NAME_SIZE, "%s", display_language);
+ //to make gettext work
+ setenv("LC_MESSAGES", sd->display_language, 1);
+ free(display_language);
+ } else
+ WARNING("Can't get db/menu_widget/language value");
+
+ DEBUG("SCREEN READER DATA SET TO: Display_Language: %s, Tracking signal: %s;", sd->display_language, sd->tracking_signal_name);
+
+ DEBUG("END");
+ return to_ret;
+}
+
+int _set_vconf_callback_and_print_message_on_error_and_return_error_code(const char *in_key, vconf_callback_fn cb, void *user_data)
+{
+ int ret = vconf_notify_key_changed(in_key, cb, user_data);
+ if (ret != 0)
+ DEBUG("Could not add notify callback to %s key", in_key);
+
+ return ret;
+}
+
+bool vconf_init(Service_Data * service_data)
+{
+ DEBUG("--------------------- VCONF_init START ---------------------");
+ int ret = 0;
+
+ if (vconf_set(keys)) {
+ DEBUG("nothing is written\n");
+ } else {
+ DEBUG("everything is written\n");
+ }
+
+ vconf_keylist_free(keys);
+ // ----------------------------------------------------------------------------------
+
+ ret = get_key_values(service_data);
+ if (ret != 0) {
+ DEBUG("Could not set data from vconf: %d", ret);
+ }
+
+ _set_vconf_callback_and_print_message_on_error_and_return_error_code("db/menu_widget/language", display_language_cb, service_data);
+
+ DEBUG("---------------------- VCONF_init END ----------------------\n\n");
+ return true;
+}
diff --git a/src/smart_notification.c b/src/smart_notification.c
new file mode 100644
index 0000000..e519797
--- /dev/null
+++ b/src/smart_notification.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <Ecore.h>
+#include <Ecore_Evas.h>
+#include <Evas.h>
+#include <atspi/atspi.h>
+#include <tone_player.h>
+#include "logger.h"
+#include "screen_reader_tts.h"
+#include "screen_reader_haptic.h"
+#include "smart_notification.h"
+
+#define RED "\x1B[31m"
+#define RESET "\033[0m"
+
+static Eina_Bool status = EINA_FALSE;
+
+static void _smart_notification_focus_chain_end(void);
+static void _smart_notification_realized_items(int start_idx, int end_idx);
+
+/**
+ * @brief Smart Notifications event handler
+ *
+ * @param nt Notification event type
+ * @param start_index int first visible items index smart_notification_realized_items
+ * @param end_index int last visible items index used for smart_notification_realized_items
+ */
+void smart_notification(Notification_Type nt, int start_index, int end_index)
+{
+ DEBUG("START");
+ if (!status)
+ return;
+
+ switch (nt) {
+ case FOCUS_CHAIN_END_NOTIFICATION_EVENT:
+ _smart_notification_focus_chain_end();
+ break;
+ case REALIZED_ITEMS_NOTIFICATION_EVENT:
+ _smart_notification_realized_items(start_index, end_index);
+ break;
+ default:
+ DEBUG("Gesture type %d not handled in switch", nt);
+ }
+}
+
+/**
+ * @brief Used for getting first and last index of visible items
+ *
+ * @param scrollable_object AtspiAccessible object on which scroll was triggered
+ * @param start_index int first visible items index smart_notification_realized_items
+ * @param end_index int last visible items index used for smart_notification_realized_items
+ */
+void get_realized_items_count(AtspiAccessible * scrollable_object, int *start_idx, int *end_idx)
+{
+ DEBUG("START");
+ int count_child, jdx;
+ AtspiAccessible *child_iter;
+ AtspiStateType state = ATSPI_STATE_SHOWING;
+
+ if (!scrollable_object) {
+ DEBUG("No scrollable object");
+ return;
+ }
+
+ count_child = atspi_accessible_get_child_count(scrollable_object, NULL);
+
+ for (jdx = 0; jdx < count_child; jdx++) {
+ child_iter = atspi_accessible_get_child_at_index(scrollable_object, jdx, NULL);
+ if (!child_iter)
+ continue;
+
+ AtspiStateSet *state_set = atspi_accessible_get_state_set(child_iter);
+
+ gboolean is_visible = atspi_state_set_contains(state_set, state);
+ if (is_visible) {
+ *start_idx = jdx;
+ DEBUG("Item with index %d is visible", jdx);
+ } else
+ DEBUG("Item with index %d is NOT visible", jdx);
+ }
+ *end_idx = *start_idx + 8;
+}
+
+/**
+ * @brief Scroll-start/Scroll-end event callback
+ *
+ * @param event AtspiEvent
+ * @param user_data UNUSED
+ */
+
+static void _scroll_event_cb(AtspiEvent * event, gpointer user_data)
+{
+ if (!status)
+ return;
+
+ int start_index, end_index;
+ start_index = 0;
+ end_index = 0;
+
+ gchar *role_name = atspi_accessible_get_role_name(event->source, NULL);
+ fprintf(stderr, "Event: %s: %d, obj: %p: role: %s\n", event->type, event->detail1, event->source, role_name);
+ g_free(role_name);
+
+ if (!strcmp(event->type, "object:scroll-start")) {
+ DEBUG("Scrolling started");
+ tts_speak(_("IDS_SCROLLING_STARTED"), EINA_TRUE);
+ } else if (!strcmp(event->type, "object:scroll-end")) {
+ DEBUG("Scrolling finished");
+ tts_speak(_("IDS_SCROLLING_FINISHED"), EINA_FALSE);
+ get_realized_items_count((AtspiAccessible *) event->source, &start_index, &end_index);
+ _smart_notification_realized_items(start_index, end_index);
+ }
+}
+
+/**
+ * @brief Initializer for smart notifications
+ *
+ *
+ */
+void smart_notification_init(void)
+{
+ DEBUG("Smart Notification init!");
+
+ AtspiEventListener *listener;
+
+ listener = atspi_event_listener_new(_scroll_event_cb, NULL, NULL);
+ atspi_event_listener_register(listener, "object:scroll-start", NULL);
+ atspi_event_listener_register(listener, "object:scroll-end", NULL);
+
+ haptic_module_init();
+
+ status = EINA_TRUE;
+}
+
+/**
+ * @brief Smart notifications shutdown
+ *
+ */
+void smart_notification_shutdown(void)
+{
+ status = EINA_FALSE;
+}
+
+/**
+ * @brief Smart notifications focus chain event handler
+ *
+ */
+static void _smart_notification_focus_chain_end(void)
+{
+ if (!status)
+ return;
+
+ DEBUG(RED "Smart notification - FOCUS CHAIN END" RESET);
+
+ tone_player_stop(0);
+ tone_player_start(TONE_TYPE_SUP_CONFIRM, SOUND_TYPE_MEDIA, 200, NULL);
+}
+
+/**
+ * @brief Smart notifications realized items event handler
+ *
+ */
+static void _smart_notification_realized_items(int start_idx, int end_idx)
+{
+ if (!status)
+ return;
+
+ if (start_idx == end_idx)
+ return;
+
+ DEBUG(RED "Smart notification - Visible items notification" RESET);
+
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), _("IDS_REACHED_ITEMS_NOTIFICATION"), start_idx, end_idx);
+
+ tts_speak(buf, EINA_FALSE);
+}
diff --git a/src/window_tracker.c b/src/window_tracker.c
new file mode 100644
index 0000000..ebdb9be
--- /dev/null
+++ b/src/window_tracker.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include "window_tracker.h"
+#include "logger.h"
+#include <Ecore_X.h>
+#include <Ecore_X_Atoms.h>
+
+static Window_Tracker_Cb user_cb;
+static void *user_data;
+static AtspiEventListener *listener;
+static AtspiAccessible *last_active_win;
+
+static void _on_atspi_window_cb(const AtspiEvent * event)
+{
+ DEBUG("Event: %s: %s", event->type, atspi_accessible_get_name(event->source, NULL));
+
+ if (!strcmp(event->type, "window:activate") && last_active_win != event->source) //if we got activate 2 times
+ {
+
+ if (user_cb)
+ user_cb(user_data, event->source);
+ last_active_win = event->source;
+ }
+}
+
+static AtspiAccessible *_get_active_win(void)
+{
+ DEBUG("START");
+ int i, j, desktop_children_count, app_children_count;
+ last_active_win = NULL;
+ AtspiAccessible *desktop = atspi_get_desktop(0);
+ if (!desktop) {
+ ERROR("DESKTOP NOT FOUND");
+ return NULL;
+ }
+
+ Ecore_X_Window focus_window = ecore_x_window_focus_get();
+ unsigned int active_window_pid = 0;
+ if (focus_window) {
+ //invoking atspi_accessible_get_child_count for non active apps results in very long screen-reader startup
+ //not active apps have low priority and dbus calls take a lot of time (a few hundred ms per call)
+ //Hence we first try to determine accessible window using pid of currently focused window
+ if (!ecore_x_window_prop_card32_get(focus_window, ECORE_X_ATOM_NET_WM_PID, &active_window_pid, 1))
+ active_window_pid = 0;
+ if (active_window_pid)
+ DEBUG("First we will try filter apps by PID: %i", active_window_pid);
+ }
+ desktop_children_count = atspi_accessible_get_child_count(desktop, NULL);
+ for (i = 0; i < desktop_children_count; i++) {
+ AtspiAccessible *app = atspi_accessible_get_child_at_index(desktop, i, NULL);
+
+ if (active_window_pid == 0 || active_window_pid == atspi_accessible_get_process_id(app, NULL))
+ app_children_count = atspi_accessible_get_child_count(app, NULL);
+ else
+ app_children_count = 0;
+ for (j = 0; j < app_children_count; j++) {
+ AtspiAccessible *win = atspi_accessible_get_child_at_index(app, j, NULL);
+ AtspiStateSet *states = atspi_accessible_get_state_set(win);
+ AtspiRole role = atspi_accessible_get_role(win, NULL);
+ if ((atspi_state_set_contains(states, ATSPI_STATE_ACTIVE)) && (role == ATSPI_ROLE_WINDOW))
+ last_active_win = win;
+
+ g_object_unref(states);
+ g_object_unref(win);
+
+ if (last_active_win)
+ break;
+ }
+ g_object_unref(app);
+ if (active_window_pid > 0 && (i == desktop_children_count - 1)) {
+ // we are in last iteration and we should fall back to normal iteration over child windows
+ // without filtering by focus windows PID
+ i = -1;
+ active_window_pid = 0;
+ }
+ if (last_active_win)
+ break;
+ }
+ g_object_unref(desktop);
+ DEBUG("END last_active_win: %p", last_active_win);
+ return last_active_win;
+}
+
+void window_tracker_init(void)
+{
+ DEBUG("START");
+ listener = atspi_event_listener_new_simple(_on_atspi_window_cb, NULL);
+ atspi_event_listener_register(listener, "window:activate", NULL);
+}
+
+void window_tracker_shutdown(void)
+{
+ DEBUG("START");
+ atspi_event_listener_deregister(listener, "window:activate", NULL);
+ g_object_unref(listener);
+ listener = NULL;
+ user_cb = NULL;
+ user_data = NULL;
+ last_active_win = NULL;
+}
+
+void window_tracker_register(Window_Tracker_Cb cb, void *data)
+{
+ DEBUG("START");
+ user_cb = cb;
+ user_data = data;
+}
+
+void window_tracker_active_window_request(void)
+{
+ DEBUG("START");
+ _get_active_win();
+ if (user_cb)
+ user_cb(user_data, last_active_win);
+}