Init screen-reader from tizen.org
authorPatryk Kaczmarek <patryk.k@samsung.com>
Wed, 22 Apr 2015 15:44:09 +0000 (17:44 +0200)
committerPatryk Kaczmarek <patryk.k@samsung.com>
Wed, 22 Apr 2015 17:39:52 +0000 (19:39 +0200)
Change-Id: I299b41e88eb61ffcc8c64ab628bfcd7d042335e6
Signed-off-by: Patryk Kaczmarek <patryk.k@samsung.com>
45 files changed:
CMakeLists.sub [new file with mode: 0755]
CMakeLists.txt [new file with mode: 0755]
include/app_tracker.h [new file with mode: 0644]
include/flat_navi.h [new file with mode: 0644]
include/keyboard_tracker.h [new file with mode: 0644]
include/logger.h [new file with mode: 0644]
include/navigator.h [new file with mode: 0644]
include/object_cache.h [new file with mode: 0644]
include/pivot_chooser.h [new file with mode: 0644]
include/position_sort.h [new file with mode: 0644]
include/screen_reader.h [new file with mode: 0644]
include/screen_reader_haptic.h [new file with mode: 0644]
include/screen_reader_spi.h [new file with mode: 0644]
include/screen_reader_system.h [new file with mode: 0644]
include/screen_reader_tts.h [new file with mode: 0644]
include/screen_reader_vconf.h [new file with mode: 0644]
include/smart_notification.h [new file with mode: 0644]
include/structural_navi.h [new file with mode: 0644]
include/window_tracker.h [new file with mode: 0644]
org.tizen.screen-reader.manifest [new file with mode: 0644]
org.tizen.screen-reader.xml [new file with mode: 0755]
packaging/org.tizen.screen-reader.spec [new file with mode: 0755]
res/icons/screen-reader.png [new file with mode: 0755]
src/app_tracker.c [new file with mode: 0644]
src/flat_navi.c [new file with mode: 0644]
src/keyboard_tracker.c [new file with mode: 0644]
src/main.c [new file with mode: 0755]
src/navigator.c [new file with mode: 0644]
src/object_cache.c [new file with mode: 0644]
src/pivot_chooser.c [new file with mode: 0644]
src/position_sort.c [new file with mode: 0644]
src/screen_reader.c [new file with mode: 0755]
src/screen_reader_haptic.c [new file with mode: 0644]
src/screen_reader_spi.c [new file with mode: 0644]
src/screen_reader_system.c [new file with mode: 0644]
src/screen_reader_tts.c [new file with mode: 0644]
src/screen_reader_vconf.c [new file with mode: 0644]
src/smart_notification.c [new file with mode: 0644]
src/structural_navi.c [new file with mode: 0644]
src/window_tracker.c [new file with mode: 0644]
tests/CMakeLists.sub [new file with mode: 0755]
tests/CMakeLists.txt [new file with mode: 0644]
tests/atspi/atspi.c [new file with mode: 0644]
tests/atspi/atspi.h [new file with mode: 0644]
tests/screen_reader_test_suite.c [new file with mode: 0644]

diff --git a/CMakeLists.sub b/CMakeLists.sub
new file mode 100755 (executable)
index 0000000..7c5d679
--- /dev/null
@@ -0,0 +1,26 @@
+## PROJECT NAME
+PROJECT(screen-reader C)
+
+## INCLUDES
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
+
+## DEFINITIONS
+ADD_DEFINITIONS("")
+
+## LIBRARY PATH
+SET(SLP_LD_PATH_FLAGS "")
+
+## LIBRARY
+SET(SLP_LD_FLAGS "")
+
+## DEBUG
+SET(SLP_DEBUG_FLAGS "-g")
+
+## OPTIMIZATION
+SET(SLP_OPT_FLAGS "-O0")
+
+## COMPILER FLAGS
+SET(SLP_COMPILER_FLAGS "-Wall -Wunused")
+
+## LINKER FLAGS
+SET(SLP_LINKER_FLAGS "")
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..3c37e76
--- /dev/null
@@ -0,0 +1,45 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+ENABLE_TESTING()
+
+# FIND ALL SOURCE IN A SOURCE DIRECTORY
+AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src SRCS)
+
+#INCLUDE FILE 
+INCLUDE(CMakeLists.sub)
+SET(RESOURCE_DIR "${CMAKE_SOURCE_DIR}/res")
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(pkgs REQUIRED
+       capi-appfw-application
+       bundle
+       atspi-2
+       gobject-2.0
+       dlog
+       vconf
+       tts
+       capi-system-device
+)
+
+FOREACH(flag ${pkgs_CFLAGS})
+       SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(CMAKE_C_FLAGS_DUMP "${CMAKE_C_FLAGS}")
+SET(CMAKE_CXX_FLAGS_DUMP "${CMAKE_CXX_FLAGS}")
+SET(CMAKE_C_FLAGS "${SLP_DEBUG_FLAGS} ${SLP_OPT_FLAGS} ${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} ${SLP_COMPILER_FLAGS}")
+SET(CMAKE_CXX_FLAGS "${SLP_DEBUG_FLAGS} ${SLP_OPT_FLAGS} ${CMAKE_CXX_FLAGS} ${EXTRA_CFLAGS} ${SLP_COMPILER_FLAGS}")
+
+MESSAGE(${CMAKE_C_FLAGS})
+
+ADD_EXECUTABLE(${PROJECT_NAME} ${SRCS})
+
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${pkgs_LDFLAGS} ${SLP_LD_PATH_FLAGS} ${SLP_LD_FLAGS} ${SLP_LINKER_FLAGS})
+
+# Install 
+INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin)
+INSTALL(DIRECTORY ${RESOURCE_DIR}/icons DESTINATION res) 
+# Install Manifest File
+INSTALL(FILES org.tizen.screen-reader.xml DESTINATION /usr/share/packages)
+ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/tests)
+ADD_TEST(NAME screen_reader_test_suite COMMAND ${CMAKE_SOURCE_DIR}/tests/screen_reader_test_suite)
+# END OF A FILE
diff --git a/include/app_tracker.h b/include/app_tracker.h
new file mode 100644 (file)
index 0000000..f813ab9
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef APP_TRACKER_H_
+#define APP_TRACKER_H_
+
+#include <atspi/atspi.h>
+
+/**
+ * @brief Application events
+ *
+ * @APP_TRACKER_EVENT_VIEW_CHANGE_STARTED - occuring when any of application's
+ * UI parts has change its visuals, including moving, resizing widgets, scrolling etc.
+ *
+ * @APP_TRACKER_EVENT_VIEW_CHANGED occuring when any of application's
+ * UI parts has changed its visuals and a prefined time period elapsed 
+ *
+ */
+typedef enum {
+       APP_TRACKER_EVENT_VIEW_CHANGE_STARTED,
+       APP_TRACKER_EVENT_VIEW_CHANGED,
+       APP_TRACKER_EVENT_COUNT
+} AppTrackerEventType;
+
+/**
+ * @brief Callback
+ */
+typedef void (*AppTrackerEventCB)(AppTrackerEventType type, void *user_data);
+
+/**
+ * @brief Register listener on given event type.
+ *
+ * @param obj AtspiAccessible application object type
+ * @param event type of event to register on
+ * @param cb callback type
+ * @param user_data pointer passed to cb function.
+ */
+void app_tracker_callback_register(AtspiAccessible *app, AppTrackerEventType event, AppTrackerEventCB cb, void *user_data);
+
+/**
+ * @brief Unregister listener on given event type.
+ *
+ * @param obj AtspiAccessible application object type
+ * @param event type of event to register on
+ * @param cb callback type
+ * @param user_data pointer passed to cb function.
+ */
+void app_tracker_callback_unregister(AtspiAccessible *app, AppTrackerEventType event, AppTrackerEventCB cb, void *user_data);
+
+/**
+ * @brief Initialize app tracker module.
+ *
+ * @return initi count.
+ *
+ * @nore function atspi_init will be called.
+ */
+int app_tracker_init(void);
+
+/**
+ * @brief Shutdown app tracker module
+ */
+void app_tracker_shutdown(void);
+
+#endif /* end of include guard: APP_TRACKER_H_ */
diff --git a/include/flat_navi.h b/include/flat_navi.h
new file mode 100644 (file)
index 0000000..2b0fc70
--- /dev/null
@@ -0,0 +1,148 @@
+#ifndef FLAT_NAVI_H_
+#define FLAT_NAVI_H_
+
+#include <atspi/atspi.h>
+
+typedef struct _FlatNaviContext FlatNaviContext;
+
+/**
+ * @brief Creates new FlatNaviContext.
+ *
+ * FlatNaviContext organizes UI elements in a way that is similar to
+ * reading orded (from left to right) and allow it traversing.
+ *
+ * @param root AtspiAccessible root object on which descendants FlatNaviContext
+ * will be builded.
+ *
+ * @return New FlatNaviContext. Should be free with flat_navi_context_free.
+ *
+ * @note Context will use positions obtain from object_cache. It is up
+ * to developer to guarantee that cache is up-to-date.
+ * @note Context will use position_sort to sort elements into lines.
+ * @note Context will use heuristics to those choose elements of UI
+ * that will be segmented into lines. Plese refer to source code for further details.
+ * @note default current element is first object in first line.
+ *
+ */
+FlatNaviContext *flat_navi_context_create(AtspiAccessible *root);
+
+/**
+ * @brief Frees FlatNaviContext
+ * @param ctx FlatNaviContext
+ */
+void flat_navi_context_free(FlatNaviContext *ctx);
+
+/**
+ * Advances to next element in natural reading order and returns
+ * new current element.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ *
+ * @note If current element is last in line function returns NULL
+ */
+AtspiAccessible *flat_navi_context_next(FlatNaviContext *ctx);
+
+/**
+ * Advances to previous element in natural reading order and returns
+ * new current element.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ *
+ * @note If current element is first in line function returns NULL
+ */
+AtspiAccessible *flat_navi_context_prev(FlatNaviContext *ctx);
+
+/**
+ * Advances to last element in current line.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ */
+AtspiAccessible *flat_navi_context_last(FlatNaviContext *ctx);
+
+/**
+ * Advances to first element in current line.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ */
+AtspiAccessible *flat_navi_context_first(FlatNaviContext *ctx);
+
+/**
+ * Get current element
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ */
+AtspiAccessible *flat_navi_context_current_get(FlatNaviContext *ctx);
+
+/**
+ * Set current element.
+ *
+ * @note There might be situation when target object is not in FlatNaviContext.
+ * (eg. was filtered by internal heuristic) in this situations function returns
+ * EINA_FALSE.
+ *
+ * @param ctx FlatNaviContext
+ * @param target new current FlatNaviContext object
+ *
+ * @return EINA_TRUE is operation successed, EINA_FALSE otherwise
+ */
+gboolean flat_navi_context_current_set(FlatNaviContext *ctx, AtspiAccessible *target);
+
+/**
+ * Advances to previous line in natural reading order and returns
+ * new current element.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ *
+ * @note If current line is first one, function returns NULL
+ * @note current element will be first of line.
+ */
+AtspiAccessible *flat_navi_context_line_prev(FlatNaviContext *ctx);
+
+/**
+ * Advances to next line in natural reading order and returns
+ * new current element.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ *
+ * @note If current line is last one, function returns NULL
+ * @note current element will be first of line.
+ */
+AtspiAccessible *flat_navi_context_line_next(FlatNaviContext *ctx);
+
+/**
+ * Advances to first line.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ *
+ * @note current element will be first of line.
+ */
+AtspiAccessible *flat_navi_context_line_first(FlatNaviContext *ctx);
+
+/**
+ * Advances to last line.
+ *
+ * @param ctx FlatNaviContext
+ *
+ * @return AtspiAccessible* pointer to current object
+ *
+ * @note current element will be first of line.
+ */
+AtspiAccessible *flat_navi_context_line_last(FlatNaviContext *ctx);
+
+#endif /* end of include guard: FLAT_NAVI_H_ */
diff --git a/include/keyboard_tracker.h b/include/keyboard_tracker.h
new file mode 100644 (file)
index 0000000..4adb8eb
--- /dev/null
@@ -0,0 +1,17 @@
+/**
+ * @brief Supported Keys
+ */
+enum _Key {
+     KEY_LEFT,
+     KEY_RIGHT,
+     KEY_UP,
+     KEY_DOWN,
+     KEY_COUNT
+};
+
+typedef enum _Key Key;
+
+typedef void (*Keyboard_Tracker_Cb) (void *data, Key k);
+void keyboard_tracker_init(void);
+void keyboard_tracker_register(Keyboard_Tracker_Cb cb, void *data);
+void keyboard_tracker_shutdown(void);
diff --git a/include/logger.h b/include/logger.h
new file mode 100644 (file)
index 0000000..4886f19
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 _LOGGER_H_
+
+#include <dlog.h>
+
+#ifdef  LOG_TAG
+#undef  LOG_TAG
+#endif
+#define LOG_TAG "SmartNavigator"
+
+#ifndef ERROR
+#define ERROR(fmt, arg...) LOGE("[%s:%d]:" fmt, __FILE__, __LINE__, ##arg)
+#endif
+
+#ifndef DEBUG
+#define DEBUG(fmt, arg...) LOGD("[%s:%d]:" fmt, __FILE__, __LINE__, ##arg)
+#endif
+
+#ifndef INFO
+#define INFO(fmt, arg...) LOGI("[%s:%d]:" fmt, __FILE__, __LINE__, ##arg)
+#endif
+
+
+#endif /* _LOGGER_H_ */
diff --git a/include/navigator.h b/include/navigator.h
new file mode 100644 (file)
index 0000000..a61932a
--- /dev/null
@@ -0,0 +1,4 @@
+#include <atspi/atspi.h>
+
+void navigator_init(void);
+void navigator_shutdown(void);
diff --git a/include/object_cache.h b/include/object_cache.h
new file mode 100644 (file)
index 0000000..cd97d67
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef SMART_NAVI_OBJECT_CACHE_H_H
+#define SMART_NAVI_OBJECT_CACHE_H_H
+
+#include <atspi/atspi.h>
+
+typedef struct {
+    // Boundaries of object taken from atspi_component_get_extents function.
+    // NULL if object do not implemetn AtspiComponent interface
+    AtspiRect *bounds;
+} ObjectCache;
+
+typedef void (*ObjectCacheReadyCb)(void *data);
+
+/**
+ * @brief Recursivly build ObjectCache structures for root Accessible
+ * object from and its descendants. (Children's children also, etc.)
+ *
+ * @param root starting object.
+ *
+ * @remarks This function may block main-loop for significant ammount of time.
+ *          Flushes all previously cached items.
+ */
+void object_cache_build(AtspiAccessible *root);
+
+/**
+ * @brief Recursivly build ObjectCache structures for ever Accessible
+ * object from root and its all descendants.
+ *
+ * @param root starting object.
+ * @param bulk_size Ammount of AtspiAccessible that will be cached on every
+ * ecore_main_loop entering into 'idle' state.
+ * @param cb function to be called after creating cache is done.
+ * @param user_data passed to cb
+ *
+ * @remarks Flushes all previously cached items.
+ */
+void object_cache_build_async(AtspiAccessible *root, int bulk_size, ObjectCacheReadyCb cb, void *user_data);
+
+/**
+ * @brief Gets and ObjectCache item from accessible object
+ *
+ * @param obj AtspiAccessible
+ *
+ * @remarks If obj is not in cache function will block main loop and build ObjectCache
+ * struct.
+ */
+const ObjectCache *object_cache_get(AtspiAccessible *obj);
+
+/**
+ * @brief Shoutdown cache.
+ *
+ * Function will free all gathered caches.
+ *
+ * @remarks Use it after You have used any of above object_cache_* functions.
+ */
+void object_cache_shutdown(void);
+
+#endif /* end of include guard: OBJECT_CACHE_H_H */
diff --git a/include/pivot_chooser.h b/include/pivot_chooser.h
new file mode 100644 (file)
index 0000000..16af72e
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef SMART_NAVI_PIVOT_CHOOSER_H_
+#define SMART_NAVI_PIVOT_CHOOSER_H_
+
+#include <atspi/atspi.h>
+
+/**
+ * @brief Heuristic choosing first element of the UI.
+ *
+ * @param win Accessibility search tree object root.
+ *
+ * @return Highlight candidate
+ */
+AtspiAccessible *pivot_chooser_pivot_get(AtspiAccessible *win);
+
+#endif /* end of include guard: PIVOT_CHOOSER_H_ */
diff --git a/include/position_sort.h b/include/position_sort.h
new file mode 100644 (file)
index 0000000..7954d46
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef SMART_NAVI_POSITION_SORT_H_
+#define SMART_NAVI_POSITION_SORT_H_
+
+#include <atspi/atspi.h>
+#include <glib.h>
+
+
+/**
+ * @brief Sort objects by position they appear on screen
+ *
+ *  example:
+ *
+ *  ------------------------------
+ *  |                   _______  |
+ *  |  _______          |     |  |
+ *  |  |     |  ______  |     |  |
+ *  |  |  A  |  |    |  |     |  |
+ *  |  |_____|  | B  |  |  C  |  |
+ *  |           |    |  |     |  |
+ *  |           |____|  |     |  |
+ *  |   _____           |     |  |             line 0: A, B, C
+ *  |   |   |           |_____|  |             line 1: D
+ *  |   | D |                    |    ====>    line 2: F
+ *  |   |___| _____              |             line 3: G
+ *  |         |   |              |
+ *  |         | F | _____        |
+ *  |         |___| |   |        |
+ *  |               | G |        |
+ *  |               |___|        |
+ *  |                            |
+ *  ------------------------------
+ *
+ * @ret list List of lists
+ */
+GList *position_sort(const GList *obj);
+
+#endif /* end of include guard: POSITION_SORT_H_ */
diff --git a/include/screen_reader.h b/include/screen_reader.h
new file mode 100644 (file)
index 0000000..9a7ef4c
--- /dev/null
@@ -0,0 +1,80 @@
+#ifndef __screen_reader_H__
+#define __screen_reader_H__
+
+#include <atspi/atspi.h>
+#include <tts.h>
+
+#define LAN_NAME 6
+
+#define MAX_REACHED ", maximum value reached"
+#define MIN_REACHED ", minimum value reached"
+#define MAX_POS_REACHED ", end of text reached"
+#define MIN_POS_REACHED ", begin of text reached"
+
+#define FOCUS_SIG "focused"
+
+#define EDITING_STARTED "Editing"
+
+#define FOCUS_CHANGED_SIG "object:state-changed:focused"
+#define VALUE_CHANGED_SIG "object:property-change:accessible-value"
+#define CARET_MOVED_SIG "object:text-caret-moved"
+
+typedef struct
+{
+       char *language;
+       int voice_type;
+} Voice_Info;
+
+
+typedef enum
+{
+       read_as_xml,
+       read_as_plain,
+       dont_read
+} Wrong_Validation_Reacction;
+
+typedef struct _Service_Data
+{
+       //Set by vconf
+       int information_level;
+       bool run_service;
+       char language[LAN_NAME];
+       int voice_type;
+       int reading_speed;
+       char *tracking_signal_name;
+
+       //Set by tts
+       tts_h tts;
+       GList *available_languages;
+
+       char *text_to_say_info;
+       char *current_value;
+       char *current_char;
+
+       //Actions to do when tts state is 'ready'
+       int _dbus_txt_readed;
+       bool say_text;
+       bool update_language_list;
+
+       //Set by spi
+       AtspiEventListener *spi_listener;
+
+       AtspiAccessible  *currently_focused;
+       AtspiAccessible  *mouse_down_widget;
+       AtspiAccessible  *clicked_widget;
+
+       //Set by dbus
+       char **last_tokens;
+       char *available_requests;
+       char **available_apps;
+
+       const char *text_from_dbus;
+} Service_Data;
+
+Service_Data *get_pointer_to_service_data_struct();
+
+int screen_reader_create_service(void *data);
+
+int screen_reader_terminate_service(void *data);
+
+#endif
diff --git a/include/screen_reader_haptic.h b/include/screen_reader_haptic.h
new file mode 100644 (file)
index 0000000..d3f9beb
--- /dev/null
@@ -0,0 +1,4 @@
+void haptic_module_init(void);
+void haptic_module_disconnect(void);
+void haptic_vibrate_start(void);
+void haptic_vibrate_stop(void);
diff --git a/include/screen_reader_spi.h b/include/screen_reader_spi.h
new file mode 100644 (file)
index 0000000..3047284
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * screen_reader_spi.h
+ *
+ *  Created on: Feb 20, 2014
+ *      Author: m.skorupinsk
+ */
+
+#ifndef SCREEN_READER_SPI_H_
+#define SCREEN_READER_SPI_H_
+
+#include <atspi/atspi.h>
+#include "screen_reader.h"
+
+void spi_init(Service_Data *sd);
+void spi_event_listener_cb(AtspiEvent *event, void *user_data);
+char *spi_event_get_text_to_read(AtspiEvent *event, void *user_data);
+
+#endif /* SCREEN_READER_SPI_H_ */
diff --git a/include/screen_reader_system.h b/include/screen_reader_system.h
new file mode 100644 (file)
index 0000000..bafe015
--- /dev/null
@@ -0,0 +1,2 @@
+void system_notifications_init(void);
+void system_notifications_shutdown(void);
\ No newline at end of file
diff --git a/include/screen_reader_tts.h b/include/screen_reader_tts.h
new file mode 100644 (file)
index 0000000..7344060
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * screen_reader_tts.h
+ *
+ *  Created on: Feb 19, 2014
+ *      Author: m.skorupinsk
+ */
+
+#ifndef SCREEN_READER_TTS_H_
+#define SCREEN_READER_TTS_H_
+
+#include <tts.h>
+#include "screen_reader.h"
+
+extern tts_h h_tts;
+extern char* language;
+extern int voice;
+extern int speed;
+
+bool tts_init(void *data);
+void state_changed_cb(tts_h tts, tts_state_e previous, tts_state_e current, void* user_data);
+gboolean tts_speak(char *text_to_speak, gboolean flush_switch);
+void spi_stop(void *data);
+
+#endif /* SCREEN_READER_TTS_H_ */
diff --git a/include/screen_reader_vconf.h b/include/screen_reader_vconf.h
new file mode 100644 (file)
index 0000000..831dd3a
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * screen_reader_vconf.h
+ *
+ *  Created on: Feb 19, 2014
+ *      Author: m.skorupinsk
+ */
+
+#ifndef SCREEN_READER_VCONF_H_
+#define SCREEN_READER_VCONF_H_
+
+#include "screen_reader.h"
+#include "logger.h"
+
+bool vconf_init(Service_Data *service_data);
+
+#endif /* SCREEN_READER_VCONF_H_ */
diff --git a/include/smart_notification.h b/include/smart_notification.h
new file mode 100644 (file)
index 0000000..b98ecc6
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef SMART_NOTIFICATION_H_
+#define SMART_NOTIFICATION_H_
+
+/**
+ * @brief Type of notification events.
+ *
+ * @FOCUS_CHAIN_END_NOTIFICATION_EVENT emitted when
+ * currnetly focued widget is the last one
+ * in focus chain for application current view.
+ *
+ * @REALIZED_ITEMS_NOTIFICATION_EVENT
+ */
+enum _Notification_Type {
+     FOCUS_CHAIN_END_NOTIFICATION_EVENT,
+     REALIZED_ITEMS_NOTIFICATION_EVENT,
+};
+
+typedef enum _Notification_Type Notification_Type;
+
+/**
+ * @brief Initializes notification submodule.
+ *
+ * @description
+ * Notification submodule is resposnisle for providing
+ * voice output to end user when on of following events
+ * occur:
+ *
+ * 1. User starts scrolling active application view.
+ * 2. User finished scrolling (and all scrolling related
+ *    animations has stopped) active application view.
+ * 3. When user has navigated to the last widget in focus
+ *    chain.
+ * 4. After scrolling lists, some of item becomes visible
+ *
+ * @nore
+ * 1 and 2 are handled internally by smart navigation submodule.
+ * about events 3 and 4 submodule has to be informed by the developer
+ * by using smart_notification API.
+ */
+void smart_notification_init(void);
+
+/**
+ * @brief Notifies smart_notification about UI event.
+ *
+ * @param nt Type of the occured event.
+ * @param start_index Item of the first item that becomes visible
+ * after scrolling list widget.
+ * @param end_index index of the last item that becomes visible
+ * after scrolling list widget.
+ *
+ * @note start_index and end_index are interpreted only
+ * when nt parameter is REALIZED_ITEMS_NOTIFICATION_EVENT
+ * @note initializes haptic module.
+ */
+void smart_notification(Notification_Type nt, int start_index, int end_index);
+
+/**
+ * @brief Shutdowns notification subsystem.
+ */
+void smart_notification_shutdown(void);
+
+#endif /* end of include guard: SMART_NOTIFICATION_H_ */
diff --git a/include/structural_navi.h b/include/structural_navi.h
new file mode 100644 (file)
index 0000000..655e3bf
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef SMART_NAVI_STRUCTURAL_NAVI_H
+#define SMART_NAVI_STRUCTURAL_NAVI_H_
+
+#include <atspi/atspi.h>
+
+/**
+ * @brief Structural navigation submodule.
+ *
+ * @detail
+ * Structural navigation submodule can be used to traverse at-spi object tree
+ * hierarchy in unified, position sorted manner.
+ */
+
+/**
+ * @brief Gets next Accessible object in "structural navigation" hierarchy.
+ *
+ * @detail
+ * Gest next sibling object in accessibile objects tree which is considered as
+ * next by taking into account its position on screen. (see @position_sort for
+ * more details about visuall sorting)
+ *
+ * @param current AtspiAccessible instance from which sibling object will be
+ * searched.
+ *
+ * @retun 'Next' AtspiAccessible object pointer or NULL if not found.
+ */
+AtspiAccessible *structural_navi_same_level_next(AtspiAccessible *current);
+
+/**
+ * @brief Gets previous Accessible object in "structural navigation" hierarchy.
+ *
+ * @detail
+ * Gest previous sibling object in accessibile objects tree which is considered as
+ * previous by taking into account its position on screen. (see @position_sort for
+ * more details about visuall sorting)
+ *
+ * @param current AtspiAccessible instance from which sibling object will be
+ * searched.
+ *
+ * @retun 'Previous' AtspiAccessible object pointer or NULL if not found.
+ */
+AtspiAccessible *structural_navi_same_level_prev(AtspiAccessible *current);
+
+/**
+ * @brief Gests parent's object.
+ *
+ * @detail
+ * Gets parent object contained within same application. If there is no parent
+ * object contained in same application function should return NULL.
+ *
+ * @param object AtspiAccessible which parent will be searched.
+ * @return Parent AtspiAccessible object or NULL if not found or @current
+ * is an application object.
+ */
+AtspiAccessible *structural_navi_level_up(AtspiAccessible *current);
+
+/**
+ * @brief Gets child object.
+ *
+ * @detail
+ * Gets AtspiAccessible object considered as first child in structural
+ * navigation hierachy. Child will be sorted by its position on the screen.
+ * (see @position_sort for more details)
+ *
+ * @param object AtspiAccessible which child will be searched.
+ * @return child AtspiAccessible object or NULL if not found.
+ */
+AtspiAccessible *structural_navi_level_down(AtspiAccessible *current);
+
+/**
+ * @brief Gets next object.
+ *
+ * @detail
+ * Get next AtspiAccessible object from Atspi realation FLOWS_TO.
+ *
+ * @param object AtspiAccessible
+ * @return next AtspiAccessible object or NULL if not found.
+ */
+AtspiAccessible *structural_navi_app_chain_next(AtspiAccessible *current);
+
+/**
+ * @brief Gets previous object.
+ *
+ * @detail
+ * Get previous AtspiAccessible object from Atspi realation FLOWS_FROM.
+ *
+ * @param object AtspiAccessible
+ * @return next AtspiAccessible object or NULL if not found.
+ */
+AtspiAccessible *structural_navi_app_chain_prev(AtspiAccessible *current);
+
+#endif /* end of include guard: STRUCTURAL_NAVI_H_ */
diff --git a/include/window_tracker.h b/include/window_tracker.h
new file mode 100644 (file)
index 0000000..a36fd28
--- /dev/null
@@ -0,0 +1,8 @@
+#include <atspi/atspi.h>
+
+void window_tracker_init(void);
+void window_tracker_shutdown(void);
+
+typedef void (*Window_Tracker_Cb) (void *data, AtspiAccessible *window);
+void window_tracker_register(Window_Tracker_Cb cb, void *data);
+void window_tracker_active_window_request(void);
diff --git a/org.tizen.screen-reader.manifest b/org.tizen.screen-reader.manifest
new file mode 100644 (file)
index 0000000..9c82c66
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+   <request>
+      <domain name="_"/>
+   </request>
+</manifest>
diff --git a/org.tizen.screen-reader.xml b/org.tizen.screen-reader.xml
new file mode 100755 (executable)
index 0000000..b7eb4b2
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<manifest xmlns="http://tizen.org/ns/packages" package="org.tizen.screen-reader" version="0.1.0" install-location="internal-only">
+        <label>screen-reader</label>
+        <description>SCREEN READER</description>
+        <ui-application appid="org.tizen.screen-reader" exec="/usr/apps/org.tizen.screen-reader/bin/screen-reader" nodisplay="false" multiple="false" type="capp" taskmanage="true">
+                <label>screen-reader</label>
+                <icon>/usr/apps/org.tizen.screen-reader/res/icons/screen-reader.png</icon>
+                <label xml:lang="en-us">screen-reader</label>
+        </ui-application>
+</manifest>
+
diff --git a/packaging/org.tizen.screen-reader.spec b/packaging/org.tizen.screen-reader.spec
new file mode 100755 (executable)
index 0000000..a5ce4d2
--- /dev/null
@@ -0,0 +1,58 @@
+Name:       org.tizen.screen-reader
+Summary:    Empty app
+Version:    0.0.1
+Release:    1
+Group:      Applications/Core Applications
+License:    Apache-2.0
+Source0:    %{name}-%{version}.tar.gz
+
+BuildRequires:  at-spi2-core
+BuildRequires:  at-spi2-core-devel
+BuildRequires:  cmake
+BuildRequires:  pkgconfig(capi-appfw-application)
+BuildRequires:  pkgconfig(capi-system-device)
+BuildRequires:  pkgconfig(dlog)
+BuildRequires:  pkgconfig(vconf)
+BuildRequires:  pkgconfig(bundle)
+BuildRequires:  tts
+BuildRequires:  tts-devel
+BuildRequires:  vconf
+BuildRequires:  pkgconfig(check)
+
+%define AppInstallPath /usr/apps/%{name}
+%define Exec screen-reader
+
+
+%description
+An utility library for developers of the menu screen.
+
+%prep
+%setup -q
+
+%build
+cmake . -DCMAKE_INSTALL_PREFIX="%{AppInstallPath}" -DCMAKE_TARGET="%{Exec}" -DCMAKE_PACKAGE="%{name}"
+make %{?jobs:-j%jobs} \
+2>&1 | sed \
+-e 's%^.*: error: .*$%\x1b[37;41m&\x1b[m%' \
+-e 's%^.*: warning: .*$%\x1b[30;43m&\x1b[m%'
+export LD_LIBRARY_PATH=/emul/ia32-linux/lib:/emul/ia32-linux/usr/lib:$LD_LIBRARY_PATH
+make test
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+%post 
+/sbin/ldconfig
+vconftool set -t string db/setting/accessibility/language "en_US"
+vconftool set -t int db/setting/accessibility/information_level 2
+vconftool set -t int db/setting/accessibility/voice 1
+vconftool set -t string db/setting/accessibility/tracking_signal "focused"
+
+%postun -p /sbin/ldconfig
+
+%files
+%manifest org.tizen.screen-reader.manifest
+%{AppInstallPath}/bin/screen-reader
+%{AppInstallPath}/res/icons/screen-reader.png
+/usr/share/packages/%{name}.xml
diff --git a/res/icons/screen-reader.png b/res/icons/screen-reader.png
new file mode 100755 (executable)
index 0000000..778d06b
Binary files /dev/null and b/res/icons/screen-reader.png differ
diff --git a/src/app_tracker.c b/src/app_tracker.c
new file mode 100644 (file)
index 0000000..17355a3
--- /dev/null
@@ -0,0 +1,218 @@
+#include "app_tracker.h"
+#include "logger.h"
+
+typedef struct {
+     AtspiAccessible *root;
+     GList *callbacks;
+     guint timer;
+} SubTreeRootData;
+
+typedef struct {
+     AppTrackerEventType type;
+     AppTrackerEventCB func;
+     void *user_data;
+} EventCallbackData;
+
+#define APP_TRACKER_INVACTIVITY_TIMEOUT 200
+
+static int _init_count;
+static GList *_roots;
+static AtspiEventListener *_listener;
+
+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 void
+_subtree_callbacks_call(AppTrackerEventType event, SubTreeRootData *std)
+{
+   GList *l;
+   EventCallbackData *ecd;
+
+   for (l = std->callbacks; l != NULL; l = l->next)
+     {
+        ecd = l->data;
+        if (ecd->type == event)
+          {
+             ecd->func(event, ecd->user_data);
+          }
+     }
+}
+
+static gboolean
+_on_timeout_cb(gpointer user_data)
+{
+   SubTreeRootData *std = user_data;
+
+   _subtree_callbacks_call(APP_TRACKER_EVENT_VIEW_CHANGED, std);
+
+   std->timer = 0;
+
+   return FALSE;
+}
+
+static void 
+_on_atspi_event_cb(const AtspiEvent *event)
+{
+   GList *l;
+   SubTreeRootData *std;
+
+   if (!event || !event->source) return;
+
+   for (l = _roots; l != NULL; l = l->next)
+     {
+        std = l->data;
+        if (_is_descendant(std->root, event->source))
+          {
+             if (std->timer)
+               g_source_remove(std->timer);
+             else
+               _subtree_callbacks_call(APP_TRACKER_EVENT_VIEW_CHANGE_STARTED, std);
+
+             std->timer = g_timeout_add(APP_TRACKER_INVACTIVITY_TIMEOUT, _on_timeout_cb, std);
+          }
+     }
+}
+
+static int
+_app_tracker_init_internal(void)
+{
+   _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:children-changed", NULL);
+   atspi_event_listener_register(_listener, "object:bounds-changed", NULL);
+   atspi_event_listener_register(_listener, "object:visible-data-changed", 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:children-changed", NULL);
+   atspi_event_listener_deregister(_listener, "object:bounds-changed", NULL);
+   atspi_event_listener_deregister(_listener, "object:visible-data-changed", NULL);
+
+   g_object_unref(_listener);
+   _listener = NULL;
+
+   g_list_free_full(_roots, _free_rootdata);
+   _roots = NULL;
+}
+
+int app_tracker_init(void)
+{
+   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, AppTrackerEventType event, AppTrackerEventCB cb, void *user_data)
+{
+   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->callbacks = NULL;
+        rd->timer = 0;
+        _roots = g_list_append(_roots, rd);
+     }
+
+   cd = g_new(EventCallbackData, 1);
+   cd->type = event;
+   cd->func = cb;
+   cd->user_data = user_data;
+
+   rd->callbacks = g_list_append(rd->callbacks, cd);
+}
+
+void app_tracker_callback_unregister(AtspiAccessible *app, AppTrackerEventType event, AppTrackerEventCB cb, void *user_data)
+{
+   GList *l;
+   EventCallbackData *ecd;
+   SubTreeRootData *std = NULL;
+
+   for (l = _roots; l != NULL; l = l->next)
+     {
+        if (((SubTreeRootData*)l->data)->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);
+     }
+}
diff --git a/src/flat_navi.c b/src/flat_navi.c
new file mode 100644 (file)
index 0000000..482c29d
--- /dev/null
@@ -0,0 +1,417 @@
+#include "flat_navi.h"
+#include "position_sort.h"
+#include "object_cache.h"
+#include "logger.h"
+#include <glib.h>
+#include <stdlib.h>
+
+struct _FlatNaviContext {
+     AtspiAccessible *root;
+     GList *current;
+     GList *current_line;
+     GList *lines;
+     GList *candidates;
+};
+
+static const AtspiStateType required_states[] = {
+     ATSPI_STATE_VISIBLE,
+     ATSPI_STATE_SHOWING,
+     ATSPI_STATE_LAST_DEFINED
+};
+
+static const AtspiRole interesting_roles[] = {
+     ATSPI_ROLE_CHECK_BOX,
+     ATSPI_ROLE_COLOR_CHOOSER,
+     ATSPI_ROLE_COMBO_BOX,
+     ATSPI_ROLE_DATE_EDITOR,
+     ATSPI_ROLE_FILE_CHOOSER,
+     ATSPI_ROLE_FONT_CHOOSER,
+     ATSPI_ROLE_HEADER,
+     ATSPI_ROLE_HEADING,
+     ATSPI_ROLE_ICON,
+     ATSPI_ROLE_ENTRY,
+     ATSPI_ROLE_LABEL,
+     ATSPI_ROLE_LIST_ITEM,
+     ATSPI_ROLE_MENU_ITEM,
+     ATSPI_ROLE_PARAGRAPH,
+     ATSPI_ROLE_PASSWORD_TEXT,
+     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
+};
+
+/**
+ * @brief Returns a list of all descendants objects
+ *
+ * @return GList of AtspiAccessible* type. List Should be
+ * be free with _accessible_list_free after usage
+ *
+ * @note obj parameter will also be included (in list head)
+ * @note every obj has increased ref. call g_object_unref on every
+ * object after usage.
+ */
+static GList*
+_descendants_list_get(AtspiAccessible *obj)
+{
+   GList *ret = NULL, *toprocess = NULL;
+   int i;
+
+   if (!obj) return NULL;
+
+   // to keep all refcounts in ret list +1
+   g_object_ref(obj);
+   toprocess = g_list_append(toprocess, obj);
+
+   while (toprocess)
+     {
+        AtspiAccessible *obj = toprocess->data;
+        toprocess = g_list_delete_link(toprocess, toprocess);
+        int n = atspi_accessible_get_child_count(obj, NULL);
+        for (i = 0; i < n; i++)
+          {
+             AtspiAccessible *child = atspi_accessible_get_child_at_index(obj, i, NULL);
+             if (child) toprocess = g_list_append(toprocess, child);
+          }
+
+        ret = g_list_append(ret, obj);
+     }
+
+   return ret;
+}
+
+static void
+_accessible_list_free(GList *d)
+{
+   for (; d != NULL; d = d->next)
+      g_object_unref(d->data);
+}
+
+typedef struct {
+     GList *success;
+     GList *failure;
+} FilterResult;
+
+typedef gboolean (*Filter_Each_Cb)(const void *container, void *data, void *fdata);
+
+static FilterResult
+_accessible_list_split_with_filter(GList *list, Filter_Each_Cb cb, void *user_data)
+{
+   FilterResult ret = { NULL, NULL};
+   GList *l;
+   AtspiAccessible *obj;
+
+   for (l = list; l; l = l->next)
+     {
+        obj = l->data;
+        gboolean res = cb(NULL, user_data, obj);
+        if (res)
+          ret.success = g_list_append(ret.success, obj);
+        else
+          ret.failure = g_list_append(ret.failure, obj);
+     }
+   g_list_free(list);
+
+   return ret;
+}
+
+static gboolean
+_filter_state_cb(const void *container, void *data, void *fdata)
+{
+   AtspiStateType *state = data;
+   gboolean ret = TRUE;
+   AtspiAccessible *obj = fdata;
+   AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+
+   while (*state != ATSPI_STATE_LAST_DEFINED)
+     {
+        if (!atspi_state_set_contains(ss, *state))
+          {
+             ret = FALSE;
+             break;
+          }
+        state++;
+     }
+
+   g_object_unref(ss);
+   return ret;
+}
+
+static gboolean
+_filter_role_cb(const void *container, void *data, void *fdata)
+{
+   AtspiRole *role = data;
+   gboolean ret = FALSE;
+   AtspiAccessible *obj = fdata;
+
+   while (*role != ATSPI_ROLE_LAST_DEFINED)
+     {
+        if (atspi_accessible_get_role(obj, NULL) == *role)
+          {
+             ret = TRUE;
+             break;
+          }
+        role++;
+     }
+
+   return ret;
+}
+
+static inline gboolean
+_rectangle_intersect(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
+{
+   return !(((y1 + h1) <= y2) || (y1 >= (y2 + h2)) || ((x1 + w1) <= x2) || (x1 >= (x2 + w2)));
+}
+
+static gboolean
+_filter_viewport_cb(const void *container, void *data, void *fdata)
+{
+   const ObjectCache *oc, *ocr = data;
+   AtspiAccessible *obj = fdata;
+
+   oc = object_cache_get(obj);
+   if (!oc || !oc->bounds || (oc->bounds->height < 0) || (oc->bounds->width < 0))
+     return FALSE;
+
+   // at least one pixel of child have to be in viewport
+   return _rectangle_intersect(ocr->bounds->x, ocr->bounds->y, ocr->bounds->width, ocr->bounds->height,
+                               oc->bounds->x, oc->bounds->y, oc->bounds->width, oc->bounds->height);
+}
+
+static GList*
+_flat_review_candidates_get(AtspiAccessible *root)
+{
+   GList *desc, *ret = NULL;
+   FilterResult fr0;
+   
+   desc = _descendants_list_get(root);
+
+   DEBUG("Descendants: %d", g_list_length(desc));
+
+   // remove object that are not in root's viewport
+   const ObjectCache *oc = object_cache_get(root);
+   if (!oc || !oc->bounds || (oc->bounds->height < 0) || (oc->bounds->width < 0))
+     {
+        ERROR("Invalid root object bounds. Unable to filter descendants by position");
+        fr0.success = desc;
+     }
+   else
+     {
+        fr0 = _accessible_list_split_with_filter(desc, _filter_viewport_cb, (void*)oc);
+        _accessible_list_free(fr0.failure);
+     }
+
+   // remove objects without required states
+   FilterResult fr1 = _accessible_list_split_with_filter(fr0.success, _filter_state_cb, (void*)required_states);
+   _accessible_list_free(fr1.failure);
+
+   // get 'interesting' objects
+   FilterResult fr2 = _accessible_list_split_with_filter(fr1.success, _filter_role_cb, (void*)interesting_roles);
+   ret = g_list_concat(ret, fr2.success);
+   _accessible_list_free(fr2.failure);
+
+   DEBUG("Candidates: %d", g_list_length(ret));
+
+   return ret;
+}
+
+static void
+debug(FlatNaviContext *ctx)
+{
+   GList *l1, *l2, *line;
+   AtspiAccessible *obj;
+   int i, l = 0;
+
+   DEBUG("===============================");
+   for (l1 = ctx->lines; l1 != NULL; l1 = l1->next)
+     {
+        line = l1->data;
+        i = 0;
+        DEBUG("==== Line %d ====", l);
+        for (l2 = line; l2 != NULL; l2 = l2->next)
+          {
+             obj = l2->data;
+             char *name = atspi_accessible_get_name(obj, NULL);
+             char *role = atspi_accessible_get_role_name(obj, NULL);
+             const ObjectCache *oc = object_cache_get(obj);
+             if (oc) {
+             DEBUG("%d %s %s, (%d %d %d %d)", i++, name, role,
+                   oc->bounds->x, oc->bounds->y, oc->bounds->width, oc->bounds->height);
+             }
+             if (name) g_free(name);
+             if (role) g_free(role);
+          }
+        l++;
+     }
+   DEBUG("===============================");
+}
+
+FlatNaviContext *flat_navi_context_create(AtspiAccessible *root)
+{
+   FlatNaviContext *ret;
+
+   ret = calloc(1, sizeof(FlatNaviContext));
+   if (!ret) return NULL;
+
+   ret->root = root;
+   ret->candidates = _flat_review_candidates_get(root);
+   ret->lines = position_sort(ret->candidates); 
+   ret->current_line = ret->lines;
+   ret->current = ret->current_line ? ret->current_line->data : NULL;
+
+   debug(ret);
+
+   return ret;
+}
+
+void flat_navi_context_free(FlatNaviContext *ctx)
+{
+   GList *l;
+   for (l = ctx->lines; l != NULL; l = l->next)
+      g_list_free((GList*)l->data);
+   _accessible_list_free(ctx->candidates);
+   free(ctx);
+}
+
+AtspiAccessible *flat_navi_context_current_get(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+   return ctx->current->data;
+}
+
+gboolean flat_navi_context_current_set(FlatNaviContext *ctx, AtspiAccessible *target)
+{
+   if(!ctx || !target) return FALSE;
+
+   GList *l, *l2, *line;
+   AtspiAccessible *obj;
+   gboolean found = FALSE;
+
+   for (l = ctx->lines; l; l = l->next)
+     {
+        line = l->data;
+        for (l2 = line; l2; l2 = l2->next)
+          {
+             obj = l2->data;
+             if (obj == target)
+               {
+                  found = TRUE;
+                  break;
+               }
+          }
+        if (found)
+          {
+             ctx->current_line = l;
+             ctx->current = l2;
+             break;
+          }
+     }
+
+   return found;
+}
+
+AtspiAccessible *flat_navi_context_next(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+   GList *ret = ctx->current->next;
+
+   if (ret)
+     {
+        ctx->current = ret;
+        return ctx->current->data;
+     }
+   return NULL;
+}
+
+AtspiAccessible *flat_navi_context_prev(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+   GList *ret = ctx->current->prev;
+
+   if (ret)
+     {
+        ctx->current = ret;
+        return ctx->current->data;
+     }
+   return NULL;
+}
+
+AtspiAccessible *flat_navi_context_first(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+   GList *ret = ctx->current_line->data;
+
+   if (ret)
+     {
+        ctx->current = ret;
+        return ctx->current->data;
+     }
+   return NULL;
+}
+
+AtspiAccessible *flat_navi_context_last(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+   GList *ret = g_list_last(ctx->current);
+
+   if (ret)
+     {
+        ctx->current = ret;
+        return ctx->current->data;
+     }
+   return NULL;
+}
+
+AtspiAccessible *flat_navi_context_line_next(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+
+   GList *ret = ctx->current_line->next;
+   if (!ret) return NULL;
+
+   ctx->current_line = ret;
+   ctx->current = ctx->current_line->data;
+
+   return ctx->current->data;
+}
+
+AtspiAccessible *flat_navi_context_line_prev(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+
+   GList *ret = ctx->current_line->prev;
+   if (!ret) return NULL;
+
+   ctx->current_line = ret;
+   ctx->current = ctx->current_line->data;
+
+   return ctx->current->data;
+}
+
+AtspiAccessible *flat_navi_context_line_first(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+
+   ctx->current_line = ctx->lines;
+   ctx->current = ctx->current_line->data;
+
+   return ctx->current->data;
+}
+
+AtspiAccessible *flat_navi_context_line_last(FlatNaviContext *ctx)
+{
+   if (!ctx || !ctx->lines) return NULL;
+
+   ctx->current_line = g_list_last(ctx->current_line);
+   ctx->current = ctx->current_line->data;
+
+   return ctx->current->data;
+}
diff --git a/src/keyboard_tracker.c b/src/keyboard_tracker.c
new file mode 100644 (file)
index 0000000..3f950cf
--- /dev/null
@@ -0,0 +1,48 @@
+#include <string.h>
+#include <atspi/atspi.h>
+#include "keyboard_tracker.h"
+#include "logger.h"
+
+static AtspiDeviceListener *listener;
+static Keyboard_Tracker_Cb user_cb;
+static void *user_data;
+
+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)
+{
+   atspi_init();
+   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);
+   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);
+   DEBUG("keyboard tracker shutdown");
+}
diff --git a/src/main.c b/src/main.c
new file mode 100755 (executable)
index 0000000..e5e51cf
--- /dev/null
@@ -0,0 +1,33 @@
+#include <app.h>
+#include "navigator.h"
+#include "window_tracker.h"
+#include "logger.h"
+#include "screen_reader.h"
+
+
+static bool app_create(void *data)
+{
+    atspi_init();
+    navigator_init();
+    screen_reader_create_service(data);
+
+    return true;
+}
+
+static void app_terminate(void *data)
+{
+    screen_reader_terminate_service(data);
+    navigator_shutdown();
+}
+
+int main(int argc, char **argv)
+{
+    app_create(get_pointer_to_service_data_struct());
+
+    GMainLoop *ml = g_main_loop_new(NULL, FALSE);
+    g_main_loop_run (ml);
+
+    app_terminate(get_pointer_to_service_data_struct());
+
+    return 0;
+}
diff --git a/src/navigator.c b/src/navigator.c
new file mode 100644 (file)
index 0000000..b9c2360
--- /dev/null
@@ -0,0 +1,227 @@
+#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 "structural_navi.h"
+#include "object_cache.h"
+#include "flat_navi.h"
+#include "app_tracker.h"
+#include "smart_notification.h"
+#include "screen_reader_system.h"
+#include "screen_reader_haptic.h"
+
+#define QUICKPANEL_DOWN TRUE
+#define QUICKPANEL_UP FALSE
+
+#define DISTANCE_NB 8
+
+#define DEBUG_MODE
+
+#define GERROR_CHECK(error)\
+  if (error)\
+   {\
+     ERROR("Error_log:%s",error->message);\
+     g_error_free(error);\
+     error = NULL;\
+   }
+
+static AtspiAccessible *top_window;
+static gboolean _window_cache_builded;
+static FlatNaviContext *context;
+
+static void
+_current_highlight_object_set(AtspiAccessible *obj)
+{
+   //TODO
+   //speak virtually focused widget
+}
+
+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)
+        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)
+    }
+}
+
+static void _focus_next(void)
+{
+   AtspiAccessible *obj;
+   if (!context)
+     {
+        ERROR("No navigation context created");
+        return;
+     }
+   obj = flat_navi_context_next(context);
+   // try next line
+   if (!obj)
+        obj = flat_navi_context_line_next(context);
+   // try 'cycle' objects in context
+   if (!obj)
+     {
+        flat_navi_context_line_first(context);
+        obj = flat_navi_context_first(context);
+        smart_notification(FOCUS_CHAIN_END_NOTIFICATION_EVENT, 0, 0);
+     }
+   if (obj)
+     _current_highlight_object_set(obj);
+   else
+     DEBUG("Next 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);
+   // try previous line
+   if (!obj)
+     {
+        obj = flat_navi_context_line_prev(context);
+        if (obj)
+           obj = flat_navi_context_last(context);
+     }
+   // try 'cycle' objects in context
+   if (!obj)
+     {
+        flat_navi_context_line_last(context);
+        obj = flat_navi_context_last(context);
+        smart_notification(FOCUS_CHAIN_END_NOTIFICATION_EVENT, 0, 0);
+     }
+   if (obj)
+     _current_highlight_object_set(obj);
+   else
+     DEBUG("Previous widget not found. Abort");
+}
+
+static void
+_on_cache_builded(void *data)
+{
+   DEBUG("Cache building");
+   _window_cache_builded = TRUE;
+   AtspiAccessible *pivot = NULL;
+   if (context)
+     {
+        pivot = flat_navi_context_current_get(context);
+        flat_navi_context_free(context);
+     }
+   context = flat_navi_context_create(top_window);
+
+   // try to set previous object in new context
+   if (flat_navi_context_current_set(context, pivot))
+     _current_highlight_object_set(pivot);
+   else
+     _current_highlight_object_set(flat_navi_context_current_get(context));
+   DEBUG("Cache building finished");
+}
+
+static void
+_view_content_changed(AppTrackerEventType type, void *user_data)
+{
+   DEBUG("View content changed");
+   _window_cache_builded = FALSE;
+   if (top_window)
+      object_cache_build_async(top_window, 5, _on_cache_builded, NULL);
+}
+
+static void on_window_activate(void *data, AtspiAccessible *window)
+{
+   gchar *name;
+   DEBUG("... on window activate ...");
+
+   app_tracker_callback_unregister(top_window, APP_TRACKER_EVENT_VIEW_CHANGED, _view_content_changed, NULL);
+
+   if(window)
+   {
+      app_tracker_callback_register(window, APP_TRACKER_EVENT_VIEW_CHANGED, _view_content_changed, NULL);
+      name = atspi_accessible_get_name(window, NULL);
+      DEBUG("Window name: %s", name);
+      _window_cache_builded = FALSE;
+      object_cache_build_async(window, 5, _on_cache_builded, NULL);
+      g_free(name);
+   }
+   else
+   {
+       ERROR("No top window found!");
+   }
+   top_window = window;
+}
+
+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)
+{
+   window_tracker_init();
+   window_tracker_register(on_window_activate, NULL);
+   window_tracker_active_window_request();
+   app_tracker_init();
+   smart_notification_init();
+   system_notifications_init();
+   keyboard_tracker_init();
+   keyboard_tracker_register(kb_tracker, NULL);
+}
+
+void navigator_shutdown(void)
+{
+   if (context)
+     {
+        flat_navi_context_free(context);
+        context = NULL;
+     }
+   object_cache_shutdown();
+   app_tracker_shutdown();
+   window_tracker_shutdown();
+   smart_notification_shutdown();
+   system_notifications_shutdown();
+   keyboard_tracker_shutdown();
+}
diff --git a/src/object_cache.c b/src/object_cache.c
new file mode 100644 (file)
index 0000000..99b3cb5
--- /dev/null
@@ -0,0 +1,232 @@
+#include "object_cache.h"
+#include "logger.h"
+
+#include <glib.h>
+
+static GHashTable *cache;
+static int idler = -1;
+static GList *toprocess;
+static void *user_data;
+static ObjectCacheReadyCb callback;
+static int bulk;
+
+static void
+_cache_item_free_cb(gpointer data)
+{
+   if (!data) return;
+   ObjectCache *co = data;
+   g_free(co->bounds);
+   g_free(co);
+}
+
+static void
+_list_obj_unref_and_free(GList *toprocess)
+{
+   for (; toprocess; toprocess = toprocess->next)
+      g_object_unref(toprocess->data);
+}
+
+static void
+_object_cache_free_internal(void)
+{
+   if (idler > 0)
+     {
+        g_source_remove(idler);
+        idler = -1;
+     }
+   if (toprocess)
+     {
+        _list_obj_unref_and_free(toprocess);
+        toprocess = NULL;
+     }
+   if (cache)
+     {
+        g_hash_table_destroy(cache);
+        cache = NULL;
+     }
+}
+
+/**
+ * Returnes a list of all accessible object implementing AtkCompoment interface
+ * GList should be free with g_list_Free function.
+ * Every AtspiAccessible in list should be unrefed with g_object_unref.
+ */
+static GList*
+_cache_candidates_list_prepare(AtspiAccessible *root)
+{
+   GList *toprocess = NULL, *ret = NULL;
+   int n, i;
+
+   if (!root) return NULL;
+
+   // Keep ref counter +1 on every object in returned list
+   g_object_ref(root);
+   toprocess = g_list_append(toprocess, root);
+
+   while (toprocess)
+     {
+        AtspiAccessible *obj = toprocess->data;
+        toprocess = g_list_delete_link(toprocess, toprocess);
+        if (!obj)
+          continue;
+        n = atspi_accessible_get_child_count(obj, NULL);
+        for (i = 0; i < n; i++)
+          {
+             AtspiAccessible *cand = atspi_accessible_get_child_at_index(obj, i, NULL);
+             if(cand)
+               toprocess = g_list_append(toprocess, cand);
+          }
+        ret = g_list_append(ret, obj);
+     }
+
+   return ret;
+}
+
+static ObjectCache*
+_cache_item_construct(AtspiAccessible *obj)
+{
+   ObjectCache *ret;
+   AtspiComponent *comp;
+
+   if (!obj)
+     return NULL;
+
+   ret = g_new0(ObjectCache, 1);
+   if (!ret)
+     {
+        ERROR("out-of memory");
+        return NULL;
+     }
+
+   comp = atspi_accessible_get_component(obj);
+   if (!comp) {
+     ERROR("Cached Object do not implement Component interface");
+   }
+   else {
+      ret->bounds = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_SCREEN, NULL);
+      g_object_unref(comp);
+   }
+
+   return ret;
+}
+
+static GList*
+_cache_item_n_cache(GList *objs, int count)
+{
+   int i = 0;
+   GList *ret = objs;
+   ObjectCache *oc;
+   AtspiAccessible *obj;
+
+   for (i = 0; (i < count) && ret; i++)
+     {
+        obj = ret->data;
+        ret = g_list_delete_link(ret, ret);
+
+        oc = _cache_item_construct(obj);
+        if (!oc) break;
+
+        g_hash_table_insert (cache, obj, oc);
+
+        g_object_unref(obj);
+     }
+
+   return ret;
+}
+
+static GHashTable*
+_object_cache_new(void)
+{
+   return g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, _cache_item_free_cb);
+}
+
+void
+object_cache_build(AtspiAccessible *root)
+{
+   GList *objs;
+
+   _object_cache_free_internal();
+   cache = _object_cache_new();
+   if (!cache)
+     {
+        ERROR("ObjectCache: hash table creation failed");
+        return;
+     }
+
+   objs = _cache_candidates_list_prepare(root);
+   _cache_item_n_cache(objs, g_list_length(objs));
+
+   return;
+}
+
+static gboolean
+_do_cache(void *data)
+{
+   toprocess = _cache_item_n_cache(toprocess, bulk);
+   idler = -1;
+
+   if (toprocess)
+     idler = g_idle_add(_do_cache, NULL);
+   else if (callback) callback(user_data);
+
+   return FALSE;
+}
+
+void
+object_cache_build_async(AtspiAccessible *root, int bulk_size, ObjectCacheReadyCb cb, void *ud)
+{
+   _object_cache_free_internal();
+
+   callback = cb;
+   user_data = ud;
+   bulk = bulk_size;
+
+   cache = _object_cache_new();
+   if (!cache)
+     {
+        ERROR("ObjectCache: hash table creation failed");
+        return;
+     }
+
+   toprocess = _cache_candidates_list_prepare(root);
+   idler = g_idle_add(_do_cache, NULL);
+
+   return;
+}
+
+void
+object_cache_shutdown(void)
+{
+   _object_cache_free_internal();
+}
+
+const ObjectCache*
+object_cache_get(AtspiAccessible *obj)
+{
+   ObjectCache *ret = NULL;
+   if (idler > 0)
+     {
+        ERROR("Invalid usage. Async cache build is ongoing...");
+        return NULL;
+     }
+
+   if (!cache)
+     {
+        cache = _object_cache_new();
+        if (!cache)
+          {
+             ERROR("ObjectCache: hash table creation failed");
+             return NULL;
+          }
+     }
+
+   ret = g_hash_table_lookup(cache, obj);
+   if (!ret)
+     {
+        // fallback to blocking d-bus call
+        ret = _cache_item_construct(obj);
+        g_hash_table_insert(cache, obj, ret);
+     }
+
+   return ret;
+}
diff --git a/src/pivot_chooser.c b/src/pivot_chooser.c
new file mode 100644 (file)
index 0000000..f08e022
--- /dev/null
@@ -0,0 +1,127 @@
+#include <atspi/atspi.h>
+#include "logger.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)
+{
+   GList *candidates = NULL, *queue = NULL;
+
+   // ref object to keep same ref count
+   g_object_ref(parent);
+   queue = g_list_append(queue, parent);
+
+   while (queue)
+     {
+        AtspiAccessible *obj = queue->data;
+        queue = g_list_delete_link(queue, queue);
+
+        int n = atspi_accessible_get_child_count(obj, NULL);
+        if (n == 0)
+          candidates = g_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 = g_list_append(queue, child);
+               }
+             g_object_unref(obj);
+          }
+     }
+
+   // FIXME sort by (x,y) first ??
+   while (candidates)
+     {
+        AtspiAccessible *obj = candidates->data;
+        candidates = g_list_delete_link(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);
+             g_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/position_sort.c b/src/position_sort.c
new file mode 100644 (file)
index 0000000..973a5c7
--- /dev/null
@@ -0,0 +1,159 @@
+#include "logger.h"
+#include "object_cache.h"
+#include "position_sort.h"
+
+
+static int
+_sort_vertically(const void *a, const void *b)
+{
+   AtspiAccessible *objA, *objB;
+   const ObjectCache *cA, *cB;
+
+   objA = (AtspiAccessible*)a;
+   objB = (AtspiAccessible*)b;
+
+   cA = object_cache_get(objA);
+   cB = object_cache_get(objB);
+
+   if (cA->bounds->y == cB->bounds->y)
+     return 0;
+   else if (cA->bounds->y > cB->bounds->y)
+     return 1;
+   else
+     return -1;
+}
+
+static int
+_sort_horizontally(const void *a, const void *b)
+{
+   AtspiAccessible *objA, *objB;
+   const ObjectCache *cA, *cB;
+
+   objA = (AtspiAccessible*)a;
+   objB = (AtspiAccessible*)b;
+
+   cA = object_cache_get(objA);
+   cB = object_cache_get(objB);
+
+   if (cA->bounds->x == cB->bounds->x)
+     {
+        if (cA->bounds->y > cB->bounds->y)
+          return 1;
+        if (cA->bounds->y < cB->bounds->y)
+          return -1;
+        return 0;
+     }
+   else if (cA->bounds->x > cB->bounds->x)
+     return 1;
+   else
+     return -1;
+}
+
+static GList*
+_get_zones(const GList *objs)
+{
+   GList *candidates = NULL;
+   const GList *l;
+   AtspiAccessible *obj;
+   AtspiComponent *comp;
+   const ObjectCache *oc;
+
+   for (l = objs; l; l = l->next)
+     {
+        obj = l->data;
+        if ((comp = atspi_accessible_get_component(obj)) != NULL)
+          {
+             oc = object_cache_get(obj);
+
+             // some objects may implement AtspiCompoment interface, however
+             // they do not have valid sizes.
+             if (!oc->bounds || (oc->bounds->width < 0) || oc->bounds->height < 0)
+               {
+                  DEBUG("Invalid bounds. skipping from zone list: %s %s",
+                   atspi_accessible_get_name(obj, NULL),
+                   atspi_accessible_get_role_name(obj, NULL));
+                  continue;
+               }
+             candidates = g_list_append(candidates, obj);
+          }
+        else
+          DEBUG("No component interface: skipping %s %s",
+                atspi_accessible_get_name(obj, NULL),
+                atspi_accessible_get_role_name(obj, NULL));
+     }
+
+   // Sort object by y - coordinate
+   return g_list_sort(candidates, _sort_vertically);
+}
+
+static GList*
+_get_lines(const GList *objs)
+{
+   GList *line = NULL, *lines = NULL;
+   const GList *l;
+   AtspiAccessible *obj;
+   const ObjectCache *line_beg;
+
+   for (l = objs; l; l = l->next)
+     {
+        obj = l->data;
+        if (!line) {
+          // set first object in line
+          line = g_list_append(line, obj);
+          line_beg = object_cache_get(obj);
+          continue;
+        }
+
+        const ObjectCache *oc = object_cache_get(obj);
+        // Object are considered as present in same line, if
+        // its y coordinate begins maximum 25% below
+        // y coordinate% of first object in line.
+        if ((line_beg->bounds->y + (int)(0.25 * (double)line_beg->bounds->height)) >
+            oc->bounds->y)
+          {
+             line = g_list_append(line, obj);
+             continue;
+          }
+        else
+          {
+             //finish line & set new line leader
+             lines = g_list_append(lines, line);
+             line = NULL;
+             line = g_list_append(line, obj);
+             line_beg = object_cache_get(obj);
+          }
+     }
+
+   // finish last line
+   if (line) lines = g_list_append(lines, line);
+
+   return lines;
+}
+
+GList *position_sort(const GList *objs)
+{
+   GList *l, *line, *zones, *lines = NULL;
+   int i = 0;
+
+   // Get list of objects occupying place on the screen
+   DEBUG("PositionSort: Candidates; %d", g_list_length((GList*)objs));
+   zones = _get_zones(objs);
+
+   // Cluster all zones into lines - verticaly
+   DEBUG("PositionSort: Zones; %d", g_list_length(zones));
+   lines = _get_lines(zones);
+
+   // sort all zones in line - horizontaly
+   DEBUG("PositionSort: Lines; %d", g_list_length(lines));
+   for (l = lines; l; l = l->next)
+     {
+        line = l->data;
+        DEBUG("PositionSort: Line %d: %d items", i++, g_list_length(line));
+        line = g_list_sort(line, _sort_horizontally);
+        l->data = line;
+     }
+
+   if (zones) g_list_free(zones);
+
+   return lines;
+}
diff --git a/src/screen_reader.c b/src/screen_reader.c
new file mode 100755 (executable)
index 0000000..655b768
--- /dev/null
@@ -0,0 +1,94 @@
+#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
+               .information_level = 1,
+               .run_service = 1,
+               .language = "en_US",
+               .voice_type = TTS_VOICE_TYPE_FEMALE,
+               .reading_speed = 2,
+               .tracking_signal_name = FOCUS_CHANGED_SIG,
+
+
+               //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)
+{
+       DEBUG("Service Create Callback \n");
+
+       Service_Data *service_data = data;
+
+       tts_init(service_data);
+       vconf_init(service_data);
+       spi_init(service_data);
+
+
+       /* 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;
+
+       int vconf_ret = vconf_set_bool("db/setting/accessibility/screen_reader", FALSE);
+       if(vconf_ret == 0)
+       {
+               DEBUG("TTS key set to false");
+       }
+       else
+       {
+               DEBUG("COULD NOT SET tts key to 0");
+       }
+
+       
+       vconf_ret = vconf_set_bool(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS, FALSE);
+       if(vconf_ret == 0)
+       {
+               DEBUG("TTS key set to false");
+       }
+       else
+       {
+               DEBUG("COULD NOT SET tts key to 0");
+       }
+
+       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_haptic.c b/src/screen_reader_haptic.c
new file mode 100644 (file)
index 0000000..ef3ad06
--- /dev/null
@@ -0,0 +1,100 @@
+#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 (file)
index 0000000..0662ea8
--- /dev/null
@@ -0,0 +1,402 @@
+#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
+#include <stdlib.h>
+
+#define MEMORY_ERROR "Memory allocation failed"
+#define NO_VALUE_INTERFACE "No value interface present"
+#define NO_TEXT_INTERFACE "No text interface present"
+
+#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_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("--------------------------------------------------------");
+}
+
+gboolean double_click_timer_cb(void *data)
+{
+    Service_Data *sd = data;
+    sd->clicked_widget = NULL;
+
+    return 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)
+{
+    if (!allow_recursive_name(obj))
+        return strdup("");
+
+    if (!obj)
+        return strdup("");
+    int child_count = atspi_accessible_get_child_count(obj, NULL);
+
+    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);
+      if (strncmp(name, "\0", 1)) {
+        strncat(ret, name, sizeof(ret) - strlen(ret) - 1);
+      }
+      strncat(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_get_atspi_accessible_basic_text_description(AtspiAccessible *obj)
+{
+    if(!obj) return NULL;
+
+    char *name;
+    char *description;
+    char *role_name;
+    char *other;
+    char *return_text = NULL;
+
+    description = atspi_accessible_get_description(obj, NULL);
+    name = atspi_accessible_get_name(obj, NULL);
+    role_name = atspi_accessible_get_role_name(obj, NULL);
+    other = generate_description_for_subtree(obj);
+
+    if (strncmp(description, "\0", 1))
+        return_text = strdup(description);
+    else if (strncmp(name, "\0", 1))
+        return_text = strdup(name);
+    else if (strncmp(other, "\0", 1))
+        return_text = strdup(other);
+    else
+        return_text = strdup(role_name);
+
+    free(name);
+    free(description);
+    free(role_name);
+    free(other);
+
+    return return_text;
+}
+
+static char *spi_on_state_changed_get_text(AtspiEvent *event, void *user_data)
+{
+    Service_Data *sd = (Service_Data*)user_data;
+    char *return_text = NULL;
+    sd->currently_focused = event->source;
+
+    DEBUG("->->->->->-> WIDGET GAINED HIGHLIGHT <-<-<-<-<-<-<-");
+    return_text = spi_get_atspi_accessible_basic_text_description(sd->currently_focused);
+
+    return return_text;
+}
+
+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(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), MIN_POS_REACHED) < 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), MAX_POS_REACHED) < 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;
+
+    sd->currently_focused = event->source;
+
+    AtspiValue *value_interface = atspi_accessible_get_value(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, MAX_REACHED) < 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, MIN_REACHED) < 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;
+}
+
+static void spi_object_append_relations_to_text_to_read(AtspiAccessible *obj,
+                                                        char **text_to_read)
+{
+    if(!obj || !text_to_read || !*text_to_read)
+    {
+        ERROR("Invalid parameter");
+        return;
+    }
+
+    GError *error = NULL;
+
+    GArray *relations = atspi_accessible_get_relation_set(obj, &error);
+    if(error != NULL)
+    {
+        ERROR("Can not get atspi object relations: %s", error->message);
+        g_error_free(error);
+        return;
+    }
+
+    if(!relations)
+    {
+        ERROR("Can not get accessible object relations");
+        return;
+    }
+
+    int i = 0;
+    for(; i < relations->len; ++i)
+    {
+        AtspiRelation *relation = g_array_index(relations, AtspiRelation*, i);
+        if(!relation) continue;
+
+        AtspiRelationType relation_type = atspi_relation_get_relation_type(relation);
+        if(relation_type == ATSPI_RELATION_LABEL_FOR ||
+            relation_type == ATSPI_RELATION_LABELLED_BY)
+        {
+            int relation_objects = atspi_relation_get_n_targets(relation);
+            int j;
+            for(j = 0; j < relation_objects; ++j)
+            {
+                AtspiAccessible *relation_object = atspi_relation_get_target(relation, j);
+                if(!relation_object || obj == relation_object) continue;
+
+                char *relation_obj_text = spi_get_atspi_accessible_basic_text_description(relation_object);
+
+                char *text = NULL;
+                if(asprintf(&text, "%s %s", *text_to_read, relation_obj_text) == -1)
+                    continue;
+                free(relation_obj_text);
+                free(*text_to_read);
+                *text_to_read = text;
+                g_object_unref(relation_object);
+            }
+        }
+    }
+
+    g_array_free(relations, TRUE);
+}
+
+char *spi_event_get_text_to_read(AtspiEvent *event, void *user_data)
+{
+    Service_Data *sd = (Service_Data*)user_data;
+    char *text_to_read;
+
+    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
+    {
+        DEBUG("Unhandled event type: %s %d:%d", event->type, event->detail1, event->detail2);
+        return NULL;
+    }
+
+    spi_object_append_relations_to_text_to_read(sd->currently_focused, &text_to_read);
+    DEBUG("%p %s", sd->currently_focused, text_to_read);
+
+    return text_to_read;
+}
+
+void spi_event_listener_cb(AtspiEvent *event, void *user_data)
+{
+    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;
+    }
+    tts_speak(text_to_read, FALSE);
+    free(text_to_read);
+}
+
+/**
+  * @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");
+      }
+
+    // ---------------------------------------------------------------------------------------------------
+
+    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");
+      }
+
+    gboolean ret2 = atspi_event_listener_register(sd->spi_listener, CARET_MOVED_SIG, NULL);
+    if(ret2 == false)
+      {
+         DEBUG("FAILED TO REGISTER spi caret moved listener");
+      }
+
+    gboolean ret3 = atspi_event_listener_register(sd->spi_listener, VALUE_CHANGED_SIG, NULL);
+    if(ret3 == false)
+      {
+         DEBUG("FAILED TO REGISTER spi value changed listener");
+      }
+
+
+    if(ret1 == true && ret2 == true && ret3 == true)
+      {
+         DEBUG("spi listener REGISTERED");
+      }
+
+    DEBUG( "---------------------- SPI_init END ----------------------\n\n");
+}
diff --git a/src/screen_reader_system.c b/src/screen_reader_system.c
new file mode 100644 (file)
index 0000000..9dfbd86
--- /dev/null
@@ -0,0 +1,114 @@
+#include <device/battery.h>
+#include <device/display.h>
+#include <device/callback.h>
+#include "screen_reader.h"
+#include "screen_reader_tts.h"
+#include "smart_notification.h"
+#include "logger.h"
+
+#define CHARGING "Battery charger connected"
+#define NOT_CHARGING "Battery charger disconnected"
+#define SCREEN_ON "Screen is on"
+#define SCREEN_OFF "Screen is off"
+#define BATTERY_LOW "Battery level is low"
+#define BATTERY_FULL "Battery level is full"
+#define BATTERY_CRITICAL "Battery level is critical"
+
+static void device_system_cb(device_callback_e type, void *value, void *user_data);
+
+/**
+ * @brief Initializer for smart notifications
+ *
+ */
+void system_notifications_init(void)
+{
+    // 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);
+}
+
+/**
+ * @brief Initializer for smart notifications
+ *
+ */
+void system_notifications_shutdown(void)
+{
+    // 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);
+}
+
+/**
+ * @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(BATTERY_LOW, TRUE);
+           }
+         else if(status == DEVICE_BATTERY_LEVEL_CRITICAL)
+           {
+              tts_speak(BATTERY_CRITICAL, TRUE);
+           }
+         else if(status == DEVICE_BATTERY_LEVEL_FULL)
+           {
+              tts_speak(BATTERY_FULL, 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(CHARGING, FALSE);
+           }
+         else
+           {
+              tts_speak(NOT_CHARGING, 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(SCREEN_ON, FALSE);
+           }
+         else if(state == DISPLAY_STATE_SCREEN_OFF)
+           {
+              tts_speak(SCREEN_OFF, FALSE);
+           }
+      }
+}
diff --git a/src/screen_reader_tts.c b/src/screen_reader_tts.c
new file mode 100644 (file)
index 0000000..5b9775f
--- /dev/null
@@ -0,0 +1,254 @@
+#define _GNU_SOURCE
+
+#include "screen_reader_tts.h"
+#include "logger.h"
+#include <stdlib.h>
+#include <stdio.h>
+#define MEMORY_ERROR "Memory allocation failed"
+
+// ---------------------------- DEBUG HELPERS ------------------------------
+
+#define FLUSH_LIMIT 1
+
+static int last_utt_id;
+static gboolean pause_state = FALSE;
+static gboolean flush_flag = FALSE;
+
+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";
+           }
+      }
+}
+
+//-------------------------------------------------------------------------------------------------
+
+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(asprintf(&vi->language, "%s",language) < 0)
+      {
+         free(vi);
+         ERROR(MEMORY_ERROR);
+         return FALSE;
+      }
+
+    vi->voice_type = voice_type;
+
+    sd->available_languages = g_list_append(sd->available_languages, vi);
+
+    return TRUE;
+}
+
+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 = TRUE;
+    else
+      {
+         if(flush_flag)
+            flush_flag = FALSE;
+      }
+    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_DEFAULT );
+    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");
+    return true;
+}
+
+gboolean tts_pause_set(gboolean pause_switch)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    if(!sd)
+       return FALSE;
+
+    if(pause_switch)
+      {
+         if(!tts_pause(sd->tts))
+            pause_state = TRUE;
+         else
+            return FALSE;
+      }
+    else if(!pause_switch)
+      {
+         if(!tts_play(sd->tts))
+            pause_state = FALSE;
+         else
+            return FALSE;
+      }
+    return TRUE;
+}
+
+gboolean tts_speak(char *text_to_speak, gboolean flush_switch)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    int speak_id;
+
+    if(!sd)
+       return FALSE;
+
+    if(flush_flag || flush_switch)
+       tts_stop(sd->tts);
+
+    DEBUG( "tts_speak\n");
+    DEBUG( "text to say:%s\n", text_to_speak);
+    if ( !text_to_speak ) return FALSE;
+    if ( !text_to_speak[0] ) return FALSE;
+
+    if(tts_add_text( sd->tts, text_to_speak, sd->language, TTS_VOICE_TYPE_AUTO, TTS_SPEED_AUTO, &speak_id))
+       return FALSE;
+
+    DEBUG("added id to:%d\n", speak_id);
+    last_utt_id = speak_id;
+
+    return TRUE;
+}
+
+gboolean 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 FALSE;
+      }
+
+    if(state == TTS_STATE_READY)
+      {
+         tts_foreach_supported_voices(sd->tts, get_supported_voices_cb, sd);
+      }
+    else
+      {
+         sd->update_language_list = TRUE;
+      }
+
+    DEBUG("END");
+    return 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(current == TTS_STATE_READY || current == TTS_STATE_PAUSED)
+      {
+         DEBUG("TTS state == %s!", get_tts_state(current));
+         tts_play(sd->tts);
+      }
+    else
+      {
+         DEBUG("TTS state != ready or paused!\n");
+      }
+}
+
+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 (file)
index 0000000..4bbf099
--- /dev/null
@@ -0,0 +1,320 @@
+#include <vconf.h>
+#include "screen_reader_vconf.h"
+#include "screen_reader_spi.h"
+#include "logger.h"
+#include <stdio.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;
+
+bool set_langauge(Service_Data *sd, const char *new_language, int new_voice)
+{
+       DEBUG("START");
+
+       GList *l;
+       Voice_Info *vi;
+
+       if(strncmp(sd->language, new_language, LAN_NAME - 1) == 0 && sd->voice_type == new_voice)
+       {
+               DEBUG("No need to change accessibility language: %s(%d) -> %s(%d)",
+                               sd->language, sd->voice_type, new_language, new_voice);
+
+               return true;
+       }
+
+       for (l = sd->available_languages; l; l = l->next)
+       {
+               vi = l->data;
+               DEBUG("foreach %s <- %s", vi->language, new_language);
+               if(strncmp(vi->language, new_language, LAN_NAME - 1) == 0 &&
+                               vi->voice_type == new_voice)
+               {
+                       DEBUG("str_cpy %s (%d) -> %s (%d)", sd->language, sd->voice_type, vi->language, vi->voice_type);
+                       snprintf(sd->language, LAN_NAME, "%s", vi->language);
+                       sd->voice_type = vi->voice_type;
+                       DEBUG("after_str_cpy");
+
+                       DEBUG("ACCESSIBILITY LANGUAGE CHANGED");
+                       DEBUG("END");
+                       return true;
+               }
+       }
+
+       DEBUG("ACCESSIBILITY LANGUAGE FAILED TO CHANGED");
+
+       vconf_set_str("db/setting/accessibility/language", sd->language);
+       vconf_set_int("db/setting/accessibility/voice", sd->voice_type);
+
+       DEBUG("END");
+       return false;
+}
+
+char *fold_tracker_signal(const char *signal_name)
+{
+       if(strcmp(signal_name, FOCUS_SIG) == 0)
+       {
+               return FOCUS_CHANGED_SIG;
+       }
+        return NULL;
+}
+
+bool set_tracking_listener(Service_Data *sd, const char *signal_name)
+{
+       DEBUG("START: %s", sd->tracking_signal_name);
+
+       char *new_tracking_signal;
+
+       new_tracking_signal = fold_tracker_signal(signal_name);
+
+       if(strcmp(sd->tracking_signal_name, new_tracking_signal) == 0)
+       {
+               DEBUG("No need to change accessibility tracking signal: %s == %s",
+                               sd->tracking_signal_name, new_tracking_signal);
+
+               return true;
+       }
+
+       gboolean ret = atspi_event_listener_deregister(sd->spi_listener, sd->tracking_signal_name, NULL);
+       if(ret == false)
+       {
+               DEBUG("Tracking signal listerner deregister successful");
+       }
+
+       sd->tracking_signal_name = strdup(new_tracking_signal);
+
+       gboolean ret1 = atspi_event_listener_register(sd->spi_listener, sd->tracking_signal_name, NULL);
+       if(ret1 == false)
+       {
+               DEBUG("FAILED TO REGISTER spi focus listener");
+               return false;
+       }
+       else
+       {
+               DEBUG("Tracking listener register for new signal");
+       }
+       return true;
+}
+
+// ------------------------------ vconf callbacks----------------------
+
+void information_level_cb(keynode_t *node, void *user_data)
+{
+       DEBUG("START");
+       DEBUG("Information level set: %d", vconf_keynode_get_int(node));
+
+       Service_Data *service_data = user_data;
+       service_data->information_level = vconf_keynode_get_int(node);
+
+       DEBUG("END");
+}
+
+void app_termination_cb(keynode_t *node, void *user_data)
+{
+       DEBUG("START");
+       DEBUG("Application terminate %d", !vconf_keynode_get_int(node));
+
+       Service_Data *service_data = user_data;
+       service_data->run_service = vconf_keynode_get_int(node);
+
+       if(service_data->run_service == 0)
+       {
+               atspi_exit();
+       }
+
+       DEBUG("END");
+}
+
+void language_cb(keynode_t *node, void *user_data)
+{
+       DEBUG("START");
+       DEBUG("Trying to set language to: %s", vconf_keynode_get_str(node));
+
+       Service_Data *sd = user_data;
+
+       int voice_type;
+
+       vconf_get_int("db/setting/accessibility/voice", (int*)(&voice_type));
+       set_langauge(sd, vconf_keynode_get_str(node), voice_type);
+
+       DEBUG("END");
+}
+
+void voice_cb(keynode_t *node, void *user_data)
+{
+       DEBUG("START");
+       DEBUG("Voice set to: %d", vconf_keynode_get_int(node));
+
+       Service_Data *sd = user_data;
+
+       const char *lang = vconf_get_str("db/setting/accessibility/language");
+       set_langauge(sd, lang, vconf_keynode_get_int(node));
+
+       DEBUG("END");
+}
+
+void reading_speed_cb(keynode_t *node, void *user_data)
+{
+       DEBUG("START");
+       DEBUG("Reading speed set to: %d", vconf_keynode_get_int(node));
+
+       Service_Data *service_data = user_data;
+       service_data->reading_speed = vconf_keynode_get_int(node);
+
+       DEBUG("END");
+}
+
+void tracking_signal_changed_cb(keynode_t *node, void *user_data)
+{
+       DEBUG("START");
+       DEBUG("Tracking signal set to: %s", vconf_keynode_get_str(node));
+
+       Service_Data *sd = user_data;
+       const char *tracking_signal = vconf_get_str("db/setting/accessibility/tracking_signal");
+       set_tracking_listener(sd, tracking_signal);
+}
+
+// --------------------------------------------------------------------
+
+int get_key_values(Service_Data *sd)
+{
+       DEBUG("START");
+
+       char *language = vconf_get_str("db/setting/accessibility/language");
+
+       if(sd->language == NULL)
+       {
+               DEBUG("FAILED TO SET LANGUAGE");
+       }
+
+       int ret = -1;
+       int to_ret = 0;
+
+
+       int voice;
+       ret = vconf_get_int("db/setting/accessibility/voice", &voice);
+       if(ret != 0)
+       {
+               to_ret -= -1;
+               DEBUG("FAILED TO SET VOICE TYPE: %d", ret);
+       }
+
+       set_langauge(sd, language, voice);
+
+       ret = vconf_get_int("db/setting/accessibility/speech_rate", &sd->reading_speed);
+       if(ret != 0)
+       {
+               to_ret -= -2;
+               DEBUG("FAILED TO SET READING SPEED: %d", ret);
+       }
+
+       ret = vconf_get_int("db/setting/accessibility/information_level", &sd->information_level);
+       if(ret != 0)
+       {
+               to_ret -= -4;
+               DEBUG("FAILED TO SET INFORMATION LEVEL: %d", ret);
+       }
+
+       DEBUG("SCREEN READER DATA SET TO: Language: %s; Voice: %d, Reading_Speed: %d, Information_Level: %d, Tracking signal: %s;",
+                       sd->language, sd->voice_type, sd->reading_speed, sd->information_level, sd->tracking_signal_name);
+
+       DEBUG("END");
+       return to_ret;
+}
+
+bool vconf_init(Service_Data *service_data)
+{
+       DEBUG( "--------------------- VCONF_init START ---------------------");
+       int ret = 0;
+
+       //TODO: Remove when adequate keys in the settings(control?) panel are added
+       // -----------------------  TEST ONLY -----------------------------------------------
+       DEBUG("TESTS START");
+       keys = vconf_keylist_new();
+       vconf_keylist_add_int(keys, "db/setting/accessibility/information_level", 2);
+       vconf_keylist_add_str(keys, "db/setting/accessibility/language", "en_US");
+       vconf_keylist_add_int(keys, "db/setting/accessibility/voice", 1);
+       vconf_keylist_add_str(keys, "db/setting/accessibility/tracking_signal", FOCUS_SIG);
+    //-----------------------------------------------------------------------------------
+       //vconf_set_bool(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS, EINA_TRUE);
+
+       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);
+       }
+
+       ret = vconf_notify_key_changed("db/setting/accessibility/information_level", information_level_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add information level callback");
+               return false;
+       }
+
+       ret = vconf_notify_key_changed("db/setting/accessibility/screen_reader", app_termination_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add app termination callback");
+               return false;
+       }
+
+       ret = vconf_notify_key_changed("db/menu_widget/language", language_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add language callback");
+               return false;
+       }
+
+       ret = vconf_notify_key_changed("db/setting/accessibility/language", language_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add language callback");
+               return false;
+       }
+
+       ret = vconf_notify_key_changed("db/setting/accessibility/voice", voice_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add voice callback");
+               return false;
+       }
+
+       ret = vconf_notify_key_changed("db/setting/accessibility/speech_rate", reading_speed_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add reading speed callback callback");
+               return false;
+       }
+
+       ret = vconf_notify_key_changed("db/setting/accessibility/tracking_signal", tracking_signal_changed_cb, service_data);
+       if(ret != 0)
+       {
+               DEBUG("Could not add reading speed callback callback");
+               return false;
+       }
+
+       DEBUG("ALL CALBACKS ADDED");
+
+       DEBUG( "---------------------- VCONF_init END ----------------------\n\n");
+       return true;
+}
diff --git a/src/smart_notification.c b/src/smart_notification.c
new file mode 100644 (file)
index 0000000..9d4be52
--- /dev/null
@@ -0,0 +1,175 @@
+#include <atspi/atspi.h>
+#include <stdio.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"
+
+#define ITEMS_NOTIFICATION "Visible items from %d to %d"
+
+static gboolean status = 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)
+{
+    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)
+{
+    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);
+
+        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, void *user_data)
+{
+   if(!status)
+        return;
+
+   int start_index, end_index;
+   start_index = 0;
+   end_index = 0;
+
+   DEBUG("Event: %s: %d, obj: %p: role: %s\n", event->type, event->detail1, event->source,
+         atspi_accessible_get_role_name(event->source, NULL));
+
+   if (!strcmp(event->type, "object:scroll-start"))
+     {
+        DEBUG("Scrolling started");
+        tts_speak("Scrolling started", TRUE);
+     }
+   else if (!strcmp(event->type, "object:scroll-end"))
+     {
+        DEBUG("Scrolling finished");
+        tts_speak("Scrolling finished", 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 = TRUE;
+}
+
+/**
+ * @brief Smart notifications shutdown
+ *
+ */
+void smart_notification_shutdown(void)
+{
+    status = 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);
+}
+
+/**
+ * @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), ITEMS_NOTIFICATION, start_idx, end_idx);
+
+    tts_speak(strdup(buf), FALSE);
+}
diff --git a/src/structural_navi.c b/src/structural_navi.c
new file mode 100644 (file)
index 0000000..2a07c1d
--- /dev/null
@@ -0,0 +1,162 @@
+#include "structural_navi.h"
+#include "position_sort.h"
+#include "logger.h"
+
+static GList*
+_atspi_children_list_get(AtspiAccessible *parent)
+{
+   GList *ret = NULL;
+   int count = atspi_accessible_get_child_count(parent, NULL);
+   int i;
+
+   for (i = 0; i < count; i++)
+     {
+        AtspiAccessible *child = atspi_accessible_get_child_at_index(parent, i, NULL);
+        if (child) ret = g_list_append(ret, child);
+     }
+
+   return ret;
+}
+
+static void
+_atspi_children_list_free(GList *children)
+{
+   for (; children; children = children->next)
+      g_object_unref(children->data);
+}
+
+static GList*
+_flat_review_get(GList *tosort)
+{
+   GList *ret = NULL, *lines, *l, *line;
+
+   lines = position_sort(tosort);
+
+   for (l = lines; l; l = l->next)
+     {
+        line = l->data;
+        ret = g_list_concat(ret, line);
+     }
+
+   g_list_free(lines);
+
+   return ret;
+}
+
+AtspiAccessible *structural_navi_same_level_next(AtspiAccessible *current)
+{
+    AtspiAccessible *parent, *ret = NULL;
+    AtspiRole role;
+
+    parent = atspi_accessible_get_parent(current, NULL);
+    if (!parent) return NULL;
+
+    role = atspi_accessible_get_role(parent, NULL);
+    if (role != ATSPI_ROLE_DESKTOP_FRAME)
+      {
+         GList *children = _atspi_children_list_get(parent);
+         GList *sorted = _flat_review_get(children);
+         GList *me = g_list_find(sorted, current);
+         if (me)
+           ret = me->next ? me->next->data : NULL;
+
+         if (sorted) g_list_free(sorted);
+         _atspi_children_list_free(children);
+
+         return ret;
+      }
+    return NULL;
+}
+
+AtspiAccessible *structural_navi_same_level_prev(AtspiAccessible *current)
+{
+    AtspiAccessible *parent, *ret = NULL;
+    AtspiRole role;
+
+    parent = atspi_accessible_get_parent(current, NULL);
+    if (!parent) return NULL;
+
+    role = atspi_accessible_get_role(parent, NULL);
+    if (role != ATSPI_ROLE_DESKTOP_FRAME)
+      {
+         GList *children = _atspi_children_list_get(parent);
+         GList *sorted = _flat_review_get(children);
+         GList *me = g_list_find(sorted, current);
+         if (me)
+           ret = me->prev ? me->prev->data : NULL;
+
+         if (sorted) g_list_free(sorted);
+         _atspi_children_list_free(children);
+
+         return ret;
+      }
+    return NULL;
+}
+
+AtspiAccessible *structural_navi_level_up(AtspiAccessible *current)
+{
+    AtspiAccessible *parent;
+    AtspiRole role;
+
+    parent = atspi_accessible_get_parent(current, NULL);
+    if (!parent) return NULL;
+
+    role = atspi_accessible_get_role(parent, NULL);
+    if (role != ATSPI_ROLE_DESKTOP_FRAME)
+      {
+         return parent;
+      }
+    return NULL;
+}
+
+AtspiAccessible *structural_navi_level_down(AtspiAccessible *current)
+{
+   AtspiAccessible *ret;
+
+   GList *children = _atspi_children_list_get(current);
+   GList *sorted = _flat_review_get(children);
+
+   ret = sorted ? sorted->data : NULL;
+
+   if (sorted) g_list_free(sorted);
+   _atspi_children_list_free(children);
+
+   return ret;
+}
+
+static AtspiAccessible*
+_navi_app_chain_next(AtspiAccessible *current, AtspiRelationType search_type)
+{
+   GArray *relations;
+   AtspiAccessible *ret = NULL;
+   AtspiRelation *relation;
+   AtspiRelationType type;
+   int i;
+
+   relations = atspi_accessible_get_relation_set(current, NULL);
+
+    for (i = 0; i < relations->len; i++)
+      {
+         relation = g_array_index (relations, AtspiRelation*, i);
+         type = atspi_relation_get_relation_type(relation);
+
+         if (type == search_type)
+           {
+              ret = atspi_relation_get_target(relation, 0);
+              break;
+           }
+      }
+
+   g_array_free(relations, TRUE);
+   return ret;
+}
+
+AtspiAccessible *structural_navi_app_chain_next(AtspiAccessible *current)
+{
+   return _navi_app_chain_next(current, ATSPI_RELATION_FLOWS_TO);
+}
+
+AtspiAccessible *structural_navi_app_chain_prev(AtspiAccessible *current)
+{
+   return _navi_app_chain_next(current, ATSPI_RELATION_FLOWS_FROM);
+}
diff --git a/src/window_tracker.c b/src/window_tracker.c
new file mode 100644 (file)
index 0000000..608a486
--- /dev/null
@@ -0,0 +1,90 @@
+#include <string.h>
+#include "window_tracker.h"
+#include "logger.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)
+{
+   if (!strcmp(event->type, "window:activate") ||
+       !strcmp(event->type, "window:restore"))
+     {
+        if (last_active_win != event->source)
+            {
+              if (user_cb) user_cb(user_data, event->source);
+              last_active_win = event->source;
+            }
+     }
+   else if (!strcmp(event->type, "window:deactivate") ||
+            !strcmp(event->type, "window:destroy"))
+     {
+        if ((last_active_win == event->source) &&
+            user_cb)
+          user_cb(user_data, NULL);
+        last_active_win = NULL;
+     }
+}
+
+static AtspiAccessible*
+_get_active_win(void)
+{
+   int i, j;
+   last_active_win = NULL;
+   AtspiAccessible *desktop = atspi_get_desktop(0);
+
+   for (i = 0; i < atspi_accessible_get_child_count(desktop, NULL); i++) {
+       AtspiAccessible *app = atspi_accessible_get_child_at_index(desktop, i, NULL);
+       for (j = 0; j < atspi_accessible_get_child_count(app, NULL); 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))
+            {
+               g_object_unref(states);
+               last_active_win = win;
+               break;
+            }
+          g_object_unref(states);
+       }
+   }
+   return last_active_win;
+}
+
+void window_tracker_init(void)
+{
+   listener = atspi_event_listener_new_simple(_on_atspi_window_cb, NULL);
+   atspi_event_listener_register(listener, "window:activate", NULL);
+   atspi_event_listener_register(listener, "window:deactivate", NULL);
+   atspi_event_listener_register(listener, "window:restore", NULL);
+   atspi_event_listener_register(listener, "window:destroy", NULL);
+}
+
+void window_tracker_shutdown(void)
+{
+   atspi_event_listener_deregister(listener, "window:activate", NULL);
+   atspi_event_listener_deregister(listener, "window:deactivate", NULL);
+   atspi_event_listener_deregister(listener, "window:restore", NULL);
+   atspi_event_listener_deregister(listener, "window:destroy", 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)
+{
+   user_cb = cb;
+   user_data = data;
+}
+
+void window_tracker_active_window_request(void)
+{
+   _get_active_win();
+   if (user_cb) user_cb(user_data, last_active_win);
+}
diff --git a/tests/CMakeLists.sub b/tests/CMakeLists.sub
new file mode 100755 (executable)
index 0000000..47fcac2
--- /dev/null
@@ -0,0 +1,26 @@
+## PROJECT NAME
+PROJECT(smart-navigator-tests C)
+
+## INCLUDES
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
+
+## DEFINITIONS
+ADD_DEFINITIONS("")
+
+## LIBRARY PATH
+SET(SLP_LD_PATH_FLAGS "")
+
+## LIBRARY
+SET(SLP_LD_FLAGS "")
+
+## DEBUG
+SET(SLP_DEBUG_FLAGS "-g")
+
+## OPTIMIZATION
+SET(SLP_OPT_FLAGS "-O0")
+
+## COMPILER FLAGS
+SET(SLP_COMPILER_FLAGS "-Wall -Wunused")
+
+## LINKER FLAGS
+SET(SLP_LINKER_FLAGS "")
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9673664
--- /dev/null
@@ -0,0 +1,39 @@
+
+INCLUDE_DIRECTORIES(.)
+add_library(atspi
+            atspi/atspi.h
+            atspi/atspi.c
+            )
+
+INCLUDE(CMakeLists.sub)
+set_target_properties(atspi PROPERTIES
+         LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/atspi)
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(tests REQUIRED
+    gobject-2.0
+    dlog
+    dbus-1
+    tts
+    check
+    vconf
+)
+
+FOREACH(flag ${tests_CFLAGS})
+       SET(EXTRA_CFLAGS_TESTS "${EXTRA_CFLAGS_TESTS} ${flag}")
+ENDFOREACH(flag)
+
+
+SET(CMAKE_C_FLAGS "${SLP_DEBUG_FLAGS} ${SLP_OPT_FLAGS} ${CMAKE_C_FLAGS_DUMP} ${EXTRA_CFLAGS_TESTS} ${SLP_COMPILER_FLAGS}")
+SET(CMAKE_CXX_FLAGS "${SLP_DEBUG_FLAGS} ${SLP_OPT_FLAGS} ${CMAKE_CXX_FLAGS_DUMP} ${EXTRA_CFLAGS_TESTS} ${SLP_COMPILER_FLAGS}")
+
+SET(TESTED_SRCS ${CMAKE_SOURCE_DIR}/src/screen_reader.c
+                ${CMAKE_SOURCE_DIR}/src/screen_reader_vconf.c
+                ${CMAKE_SOURCE_DIR}/src/screen_reader_spi.c
+                ${CMAKE_SOURCE_DIR}/src/screen_reader_tts.c
+                ${CMAKE_SOURCE_DIR}/src/flat_navi.c
+                ${CMAKE_SOURCE_DIR}/src/position_sort.c
+                ${CMAKE_SOURCE_DIR}/src/object_cache.c)
+
+ADD_EXECUTABLE(screen_reader_test_suite screen_reader_test_suite.c ${TESTED_SRCS})
+TARGET_LINK_LIBRARIES(screen_reader_test_suite atspi ${tests_LDFLAGS} ${SLP_LD_PATH_FLAGS} ${SLP_LD_FLAGS} ${SLP_LINKER_FLAGS})
diff --git a/tests/atspi/atspi.c b/tests/atspi/atspi.c
new file mode 100644 (file)
index 0000000..80bd00c
--- /dev/null
@@ -0,0 +1,406 @@
+
+#include "atspi.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static AtspiValue *value = NULL;
+static AtspiText *text = NULL;
+static AtspiEditableText *editable_text = NULL;
+//static AtspiComponent *component = NULL;
+
+G_DEFINE_TYPE(AtspiAccessible, atspi_accessible, G_TYPE_OBJECT);
+G_DEFINE_TYPE(AtspiComponent, atspi_component, G_TYPE_OBJECT);
+G_DEFINE_TYPE(AtspiStateSet, atspi_state_set, G_TYPE_OBJECT);
+
+void atspi_rect_free (AtspiRect *rect)
+{
+  g_free (rect);
+}
+
+AtspiRect *atspi_rect_copy (AtspiRect *src)
+{
+  AtspiRect *dst = g_new (AtspiRect, 1);
+  dst->x = src->x;
+  dst->y = src->y;
+  dst->height = src->height;
+  dst->width = src->width;
+  return dst;
+}
+
+G_DEFINE_BOXED_TYPE (AtspiRect, atspi_rect, atspi_rect_copy, atspi_rect_free)
+
+void atspi_alloc_memory()
+{
+    value = (AtspiValue*)malloc(sizeof(AtspiValue));
+    text = (AtspiText*)malloc(sizeof(AtspiText));
+    editable_text = (AtspiEditableText*)malloc(sizeof(AtspiEditableText));
+}
+
+void atspi_free_memory(void)
+{
+    free(editable_text);
+    free(value);
+    free(text);
+}
+
+gchar * atspi_accessible_get_name (AtspiAccessible *obj, GError **error)
+{
+    if(!obj || !obj->name) return strdup("\0");
+
+    return strdup(obj->name);
+}
+
+gchar * atspi_accessible_get_role_name (AtspiAccessible *obj, GError **error)
+{
+    if(!obj) return strdup("\0");
+    AtspiRole role = obj->role;
+    switch(role)
+    {
+        case ATSPI_ROLE_APPLICATION:
+            return strdup("Application");
+        case ATSPI_ROLE_PUSH_BUTTON:
+            return strdup("Push button");
+        case ATSPI_ROLE_ICON:
+            return strdup("Icon");
+        case ATSPI_ROLE_CHECK_BOX:
+            return strdup("Check box");
+        case ATSPI_ROLE_ENTRY:
+            return strdup("Entry");
+        case ATSPI_ROLE_FILLER:
+            return strdup("filler");
+        default:
+            return strdup("\0");
+    }
+}
+
+gchar * atspi_accessible_get_toolkit_name (AtspiAccessible *obj, GError **error)
+{
+    return "fake atspi";
+}
+
+gchar * atspi_accessible_get_description (AtspiAccessible *obj, GError **error)
+{
+    if(!obj || !obj->description) return strdup("\0");
+    return strdup(obj->description);
+}
+
+AtspiText * atspi_accessible_get_text (AtspiAccessible *obj)
+{
+    if(!obj) return NULL;
+    return text;
+}
+
+gint atspi_text_get_character_count (AtspiText *obj, GError **error)
+{
+    if(!obj) return -1;
+    return 6;
+}
+
+gint atspi_text_get_caret_offset (AtspiText *obj, GError **error)
+{
+    if(!obj) return -1;
+    return 5;
+}
+
+gchar * atspi_text_get_text (AtspiText *obj, gint start_offset, gint end_offset, GError **error)
+{
+    if(!obj) return NULL;
+    return "AtspiText text";
+}
+
+AtspiValue * atspi_accessible_get_value (AtspiAccessible *obj)
+{
+    if(!obj) return NULL;
+    return value;
+}
+
+gdouble atspi_value_get_current_value (AtspiValue *obj, GError **error)
+{
+    return 1.0;
+}
+
+gdouble atspi_value_get_maximum_value (AtspiValue *obj, GError **error)
+{
+    return 2.0;
+}
+
+gdouble atspi_value_get_minimum_value (AtspiValue *obj, GError **error)
+{
+    return 0.0;
+}
+
+AtspiEventListener *atspi_event_listener_new (AtspiEventListenerCB callback,
+                                                gpointer user_data,
+                                                GDestroyNotify callback_destroyed)
+{
+    return NULL;
+}
+
+gboolean atspi_event_listener_register (AtspiEventListener *listener,
+                                        const gchar *event_type,
+                                        GError **error)
+{
+    return FALSE;
+}
+
+gboolean atspi_event_listener_deregister (AtspiEventListener *listener,
+                                          const gchar *event_type,
+                                          GError **error)
+{
+    return FALSE;
+}
+
+AtspiStateSet * atspi_accessible_get_state_set (AtspiAccessible *obj)
+{
+    if (!obj || !obj->states) return NULL;
+    return obj->states;
+}
+
+gboolean atspi_state_set_contains (AtspiStateSet *set, AtspiStateType state)
+{
+    if (!set)
+      return FALSE;
+    return (set->states & ((gint64)1 << state)) ? TRUE : FALSE;
+}
+
+void atspi_state_set_add (AtspiStateSet *set, AtspiStateType state)
+{
+    if (!set) return;
+    set->states |= (((gint64)1) << state);
+}
+
+gboolean atspi_component_grab_highlight (AtspiComponent *obj, GError **error)
+{
+    return FALSE;
+}
+
+AtspiScrollable *atspi_accessible_get_scrollable (AtspiAccessible *accessible)
+{
+    return NULL;
+}
+
+gboolean atspi_component_clear_highlight (AtspiComponent *obj, GError **error)
+{
+    return FALSE;
+}
+
+GArray *atspi_state_set_get_states (AtspiStateSet *set)
+{
+  gint i = 0;
+  guint64 val = 1;
+  GArray *ret;
+
+  g_return_val_if_fail (set != NULL, NULL);
+  ret = g_array_new (TRUE, TRUE, sizeof (AtspiStateType));
+  if (!ret)
+    return NULL;
+  for (i = 0; i < 64; i++)
+  {
+    if (set->states & val)
+      ret = g_array_append_val (ret, i);
+    val <<= 1;
+  }
+  return ret;
+
+}
+
+AtspiRole atspi_accessible_get_role (AtspiAccessible *obj, GError **error)
+{
+    if(!obj) return ATSPI_ROLE_INVALID;
+    return obj->role;
+}
+
+gint atspi_accessible_get_child_count (AtspiAccessible *obj, GError **error)
+{
+    if(!obj || !obj->children) return 0;
+    return g_list_length(obj->children);
+}
+
+AtspiAccessible * atspi_accessible_get_child_at_index (AtspiAccessible *obj, gint child_index, GError **error)
+{
+    if(!obj || child_index >= g_list_length(obj->children)) return NULL;
+    return g_object_ref(g_list_nth_data(obj->children, child_index));
+}
+
+AtspiComponent * atspi_accessible_get_component (AtspiAccessible *obj)
+{
+    if(!obj) return NULL;
+    AtspiComponent *component = g_object_new(ATSPI_COMPONENT_OBJECT_TYPE, 0);
+    *(component->role) = obj->role;
+    return component;
+}
+
+AtspiStateSet * atspi_state_set_new (GArray *states)
+{
+    AtspiStateSet *set = g_object_new (ATSPI_STATE_OBJECT_TYPE, NULL);
+    if (!set) return NULL;
+    int i;
+    for (i = 0; i < states->len; i++) {
+      atspi_state_set_add (set, g_array_index (states, AtspiStateType, i));
+    }
+    return set;
+}
+
+AtspiRect *atspi_component_get_extents (AtspiComponent *component, AtspiCoordType ctype, GError **error)
+{
+    if(!component) return NULL;
+
+    AtspiRect rect;
+    if(*(component->role) == ATSPI_ROLE_APPLICATION)
+    {
+        rect.x = 0;
+        rect.y = 0;
+        rect.width = 100;
+        rect.height = 100;
+    }
+    else if(*(component->role) == ATSPI_ROLE_PUSH_BUTTON)
+    {
+        rect.x = 0;
+        rect.y = 0;
+        rect.width = 50;
+        rect.height = 50;
+    }
+    else if(*(component->role) == ATSPI_ROLE_ICON)
+    {
+        rect.x = 50;
+        rect.y = 0;
+        rect.width = 50;
+        rect.height = 50;
+    }
+    else if(*(component->role) == ATSPI_ROLE_CHECK_BOX)
+    {
+        rect.x = 0;
+        rect.y = 50;
+        rect.width = 50;
+        rect.height = 50;
+    }
+    else if(*(component->role) == ATSPI_ROLE_ENTRY)
+    {
+        rect.x = 50;
+        rect.y = 50;
+        rect.width = 50;
+        rect.height = 50;
+    }
+    else
+    {
+        rect.x = 0;
+        rect.y = 0;
+        rect.width = 0;
+        rect.height = 0;
+    }
+    return atspi_rect_copy(&rect);
+}
+
+AtspiAccessible *atspi_create_accessible()
+{
+    AtspiAccessible *obj = g_object_new(ATSPI_ACCESSIBLE_OBJECT_TYPE, 0);
+    obj->children = NULL;
+
+    GArray *states = g_array_new (TRUE, TRUE, sizeof (AtspiStateType));
+    AtspiStateType s[] = {
+         ATSPI_STATE_VISIBLE,
+         ATSPI_STATE_SHOWING,
+         ATSPI_STATE_LAST_DEFINED
+    };
+    int i;
+    for (i=0; i<sizeof(s); i++)
+      g_array_append_val (states, s[i]);
+    obj->states = atspi_state_set_new(states);
+
+    return obj;
+}
+
+void atspi_delete_accessible(AtspiAccessible *obj)
+{
+    if(!obj) return;
+    if(obj->children)
+    {
+        atpis_accessible_remove_children(obj);
+    }
+    g_object_unref(obj);
+}
+
+void atspi_accessible_add_child(AtspiAccessible *obj, AtspiAccessible *child)
+{
+    obj->children = g_list_append(obj->children, child);
+}
+
+void atpis_accessible_remove_children(AtspiAccessible *obj)
+{
+    GList *l = obj->children;
+    while (l != NULL)
+    {
+        GList *next = l->next;
+        if(l->data)
+        {
+            atspi_delete_accessible(l->data);
+        }
+        l = next;
+    }
+    g_list_free(obj->children);
+}
+
+static void atspi_state_set_init(AtspiStateSet* set)
+{
+}
+
+static void atspi_state_set_class_init(AtspiStateSetClass *_class)
+{
+}
+
+static void atspi_accessible_class_init(AtspiAccessibleClass *_class)
+{
+}
+
+static void atspi_accessible_init(AtspiAccessible* obj)
+{
+}
+
+static void atspi_component_finalize(GObject *obj)
+{
+    AtspiComponent *component = (AtspiComponent*)obj;
+    free(component->role);
+}
+
+static void atspi_component_class_init(AtspiComponentClass *class)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+    gobject_class->finalize = atspi_component_finalize;
+}
+
+static void atspi_component_init(AtspiComponent* obj)
+{
+    obj->role = (AtspiRole*)malloc(sizeof(AtspiRole));
+}
+
+AtspiEditableText * atspi_accessible_get_editable_text (AtspiAccessible *obj)
+{
+    return editable_text;
+}
+
+GArray * atspi_accessible_get_relation_set (AtspiAccessible *obj, GError **error)
+{
+    return NULL;
+}
+
+AtspiRelationType atspi_relation_get_relation_type (AtspiRelation *obj)
+{
+    return ATSPI_RELATION_NULL;
+}
+
+gint atspi_relation_get_n_targets (AtspiRelation *obj)
+{
+    return 0;
+}
+
+AtspiAccessible * atspi_relation_get_target (AtspiRelation *obj, gint i)
+{
+    return NULL;
+}
+
+int atspi_exit(void)
+{
+   return 1;
+}
diff --git a/tests/atspi/atspi.h b/tests/atspi/atspi.h
new file mode 100644 (file)
index 0000000..089f68f
--- /dev/null
@@ -0,0 +1,400 @@
+
+
+#ifndef __ATSPI_H__
+#define __ATSPI_H__
+
+#include <glib-2.0/glib.h>
+#include <glib-2.0/glib-object.h>
+#include <dbus/dbus.h>
+
+#define ATSPI_ACCESSIBLE_OBJECT_TYPE                (atspi_accessible_get_type ())
+#define ATSPI_ACCESSIBLE(obj)                       (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATSPI_ACCESSIBLE_OBJECT_TYPE, AtspiAccessible))
+#define ATSPI_ACESSIBLE_IS_OBJECT(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATSPI_ACCESSIBLE_OBJECT_TYPE))
+#define ATSPI_ACCESSIBLE_CLASS(_class)              (G_TYPE_CHECK_CLASS_CAST ((_class), ATSPI_ACCESSIBLE_OBJECT_TYPE, AtspiAccessibleClass))
+#define ATSPI_ACCESSIBLE_IS_OBJECT_CLASS(_class)    (G_TYPE_CHECK_CLASS_TYPE ((_class), ATSPI_ACCESSIBLE_OBJECT_TYPE))
+#define ATSPI_ACCESSIBLE_GET_CLASS(obj)             (G_TYPE_INSTANCE_GET_CLASS ((obj), ATSPI_ACCESSIBLE_OBJECT_TYPE, AtspiAccessibleClass))
+
+#define ATSPI_COMPONENT_OBJECT_TYPE                 (atspi_component_get_type ())
+#define ATSPI_COMPONENT(obj)                        (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATSPI_COMPONENT_OBJECT_TYPE, AtspiAccessible))
+#define ATSPI_COMPONENT_IS_OBJECT(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATSPI_COMPONENT_OBJECT_TYPE))
+#define ATSPI_COMPONENT_CLASS(_class)               (G_TYPE_CHECK_CLASS_CAST ((_class), ATSPI_COMPONENT_OBJECT_TYPE, AtspiAccessibleClass))
+#define ATSPI_COMPONENT_IS_OBJECT_CLASS(_class)     (G_TYPE_CHECK_CLASS_TYPE ((_class), ATSPI_COMPONENT_OBJECT_TYPE))
+#define ATSPI_COMPONENT_GET_CLASS(obj)              (G_TYPE_INSTANCE_GET_CLASS ((obj), ATSPI_COMPONENT_OBJECT_TYPE, AtspiAccessibleClass))
+
+#define ATSPI_STATE_OBJECT_TYPE                     (atspi_state_set_get_type ())
+#define ATSPI_STATE(obj)                            (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATSPI_STATE_OBJECT_TYPE, AtspiStateSet))
+#define ATSPI_STATE_IS_OBJECT(obj)                  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATSPI_STATE_OBJECT_TYPE))
+#define ATSPI_STATE_CLASS(_class)                   (G_TYPE_CHECK_CLASS_CAST ((_class), ATSPI_STATE_OBJECT_TYPE, AtspiStateSetClass))
+#define ATSPI_STATE_IS_OBJECT_CLASS(_class)         (G_TYPE_CHECK_CLASS_TYPE ((_class), ATSPI_STATE_OBJECT_TYPE))
+#define ATSPI_STATE_GET_CLASS(obj)                  (G_TYPE_INSTANCE_GET_CLASS ((obj), ATSPI_STATE_OBJECT_TYPE, AtspiStateSetClass))
+
+
+typedef struct _AtspiApplication AtspiApplication;
+typedef struct _AtspiObject AtspiObject;
+typedef struct _AtspiAccessible AtspiAccessible;
+typedef struct _AtspiEvent AtspiEvent;
+typedef struct _AtspiStateSet AtspiStateSet;
+typedef struct _AtspiEventListener AtspiEventListener;
+typedef struct _AtspiText AtspiText;
+typedef struct _AtspiValue AtspiValue;
+typedef struct _AtspiComponent AtspiComponent;
+typedef struct _AtspiScrollable AtspiScrollable;
+typedef struct _AtspiRect AtspiRect;
+typedef struct _AtspiEditableText AtspiEditableText;
+typedef struct _AtspiRelation AtspiRelation;
+
+typedef struct _AtspiAccessibleClass AtspiAccessibleClass;
+typedef struct _AtspiComponentClass AtspiComponentClass;
+typedef struct _AtspiStateSetClass AtspiStateSetClass;
+
+typedef void (*AtspiEventListenerCB) (AtspiEvent *event, void *user_data);
+
+typedef enum
+{
+  ATSPI_CACHE_NONE     = 0,
+  ATSPI_CACHE_PARENT   = 1 << 0,
+  ATSPI_CACHE_CHILDREN    = 1 << 1,
+  ATSPI_CACHE_NAME        = 1 << 2,
+  ATSPI_CACHE_DESCRIPTION = 1 << 3,
+  ATSPI_CACHE_STATES      = 1 << 4,
+  ATSPI_CACHE_ROLE        = 1 << 5,
+  ATSPI_CACHE_INTERFACES  = 1 << 6,
+  ATSPI_CACHE_ATTRIBUTES = 1 << 7,
+  ATSPI_CACHE_ALL         = 0x3fffffff,
+  ATSPI_CACHE_DEFAULT = ATSPI_CACHE_PARENT | ATSPI_CACHE_CHILDREN |
+                        ATSPI_CACHE_NAME | ATSPI_CACHE_DESCRIPTION |
+                        ATSPI_CACHE_STATES | ATSPI_CACHE_ROLE |
+                        ATSPI_CACHE_INTERFACES,
+  ATSPI_CACHE_UNDEFINED   = 0x40000000,
+} AtspiCache;
+
+typedef enum {
+    ATSPI_ROLE_INVALID,
+    ATSPI_ROLE_ACCELERATOR_LABEL,
+    ATSPI_ROLE_ALERT,
+    ATSPI_ROLE_ANIMATION,
+    ATSPI_ROLE_ARROW,
+    ATSPI_ROLE_CALENDAR,
+    ATSPI_ROLE_CANVAS,
+    ATSPI_ROLE_CHECK_BOX,
+    ATSPI_ROLE_CHECK_MENU_ITEM,
+    ATSPI_ROLE_COLOR_CHOOSER,
+    ATSPI_ROLE_COLUMN_HEADER,
+    ATSPI_ROLE_COMBO_BOX,
+    ATSPI_ROLE_DATE_EDITOR,
+    ATSPI_ROLE_DESKTOP_ICON,
+    ATSPI_ROLE_DESKTOP_FRAME,
+    ATSPI_ROLE_DIAL,
+    ATSPI_ROLE_DIALOG,
+    ATSPI_ROLE_DIRECTORY_PANE,
+    ATSPI_ROLE_DRAWING_AREA,
+    ATSPI_ROLE_FILE_CHOOSER,
+    ATSPI_ROLE_FILLER,
+    ATSPI_ROLE_FOCUS_TRAVERSABLE,
+    ATSPI_ROLE_FONT_CHOOSER,
+    ATSPI_ROLE_FRAME,
+    ATSPI_ROLE_GLASS_PANE,
+    ATSPI_ROLE_HTML_CONTAINER,
+    ATSPI_ROLE_ICON,
+    ATSPI_ROLE_IMAGE,
+    ATSPI_ROLE_INTERNAL_FRAME,
+    ATSPI_ROLE_LABEL,
+    ATSPI_ROLE_LAYERED_PANE,
+    ATSPI_ROLE_LIST,
+    ATSPI_ROLE_LIST_ITEM,
+    ATSPI_ROLE_MENU,
+    ATSPI_ROLE_MENU_BAR,
+    ATSPI_ROLE_MENU_ITEM,
+    ATSPI_ROLE_OPTION_PANE,
+    ATSPI_ROLE_PAGE_TAB,
+    ATSPI_ROLE_PAGE_TAB_LIST,
+    ATSPI_ROLE_PANEL,
+    ATSPI_ROLE_PASSWORD_TEXT,
+    ATSPI_ROLE_POPUP_MENU,
+    ATSPI_ROLE_PROGRESS_BAR,
+    ATSPI_ROLE_PUSH_BUTTON,
+    ATSPI_ROLE_RADIO_BUTTON,
+    ATSPI_ROLE_RADIO_MENU_ITEM,
+    ATSPI_ROLE_ROOT_PANE,
+    ATSPI_ROLE_ROW_HEADER,
+    ATSPI_ROLE_SCROLL_BAR,
+    ATSPI_ROLE_SCROLL_PANE,
+    ATSPI_ROLE_SEPARATOR,
+    ATSPI_ROLE_SLIDER,
+    ATSPI_ROLE_SPIN_BUTTON,
+    ATSPI_ROLE_SPLIT_PANE,
+    ATSPI_ROLE_STATUS_BAR,
+    ATSPI_ROLE_TABLE,
+    ATSPI_ROLE_TABLE_CELL,
+    ATSPI_ROLE_TABLE_COLUMN_HEADER,
+    ATSPI_ROLE_TABLE_ROW_HEADER,
+    ATSPI_ROLE_TEAROFF_MENU_ITEM,
+    ATSPI_ROLE_TERMINAL,
+    ATSPI_ROLE_TEXT,
+    ATSPI_ROLE_TOGGLE_BUTTON,
+    ATSPI_ROLE_TOOL_BAR,
+    ATSPI_ROLE_TOOL_TIP,
+    ATSPI_ROLE_TREE,
+    ATSPI_ROLE_TREE_TABLE,
+    ATSPI_ROLE_UNKNOWN,
+    ATSPI_ROLE_VIEWPORT,
+    ATSPI_ROLE_WINDOW,
+    ATSPI_ROLE_EXTENDED,
+    ATSPI_ROLE_HEADER,
+    ATSPI_ROLE_FOOTER,
+    ATSPI_ROLE_PARAGRAPH,
+    ATSPI_ROLE_RULER,
+    ATSPI_ROLE_APPLICATION,
+    ATSPI_ROLE_AUTOCOMPLETE,
+    ATSPI_ROLE_EDITBAR,
+    ATSPI_ROLE_EMBEDDED,
+    ATSPI_ROLE_ENTRY,
+    ATSPI_ROLE_CHART,
+    ATSPI_ROLE_CAPTION,
+    ATSPI_ROLE_DOCUMENT_FRAME,
+    ATSPI_ROLE_HEADING,
+    ATSPI_ROLE_PAGE,
+    ATSPI_ROLE_SECTION,
+    ATSPI_ROLE_REDUNDANT_OBJECT,
+    ATSPI_ROLE_FORM,
+    ATSPI_ROLE_LINK,
+    ATSPI_ROLE_INPUT_METHOD_WINDOW,
+    ATSPI_ROLE_TABLE_ROW,
+    ATSPI_ROLE_TREE_ITEM,
+    ATSPI_ROLE_DOCUMENT_SPREADSHEET,
+    ATSPI_ROLE_DOCUMENT_PRESENTATION,
+    ATSPI_ROLE_DOCUMENT_TEXT,
+    ATSPI_ROLE_DOCUMENT_WEB,
+    ATSPI_ROLE_DOCUMENT_EMAIL,
+    ATSPI_ROLE_COMMENT,
+    ATSPI_ROLE_LIST_BOX,
+    ATSPI_ROLE_GROUPING,
+    ATSPI_ROLE_IMAGE_MAP,
+    ATSPI_ROLE_NOTIFICATION,
+    ATSPI_ROLE_INFO_BAR,
+    ATSPI_ROLE_LEVEL_BAR,
+    ATSPI_ROLE_LAST_DEFINED,
+} AtspiRole;
+
+typedef enum {
+    ATSPI_STATE_INVALID,
+    ATSPI_STATE_ACTIVE,
+    ATSPI_STATE_ARMED,
+    ATSPI_STATE_BUSY,
+    ATSPI_STATE_CHECKED,
+    ATSPI_STATE_COLLAPSED,
+    ATSPI_STATE_DEFUNCT,
+    ATSPI_STATE_EDITABLE,
+    ATSPI_STATE_ENABLED,
+    ATSPI_STATE_EXPANDABLE,
+    ATSPI_STATE_EXPANDED,
+    ATSPI_STATE_FOCUSABLE,
+    ATSPI_STATE_FOCUSED,
+    ATSPI_STATE_HAS_TOOLTIP,
+    ATSPI_STATE_HORIZONTAL,
+    ATSPI_STATE_ICONIFIED,
+    ATSPI_STATE_MODAL,
+    ATSPI_STATE_MULTI_LINE,
+    ATSPI_STATE_MULTISELECTABLE,
+    ATSPI_STATE_OPAQUE,
+    ATSPI_STATE_PRESSED,
+    ATSPI_STATE_RESIZABLE,
+    ATSPI_STATE_SELECTABLE,
+    ATSPI_STATE_SELECTED,
+    ATSPI_STATE_SENSITIVE,
+    ATSPI_STATE_SHOWING,
+    ATSPI_STATE_SINGLE_LINE,
+    ATSPI_STATE_STALE,
+    ATSPI_STATE_TRANSIENT,
+    ATSPI_STATE_VERTICAL,
+    ATSPI_STATE_VISIBLE,
+    ATSPI_STATE_MANAGES_DESCENDANTS,
+    ATSPI_STATE_INDETERMINATE,
+    ATSPI_STATE_REQUIRED,
+    ATSPI_STATE_TRUNCATED,
+    ATSPI_STATE_ANIMATED,
+    ATSPI_STATE_INVALID_ENTRY,
+    ATSPI_STATE_SUPPORTS_AUTOCOMPLETION,
+    ATSPI_STATE_SELECTABLE_TEXT,
+    ATSPI_STATE_IS_DEFAULT,
+    ATSPI_STATE_VISITED,
+    ATSPI_STATE_HIGHLIGHTED,
+    ATSPI_STATE_LAST_DEFINED,
+} AtspiStateType;
+
+typedef enum
+{
+    ATSPI_RELATION_NULL,
+    ATSPI_RELATION_LABEL_FOR,
+    ATSPI_RELATION_LABELLED_BY,
+} AtspiRelationType;
+
+struct _AtspiApplication
+{
+  GObject parent;
+  GHashTable *hash;
+  char *bus_name;
+  DBusConnection *bus;
+  AtspiAccessible *root;
+  AtspiCache cache;
+  gchar *toolkit_name;
+  gchar *toolkit_version;
+  gchar *atspi_version;
+  struct timeval time_added;
+};
+
+struct _AtspiObject
+{
+    GObject parent;
+    AtspiApplication *app;
+    char *path;
+};
+
+struct _AtspiAccessible
+{
+    //GObject parent;
+    AtspiObject parent;
+    AtspiAccessible *accessible_parent;
+    GList *children;
+    AtspiRole role;
+    gint interfaces;
+    char *name;
+    char *description;
+    AtspiStateSet *states;
+    GHashTable *attributes;
+    guint cached_properties;
+};
+
+struct _AtspiAccessibleClass
+{
+    GObjectClass parent_class;
+};
+
+struct _AtspiComponentClass
+{
+    GObjectClass parent_class;
+};
+
+struct _AtspiEvent
+{
+    gchar  *type;
+    AtspiAccessible  *source;
+    gint         detail1;
+    gint         detail2;
+    GValue any_data;
+};
+
+struct _AtspiStateSet
+{
+  GObject parent;
+  struct _AtspiAccessible *accessible;
+  gint64 states;
+};
+
+struct _AtspiStateSetClass
+{
+    GObjectClass parent_class;
+};
+
+struct _AtspiEventListener
+{
+  GObject parent;
+  AtspiEventListenerCB callback;
+  void *user_data;
+  GDestroyNotify cb_destroyed;
+};
+
+struct _AtspiText
+{
+    GTypeInterface parent;
+};
+
+struct _AtspiEditableText
+{
+    GTypeInterface parent;
+};
+
+struct _AtspiValue
+{
+    GTypeInterface parent;
+};
+
+struct _AtspiComponent
+{
+  GTypeInterface parent;
+  AtspiRole *role;
+};
+
+struct _AtspiScrollable
+{
+    GTypeInterface parent;
+};
+
+struct _AtspiRelation
+{
+    GTypeInterface parent;
+};
+
+struct _AtspiRect
+{
+  gint x;
+  gint y;
+  gint width;
+  gint height;
+};
+
+typedef enum {
+    ATSPI_COORD_TYPE_SCREEN,
+    ATSPI_COORD_TYPE_WINDOW,
+} AtspiCoordType;
+
+gchar * atspi_accessible_get_name (AtspiAccessible *obj, GError **error);
+gchar * atspi_accessible_get_role_name (AtspiAccessible *obj, GError **error);
+gchar * atspi_accessible_get_toolkit_name (AtspiAccessible *obj, GError **error);
+gchar * atspi_accessible_get_description (AtspiAccessible *obj, GError **error);
+AtspiText * atspi_accessible_get_text (AtspiAccessible *obj);
+gint atspi_text_get_character_count (AtspiText *obj, GError **error);
+gint atspi_text_get_caret_offset (AtspiText *obj, GError **error);
+gchar * atspi_text_get_text (AtspiText *obj, gint start_offset, gint end_offset, GError **error);
+AtspiValue * atspi_accessible_get_value (AtspiAccessible *obj);
+gdouble atspi_value_get_current_value (AtspiValue *obj, GError **error);
+gdouble atspi_value_get_maximum_value (AtspiValue *obj, GError **error);
+gdouble atspi_value_get_minimum_value (AtspiValue *obj, GError **error);
+AtspiEventListener *atspi_event_listener_new (AtspiEventListenerCB callback,
+                                                gpointer user_data,
+                                                GDestroyNotify callback_destroyed);
+gboolean atspi_event_listener_register (AtspiEventListener *listener,
+                                        const gchar *event_type,
+                                        GError **error);
+gboolean atspi_event_listener_deregister (AtspiEventListener *listener,
+                                          const gchar *event_type,
+                                          GError **error);
+AtspiStateSet * atspi_accessible_get_state_set (AtspiAccessible *obj);
+gboolean atspi_state_set_contains (AtspiStateSet *set, AtspiStateType state);
+void atspi_state_set_add (AtspiStateSet *set, AtspiStateType state);
+GArray *atspi_state_set_get_states (AtspiStateSet *set);
+AtspiStateSet * atspi_state_set_new (GArray *states);
+
+void atspi_alloc_memory(void);
+
+void atspi_free_memory(void);
+gboolean atspi_component_grab_highlight (AtspiComponent *obj, GError **error);
+AtspiScrollable *atspi_accessible_get_scrollable (AtspiAccessible *accessible);
+gboolean atspi_component_clear_highlight (AtspiComponent *obj, GError **error);
+AtspiRole atspi_accessible_get_role (AtspiAccessible *obj, GError **error);
+gint atspi_accessible_get_child_count (AtspiAccessible *obj, GError **error);
+AtspiAccessible * atspi_accessible_get_child_at_index (AtspiAccessible *obj, gint child_index, GError **error);
+AtspiComponent * atspi_accessible_get_component (AtspiAccessible *obj);
+AtspiRect *atspi_component_get_extents (AtspiComponent *obj, AtspiCoordType ctype, GError **error);
+AtspiAccessible *atspi_create_accessible(void);
+void atspi_delete_accessible(AtspiAccessible *obj);
+void atspi_accessible_add_child(AtspiAccessible *obj, AtspiAccessible *child);
+void atpis_accessible_remove_children(AtspiAccessible *obj);
+AtspiEditableText * atspi_accessible_get_editable_text (AtspiAccessible *obj);
+GArray * atspi_accessible_get_relation_set (AtspiAccessible *obj, GError **error);
+AtspiRelationType atspi_relation_get_relation_type (AtspiRelation *obj);
+gint atspi_relation_get_n_targets (AtspiRelation *obj);
+AtspiAccessible * atspi_relation_get_target (AtspiRelation *obj, gint i);
+int atspi_exit(void);
+
+#endif /*__ATSPI_H__*/
diff --git a/tests/screen_reader_test_suite.c b/tests/screen_reader_test_suite.c
new file mode 100644 (file)
index 0000000..ecef9a2
--- /dev/null
@@ -0,0 +1,468 @@
+
+#include "screen_reader_spi.h"
+#include "flat_navi.h"
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <atspi/atspi.h>
+
+static AtspiAccessible *root;
+static AtspiAccessible *child1;
+static AtspiAccessible *child2;
+static AtspiAccessible *child3;
+static AtspiAccessible *child4;
+static FlatNaviContext *ctx;
+
+void setup(void)
+{
+    Service_Data *data = get_pointer_to_service_data_struct();
+    data->information_level = 1;
+    data->run_service = 1;
+    data->voice_type = TTS_VOICE_TYPE_FEMALE;
+    data->reading_speed = 2;
+    data->tracking_signal_name = "test_event";
+
+    //Set by tts
+    data->tts = NULL;
+    data->available_languages = NULL;
+
+    //Actions to do when tts state is 'ready'
+    data->say_text = false;
+    data->update_language_list = false;
+
+    data->text_to_say_info = NULL;
+}
+
+void teardown(void)
+{
+}
+
+void setup_flat_navi()
+{
+    setup();
+    root = atspi_create_accessible();
+    root->role = ATSPI_ROLE_APPLICATION;
+    child1 = atspi_create_accessible();
+    child2 = atspi_create_accessible();
+    child3 = atspi_create_accessible();
+    child4 = atspi_create_accessible();
+    child1->role = ATSPI_ROLE_PUSH_BUTTON;
+    child2->role = ATSPI_ROLE_ICON;
+    child3->role = ATSPI_ROLE_CHECK_BOX;
+    child4->role = ATSPI_ROLE_ENTRY;
+    atspi_accessible_add_child(root, child1);
+    atspi_accessible_add_child(root, child2);
+    atspi_accessible_add_child(root, child3);
+    atspi_accessible_add_child(root, child4);
+    atspi_alloc_memory();
+    ctx = flat_navi_context_create(root);
+}
+
+void teardown_flat_navi()
+{
+    flat_navi_context_free(ctx);
+    atspi_free_memory();
+    atspi_delete_accessible(root);
+    teardown();
+}
+
+START_TEST(spi_init_null_parameter)
+{
+    spi_init(NULL);
+}
+END_TEST
+
+START_TEST(spi_init_service_data_parameter)
+{
+    Service_Data *data = get_pointer_to_service_data_struct();
+    spi_init(data);
+}
+END_TEST
+
+START_TEST(spi_on_state_change_name)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    event.type = "test_event";
+    sd->tracking_signal_name = "test_event";
+    event.detail1 = 1;
+    accessible->name = "test_name";
+    event.source = accessible;
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    fail_if(!return_value || strcmp(return_value, "test_name"));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+
+START_TEST(spi_on_state_change_name_tree)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    AtspiAccessible *child1 = atspi_create_accessible();
+    AtspiAccessible *child11 = atspi_create_accessible();
+    AtspiAccessible *child12 = atspi_create_accessible();
+    AtspiAccessible *child2 = atspi_create_accessible();
+    accessible->role = ATSPI_ROLE_FILLER;
+    child1->role = ATSPI_ROLE_FILLER;
+    child11->role = ATSPI_ROLE_FILLER;
+    child12->role = ATSPI_ROLE_FILLER;
+    child2->role = ATSPI_ROLE_FILLER;
+    child1->name = "one";
+    child11->name = "two";
+    child12->name = "two1";
+    child2->name = "three";
+    atspi_accessible_add_child(accessible, child1);
+    atspi_accessible_add_child(child1, child11);
+    atspi_accessible_add_child(child1, child12);
+    atspi_accessible_add_child(accessible, child2);
+    event.type = "test_event";
+    sd->tracking_signal_name = "test_event";
+    event.detail1 = 1;
+    event.source = accessible;
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    fail_if(!return_value || strcmp(return_value, "one two two1 three "));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+START_TEST(spi_on_state_change_description)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    event.type = "test_event";
+    sd->tracking_signal_name = "test_event";
+    event.detail1 = 1;
+    accessible->name = "test_name";
+    accessible->description = "test description";
+    event.source = accessible;
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    fail_if(!return_value || strcmp(return_value, "test description"));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+
+START_TEST(spi_on_state_change_role)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    event.type = "test_event";
+    sd->tracking_signal_name = "test_event";
+    event.detail1 = 1;
+    accessible->role = ATSPI_ROLE_ICON;
+    accessible->name = NULL;
+    accessible->description = NULL;
+    event.source = accessible;
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    fail_if(!return_value || strcmp(return_value, atspi_accessible_get_role_name(accessible, NULL)));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+
+START_TEST(spi_on_caret_move)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    event.type = "object:text-caret-moved";
+    accessible->name = "test_name";
+    event.source = accessible;
+    atspi_alloc_memory();
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    atspi_free_memory();
+    fail_if(!return_value || strcmp(return_value, "AtspiText text"));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+
+START_TEST(spi_on_value_changed)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    event.type = VALUE_CHANGED_SIG;
+    accessible->name = "test_name";
+    event.source = accessible;
+    atspi_alloc_memory();
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    atspi_free_memory();
+    fail_if(!return_value || strcmp(return_value, "1.00"));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+
+START_TEST(spi_on_editing)
+{
+    Service_Data *sd = get_pointer_to_service_data_struct();
+    AtspiEvent event;
+    AtspiAccessible *accessible = atspi_create_accessible();
+    event.type = FOCUS_CHANGED_SIG;
+    accessible->name = "test_name";
+    event.source = accessible;
+    atspi_alloc_memory();
+    char *return_value = spi_event_get_text_to_read(&event, sd);
+    atspi_free_memory();
+    fail_if(!return_value || strcmp(return_value, EDITING_STARTED));
+    free(return_value);
+    atspi_delete_accessible(accessible);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_create_null_parameter)
+{
+    FlatNaviContext *test_ctx = flat_navi_context_create(NULL);
+    fail_if(!test_ctx);
+    free(test_ctx);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_create_valid_parameter)
+{
+    FlatNaviContext *test_ctx = flat_navi_context_create(root);
+    fail_if(!test_ctx);
+    flat_navi_context_free(test_ctx);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_get_current_null_parameter)
+{
+    AtspiAccessible *current = flat_navi_context_current_get(NULL);
+    fail_if(current);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_get_current_valid_parameter)
+{
+    AtspiAccessible *current = flat_navi_context_current_get(ctx);
+    fail_if(!current || current != child1);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_next_null_parameter)
+{
+    AtspiAccessible *next = flat_navi_context_next(NULL);
+    fail_if(next);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_next_valid_parameter)
+{
+    AtspiAccessible *next = flat_navi_context_next(ctx);
+    fail_if(!next || next != child2);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_prev_null_parameter)
+{
+    AtspiAccessible *prev = flat_navi_context_prev(NULL);
+    fail_if(prev);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_prev_valid_parameter)
+{
+    AtspiAccessible *prev = flat_navi_context_prev(ctx);
+    fail_if(prev);
+    flat_navi_context_current_set(ctx, child4);
+    prev = flat_navi_context_prev(ctx);
+    fail_if(!prev || prev != child3);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_last_null_parameter)
+{
+    AtspiAccessible *last = flat_navi_context_last(NULL);
+    fail_if(last);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_last_valid_parameter)
+{
+    AtspiAccessible *last = flat_navi_context_last(ctx);
+    fail_if(!last || last != child2);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_first_null_parameter)
+{
+    AtspiAccessible *first = flat_navi_context_first(NULL);
+    fail_if(first);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_first_valid_parameter)
+{
+    AtspiAccessible *first = flat_navi_context_first(ctx);
+    fail_if(!first || first != child1);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_current_set_null_parameters)
+{
+    gboolean ret = flat_navi_context_current_set(NULL, NULL);
+    fail_if(ret != FALSE);
+    ret = flat_navi_context_current_set(ctx, NULL);
+    fail_if(ret != FALSE);
+    AtspiAccessible *last = flat_navi_context_last(ctx);
+    ret = flat_navi_context_current_set(NULL, last);
+    fail_if(ret != FALSE);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_current_set_valid_parameters)
+{
+    AtspiAccessible *last = flat_navi_context_last(ctx);
+    gboolean ret = flat_navi_context_current_set(ctx, last);
+    AtspiAccessible *current = flat_navi_context_current_get(ctx);
+    fail_if(ret != TRUE || current != last);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_prev_null_parameter)
+{
+    AtspiAccessible *prev = flat_navi_context_line_prev(NULL);
+    fail_if(prev);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_prev_valid_parameter)
+{
+    AtspiAccessible *prev = flat_navi_context_line_prev(ctx);
+    fail_if(prev);
+    flat_navi_context_current_set(ctx, child4);
+    prev = flat_navi_context_line_prev(ctx);
+    fail_if(!prev || prev != child1);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_next_null_parameter)
+{
+    AtspiAccessible *next = flat_navi_context_line_next(NULL);
+    fail_if(next);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_next_valid_parameter)
+{
+    AtspiAccessible *next = flat_navi_context_line_next(ctx);
+    fail_if(!next || next != child3);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_first_null_parameter)
+{
+    AtspiAccessible *first = flat_navi_context_line_first(NULL);
+    fail_if(first);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_first_valid_parameter)
+{
+    AtspiAccessible *first = flat_navi_context_line_first(ctx);
+    fail_if(!first || first != child1);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_last_null_parameter)
+{
+    AtspiAccessible *last = flat_navi_context_line_last(NULL);
+    fail_if(last);
+}
+END_TEST
+
+START_TEST(spi_flat_navi_context_line_last_valid_parameter)
+{
+    AtspiAccessible *last = flat_navi_context_line_last(ctx);
+    fail_if(!last || last != child3);
+}
+END_TEST
+
+Suite *screen_reader_suite(void)
+{
+    Suite *s;
+    TCase *tc_spi_screen_reader_init;
+    TCase *tc_spi_screen_reader_on_state_changed;
+    TCase *tc_spi_screen_reader_on_caret_move;
+    TCase *tc_spi_screen_reader_on_access_value;
+    TCase *tc_spi_screen_reader_on_editing;
+    TCase *tc_spi_screen_reader_flat_navi;
+
+    s = suite_create("Screen reader");
+    tc_spi_screen_reader_init = tcase_create("tc_spi_screen_reader_init");
+    tc_spi_screen_reader_on_state_changed = tcase_create("tc_spi_screen_reader_on_state_changed");
+    tc_spi_screen_reader_on_caret_move = tcase_create("tc_spi_screen_reader_on_caret_move");
+    tc_spi_screen_reader_on_access_value = tcase_create("tc_spi_screen_reader_on_access_value");
+    tc_spi_screen_reader_on_editing = tcase_create("tc_spi_screen_reader_on_editing");
+    tc_spi_screen_reader_flat_navi = tcase_create("tc_scpi_screen_reader_flat_navi");
+
+    tcase_add_checked_fixture(tc_spi_screen_reader_init, setup, teardown);
+    tcase_add_checked_fixture(tc_spi_screen_reader_on_state_changed, setup, teardown);
+    tcase_add_checked_fixture(tc_spi_screen_reader_on_caret_move, setup, teardown);
+    tcase_add_checked_fixture(tc_spi_screen_reader_on_access_value, setup, teardown);
+    tcase_add_checked_fixture(tc_spi_screen_reader_on_editing, setup, teardown);
+
+    tcase_add_checked_fixture(tc_spi_screen_reader_flat_navi, setup_flat_navi, teardown_flat_navi);
+
+    tcase_add_test(tc_spi_screen_reader_init, spi_init_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_init, spi_init_service_data_parameter);
+    tcase_add_test(tc_spi_screen_reader_on_state_changed, spi_on_state_change_name);
+    tcase_add_test(tc_spi_screen_reader_on_state_changed, spi_on_state_change_name_tree);
+    tcase_add_test(tc_spi_screen_reader_on_state_changed, spi_on_state_change_description);
+    tcase_add_test(tc_spi_screen_reader_on_state_changed, spi_on_state_change_role);
+    tcase_add_test(tc_spi_screen_reader_on_caret_move, spi_on_caret_move);
+    tcase_add_test(tc_spi_screen_reader_on_access_value, spi_on_value_changed);
+    tcase_add_test(tc_spi_screen_reader_on_editing, spi_on_editing);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_create_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_create_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_get_current_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_get_current_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_next_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_next_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_prev_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_prev_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_last_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_last_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_first_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_first_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_current_set_null_parameters);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_current_set_valid_parameters);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_prev_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_prev_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_next_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_next_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_first_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_first_valid_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_last_null_parameter);
+    tcase_add_test(tc_spi_screen_reader_flat_navi, spi_flat_navi_context_line_last_valid_parameter);
+
+    suite_add_tcase(s, tc_spi_screen_reader_init);
+    suite_add_tcase(s, tc_spi_screen_reader_on_state_changed);
+    suite_add_tcase(s, tc_spi_screen_reader_on_caret_move);
+    suite_add_tcase(s, tc_spi_screen_reader_on_access_value);
+    suite_add_tcase(s, tc_spi_screen_reader_flat_navi);
+
+    return s;
+}
+
+int main()
+{
+    int number_failed;
+    Suite *s;
+    SRunner *sr;
+
+    s = screen_reader_suite();
+    sr = srunner_create(s);
+
+    srunner_run_all(sr, CK_NORMAL);
+    number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}