From 35e34a39231c313a9057aa1afc6ee875e50d185c Mon Sep 17 00:00:00 2001 From: "jk7744.park" Date: Mon, 26 Oct 2015 15:41:38 +0900 Subject: [PATCH] tizen 2.4 release --- CMakeLists.sub | 26 + CMakeLists.txt | 64 + LICENSE | 208 +++ README | 9 + include/app_tracker.h | 46 + include/common_helpers.h | 53 + include/dbus_gesture_adapter.h | 12 + include/elm_access_adapter.h | 27 + include/etest.h | 14 + include/flat_navi.h | 141 ++ include/keyboard_tracker.h | 18 + include/logger.h | 26 + include/navigator.h | 4 + include/pivot_chooser.h | 15 + include/screen_reader.h | 74 ++ include/screen_reader_gestures.h | 107 ++ include/screen_reader_haptic.h | 4 + include/screen_reader_spi.h | 11 + include/screen_reader_switch.h | 32 + include/screen_reader_system.h | 11 + include/screen_reader_tts.h | 23 + include/screen_reader_vconf.h | 10 + include/smart_notification.h | 63 + include/window_tracker.h | 8 + org.tizen.screen-reader.manifest | 47 + org.tizen.screen-reader.xml | 15 + packaging/org.tizen.screen-reader.spec | 78 ++ res/icons/screen-reader.png | Bin 0 -> 33841 bytes res/po/CMakeLists.txt | 21 + res/po/en_US.po | 226 ++++ src/app_tracker.c | 343 +++++ src/dbus_gesture_adapter.c | 215 +++ src/elm_access_adapter.c | 65 + src/flat_navi.c | 693 ++++++++++ src/keyboard_tracker.c | 179 +++ src/main.c | 272 ++++ src/navigator.c | 2292 ++++++++++++++++++++++++++++++++ src/pivot_chooser.c | 142 ++ src/screen_reader.c | 87 ++ src/screen_reader_gestures.c | 1126 ++++++++++++++++ src/screen_reader_haptic.c | 98 ++ src/screen_reader_spi.c | 329 +++++ src/screen_reader_switch.c | 126 ++ src/screen_reader_system.c | 670 ++++++++++ src/screen_reader_tts.c | 347 +++++ src/screen_reader_vconf.c | 119 ++ src/smart_notification.c | 192 +++ src/window_tracker.c | 130 ++ tests/CMakeLists.sub | 26 + tests/CMakeLists.txt | 43 + tests/atspi/atspi.c | 519 ++++++++ tests/atspi/atspi.h | 451 +++++++ tests/smart_navi_suite.c | 596 +++++++++ 53 files changed, 10453 insertions(+) create mode 100755 CMakeLists.sub create mode 100755 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README create mode 100644 include/app_tracker.h create mode 100644 include/common_helpers.h create mode 100644 include/dbus_gesture_adapter.h create mode 100644 include/elm_access_adapter.h create mode 100644 include/etest.h create mode 100644 include/flat_navi.h create mode 100644 include/keyboard_tracker.h create mode 100644 include/logger.h create mode 100644 include/navigator.h create mode 100644 include/pivot_chooser.h create mode 100644 include/screen_reader.h create mode 100644 include/screen_reader_gestures.h create mode 100644 include/screen_reader_haptic.h create mode 100644 include/screen_reader_spi.h create mode 100644 include/screen_reader_switch.h create mode 100644 include/screen_reader_system.h create mode 100644 include/screen_reader_tts.h create mode 100644 include/screen_reader_vconf.h create mode 100644 include/smart_notification.h create mode 100644 include/window_tracker.h create mode 100644 org.tizen.screen-reader.manifest create mode 100755 org.tizen.screen-reader.xml create mode 100755 packaging/org.tizen.screen-reader.spec create mode 100755 res/icons/screen-reader.png create mode 100644 res/po/CMakeLists.txt create mode 100644 res/po/en_US.po create mode 100644 src/app_tracker.c create mode 100644 src/dbus_gesture_adapter.c create mode 100644 src/elm_access_adapter.c create mode 100644 src/flat_navi.c create mode 100644 src/keyboard_tracker.c create mode 100644 src/main.c create mode 100644 src/navigator.c create mode 100644 src/pivot_chooser.c create mode 100644 src/screen_reader.c create mode 100644 src/screen_reader_gestures.c create mode 100644 src/screen_reader_haptic.c create mode 100644 src/screen_reader_spi.c create mode 100644 src/screen_reader_switch.c create mode 100644 src/screen_reader_system.c create mode 100644 src/screen_reader_tts.c create mode 100644 src/screen_reader_vconf.c create mode 100644 src/smart_notification.c create mode 100644 src/window_tracker.c create mode 100755 tests/CMakeLists.sub create mode 100644 tests/CMakeLists.txt create mode 100644 tests/atspi/atspi.c create mode 100644 tests/atspi/atspi.h create mode 100644 tests/smart_navi_suite.c diff --git a/CMakeLists.sub b/CMakeLists.sub new file mode 100755 index 0000000..eab1e8b --- /dev/null +++ b/CMakeLists.sub @@ -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 -Wno-format -Wno-format-security") + +## LINKER FLAGS +SET(SLP_LINKER_FLAGS "") diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..20a7467 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,64 @@ +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") +SET(LOCALEDIR "/usr/apps/org.tizen.screen-reader/res/locale") + +IF("${SEC_FEATURE_TAPI_ENABLE}" STREQUAL "1") + MESSAGE("SEC_FEATURE_TAPI_ENABLE: ${SEC_FEATURE_TAPI_ENABLE}") + SET(TAPI_REQUIRED_PKG "tapi") +ELSE() + MESSAGE("SEC_FEATURE_TAPI_ENABLE: ${SEC_FEATURE_TAPI_ENABLE}") + ADD_DEFINITIONS("-DSCREEN_READER_TV") +ENDIF() + +INCLUDE(FindPkgConfig) +pkg_check_modules(pkgs REQUIRED + bundle + appcore-efl + eldbus + elementary + ecore + atspi-2 + gobject-2.0 + ecore-x + dlog + vconf + tts + capi-media-tone-player + capi-system-device + capi-network-bluetooth + notification + capi-network-wifi + capi-appfw-service-application + ${TAPI_REQUIRED_PKG} +) + +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} -fPIE") +SET(CMAKE_CXX_FLAGS "${SLP_DEBUG_FLAGS} ${SLP_OPT_FLAGS} ${CMAKE_CXX_FLAGS} ${EXTRA_CFLAGS} ${SLP_COMPILER_FLAGS}") + +ADD_EXECUTABLE(${PROJECT_NAME} ${SRCS}) + +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${pkgs_LDFLAGS} ${SLP_LD_PATH_FLAGS} ${SLP_LD_FLAGS} ${SLP_LINKER_FLAGS} "-pie") + +# 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 /opt/share/packages) + +ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/res/po) +ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/tests) +ADD_TEST(NAME smart_navi_tests COMMAND ${CMAKE_SOURCE_DIR}/tests/smart_navi_test_suite) +# END OF A FILE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ca008cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,208 @@ +Copyright (C) 2015 Samsung Electronics Co., Ltd All rights reserved. + +Flora License + +Version 1.1, April, 2013 + +http://floralicense.org/license/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and +all other entities that control, are controlled by, or are +under common control with that entity. For the purposes of +this definition, "control" means (i) the power, direct or indirect, +to cause the direction or management of such entity, +whether by contract or otherwise, or (ii) ownership of fifty percent (50%) +or more of the outstanding shares, or (iii) beneficial ownership of +such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, +and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice +that is included in or attached to the work (an example is provided +in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial +revisions, annotations, elaborations, or other modifications represent, +as a whole, an original work of authorship. For the purposes of this License, +Derivative Works shall not include works that remain separable from, +or merely link (or bind by name) to the interfaces of, the Work and +Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor +for inclusion in the Work by the copyright owner or by an individual or +Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to the Licensor or +its representatives, including but not limited to communication on +electronic mailing lists, source code control systems, and issue +tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding +communication that is conspicuously marked or otherwise designated +in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +"Tizen Certified Platform" shall mean a software platform that complies +with the standards set forth in the Tizen Compliance Specification +and passes the Tizen Compliance Tests as defined from time to time +by the Tizen Technical Steering Group and certified by the Tizen +Association or its designated agent. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work +solely as incorporated into a Tizen Certified Platform, where such +license applies only to those patent claims licensable by such +Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work solely +as incorporated into a Tizen Certified Platform to which such +Contribution(s) was submitted. If You institute patent litigation +against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Work or a Contribution incorporated +within the Work constitutes direct or contributory patent infringement, +then any patent licenses granted to You under this License for that +Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof pursuant to the copyright license +above, in any medium, with or without modifications, and in Source or +Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works + a copy of this License; and + 2. You must cause any modified files to carry prominent notices stating + that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works that + You distribute, all copyright, patent, trademark, and attribution + notices from the Source form of the Work, excluding those notices + that do not pertain to any part of the Derivative Works; and + 4. If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable + copy of the attribution notices contained within such NOTICE file, + excluding those notices that do not pertain to any part of + the Derivative Works, in at least one of the following places: + within a NOTICE text file distributed as part of the Derivative Works; + within the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the Derivative Works, + if and wherever such third-party notices normally appear. + The contents of the NOTICE file are for informational purposes only + and do not modify the License. You may add Your own attribution notices + within Derivative Works that You distribute, alongside or as an addendum + to the NOTICE text from the Work, provided that such additional attribution + notices cannot be construed as modifying the License. You may add Your own + copyright statement to Your modifications and may provide additional or + different license terms and conditions for use, reproduction, or + distribution of Your modifications, or for any such Derivative Works + as a whole, provided Your use, reproduction, and distribution of + the Work otherwise complies with the conditions stated in this License + and your own copyright statement or terms and conditions do not conflict + the conditions stated in the License including section 3. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Flora License to your work + +To apply the Flora License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Flora License, Version 1.1 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://floralicense.org/license/ + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README b/README new file mode 100644 index 0000000..fc09a6f --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +Before running smart navigator extend /usr/lib/systemd/system/pulseaudio.service +[Service] +... +ExecStartPre=/bin/chmod 777 /tmp/pulseaudio +... +To give other processes like smart-navigator access to pulseaudio tmp. + +Extending service with User=pulse or changing owner ship on /tmp/pulseaudio +results with malfunction of service. diff --git a/include/app_tracker.h b/include/app_tracker.h new file mode 100644 index 0000000..8e2ad7c --- /dev/null +++ b/include/app_tracker.h @@ -0,0 +1,46 @@ +#ifndef APP_TRACKER_H_ +#define APP_TRACKER_H_ + +#include +#include + +/** + * @brief Callback + */ +typedef void (*AppTrackerEventCB)(AtspiAccessible *root, void *user_data); + +/** + * @brief Register listener on given event type. + * + * @param obj AtspiAccessible application object type + * @param cb callback type + * @param user_data pointer passed to cb function. + */ +void app_tracker_callback_register(AtspiAccessible *app, AppTrackerEventCB cb, void *user_data); +void app_tracker_new_obj_highlighted_callback_register(AppTrackerEventCB cb); + +/** + * @brief Unregister listener on given event type. + * + * @param obj AtspiAccessible application object type + * @param cb callback type + * @param user_data pointer passed to cb function. + */ +void app_tracker_callback_unregister(AtspiAccessible *app, AppTrackerEventCB cb, void *user_data); +void app_tracker_new_obj_highlighted_callback_unregister(AppTrackerEventCB cb); + +/** + * @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/common_helpers.h b/include/common_helpers.h new file mode 100644 index 0000000..28fb9d8 --- /dev/null +++ b/include/common_helpers.h @@ -0,0 +1,53 @@ +#ifndef COMMON_HELPERS_H_ +#define COMMON_HELPERS_H_ + +#include + +#ifdef DEBUG +#define PLOG(fmt, ...) \ + fprintf(stderr, " %s(%d) --> ", __FUNCTION__, __LINE__); \ + fprintf(stderr, fmt, ##__VA_ARGS__);\ + fprintf(stderr, "\n");\ + LOGD(fmt, ##__VA_ARGS__); + +#define PLOGD(fmt, ...) \ + PLOG(fmt, ##__VA_ARGS__) + +#define PLOGW(fmt, ...) \ + fprintf(stderr, " %s(%d) --> ", __FUNCTION__, __LINE__); \ + fprintf(stderr, fmt, ##__VA_ARGS__);\ + fprintf(stderr, "\n");\ + LOGD(fmt, ##__VA_ARGS__); + +#define PLOGE(fmt, ...) \ + fprintf(stderr, " %s(%d) --> ", __FUNCTION__, __LINE__); \ + fprintf(stderr, fmt, ##__VA_ARGS__);\ + fprintf(stderr, "\n");\ + LOGD(fmt, ##__VA_ARGS__); + +#define PLOGI(fmt, ...) \ + fprintf(stderr, " %s(%d) --> ", __FUNCTION__, __LINE__); \ + fprintf(stderr, fmt, ##__VA_ARGS__);\ + fprintf(stderr, "\n");\ + LOGD(fmt, ##__VA_ARGS__); + +#else + +#define PLOG(fmt, ...) \ + LOGD(fmt, ##__VA_ARGS__); + +#define PLOGD(fmt, ...) \ + PLOG(fmt, ##__VA_ARGS__); + +#define PLOGW(fmt, ...) \ + LOGW(fmt, ##__VA_ARGS__); + +#define PLOGE(fmt, ...) \ + LOGE(fmt, ##__VA_ARGS__); + +#define PLOGI(fmt, ...) \ + LOGI(fmt, ##__VA_ARGS__);\ + +#endif + +#endif /* COMMON_HELPERS_H_ */ diff --git a/include/dbus_gesture_adapter.h b/include/dbus_gesture_adapter.h new file mode 100644 index 0000000..ec42f29 --- /dev/null +++ b/include/dbus_gesture_adapter.h @@ -0,0 +1,12 @@ +#ifndef DBUS_GESTURE_ADAPTER_H_ +#define DBUS_GESTURE_ADAPTER_H_ + +#include "screen_reader_gestures.h" + +void dbus_gesture_adapter_init(void); + +void dbus_gesture_adapter_shutdown(void); + +void dbus_gesture_adapter_emit(const Gesture_Info *info); + +#endif diff --git a/include/elm_access_adapter.h b/include/elm_access_adapter.h new file mode 100644 index 0000000..027108b --- /dev/null +++ b/include/elm_access_adapter.h @@ -0,0 +1,27 @@ +#ifndef ELM_ACCESS_KEYBOARD_ADAPTER_H_ +#define ELM_ACCESS_KEYBOARD_ADAPTER_H_ + +#include +#include + +/** + * @brief Send ecore x message with elm access read action + * + * @param win keyboard window + * @param x x coordinate of gesture relative to X root window + * @param y y coordinate of gesture relative to X root window + * + */ +void elm_access_adaptor_emit_read(Ecore_X_Window win, int x, int y); + +/** + * @brief Send ecore x message with elm access activate action + * + * @param win keyboard window + * @param x x coordinate of gesture relative to X root window + * @param y y coordinate of gesture relative to X root window + * + */ +void elm_access_adaptor_emit_activate(Ecore_X_Window win, int x, int y); + +#endif diff --git a/include/etest.h b/include/etest.h new file mode 100644 index 0000000..69b14fc --- /dev/null +++ b/include/etest.h @@ -0,0 +1,14 @@ +#ifndef __ETEST_H__ +#define __ETEST_H__ + +#include +#include +#include +#include + +struct appdata +{ + Evas_Object *win; +}; + +#endif \ No newline at end of file diff --git a/include/flat_navi.h b/include/flat_navi.h new file mode 100644 index 0000000..0b1e49d --- /dev/null +++ b/include/flat_navi.h @@ -0,0 +1,141 @@ +#ifndef FLAT_NAVI_H_ +#define FLAT_NAVI_H_ + +#include +#include + +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); + +/** + * Returns the root of given context + * @param ctx Flat navi context + * @return + */ +AtspiAccessible *flat_navi_context_root_get(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 + */ +Eina_Bool flat_navi_context_current_set(FlatNaviContext *ctx, AtspiAccessible *target); + +/** + * Set current context at given position. + * + * @param ctx FlatNaviContext + * + * @param x_cord gint X coordinate + * + * @param y_cord gint Y coordinate + * + * @param obj AtspiAccessible Reference to object on point + * + * @return Eina_Bool true on success + * + * @note current element will be first of line. + */ +Eina_Bool flat_navi_context_current_at_x_y_set( FlatNaviContext *ctx, gint x_cord, gint y_cord , AtspiAccessible **obj); + +/** + * Returns the first item in context; + * @param ctx FlatNaviContext + * @return Pointer to the first item in context + */ +const AtspiAccessible *flat_navi_context_first_get(FlatNaviContext *ctx); + +/** + * Returns the last item in context; + * @param ctx FlatNaviContext + * @return Pointer to the last item in context + */ +const AtspiAccessible *flat_navi_context_last_get(FlatNaviContext *ctx); + +Eina_Bool flat_navi_is_valid(FlatNaviContext *context, AtspiAccessible *new_root); + +#endif /* end of include guard: FLAT_NAVI_H_ */ diff --git a/include/keyboard_tracker.h b/include/keyboard_tracker.h new file mode 100644 index 0000000..f94f881 --- /dev/null +++ b/include/keyboard_tracker.h @@ -0,0 +1,18 @@ +/** + * @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 index 0000000..f9d6a0d --- /dev/null +++ b/include/logger.h @@ -0,0 +1,26 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +#define TIZEN_ENGINEER_MODE +#include +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "SCREEN-READER" + +#define INFO(format, arg...) LOGI(format, ##arg) +#define DEBUG(format, arg...) LOGD(format, ##arg) +#define ERROR(format, arg...) LOGE(format, ##arg) +#define WARNING(format, arg...) LOGW(format, ##arg) + +#define MEMORY_ERROR "Memory allocation error" + +#ifdef _ +#undef _ +#endif + +#define _(str) (gettext(str)) + +#endif /* end of include guard: LOGGER_H_ */ diff --git a/include/navigator.h b/include/navigator.h new file mode 100644 index 0000000..a61932a --- /dev/null +++ b/include/navigator.h @@ -0,0 +1,4 @@ +#include + +void navigator_init(void); +void navigator_shutdown(void); diff --git a/include/pivot_chooser.h b/include/pivot_chooser.h new file mode 100644 index 0000000..164e84c --- /dev/null +++ b/include/pivot_chooser.h @@ -0,0 +1,15 @@ +#ifndef SMART_NAVI_PIVOT_CHOOSER_H_ +#define SMART_NAVI_PIVOT_CHOOSER_H_ + +#include + +/** + * @brief Some heuristic choosing candidate to reacieve highlight. + * + * @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/screen_reader.h b/include/screen_reader.h new file mode 100644 index 0000000..7608a6f --- /dev/null +++ b/include/screen_reader.h @@ -0,0 +1,74 @@ +#ifndef __screen_reader_H__ +#define __screen_reader_H__ + +#include +#include +#include + +#define LANGUAGE_NAME_SIZE 6 + +#define FOCUS_CHANGED_SIG "object:state-changed:focused" +#define HIGHLIGHT_CHANGED_SIG "object:state-change:highlighted" +#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 + bool run_service; + char display_language[LANGUAGE_NAME_SIZE]; + char *tracking_signal_name; + + //Set by tts + tts_h tts; + Eina_List *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 *state_changed_listener; + AtspiEventListener *value_changed_listener; + AtspiEventListener *caret_moved_listener; + AtspiEventListener *spi_listener; + + AtspiAccessible *currently_focused; + AtspiAccessible *mouse_down_widget; + AtspiAccessible *clicked_widget; + + //Set by dbus + Eldbus_Proxy *proxy; + 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_gestures.h b/include/screen_reader_gestures.h new file mode 100644 index 0000000..0e5b1d4 --- /dev/null +++ b/include/screen_reader_gestures.h @@ -0,0 +1,107 @@ +#ifndef SCREEN_READER_GESTURES_H_ +#define SCREEN_READER_GESTURES_H_ + +#include +#include + +/** + * @brief Accessibility gestures + */ +enum _Gesture +{ + ONE_FINGER_HOVER, + TWO_FINGERS_HOVER, + THREE_FINGERS_HOVER, + ONE_FINGER_FLICK_LEFT, + ONE_FINGER_FLICK_RIGHT, + ONE_FINGER_FLICK_UP, + ONE_FINGER_FLICK_DOWN, + TWO_FINGERS_FLICK_LEFT, + TWO_FINGERS_FLICK_RIGHT, + TWO_FINGERS_FLICK_UP, + TWO_FINGERS_FLICK_DOWN, + THREE_FINGERS_FLICK_LEFT, + THREE_FINGERS_FLICK_RIGHT, + THREE_FINGERS_FLICK_UP, + THREE_FINGERS_FLICK_DOWN, + ONE_FINGER_SINGLE_TAP, + ONE_FINGER_DOUBLE_TAP, + ONE_FINGER_TRIPLE_TAP, + TWO_FINGERS_SINGLE_TAP, + TWO_FINGERS_DOUBLE_TAP, + TWO_FINGERS_TRIPLE_TAP, + THREE_FINGERS_SINGLE_TAP, + THREE_FINGERS_DOUBLE_TAP, + THREE_FINGERS_TRIPLE_TAP, + ONE_FINGER_FLICK_LEFT_RETURN, + ONE_FINGER_FLICK_RIGHT_RETURN, + ONE_FINGER_FLICK_UP_RETURN, + ONE_FINGER_FLICK_DOWN_RETURN, + TWO_FINGERS_FLICK_LEFT_RETURN, + TWO_FINGERS_FLICK_RIGHT_RETURN, + TWO_FINGERS_FLICK_UP_RETURN, + TWO_FINGERS_FLICK_DOWN_RETURN, + THREE_FINGERS_FLICK_LEFT_RETURN, + THREE_FINGERS_FLICK_RIGHT_RETURN, + THREE_FINGERS_FLICK_UP_RETURN, + THREE_FINGERS_FLICK_DOWN_RETURN, + GESTURES_COUNT, +}; +typedef enum _Gesture Gesture; + +typedef struct +{ + Gesture type; // Type of recognized gesture + int x_beg, x_end; // (x,y) coordinates when gesture begin (screen coords) + int y_beg, y_end; // (x,y) coordinates when gesture ends (screen coords) + pid_t pid; // pid of process on which gesture took place. + int state; // 0 - begin, 1 - ongoing, 2 - ended, 3 - aborted + int event_time; +} Gesture_Info; + +/** + * @brief Initialize gesture navigation profile. + * + * @return EINA_TRUE is initialization was successfull, EINA_FALSE otherwise + */ +Eina_Bool screen_reader_gestures_init(void); + +/** + * @brief Shutdown gesture navigation profile. + */ +void screen_reader_gestures_shutdown(void); + +Eina_Bool screen_reader_gesture_x_grab_touch_devices(Ecore_X_Window win); + +typedef void (*GestureCB)(void *data, Gesture_Info *info); + +/** + * @brief Registers callback on gestures + */ +void screen_reader_gestures_tracker_register(GestureCB cb, void *data); + +/** + * @brief Start event emission + */ +void start_scroll(int x, int y); + +/** + * @brief Continue event emission + */ +void continue_scroll(int x, int y); + +/** + * @brief End event emit + */ +void end_scroll(int x, int y); + +/** + * @brief Get top window object on which gesture occure + * + * @param x Gesture X coordinate + * @param y Gesture Y coordinate + * + * @return Ecore_X_Window Object which represents top window on which gesture occure + */ +Ecore_X_Window top_window_get (int x, int y); +#endif diff --git a/include/screen_reader_haptic.h b/include/screen_reader_haptic.h new file mode 100644 index 0000000..d3f9beb --- /dev/null +++ b/include/screen_reader_haptic.h @@ -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 index 0000000..c616369 --- /dev/null +++ b/include/screen_reader_spi.h @@ -0,0 +1,11 @@ +#ifndef SCREEN_READER_SPI_H_ +#define SCREEN_READER_SPI_H_ + +#include +#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_switch.h b/include/screen_reader_switch.h new file mode 100644 index 0000000..67cadb3 --- /dev/null +++ b/include/screen_reader_switch.h @@ -0,0 +1,32 @@ +#ifndef _SCREEN_READER_SWITCH +#define _SCREEN_READER_SWITCH + +/** + * @brief Set "ScreenReaderEnabled" status of AT-SPI framework + * + * Function used to iniform applications that screen reader has been enabled in + * the system. + * + * ScreenReaderEnabled property refers to + * org.a11y.Bus bus at /org/a11y/bus object path in org.a11y.Status interface + * + * @param value EINA_TRUE if screen reader should be enabled, EINA_FALSE otherwise. + * @return EINA_TRUE if setting 'ScreenReaderEnabled' to value has successed, + * EINA_FALSE otherwise + */ +Eina_Bool screen_reader_switch_enabled_set(Eina_Bool value); + + +/** + * @brief Get "ScreenReaderEnabled" status of AT-SPI framework + * + * ScreenReaderEnabled property refers to + * org.a11y.Bus bus at /org/a11y/bus object path in org.a11y.Status interface + * + * @param value EINA_TRUE if screen reader is enabled, EINA_FALSE otherwise. + * @return EINA_TRUE if getting of value has successed, + * EINA_FALSE otherwise + */ +Eina_Bool screen_reader_switch_enabled_get(Eina_Bool *value); + +#endif diff --git a/include/screen_reader_system.h b/include/screen_reader_system.h new file mode 100644 index 0000000..c2031a6 --- /dev/null +++ b/include/screen_reader_system.h @@ -0,0 +1,11 @@ +#ifndef SCREEN_READER_TV +void system_notifications_init(void); +void system_notifications_shutdown(void); + +void device_time_get(void); +void device_battery_get(void); +void device_signal_strenght_get(void); +void device_missed_events_get(void); +void device_date_get(void); +void device_bluetooth_get(void); +#endif diff --git a/include/screen_reader_tts.h b/include/screen_reader_tts.h new file mode 100644 index 0000000..9c039f1 --- /dev/null +++ b/include/screen_reader_tts.h @@ -0,0 +1,23 @@ +#ifndef SCREEN_READER_TTS_H_ +#define SCREEN_READER_TTS_H_ + +#include +#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); +void tts_speak(char *text_to_speak, Eina_Bool flush_switch); +void spi_stop(void *data); + +void tts_stop_set(void); +Eina_Bool tts_pause_get(void); +Eina_Bool tts_pause_set(Eina_Bool pause_switch); + +void set_utterance_cb( void(*utter_cb)(void)); + +#endif /* SCREEN_READER_TTS_H_ */ diff --git a/include/screen_reader_vconf.h b/include/screen_reader_vconf.h new file mode 100644 index 0000000..0affa51 --- /dev/null +++ b/include/screen_reader_vconf.h @@ -0,0 +1,10 @@ +#ifndef SCREEN_READER_VCONF_H_ +#define SCREEN_READER_VCONF_H_ + +#include "screen_reader.h" +#include "logger.h" + +bool vconf_init(Service_Data *service_data); +void screen_reader_vconf_update_tts_language(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 index 0000000..7255419 --- /dev/null +++ b/include/smart_notification.h @@ -0,0 +1,63 @@ +#ifndef SMART_NOTIFICATION_H_ +#define SMART_NOTIFICATION_H_ + +/** + * @brief Type of notification events. + * + * @FOCUS_CHAIN_END_NOTIFICATION_EVENT emitted when + * currnetly focued or highlighted 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/window_tracker.h b/include/window_tracker.h new file mode 100644 index 0000000..a36fd28 --- /dev/null +++ b/include/window_tracker.h @@ -0,0 +1,8 @@ +#include + +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 index 0000000..cbe437f --- /dev/null +++ b/org.tizen.screen-reader.manifest @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.tizen.screen-reader.xml b/org.tizen.screen-reader.xml new file mode 100755 index 0000000..10932c6 --- /dev/null +++ b/org.tizen.screen-reader.xml @@ -0,0 +1,15 @@ + + + + SMART NAVIGATOR + + + /usr/apps/org.tizen.screen-reader/res/icons/screen-reader.png + + + + + + + + diff --git a/packaging/org.tizen.screen-reader.spec b/packaging/org.tizen.screen-reader.spec new file mode 100755 index 0000000..77e376c --- /dev/null +++ b/packaging/org.tizen.screen-reader.spec @@ -0,0 +1,78 @@ +%define AppInstallPath /usr/apps/%{name} +%define Exec screen-reader + + +Name: org.tizen.screen-reader +Summary: Screen Reader Assistive Technology +Version: 0.0.7 +Release: 1 +License: Flora-1.1 +Source0: %{name}-%{version}.tar.gz + +BuildRequires: at-spi2-core +BuildRequires: at-spi2-core-devel +BuildRequires: cmake +BuildRequires: pkgconfig(appcore-efl) +BuildRequires: pkgconfig(ecore) +BuildRequires: pkgconfig(ecore-x) +BuildRequires: pkgconfig(eina) +BuildRequires: pkgconfig(eldbus) +BuildRequires: pkgconfig(elementary) +BuildRequires: pkgconfig(capi-media-tone-player) +BuildRequires: pkgconfig(capi-system-device) +BuildRequires: tts +BuildRequires: tts-devel +BuildRequires: vconf +BuildRequires: gettext-tools +BuildRequires: pkgconfig(check) +BuildRequires: pkgconfig(capi-network-bluetooth) +BuildRequires: pkgconfig(notification) +BuildRequires: pkgconfig(capi-network-wifi) +BuildRequires: pkgconfig(capi-appfw-service-application) +%if "%{?tizen_profile_name}" != "tv" +BuildRequires: pkgconfig(tapi) +%endif + +%description +An utility library for developers of the menu screen. + +%prep +%setup -q + +%build +rm -rf CMakeFiles CMakeCache.txt + +%if "%{?tizen_profile_name}" != "tv" + export SEC_FEATURE_TAPI_ENABLE="1" +%else + export SEC_FEATURE_TAPI_ENABLE="0" +%endif +export CFLAGS+=" -DELM_ACCESS_KEYBOARD" + +cmake . -DCMAKE_INSTALL_PREFIX="%{AppInstallPath}" \ + -DCMAKE_TARGET="%{Exec}" \ + -DCMAKE_PACKAGE="%{name}" \ + -DSEC_FEATURE_TAPI_ENABLE=${SEC_FEATURE_TAPI_ENABLE} + +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 + +%postun -p /sbin/ldconfig + +%files +%manifest org.tizen.screen-reader.manifest +%{AppInstallPath}/bin/screen-reader +%{AppInstallPath}/res/icons/screen-reader.png +%{AppInstallPath}/res/locale/*/LC_MESSAGES/* +/opt/share/packages/%{name}.xml diff --git a/res/icons/screen-reader.png b/res/icons/screen-reader.png new file mode 100755 index 0000000000000000000000000000000000000000..778d06bf020c0c6552f729a82a1fd5c89cef98b5 GIT binary patch literal 33841 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 kYw>;SUUg)IM)MW9-PONpai!Scallbacks; l != NULL; l = l->next) { + ecd = l->data; + ecd->func(std->root, ecd->user_data); + } + DEBUG("END"); +} + +static gboolean _on_timeout_cb(gpointer user_data) +{ + DEBUG("START"); + SubTreeRootData *std = user_data; + + _subtree_callbacks_call(std); + + std->timer = 0; + DEBUG("END"); + return FALSE; +} + +static void _print_event_object_info(const AtspiEvent * event) +{ + gchar *name = atspi_accessible_get_name(event->source, NULL), *role = atspi_accessible_get_role_name(event->source, NULL); + + DEBUG("signal:%s, name: %s, role: %s, detail1:%i, detail2: %i", event->type, role, name, event->detail1, event->detail2); + g_free(name); + g_free(role); +} + +static void _on_atspi_event_cb(const AtspiEvent * event) +{ + GList *l; + SubTreeRootData *std; + + if (!event) + return; + if (!event->source) { + ERROR("empty event source"); + return; + } + + if ((atspi_accessible_get_role(event->source, NULL) == ATSPI_ROLE_DESKTOP_FRAME)) { + return; + } + + _print_event_object_info(event); + + if (!strcmp(event->type, "object:property-change:accessible-name") && _object_has_highlighted_state(event->source)) { + gchar *name = atspi_accessible_get_name(event->source, NULL); + DEBUG("New name for object, read:%s", name); + tts_speak (name, EINA_TRUE); + g_free(name); + return; + } + AtspiAccessible *new_highlighted_obj = NULL; + + if (!strcmp(event->type, "object:state-changed:highlighted")) + new_highlighted_obj = event->source; + else if (!strcmp(event->type, "object:active-descendant-changed")) + new_highlighted_obj = atspi_accessible_get_child_at_index(event->source, event->detail1, NULL); + + if (new_highlighted_obj && _new_obj_highlighted_callback && _object_has_highlighted_state(new_highlighted_obj)) { + DEBUG("HIGHLIGHTED OBJECT IS ABOUT TO CHANGE"); + _new_obj_highlighted_callback(new_highlighted_obj, NULL); + g_object_unref(new_highlighted_obj); + new_highlighted_obj = NULL; + } + + if (!strcmp("object:state-changed:showing", event->type) || + !strcmp("object:state-changed:visible", event->type) || + !strcmp("object:state-changed:defunct", event->type)) { + for (l = _roots; l != NULL; l = l->next) { + std = l->data; + + if (!_object_has_showing_state(std->root) && std->base_root) { + std->root = std->base_root; + std->base_root = NULL; + } + + if (_is_descendant(std->root, event->source)) { + if (std->timer) + g_source_remove(std->timer); + DEBUG("Before Checking if modal is showing"); + if (_object_has_modal_state(event->source)) { + DEBUG("Object is modal"); + std->base_root = std->root; + std->root = event->source; + } + std->timer = g_timeout_add(APP_TRACKER_INVACTIVITY_TIMEOUT, _on_timeout_cb, std); + } + } + } +} + +static int _app_tracker_init_internal(void) +{ + DEBUG("START"); + _new_obj_highlighted_callback = NULL; + _listener = atspi_event_listener_new_simple(_on_atspi_event_cb, NULL); + + atspi_event_listener_register(_listener, "object:state-changed:showing", NULL); + atspi_event_listener_register(_listener, "object:state-changed:visible", NULL); + atspi_event_listener_register(_listener, "object:state-changed:defunct", NULL); + atspi_event_listener_register(_listener, "object:state-changed:highlighted", NULL); + atspi_event_listener_register(_listener, "object:bounds-changed", NULL); + atspi_event_listener_register(_listener, "object:visible-data-changed", NULL); + atspi_event_listener_register(_listener, "object:active-descendant-changed", NULL); + atspi_event_listener_register(_listener, "object:property-change", NULL); + + return 0; +} + +static void _free_callbacks(gpointer data) +{ + g_free(data); +} + +static void _free_rootdata(gpointer data) +{ + SubTreeRootData *std = data; + g_list_free_full(std->callbacks, _free_callbacks); + if (std->timer) + g_source_remove(std->timer); + g_free(std); +} + +static void _app_tracker_shutdown_internal(void) +{ + atspi_event_listener_deregister(_listener, "object:state-changed:showing", NULL); + atspi_event_listener_deregister(_listener, "object:state-changed:visible", NULL); + atspi_event_listener_deregister(_listener, "object:state-changed:highlighted", NULL); + atspi_event_listener_deregister(_listener, "object:bounds-changed", NULL); + atspi_event_listener_deregister(_listener, "object:state-changed:defunct", NULL); + atspi_event_listener_deregister(_listener, "object:visible-data-changed", NULL); + atspi_event_listener_deregister(_listener, "object:active-descendant-changed", NULL); + atspi_event_listener_deregister(_listener, "object:property-change", NULL); + + g_object_unref(_listener); + _listener = NULL; + _new_obj_highlighted_callback = NULL; + g_list_free_full(_roots, _free_rootdata); + _roots = NULL; +} + +int app_tracker_init(void) +{ + DEBUG("START"); + if (!_init_count) + if (_app_tracker_init_internal()) + return -1; + return ++_init_count; +} + +void app_tracker_shutdown(void) +{ + if (_init_count == 1) + _app_tracker_shutdown_internal(); + if (--_init_count < 0) + _init_count = 0; +} + +void app_tracker_callback_register(AtspiAccessible * app, AppTrackerEventCB cb, void *user_data) +{ + DEBUG("START"); + SubTreeRootData *rd = NULL; + EventCallbackData *cd; + GList *l; + + if (!_init_count || !cb) + return; + + for (l = _roots; l != NULL; l = l->next) { + rd = l->data; + if (((SubTreeRootData *) l->data)->root == app) { + rd = l->data; + break; + } + } + + if (!rd) { + rd = g_new(SubTreeRootData, 1); + rd->root = app; + rd->base_root = NULL; + rd->callbacks = NULL; + rd->timer = 0; + _roots = g_list_append(_roots, rd); + } + + cd = g_new(EventCallbackData, 1); + cd->func = cb; + cd->user_data = user_data; + + rd->callbacks = g_list_append(rd->callbacks, cd); + DEBUG("END"); +} + +void app_tracker_new_obj_highlighted_callback_register(AppTrackerEventCB cb) +{ + _new_obj_highlighted_callback = cb; +} + +void app_tracker_callback_unregister(AtspiAccessible * app, AppTrackerEventCB cb, void *user_data) +{ + DEBUG("START"); + GList *l; + EventCallbackData *ecd; + SubTreeRootData *std = NULL; + + for (l = _roots; l != NULL; l = l->next) { + if (((SubTreeRootData *) l->data)->root == app || ((SubTreeRootData *) l->data)->base_root == app) { + std = l->data; + break; + } + } + + if (!std) + return; + + for (l = std->callbacks; l != NULL; l = l->next) { + ecd = l->data; + if ((ecd->func == cb) && (ecd->user_data == user_data)) { + std->callbacks = g_list_delete_link(std->callbacks, l); + break; + } + } + + if (!std->callbacks) { + if (std->timer) + g_source_remove(std->timer); + _roots = g_list_remove(_roots, std); + g_free(std); + } +} + +void app_tracker_new_obj_highlighted_callback_unregister(AppTrackerEventCB cb) +{ + _new_obj_highlighted_callback = NULL; +} diff --git a/src/dbus_gesture_adapter.c b/src/dbus_gesture_adapter.c new file mode 100644 index 0000000..e4427ad --- /dev/null +++ b/src/dbus_gesture_adapter.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dbus_gesture_adapter.h" +#include "logger.h" + +#include + +#define E_A11Y_SERVICE_BUS_NAME "com.samsung.EModule" +#define E_A11Y_SERVICE_NAVI_IFC_NAME "com.samsung.GestureNavigation" +#define E_A11Y_SERVICE_NAVI_OBJ_PATH "/com/samsung/GestureNavigation" + +static Eldbus_Connection *conn; +static Eldbus_Service_Interface *service; + +enum _Signals { + GESTURE_DETECTED = 0, +}; + +static const Eldbus_Signal _signals[] = { + [GESTURE_DETECTED] = {"GestureDetected", ELDBUS_ARGS({"siiiiu", NULL}), 0}, + {NULL, ELDBUS_ARGS({NULL, NULL}), 0} +}; + +static const Eldbus_Service_Interface_Desc desc = { + E_A11Y_SERVICE_NAVI_IFC_NAME, NULL, _signals, NULL, NULL, NULL +}; + +void _on_get_a11y_address(void *data, const Eldbus_Message * msg, Eldbus_Pending * pending) +{ + const char *sock_addr; + const char *errname, *errmsg; + Eldbus_Connection *session = data; + + if (eldbus_message_error_get(msg, &errname, &errmsg)) { + ERROR("GetAddress failed: %s %s", errname, errmsg); + return; + } + + if (!eldbus_message_arguments_get(msg, "s", &sock_addr) || !sock_addr) { + ERROR("Could not get A11Y Bus socket address."); + goto end; + } + + if (!(conn = eldbus_address_connection_get(sock_addr))) { + ERROR("Failed to connect to %s", sock_addr); + goto end; + } + + if (!(service = eldbus_service_interface_register(conn, E_A11Y_SERVICE_NAVI_OBJ_PATH, &desc))) { + ERROR("Failed to register %s interface", E_A11Y_SERVICE_NAVI_IFC_NAME); + eldbus_connection_unref(conn); + conn = NULL; + goto end; + } + + eldbus_name_request(conn, E_A11Y_SERVICE_BUS_NAME, ELDBUS_NAME_REQUEST_FLAG_DO_NOT_QUEUE, NULL, NULL); + + end: + eldbus_connection_unref(session); +} + +void dbus_gesture_adapter_init(void) +{ + Eldbus_Connection *session; + Eldbus_Message *msg; + + eldbus_init(); + + if (!(session = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION))) { + ERROR("Unable to get session bus"); + return; + } + + if (!(msg = eldbus_message_method_call_new("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress"))) { + ERROR("DBus message allocation failed"); + goto fail_msg; + } + + if (!eldbus_connection_send(session, msg, _on_get_a11y_address, session, -1)) { + ERROR("Message send failed"); + goto fail_send; + } + + return; + + fail_send: + eldbus_message_unref(msg); + fail_msg: + eldbus_connection_unref(session); +} + +void dbus_gesture_adapter_shutdown(void) +{ + if (service) + eldbus_service_object_unregister(service); + if (conn) + eldbus_connection_unref(conn); + + conn = NULL; + service = NULL; + + eldbus_shutdown(); +} + +static const char *_gesture_enum_to_string(Gesture g) +{ + switch (g) { + case ONE_FINGER_HOVER: + return "OneFingerHover"; + case TWO_FINGERS_HOVER: + return "TwoFingersHover"; + case THREE_FINGERS_HOVER: + return "ThreeFingersHover"; + case ONE_FINGER_FLICK_LEFT: + return "OneFingerFlickLeft"; + case ONE_FINGER_FLICK_RIGHT: + return "OneFingerFlickRight"; + case ONE_FINGER_FLICK_UP: + return "OneFingerFlickUp"; + case ONE_FINGER_FLICK_DOWN: + return "OneFingerFlickDown"; + case TWO_FINGERS_FLICK_UP: + return "TwoFingersFlickUp"; + case TWO_FINGERS_FLICK_DOWN: + return "TwoFingersFlickDown"; + case TWO_FINGERS_FLICK_LEFT: + return "TwoFingersFlickLeft"; + case TWO_FINGERS_FLICK_RIGHT: + return "TwoFingersFlickRight"; + case THREE_FINGERS_FLICK_LEFT: + return "ThreeFingersFlickLeft"; + case THREE_FINGERS_FLICK_RIGHT: + return "ThreeFingersFlickRight"; + case THREE_FINGERS_FLICK_UP: + return "ThreeFingersFlickUp"; + case THREE_FINGERS_FLICK_DOWN: + return "ThreeFingersFlickDown"; + case ONE_FINGER_SINGLE_TAP: + return "OneFingerSingleTap"; + case ONE_FINGER_DOUBLE_TAP: + return "OneFingerDoubleTap"; + case ONE_FINGER_TRIPLE_TAP: + return "OneFingerTripleTap"; + case TWO_FINGERS_SINGLE_TAP: + return "TwoFingersSingleTap"; + case TWO_FINGERS_DOUBLE_TAP: + return "TwoFingersDoubleTap"; + case TWO_FINGERS_TRIPLE_TAP: + return "TwoFingersTripleTap"; + case THREE_FINGERS_SINGLE_TAP: + return "ThreeFingersSingleTap"; + case THREE_FINGERS_DOUBLE_TAP: + return "ThreeFingersDoubleTap"; + case THREE_FINGERS_TRIPLE_TAP: + return "ThreeFingersTripleTap"; + case ONE_FINGER_FLICK_LEFT_RETURN: + return "OneFingerFlickLeftReturn"; + case ONE_FINGER_FLICK_RIGHT_RETURN: + return "OneFingerFlickRightReturn"; + case ONE_FINGER_FLICK_UP_RETURN: + return "OneFingerFlickUpReturn"; + case ONE_FINGER_FLICK_DOWN_RETURN: + return "OneFingerFlickDownReturn"; + case TWO_FINGERS_FLICK_LEFT_RETURN: + return "TwoFingersFlickLeftReturn"; + case TWO_FINGERS_FLICK_RIGHT_RETURN: + return "TwoFingersFlickRightReturn"; + case TWO_FINGERS_FLICK_UP_RETURN: + return "TwoFingersFlickUpReturn"; + case TWO_FINGERS_FLICK_DOWN_RETURN: + return "TwoFingersFlickDownReturn"; + case THREE_FINGERS_FLICK_LEFT_RETURN: + return "ThreeFingersFlickLeftReturn"; + case THREE_FINGERS_FLICK_RIGHT_RETURN: + return "ThreeFingersFlickRightReturn"; + case THREE_FINGERS_FLICK_UP_RETURN: + return "ThreeFingersFlickUpReturn"; + case THREE_FINGERS_FLICK_DOWN_RETURN: + return "ThreeFingersFlickDownReturn"; + default: + ERROR("unhandled gesture enum"); + return NULL; + } +} + +void dbus_gesture_adapter_emit(const Gesture_Info * info) +{ + const char *name; + + if (!service) + return; + + name = _gesture_enum_to_string(info->type); + if (!name) + return; + + if (!eldbus_service_signal_emit(service, GESTURE_DETECTED, name, info->x_beg, info->y_beg, info->x_end, info->y_end, info->state)) { + ERROR("Unable to send GestureDetected signal"); + } else + DEBUG("Successfullt send GestureDetected singal"); +} diff --git a/src/elm_access_adapter.c b/src/elm_access_adapter.c new file mode 100644 index 0000000..fc46227 --- /dev/null +++ b/src/elm_access_adapter.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "elm_access_adapter.h" +#include "logger.h" + +static void _get_root_coords(Ecore_X_Window win, int *x, int *y) +{ + Ecore_X_Window root = ecore_x_window_root_first_get(); + Ecore_X_Window parent = ecore_x_window_parent_get(win); + int wx, wy; + + if (x) + *x = 0; + if (y) + *y = 0; + + while (parent && (root != parent)) { + ecore_x_window_geometry_get(parent, &wx, &wy, NULL, NULL); + if (x) + *x += wx; + if (y) + *y += wy; + parent = ecore_x_window_parent_get(parent); + } +} + +static void _send_ecore_x_client_msg(Ecore_X_Window win, int x, int y, Eina_Bool activate) +{ + int x_win, y_win; + long type; + _get_root_coords(win, &x_win, &y_win); + DEBUG("Window screen size:%d %d", x_win, y_win); + DEBUG("activate keyboard: %d %d", x, y); + + if (activate) + type = ECORE_X_ATOM_E_ILLUME_ACCESS_ACTION_ACTIVATE; + else + type = ECORE_X_ATOM_E_ILLUME_ACCESS_ACTION_READ; + + ecore_x_client_message32_send(win, ECORE_X_ATOM_E_ILLUME_ACCESS_CONTROL, ECORE_X_EVENT_MASK_WINDOW_CONFIGURE, win, type, x - x_win, y - y_win, 0); +} + +void elm_access_adaptor_emit_activate(Ecore_X_Window win, int x, int y) +{ + _send_ecore_x_client_msg(win, x, y, EINA_TRUE); +} + +void elm_access_adaptor_emit_read(Ecore_X_Window win, int x, int y) +{ + _send_ecore_x_client_msg(win, x, y, EINA_FALSE); +} diff --git a/src/flat_navi.c b/src/flat_navi.c new file mode 100644 index 0000000..72aac40 --- /dev/null +++ b/src/flat_navi.c @@ -0,0 +1,693 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flat_navi.h" +#include "logger.h" + +struct _FlatNaviContext { + AtspiAccessible *root; + AtspiAccessible *current; + AtspiAccessible *first; + AtspiAccessible *last; +}; + +static const AtspiStateType required_states[] = { + ATSPI_STATE_SHOWING, + ATSPI_STATE_VISIBLE, + ATSPI_STATE_FOCUSABLE, + ATSPI_STATE_LAST_DEFINED +}; + +static const AtspiRole interesting_roles[] = { + ATSPI_ROLE_CALENDAR, + ATSPI_ROLE_CHECK_BOX, + ATSPI_ROLE_COLOR_CHOOSER, + ATSPI_ROLE_COMBO_BOX, + ATSPI_ROLE_DATE_EDITOR, + ATSPI_ROLE_DIALOG, + ATSPI_ROLE_FILE_CHOOSER, + ATSPI_ROLE_FILLER, + ATSPI_ROLE_FONT_CHOOSER, + ATSPI_ROLE_GLASS_PANE, + ATSPI_ROLE_HEADER, + ATSPI_ROLE_HEADING, + ATSPI_ROLE_ICON, + ATSPI_ROLE_ENTRY, + ATSPI_ROLE_LABEL, + ATSPI_ROLE_LINK, + ATSPI_ROLE_LIST_ITEM, + ATSPI_ROLE_MENU_ITEM, + ATSPI_ROLE_PANEL, + ATSPI_ROLE_PARAGRAPH, + ATSPI_ROLE_PASSWORD_TEXT, + ATSPI_ROLE_POPUP_MENU, + ATSPI_ROLE_PUSH_BUTTON, + ATSPI_ROLE_PROGRESS_BAR, + ATSPI_ROLE_RADIO_BUTTON, + ATSPI_ROLE_RADIO_MENU_ITEM, + ATSPI_ROLE_SLIDER, + ATSPI_ROLE_SPIN_BUTTON, + ATSPI_ROLE_TABLE_CELL, + ATSPI_ROLE_TEXT, + ATSPI_ROLE_TOGGLE_BUTTON, + ATSPI_ROLE_TOOL_TIP, + ATSPI_ROLE_TREE_ITEM, + ATSPI_ROLE_LAST_DEFINED +}; + +static Eina_Bool _has_escape_action(AtspiAccessible * obj) +{ + Eina_Bool ret = EINA_FALSE; + + AtspiAction *action = NULL; + + action = atspi_accessible_get_action_iface(obj); + if (action) { + int i = 0; + for (; i < atspi_action_get_n_actions(action, NULL); i++) { + gchar *action_name = atspi_action_get_action_name(action, i, NULL); + Eina_Bool equal = !strcmp(action_name, "escape"); + g_free(action_name); + if (equal) { + ret = EINA_TRUE; + break; + } + } + g_object_unref(action); + } + DEBUG("Obj %s %s escape action", atspi_accessible_get_role_name(obj, NULL), ret ? "has" : "doesn't have"); + return ret; +} + +static Eina_Bool _is_collapsed(AtspiStateSet * ss) +{ + if (!ss) + return EINA_FALSE; + + Eina_Bool ret = EINA_FALSE; + if (atspi_state_set_contains(ss, ATSPI_STATE_EXPANDABLE) && !atspi_state_set_contains(ss, ATSPI_STATE_EXPANDED)) + ret = EINA_TRUE; + + return ret; +} + +static Eina_Bool _object_is_item(AtspiAccessible * obj) +{ + if (!obj) + return EINA_FALSE; + + Eina_Bool ret = EINA_FALSE; + AtspiRole role = atspi_accessible_get_role(obj, NULL); + if (role == ATSPI_ROLE_LIST_ITEM || role == ATSPI_ROLE_MENU_ITEM) + ret = EINA_TRUE; + DEBUG("IS ITEM %d", ret); + return ret; +} + +static AtspiAccessible *_get_object_in_relation(AtspiAccessible * source, AtspiRelationType search_type) +{ + GArray *relations; + AtspiAccessible *ret = NULL; + AtspiRelation *relation; + AtspiRelationType type; + int i; + if (source) { + DEBUG("CHECKING RELATIONS"); + relations = atspi_accessible_get_relation_set(source, NULL); + if (relations) { + for (i = 0; i < relations->len; i++) { + DEBUG("ALL RELATIONS FOUND: %d", relations->len); + relation = g_array_index(relations, AtspiRelation *, i); + type = atspi_relation_get_relation_type(relation); + DEBUG("RELATION: %d", type); + + if (type == search_type) { + ret = atspi_relation_get_target(relation, 0); + DEBUG("SEARCHED RELATION FOUND"); + break; + } + } + g_array_free(relations, TRUE); + } + } + return ret; +} + +static Eina_Bool _accept_object(AtspiAccessible * obj) +{ + DEBUG("START"); + if (!obj) + return EINA_FALSE; + + Eina_Bool ret = EINA_FALSE; + gchar *name = NULL; + gchar *desc = NULL; + AtspiAction *action = NULL; + AtspiEditableText *etext = NULL; + AtspiText *text = NULL; + AtspiValue *value = NULL; + AtspiStateSet *ss = NULL; + AtspiComponent *component; + AtspiRect *extent; + + AtspiRole r = atspi_accessible_get_role(obj, NULL); + + switch (r) { + case ATSPI_ROLE_APPLICATION: + case ATSPI_ROLE_FILLER: + case ATSPI_ROLE_SCROLL_PANE: + case ATSPI_ROLE_SPLIT_PANE: + case ATSPI_ROLE_WINDOW: + case ATSPI_ROLE_IMAGE: + case ATSPI_ROLE_LIST: + case ATSPI_ROLE_PAGE_TAB_LIST: + case ATSPI_ROLE_TOOL_BAR: + case ATSPI_ROLE_REDUNDANT_OBJECT: + return EINA_FALSE; + case ATSPI_ROLE_DIALOG: + if (!_has_escape_action(obj)) + return EINA_FALSE; + break; + default: + break; + } + + // When given accessibility object is controlled by other object we consider + // it as not "user-presentable" on and skip it in navigation tree + AtspiAccessible *relation = _get_object_in_relation(obj, ATSPI_RELATION_CONTROLLED_BY); + if (relation) + { + g_object_unref(relation); + return EINA_FALSE; + } + + ss = atspi_accessible_get_state_set(obj); + if (ss) { + if (_object_is_item(obj)) { + AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL); + if (parent) { + AtspiStateSet *pss = atspi_accessible_get_state_set(parent); + g_object_unref(parent); + if (pss) { + ret = atspi_state_set_contains(pss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(pss, ATSPI_STATE_VISIBLE) && !_is_collapsed(pss); + DEBUG("ITEM HAS SHOWING && VISIBLE && NOT COLLAPSED PARENT %d", ret); + g_object_unref(pss); + g_object_unref(ss); + return ret; + } + } + } else { + /* Extent of candidate object could be 0 */ + component = atspi_accessible_get_component_iface(obj); + extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL); + g_object_unref(component); + + if (extent->width <= 0 || extent->height <= 0) { + g_free(extent); + g_object_unref(ss); + return EINA_FALSE; + } + g_free(extent); + + ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(ss, ATSPI_STATE_VISIBLE); + } + g_object_unref(ss); + } + if (!ret) { + return EINA_FALSE; + } + + name = atspi_accessible_get_name(obj, NULL); + + ret = EINA_FALSE; + if (name) { + if (strncmp(name, "\0", 1)) { + DEBUG("Has name:[%s]", name); + ret = EINA_TRUE; + } + g_free(name); + } + if (!ret) { + desc = atspi_accessible_get_description(obj, NULL); + if (desc) { + if (strncmp(desc, "\0", 1)) { + DEBUG("Has description:[%s]", desc); + ret = EINA_TRUE; + } + g_free(desc); + } + } + if (!ret) { + action = atspi_accessible_get_action_iface(obj); + if (action) { + DEBUG("Has action interface"); + ret = EINA_TRUE; + g_object_unref(action); + } + } + if (!ret) { + value = atspi_accessible_get_value_iface(obj); + if (value) { + DEBUG("Has value interface"); + ret = EINA_TRUE; + g_object_unref(value); + } + } + if (!ret) { + etext = atspi_accessible_get_editable_text_iface(obj); + if (etext) { + DEBUG("Has editable text interface"); + ret = EINA_TRUE; + g_object_unref(etext); + } + } + if (!ret) { + text = atspi_accessible_get_text_iface(obj); + if (text) { + DEBUG("Has text interface"); + ret = EINA_TRUE; + g_object_unref(text); + } + } + + DEBUG("END:%d", ret); + return ret; +} + +#ifdef SCREEN_READER_FLAT_NAVI_TEST_DUMMY_IMPLEMENTATION +Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target) +{ + return EINA_FALSE; +} +#else + +int _object_has_modal_state(AtspiAccessible * obj) +{ + if (!obj) + return EINA_FALSE; + + Eina_Bool ret = EINA_FALSE; + + AtspiStateSet *ss = atspi_accessible_get_state_set(obj); + + if (atspi_state_set_contains(ss, ATSPI_STATE_MODAL)) + ret = EINA_TRUE; + g_object_unref(ss); + return ret; +} + +Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target) +{ + if (!ctx || !target) + return EINA_FALSE; + + if (!ctx->root) { + DEBUG("NO top window"); + return EINA_FALSE; + } + + AtspiAccessible *current_obj = flat_navi_context_current_get(ctx); + + Eina_Bool ret = EINA_FALSE; + GError *error = NULL; + + AtspiAccessible *obj = g_object_ref(ctx->root); + AtspiAccessible *youngest_ancestor_in_context = (_accept_object(obj) ? g_object_ref(obj) : NULL); + AtspiComponent *component; + Eina_Bool look_for_next_descendant = EINA_TRUE; + + while (look_for_next_descendant) { + component = atspi_accessible_get_component_iface(obj); + + g_object_unref(obj); + obj = component ? atspi_component_get_accessible_at_point(component, x_cord, y_cord, ATSPI_COORD_TYPE_WINDOW, &error) : NULL; + g_clear_object(&component); + + if (error) { + DEBUG("Got error from atspi_component_get_accessible_at_point, domain: %i, code: %i, message: %s", error->domain, error->code, error->message); + g_clear_error(&error); + g_clear_object(&obj); + if (youngest_ancestor_in_context) + g_clear_object(&youngest_ancestor_in_context); + look_for_next_descendant = EINA_FALSE; + } else if (obj) { + DEBUG("Found object %s, role %s", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL)); + if (_accept_object(obj)) { + DEBUG("Object %s with role %s fulfills highlight conditions", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL)); + if (youngest_ancestor_in_context) + g_object_unref(youngest_ancestor_in_context); + youngest_ancestor_in_context = g_object_ref(obj); + } + } else { + g_clear_object(&obj); + look_for_next_descendant = EINA_FALSE; + } + } + + if (youngest_ancestor_in_context && !_object_has_modal_state(youngest_ancestor_in_context)) { + if (youngest_ancestor_in_context == current_obj || flat_navi_context_current_set(ctx, youngest_ancestor_in_context)) { + DEBUG("Setting highlight to object %s with role %s", atspi_accessible_get_name(youngest_ancestor_in_context, NULL), atspi_accessible_get_role_name(youngest_ancestor_in_context, NULL)); + *target = youngest_ancestor_in_context; + ret = EINA_TRUE; + } + } else + DEBUG("NO widget under (%d, %d) found or the same widget under hover", x_cord, y_cord); + DEBUG("END"); + return ret; +} +#endif + +AtspiAccessible *_get_child(AtspiAccessible * obj, int i) +{ + DEBUG("START:%d", i); + if (i < 0) { + DEBUG("END"); + return NULL; + } + if (!obj) { + DEBUG("END"); + return NULL; + } + int cc = atspi_accessible_get_child_count(obj, NULL); + if (cc == 0 || i >= cc) { + DEBUG("END"); + return NULL; + } + return atspi_accessible_get_child_at_index(obj, i, NULL); +} + +static Eina_Bool _has_next_sibling(AtspiAccessible * obj, int next_sibling_idx_modifier) +{ + Eina_Bool ret = EINA_FALSE; + if (!obj) return ret; + + int idx = atspi_accessible_get_index_in_parent(obj, NULL); + if (idx >= 0) { + AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL); + int cc = atspi_accessible_get_child_count(parent, NULL); + g_object_unref(parent); + if ((next_sibling_idx_modifier > 0 && idx < cc - 1) || (next_sibling_idx_modifier < 0 && idx > 0)) { + ret = EINA_TRUE; + } + } + return ret; +} + +AtspiAccessible *_directional_depth_first_search(AtspiAccessible * root, AtspiAccessible * start, int next_sibling_idx_modifier, Eina_Bool(*stop_condition) (AtspiAccessible *)) +{ + Eina_Bool start_is_not_defunct = EINA_FALSE; + AtspiStateSet *ss; + + if (start) { + AtspiStateSet *ss = atspi_accessible_get_state_set(start); + start_is_not_defunct = !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT); + g_object_unref(ss); + if (!start_is_not_defunct) + DEBUG("Start is defunct!"); + } + + AtspiAccessible *node = (start && start_is_not_defunct) + ? g_object_ref(start) + : (root ? g_object_ref(root) : NULL); + + if (!node) + return NULL; + + AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0) + ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO) + : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM); + + Eina_Bool relation_mode = EINA_FALSE; + if (next_related_in_direction) { + relation_mode = EINA_TRUE; + g_object_unref(next_related_in_direction); + } + + while (node) { + AtspiAccessible *prev_related_in_direction = (next_sibling_idx_modifier > 0) + ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM) + : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO); + + if (node != start && (relation_mode || !prev_related_in_direction) && stop_condition(node)) { + g_object_unref(prev_related_in_direction); + return node; + } + + AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0) + ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO) + : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM); + + DEBUG("RELATION MODE: %d", relation_mode); + if (!prev_related_in_direction) + DEBUG("PREV IN RELATION NULL"); + if (!next_related_in_direction) + DEBUG("NEXT IN RELATION NULL"); + + if ((!relation_mode && !prev_related_in_direction && next_related_in_direction) || (relation_mode && next_related_in_direction)) { + DEBUG("APPLICABLE FOR RELATION NAVIG"); + g_object_unref(prev_related_in_direction); + relation_mode = EINA_TRUE; + g_object_unref(node); + node = next_related_in_direction; + } else { + g_object_unref(prev_related_in_direction); + g_object_unref(next_related_in_direction); + relation_mode = EINA_FALSE; + int cc = atspi_accessible_get_child_count(node, NULL); + ss = atspi_accessible_get_state_set(node); + + if (cc > 0 && atspi_state_set_contains(ss, ATSPI_STATE_SHOWING)) // walk down + { + int idx = next_sibling_idx_modifier > 0 ? 0 : cc - 1; + g_object_unref(node); + node = atspi_accessible_get_child_at_index(node, idx, NULL); + DEBUG("DFS DOWN"); + } else { + while (!_has_next_sibling(node, next_sibling_idx_modifier) || node == root) // no next sibling + { + DEBUG("DFS NO SIBLING"); + if (!node || node == root) { + DEBUG("DFS END"); + g_object_unref(node); + g_object_unref(ss); + return NULL; + } + g_object_unref(node); + node = atspi_accessible_get_parent(node, NULL); // walk up... + DEBUG("DFS UP"); + } + int idx = atspi_accessible_get_index_in_parent(node, NULL); + g_object_unref(node); + node = atspi_accessible_get_child_at_index(atspi_accessible_get_parent(node, NULL), idx + next_sibling_idx_modifier, NULL); //... and next + DEBUG("DFS NEXT %d", idx + next_sibling_idx_modifier); + } + g_object_unref(ss); + } + } + DEBUG("DFS END"); + return NULL; +} + +AtspiAccessible *_first(FlatNaviContext * ctx) +{ + DEBUG("START"); + return _directional_depth_first_search(ctx->root, NULL, 1, &_accept_object); +} + +AtspiAccessible *_last(FlatNaviContext * ctx) +{ + DEBUG("START"); + return _directional_depth_first_search(ctx->root, NULL, -1, &_accept_object); +} + +AtspiAccessible *_next(FlatNaviContext * ctx) +{ + DEBUG("START"); + AtspiAccessible *root = ctx->root; + AtspiAccessible *current = ctx->current; + AtspiAccessible *ret = NULL; + + ret = _directional_depth_first_search(root, current, 1, &_accept_object); + + if (current && !ret) { + DEBUG("DFS SECOND PASS"); + ret = _directional_depth_first_search(root, NULL, 1, &_accept_object); + } + return ret; +} + +AtspiAccessible *_prev(FlatNaviContext * ctx) +{ + DEBUG("START\n"); + AtspiAccessible *root = ctx->root; + AtspiAccessible *current = ctx->current; + AtspiAccessible *ret = NULL; + + ret = _directional_depth_first_search(root, current, -1, &_accept_object); + if (current && !ret) { + DEBUG("DFS SECOND PASS"); + ret = _directional_depth_first_search(root, NULL, -1, &_accept_object); + } + return ret; +} + +int flat_navi_context_current_children_count_visible_get(FlatNaviContext * ctx) +{ + if (!ctx) + return -1; + int count = 0; + /* + AtspiAccessible *obj = NULL; + AtspiStateSet *ss = NULL; + + Eina_List *l, *l2, *line; + AtspiAccessible *current = flat_navi_context_current_get(ctx); + AtspiAccessible *parent = atspi_accessible_get_parent (current, NULL); + + EINA_LIST_FOREACH(ctx->lines, l, line) + { + EINA_LIST_FOREACH(line, l2, obj) + { + ss = atspi_accessible_get_state_set(obj); + if (atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && parent == atspi_accessible_get_parent(obj, NULL)) + count++; + g_object_unref(ss); + } + } + */ + return count; +} + +FlatNaviContext *flat_navi_context_create(AtspiAccessible * root) +{ + DEBUG("START"); + if (!root) + return NULL; + FlatNaviContext *ret; + ret = calloc(1, sizeof(FlatNaviContext)); + if (!ret) + return NULL; + + ret->root = root; + ret->current = _first(ret); + return ret; +} + +void flat_navi_context_free(FlatNaviContext * ctx) +{ + if (!ctx) + return; + free(ctx); +} + +AtspiAccessible *flat_navi_context_root_get(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + return ctx->root; +} + +const AtspiAccessible *flat_navi_context_first_get(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + return _first(ctx); +} + +const AtspiAccessible *flat_navi_context_last_get(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + return _last(ctx); +} + +AtspiAccessible *flat_navi_context_current_get(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + return ctx->current; +} + +Eina_Bool flat_navi_context_current_set(FlatNaviContext * ctx, AtspiAccessible * target) +{ + if (!ctx || !target) + return EINA_FALSE; + + ctx->current = target; + + return EINA_TRUE; +} + +AtspiAccessible *flat_navi_context_next(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + AtspiAccessible *ret = _next(ctx); + ctx->current = ret; + + return ret; + +} + +AtspiAccessible *flat_navi_context_prev(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + AtspiAccessible *ret = _prev(ctx); + ctx->current = ret; + + return ret; +} + +AtspiAccessible *flat_navi_context_first(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + AtspiAccessible *ret = _first(ctx); + ctx->current = ret; + + return ret; +} + +AtspiAccessible *flat_navi_context_last(FlatNaviContext * ctx) +{ + if (!ctx) + return NULL; + + AtspiAccessible *ret = _last(ctx); + ctx->current = ret; + + return ret; +} + +Eina_Bool flat_navi_is_valid(FlatNaviContext * context, AtspiAccessible * new_root) +{ + Eina_Bool ret = EINA_FALSE; + if (!context || !context->current || context->root != new_root) + return ret; + AtspiStateSet *ss = atspi_accessible_get_state_set(context->current); + + ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT); + g_object_unref(ss); + return ret; +} diff --git a/src/keyboard_tracker.c b/src/keyboard_tracker.c new file mode 100644 index 0000000..70f62d9 --- /dev/null +++ b/src/keyboard_tracker.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "keyboard_tracker.h" +#include "logger.h" + +static AtspiDeviceListener *listener; +static Keyboard_Tracker_Cb user_cb; +static void *user_data; +static Ecore_Event_Handler *root_xwindow_property_changed_hld = NULL; +static Ecore_Event_Handler *active_xwindow_property_changed_hld = NULL; + +static void _check_keyboard_state(Ecore_X_Window keyboard_win) +{ + Ecore_X_Virtual_Keyboard_State keyboard_state; + static Ecore_X_Virtual_Keyboard_State last_keyboard_state = ECORE_X_VIRTUAL_KEYBOARD_STATE_OFF; + + if (!keyboard_win) + { + return; + } + + keyboard_state = ecore_x_e_virtual_keyboard_state_get(keyboard_win); + if (keyboard_state == last_keyboard_state) + { + return; + } + + if (keyboard_state == ECORE_X_VIRTUAL_KEYBOARD_STATE_ON) + { + tts_speak (_("IDS_VISUAL_KEYBOARD_ENABLED"), EINA_FALSE); + last_keyboard_state = keyboard_state; + } + else if (keyboard_state == ECORE_X_VIRTUAL_KEYBOARD_STATE_OFF) + { + tts_speak (_("IDS_VISUAL_KEYBOARD_DISABLED"), EINA_FALSE); + last_keyboard_state = keyboard_state; + } +} + +static Eina_Bool _active_xwindow_property_changed_cb(void *data, int type, void *event) +{ + Ecore_X_Event_Window_Property *wp; + wp = (Ecore_X_Event_Window_Property *)event; + + if (!wp) + { + return EINA_FALSE; + } + + if (wp->atom == ECORE_X_ATOM_E_VIRTUAL_KEYBOARD_STATE) + { + DEBUG("keyboard state event"); + _check_keyboard_state(wp->win); + } + + return EINA_TRUE; +} + +void active_xwindow_property_tracker_register() +{ + Ecore_X_Window active_window = 0; + ecore_x_window_prop_xid_get(ecore_x_window_root_first_get(), ECORE_X_ATOM_NET_ACTIVE_WINDOW, ECORE_X_ATOM_WINDOW, &active_window, 1); + if (active_window) + { + ecore_x_event_mask_set(active_window, ECORE_X_EVENT_MASK_WINDOW_PROPERTY); + active_xwindow_property_changed_hld = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, _active_xwindow_property_changed_cb, NULL); + } +} + +void active_xwindow_property_tracker_unregister() +{ + if (active_xwindow_property_changed_hld) + { + ecore_event_handler_del(active_xwindow_property_changed_hld); + active_xwindow_property_changed_hld = NULL; + } +} + +static Eina_Bool _root_xwindow_property_changed_cb(void *data, int type, void *event) +{ + Ecore_X_Event_Window_Property *wp; + wp = (Ecore_X_Event_Window_Property *)event; + + if (!wp) + { + return EINA_FALSE; + } + + if (wp->atom == ECORE_X_ATOM_NET_ACTIVE_WINDOW) + { + DEBUG("active window change"); + active_xwindow_property_tracker_unregister(); + active_xwindow_property_tracker_register(); + } + + return EINA_TRUE; +} + +void root_xwindow_property_tracker_register() +{ + Ecore_X_Window root_window; + + root_window = ecore_x_window_root_first_get(); + if (root_window) + { + ecore_x_event_mask_set(root_window, ECORE_X_EVENT_MASK_WINDOW_PROPERTY); + root_xwindow_property_changed_hld = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, _root_xwindow_property_changed_cb, NULL); + } +} + +void root_xwindow_property_tracker_unregister() +{ + if (root_xwindow_property_changed_hld) + { + ecore_event_handler_del(root_xwindow_property_changed_hld); + root_xwindow_property_changed_hld = NULL; + } +} + +static gboolean device_cb(const AtspiDeviceEvent * stroke, void *data) +{ + Key k; + if (!strcmp(stroke->event_string, "KP_Up")) + k = KEY_UP; + else if (!strcmp(stroke->event_string, "KP_Down")) + k = KEY_DOWN; + else if (!strcmp(stroke->event_string, "KP_Left")) + k = KEY_LEFT; + else if (!strcmp(stroke->event_string, "KP_Right")) + k = KEY_RIGHT; + else + return FALSE; + + if (user_cb) + user_cb(user_data, k); + + return TRUE; +} + +void keyboard_tracker_init(void) +{ + listener = atspi_device_listener_new(device_cb, NULL, NULL); + atspi_register_keystroke_listener(listener, NULL, 0, ATSPI_KEY_PRESSED, ATSPI_KEYLISTENER_SYNCHRONOUS | ATSPI_KEYLISTENER_CANCONSUME, NULL); + active_xwindow_property_tracker_register(); + root_xwindow_property_tracker_register(); + DEBUG("keyboard tracker init"); +} + +void keyboard_tracker_register(Keyboard_Tracker_Cb cb, void *data) +{ + user_cb = cb; + user_data = data; +} + +void keyboard_tracker_shutdown(void) +{ + atspi_deregister_keystroke_listener(listener, NULL, 0, ATSPI_KEY_PRESSED, NULL); + root_xwindow_property_tracker_unregister(); + active_xwindow_property_tracker_unregister(); + DEBUG("keyboard tracker shutdown"); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2b20ab7 --- /dev/null +++ b/src/main.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "navigator.h" +#include "window_tracker.h" +#include "logger.h" +#include "screen_reader.h" +#include "screen_reader_gestures.h" +#include "screen_reader_switch.h" + +#define MAX_STACK_FRAMES 64 +static void *stack_traces[MAX_STACK_FRAMES]; + +void posix_print_stack_trace(FILE * log_file) +{ + int i, trace_size = 0; + char **messages = (char **)NULL; + + trace_size = backtrace(stack_traces, MAX_STACK_FRAMES); + messages = backtrace_symbols(stack_traces, trace_size); + + /* skip the first couple stack frames (as they are this function and + our handler) and also skip the last frame as it's (always?) junk. */ + // for (i = 3; i < (trace_size - 1); ++i) + for (i = 0; i < trace_size; ++i) // we'll use this for now so you can see what's going on + { + fprintf(log_file, " BACKTRACE LINE %i: %s\n", i, messages[i]); + } + if (messages) { + free(messages); + } +} + +void print_warning(int sig, siginfo_t * siginfo, FILE * log_file) +{ + switch (sig) { + case SIGSEGV: + fputs("Caught SIGSEGV: Segmentation Fault\n", log_file); + break; + case SIGINT: + fputs("Caught SIGINT: Interactive attention signal, (usually ctrl+c)\n", log_file); + break; + case SIGFPE: + switch (siginfo->si_code) { + case FPE_INTDIV: + fputs("Caught SIGFPE: (integer divide by zero)\n", log_file); + break; + case FPE_INTOVF: + fputs("Caught SIGFPE: (integer overflow)\n", log_file); + break; + case FPE_FLTDIV: + fputs("Caught SIGFPE: (floating-point divide by zero)\n", log_file); + break; + case FPE_FLTOVF: + fputs("Caught SIGFPE: (floating-point overflow)\n", log_file); + break; + case FPE_FLTUND: + fputs("Caught SIGFPE: (floating-point underflow)\n", log_file); + break; + case FPE_FLTRES: + fputs("Caught SIGFPE: (floating-point inexact result)\n", log_file); + break; + case FPE_FLTINV: + fputs("Caught SIGFPE: (floating-point invalid operation)\n", log_file); + break; + case FPE_FLTSUB: + fputs("Caught SIGFPE: (subscript out of range)\n", log_file); + break; + default: + fputs("Caught SIGFPE: Arithmetic Exception\n", log_file); + break; + } + break; + case SIGILL: + switch (siginfo->si_code) { + case ILL_ILLOPC: + fputs("Caught SIGILL: (illegal opcode)\n", log_file); + break; + case ILL_ILLOPN: + fputs("Caught SIGILL: (illegal operand)\n", log_file); + break; + case ILL_ILLADR: + fputs("Caught SIGILL: (illegal addressing mode)\n", log_file); + break; + case ILL_ILLTRP: + fputs("Caught SIGILL: (illegal trap)\n", log_file); + break; + case ILL_PRVOPC: + fputs("Caught SIGILL: (privileged opcode)\n", log_file); + break; + case ILL_PRVREG: + fputs("Caught SIGILL: (privileged register)\n", log_file); + break; + case ILL_COPROC: + fputs("Caught SIGILL: (coprocessor error)\n", log_file); + break; + case ILL_BADSTK: + fputs("Caught SIGILL: (internal stack error)\n", log_file); + break; + default: + fputs("Caught SIGILL: Illegal Instruction\n", log_file); + break; + } + break; + case SIGTERM: + fputs("Caught SIGTERM: a termination request was sent to the program\n", log_file); + break; + case SIGABRT: + fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", log_file); + break; + default: + break; + } +} + +void posix_signal_handler(int sig, siginfo_t * siginfo, void *context) +{ + char file_name[256]; + struct tm *timeinfo; + time_t rawtime = time(NULL); + timeinfo = localtime(&rawtime); + if (timeinfo) + snprintf(file_name, sizeof(file_name), "/tmp/screen_reader_crash_stacktrace_%i%i%i_%i:%i:%i_pid_%i.log", timeinfo->tm_year, timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, getpid()); + else + snprintf(file_name, sizeof(file_name), "/tmp/screen_reader_crash_stacktrace_pid_%i.log", getpid()); + + FILE *log_file = fopen(file_name, "w"); + if (log_file) { + (void)context; + print_warning(sig, siginfo, stderr); + + /* check file if it is symbolic link */ + struct stat lstat_info; + if (lstat(file_name, &lstat_info) != -1) { + print_warning(sig, siginfo, log_file); + posix_print_stack_trace(log_file); + } + + fclose(log_file); + log_file = NULL; + } + + _Exit(1); +} + +static uint8_t alternate_stack[SIGSTKSZ]; +void set_signal_handler() +{ + /* setup alternate stack */ + { + stack_t ss = { }; + /* malloc is usually used here, I'm not 100% sure my static allocation + is valid but it seems to work just fine. */ + ss.ss_sp = (void *)alternate_stack; + ss.ss_size = SIGSTKSZ; + ss.ss_flags = 0; + + if (sigaltstack(&ss, NULL) != 0) { + err(1, "sigaltstack"); + } + } + + /* register our signal handlers */ + { + struct sigaction sig_action = { }; + sig_action.sa_sigaction = posix_signal_handler; + sigemptyset(&sig_action.sa_mask); + + sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK; + + if (sigaction(SIGSEGV, &sig_action, NULL) != 0) { + err(1, "sigaction"); + } + if (sigaction(SIGFPE, &sig_action, NULL) != 0) { + err(1, "sigaction"); + } + if (sigaction(SIGINT, &sig_action, NULL) != 0) { + err(1, "sigaction"); + } + if (sigaction(SIGILL, &sig_action, NULL) != 0) { + err(1, "sigaction"); + } + if (sigaction(SIGTERM, &sig_action, NULL) != 0) { + err(1, "sigaction"); + } + if (sigaction(SIGABRT, &sig_action, NULL) != 0) { + err(1, "sigaction"); + } + } +} + +static bool app_create(void *data) +{ + if (vconf_set_bool(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS, 1)) + ERROR("Can't set value of %s vconf key to 1", VCONFKEY_SETAPPL_ACCESSIBILITY_TTS); + DEBUG("atspi_init"); + atspi_init(); + DEBUG("logger_init"); + DEBUG("screen_reader_create_service"); + screen_reader_create_service(data); +#ifndef SCREEN_READER_TV + DEBUG("screen_reader_gestures_init"); + screen_reader_gestures_init(); + DEBUG("navigator_init"); + navigator_init(); +#endif + DEBUG("screen_reader_switch_enabled_set"); + screen_reader_switch_enabled_set(EINA_TRUE); + return true; +} + +static void app_terminate(void *data) +{ + DEBUG("screen reader terminating"); +#ifndef SCREEN_READER_TV + DEBUG("terminate navigator"); + navigator_shutdown(); + DEBUG("terminate gestures"); + screen_reader_gestures_shutdown(); +#endif + DEBUG("terminate service"); + screen_reader_terminate_service(data); + DEBUG("clear ScreenReaderEnabled property"); + screen_reader_switch_enabled_set(EINA_FALSE); + DEBUG("screen reader terminated"); + if (vconf_set_bool(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS, 0)) + ERROR("Can't set value of %s vconf key to 0", VCONFKEY_SETAPPL_ACCESSIBILITY_TTS); + DEBUG("libatspi terminated"); + atspi_exit(); +} + +static void app_control(app_control_h app_control, void *data) +{ + return; +} + +int main(int argc, char **argv) +{ + set_signal_handler(); + unsetenv("ELM_ATSPI_MODE"); + + + service_app_lifecycle_callback_s event_callback; + + event_callback.create = app_create; + event_callback.terminate = app_terminate; + event_callback.app_control = app_control; + + return service_app_main(argc, argv, &event_callback, get_pointer_to_service_data_struct()); +} diff --git a/src/navigator.c b/src/navigator.c new file mode 100644 index 0000000..c965cd8 --- /dev/null +++ b/src/navigator.c @@ -0,0 +1,2292 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "logger.h" +#include "navigator.h" +#include "window_tracker.h" +#include "keyboard_tracker.h" +#include "pivot_chooser.h" +#include "flat_navi.h" +#include "app_tracker.h" +#include "smart_notification.h" +#include "screen_reader_system.h" +#include "screen_reader_haptic.h" +#include "screen_reader_tts.h" +#include "screen_reader_gestures.h" +#include "dbus_gesture_adapter.h" +#include "elm_access_adapter.h" + +#define QUICKPANEL_DOWN TRUE +#define QUICKPANEL_UP FALSE + +#define DISTANCE_NB 8 +#define MENU_ITEM_TAB_INDEX_SIZE 16 +#define HOVERSEL_TRAIT_SIZE 200 +#define TTS_MAX_TEXT_SIZE 2000 +#define GESTURE_LIMIT 10 + +//Timeout in ms which will be used as interval for handling ongoing +//hoved gesture updates. It is introduced to improve performance. +//Even if user makes many mouse move events within hover gesture +//only 5 highlight updates a second will be performed. Else we will +//highly pollute dbus bus and and decrease highlight performance. +#define ONGOING_HOVER_GESTURE_INTERPRETATION_INTERVAL 200 + +#define DEBUG_MODE + +#define GERROR_CHECK(error)\ + if (error)\ + {\ + ERROR("Error_log:%s",error->message);\ + g_error_free(error);\ + error = NULL;\ + } + +static void on_window_activate(void *data, AtspiAccessible * window); + +typedef struct { + int x, y; +} last_focus_t; + +static last_focus_t gesture_start_p = { -1, -1 }; +static last_focus_t last_focus = { -1, -1 }; + +static AtspiAccessible *current_obj; +static AtspiComponent *current_comp = NULL; +static AtspiAccessible *top_window; +static FlatNaviContext *context; +static bool prepared = false; +static int counter = 0; +int _last_hover_event_time = -1; + +static struct { + AtspiAccessible *focused_object; + bool auto_review_on; +} s_auto_review = { +.focused_object = NULL,.auto_review_on = false}; + +char *state_to_char(AtspiStateType state) +{ + switch (state) { + case ATSPI_STATE_INVALID: + return strdup("ATSPI_STATE_INVALID"); + case ATSPI_STATE_ACTIVE: + return strdup("ATSPI_STATE_ACTIVE"); + case ATSPI_STATE_ARMED: + return strdup("ATSPI_STATE_ARMED"); + case ATSPI_STATE_BUSY: + return strdup("ATSPI_STATE_BUSY"); + case ATSPI_STATE_CHECKED: + return strdup("ATSPI_STATE_CHECKED"); + case ATSPI_STATE_COLLAPSED: + return strdup("ATSPI_STATE_COLLAPSED"); + case ATSPI_STATE_DEFUNCT: + return strdup("ATSPI_STATE_DEFUNCT"); + case ATSPI_STATE_EDITABLE: + return strdup("ATSPI_STATE_EDITABLE"); + case ATSPI_STATE_ENABLED: + return strdup("ATSPI_STATE_ENABLED"); + case ATSPI_STATE_EXPANDABLE: + return strdup("ATSPI_STATE_EXPANDABLE"); + case ATSPI_STATE_EXPANDED: + return strdup("ATSPI_STATE_EXPANDED"); + case ATSPI_STATE_FOCUSABLE: + return strdup("ATSPI_STATE_FOCUSABLE"); + case ATSPI_STATE_FOCUSED: + return strdup("ATSPI_STATE_FOCUSED"); + case ATSPI_STATE_HAS_TOOLTIP: + return strdup("ATSPI_STATE_HAS_TOOLTIP"); + case ATSPI_STATE_HORIZONTAL: + return strdup("ATSPI_STATE_HORIZONTAL"); + case ATSPI_STATE_ICONIFIED: + return strdup("ATSPI_STATE_ICONIFIED"); + case ATSPI_STATE_MULTI_LINE: + return strdup("ATSPI_STATE_MULTI_LINE"); + case ATSPI_STATE_MULTISELECTABLE: + return strdup("ATSPI_STATE_MULTISELECTABLE"); + case ATSPI_STATE_OPAQUE: + return strdup("ATSPI_STATE_OPAQUE"); + case ATSPI_STATE_PRESSED: + return strdup("ATSPI_STATE_PRESSED"); + case ATSPI_STATE_RESIZABLE: + return strdup("ATSPI_STATE_RESIZABLE"); + case ATSPI_STATE_SELECTABLE: + return strdup("ATSPI_STATE_SELECTABLE"); + case ATSPI_STATE_SELECTED: + return strdup("ATSPI_STATE_SELECTED"); + case ATSPI_STATE_SENSITIVE: + return strdup("ATSPI_STATE_SENSITIVE"); + case ATSPI_STATE_SHOWING: + return strdup("ATSPI_STATE_SHOWING"); + case ATSPI_STATE_SINGLE_LINE: + return strdup("ATSPI_STATE_SINGLE_LINE"); + case ATSPI_STATE_STALE: + return strdup("ATSPI_STATE_STALE"); + case ATSPI_STATE_TRANSIENT: + return strdup("ATSPI_STATE_TRANSIENT"); + case ATSPI_STATE_VERTICAL: + return strdup("ATSPI_STATE_VERTICAL"); + case ATSPI_STATE_VISIBLE: + return strdup("ATSPI_STATE_VISIBLE"); + case ATSPI_STATE_MANAGES_DESCENDANTS: + return strdup("ATSPI_STATE_MANAGES_DESCENDANTS"); + case ATSPI_STATE_INDETERMINATE: + return strdup("ATSPI_STATE_INDETERMINATE"); + case ATSPI_STATE_REQUIRED: + return strdup("ATSPI_STATE_REQUIRED"); + case ATSPI_STATE_TRUNCATED: + return strdup("ATSPI_STATE_TRUNCATED"); + case ATSPI_STATE_ANIMATED: + return strdup("ATSPI_STATE_ANIMATED"); + case ATSPI_STATE_INVALID_ENTRY: + return strdup("ATSPI_STATE_INVALID_ENTRY"); + case ATSPI_STATE_SUPPORTS_AUTOCOMPLETION: + return strdup("ATSPI_STATE_SUPPORTS_AUTOCOMPLETION"); + case ATSPI_STATE_SELECTABLE_TEXT: + return strdup("ATSPI_STATE_SELECTABLE_TEXT"); + case ATSPI_STATE_IS_DEFAULT: + return strdup("ATSPI_STATE_IS_DEFAULT"); + case ATSPI_STATE_VISITED: + return strdup("ATSPI_STATE_VISITED"); + case ATSPI_STATE_CHECKABLE: + return strdup("ATSPI_STATE_CHECKABLE"); + case ATSPI_STATE_HAS_POPUP: + return strdup("ATSPI_STATE_HAS_POPUP"); + case ATSPI_STATE_READ_ONLY: + return strdup("ATSPI_STATE_READ_ONLY"); + case ATSPI_STATE_LAST_DEFINED: + return strdup("ATSPI_STATE_LAST_DEFINED"); + case ATSPI_STATE_MODAL: + return strdup("ATSPI_STATE_MODAL"); + case ATSPI_STATE_HIGHLIGHTED: + return strdup("ATSPI_STATE_HIGHLIGHTED"); + case ATSPI_STATE_HIGHLIGHTABLE: + return strdup("ATSPI_STATE_HIGHLIGHTABLE"); + default: + return strdup("\0"); + } + +} + +static void display_info_about_object(AtspiAccessible * obj, bool display_parent_info) +{ + if(!obj) + { + return; + } + + DEBUG("START"); + DEBUG("------------------------"); + gchar *name = atspi_accessible_get_name(obj, NULL); + gchar *role = atspi_accessible_get_localized_role_name(obj, NULL); + gchar *description = atspi_accessible_get_description(obj, NULL); + char *state_name = NULL; + AtspiStateSet *st = atspi_accessible_get_state_set(obj); + GArray *states = atspi_state_set_get_states(st); + AtspiComponent *comp = atspi_accessible_get_component_iface(obj); + AtspiValue *value = atspi_accessible_get_value_iface(obj); + AtspiRect *rect_screen = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_SCREEN, NULL); + AtspiRect *rect_win = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_WINDOW, NULL); + + if(display_parent_info) { + AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL); + display_info_about_object(parent, false); + g_object_unref(parent); + } + + DEBUG("NAME:%s", name); + DEBUG("ROLE:%s", role); + DEBUG("DESCRIPTION:%s", description); + DEBUG("CHILDS:%d", atspi_accessible_get_child_count(obj, NULL)); + DEBUG("HIGHLIGHT_INDEX:%d", atspi_component_get_highlight_index(comp, NULL)); + DEBUG("INDEX IN PARENT:%d", atspi_accessible_get_index_in_parent(obj, NULL)); + if (value) { + DEBUG("VALUE:%f", atspi_value_get_current_value(value, NULL)); + DEBUG("VALUE MAX:%f", atspi_value_get_maximum_value(value, NULL)); + DEBUG("VALUE MIN:%f", atspi_value_get_minimum_value(value, NULL)); + g_object_unref(value); + } + DEBUG("STATES:"); + int a; + AtspiStateType stat; + for (a = 0; states && (a < states->len); ++a) { + stat = g_array_index(states, AtspiStateType, a); + state_name = state_to_char(stat); + DEBUG(" %s", state_name); + free(state_name); + } + g_array_free(states, 0); + DEBUG("LOCALE:%s", atspi_accessible_get_object_locale(obj, NULL)); + DEBUG("SIZE ON SCREEN, width:%d, height:%d", rect_screen->width, rect_screen->height); + DEBUG("POSITION ON SCREEN: x:%d y:%d", rect_screen->x, rect_screen->y); + DEBUG("SIZE ON WIN, width:%d, height:%d", rect_win->width, rect_win->height); + DEBUG("POSITION ON WIN: x:%d y:%d", rect_win->x, rect_win->y); + DEBUG("INTERFACES:"); + GArray *ifaces = atspi_accessible_get_interfaces(obj); + for (a = 0; ifaces && (a < ifaces->len); ++a) { + gchar * interface_name = g_array_index(ifaces, gchar *, a); + DEBUG(" %s", interface_name); + g_free(interface_name); + } + if (ifaces) + g_array_free(ifaces, FALSE); + + DEBUG("------------------------"); + DEBUG("END"); + g_free(name); + g_free(role); + g_free(description); +} + +char *generate_description_for_subtrees(AtspiAccessible * obj) +{ + DEBUG("START"); + + if (!obj) + return strdup(""); + return strdup(""); + /* + AtspiRole role; + int child_count; + int i; + char *name = NULL; + char *below = NULL; + char ret[TTS_MAX_TEXT_SIZE] = "\0"; + AtspiAccessible *child = NULL; + + int child_count = atspi_accessible_get_child_count(obj, NULL); + + role = atspi_accessible_get_role(obj, NULL); + + // Do not generate that for popups + if (role == ATSPI_ROLE_POPUP_MENU || role == ATSPI_ROLE_DIALOG) + return strdup(""); + + child_count = atspi_accessible_get_child_count(obj, NULL); + + DEBUG("There is %d children inside this object", child_count); + if (!child_count) + return strdup(""); + + for (i=0; i < child_count; i++) + { + child = atspi_accessible_get_child_at_index(obj, i, NULL); + name = atspi_accessible_get_name(child, NULL); + DEBUG("%d child name:%s", i, name); + if (name && strncmp(name, "\0", 1)) + { + strncat(ret, name, sizeof(ret) - strlen(ret) - 1); + } + strncat(ret, " ", sizeof(ret) - strlen(ret) - 1); + below = generate_description_for_subtrees(child); + DEBUG("%s from below", below); + if (strncmp(below, "\0", 1)) + { + strncat(ret, below, sizeof(ret) - strlen(ret) - 1); + } + + g_object_unref(child); + free(below); + free(name); + } + return strdup(ret); + */ +} + +static int _check_list_children_count(AtspiAccessible * obj) +{ + int list_count = 0; + int i; + AtspiAccessible *child = NULL; + + if (!obj) + return 0; + + if (atspi_accessible_get_role(obj, NULL) == ATSPI_ROLE_LIST) { + int children_count = atspi_accessible_get_child_count(obj, NULL); + + for (i = 0; i < children_count; i++) { + child = atspi_accessible_get_child_at_index(obj, i, NULL); + if (atspi_accessible_get_role(child, NULL) == ATSPI_ROLE_LIST_ITEM) + list_count++; + g_object_unref(child); + } + } + + return list_count; +} + +static int _find_popup_list_children_count(AtspiAccessible * obj) +{ + int list_items_count = 0; + int children_count = atspi_accessible_get_child_count(obj, NULL); + int i; + AtspiAccessible *child = NULL; + + list_items_count = _check_list_children_count(obj); + if (list_items_count > 0) + return list_items_count; + + for (i = 0; i < children_count; i++) { + child = atspi_accessible_get_child_at_index(obj, i, NULL); + list_items_count = _find_popup_list_children_count(child); + if (list_items_count > 0) + return list_items_count; + g_object_unref(child); + } + + return 0; +} + +static bool _widget_has_state(AtspiAccessible * obj, AtspiStateType type) +{ + Eina_Bool ret = EINA_FALSE; + AtspiStateSet *st = atspi_accessible_get_state_set(obj); + if (atspi_state_set_contains(st, type)) + ret = EINA_TRUE; + g_object_unref(st); + return ret; +} + +int get_accuracy(double val, int max_accuracy) +{ + char val_str[HOVERSEL_TRAIT_SIZE] = ""; + int position; + int accuracy; + + snprintf(val_str, HOVERSEL_TRAIT_SIZE, "%.*f", max_accuracy, val); + accuracy = max_accuracy; + position = strlen(val_str) - 1; + while ( position > 0 && val_str[position] == '0' ) { + --position; + --accuracy; + } + return accuracy; +} + +void add_slider_description(char *dest, uint dest_size, AtspiAccessible *obj) +{ + gchar *role_name; + AtspiValue *value_iface; + double val; + double min_val; + double max_val; + char trait[HOVERSEL_TRAIT_SIZE] = ""; + int accuracy; + + role_name = atspi_accessible_get_localized_role_name(obj, NULL); + if (role_name) { + strncat(dest, role_name, dest_size - strlen(dest) - 1); + g_free(role_name); + } + + value_iface = atspi_accessible_get_value_iface(obj); + if (!value_iface) { + return; + } + + accuracy = get_accuracy( atspi_value_get_minimum_increment(value_iface, NULL), 3 ); + val = atspi_value_get_current_value(value_iface, NULL); + max_val = atspi_value_get_maximum_value(value_iface, NULL); + min_val = atspi_value_get_minimum_value(value_iface, NULL); + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_SLIDER_VALUE"), accuracy, min_val, accuracy, max_val, accuracy, val); + strncat(dest, trait, dest_size - strlen(dest) - 1); + + if (_widget_has_state(obj, ATSPI_STATE_ENABLED)) { + strncat(dest, _("IDS_TRAIT_SLIDER_SWIPE_COMMUNICATE"), dest_size - strlen(dest) - 1); + } + g_object_unref(value_iface); +} + +char *generate_trait(AtspiAccessible * obj) +{ + if (!obj) + return strdup(""); + + AtspiRole role = atspi_accessible_get_role(obj, NULL); + AtspiStateSet *state_set = atspi_accessible_get_state_set(obj); + char ret[TTS_MAX_TEXT_SIZE] = "\0"; + switch (role) { + case ATSPI_ROLE_ENTRY: { + gchar *role_name = atspi_accessible_get_localized_role_name(obj, NULL); + if (role_name) { + strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + if (atspi_state_set_contains(state_set, ATSPI_STATE_FOCUSED)) + strncat(ret, _("IDS_TRAIT_TEXT_EDIT_FOCUSED"), sizeof(ret) - strlen(ret) - 1); + else + strncat(ret, _("IDS_TRAIT_TEXT_EDIT"), sizeof(ret) - strlen(ret) - 1); + g_free(role_name); + } + break; + } + case ATSPI_ROLE_MENU_ITEM: { + AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL); + int children_count = atspi_accessible_get_child_count(parent, NULL); + int index = atspi_accessible_get_index_in_parent(obj, NULL); + char tab_index[MENU_ITEM_TAB_INDEX_SIZE]; + snprintf(tab_index, MENU_ITEM_TAB_INDEX_SIZE, _("IDS_TRAIT_MENU_ITEM_TAB_INDEX"), index + 1, children_count); + strncat(ret, tab_index, sizeof(ret) - strlen(ret) - 1); + g_object_unref(parent); + break; + } + case ATSPI_ROLE_POPUP_MENU: { + int children_count = atspi_accessible_get_child_count(obj, NULL); + char trait[HOVERSEL_TRAIT_SIZE]; + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_CTX_POPUP")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_SHOWING")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, " ", sizeof(ret) - strlen(ret) - 1); + + snprintf(trait, HOVERSEL_TRAIT_SIZE, "%d", children_count); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, " ", sizeof(ret) - strlen(ret) - 1); + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_ITEMS")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_POPUP_CLOSE")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + break; + } + case ATSPI_ROLE_DIALOG: { + int children_count = _find_popup_list_children_count(obj); + char trait[HOVERSEL_TRAIT_SIZE]; + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_POPUP")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + + if (children_count > 0) { + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_SHOWING")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, " ", sizeof(ret) - strlen(ret) - 1); + + snprintf(trait, HOVERSEL_TRAIT_SIZE, "%d", children_count); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, " ", sizeof(ret) - strlen(ret) - 1); + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_ITEMS")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + } + + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_POPUP_CLOSE")); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + break; + } + case ATSPI_ROLE_GLASS_PANE: { + AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL); + int children_count = atspi_accessible_get_child_count(parent, NULL); + char trait[HOVERSEL_TRAIT_SIZE]; + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_PD_HOVERSEL"), children_count); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + g_object_unref(parent); + break; + } + case ATSPI_ROLE_LIST_ITEM: { + AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL); + AtspiRole parent_role = atspi_accessible_get_role(parent, NULL); + + if(parent_role == ATSPI_ROLE_TREE_TABLE) { + + AtspiStateSet *state_set = atspi_accessible_get_state_set(obj); + gboolean is_selected = atspi_state_set_contains(state_set, ATSPI_STATE_SELECTED); + g_object_unref(state_set); + + if(is_selected) { + strncat(ret, _("IDS_TRAIT_ITEM_SELECTED"), sizeof(ret) - strlen(ret) - 1); + } + + AtspiStateSet *parent_state_set = atspi_accessible_get_state_set(parent); + bool is_parent_multiselectable = atspi_state_set_contains(parent_state_set, ATSPI_STATE_MULTISELECTABLE); + + g_object_unref(parent_state_set); + g_object_unref(parent); + + if(is_parent_multiselectable) { + + char buf[200]; + + AtspiSelection *parent_selection = atspi_accessible_get_selection(parent); + int selected_children_count = atspi_selection_get_n_selected_children(parent_selection, NULL); + + if(is_selected) { + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + } + + snprintf(buf, 200, _("IDS_TRAIT_ITEM_SELECTED_COUNT"), selected_children_count); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + + g_object_unref(parent_selection); + } + + } else if (atspi_state_set_contains(state_set, ATSPI_STATE_EXPANDABLE)) { + if (atspi_state_set_contains(state_set, ATSPI_STATE_EXPANDED)) { + strncat(ret, _("IDS_TRAIT_GROUP_INDEX_EXPANDED"), sizeof(ret) - strlen(ret) - 1); + } else { + strncat(ret, _("IDS_TRAIT_GROUP_INDEX_COLLAPSED"), sizeof(ret) - strlen(ret) - 1); + } + } + g_object_unref(parent); + break; + } + case ATSPI_ROLE_CHECK_BOX: + case ATSPI_ROLE_RADIO_BUTTON: { + if (atspi_state_set_contains(state_set, ATSPI_STATE_CHECKED)) { + strncat(ret, _("IDS_TRAIT_CHECK_BOX_SELECTED"), sizeof(ret) - strlen(ret) - 1); + } else { + strncat(ret, _("IDS_TRAIT_CHECK_BOX_NOT_SELECTED"), sizeof(ret) - strlen(ret) - 1); + } + + if (role == ATSPI_ROLE_RADIO_BUTTON) { + /* Say role name ("radio button"), but only if it's not a color chooser */ + AtspiAccessible *parent; + AtspiRole parent_role; + parent = atspi_accessible_get_parent(obj, NULL); + parent_role = atspi_accessible_get_role(parent, NULL); + if (parent_role != ATSPI_ROLE_COLOR_CHOOSER) { + gchar *role_name; + role_name = atspi_accessible_get_localized_role_name(obj, NULL); + if (role_name) { + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1); + g_free(role_name); + } + } + g_object_unref(parent); + } + break; + } + case ATSPI_ROLE_PUSH_BUTTON: { + strncat(ret, _("IDS_TRAIT_PUSH_BUTTON"), sizeof(ret) - strlen(ret) - 1); + break; + } + case ATSPI_ROLE_PROGRESS_BAR: { + AtspiValue *value = atspi_accessible_get_value_iface(obj); + if (value) { + double val = atspi_value_get_current_value(value, NULL); + char trait[HOVERSEL_TRAIT_SIZE]; + if (val > 0) { + snprintf(trait, HOVERSEL_TRAIT_SIZE, _("IDS_TRAIT_PD_PROGRESSBAR_PERCENT"), val * 100); + strncat(ret, trait, sizeof(ret) - strlen(ret) - 1); + } else { + strncat(ret, _("IDS_TRAIT_PD_PROGRESSBAR"), sizeof(ret) - strlen(ret) - 1); + } + g_object_unref(value); + } + break; + } + case ATSPI_ROLE_TOGGLE_BUTTON: { + strncat(ret, _("IDS_TRAIT_TOGGLE_BUTTON"), sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + if (atspi_state_set_contains(state_set, ATSPI_STATE_CHECKED)) { + strncat(ret, _("IDS_TRAIT_TOGGLE_BUTTON_ON"), sizeof(ret) - strlen(ret) - 1); + } else { + strncat(ret, _("IDS_TRAIT_TOGGLE_BUTTON_OFF"), sizeof(ret) - strlen(ret) - 1); + } + break; + } + case ATSPI_ROLE_SLIDER: { + add_slider_description(ret, sizeof(ret), obj); + break; + } + case ATSPI_ROLE_HEADING: + case ATSPI_ROLE_GROUPING: { + break; + } + default: { + gchar *role_name = atspi_accessible_get_localized_role_name(obj, NULL); + if (role_name) { + strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1); + g_free(role_name); + } + } + } + + if (state_set) + g_object_unref(state_set); + + return strdup(ret); +} + +char *generate_text_for_relation_objects(AtspiAccessible * obj, AtspiRelationType search, char *(*text_generate_cb)(AtspiAccessible *obj)) +{ + GError *err = NULL; + GArray *relations; + AtspiRelation *relation; + AtspiRelationType type; + Eina_Strbuf *buf; + int i, j; + char *ret = NULL; + + if (!obj || !text_generate_cb) return NULL; + + relations = atspi_accessible_get_relation_set(obj, &err); + if (err || !relations) + { + if (err) g_error_free(err); + return NULL; + } + + buf = eina_strbuf_new(); + + for (i = 0; i < relations->len; i++) + { + relation = g_array_index(relations, AtspiRelation *, i); + type = atspi_relation_get_relation_type(relation); + if (type == search) + { + for (j = 0; j < atspi_relation_get_n_targets(relation); j++) + { + AtspiAccessible *target = atspi_relation_get_target(relation, j); + char *text = text_generate_cb(target); + if (j == 0) + eina_strbuf_append_printf(buf, "%s", text); + else + eina_strbuf_append_printf(buf, ", %s", text); + g_object_unref(target); + free(text); + } + } + g_object_unref(relation); + } + g_array_free(relations, TRUE); + ret = eina_strbuf_string_steal(buf); + eina_strbuf_free(buf); + + return ret; +} + +static char *generate_description_from_relation_object(AtspiAccessible *obj) +{ + GError *err = NULL; + char *ret = generate_trait(obj); + char *desc = atspi_accessible_get_description(obj, &err); + + if (err) + { + g_error_free(err); + g_free(desc); + return ret; + } + + if (desc) { + if (desc[0] != '\0') { + char *tmp = ret; + if (asprintf(&ret, "%s, %s", desc, ret) < 0) + ERROR("asprintf failed."); + free(tmp); + } + g_free(desc); + } + + + return ret; +} + +static char *generate_name_from_relation_object(AtspiAccessible *obj) +{ + GError *err = NULL; + char *name = atspi_accessible_get_name(obj, &err); + + if(err) + { + g_error_free(err); + g_free(name); + return NULL; + } + + return name; +} + +static char *generate_what_to_read(AtspiAccessible * obj) +{ + char *name; + char *names = NULL; + char *description; + char *role_name; + char *other; + char *text = NULL; + char ret[TTS_MAX_TEXT_SIZE] = "\0"; + char *description_from_relation; + char *name_from_relation; + + description = atspi_accessible_get_description(obj, NULL); + name = atspi_accessible_get_name(obj, NULL); + role_name = generate_trait(obj); + other = generate_description_for_subtrees(obj); + description_from_relation = generate_text_for_relation_objects(obj, ATSPI_RELATION_DESCRIBED_BY, generate_description_from_relation_object); + name_from_relation = generate_text_for_relation_objects(obj, ATSPI_RELATION_LABELLED_BY, generate_name_from_relation_object); + AtspiText *iface_text = atspi_accessible_get_text_iface(obj); + if (iface_text) { + text = atspi_text_get_text(iface_text, 0, atspi_text_get_character_count(iface_text, NULL), NULL); + g_object_unref(iface_text); + } + + DEBUG("->->->->->-> WIDGET GAINED HIGHLIGHT: %s <-<-<-<-<-<-<-", name); + DEBUG("->->->->->-> FROM SUBTREE HAS NAME: %s <-<-<-<-<-<-<-", other); + + display_info_about_object(obj, false); + + if (name && strncmp(name, "\0", 1)) + names = strdup(name); + else if (other && strncmp(other, "\0", 1)) + names = strdup(other); + + if (text) { + strncat(ret, text, sizeof(ret) - strlen(ret) - 1); + } + + DEBUG("Text:%s", text); + + if (names && strlen(names) > 0) { + if (strlen(ret) > 0) + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, names, sizeof(ret) - strlen(ret) - 1); + } + + if (name_from_relation && strlen(name_from_relation) > 0) { + if(strlen(ret) > 0) + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, name_from_relation, sizeof(ret) - strlen(ret) - 1); + } + + if (role_name && strlen(role_name) > 0) { + if (strlen(ret) > 0) + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1); + } + + if (description && strlen(description) > 0) { + if (strlen(ret) > 0) + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, description, sizeof(ret) - strlen(ret) - 1); + } + + if (description_from_relation && (description_from_relation[0] != '\n')) { + if (strlen(ret) > 0) + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, description_from_relation, sizeof(ret) - strlen(ret) - 1); + } + + free(text); + free(name); + free(names); + free(name_from_relation); + free(description); + free(role_name); + free(other); + free(description_from_relation); + + return strdup(ret); +} + +static void _current_highlight_object_set(AtspiAccessible * obj) +{ + DEBUG("START"); + GError *err = NULL; + gchar *role = NULL; + + if (!obj) { + DEBUG("Clearing highlight object"); + current_obj = NULL; + if (current_comp) { + atspi_component_clear_highlight(current_comp, &err); + g_object_ref(current_comp); + current_comp = NULL; + } + + return; + } + if (current_obj == obj) { + DEBUG("Object already highlighted"); + DEBUG("Object name:%s", atspi_accessible_get_name(obj, NULL)); + return; + } + if (obj && ATSPI_IS_COMPONENT(obj)) { + DEBUG("OBJ WITH COMPONENT"); + AtspiComponent *comp = atspi_accessible_get_component_iface(obj); + if (!comp) { + GError *err = NULL; + role = atspi_accessible_get_role_name(obj, &err); + ERROR("AtspiComponent *comp NULL, [%s]", role); + GERROR_CHECK(err); + g_free(role); + return; + } + if (current_comp) { + atspi_component_clear_highlight(current_comp, &err); + } + atspi_component_grab_highlight(comp, &err); + current_comp = comp; + GERROR_CHECK(err) + + Eina_Bool is_paused = tts_pause_get(); + if (is_paused) { + tts_stop_set(); + tts_pause_set(EINA_FALSE); + } + current_obj = obj; + char *text_to_speak = NULL; + text_to_speak = generate_what_to_read(obj); + DEBUG("SPEAK:%s", text_to_speak); + + tts_speak(text_to_speak, EINA_TRUE); + g_free(text_to_speak); + } else + DEBUG("Unable to highlight on object"); + DEBUG("END"); +} + +void test_debug(AtspiAccessible * current_widget) +{ + int jdx; + int count_child; + gchar *role; + GError *err = NULL; + AtspiAccessible *child_iter = NULL; + AtspiAccessible *parent = atspi_accessible_get_parent(current_widget, &err); + GERROR_CHECK(err) + + if (!parent) + return; + count_child = atspi_accessible_get_child_count(parent, &err); + GERROR_CHECK(err) + DEBUG("Total childs in parent: %d\n", count_child); + if (!count_child) + { + g_object_unref(parent); + return; + } + + for (jdx = 0; jdx < count_child; jdx++) { + child_iter = atspi_accessible_get_child_at_index(parent, jdx, &err); + GERROR_CHECK(err) + + if (current_widget == child_iter) { + role = atspi_accessible_get_role_name(parent, &err); + DEBUG("Childen found in parent: %s at index: %d\n", role, jdx); + } else { + role = atspi_accessible_get_role_name(parent, &err); + DEBUG("Childen not found in parent: %s at index: %d\n", role, jdx); + } + g_free(role); + GERROR_CHECK(err) + } + g_object_unref(parent); +} + +static void _focus_widget(Gesture_Info * info) +{ + DEBUG("START"); + + if ((last_focus.x == info->x_beg) && (last_focus.y == info->y_beg)) + return; + + AtspiAccessible *obj = NULL; + if (flat_navi_context_current_at_x_y_set(context, info->x_beg, info->y_beg, &obj)) { + last_focus.x = info->x_beg; + last_focus.y = info->y_beg; + _current_highlight_object_set(obj); + } + + DEBUG("END"); +} + +static void _focus_next(void) +{ + DEBUG("START"); + AtspiAccessible *obj; + if (!context) { + ERROR("No navigation context created"); + return; + } + + obj = flat_navi_context_next(context); + if (obj) + _current_highlight_object_set(obj); + else + DEBUG("Next widget not found. Abort"); + DEBUG("END"); +} + +static void _focus_next_visible(void) +{ + DEBUG("START"); + AtspiAccessible *obj; + AtspiStateSet *ss = NULL; + Eina_Bool visible = EINA_FALSE; + if (!context) { + ERROR("No navigation context created"); + return; + } + + do { + obj = flat_navi_context_next(context); + // try 'cycle' objects in context + if (obj) { + ss = atspi_accessible_get_state_set(obj); + visible = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING); + g_object_unref(ss); + } + } + while (obj && !visible); + + if (obj) + _current_highlight_object_set(obj); + else + DEBUG("Next widget not found. Abort"); + DEBUG("END"); +} + +static void _focus_prev_visible(void) +{ + AtspiAccessible *obj; + AtspiStateSet *ss = NULL; + Eina_Bool visible = EINA_FALSE; + if (!context) { + ERROR("No navigation context created"); + return; + } + do { + obj = flat_navi_context_prev(context); + // try 'cycle' objects in context + if (obj) { + ss = atspi_accessible_get_state_set(obj); + visible = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING); + g_object_unref(ss); + } + } + while (obj && !visible); + + if (obj) + _current_highlight_object_set(obj); + else + DEBUG("Previous widget not found. Abort"); +} + +static void _focus_prev(void) +{ + AtspiAccessible *obj; + if (!context) { + ERROR("No navigation context created"); + return; + } + + obj = flat_navi_context_prev(context); + if (obj) + _current_highlight_object_set(obj); + else + DEBUG("Previous widget not found. Abort"); +} + +static void _caret_move_beg(void) +{ + AtspiAccessible *current_widget = NULL; + AtspiText *text_interface; + gboolean ret; + GError *err = NULL; + + if (!current_obj) + return; + + current_widget = current_obj; + + text_interface = atspi_accessible_get_text_iface(current_widget); + if (text_interface) { + ret = atspi_text_set_caret_offset(text_interface, 0, &err); + GERROR_CHECK(err) + if (ret) { + DEBUG("Caret position increment done"); + gchar *text = atspi_text_get_text(text_interface, 0, 1, NULL); + DEBUG("SPEAK:%s", text); + tts_speak(text, EINA_TRUE); + tts_speak(_("IDS_TEXT_BEGIN"), EINA_FALSE); + g_free(text); + } else { + ERROR("Caret position increment error"); + } + g_object_unref(text_interface); + } else + ERROR("No text interface supported!"); +} + +static void _caret_move_end(void) +{ + AtspiAccessible *current_widget = NULL; + AtspiText *text_interface; + gboolean ret; + GError *err = NULL; + + if (!current_obj) + return; + + current_widget = current_obj; + + text_interface = atspi_accessible_get_text_iface(current_widget); + if (text_interface) { + int len = atspi_text_get_character_count(text_interface, NULL); + ret = atspi_text_set_caret_offset(text_interface, len, &err); + if (ret) { + DEBUG("Caret position increment done"); + DEBUG("SPEAK:%s", _("IDS_TEXT_END")); + tts_speak(_("IDS_TEXT_END"), EINA_TRUE); + } else + ERROR("Caret position to end error"); + g_object_unref(text_interface); + } +} + +static void _caret_move_forward(void) +{ + AtspiAccessible *current_widget = NULL; + AtspiText *text_interface; + gint current_offset; + gboolean ret; + int offset_pos; + gchar *text; + GError *err = NULL; + if (!current_obj) + return; + + current_widget = current_obj; + + text_interface = atspi_accessible_get_text_iface(current_widget); + if (text_interface) { + current_offset = atspi_text_get_caret_offset(text_interface, &err); + GERROR_CHECK(err) + ret = atspi_text_set_caret_offset(text_interface, current_offset + 1, &err); + GERROR_CHECK(err) + if (ret) { + offset_pos = atspi_text_get_caret_offset(text_interface, NULL); + text = atspi_text_get_text(text_interface, offset_pos, offset_pos + 1, NULL); + DEBUG("Caret position increment done"); + DEBUG("Current caret position:%d", offset_pos); + DEBUG("Current caret offset:%d", current_offset); + if (offset_pos == atspi_text_get_character_count(text_interface, NULL)) { + DEBUG("SPEAK:%s", _("IDS_TEXT_END")); + tts_speak(_("IDS_TEXT_END"), EINA_FALSE); + } else { + DEBUG("SPEAK:%s", text); + tts_speak(text, EINA_TRUE); + } + g_free(text); + } else { + ERROR("Caret position increment error"); + } + g_object_unref(text_interface); + } else + ERROR("No text interface supported!"); + return; + +} + +static void _caret_move_backward(void) +{ + AtspiAccessible *current_widget = NULL; + AtspiText *text_interface; + gint current_offset; + int offset_pos; + gchar *text; + GError *err = NULL; + gboolean ret; + + if (!current_obj) + return; + + current_widget = current_obj; + + GERROR_CHECK(err) + + text_interface = atspi_accessible_get_text_iface(current_widget); + if (text_interface) { + current_offset = atspi_text_get_caret_offset(text_interface, &err); + GERROR_CHECK(err) + ret = atspi_text_set_caret_offset(text_interface, current_offset - 1, &err); + GERROR_CHECK(err) + if (ret) { + offset_pos = atspi_text_get_caret_offset(text_interface, NULL); + text = atspi_text_get_text(text_interface, offset_pos, offset_pos + 1, NULL); + DEBUG("Caret position decrement done"); + DEBUG("Current caret position:%d", offset_pos); + DEBUG("SPEAK:%s", text); + tts_speak(text, EINA_TRUE); + g_free(text); + if (offset_pos == 0) { + DEBUG("SPEAK:%s", _("IDS_TEXT_BEGIN")); + tts_speak(_("IDS_TEXT_BEGIN"), EINA_FALSE); + } + } else { + ERROR("Caret position decrement error"); + } + g_object_unref(text_interface); + } else + ERROR("No text interface supported!"); + return; +} + +static void _read_value(AtspiValue * value) +{ + if (!value) + return; + + gdouble current_val = atspi_value_get_current_value(value, NULL); + gdouble max_val = atspi_value_get_maximum_value(value, NULL); + gdouble min_val = atspi_value_get_minimum_value(value, NULL); + + int proc = (current_val / fabs(max_val - min_val)) * 100; + + char buf[256] = "\0"; + snprintf(buf, sizeof(buf), "%d percent", proc); + DEBUG("has value %s", buf); + tts_speak(buf, EINA_TRUE); +} + +static void _value_inc(void) +{ + AtspiAccessible *current_widget = NULL; + GError *err = NULL; + + if (!current_obj) + return; + + current_widget = current_obj; + + AtspiValue *value_interface = atspi_accessible_get_value_iface(current_widget); + if (value_interface) { + DEBUG("Value interface supported!\n"); + gdouble current_val = atspi_value_get_current_value(value_interface, &err); + GERROR_CHECK(err) + DEBUG("Current value: %f\n ", (double)current_val); + gdouble minimum_inc = atspi_value_get_minimum_increment(value_interface, &err); + DEBUG("Minimum increment: %f\n ", (double)minimum_inc); + GERROR_CHECK(err) + atspi_value_set_current_value(value_interface, current_val + minimum_inc, &err); + GERROR_CHECK(err) + _read_value(value_interface); + g_object_unref(value_interface); + return; + } + ERROR("No value interface supported!\n"); +} + +static void _value_dec(void) +{ + AtspiAccessible *current_widget = NULL; + GError *err = NULL; + + if (!current_obj) + return; + current_widget = current_obj; + + AtspiValue *value_interface = atspi_accessible_get_value_iface(current_widget); + if (value_interface) { + DEBUG("Value interface supported!\n"); + gdouble current_val = atspi_value_get_current_value(value_interface, &err); + GERROR_CHECK(err) + DEBUG("Current value: %f\n ", (double)current_val); + gdouble minimum_inc = atspi_value_get_minimum_increment(value_interface, &err); + GERROR_CHECK(err) + DEBUG("Minimum increment: %f\n ", (double)minimum_inc); + atspi_value_set_current_value(value_interface, current_val - minimum_inc, &err); + GERROR_CHECK(err) + _read_value(value_interface); + g_object_unref(value_interface); + return; + } + ERROR("No value interface supported!\n"); +} + +static void _activate_widget(void) +{ + //activate the widget + //only if activate mean click + //special behavior for entry, caret should move from first/last last/first + DEBUG("START"); + AtspiAccessible *current_widget = NULL; + AtspiComponent *focus_component = NULL; + AtspiAccessible *parent = NULL; + AtspiStateSet *ss = NULL; + AtspiSelection *selection = NULL; + AtspiAction *action; + AtspiEditableText *edit = NULL; + + GError *err = NULL; + gchar *actionName = NULL; + gint number = 0; + gint i = 0; + gint index = 0; + Eina_Bool activate_found = EINA_FALSE; + AtspiRole role = ATSPI_ROLE_INVALID; + + if (!current_obj) + return; + + if (!_widget_has_state(current_obj, ATSPI_STATE_ENABLED)) { + DEBUG("Widget is disabled so cannot be activated"); + return; + } + + current_widget = current_obj; + + role = atspi_accessible_get_role(current_widget, NULL); + if (role == ATSPI_ROLE_SLIDER) { + return; + } + + display_info_about_object(current_widget, false); + + edit = atspi_accessible_get_editable_text_iface(current_widget); + if (edit) { + DEBUG("Activated object has editable Interface"); + focus_component = atspi_accessible_get_component_iface(current_widget); + if (focus_component) { + if (atspi_component_grab_focus(focus_component, &err) == TRUE) { + GERROR_CHECK(err) + + DEBUG("Entry activated\n"); + + char *text_to_speak = NULL; + text_to_speak = generate_what_to_read(current_widget); + + DEBUG("SPEAK:%s", text_to_speak); + + tts_speak(text_to_speak, EINA_TRUE); + g_free(text_to_speak); + g_object_unref(focus_component); + } + } + g_object_unref(edit); + return; + } + + action = atspi_accessible_get_action_iface(current_widget); + if (action) { + number = atspi_action_get_n_actions(action, &err); + DEBUG("Number of available action = %d\n", number); + GERROR_CHECK(err) + activate_found = EINA_FALSE; + while (i < number && !activate_found) { + actionName = atspi_action_get_name(action, i, &err); + if (actionName && !strcmp("activate", actionName)) { + DEBUG("There is activate action"); + activate_found = EINA_TRUE; + } else { + i++; + } + g_free(actionName); + } + if (activate_found) { + DEBUG("PERFORMING ATSPI ACTION NO.%d", i); + atspi_action_do_action(action, i, &err); + } else if (number > 0) { + DEBUG("PERFORMING ATSPI DEFAULT ACTION"); + atspi_action_do_action(action, 0, &err); + } else + ERROR("There is no actions inside Action interface"); + if (action) + g_object_unref(action); + GERROR_CHECK(err) + return; + } + + ss = atspi_accessible_get_state_set(current_widget); + if (atspi_state_set_contains(ss, ATSPI_STATE_SELECTABLE) == EINA_TRUE) { + DEBUG("OBJECT IS SELECTABLE"); + parent = atspi_accessible_get_parent(current_widget, NULL); + if (parent) { + index = atspi_accessible_get_index_in_parent(current_widget, NULL); + selection = atspi_accessible_get_selection_iface(parent); + if (selection) { + if(atspi_state_set_contains(ss, ATSPI_STATE_SELECTED)) { + atspi_selection_deselect_child (selection, index, NULL); + + } else { + DEBUG("SELECT CHILD NO:%d\n", index); + atspi_selection_select_child(selection, index, NULL); + } + + g_object_unref(selection); + g_object_unref(parent); + g_object_unref(ss); + return; + } else + ERROR("no selection iterface in parent"); + + g_object_unref(parent); + } + } + g_object_unref(ss); + +} + +static void _quickpanel_change_state(gboolean quickpanel_switch) +{ + DEBUG("START"); + Ecore_X_Window xwin = 0; + + if (quickpanel_switch) + DEBUG("QUICKPANEL STATE ON"); + else + DEBUG("QUICKPANEL STATE OFF"); + + Ecore_X_Illume_Quickpanel_State state; + + ecore_x_window_prop_xid_get(ecore_x_window_root_first_get(), ECORE_X_ATOM_NET_ACTIVE_WINDOW, ECORE_X_ATOM_WINDOW, &xwin, 1); + + state = quickpanel_switch ? ECORE_X_ILLUME_QUICKPANEL_STATE_ON : ECORE_X_ILLUME_QUICKPANEL_STATE_OFF; + + ecore_x_e_illume_quickpanel_state_set(xwin, state); + + ecore_x_e_illume_quickpanel_state_send(ecore_x_e_illume_zone_get(xwin), state); + + ecore_main_loop_iterate(); +} + +/** + * @brief Gets 'deepest' Scrollable accessible containing (x,y) point + */ +/* +static AtspiScrollable* +_find_scrollable_ancestor_at_xy(int x, int y) +{ + AtspiAccessible *ret = NULL; + AtspiRect *rect; + GError *err = NULL; + + if (!top_window || !ATSPI_IS_COMPONENT(top_window)) + { + DEBUG("No active window detected or no AtspiComponent interface available"); + return NULL; + } + + rect = atspi_component_get_extents(ATSPI_COMPONENT(top_window), ATSPI_COORD_TYPE_SCREEN, &err); + GERROR_CHECK(err) + if (!rect) + { + ERROR("Unable to fetch window screen coordinates"); + return NULL; + } + + // Scroll must originate within window borders + if ((x < rect->x) || (x > rect->x + rect->width) || + (y < rect->y) || (y > rect->y + rect->height)) + { + DEBUG("Scroll don't start within active window borders"); + g_free(rect); + return NULL; + } + + ret = atspi_component_get_accessible_at_point(ATSPI_COMPONENT(top_window), x, y, ATSPI_COORD_TYPE_SCREEN, &err); + GERROR_CHECK(err) + if (!ret) + { + ERROR("Unable to get accessible objct at (%d, %d) screen coordinates.", x, y); + return NULL; + } +gchar *name; +gchar *role; + // find accessible object with Scrollable interface + while (ret && (ret != top_window)) + { + name = atspi_accessible_get_name(ret, &err); + GERROR_CHECK(err) + role = atspi_accessible_get_role_name(ret, &err); + GERROR_CHECK(err) + DEBUG("Testing for scrollability: %s %s", + name, role); + if (atspi_accessible_get_scrollable(ret)) + { + DEBUG("Scrollable widget found at (%d, %d), name: %s, role: %s", x, y, + name ,role); + g_free(name); + g_free(role); + return ATSPI_SCROLLABLE(ret); + } + g_free(name); + g_free(role); + ret = atspi_accessible_get_parent(ret, &err); + if (err) + { + ERROR("Unable to fetch AT-SPI parent"); + GERROR_CHECK(err) + return NULL; + } + } + + return NULL; +} + +static void _widget_scroll_begin(Gesture_Info *gi) +{ + GError *err = NULL; + + if (scrolled_obj) + { + ERROR("Scrolling context active when initializing new scrolling context! This should never happen."); + ERROR("Force reset of current scrolling context..."); + atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_END, gi->x_begin, gi->y_begin, &err); + if (err) + { + ERROR("Failed to reset scroll context."); + GERROR_CHECK(err) + scrolled_obj = NULL; + } + } + + scrolled_obj = _find_scrollable_ancestor_at_xy(gi->x_begin, gi->y_begin); + + if (!scrolled_obj) + { + DEBUG("No scrollable widget found at (%d, %d) coordinates", gi->x_begin, gi->y_begin); + return; + } + + atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_START, gi->x_begin, gi->y_begin, &err); + if (err) + { + ERROR("Failed to initialize scroll operation"); + GERROR_CHECK(err) + scrolled_obj = NULL; + } +} + +static void _widget_scroll_continue(Gesture_Info *gi) +{ + GError *err = NULL; + if (!scrolled_obj) + { + DEBUG("Scrolling context not initialized!"); + return; + } + atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_CONTINUE, gi->x_begin, gi->y_begin, &err); + GERROR_CHECK(err) +} + +static void _widget_scroll_end(Gesture_Info *gi) +{ + GError *err = NULL; + if (!scrolled_obj) + { + ERROR("Scrolling context not initialized!"); + return; + } + + atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_END, gi->x_begin, gi->y_begin, &err); + scrolled_obj = NULL; + GERROR_CHECK(err) +} +*/ + +static void _widget_scroll(Gesture_Info * gi) +{ + DEBUG("Recognized gesture state: %d", gi->state); + + if (gi->state == 0) { + DEBUG("save coordinates %d %d", gesture_start_p.x, gesture_start_p.y); + gesture_start_p.x = gi->x_beg; + gesture_start_p.y = gi->y_beg; + } + + if (gi->state != 2) { + DEBUG("Scroll not finished yet"); + return; + } + + AtspiAccessible *obj = NULL; + obj = flat_navi_context_current_get(context); + if (!obj) { + ERROR("No context"); + return; + } + + AtspiStateSet *ss = atspi_accessible_get_state_set(obj); + if (!ss) { + ERROR("no stetes"); + return; + } + + if (!atspi_state_set_contains(ss, ATSPI_STATE_SHOWING)) { + DEBUG("current context do not have visible state, swith to next/prev"); + if (gesture_start_p.y > gi->y_end || gesture_start_p.x > gi->x_end) { + DEBUG("NEXT"); + _focus_next_visible(); + } else if (gesture_start_p.y < gi->y_end || gesture_start_p.x < gi->x_end) { + DEBUG("PREVIOUS"); + _focus_prev_visible(); + } + } + DEBUG("end"); + g_object_unref(ss); + g_object_unref(obj); +} + +static void _read_quickpanel(void) +{ + DEBUG("START"); + + device_time_get(); + device_battery_get(); + device_bluetooth_get(); + device_signal_strenght_get(); + + device_date_get(); + device_missed_events_get(); + DEBUG("END"); +} + +static void _set_pause(void) +{ + DEBUG("START"); + + Eina_Bool res = EINA_FALSE; + bool pause = tts_pause_get(); + res = tts_pause_set(!pause); + if (!res) { + ERROR("Failed to set pause state"); + } + + DEBUG("END"); +} + +void auto_review_highlight_set(void) +{ + AtspiAccessible *obj = flat_navi_context_next(context); + + DEBUG("START"); + + if (!obj) { + DEBUG("obj == NULL"); + s_auto_review.auto_review_on = false; + return; + } else if (obj == flat_navi_context_last_get(context)) { + DEBUG("obj == flat_navi_context_last_get()"); + s_auto_review.auto_review_on = false; + } + + _current_highlight_object_set(obj); + + DEBUG("END"); +} + +void auto_review_highlight_top(void) +{ + DEBUG("START"); + char *text_to_speak = NULL; + AtspiAccessible *obj = flat_navi_context_current_get(context); + AtspiAccessible *first = flat_navi_context_first(context); + + if (first != obj) { + _current_highlight_object_set(first); + } else { + text_to_speak = generate_what_to_read(obj); + DEBUG("Text to speak: %s", text_to_speak); + tts_speak(text_to_speak, EINA_TRUE); + free(text_to_speak); + } + + DEBUG("END"); +} + +static void _on_auto_review_stop(void) +{ + DEBUG("START"); + s_auto_review.auto_review_on = false; + DEBUG("END"); +} + +static void _on_utterance(void) +{ + DEBUG("START"); + DEBUG("s_auto_review.auto_review_on == %d", s_auto_review.auto_review_on); + + if (s_auto_review.auto_review_on) { + auto_review_highlight_set(); + } + DEBUG("END"); +} + +static void _review_from_current(void) +{ + DEBUG("START"); + + s_auto_review.focused_object = flat_navi_context_current_get(context); + s_auto_review.auto_review_on = true; + auto_review_highlight_set(); + + DEBUG("END"); +} + +static void _review_from_top() +{ + DEBUG("START"); + + s_auto_review.focused_object = flat_navi_context_current_get(context); + s_auto_review.auto_review_on = true; + auto_review_highlight_top(); + + DEBUG("END"); +} + +static void _direct_scroll_back(void) +{ + DEBUG("ONE_FINGER_FLICK_LEFT_RETURN"); + if (!context) { + ERROR("No navigation context created"); + return; + } + + AtspiAccessible *obj = NULL; + AtspiAccessible *current = NULL; + AtspiAccessible *parent = NULL; + AtspiRole role; + + current = flat_navi_context_current_get(context); + parent = atspi_accessible_get_parent(current, NULL); + role = atspi_accessible_get_role(parent, NULL); + + if (role != ATSPI_ROLE_LIST) { + DEBUG("That operation can be done only on list, it is:%s", atspi_accessible_get_role_name(parent, NULL)); + g_object_unref(parent); + g_object_unref(current); + return; + } + + int index = atspi_accessible_get_index_in_parent(current, NULL); + int children_count = atspi_accessible_get_child_count(parent, NULL); + + if (children_count <= 0) { + ERROR("NO visible element on list"); + g_object_unref(parent); + g_object_unref(current); + return; + } + + DEBUG("start from element with index:%d/%d", index, children_count); + + if (index <= 0) { + DEBUG("first element"); + obj = atspi_accessible_get_child_at_index(parent, 0, NULL); + smart_notification(FOCUS_CHAIN_END_NOTIFICATION_EVENT, 0, 0); + } + + else { + DEBUG("go back to %d element", index); + obj = atspi_accessible_get_child_at_index(parent, index, NULL); + } + + if (obj) { + DEBUG("Will set highlight and context"); + if (flat_navi_context_current_set(context, obj)) { + DEBUG("current obj set"); + } + _current_highlight_object_set(obj); + } + g_object_unref(parent); + g_object_unref(current); +} + +static void _direct_scroll_forward(void) +{ + DEBUG("ONE_FINGER_FLICK_RIGHT_RETURN"); + + if (!context) { + ERROR("No navigation context created"); + return; + } + + AtspiAccessible *obj = NULL; + AtspiAccessible *current = NULL; + AtspiAccessible *parent = NULL; + AtspiRole role; + + current = flat_navi_context_current_get(context); + parent = atspi_accessible_get_parent(current, NULL); + role = atspi_accessible_get_role(parent, NULL); + + if (role != ATSPI_ROLE_LIST) { + DEBUG("That operation can be done only on list, it is:%s", atspi_accessible_get_role_name(parent, NULL)); + g_object_unref(parent); + g_object_unref(current); + return; + } + + int index = atspi_accessible_get_index_in_parent(current, NULL); + int children_count = atspi_accessible_get_child_count(parent, NULL); + + if (children_count <= 0) { + ERROR("NO visible element on list"); + g_object_unref(parent); + g_object_unref(current); + return; + } + + DEBUG("start from element with index:%d/%d", index, children_count); + + if (index >= children_count) { + DEBUG("last element"); + obj = atspi_accessible_get_child_at_index(parent, children_count - 1, NULL); + smart_notification(FOCUS_CHAIN_END_NOTIFICATION_EVENT, 0, 0); + } + + else { + DEBUG("go back to %d element", index); + obj = atspi_accessible_get_child_at_index(parent, index, NULL); + } + + if (obj) { + DEBUG("Will set highlight and context"); + if (flat_navi_context_current_set(context, obj)) { + DEBUG("current obj set"); + } + _current_highlight_object_set(obj); + } + g_object_unref(parent); + g_object_unref(current); +} + +static void _direct_scroll_to_first(void) +{ + DEBUG("ONE_FINGER_FLICK_UP_RETURN"); + if (!context) { + ERROR("No navigation context created"); + return; + } + AtspiAccessible *obj = flat_navi_context_first(context); + if (obj) + _current_highlight_object_set(obj); + else + DEBUG("First widget not found. Abort"); + DEBUG("END"); +} + +static void _direct_scroll_to_last(void) +{ + DEBUG("ONE_FINGER_FLICK_DOWN_RETURN"); + if (!context) { + ERROR("No navigation context created"); + return; + } + AtspiAccessible *obj = flat_navi_context_last(context); + if (obj) + _current_highlight_object_set(obj); + else + DEBUG("Last widget not found. Abort"); + DEBUG("END"); +} + +static Eina_Bool _has_value(void) +{ + DEBUG("START"); + AtspiAccessible *obj = NULL; + + if (!current_obj) + return EINA_FALSE; + + obj = current_obj; + + if (!obj) + return EINA_FALSE; + + AtspiValue *value = atspi_accessible_get_value_iface(obj); + + if (value) { + g_object_unref(value); + return EINA_TRUE; + } + + return EINA_FALSE; +} + +static Eina_Bool _is_enabled(void) +{ + if (!current_obj) { + return EINA_FALSE; + } + + return _widget_has_state(current_obj, ATSPI_STATE_ENABLED); +} + +static Eina_Bool _is_active_entry(void) +{ + DEBUG("START"); + + if (!context) { + ERROR("No navigation context created"); + return EINA_FALSE; + } + AtspiAccessible *obj = NULL; + AtspiRole role; + obj = flat_navi_context_current_get(context); + + if (!obj) + return EINA_FALSE; + + role = atspi_accessible_get_role(obj, NULL); + if (role == ATSPI_ROLE_ENTRY) { + AtspiStateSet *state_set = atspi_accessible_get_state_set(obj); + if (atspi_state_set_contains(state_set, ATSPI_STATE_FOCUSED)) { + g_object_unref(state_set); + return EINA_TRUE; + } + g_object_unref(state_set); + return EINA_FALSE; + } + + DEBUG("END"); + return EINA_FALSE; +} + +static Eina_Bool _is_slider(AtspiAccessible * obj) +{ + DEBUG("START"); + + if (!obj) + return EINA_FALSE; + + AtspiRole role; + + role = atspi_accessible_get_role(obj, NULL); + if (role == ATSPI_ROLE_SLIDER) { + return EINA_TRUE; + } + return EINA_FALSE; +} + +static void _move_slider(Gesture_Info * gi) +{ + DEBUG("ONE FINGER DOUBLE TAP AND HOLD"); + + if (!context) { + ERROR("No navigation context created"); + return; + } + + AtspiAccessible *obj = NULL; + AtspiValue *value = NULL; + AtspiComponent *comp = NULL; + AtspiRect *rect = NULL; + int click_point_x = 0; + int click_point_y = 0; + + obj = current_obj; + + if (!obj) { + DEBUG("no object"); + prepared = false; + return; + } + + if (!_is_slider(obj)) { + DEBUG("Object is not a slider"); + prepared = false; + return; + } + + if (!_widget_has_state(obj, ATSPI_STATE_ENABLED)) { + DEBUG("Slider is disabled"); + prepared = false; + return; + } + + if (gi->state == 0) { + comp = atspi_accessible_get_component_iface(obj); + if (!comp) { + ERROR("that slider do not have component interface"); + prepared = false; + return; + } + + rect = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_SCREEN, NULL); + + DEBUG("Current object is in:%d %d", rect->x, rect->y); + DEBUG("Current object has size:%d %d", rect->width, rect->height); + + click_point_x = rect->x + rect->width / 2; + click_point_y = rect->y + rect->height / 2; + DEBUG("Click on point %d %d", click_point_x, click_point_y); + start_scroll(click_point_x, click_point_y); + } + + if (gi->state == 1) { + counter++; + DEBUG("SCROLLING but not meet counter:%d", counter); + if (counter >= GESTURE_LIMIT) { + counter = 0; + DEBUG("Scroll on point %d %d", gi->x_end, gi->y_end); + continue_scroll(gi->x_end, gi->y_end); + } + } + + if (gi->state == 2) { + DEBUG("state == 2"); + end_scroll(gi->x_end, gi->y_end); + prepared = false; + value = atspi_accessible_get_value_iface(obj); + if (value) { + _read_value(value); + g_object_unref(value); + } else { + ERROR("There is not value interface in slider"); + } + } + DEBUG("END"); +} + +AtspiAction *_get_main_window(void) +{ + AtspiAccessible *win = flat_navi_context_root_get(context); + if (!win) { + ERROR("win == NULL"); + return NULL; + } + + AtspiAction *action = atspi_accessible_get_action_iface(win); + if (!action) { + ERROR("action == NULL"); + return NULL; + } + + return action; +} + +static int _find_action_index(AtspiAction * action, char *action_name_to_find) +{ + int action_num = atspi_action_get_n_actions(action, NULL); + char *action_name = NULL; + + int i = 0; + for (i = 0; i < action_num; ++i) { + action_name = atspi_action_get_action_name(action, i, NULL); + + if (!strcmp(action_name_to_find, action_name)) { + return i; + } + } + + return -i; +} + +static void _start_stop_signal_send(void) +{ + int action_index = -1; + char *action_name = "pause_play"; + AtspiAction *action = _get_main_window(); + if (!action) { + ERROR("Could not get the action inteface"); + } + + if (!action) { + ERROR("action == NULL"); + return; + } + + action_index = _find_action_index(action, action_name); + if (action_index < 0) { + ERROR("Pause_play action not found"); + return; + } + + DEBUG("ACTION: %s has index: %d", action_name, action_index); + atspi_action_do_action(action, action_index, NULL); +} + +static void on_gesture_detected(void *data, Gesture_Info * info) +{ + Ecore_X_Window keyboard_win; + _on_auto_review_stop(); + + if (info->type == ONE_FINGER_SINGLE_TAP && info->state == 3) { + DEBUG("One finger single tap aborted"); + prepared = true; + } + + switch (info->type) { + case ONE_FINGER_HOVER: + if (prepared) { + DEBUG("Prepare to move slider"); + _move_slider(info); + } else { + if (_last_hover_event_time < 0) + _last_hover_event_time = info->event_time; + //info->event_time and _last_hover_event_time contain timestamp in ms. + //RETURN so we do not handle all incoming event + if ((info->event_time - _last_hover_event_time) < ONGOING_HOVER_GESTURE_INTERPRETATION_INTERVAL && info->state == 1) + return; + _last_hover_event_time = info->state != 1 ? -1 : info->event_time; +#ifdef ELM_ACCESS_KEYBOARD + keyboard_win = top_window_get(info->x_end, info->y_end); + if (keyboard_win && ecore_x_e_virtual_keyboard_get(keyboard_win)) { + elm_access_adaptor_emit_read(keyboard_win, info->x_end, info->y_end); + break; + } +#endif + _focus_widget(info); + } + break; + case TWO_FINGERS_HOVER: + _widget_scroll(info); + break; + case ONE_FINGER_FLICK_LEFT: + _focus_prev(); + break; + case ONE_FINGER_FLICK_RIGHT: + _focus_next(); + break; + case ONE_FINGER_FLICK_UP: + if (_is_active_entry()) + _caret_move_backward(); + else if (_has_value() && _is_enabled()) + _value_inc(); + else + _focus_prev(); + break; + case ONE_FINGER_FLICK_DOWN: + if (_is_active_entry()) + _caret_move_forward(); + else if (_has_value() && _is_enabled()) + _value_dec(); + else + _focus_next(); + break; + case ONE_FINGER_SINGLE_TAP: +#ifdef ELM_ACCESS_KEYBOARD + keyboard_win = top_window_get(info->x_end, info->y_end); + if (keyboard_win && ecore_x_e_virtual_keyboard_get(keyboard_win)) { + elm_access_adaptor_emit_read(keyboard_win, info->x_end, info->y_end); + break; + } +#endif + if (!prepared) + _focus_widget(info); + break; + case ONE_FINGER_DOUBLE_TAP: +#ifdef ELM_ACCESS_KEYBOARD + keyboard_win = top_window_get(info->x_end, info->y_end); + if (keyboard_win && ecore_x_e_virtual_keyboard_get(keyboard_win)) { + elm_access_adaptor_emit_activate(keyboard_win, info->x_end, info->y_end); + break; + } +#endif + _activate_widget(); + break; + case TWO_FINGERS_SINGLE_TAP: + _set_pause(); + break; + case TWO_FINGERS_DOUBLE_TAP: + _start_stop_signal_send(); + break; + case TWO_FINGERS_TRIPLE_TAP: +#ifndef SCREEN_READER_TV + _read_quickpanel(); +#endif + break; + case THREE_FINGERS_SINGLE_TAP: + _review_from_top(); + break; + case THREE_FINGERS_DOUBLE_TAP: + _review_from_current(); + break; + case THREE_FINGERS_FLICK_DOWN: + _quickpanel_change_state(QUICKPANEL_DOWN); + break; + case THREE_FINGERS_FLICK_UP: + _quickpanel_change_state(QUICKPANEL_UP); + break; + case ONE_FINGER_FLICK_LEFT_RETURN: + _direct_scroll_back(); + break; + case ONE_FINGER_FLICK_RIGHT_RETURN: + _direct_scroll_forward(); + break; + case ONE_FINGER_FLICK_UP_RETURN: + if (_is_active_entry()) + _caret_move_beg(); + else + _direct_scroll_to_first(); + break; + case ONE_FINGER_FLICK_DOWN_RETURN: + if (_is_active_entry()) + _caret_move_end(); + else + _direct_scroll_to_last(); + break; + default: + DEBUG("Gesture type %d not handled in switch", info->type); + } + + dbus_gesture_adapter_emit(info); +} + +static void _view_content_changed(AtspiAccessible * root, void *user_data) +{ + if (flat_navi_is_valid(context, root)) + return; + if (!_widget_has_state(root, ATSPI_STATE_SHOWING)) + return; + flat_navi_context_free(context); + context = flat_navi_context_create(root); + _current_highlight_object_set(flat_navi_context_current_get(context)); +} + +static void _new_highlighted_obj_changed(AtspiAccessible * new_highlighted_obj, void *user_data) +{ + DEBUG("context: %p, current: %p, new_highlighted_obj: %p", context, flat_navi_context_current_get(context), new_highlighted_obj); + if (context && flat_navi_context_current_get(context) != new_highlighted_obj) { + flat_navi_context_current_set(context, g_object_ref(new_highlighted_obj)); + } +} + +void clear(gpointer d) +{ + AtspiAccessible **data = d; + AtspiAccessible *obj = *data; + g_object_unref(obj); +} + +static AtspiAccessible *_get_modal_descendant(AtspiAccessible * root) +{ + GError *err = NULL; + AtspiStateSet *states = atspi_state_set_new(NULL); + atspi_state_set_add(states, ATSPI_STATE_MODAL); + atspi_state_set_add(states, ATSPI_STATE_SHOWING); + atspi_state_set_add(states, ATSPI_STATE_VISIBLE); + DEBUG("GET MODAL: STATE SET PREPARED"); + AtspiMatchRule *rule = atspi_match_rule_new(states, + ATSPI_Collection_MATCH_ALL, + NULL, + ATSPI_Collection_MATCH_INVALID, + NULL, + ATSPI_Collection_MATCH_INVALID, + NULL, + ATSPI_Collection_MATCH_INVALID, + FALSE); + DEBUG("GET MODAL: MATCHING RULE PREPARED"); + AtspiAccessible *ret = NULL; + AtspiCollection *col_iface = atspi_accessible_get_collection_iface(root); + GArray *result = atspi_collection_get_matches(col_iface, + rule, + ATSPI_Collection_SORT_ORDER_CANONICAL, + 1, + TRUE, + &err); + GERROR_CHECK(err); + DEBUG("GET MODAL: QUERY PERFORMED"); + g_object_unref(states); + g_object_unref(rule); + g_object_unref(col_iface); + if (result && result->len > 0) { + DEBUG("GET MODAL: MODAL FOUND"); + g_array_set_clear_func(result, clear); + ret = g_object_ref(g_array_index(result, AtspiAccessible *, 0)); + g_array_free(result, TRUE); + } + return ret; +} + +static void on_window_activate(void *data, AtspiAccessible * window) +{ + DEBUG("START"); + + if (top_window) + app_tracker_callback_unregister(top_window, _view_content_changed, NULL); + + if (window) { + DEBUG("Window name: %s", atspi_accessible_get_name(window, NULL)); + // TODO: modal descendant of window should be used (if exists) otherwise window + AtspiAccessible *modal_descendant = _get_modal_descendant(window); + app_tracker_callback_register(modal_descendant ? modal_descendant : window, _view_content_changed, NULL); + _view_content_changed(modal_descendant ? modal_descendant : window, NULL); + g_object_unref(modal_descendant); + } else { + flat_navi_context_free(context); + ERROR("No top window found!"); + } + top_window = window; + DEBUG("END"); +} + +void kb_tracker(void *data, Key k) +{ + switch (k) { + case KEY_LEFT: + _focus_prev(); + break; + case KEY_RIGHT: + _focus_next(); + break; + default: + DEBUG("Key %d not supported \n", k); + } +} + +void navigator_init(void) +{ + DEBUG("START"); + + set_utterance_cb(_on_utterance); + + screen_reader_gestures_tracker_register(on_gesture_detected, NULL); + // register on active_window + dbus_gesture_adapter_init(); + app_tracker_init(); + app_tracker_new_obj_highlighted_callback_register(_new_highlighted_obj_changed); + window_tracker_init(); + window_tracker_register(on_window_activate, NULL); + window_tracker_active_window_request(); + smart_notification_init(); +#ifndef SCREEN_READER_TV + system_notifications_init(); +#endif + keyboard_tracker_init(); + keyboard_tracker_register(kb_tracker, NULL); +} + +void navigator_shutdown(void) +{ + GError *err = NULL; + if (current_obj) { + AtspiComponent *comp = atspi_accessible_get_component_iface(current_obj); + if (comp) { + atspi_component_clear_highlight(comp, &err); + GERROR_CHECK(err); + } + } + if (context) { + flat_navi_context_free(context); + context = NULL; + } + dbus_gesture_adapter_shutdown(); + app_tracker_shutdown(); + window_tracker_shutdown(); + smart_notification_shutdown(); +#ifndef SCREEN_READER_TV + system_notifications_shutdown(); +#endif + keyboard_tracker_shutdown(); +} diff --git a/src/pivot_chooser.c b/src/pivot_chooser.c new file mode 100644 index 0000000..481663b --- /dev/null +++ b/src/pivot_chooser.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "logger.h" +#include + +/** + * @brief Finds first leaf in object hierarchy with given states, + * starting from object given as parent. + * + * This heuristic assumes that focused element have focused + * parent widgets. + */ +static AtspiAccessible *_pivot_with_state_top_down_find(AtspiAccessible * parent, AtspiStateType type) +{ + AtspiAccessible *ret = NULL; + AtspiStateSet *states; + int i; + + states = atspi_accessible_get_state_set(parent); + if (!states || atspi_state_set_contains(states, type)) { + int n = atspi_accessible_get_child_count(parent, NULL); + if (n == 0) + ret = parent; + for (i = 0; i < n; i++) { + AtspiAccessible *child = atspi_accessible_get_child_at_index(parent, i, NULL); + if (!child) + continue; + + ret = _pivot_with_state_top_down_find(child, type); + + g_object_unref(child); + + if (ret) + break; + } + } + + g_object_unref(states); + + return ret; +} + +/** + * @brief Finds first leaf descendant of given object with state @p type + */ +static AtspiAccessible *_pivot_with_state_flat_find(AtspiAccessible * parent, AtspiStateType type) +{ + Eina_List *candidates = NULL, *queue = NULL; + + // ref object to keep same ref count + g_object_ref(parent); + queue = eina_list_append(queue, parent); + + while (queue) { + AtspiAccessible *obj = eina_list_data_get(queue); + queue = eina_list_remove_list(queue, queue); + + int n = atspi_accessible_get_child_count(obj, NULL); + if (n == 0) + candidates = eina_list_append(candidates, obj); + else { + int i; + for (i = 0; i < n; i++) { + AtspiAccessible *child = atspi_accessible_get_child_at_index(obj, i, NULL); + if (child) + queue = eina_list_append(queue, child); + } + g_object_unref(obj); + } + } + + // FIXME sort by (x,y) first ?? + while (candidates) { + AtspiAccessible *obj = eina_list_data_get(candidates); + candidates = eina_list_remove_list(candidates, candidates); + + AtspiStateSet *states = atspi_accessible_get_state_set(obj); + if (states && atspi_state_set_contains(states, type)) { + g_object_unref(states); + g_object_unref(obj); + eina_list_free(candidates); + + return obj; + } + + g_object_unref(states); + g_object_unref(obj); + } + + return NULL; +} + +/** + * @brief Purpose of this methods is to find first visible object in + * hierarchy + */ +AtspiAccessible *pivot_chooser_pivot_get(AtspiAccessible * win) +{ + AtspiAccessible *ret; + + if (atspi_accessible_get_role(win, NULL) != ATSPI_ROLE_WINDOW) { + ERROR("Pivot search entry point must be a Window!"); + return NULL; + } + + DEBUG("Finding SHOWING widget using top-down method."); + ret = _pivot_with_state_top_down_find(win, ATSPI_STATE_SHOWING); + if (ret) + return ret; + + DEBUG("Finding SHOWING widget using top-down method."); + ret = _pivot_with_state_flat_find(win, ATSPI_STATE_SHOWING); + if (ret) + return ret; + + DEBUG("Finding FOCUSED widget using top-down method."); + ret = _pivot_with_state_top_down_find(win, ATSPI_STATE_FOCUSED); + if (ret) + return ret; + + DEBUG("Finding FOCUSED widget using flat search method."); + ret = _pivot_with_state_flat_find(win, ATSPI_STATE_FOCUSED); + if (ret) + return ret; + + return NULL; +} diff --git a/src/screen_reader.c b/src/screen_reader.c new file mode 100644 index 0000000..e465e1b --- /dev/null +++ b/src/screen_reader.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "screen_reader.h" +#include "screen_reader_tts.h" +#include "screen_reader_vconf.h" +#include "screen_reader_spi.h" +#include +#include "logger.h" + +#ifdef RUN_IPC_TEST_SUIT +#include "test_suite/test_suite.h" +#endif + +#define BUF_SIZE 1024 + +Service_Data service_data = { + //Set by vconf + .run_service = 1, +#ifdef SCREEN_READER_TV + .tracking_signal_name = FOCUS_CHANGED_SIG, +#else + .tracking_signal_name = HIGHLIGHT_CHANGED_SIG, +#endif + + //Set by tts + .tts = NULL, + .available_languages = NULL, + + //Actions to do when tts state is 'ready' + .update_language_list = false, + + .text_to_say_info = NULL +}; + +Service_Data *get_pointer_to_service_data_struct() +{ + return &service_data; +} + +int screen_reader_create_service(void *data) +{ + Service_Data *service_data = data; + + vconf_init(service_data); + tts_init(service_data); + +#ifdef SCREEN_READER_TV + spi_init(service_data); +#endif + + /* XML TEST */ +#ifdef RUN_IPC_TEST_SUIT + run_xml_tests(); + test_suite_init(); +#endif + + return 0; +} + +int screen_reader_terminate_service(void *data) +{ + DEBUG("Service Terminate Callback \n"); + + Service_Data *service_data = data; + + tts_stop(service_data->tts); + tts_unprepare(service_data->tts); + tts_destroy(service_data->tts); + service_data->text_from_dbus = NULL; + service_data->current_value = NULL; + + return 0; +} diff --git a/src/screen_reader_gestures.c b/src/screen_reader_gestures.c new file mode 100644 index 0000000..c55d606 --- /dev/null +++ b/src/screen_reader_gestures.c @@ -0,0 +1,1126 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "screen_reader_gestures.h" +#include "logger.h" + +#include +#include +#include + +static GestureCB _global_cb; +static void *_global_data; +static Ecore_Window win; +static Ecore_Event_Handler *property_changed_hld; + +struct _Gestures_Config { + // minimal required length of flick gesture (in pixels) + int one_finger_flick_min_length; + // maximal time of gesture + int one_finger_flick_max_time; + // timeout period to activate hover gesture (first longpress timeout) + double one_finger_hover_longpress_timeout; + // to activate flick gesture by 2 fingers (it is hotfix - gestures need serious refactoring) + int two_finger_flick_to_scroll_timeout; + // after mowing this pixels flick two finger flick to scroll gesture is started + int two_finger_flick_to_scroll_min_length; + // tap timeout - maximal ammount of time allowed between seqiential taps + double one_finger_tap_timeout; + // tap radius(in pixels) + int one_finger_tap_radius; +}; +typedef struct _Gestures_Config Gestures_Config; + +typedef enum { + FLICK_DIRECTION_UNDEFINED, + FLICK_DIRECTION_DOWN, + FLICK_DIRECTION_UP, + FLICK_DIRECTION_LEFT, + FLICK_DIRECTION_RIGHT, + FLICK_DIRECTION_DOWN_RETURN, + FLICK_DIRECTION_UP_RETURN, + FLICK_DIRECTION_LEFT_RETURN, + FLICK_DIRECTION_RIGHT_RETURN, +} flick_direction_e; + +typedef enum { + GESTURE_NOT_STARTED = 0, // Gesture is ready to start + GESTURE_ONGOING, // Gesture in progress. + GESTURE_FINISHED, // Gesture finished - should be emited + GESTURE_ABORTED // Gesture aborted +} gesture_state_e; + +typedef enum { + ONE_FINGER_GESTURE = 1, + TWO_FINGERS_GESTURE, + THREE_FINGERS_GESTURE +} gesture_type_e; + +struct _Cover { + Ecore_X_Window win; /**< Input window covering given zone */ + unsigned int n_taps; /**< Number of fingers touching screen */ + unsigned int event_time; + + struct { + gesture_state_e state; // current state of gesture + unsigned int timestamp[3]; // time of gesture; + int finger[3]; // finger number which initiates gesture + int x_org[3], y_org[3]; // coorinates of finger down event + int x_end[3], y_end[3]; // coorinates of finger up event + flick_direction_e dir; // direction of flick + int n_fingers; // number of fingers in gesture + int n_fingers_left; // number of fingers in gesture + // still touching screen + Eina_Bool finger_out[3]; // finger is out of the finger boundary + Eina_Bool return_flick[3]; + Eina_Bool flick_to_scroll; + int flick_to_scroll_last_x; + int flick_to_scroll_last_y; + } flick_gesture; + + struct { + gesture_state_e state; // currest gesture state + int x[2], y[2]; + int n_fingers; + int finger[2]; + unsigned int timestamp; // time of gesture; + unsigned int last_emission_time; // last time of gesture emission + Ecore_Timer *timer; + Eina_Bool longpressed; + } hover_gesture; + + struct { + Eina_Bool started; // indicates if taps recognition process has started + Eina_Bool pressed; // indicates if finger is down + int n_taps; // number of taps captures in sequence + int finger[3]; // device id of finget + Ecore_Timer *timer; // sequence expiration timer + int x_org[3], y_org[3]; // coordinates of first tap + gesture_type_e tap_type; + } tap_gesture_data; +}; +typedef struct _Cover Cover; + +Gestures_Config *_e_mod_config; +static Ecore_X_Window scrolled_win; +static int rx, ry; +static Eina_List *handlers; +static Cover *cov; +static int win_angle; + +static void _hover_event_emit(Cover * cov, int state); +static unsigned int _win_angle_get(void); + +void __transform_coordinates(int *ax, int *ay) +{ + Ecore_X_Window root; + int w; + int h; + int tmp; + + win_angle = _win_angle_get(); + + switch (win_angle) { + case 90: + root = ecore_x_window_root_first_get(); + ecore_x_window_geometry_get(root, NULL, NULL, &w, &h); + tmp = *ax; + *ax = h - *ay; + *ay = tmp; + break; + case 270: + root = ecore_x_window_root_first_get(); + ecore_x_window_geometry_get(root, NULL, NULL, &w, &h); + tmp = *ax; + *ax = *ay; + *ay = w - tmp; + break; + } +} + +static void _event_emit(Gesture g, int x, int y, int x_e, int y_e, int state, int event_time) +{ + Gesture_Info *info = calloc(sizeof(Gesture_Info), 1); + EINA_SAFETY_ON_NULL_RETURN(info); + + __transform_coordinates(&x, &y); + __transform_coordinates(&x_e, &y_e); + + info->type = g; + info->x_beg = x; + info->x_end = x_e; + info->y_beg = y; + info->y_end = y_e; + info->state = state; + info->event_time = event_time; + + if (_global_cb) + _global_cb(_global_data, info); + free(info); +} + +static void _flick_gesture_mouse_down(Ecore_Event_Mouse_Button * ev, Cover * cov) +{ + if (cov->flick_gesture.state == GESTURE_NOT_STARTED) { + cov->flick_gesture.state = GESTURE_ONGOING; + cov->flick_gesture.finger[0] = ev->multi.device; + cov->flick_gesture.x_org[0] = ev->root.x; + cov->flick_gesture.y_org[0] = ev->root.y; + cov->flick_gesture.timestamp[0] = ev->timestamp; + cov->flick_gesture.flick_to_scroll = EINA_FALSE; + cov->flick_gesture.n_fingers = 1; + cov->flick_gesture.n_fingers_left = 1; + cov->flick_gesture.dir = FLICK_DIRECTION_UNDEFINED; + cov->flick_gesture.finger_out[0] = EINA_FALSE; + cov->flick_gesture.return_flick[0] = EINA_FALSE; + } else if (cov->flick_gesture.state == GESTURE_ONGOING) { + // abort gesture if too many fingers touched screen + if ((cov->n_taps > 3) || (cov->flick_gesture.n_fingers > 2)) { + cov->flick_gesture.state = GESTURE_ABORTED; + return; + } + + cov->flick_gesture.x_org[cov->flick_gesture.n_fingers] = ev->root.x; + cov->flick_gesture.y_org[cov->flick_gesture.n_fingers] = ev->root.y; + cov->flick_gesture.timestamp[cov->flick_gesture.n_fingers] = ev->timestamp; + cov->flick_gesture.finger[cov->flick_gesture.n_fingers] = ev->multi.device; + cov->flick_gesture.n_fingers++; + cov->flick_gesture.n_fingers_left++; + if (cov->flick_gesture.n_fingers < 3) { /* n_fingers == 3 makes out of bounds write */ + cov->flick_gesture.finger_out[cov->flick_gesture.n_fingers] = EINA_FALSE; + cov->flick_gesture.return_flick[cov->flick_gesture.n_fingers] = EINA_FALSE; + } + } +} + +static Eina_Bool _flick_gesture_time_check(unsigned int event_time, unsigned int gesture_time) +{ + DEBUG("Flick time: %d", event_time - gesture_time); + if ((event_time - gesture_time) < _e_mod_config->one_finger_flick_max_time * 2) //Double time because of the possible of return flick + return EINA_TRUE; + else + return EINA_FALSE; +} + +static Eina_Bool _flick_gesture_length_check(int x, int y, int x_org, int y_org) +{ + int dx = x - x_org; + int dy = y - y_org; + + if ((dx * dx + dy * dy) > (_e_mod_config->one_finger_flick_min_length * _e_mod_config->one_finger_flick_min_length)) + return EINA_TRUE; + else + return EINA_FALSE; +} + +static flick_direction_e _flick_gesture_direction_get(int x, int y, int x_org, int y_org) +{ + int dx = x - x_org; + int dy = y - y_org; + int tmp; + + switch (win_angle) { + case 90: + tmp = dx; + dx = -dy; + dy = tmp; + break; + case 270: + tmp = dx; + dx = dy; + dy = -tmp; + break; + } + + if ((dy < 0) && (abs(dx) < -dy)) + return FLICK_DIRECTION_UP; + if ((dy > 0) && (abs(dx) < dy)) + return FLICK_DIRECTION_DOWN; + if ((dx > 0) && (dx > abs(dy))) + return FLICK_DIRECTION_RIGHT; + if ((dx < 0) && (-dx > abs(dy))) + return FLICK_DIRECTION_LEFT; + + return FLICK_DIRECTION_UNDEFINED; +} + +static void _flick_event_emit(Cover * cov) +{ + int ax, ay, axe, aye, i, type = -1; + ax = ay = axe = aye = 0; + + for (i = 0; i < cov->flick_gesture.n_fingers; i++) { + ax += cov->flick_gesture.x_org[i]; + ay += cov->flick_gesture.y_org[i]; + axe += cov->flick_gesture.x_end[i]; + aye += cov->flick_gesture.y_end[i]; + } + + ax /= cov->flick_gesture.n_fingers; + ay /= cov->flick_gesture.n_fingers; + axe /= cov->flick_gesture.n_fingers; + aye /= cov->flick_gesture.n_fingers; + + if (cov->flick_gesture.dir == FLICK_DIRECTION_LEFT) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_LEFT; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_LEFT; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_LEFT; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_RIGHT) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_RIGHT; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_RIGHT; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_RIGHT; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_UP) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_UP; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_UP; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_UP; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_DOWN) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_DOWN; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_DOWN; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_DOWN; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_DOWN_RETURN) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_DOWN_RETURN; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_DOWN_RETURN; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_DOWN_RETURN; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_UP_RETURN) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_UP_RETURN; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_UP_RETURN; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_UP_RETURN; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_LEFT_RETURN) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_LEFT_RETURN; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_LEFT_RETURN; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_LEFT_RETURN; + } else if (cov->flick_gesture.dir == FLICK_DIRECTION_RIGHT_RETURN) { + if (cov->flick_gesture.n_fingers == 1) + type = ONE_FINGER_FLICK_RIGHT_RETURN; + if (cov->flick_gesture.n_fingers == 2) + type = TWO_FINGERS_FLICK_RIGHT_RETURN; + if (cov->flick_gesture.n_fingers == 3) + type = THREE_FINGERS_FLICK_RIGHT_RETURN; + } + DEBUG("FLICK GESTURE: N: %d F: %d", cov->flick_gesture.n_fingers, cov->flick_gesture.dir); + _event_emit(type, ax, ay, axe, aye, 2, cov->event_time); +} + +static void _flick_gesture_mouse_up(Ecore_Event_Mouse_Button * ev, Cover * cov) +{ + if (cov->flick_gesture.state == GESTURE_ONGOING) { + int i; + // check if fingers match + for (i = 0; i < cov->flick_gesture.n_fingers; i++) { + if (cov->flick_gesture.finger[i] == ev->multi.device) + break; + } + if (i == cov->flick_gesture.n_fingers) { + DEBUG("Finger id not recognized. Gesture aborted."); + cov->flick_gesture.state = GESTURE_ABORTED; + goto end; + } + if (cov->flick_gesture.flick_to_scroll) + { + if (ev->multi.device == 1) { + //if it is second finger then update x and y, + //We use last x and y coordinates in end_scroll. + //So if the first finger is up before + //the second one we will use latest x and y of second finger + //because second was the finger that scroll follows. + //Else we can encounter that delta between last continue_scroll + //coordinates and end_scroll coordinates will be high. + cov->flick_gesture.flick_to_scroll_last_x = ev->x; + cov->flick_gesture.flick_to_scroll_last_y = ev->y; + } + + DEBUG("Flick gesture was interpreted as scroll so we aborting it."); + cov->flick_gesture.state = GESTURE_ABORTED; + goto end; + } + // check if flick for given finger is valid + if (!_flick_gesture_time_check(ev->timestamp, cov->flick_gesture.timestamp[i])) { + DEBUG("finger flick gesture timeout expired. Gesture aborted."); + cov->flick_gesture.state = GESTURE_ABORTED; + goto end; + } + // check minimal flick length + if (!_flick_gesture_length_check(ev->root.x, ev->root.y, cov->flick_gesture.x_org[i], cov->flick_gesture.y_org[i])) { + if (!cov->flick_gesture.finger_out[i]) { + DEBUG("Minimal gesture length not reached and no return flick. Gesture aborted."); + cov->flick_gesture.state = GESTURE_ABORTED; + goto end; + } + cov->flick_gesture.return_flick[i] = EINA_TRUE; + } + + flick_direction_e s = cov->flick_gesture.return_flick[i] ? cov->flick_gesture.dir : _flick_gesture_direction_get(ev->root.x, ev->root.y, + cov->flick_gesture.x_org[i], + cov->flick_gesture.y_org[i]); + + cov->flick_gesture.n_fingers_left--; + + if ((cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir > FLICK_DIRECTION_RIGHT) + && cov->flick_gesture.return_flick[i] == EINA_FALSE) { + DEBUG("Flick gesture"); + cov->flick_gesture.dir = s; + } + // gesture is valid only if all flicks are in same direction + if (cov->flick_gesture.dir != s) { + DEBUG("Flick in different direction. Gesture aborted."); + cov->flick_gesture.state = GESTURE_ABORTED; + goto end; + } + + cov->flick_gesture.x_end[i] = ev->root.x; + cov->flick_gesture.y_end[i] = ev->root.y; + + if (!cov->flick_gesture.n_fingers_left) { + _flick_event_emit(cov); + cov->flick_gesture.state = GESTURE_NOT_STARTED; + } + } + + end: + // if no finger is touching a screen, gesture will be reseted. + if (cov->flick_gesture.state == GESTURE_ABORTED) { + if (cov->flick_gesture.flick_to_scroll) { + end_scroll(cov->flick_gesture.flick_to_scroll_last_x, cov->flick_gesture.flick_to_scroll_last_y); + cov->flick_gesture.flick_to_scroll = EINA_FALSE; + } + if (cov->n_taps == 0) + cov->flick_gesture.state = GESTURE_NOT_STARTED; + } +} + +static Eina_Bool _flick_to_scroll_gesture_conditions_met(Ecore_Event_Mouse_Move * ev, int gesture_timestamp, int dx, int dy) +{ + if (ev->timestamp - gesture_timestamp > _e_mod_config->two_finger_flick_to_scroll_timeout) + if (abs(dx) > _e_mod_config->two_finger_flick_to_scroll_min_length || abs(dy) > _e_mod_config->two_finger_flick_to_scroll_min_length) + return EINA_TRUE; + + return EINA_FALSE; +} + +static void _flick_gesture_mouse_move(Ecore_Event_Mouse_Move * ev, Cover * cov) +{ + if (cov->flick_gesture.state == GESTURE_ONGOING) { + int i; + for (i = 0; i < cov->flick_gesture.n_fingers; ++i) { + if (cov->flick_gesture.finger[i] == ev->multi.device) + break; + } + if (i == cov->flick_gesture.n_fingers) { + if (cov->flick_gesture.n_fingers >= 3) //that is because of the EFL bug. Mouse move event before mouse down(!) + { + ERROR("Finger id not recognized. Gesture aborted."); + cov->flick_gesture.state = GESTURE_ABORTED; + return; + } + } + + int dx = ev->root.x - cov->flick_gesture.x_org[i]; + int dy = ev->root.y - cov->flick_gesture.y_org[i]; + int tmp; + + switch (win_angle) { + case 90: + tmp = dx; + dx = -dy; + dy = tmp; + break; + case 270: + tmp = dx; + dx = dy; + dy = -tmp; + break; + } + if (i == 1) { + if (cov->flick_gesture.flick_to_scroll || _flick_to_scroll_gesture_conditions_met(ev, cov->flick_gesture.timestamp[i], dx, dy)) { + if (!cov->flick_gesture.flick_to_scroll) { + start_scroll(ev->x, ev->y); + cov->flick_gesture.flick_to_scroll = EINA_TRUE; + } else { + continue_scroll(ev->x, ev->y); + } + cov->flick_gesture.flick_to_scroll_last_x = ev->x; + cov->flick_gesture.flick_to_scroll_last_y = ev->y; + return; + } + } + + if (!cov->flick_gesture.finger_out[i]) { + if (abs(dx) > _e_mod_config->one_finger_flick_min_length) { + cov->flick_gesture.finger_out[i] = EINA_TRUE; + if (dx > 0) { + if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_RIGHT_RETURN) { + cov->flick_gesture.dir = FLICK_DIRECTION_RIGHT_RETURN; + } else { + ERROR("Invalid direction, abort"); + cov->flick_gesture.state = GESTURE_ABORTED; + } + } else { + if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_LEFT_RETURN) { + cov->flick_gesture.dir = FLICK_DIRECTION_LEFT_RETURN; + } else { + ERROR("Invalid direction, abort"); + cov->flick_gesture.state = GESTURE_ABORTED; + } + } + return; + } + + else if (abs(dy) > _e_mod_config->one_finger_flick_min_length) { + cov->flick_gesture.finger_out[i] = EINA_TRUE; + if (dy > 0) { + if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_DOWN_RETURN) { + cov->flick_gesture.dir = FLICK_DIRECTION_DOWN_RETURN; + } else { + ERROR("Invalid direction, abort"); + cov->flick_gesture.state = GESTURE_ABORTED; + } + } else { + if (cov->flick_gesture.dir == FLICK_DIRECTION_UNDEFINED || cov->flick_gesture.dir == FLICK_DIRECTION_UP_RETURN) { + cov->flick_gesture.dir = FLICK_DIRECTION_UP_RETURN; + } else { + ERROR("Invalid direction, abort"); + cov->flick_gesture.state = GESTURE_ABORTED; + } + } + return; + } + } + } + return; +} + +static Eina_Bool _on_hover_timeout(void *data) +{ + Cover *cov = data; + DEBUG("Hover timer expierd"); + + cov->hover_gesture.longpressed = EINA_TRUE; + cov->hover_gesture.timer = NULL; + + if (cov->hover_gesture.last_emission_time == -1) { + _hover_event_emit(cov, 0); + cov->hover_gesture.last_emission_time = cov->event_time; + } + return EINA_FALSE; +} + +static void _hover_gesture_timer_reset(Cover * cov, double time) +{ + DEBUG("Hover timer reset"); + cov->hover_gesture.longpressed = EINA_FALSE; + if (cov->hover_gesture.timer) { + ecore_timer_reset(cov->hover_gesture.timer); + return; + } + cov->hover_gesture.timer = ecore_timer_add(time, _on_hover_timeout, cov); +} + +static void _hover_gesture_mouse_down(Ecore_Event_Mouse_Button * ev, Cover * cov) +{ + if (cov->hover_gesture.state == GESTURE_NOT_STARTED && cov->n_taps == 1) { + cov->hover_gesture.state = GESTURE_ONGOING; + cov->hover_gesture.timestamp = ev->timestamp; + cov->hover_gesture.last_emission_time = -1; + cov->hover_gesture.x[0] = ev->root.x; + cov->hover_gesture.y[0] = ev->root.y; + cov->hover_gesture.finger[0] = ev->multi.device; + cov->hover_gesture.n_fingers = 1; + _hover_gesture_timer_reset(cov, _e_mod_config->one_finger_hover_longpress_timeout); + } + if (cov->hover_gesture.state == GESTURE_ONGOING && cov->n_taps == 2) { + if (cov->hover_gesture.longpressed) { + _hover_event_emit(cov, 2); + goto abort; + } + cov->hover_gesture.timestamp = -1; + cov->hover_gesture.last_emission_time = -1; + cov->hover_gesture.x[1] = ev->root.x; + cov->hover_gesture.y[1] = ev->root.y; + cov->hover_gesture.finger[1] = ev->multi.device; + cov->hover_gesture.n_fingers = 2; + _hover_gesture_timer_reset(cov, _e_mod_config->one_finger_hover_longpress_timeout); + } + // abort gesture if more then 2 fingers touched screen + if ((cov->hover_gesture.state == GESTURE_ONGOING) && cov->n_taps > 2) { + DEBUG("More then 2 finged. Abort hover gesture"); + _hover_event_emit(cov, 2); + goto abort; + } + return; + + abort: + cov->hover_gesture.state = GESTURE_ABORTED; + if (cov->hover_gesture.timer) + ecore_timer_del(cov->hover_gesture.timer); + cov->hover_gesture.timer = NULL; +} + +static void _hover_gesture_mouse_up(Ecore_Event_Mouse_Button * ev, Cover * cov) +{ + int i; + if (cov->hover_gesture.state == GESTURE_ONGOING) { + + for (i = 0; i < cov->hover_gesture.n_fingers; i++) { + if (cov->hover_gesture.finger[i] == ev->multi.device) + break; + } + if (i == cov->hover_gesture.n_fingers) { + DEBUG("Invalid finger id: %d", ev->multi.device); + return; + } else { + cov->hover_gesture.state = GESTURE_ABORTED; + if (cov->hover_gesture.timer) + ecore_timer_del(cov->hover_gesture.timer); + cov->hover_gesture.timer = NULL; + // aditionally emit event to complete sequence + if (cov->hover_gesture.longpressed) + _hover_event_emit(cov, 2); + } + } + // reset gesture only if user released all his fingers + if (cov->n_taps == 0) + cov->hover_gesture.state = GESTURE_NOT_STARTED; +} + +static void _get_root_coords(Ecore_X_Window win, int *x, int *y) +{ + Ecore_X_Window root = ecore_x_window_root_first_get(); + Ecore_X_Window parent = ecore_x_window_parent_get(win); + int wx, wy; + + if (x) + *x = 0; + if (y) + *y = 0; + + while (parent && (root != parent)) { + ecore_x_window_geometry_get(parent, &wx, &wy, NULL, NULL); + if (x) + *x += wx; + if (y) + *y += wy; + parent = ecore_x_window_parent_get(parent); + } +} + +Ecore_X_Window top_window_get(int x, int y) +{ + Ecore_X_Window wins[1] = { win }; + Ecore_X_Window under = ecore_x_window_at_xy_with_skip_get(x, y, wins, sizeof(wins) / sizeof(wins[0])); + if (under) { + _get_root_coords(under, &rx, &ry); + DEBUG("Recieved window with coords:%d %d", rx, ry); + return under; + } + return 0; +} + +void start_scroll(int x, int y) +{ + Ecore_X_Window wins[1] = { win }; + Ecore_X_Window under = ecore_x_window_at_xy_with_skip_get(x, y, wins, sizeof(wins) / sizeof(wins[0])); + _get_root_coords(under, &rx, &ry); + ecore_x_mouse_in_send(under, x - rx, y - ry); + ecore_x_window_focus(under); + ecore_x_mouse_down_send(under, x - rx, y - ry, 1); + scrolled_win = under; +} + +void continue_scroll(int x, int y) +{ + ecore_x_mouse_move_send(scrolled_win, x - rx, y - ry); +} + +void end_scroll(int x, int y) +{ + ecore_x_mouse_up_send(scrolled_win, x - rx, y - ry, 1); + ecore_x_mouse_out_send(scrolled_win, x - rx, y - ry); +} + +static unsigned int _win_angle_get(void) +{ + Ecore_X_Window root, first_root; + int ret; + int count; + int angle = 0; + unsigned char *prop_data = NULL; + + first_root = ecore_x_window_root_first_get(); + root = ecore_x_window_root_get(first_root); + ret = ecore_x_window_prop_property_get(root, ECORE_X_ATOM_E_ILLUME_ROTATE_ROOT_ANGLE, ECORE_X_ATOM_CARDINAL, 32, &prop_data, &count); + + if (ret && prop_data) + memcpy(&angle, prop_data, sizeof(int)); + + if (prop_data) + free(prop_data); + + return angle; +} + +static void _hover_event_emit(Cover * cov, int state) +{ + int ax = 0, ay = 0, j; + + for (j = 0; j < cov->hover_gesture.n_fingers; j++) { + ax += cov->hover_gesture.x[j]; + ay += cov->hover_gesture.y[j]; + } + + ax /= cov->hover_gesture.n_fingers; + ay /= cov->hover_gesture.n_fingers; + + switch (cov->hover_gesture.n_fingers) { + case 1: + INFO("ONE FINGER HOVER"); + _event_emit(ONE_FINGER_HOVER, ax, ay, ax, ay, state, cov->event_time); + break; + default: + break; + } +} + +static void _hover_gesture_mouse_move(Ecore_Event_Mouse_Move * ev, Cover * cov) +{ + if (cov->hover_gesture.state == GESTURE_ONGOING) { + // check fingers + int i; + if (!cov->hover_gesture.longpressed) + return; + + for (i = 0; i < cov->hover_gesture.n_fingers; i++) { + if (cov->hover_gesture.finger[i] == ev->multi.device) + break; + } + if (i == cov->hover_gesture.n_fingers) { + DEBUG("Invalid finger id: %d", ev->multi.device); + return; + } + cov->hover_gesture.x[i] = ev->root.x; + cov->hover_gesture.y[i] = ev->root.y; + _hover_event_emit(cov, 1); + } +} + +static void _tap_event_emit(Cover * cov, int state) +{ + switch (cov->tap_gesture_data.n_taps) { + case 1: + if (cov->tap_gesture_data.tap_type == ONE_FINGER_GESTURE) { + DEBUG("ONE_FINGER_SINGLE_TAP"); + _event_emit(ONE_FINGER_SINGLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], state, cov->event_time); + } else if (cov->tap_gesture_data.tap_type == TWO_FINGERS_GESTURE) { + DEBUG("TWO_FINGERS_SINGLE_TAP"); + _event_emit(TWO_FINGERS_SINGLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[1], cov->tap_gesture_data.y_org[1], state, cov->event_time); + } else if (cov->tap_gesture_data.tap_type == THREE_FINGERS_GESTURE) { + DEBUG("THREE_FINGERS_SINGLE_TAP"); + _event_emit(THREE_FINGERS_SINGLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[2], cov->tap_gesture_data.y_org[2], state, cov->event_time); + } else { + ERROR("Unknown tap"); + } + break; + case 2: + if (cov->tap_gesture_data.tap_type == ONE_FINGER_GESTURE) { + DEBUG("ONE_FINGER_DOUBLE_TAP"); + _event_emit(ONE_FINGER_DOUBLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], state, cov->event_time); + } else if (cov->tap_gesture_data.tap_type == TWO_FINGERS_GESTURE) { + DEBUG("TWO_FINGERS_DOUBLE_TAP"); + _event_emit(TWO_FINGERS_DOUBLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[1], cov->tap_gesture_data.y_org[1], state, cov->event_time); + } else if (cov->tap_gesture_data.tap_type == THREE_FINGERS_GESTURE) { + DEBUG("THREE_FINGERS_DOUBLE_TAP"); + _event_emit(THREE_FINGERS_DOUBLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[2], cov->tap_gesture_data.y_org[2], state, cov->event_time); + } else { + ERROR("Unknown tap"); + } + break; + case 3: + if (cov->tap_gesture_data.tap_type == ONE_FINGER_GESTURE) { + DEBUG("ONE_FINGER_TRIPLE_TAP"); + _event_emit(ONE_FINGER_TRIPLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], state, cov->event_time); + } else if (cov->tap_gesture_data.tap_type == TWO_FINGERS_GESTURE) { + DEBUG("TWO_FINGERS_TRIPLE_TAP"); + _event_emit(TWO_FINGERS_TRIPLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[1], cov->tap_gesture_data.y_org[1], state, cov->event_time); + } else if (cov->tap_gesture_data.tap_type == THREE_FINGERS_GESTURE) { + DEBUG("THREE_FINGERS_TRIPLE_TAP"); + _event_emit(THREE_FINGERS_TRIPLE_TAP, cov->tap_gesture_data.x_org[0], cov->tap_gesture_data.y_org[0], cov->tap_gesture_data.x_org[2], cov->tap_gesture_data.y_org[2], state, cov->event_time); + } else { + ERROR("Unknown tap"); + } + break; + default: + ERROR("Unknown tap"); + break; + } +} + +static Eina_Bool _on_tap_timer_expire(void *data) +{ + Cover *cov = data; + DEBUG("Timer expired"); + + if (cov->tap_gesture_data.started && !cov->tap_gesture_data.pressed) + _tap_event_emit(cov, 2); + else + _tap_event_emit(cov, 3); + + // finish gesture + cov->tap_gesture_data.started = EINA_FALSE; + cov->tap_gesture_data.timer = NULL; + cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE; + cov->tap_gesture_data.finger[0] = -1; + cov->tap_gesture_data.finger[1] = -1; + cov->tap_gesture_data.finger[2] = -1; + + return EINA_FALSE; +} + +static int _tap_gesture_finger_check(Cover * cov, int x, int y) +{ + int dx = x - cov->tap_gesture_data.x_org[0]; + int dy = y - cov->tap_gesture_data.y_org[0]; + + if (cov->tap_gesture_data.finger[0] != -1 && (dx * dx + dy * dy < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius)) { + return 0; + } + + dx = x - cov->tap_gesture_data.x_org[1]; + dy = y - cov->tap_gesture_data.y_org[1]; + if (cov->tap_gesture_data.finger[1] != -1 && (dx * dx + dy * dy < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius)) { + return 1; + } + + dx = x - cov->tap_gesture_data.x_org[2]; + dy = y - cov->tap_gesture_data.y_org[2]; + if (cov->tap_gesture_data.finger[2] != -1 && (dx * dx + dy * dy < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius)) { + return 2; + } + + return -1; +} + +static void _tap_gestures_mouse_down(Ecore_Event_Mouse_Button * ev, Cover * cov) +{ + if (cov->n_taps > 4) { + ERROR("Too many fingers"); + return; + } + + cov->tap_gesture_data.pressed = EINA_TRUE; + + if (cov->tap_gesture_data.started == EINA_FALSE) { + DEBUG("First finger down"); + cov->tap_gesture_data.started = EINA_TRUE; + cov->tap_gesture_data.finger[0] = ev->multi.device; + cov->tap_gesture_data.x_org[0] = ev->root.x; + cov->tap_gesture_data.y_org[0] = ev->root.y; + cov->tap_gesture_data.finger[1] = -1; + cov->tap_gesture_data.finger[2] = -1; + cov->tap_gesture_data.n_taps = 0; + cov->tap_gesture_data.timer = ecore_timer_add(_e_mod_config->one_finger_tap_timeout, _on_tap_timer_expire, cov); + cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE; + } + + else { + if (ev->multi.device == cov->tap_gesture_data.finger[0]) { + DEBUG("First finger down"); + + if (_tap_gesture_finger_check(cov, ev->root.x, ev->root.y) == -1) { + ERROR("Abort gesture"); + cov->tap_gesture_data.started = EINA_FALSE; + ecore_timer_del(cov->tap_gesture_data.timer); + cov->tap_gesture_data.timer = NULL; + cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE; + cov->tap_gesture_data.finger[0] = -1; + cov->tap_gesture_data.finger[1] = -1; + cov->tap_gesture_data.finger[2] = -1; + _tap_gestures_mouse_down(ev, cov); + return; + } + + cov->tap_gesture_data.x_org[0] = ev->root.x; + cov->tap_gesture_data.y_org[0] = ev->root.y; + } else if (cov->tap_gesture_data.finger[1] == -1 || cov->tap_gesture_data.finger[1] == ev->multi.device) { + DEBUG("Second finger down"); + cov->tap_gesture_data.finger[1] = ev->multi.device; + + cov->tap_gesture_data.x_org[1] = ev->root.x; + cov->tap_gesture_data.y_org[1] = ev->root.y; + if (cov->tap_gesture_data.tap_type < TWO_FINGERS_GESTURE) + cov->tap_gesture_data.tap_type = TWO_FINGERS_GESTURE; + } else if (cov->tap_gesture_data.finger[2] == -1 || cov->tap_gesture_data.finger[2] == ev->multi.device) { + DEBUG("Third finger down"); + cov->tap_gesture_data.finger[2] = ev->multi.device; + + cov->tap_gesture_data.x_org[2] = ev->root.x; + cov->tap_gesture_data.y_org[2] = ev->root.y; + if (cov->tap_gesture_data.tap_type < THREE_FINGERS_GESTURE) + cov->tap_gesture_data.tap_type = THREE_FINGERS_GESTURE; + } else { + ERROR("Unknown finger down"); + } + ecore_timer_reset(cov->tap_gesture_data.timer); + } +} + +static void _tap_gestures_mouse_up(Ecore_Event_Mouse_Button * ev, Cover * cov) +{ + if (cov->tap_gesture_data.timer) { + cov->tap_gesture_data.pressed = EINA_FALSE; + + if (ev->multi.device == cov->tap_gesture_data.finger[0]) { + DEBUG("First finger up"); + + int dx = ev->root.x - cov->tap_gesture_data.x_org[0]; + int dy = ev->root.y - cov->tap_gesture_data.y_org[0]; + + if ((dx * dx + dy * dy) < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) { + if (cov->n_taps == 0) { + cov->tap_gesture_data.n_taps++; + } + } else { + ERROR("Abort gesture"); + cov->tap_gesture_data.started = EINA_FALSE; + } + } else if (ev->multi.device == cov->tap_gesture_data.finger[1]) { + DEBUG("Second finger up"); + + int dx = ev->root.x - cov->tap_gesture_data.x_org[1]; + int dy = ev->root.y - cov->tap_gesture_data.y_org[1]; + + if ((dx * dx + dy * dy) < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) { + if (cov->n_taps == 0) { + cov->tap_gesture_data.n_taps++; + } + } else { + ERROR("Abort gesture"); + cov->tap_gesture_data.started = EINA_FALSE; + } + } else if (ev->multi.device == cov->tap_gesture_data.finger[2]) { + DEBUG("Third finger up"); + + int dx = ev->root.x - cov->tap_gesture_data.x_org[2]; + int dy = ev->root.y - cov->tap_gesture_data.y_org[2]; + + if ((dx * dx + dy * dy) < _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) { + if (cov->n_taps == 0) { + cov->tap_gesture_data.n_taps++; + } + } else { + ERROR("Abort gesture"); + cov->tap_gesture_data.started = EINA_FALSE; + } + } else { + ERROR("Unknown finger up, abort gesture"); + cov->tap_gesture_data.started = EINA_FALSE; + } + } +} + +static void _tap_gestures_move(Ecore_Event_Mouse_Move * ev, Cover * cov) +{ + int i; + for (i = 0; i < sizeof(cov->tap_gesture_data.finger) / sizeof(cov->tap_gesture_data.finger[0]); i++) { + if (ev->multi.device == cov->tap_gesture_data.finger[i]) { + int dx = ev->root.x - cov->tap_gesture_data.x_org[i]; + int dy = ev->root.y - cov->tap_gesture_data.y_org[i]; + + if ((dx * dx + dy * dy) > _e_mod_config->one_finger_tap_radius * _e_mod_config->one_finger_tap_radius) { + DEBUG("abort tap gesutre"); + cov->tap_gesture_data.started = EINA_FALSE; + ecore_timer_del(cov->tap_gesture_data.timer); + cov->tap_gesture_data.timer = NULL; + cov->tap_gesture_data.tap_type = ONE_FINGER_GESTURE; + cov->tap_gesture_data.finger[0] = -1; + cov->tap_gesture_data.finger[1] = -1; + cov->tap_gesture_data.finger[2] = -1; + } + break; + } + } +} + +static Eina_Bool _cb_mouse_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_Event_Mouse_Button *ev = event; + + cov->n_taps++; + cov->event_time = ev->timestamp; + + DEBUG("mouse down: multi.device: %d, taps: %d", ev->multi.device, cov->n_taps); + + win_angle = _win_angle_get(); + + _flick_gesture_mouse_down(ev, cov); + _hover_gesture_mouse_down(ev, cov); + _tap_gestures_mouse_down(ev, cov); + + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool _cb_mouse_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_Event_Mouse_Button *ev = event; + + cov->n_taps--; + cov->event_time = ev->timestamp; + + DEBUG("mouse up, multi.device: %d, taps: %d", ev->multi.device, cov->n_taps); + + _flick_gesture_mouse_up(ev, cov); + _hover_gesture_mouse_up(ev, cov); + _tap_gestures_mouse_up(ev, cov); + + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool _cb_mouse_move(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_Event_Mouse_Move *ev = event; + + cov->event_time = ev->timestamp; + + _flick_gesture_mouse_move(ev, cov); + _hover_gesture_mouse_move(ev, cov); + _tap_gestures_move(ev, cov); + + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool _gesture_input_win_create(void) +{ + int w, h; + + if (!win) { + Ecore_Window root = ecore_x_window_root_first_get(); + if (!root) + return EINA_FALSE; + ecore_x_window_geometry_get(root, NULL, NULL, &w, &h); + win = ecore_x_window_input_new(root, 0, 0, w, h); + } + if (!win) + return EINA_FALSE; + + ecore_x_input_multi_select(win); + ecore_x_window_show(win); + ecore_x_window_raise(win); + + // restet gestures + memset(cov, 0x0, sizeof(Cover)); + + return EINA_TRUE; +} + +static Eina_Bool _win_property_changed(void *data, int type, void *event) +{ + Ecore_X_Event_Window_Property *wp = event; + + if (wp->atom != ECORE_X_ATOM_NET_CLIENT_LIST_STACKING) + return EINA_TRUE; + + _gesture_input_win_create(); + + return EINA_TRUE; +} + +static Eina_Bool _gestures_input_window_init(void) +{ + Ecore_Window root = ecore_x_window_root_first_get(); + if (!root) { + ERROR("No root window found. Is Window manager running?"); + return EINA_FALSE; + } + ecore_x_event_mask_set(root, ECORE_X_EVENT_MASK_WINDOW_PROPERTY); + property_changed_hld = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_PROPERTY, _win_property_changed, NULL); + + return _gesture_input_win_create(); +} + +static void _gestures_input_widnow_shutdown(void) +{ + ecore_event_handler_del(property_changed_hld); + if (win) + ecore_x_window_free(win); + win = 0; +} + +Eina_Bool screen_reader_gestures_init(void) +{ + ecore_init(); + ecore_x_init(NULL); + + cov = calloc(sizeof(Cover), 1); + + if (!_gestures_input_window_init()) { + free(cov); + return EINA_FALSE; + } + + _e_mod_config = calloc(sizeof(Gestures_Config), 1); + _e_mod_config->one_finger_flick_min_length = 100; + _e_mod_config->one_finger_flick_max_time = 400; + _e_mod_config->two_finger_flick_to_scroll_timeout = 100; + _e_mod_config->two_finger_flick_to_scroll_min_length = 50; + _e_mod_config->one_finger_hover_longpress_timeout = 0.81; + _e_mod_config->one_finger_tap_timeout = 0.4; + _e_mod_config->one_finger_tap_radius = 100; + + handlers = eina_list_append(NULL, ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE, _cb_mouse_move, NULL)); + handlers = eina_list_append(handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, _cb_mouse_up, NULL)); + handlers = eina_list_append(handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, _cb_mouse_down, NULL)); + + return EINA_TRUE; +} + +void screen_reader_gestures_shutdown(void) +{ + Ecore_Event_Handler *hdlr; + EINA_LIST_FREE(handlers, hdlr) { + ecore_event_handler_del(hdlr); + } + _gestures_input_widnow_shutdown(); + + ecore_x_shutdown(); + ecore_shutdown(); + free(_e_mod_config); + free(cov); +} + +void screen_reader_gestures_tracker_register(GestureCB cb, void *data) +{ + _global_cb = cb; + _global_data = data; +} diff --git a/src/screen_reader_haptic.c b/src/screen_reader_haptic.c new file mode 100644 index 0000000..f099cb4 --- /dev/null +++ b/src/screen_reader_haptic.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "logger.h" +#include "smart_notification.h" + +static haptic_device_h handle; +static haptic_effect_h effect_handle; + +#define RED "\x1B[31m" +#define RESET "\033[0m" + +/** + * @brief Initializer for haptic module + * + */ +void haptic_module_init(void) +{ + int num; + + if (!device_haptic_get_count(&num)) { + DEBUG(RED "Haptic device received!" RESET); + } else { + ERROR("Cannot receive haptic device count"); + return; + } + + if (!device_haptic_open(0, &handle)) { + DEBUG(RED "Device connected!" RESET); + } else { + ERROR("Cannot open haptic device"); + } +} + +/** + * @brief Disconnect haptic handle + * + */ +void haptic_module_disconnect(void) +{ + if (!handle) { + ERROR("Haptic handle lost"); + return; + } + if (!device_haptic_close(handle)) { + DEBUG("Haptic disconnected"); + } else { + ERROR("Haptic close error"); + } +} + +/** + * @brief Start vibrations + * + */ +void haptic_vibrate_start(void) +{ + if (!handle) { + ERROR("Haptic handle lost"); + return; + } + if (!device_haptic_vibrate(handle, 1000, 100, &effect_handle)) { + DEBUG(RED "Vibrations started!" RESET); + } else { + ERROR("Cannot start vibration"); + } +} + +/** + * @brief Stop vibrations + * + */ +void haptic_vibrate_stop(void) +{ + if (!handle) { + ERROR("Haptic handle lost"); + return; + } + if (!device_haptic_stop(handle, &effect_handle)) { + ERROR("Vibrations stopped!"); + } else { + DEBUG(RED "Cannot stop vibration" RESET); + } +} diff --git a/src/screen_reader_spi.c b/src/screen_reader_spi.c new file mode 100644 index 0000000..10645de --- /dev/null +++ b/src/screen_reader_spi.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include "screen_reader_spi.h" +#include "screen_reader_tts.h" +#include "logger.h" +#ifdef RUN_IPC_TEST_SUIT +#include "test_suite/test_suite.h" +#endif + +#define EPS 0.000000001 + +/** @brief Service_Data used as screen reader internal data struct*/ +static Service_Data *service_data; + +typedef struct { + char *key; + char *val; +} Attr; + +/** + * @brief Debug function. Print current toolkit version/event + * type/event source/event detail1/event detail2 + * + * @param AtspiEvent instance + * + */ +static void display_info(const AtspiEvent * event) +{ + AtspiAccessible *source = event->source; + gchar *name = atspi_accessible_get_name(source, NULL); + gchar *role = atspi_accessible_get_localized_role_name(source, NULL); + gchar *toolkit = atspi_accessible_get_toolkit_name(source, NULL); + + DEBUG("--------------------------------------------------------"); + DEBUG("Toolkit: %s; Event_type: %s; (%d, %d)", toolkit, event->type, event->detail1, event->detail2); + DEBUG("Name: %s; Role: %s", name, role); + DEBUG("--------------------------------------------------------"); +} + +Eina_Bool double_click_timer_cb(void *data) +{ + Service_Data *sd = data; + sd->clicked_widget = NULL; + + return EINA_FALSE; +} + +bool allow_recursive_name(AtspiAccessible * obj) +{ + AtspiRole r = atspi_accessible_get_role(obj, NULL); + if (r == ATSPI_ROLE_FILLER) + return true; + return false; +} + +char *generate_description_for_subtree(AtspiAccessible * obj) +{ + DEBUG("START"); + if (!allow_recursive_name(obj)) + return strdup(""); + + if (!obj) + return strdup(""); + int child_count = atspi_accessible_get_child_count(obj, NULL); + + DEBUG("There is %d children inside this filler", child_count); + if (!child_count) + return strdup(""); + + int i; + char *name = NULL; + char *below = NULL; + char ret[256] = "\0"; + AtspiAccessible *child = NULL; + for (i = 0; i < child_count; i++) { + child = atspi_accessible_get_child_at_index(obj, i, NULL); + name = atspi_accessible_get_name(child, NULL); + DEBUG("%d child name:%s", i, name); + if (name && strncmp(name, "\0", 1)) { + strncat(ret, name, sizeof(ret) - strlen(ret) - 1); + } + strncat(ret, " ", sizeof(ret) - strlen(ret) - 1); + below = generate_description_for_subtree(child); + if (strncmp(below, "\0", 1)) { + strncat(ret, below, sizeof(ret) - strlen(ret) - 1); + } + g_object_unref(child); + free(below); + free(name); + } + return strdup(ret); +} + +static char *spi_on_state_changed_get_text(AtspiEvent * event, void *user_data) +{ + Service_Data *sd = (Service_Data *) user_data; + char *name; + char *names = NULL; + char *description; + char *role_name; + char *other; + char ret[256] = "\0"; + sd->currently_focused = event->source; + + description = atspi_accessible_get_description(sd->currently_focused, NULL); + name = atspi_accessible_get_name(sd->currently_focused, NULL); + role_name = atspi_accessible_get_localized_role_name(sd->currently_focused, NULL); + other = generate_description_for_subtree(sd->currently_focused); + + DEBUG("->->->->->-> WIDGET GAINED HIGHLIGHT: %s <-<-<-<-<-<-<-", name); + DEBUG("->->->->->-> FROM SUBTREE HAS NAME: %s <-<-<-<-<-<-<-", other); + + if (name && strncmp(name, "\0", 1)) + names = strdup(name); + else if (other && strncmp(other, "\0", 1)) + names = strdup(other); + + if (names) { + strncat(ret, names, sizeof(ret) - strlen(ret) - 1); + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + } + + if (role_name) + strncat(ret, role_name, sizeof(ret) - strlen(ret) - 1); + + if (description) { + if (strncmp(description, "\0", 1)) + strncat(ret, ", ", sizeof(ret) - strlen(ret) - 1); + strncat(ret, description, sizeof(ret) - strlen(ret) - 1); + } + + free(name); + free(names); + free(description); + free(role_name); + free(other); + + return strdup(ret); +} + +static char *spi_on_caret_move_get_text(AtspiEvent * event, void *user_data) +{ + Service_Data *sd = (Service_Data *) user_data; + sd->currently_focused = event->source; + char *return_text; + + AtspiText *text_interface = atspi_accessible_get_text_iface(sd->currently_focused); + if (text_interface) { + DEBUG("->->->->->-> WIDGET CARET MOVED: %s <-<-<-<-<-<-<-", atspi_accessible_get_name(sd->currently_focused, NULL)); + + int char_count = (int)atspi_text_get_character_count(text_interface, NULL); + int caret_pos = atspi_text_get_caret_offset(text_interface, NULL); + if (!caret_pos) { + DEBUG("MIN POSITION REACHED"); + if (asprintf(&return_text, "%s %s", (char *)atspi_text_get_text(text_interface, caret_pos, caret_pos + 1, NULL), _("IDS_REACHED_MIN_POS")) < 0) { + ERROR(MEMORY_ERROR); + return NULL; + } + } else if (char_count == caret_pos) { + DEBUG("MAX POSITION REACHED"); + if (asprintf(&return_text, "%s %s", (char *)atspi_text_get_text(text_interface, caret_pos, caret_pos + 1, NULL), _("IDS_REACHED_MAX_POS")) < 0) { + ERROR(MEMORY_ERROR); + return NULL; + } + } else { + if (asprintf(&return_text, "%s", (char *)atspi_text_get_text(text_interface, caret_pos, caret_pos + 1, NULL)) < 0) { + ERROR(MEMORY_ERROR); + return NULL; + } + } + } else { + ERROR(MEMORY_ERROR); + return NULL; + } + return return_text; +} + +static char *spi_on_value_changed_get_text(AtspiEvent * event, void *user_data) +{ + Service_Data *sd = (Service_Data *) user_data; + char *text_to_read = NULL; + + sd->currently_focused = event->source; + + AtspiValue *value_interface = atspi_accessible_get_value_iface(sd->currently_focused); + if (value_interface) { + DEBUG("->->->->->-> WIDGET VALUE CHANGED: %s <-<-<-<-<-<-<-", atspi_accessible_get_name(sd->currently_focused, NULL)); + + double current_temp_value = (double)atspi_value_get_current_value(value_interface, NULL); + if (abs(current_temp_value - atspi_value_get_maximum_value(value_interface, NULL)) < EPS) { + DEBUG("MAX VALUE REACHED"); + if (asprintf(&text_to_read, "%.2f %s", current_temp_value, _("IDS_REACHED_MAX_VAL")) < 0) { + ERROR(MEMORY_ERROR); + return NULL; + } + } else if (abs(current_temp_value - atspi_value_get_minimum_value(value_interface, NULL)) < EPS) { + DEBUG("MIN VALUE REACHED"); + if (asprintf(&text_to_read, "%.2f %s", current_temp_value, _("IDS_REACHED_MIN_VAL")) < 0) { + ERROR(MEMORY_ERROR); + return NULL; + } + } else { + if (asprintf(&text_to_read, "%.2f", current_temp_value) < 0) { + ERROR(MEMORY_ERROR); + return NULL; + } + } + } + + return text_to_read; +} + +char *spi_event_get_text_to_read(AtspiEvent * event, void *user_data) +{ + DEBUG("START"); + Service_Data *sd = (Service_Data *) user_data; + char *text_to_read; + + DEBUG("TRACK SIGNAL:%s", sd->tracking_signal_name); + DEBUG("WENT EVENT:%s", event->type); + + if (!sd->tracking_signal_name) { + ERROR("Invalid tracking signal name"); + return NULL; + } + + if (!strncmp(event->type, sd->tracking_signal_name, strlen(event->type)) && event->detail1 == 1) { + text_to_read = spi_on_state_changed_get_text(event, user_data); + } else if (!strncmp(event->type, CARET_MOVED_SIG, strlen(event->type))) { + text_to_read = spi_on_caret_move_get_text(event, user_data); + } else if (!strncmp(event->type, VALUE_CHANGED_SIG, strlen(event->type))) { + text_to_read = spi_on_value_changed_get_text(event, user_data); + } else { + ERROR("Unknown event type"); + return NULL; + } + + return text_to_read; +} + +void spi_event_listener_cb(AtspiEvent * event, void *user_data) +{ + DEBUG("START"); + display_info(event); + + if (!user_data) { + ERROR("Invalid parameter"); + return; + } + + char *text_to_read = spi_event_get_text_to_read(event, user_data); + if (!text_to_read) { + ERROR("Can not prepare text to read"); + return; + } + DEBUG("SPEAK: %s", text_to_read); + tts_speak(text_to_read, EINA_TRUE); + + free(text_to_read); + DEBUG("END"); +} + +/** + * @brief Initializer for screen-reader atspi listeners + * + * @param user_data screen-reader internal data + * +**/ +void spi_init(Service_Data * sd) +{ + if (!sd) { + ERROR("Invalid parameter"); + return; + } + DEBUG("--------------------- SPI_init START ---------------------"); + service_data = sd; + + DEBUG(">>> Creating listeners <<<"); + + sd->spi_listener = atspi_event_listener_new(spi_event_listener_cb, service_data, NULL); + if (sd->spi_listener == NULL) { + DEBUG("FAILED TO CREATE spi state changed listener"); + } + // --------------------------------------------------------------------------------------------------- + + DEBUG("TRACKING SIGNAL:%s", sd->tracking_signal_name); + + gboolean ret1 = atspi_event_listener_register(sd->spi_listener, sd->tracking_signal_name, NULL); + if (ret1 == false) { + DEBUG("FAILED TO REGISTER spi focus/highlight listener"); + } + GError *error = NULL; + gboolean ret2 = atspi_event_listener_register(sd->spi_listener, CARET_MOVED_SIG, &error); + if (ret2 == false) { + DEBUG("FAILED TO REGISTER spi caret moved listener: %s", error ? error->message : "no error message"); + if (error) + g_clear_error(&error); + } + + gboolean ret3 = atspi_event_listener_register(sd->spi_listener, VALUE_CHANGED_SIG, &error); + if (ret3 == false) { + DEBUG("FAILED TO REGISTER spi value changed listener: %s", error ? error->message : "no error message"); + if (error) + g_clear_error(&error); + } + + if (ret1 == true && ret2 == true && ret3 == true) { + DEBUG("spi listener REGISTERED"); + } + + DEBUG("---------------------- SPI_init END ----------------------\n\n"); +} diff --git a/src/screen_reader_switch.c b/src/screen_reader_switch.c new file mode 100644 index 0000000..651d49f --- /dev/null +++ b/src/screen_reader_switch.c @@ -0,0 +1,126 @@ +#include "logger.h" +#include + +Eina_Bool screen_reader_switch_enabled_get(Eina_Bool * value) +{ + Eldbus_Connection *conn; + Eldbus_Object *dobj; + Eldbus_Proxy *proxy; + Eldbus_Message *req, *reply; + const char *errname = NULL, *errmsg = NULL; + Eina_Bool ret = EINA_FALSE; + Eldbus_Message_Iter *iter; + + eldbus_init(); + + if (!(conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION))) { + ERROR("Connection to session bus failed"); + return EINA_FALSE; + } + if (!(dobj = eldbus_object_get(conn, "org.a11y.Bus", "/org/a11y/bus"))) { + ERROR("Failed to create eldbus object for /org/a11y/bus"); + goto fail_obj; + } + if (!(proxy = eldbus_proxy_get(dobj, "org.freedesktop.DBus.Properties"))) { + ERROR("Failed to create proxy object for 'org.freedesktop.DBus.Properties'"); + goto fail_proxy; + } + if (!(req = eldbus_proxy_method_call_new(proxy, "Get"))) { + ERROR("Failed to create method call on org.freedesktop.DBus.Properties.Get"); + goto fail_proxy; + } + eldbus_message_ref(req); + + if (!eldbus_message_arguments_append(req, "ss", "org.a11y.Status", "ScreenReaderEnabled")) { + ERROR("Failed to append message args"); + goto fail_msg; + } + + reply = eldbus_proxy_send_and_block(proxy, req, 100); + if (!reply || eldbus_message_error_get(reply, &errname, &errmsg)) { + ERROR("Unable to call method org.freedesktop.DBus.Properties.Get: %s %s", errname, errmsg); + goto fail_msg; + } + + if (!eldbus_message_arguments_get(reply, "v", &iter)) { + ERROR("Invalid answer signature"); + goto fail_msg; + } else { + if (!eldbus_message_iter_arguments_get(iter, "b", value)) { + ERROR("Invalid variant signature"); + } else + ret = EINA_TRUE; + } + + fail_msg: + eldbus_message_unref(req); + fail_proxy: + eldbus_object_unref(dobj); + fail_obj: + eldbus_connection_unref(conn); + + eldbus_shutdown(); + + return ret; +} + +Eina_Bool screen_reader_switch_enabled_set(Eina_Bool value) +{ + Eldbus_Connection *conn; + Eldbus_Object *dobj; + Eldbus_Proxy *proxy; + Eldbus_Message *req, *reply; + const char *errname = NULL, *errmsg = NULL; + Eina_Bool ret = EINA_FALSE; + Eldbus_Message_Iter *iter; + + eldbus_init(); + + if (!(conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION))) { + ERROR("Connection to session bus failed"); + return EINA_FALSE; + } + if (!(dobj = eldbus_object_get(conn, "org.a11y.Bus", "/org/a11y/bus"))) { + ERROR("Failed to create eldbus object"); + goto fail_obj; + } + if (!(proxy = eldbus_proxy_get(dobj, "org.freedesktop.DBus.Properties"))) { + ERROR("Failed to create proxy object for 'org.freedesktop.DBus.Properties'"); + goto fail_proxy; + } + if (!(req = eldbus_proxy_method_call_new(proxy, "Set"))) { + ERROR("Failed to create method call on org.freedesktop.DBus.Properties.Set"); + goto fail_proxy; + } + eldbus_message_ref(req); + + if (!eldbus_message_arguments_append(req, "ss", "org.a11y.Status", "ScreenReaderEnabled")) { + ERROR("Failed to append message args"); + goto fail_msg; + } + if (!(iter = eldbus_message_iter_container_new(eldbus_message_iter_get(req), 'v', "b"))) { + ERROR("Unable to create variant iterator"); + goto fail_msg; + } + if (!eldbus_message_iter_arguments_append(iter, "b", value)) { + ERROR("Unable to append to variant iterator"); + goto fail_msg; + } + if (!eldbus_message_iter_container_close(eldbus_message_iter_get(req), iter)) { + ERROR("Failed to close variant iterator"); + goto fail_msg; + } + eldbus_proxy_send(proxy, req, NULL, NULL, -1.0); + ret = EINA_TRUE; + + fail_msg: + eldbus_message_unref(req); + fail_proxy: + eldbus_object_unref(dobj); + fail_obj: + eldbus_connection_unref(conn); + + eldbus_shutdown(); + + return ret; +} diff --git a/src/screen_reader_system.c b/src/screen_reader_system.c new file mode 100644 index 0000000..b3a457b --- /dev/null +++ b/src/screen_reader_system.c @@ -0,0 +1,670 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCREEN_READER_TV + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "screen_reader.h" +#include "screen_reader_tts.h" +#include "smart_notification.h" +#include "logger.h" + +#define MAX_SIM_COUNT 2 +#define DATE_TIME_BUFFER_SIZE 26 + +TapiHandle *tapi_handle[MAX_SIM_COUNT + 1] = { 0, }; + +static void device_system_cb(device_callback_e type, void *value, void *user_data); + +static void tapi_init(void) +{ + int i = 0; + char **cp_list = tel_get_cp_name_list(); + + if (!cp_list) { + ERROR("cp name list is null"); + return; + } + + DEBUG("TAPI INIT"); + for (i = 0; cp_list[i]; ++i) { + tapi_handle[i] = tel_init(cp_list[i]); + DEBUG("CP_LIST %d = %s", i, cp_list[i]); + } + +} + +/** + * @brief Initializer for smart notifications + * + */ +void system_notifications_init(void) +{ + DEBUG("******************** START ********************"); + int ret = -1; + + // BATTERY LOW/FULL + device_add_callback(DEVICE_CALLBACK_BATTERY_LEVEL, device_system_cb, NULL); + // BATTERY CHARGING/NOT-CHARGING + device_add_callback(DEVICE_CALLBACK_BATTERY_CHARGING, device_system_cb, NULL); + // SCREEN OFF/ON + device_add_callback(DEVICE_CALLBACK_DISPLAY_STATE, device_system_cb, NULL); + + ret = bt_initialize(); + if (ret != BT_ERROR_NONE) { + ERROR("ret == %d", ret); + } + + ret = wifi_initialize(); + if (ret != WIFI_ERROR_NONE) { + ERROR("ret == %d", ret); + } + + tapi_init(); + + DEBUG(" ********************* END ********************* "); +} + +/** + * @brief Initializer for smart notifications + * + */ +void system_notifications_shutdown(void) +{ + int ret = -1; + + // BATTERY LOW/FULL + device_remove_callback(DEVICE_CALLBACK_BATTERY_LEVEL, device_system_cb); + // BATTERY CHARGING/NOT-CHARGING + device_remove_callback(DEVICE_CALLBACK_BATTERY_CHARGING, device_system_cb); + // SCREEN OFF/ON + device_remove_callback(DEVICE_CALLBACK_DISPLAY_STATE, device_system_cb); + + ret = bt_deinitialize(); + if (ret != BT_ERROR_NONE) { + ERROR("ret == %d", ret); + } + + ret = wifi_deinitialize(); + if (ret != WIFI_ERROR_NONE) { + ERROR("ret == %d", ret); + return; + } +} + +/** + * @brief Device system callback handler + * + * @param type Device callback type + * @param value UNUSED + * @param user_data UNUSED + */ +static void device_system_cb(device_callback_e type, void *value, void *user_data) +{ + if (type == DEVICE_CALLBACK_BATTERY_LEVEL) { + device_battery_level_e status; + if (device_battery_get_level_status(&status)) { + ERROR("Cannot get battery level status"); + return; + } + + if (status == DEVICE_BATTERY_LEVEL_LOW) { + tts_speak(_("IDS_SYSTEM_BATTERY_LOW"), EINA_TRUE); + } else if (status == DEVICE_BATTERY_LEVEL_CRITICAL) { + tts_speak(_("IDS_SYSTEM_BATTERY_CRITICAL"), EINA_TRUE); + } else if (status == DEVICE_BATTERY_LEVEL_FULL) { + tts_speak(_("IDS_SYSTEM_BATTERY_FULL"), EINA_TRUE); + } + } else if (type == DEVICE_CALLBACK_BATTERY_CHARGING) { + bool charging; + if (device_battery_is_charging(&charging)) { + ERROR("Cannot check if battery is charging"); + return; + } + + if (!charging) + tts_speak(_("IDS_SYSTEM_NOT_CHARGING"), EINA_FALSE); + } else if (type == DEVICE_CALLBACK_DISPLAY_STATE) { + display_state_e state; + if (device_display_get_state(&state)) { + ERROR("Cannot check if battery is charging"); + return; + } + + if (state == DISPLAY_STATE_NORMAL) { + tts_speak(_("IDS_SYSTEM_SCREEN_ON"), EINA_FALSE); + } else if (state == DISPLAY_STATE_SCREEN_OFF) { + tts_speak(_("IDS_SYSTEM_SCREEN_OFF"), EINA_FALSE); + } + } +} + +// ******************************** Indicator info ********************************** // + +static int _read_text_get(char *key) +{ + int read_text = 0; + int ret = -1; + + ret = vconf_get_bool(key, &read_text); + if (ret != 0) { + ERROR("ret == %d", ret); + return true; + } + + return read_text; +} + +void device_time_get(void) +{ + char buffer[DATE_TIME_BUFFER_SIZE]; + int disp_12_24 = VCONFKEY_TIME_FORMAT_12; + int ret = -1; + time_t rawtime = 0; + struct tm *timeinfo = NULL; + + if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_TIME)) { + return; + } + + time(&rawtime); + timeinfo = localtime(&rawtime); + if (!timeinfo) { + ERROR("localtime returns NULL"); + return; + } + + ret = vconf_get_int(VCONFKEY_REGIONFORMAT_TIME1224, &disp_12_24); + if (ret != 0) { + ERROR("ret == %d", ret); + } + + if (disp_12_24 == VCONFKEY_TIME_FORMAT_24) { + strftime(buffer, DATE_TIME_BUFFER_SIZE, "Current time: %H %M", timeinfo); + } else { + strftime(buffer, DATE_TIME_BUFFER_SIZE, "Current time: %I %M %p", timeinfo); + } + + DEBUG("Text to say: %s", buffer); + tts_speak(buffer, EINA_FALSE); +} + +char *device_error_to_string(int e) +{ + switch (e) { + case DEVICE_ERROR_NONE: + return "DEVICE_ERROR_NONE"; + break; + + case DEVICE_ERROR_OPERATION_FAILED: + return "DEVICE_ERROR_OPERATION_FAILED"; + break; + + case DEVICE_ERROR_PERMISSION_DENIED: + return "DEVICE_ERROR_PERMISSION_DENIED"; + break; + + case DEVICE_ERROR_INVALID_PARAMETER: + return "DEVICE_ERROR_INVALID_PARAMETER"; + break; + + case DEVICE_ERROR_ALREADY_IN_PROGRESS: + return "DEVICE_ERROR_ALREADY_IN_PROGRESS"; + break; + + case DEVICE_ERROR_NOT_SUPPORTED: + return "DEVICE_ERROR_NOT_SUPPORTED"; + break; + + case DEVICE_ERROR_RESOURCE_BUSY: + return "DEVICE_ERROR_RESOURCE_BUSY"; + break; + + case DEVICE_ERROR_NOT_INITIALIZED: + return "DEVICE_ERROR_NOT_INITIALIZED"; + break; + + default: + return _("IDS_SYSTEM_NETWORK_SERVICE_UNKNOWN"); + break; + } +} + +void device_battery_get(void) +{ + char *buffer = NULL; + char *charging_text = NULL; + int percent; + bool is_charging = false; + int ret = -1; + + if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_BATTERY)) { + return; + } + + ret = device_battery_is_charging(&is_charging); + if (ret != DEVICE_ERROR_NONE) { + ERROR("ret == %s", device_error_to_string(ret)); + } + + if (is_charging) { + charging_text = _("IDS_SYSTEM_BATTERY_INFO_CHARGING"); + } else { + charging_text = ""; + } + + ret = device_battery_get_percent(&percent); + if (ret != DEVICE_ERROR_NONE) { + ERROR("ret == %s", device_error_to_string(ret)); + return; + } + + if (percent == 100) { + ret = asprintf(&buffer, "%s %s", charging_text, _("IDS_SYSTEM_BATTERY_FULLY_CHARGED_STR")); + if (ret == 0) { + free(buffer); + ERROR("Buffer length == 0"); + return; + } else if (ret < 0) { + ERROR("Buffer == NULL"); + return; + } + } else { + ret = asprintf(&buffer, "%s %d %% %s", charging_text, percent, _("IDS_SYSTEM_BATTERY_INFO_BATTERY_STR")); + if (ret == 0) { + free(buffer); + ERROR("Buffer length == 0"); + return; + } else if(ret < 0) { + ERROR("Buffer == NULL"); + return; + } + } + + if (!buffer) { + ERROR("buf == NULL"); + return; + } + + DEBUG("Text to say: %s", buffer); + tts_speak(buffer, EINA_FALSE); + free(buffer); +} + +static void _signal_strength_sim_get(void) +{ + int i = 0; + int val = 0; + int ret = -1; + int sim_card_count = 0; + Eina_Strbuf *str_buf = NULL; + char *buffer = NULL; + int service_type = TAPI_NETWORK_SERVICE_TYPE_UNKNOWN; + char *service_type_text = NULL; + + str_buf = eina_strbuf_new(); + + for (i = 0; tapi_handle[i]; ++i) { + ++sim_card_count; + } + + for (i = 0; tapi_handle[i]; ++i) { + ret = tel_get_property_int(tapi_handle[i], TAPI_PROP_NETWORK_SIGNALSTRENGTH_LEVEL, &val); + if (ret != TAPI_API_SUCCESS) { + ERROR("Can not get %s", TAPI_PROP_NETWORK_SIGNALSTRENGTH_LEVEL); + val = 0; + } + + if (sim_card_count > 1) + eina_strbuf_append_printf(str_buf, "%s %d %s %d; ", _("IDS_SYSTEM_SIGNAL_SIMCARD"), i + 1, _("IDS_SYSTEM_SIGNAL_STRENGTH"), val); + else + eina_strbuf_append_printf(str_buf, "%s %d; ", _("IDS_SYSTEM_SIGNAL_STRENGTH"), val); + DEBUG("sim: %d TAPI_PROP_NETWORK_SIGNALSTRENGTH_LEVEL %d", i, val); + + ret = tel_get_property_int(tapi_handle[i], TAPI_PROP_NETWORK_SERVICE_TYPE, &service_type); + if (ret != TAPI_API_SUCCESS) { + ERROR("Can not get %s", TAPI_PROP_NETWORK_SERVICE_TYPE); + } + + switch (service_type) { + case TAPI_NETWORK_SERVICE_TYPE_UNKNOWN: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_UNKNOWN"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_NO_SERVICE: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_NO_SERVICE"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_EMERGENCY: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_EMERGENCY"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_SEARCH: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_SEARCHING"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_2G: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_2G"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_2_5G: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_25G"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_2_5G_EDGE: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_EDGE"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_3G: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_3G"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_HSDPA: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_HSDPA"); + break; + + case TAPI_NETWORK_SERVICE_TYPE_LTE: + service_type_text = _("IDS_SYSTEM_NETWORK_SERVICE_LTE"); + break; + } + + eina_strbuf_append_printf(str_buf, " Service type: %s.", service_type_text); + } + + buffer = eina_strbuf_string_steal(str_buf); + + DEBUG("Text to say: %s", buffer); + tts_speak(buffer, EINA_FALSE); + + eina_strbuf_string_free(str_buf); + free(buffer); +} + +static void _signal_strength_wifi_get(void) +{ + int val = 0; + int ret = -1; + char *buffer = NULL; + char *wifi_text = NULL; + bool wifi_activated = false; + wifi_ap_h ap = NULL; + + ret = wifi_is_activated(&wifi_activated); + if (ret != WIFI_ERROR_NONE) { + ERROR("ret == %d", ret); + return; + } + + if (wifi_activated) { + ret = wifi_get_connected_ap(&ap); + if (ret != WIFI_ERROR_NONE) { + ERROR("ret == %d", ret); + return; + } + + if (!ap) { + DEBUG("Text to say: %s %s", _("IDS_SYSTEM_NETWORK_TYPE_WIFI"), "Not connected"); + + ret = asprintf(&buffer, " %s, %s", _("IDS_SYSTEM_NETWORK_TYPE_WIFI"), "Not connected"); + if (ret == 0) { + free(buffer); + ERROR("Buffer length == 0"); + return; + } else if (ret < 0) { + ERROR("Buffer == NULL"); + return; + } + + tts_speak(buffer, EINA_FALSE); + free(buffer); + return; + } + + ret = wifi_ap_get_rssi(ap, &val); + if (ret != WIFI_ERROR_NONE) { + ERROR("ret == %d", ret); + wifi_ap_destroy(ap); + return; + } + + switch (val) { + case 0: + wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_NO_SIGNAL"); + break; + + case 1: + wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_POOR"); + break; + + case 2: + wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_WEAK"); + break; + + case 3: + wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_MEDIUM"); + break; + + case 4: + wifi_text = _("IDS_SYSTEM_WIFI_SIGNAL_GOOD"); + break; + } + + if (!asprintf(&buffer, " %s, %s", _("IDS_SYSTEM_NETWORK_TYPE_WIFI"), wifi_text)) { + ERROR("buffer length == 0"); + wifi_ap_destroy(ap); + return; + } + + DEBUG("Text to say: %s", buffer); + tts_speak(buffer, EINA_FALSE); + free(buffer); + wifi_ap_destroy(ap); + } +} + +void device_signal_strenght_get(void) +{ + if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_SIGNAL_STRENGHT)) { + return; + } + _signal_strength_sim_get(); + _signal_strength_wifi_get(); +} + +void device_missed_events_get(void) +{ + notification_list_h list = NULL; + notification_list_h elem = NULL; + notification_h noti = NULL; + int ret = -1; + char *noti_count_text = NULL; + int noti_count = 0; + int current_noti_count = 0; + char *buffer = NULL; + + if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_MISSED_EVENTS)) { + return; + } + + ret = notification_get_list(NOTIFICATION_TYPE_NONE, -1, &list); + if (ret != NOTIFICATION_ERROR_NONE) { + ERROR("ret == %d", ret); + return; + } + + elem = notification_list_get_head(list); + + while (elem) { + noti = notification_list_get_data(elem); + notification_get_text(noti, NOTIFICATION_TEXT_TYPE_EVENT_COUNT, ¬i_count_text); + + if (noti_count_text) { + current_noti_count = atoi(noti_count_text); + if (current_noti_count > 0) { + noti_count += current_noti_count; + } else { + noti_count++; + } + } else { + noti_count++; + } + + elem = notification_list_get_next(elem); + } + + if (noti_count == 0) { + tts_speak(_("IDS_SYSTEM_NOTIFICATIONS_UNREAD_0"), EINA_FALSE); + } else if (noti_count == 1) { + tts_speak(_("IDS_SYSTEM_NOTIFICATIONS_UNREAD_1"), EINA_FALSE); + } else { + DEBUG("%d %s", noti_count, _("IDS_SYSTEM_NOTIFICATIONS_UNREAD_MANY")); + + if (asprintf(&buffer, "%d %s", noti_count, _("IDS_SYSTEM_NOTIFICATIONS_UNREAD_MANY"))) { + ERROR("buffer length equals 0"); + } + + tts_speak(buffer, EINA_FALSE); + free(buffer); + } + + ret = notification_free_list(list); + if (ret != NOTIFICATION_ERROR_NONE) { + ERROR("ret == %d", ret); + } +} + +void device_date_get(void) +{ + char buffer[DATE_TIME_BUFFER_SIZE]; + int date_format = SETTING_DATE_FORMAT_DD_MM_YYYY; + int ret = -1; + time_t rawtime = 0; + struct tm *timeinfo = NULL; + + if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_DATE)) { + return; + } + + time(&rawtime); + timeinfo = localtime(&rawtime); + if (!timeinfo) { + ERROR("localtime returns NULL"); + return; + } + + strftime(buffer, DATE_TIME_BUFFER_SIZE, "%Y:%m:%d %H:%M:%S", timeinfo); + + ret = vconf_get_int(VCONFKEY_SETAPPL_DATE_FORMAT_INT, &date_format); + if (ret != 0) { + ERROR("ret == %d", ret); + } + + switch (date_format) { + case SETTING_DATE_FORMAT_DD_MM_YYYY: + strftime(buffer, DATE_TIME_BUFFER_SIZE, "%d %B %Y", timeinfo); + break; + + case SETTING_DATE_FORMAT_MM_DD_YYYY: + strftime(buffer, DATE_TIME_BUFFER_SIZE, "%B %d %Y", timeinfo); + break; + + case SETTING_DATE_FORMAT_YYYY_MM_DD: + strftime(buffer, DATE_TIME_BUFFER_SIZE, "%Y %B %d", timeinfo); + break; + + case SETTING_DATE_FORMAT_YYYY_DD_MM: + strftime(buffer, DATE_TIME_BUFFER_SIZE, "%Y %d %B", timeinfo); + break; + } + + DEBUG("Text to say: %s", buffer); + tts_speak(buffer, EINA_FALSE); +} + +static bool bonded_device_count_cb(bt_device_info_s * device_info, void *user_data) +{ + int *device_count = (int *)user_data; + + (*device_count)++; + + return true; +} + +static bool bonded_device_get_cb(bt_device_info_s * device_info, void *user_data) +{ + char **device_name = (char **)user_data; + + if (asprintf(device_name, "%s connected", device_info->remote_name)) { + ERROR("buffer length == 0"); + } + + return false; +} + +void device_bluetooth_get(void) +{ + char *buffer = NULL; + int device_count = 0; + + if (!_read_text_get(VCONFKEY_SETAPPL_ACCESSIBILITY_TTS_INDICATOR_INFORMATION_BLUETOOTH)) { + return; + } + + bt_adapter_state_e adapter_state = BT_ADAPTER_DISABLED; + int ret = bt_adapter_get_state(&adapter_state); + if (ret != BT_ERROR_NONE) { + ERROR("ret == %d", ret); + return; + } + + if (adapter_state == BT_ADAPTER_DISABLED) { + DEBUG("Text to say: %s", _("IDS_SYSTEM_BT_BLUETOOTH_OFF")); + tts_speak(_("IDS_SYSTEM_BT_BLUETOOTH_OFF"), EINA_FALSE); + return; + } else { + bt_adapter_foreach_bonded_device(bonded_device_count_cb, (void *)&device_count); + + if (device_count == 0) { + if (!asprintf(&buffer, _("IDS_SYSTEM_BT_NO_DEVICES_CONNECTED"))) { + ERROR("buffer length == 0"); + } + } else if (device_count == 1) { + bt_adapter_foreach_bonded_device(bonded_device_get_cb, &buffer); + } else { + if (asprintf(&buffer, "%d %s", device_count, _("IDS_SYSTEM_BT_DEVICES_CONNECTED_COUNT"))) { + ERROR("buffer length == 0"); + } + } + + DEBUG("Text to say: %s", buffer); + tts_speak(buffer, EINA_FALSE); + free(buffer); + } +} + +#endif diff --git a/src/screen_reader_tts.c b/src/screen_reader_tts.c new file mode 100644 index 0000000..68c82ce --- /dev/null +++ b/src/screen_reader_tts.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include "screen_reader_tts.h" +#include "screen_reader_vconf.h" +#include "logger.h" + +// ---------------------------- DEBUG HELPERS ------------------------------ + +#define FLUSH_LIMIT 1 + +static int last_utt_id; +static Eina_Bool pause_state = EINA_FALSE; +static Eina_Bool flush_flag = EINA_FALSE; +static Eina_Strbuf *txt_keep_buff = NULL; + +static void (*on_utterance_end) (void); + +static void _text_keep(const char *txt) +{ + if (!txt_keep_buff) + return; + if (eina_strbuf_length_get(txt_keep_buff) > 0) + eina_strbuf_append(txt_keep_buff, ", "); + eina_strbuf_append(txt_keep_buff, txt); +} + +static char *get_tts_error(int r) +{ + switch (r) { + case TTS_ERROR_NONE: + { + return "no error"; + } + case TTS_ERROR_INVALID_PARAMETER: + { + return "inv param"; + } + case TTS_ERROR_OUT_OF_MEMORY: + { + return "out of memory"; + } + case TTS_ERROR_OPERATION_FAILED: + { + return "oper failed"; + } + case TTS_ERROR_INVALID_STATE: + { + return "inv state"; + } + default: + { + return "uknown error"; + } + } +} + +static char *get_tts_state(tts_state_e r) +{ + switch (r) { + case TTS_STATE_CREATED: + { + return "created"; + } + case TTS_STATE_READY: + { + return "ready"; + } + case TTS_STATE_PLAYING: + { + return "playing"; + } + case TTS_STATE_PAUSED: + { + return "pause"; + } + default: + { + return "uknown state"; + } + } +} + +//------------------------------------------------------------------------------------------------- + +void set_utterance_cb(void (*uter_cb) (void)) +{ + on_utterance_end = uter_cb; +} + +bool get_supported_voices_cb(tts_h tts, const char *language, int voice_type, void *user_data) +{ + DEBUG("LANG: %s; TYPE: %d", language, voice_type); + + Service_Data *sd = user_data; + Voice_Info *vi = calloc(1, sizeof(Voice_Info)); + if (!vi) { + ERROR(MEMORY_ERROR); + return ECORE_CALLBACK_CANCEL; + } + + if (asprintf(&vi->language, "%s", language) < 0) { + free(vi); + ERROR(MEMORY_ERROR); + return ECORE_CALLBACK_CANCEL; + } + + vi->voice_type = voice_type; + + sd->available_languages = eina_list_append(sd->available_languages, vi); + + return ECORE_CALLBACK_RENEW; +} + +static void __tts_test_utt_started_cb(tts_h tts, int utt_id, void *user_data) +{ + DEBUG("Utterance started : utt id(%d) \n", utt_id); + return; +} + +static void __tts_test_utt_completed_cb(tts_h tts, int utt_id, void *user_data) +{ + DEBUG("Utterance completed : utt id(%d) \n", utt_id); + if (last_utt_id - utt_id > FLUSH_LIMIT) + flush_flag = EINA_TRUE; + else { + if (flush_flag) + flush_flag = EINA_FALSE; + } + +#ifndef SCREEN_READER_TV + if (last_utt_id == utt_id) { + DEBUG("LAST UTTERANCE"); + pause_state = EINA_FALSE; + on_utterance_end(); + } +#endif + + return; +} + +bool tts_init(void *data) +{ + DEBUG("--------------------- TTS_init START ---------------------"); + Service_Data *sd = data; + + int r = tts_create(&sd->tts); + DEBUG("Create tts %d (%s)", r, get_tts_error(r)); + + r = tts_set_mode(sd->tts, TTS_MODE_SCREEN_READER); + DEBUG("Set tts mode SR %d (%s)", r, get_tts_error(r)); + + r = tts_prepare(sd->tts); + DEBUG("Prepare tts %d (%s)", r, get_tts_error(r)); + + tts_set_state_changed_cb(sd->tts, state_changed_cb, sd); + + tts_set_utterance_started_cb(sd->tts, __tts_test_utt_started_cb, sd); + tts_set_utterance_completed_cb(sd->tts, __tts_test_utt_completed_cb, sd); + + DEBUG("---------------------- TTS_init END ----------------------\n\n"); + txt_keep_buff = eina_strbuf_new(); + return true; +} + +Eina_Bool tts_pause_get(void) +{ + DEBUG("PAUSE STATE: %d", pause_state); + return pause_state; +} + +void tts_stop_set(void) +{ + Service_Data *sd = get_pointer_to_service_data_struct(); + tts_stop(sd->tts); +} + +Eina_Bool tts_pause_set(Eina_Bool pause_switch) +{ + Service_Data *sd = get_pointer_to_service_data_struct(); + if (!sd) + return EINA_FALSE; + + if (pause_switch) { + pause_state = EINA_TRUE; + + if (tts_pause(sd->tts)) { + pause_state = EINA_FALSE; + return EINA_FALSE; + } + } else if (!pause_switch) { + pause_state = EINA_FALSE; + + if (tts_play(sd->tts)) { + pause_state = EINA_TRUE; + return EINA_FALSE; + } + } + return EINA_TRUE; +} + +void tts_speak(char *text_to_speak, Eina_Bool flush_switch) +{ + int ret = 0; + Service_Data *sd = get_pointer_to_service_data_struct(); + int speak_id; + + if (!sd) + return; + tts_state_e state; + tts_get_state(sd->tts, &state); + + if (state != TTS_STATE_PLAYING && state != TTS_STATE_PAUSED && state != TTS_STATE_READY) { + if (text_to_speak) + _text_keep(text_to_speak); + return; + } + + if (flush_flag || flush_switch) { + if (state == TTS_STATE_PLAYING || state == TTS_STATE_PAUSED) { + ret = tts_stop(sd->tts); + if (TTS_ERROR_NONE != ret) { + DEBUG("Fail to stop TTS: resultl(%d)", ret); + } + } + } + + DEBUG("tts_speak\n"); + DEBUG("text to say:%s\n", text_to_speak); + if (!text_to_speak) + return; + if (!text_to_speak[0]) + return; + + if ((ret = tts_add_text(sd->tts, text_to_speak, NULL, TTS_VOICE_TYPE_AUTO, TTS_SPEED_AUTO, &speak_id))) { + switch (ret) { + case TTS_ERROR_INVALID_PARAMETER: + DEBUG("FAILED tts_add_text: error: TTS_ERROR_INVALID_PARAMETER"); + break; + case TTS_ERROR_INVALID_STATE: + DEBUG("FAILED tts_add_text: error: TTS_ERROR_INVALID_STATE, tts_state: %d", state); + break; + case TTS_ERROR_INVALID_VOICE: + DEBUG("FAILED tts_add_text: error: TTS_ERROR_INVALID_VOICE"); + break; + case TTS_ERROR_OPERATION_FAILED: + DEBUG("FAILED tts_add_text: error: TTS_ERROR_OPERATION_FAILED"); + break; + case TTS_ERROR_NOT_SUPPORTED: + DEBUG("FAILED tts_add_text: error: TTS_ERROR_NOT_SUPPORTED"); + break; + default: + DEBUG("FAILED tts_add_text: error: not recognized"); + } + return; + } + + DEBUG("added id to:%d\n", speak_id); + last_utt_id = speak_id; + tts_play(sd->tts); +} + +Eina_Bool update_supported_voices(void *data) +{ + DEBUG("START"); + tts_state_e state; + + Service_Data *sd = data; + + int res = tts_get_state(sd->tts, &state); + + if (res != TTS_ERROR_NONE) { + DEBUG("CANNOT RETRIVE STATE"); + return EINA_FALSE; + } + + if (state == TTS_STATE_READY) { + tts_foreach_supported_voices(sd->tts, get_supported_voices_cb, sd); + } else { + sd->update_language_list = EINA_TRUE; + } + + DEBUG("END"); + return EINA_TRUE; +} + +void state_changed_cb(tts_h tts, tts_state_e previous, tts_state_e current, void *user_data) +{ + if (pause_state) { + DEBUG("TTS is currently paused. Resume to start reading"); + return; + } + + DEBUG("++++++++++++++++state_changed_cb\n++++++++++++++++++"); + DEBUG("current state:%s and previous state:%s\n", get_tts_state(current), get_tts_state(previous)); + Service_Data *sd = user_data; + + if (TTS_STATE_CREATED == previous && TTS_STATE_READY == current) { + + update_supported_voices(sd); + + char *txt; + + if (!txt_keep_buff) + return; + if (!eina_strbuf_length_get(txt_keep_buff)) + return; + + txt = eina_strbuf_string_steal(txt_keep_buff); + eina_strbuf_free(txt_keep_buff); + txt_keep_buff = NULL; + tts_speak(txt, EINA_FALSE); + free(txt); + } +} + +void spi_stop(void *data) +{ + if (!data) { + ERROR("Invalid parameter"); + return; + } + + Service_Data *sd = data; + sd->update_language_list = false; + free((char *)sd->text_from_dbus); + free(sd->current_value); + sd->text_from_dbus = NULL; + sd->current_value = NULL; + tts_stop(sd->tts); +} diff --git a/src/screen_reader_vconf.c b/src/screen_reader_vconf.c new file mode 100644 index 0000000..b2daef2 --- /dev/null +++ b/src/screen_reader_vconf.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "screen_reader_vconf.h" +#include "screen_reader_spi.h" +#include "logger.h" + +#ifdef RUN_IPC_TEST_SUIT +#include "test_suite/test_suite.h" +#endif + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "SCREEN READER VCONF" + +keylist_t *keys = NULL; + +// ------------------------------ vconf callbacks---------------------- + +void app_termination_cb(keynode_t * node, void *user_data) +{ + DEBUG("START"); + DEBUG("Application terminate %d", !node->value.i); + + Service_Data *service_data = user_data; + service_data->run_service = node->value.i; + + if (service_data->run_service == 0) { + service_app_exit(); + } + + DEBUG("END"); +} + +void display_language_cb(keynode_t * node, void *user_data) +{ + DEBUG("START"); + DEBUG("Trying to set LC_MESSAGES to: %s", node->value.s); + + Service_Data *sd = user_data; + snprintf(sd->display_language, LANGUAGE_NAME_SIZE, "%s", node->value.s); + //to make gettext work + setenv("LC_MESSAGES", sd->display_language, 1); + + DEBUG("END"); +} + +// -------------------------------------------------------------------- + +int get_key_values(Service_Data * sd) +{ + DEBUG("START"); + int to_ret = 0; + + char *display_language = vconf_get_str("db/menu_widget/language"); + if (display_language) { + snprintf(sd->display_language, LANGUAGE_NAME_SIZE, "%s", display_language); + //to make gettext work + setenv("LC_MESSAGES", sd->display_language, 1); + free(display_language); + } else + WARNING("Can't get db/menu_widget/language value"); + + DEBUG("SCREEN READER DATA SET TO: Display_Language: %s, Tracking signal: %s;", sd->display_language, sd->tracking_signal_name); + + DEBUG("END"); + return to_ret; +} + +int _set_vconf_callback_and_print_message_on_error_and_return_error_code(const char *in_key, vconf_callback_fn cb, void *user_data) +{ + int ret = vconf_notify_key_changed(in_key, cb, user_data); + if (ret != 0) + DEBUG("Could not add notify callback to %s key", in_key); + + return ret; +} + +bool vconf_init(Service_Data * service_data) +{ + DEBUG("--------------------- VCONF_init START ---------------------"); + int ret = 0; + + if (vconf_set(keys)) { + DEBUG("nothing is written\n"); + } else { + DEBUG("everything is written\n"); + } + + vconf_keylist_free(keys); + // ---------------------------------------------------------------------------------- + + ret = get_key_values(service_data); + if (ret != 0) { + DEBUG("Could not set data from vconf: %d", ret); + } + + _set_vconf_callback_and_print_message_on_error_and_return_error_code("db/menu_widget/language", display_language_cb, service_data); + + DEBUG("---------------------- VCONF_init END ----------------------\n\n"); + return true; +} diff --git a/src/smart_notification.c b/src/smart_notification.c new file mode 100644 index 0000000..e519797 --- /dev/null +++ b/src/smart_notification.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "logger.h" +#include "screen_reader_tts.h" +#include "screen_reader_haptic.h" +#include "smart_notification.h" + +#define RED "\x1B[31m" +#define RESET "\033[0m" + +static Eina_Bool status = EINA_FALSE; + +static void _smart_notification_focus_chain_end(void); +static void _smart_notification_realized_items(int start_idx, int end_idx); + +/** + * @brief Smart Notifications event handler + * + * @param nt Notification event type + * @param start_index int first visible items index smart_notification_realized_items + * @param end_index int last visible items index used for smart_notification_realized_items + */ +void smart_notification(Notification_Type nt, int start_index, int end_index) +{ + DEBUG("START"); + if (!status) + return; + + switch (nt) { + case FOCUS_CHAIN_END_NOTIFICATION_EVENT: + _smart_notification_focus_chain_end(); + break; + case REALIZED_ITEMS_NOTIFICATION_EVENT: + _smart_notification_realized_items(start_index, end_index); + break; + default: + DEBUG("Gesture type %d not handled in switch", nt); + } +} + +/** + * @brief Used for getting first and last index of visible items + * + * @param scrollable_object AtspiAccessible object on which scroll was triggered + * @param start_index int first visible items index smart_notification_realized_items + * @param end_index int last visible items index used for smart_notification_realized_items + */ +void get_realized_items_count(AtspiAccessible * scrollable_object, int *start_idx, int *end_idx) +{ + DEBUG("START"); + int count_child, jdx; + AtspiAccessible *child_iter; + AtspiStateType state = ATSPI_STATE_SHOWING; + + if (!scrollable_object) { + DEBUG("No scrollable object"); + return; + } + + count_child = atspi_accessible_get_child_count(scrollable_object, NULL); + + for (jdx = 0; jdx < count_child; jdx++) { + child_iter = atspi_accessible_get_child_at_index(scrollable_object, jdx, NULL); + if (!child_iter) + continue; + + AtspiStateSet *state_set = atspi_accessible_get_state_set(child_iter); + + gboolean is_visible = atspi_state_set_contains(state_set, state); + if (is_visible) { + *start_idx = jdx; + DEBUG("Item with index %d is visible", jdx); + } else + DEBUG("Item with index %d is NOT visible", jdx); + } + *end_idx = *start_idx + 8; +} + +/** + * @brief Scroll-start/Scroll-end event callback + * + * @param event AtspiEvent + * @param user_data UNUSED + */ + +static void _scroll_event_cb(AtspiEvent * event, gpointer user_data) +{ + if (!status) + return; + + int start_index, end_index; + start_index = 0; + end_index = 0; + + gchar *role_name = atspi_accessible_get_role_name(event->source, NULL); + fprintf(stderr, "Event: %s: %d, obj: %p: role: %s\n", event->type, event->detail1, event->source, role_name); + g_free(role_name); + + if (!strcmp(event->type, "object:scroll-start")) { + DEBUG("Scrolling started"); + tts_speak(_("IDS_SCROLLING_STARTED"), EINA_TRUE); + } else if (!strcmp(event->type, "object:scroll-end")) { + DEBUG("Scrolling finished"); + tts_speak(_("IDS_SCROLLING_FINISHED"), EINA_FALSE); + get_realized_items_count((AtspiAccessible *) event->source, &start_index, &end_index); + _smart_notification_realized_items(start_index, end_index); + } +} + +/** + * @brief Initializer for smart notifications + * + * + */ +void smart_notification_init(void) +{ + DEBUG("Smart Notification init!"); + + AtspiEventListener *listener; + + listener = atspi_event_listener_new(_scroll_event_cb, NULL, NULL); + atspi_event_listener_register(listener, "object:scroll-start", NULL); + atspi_event_listener_register(listener, "object:scroll-end", NULL); + + haptic_module_init(); + + status = EINA_TRUE; +} + +/** + * @brief Smart notifications shutdown + * + */ +void smart_notification_shutdown(void) +{ + status = EINA_FALSE; +} + +/** + * @brief Smart notifications focus chain event handler + * + */ +static void _smart_notification_focus_chain_end(void) +{ + if (!status) + return; + + DEBUG(RED "Smart notification - FOCUS CHAIN END" RESET); + + tone_player_stop(0); + tone_player_start(TONE_TYPE_SUP_CONFIRM, SOUND_TYPE_MEDIA, 200, NULL); +} + +/** + * @brief Smart notifications realized items event handler + * + */ +static void _smart_notification_realized_items(int start_idx, int end_idx) +{ + if (!status) + return; + + if (start_idx == end_idx) + return; + + DEBUG(RED "Smart notification - Visible items notification" RESET); + + char buf[256]; + + snprintf(buf, sizeof(buf), _("IDS_REACHED_ITEMS_NOTIFICATION"), start_idx, end_idx); + + tts_speak(buf, EINA_FALSE); +} diff --git a/src/window_tracker.c b/src/window_tracker.c new file mode 100644 index 0000000..ebdb9be --- /dev/null +++ b/src/window_tracker.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "window_tracker.h" +#include "logger.h" +#include +#include + +static Window_Tracker_Cb user_cb; +static void *user_data; +static AtspiEventListener *listener; +static AtspiAccessible *last_active_win; + +static void _on_atspi_window_cb(const AtspiEvent * event) +{ + DEBUG("Event: %s: %s", event->type, atspi_accessible_get_name(event->source, NULL)); + + if (!strcmp(event->type, "window:activate") && last_active_win != event->source) //if we got activate 2 times + { + + if (user_cb) + user_cb(user_data, event->source); + last_active_win = event->source; + } +} + +static AtspiAccessible *_get_active_win(void) +{ + DEBUG("START"); + int i, j, desktop_children_count, app_children_count; + last_active_win = NULL; + AtspiAccessible *desktop = atspi_get_desktop(0); + if (!desktop) { + ERROR("DESKTOP NOT FOUND"); + return NULL; + } + + Ecore_X_Window focus_window = ecore_x_window_focus_get(); + unsigned int active_window_pid = 0; + if (focus_window) { + //invoking atspi_accessible_get_child_count for non active apps results in very long screen-reader startup + //not active apps have low priority and dbus calls take a lot of time (a few hundred ms per call) + //Hence we first try to determine accessible window using pid of currently focused window + if (!ecore_x_window_prop_card32_get(focus_window, ECORE_X_ATOM_NET_WM_PID, &active_window_pid, 1)) + active_window_pid = 0; + if (active_window_pid) + DEBUG("First we will try filter apps by PID: %i", active_window_pid); + } + desktop_children_count = atspi_accessible_get_child_count(desktop, NULL); + for (i = 0; i < desktop_children_count; i++) { + AtspiAccessible *app = atspi_accessible_get_child_at_index(desktop, i, NULL); + + if (active_window_pid == 0 || active_window_pid == atspi_accessible_get_process_id(app, NULL)) + app_children_count = atspi_accessible_get_child_count(app, NULL); + else + app_children_count = 0; + for (j = 0; j < app_children_count; j++) { + AtspiAccessible *win = atspi_accessible_get_child_at_index(app, j, NULL); + AtspiStateSet *states = atspi_accessible_get_state_set(win); + AtspiRole role = atspi_accessible_get_role(win, NULL); + if ((atspi_state_set_contains(states, ATSPI_STATE_ACTIVE)) && (role == ATSPI_ROLE_WINDOW)) + last_active_win = win; + + g_object_unref(states); + g_object_unref(win); + + if (last_active_win) + break; + } + g_object_unref(app); + if (active_window_pid > 0 && (i == desktop_children_count - 1)) { + // we are in last iteration and we should fall back to normal iteration over child windows + // without filtering by focus windows PID + i = -1; + active_window_pid = 0; + } + if (last_active_win) + break; + } + g_object_unref(desktop); + DEBUG("END last_active_win: %p", last_active_win); + return last_active_win; +} + +void window_tracker_init(void) +{ + DEBUG("START"); + listener = atspi_event_listener_new_simple(_on_atspi_window_cb, NULL); + atspi_event_listener_register(listener, "window:activate", NULL); +} + +void window_tracker_shutdown(void) +{ + DEBUG("START"); + atspi_event_listener_deregister(listener, "window:activate", NULL); + g_object_unref(listener); + listener = NULL; + user_cb = NULL; + user_data = NULL; + last_active_win = NULL; +} + +void window_tracker_register(Window_Tracker_Cb cb, void *data) +{ + DEBUG("START"); + user_cb = cb; + user_data = data; +} + +void window_tracker_active_window_request(void) +{ + DEBUG("START"); + _get_active_win(); + if (user_cb) + user_cb(user_data, last_active_win); +} diff --git a/tests/CMakeLists.sub b/tests/CMakeLists.sub new file mode 100755 index 0000000..249e698 --- /dev/null +++ b/tests/CMakeLists.sub @@ -0,0 +1,26 @@ +## PROJECT NAME +PROJECT(screen-reader-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 index 0000000..881fdb5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ + +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 + ecore + eldbus + eina + tts + check + vconf + elementary + capi-appfw-service-application +) + +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) +ADD_DEFINITIONS(-DSCREEN_READER_FLAT_NAVI_TEST_DUMMY_IMPLEMENTATION) + +ADD_EXECUTABLE(smart_navi_test_suite smart_navi_suite.c ${TESTED_SRCS}) +TARGET_LINK_LIBRARIES(smart_navi_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 index 0000000..096a51f --- /dev/null +++ b/tests/atspi/atspi.c @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "atspi.h" +#include +#include +#include + +static AtspiValue *value = NULL; +static AtspiText *text = NULL; +static AtspiEditableText *editable_text = NULL; +static AtspiAction *action = NULL; +//static AtspiComponent *component = NULL; + +G_DEFINE_TYPE(AtspiAccessible, atspi_accessible, G_TYPE_OBJECT); +G_DEFINE_TYPE(AtspiAction, atspi_action, 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"); + case ATSPI_ROLE_SCROLL_PANE: + return strdup("scroll pane"); + case ATSPI_ROLE_IMAGE: + return strdup("image"); + case ATSPI_ROLE_SPLIT_PANE: + return strdup("split pane"); + case ATSPI_ROLE_UNKNOWN: + return strdup("unknown"); + case ATSPI_ROLE_RULER: + return strdup("ruler"); + case ATSPI_ROLE_FOOTER: + return strdup("footer"); + case ATSPI_ROLE_INFO_BAR: + return strdup("infobar"); + case ATSPI_ROLE_LINK: + return strdup("link"); + default: + return strdup("\0"); + } +} + +gchar *atspi_accessible_get_localized_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); +} + +AtspiAction *atspi_accessible_get_action_iface(AtspiAccessible * obj) +{ + if (!obj) + return NULL; + return action; +} + +AtspiText *atspi_accessible_get_text_iface(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_iface(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; +} + +gboolean atspi_component_contains(AtspiComponent * obj, gint x, gint y, AtspiCoordType ctype, GError ** error) +{ + return TRUE; +} + +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_iface(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 = 1; + rect.y = 1; + 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 if (*(component->role) == ATSPI_ROLE_WINDOW) { + rect.x = 0; + rect.y = 0; + rect.width = 100; + rect.height = 100; + } else if (*(component->role) == ATSPI_ROLE_FILLER) { + 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; + obj->index_in_parent = 0; + obj->child_count = 0; + + GArray *states = g_array_new(TRUE, TRUE, sizeof(AtspiStateType)); + AtspiStateType s[] = { + ATSPI_STATE_VISIBLE, + ATSPI_STATE_SHOWING, + ATSPI_STATE_FOCUSABLE, + ATSPI_STATE_LAST_DEFINED + }; + int i; + for (i = 0; i < (int)(sizeof(s) / sizeof(s[0])); 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) +{ + child->index_in_parent = obj->child_count; + child->accessible_parent = obj; + + obj->children = g_list_append(obj->children, child); + obj->child_count++; +} + +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_action_class_init(AtspiActionClass * _class) +{ +} + +static void atspi_action_init(AtspiAction * obj) +{ +} + +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_iface(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; +} + +AtspiAccessible *atspi_accessible_get_parent(AtspiAccessible * obj, GError ** error) +{ + return g_object_ref (obj->accessible_parent); +} + +int atspi_component_get_highlight_index(AtspiComponent * obj, GError ** error) +{ + return 0; +} + +gint atspi_action_get_n_actions(AtspiAction * obj, GError ** error) +{ + return 0; +} + +gchar *atspi_action_get_action_name(AtspiAction * obj, gint i, GError ** error) +{ + return strdup(""); +} + +gint atspi_accessible_get_index_in_parent(AtspiAccessible * obj, GError ** error) +{ + return obj->index_in_parent; +} + +int atspi_exit(void) +{ + return 1; +} diff --git a/tests/atspi/atspi.h b/tests/atspi/atspi.h new file mode 100644 index 0000000..e03f548 --- /dev/null +++ b/tests/atspi/atspi.h @@ -0,0 +1,451 @@ + + +#ifndef __ATSPI_H__ +#define __ATSPI_H__ + +#include +#include +#include + +#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_ACCESSIBLE_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_ACTION_OBJECT_TYPE (atspi_action_get_type ()) +#define ATSPI_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATSPI_ACTION_OBJECT_TYPE, AtspiAccessible)) +#define ATSPI_ACTION_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATSPI_ACTION_OBJECT_TYPE)) +#define ATSPI_ACTION_CLASS(_class) (G_TYPE_CHECK_CLASS_CAST ((_class), ATSPI_ACTION_OBJECT_TYPE, AtspiAccessibleClass)) +#define ATSPI_ACTION_IS_OBJECT_CLASS(_class) (G_TYPE_CHECK_CLASS_TYPE ((_class), ATSPI_ACTION_OBJECT_TYPE)) +#define ATSPI_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ATSPI_ACTION_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 _AtspiAction AtspiAction; +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 _AtspiAction AtspiAction; + +typedef struct _AtspiAccessibleClass AtspiAccessibleClass; +typedef struct _AtspiActionClass AtspiActionClass; +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, + ATSPI_RELATION_CONTROLLER_FOR, + ATSPI_RELATION_CONTROLLED_BY, + ATSPI_RELATION_MEMBER_OF, + ATSPI_RELATION_TOOLTIP_FOR, + ATSPI_RELATION_NODE_CHILD_OF, + ATSPI_RELATION_NODE_PARENT_OF, + ATSPI_RELATION_EXTENDED, + ATSPI_RELATION_FLOWS_TO, + ATSPI_RELATION_FLOWS_FROM, + ATSPI_RELATION_SUBWINDOW_OF, + ATSPI_RELATION_EMBEDS, + ATSPI_RELATION_EMBEDDED_BY, + ATSPI_RELATION_POPUP_FOR, + ATSPI_RELATION_PARENT_WINDOW_OF, + ATSPI_RELATION_DESCRIPTION_FOR, + ATSPI_RELATION_DESCRIBED_BY, + ATSPI_RELATION_LAST_DEFINED, +} 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; + gint index_in_parent; + gint child_count; +}; + +struct _AtspiAccessibleClass +{ + GObjectClass parent_class; +}; + +struct _AtspiActionClass +{ + 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 _AtspiAction +{ + GTypeInterface parent; +}; +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_localized_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_iface (AtspiAccessible *obj); +AtspiAction * atspi_accessible_get_action_iface (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_iface (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_iface (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_iface (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); +AtspiAccessible * atspi_accessible_get_parent (AtspiAccessible *obj, GError **error); +gboolean atspi_component_contains (AtspiComponent *obj, gint x, gint y, AtspiCoordType ctype, GError **error); +int atspi_component_get_highlight_index(AtspiComponent *obj, GError **error); +gint atspi_accessible_get_index_in_parent (AtspiAccessible *obj, GError **error); +AtspiAction * atspi_accessible_get_action_iface (AtspiAccessible *obj); +gint atspi_action_get_n_actions (AtspiAction *obj, GError **error); +gchar * atspi_action_get_action_name (AtspiAction *obj, gint i, GError **error); + +int atspi_exit(void); + +#endif /*__ATSPI_H__*/ diff --git a/tests/smart_navi_suite.c b/tests/smart_navi_suite.c new file mode 100644 index 0000000..b7b637a --- /dev/null +++ b/tests/smart_navi_suite.c @@ -0,0 +1,596 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Flora License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "screen_reader_spi.h" +#include "flat_navi.h" +#include +#include +#include +#include + +static AtspiAccessible *root; +static AtspiAccessible *app; +static AtspiAccessible *win; +static AtspiAccessible *ly; +static AtspiAccessible *child1; +static AtspiAccessible *child2; +static AtspiAccessible *child3; +static AtspiAccessible *child4; +static AtspiAccessible *child5; +static AtspiAccessible *child6; +static AtspiAccessible *child7; +static AtspiAccessible *child8; +static AtspiAccessible *child9; +static AtspiAccessible *child10; +static AtspiAccessible *child11; +static AtspiAccessible *child12; +static AtspiAccessible *child13; +static AtspiAccessible *child14; +static AtspiAccessible *child15; +static AtspiAccessible *child16; +static AtspiAccessible *child17; +static AtspiAccessible *child18; +static AtspiAccessible *child19; +static AtspiAccessible *child20; +static AtspiAccessible *child21; +static AtspiAccessible *child22; +static AtspiAccessible *child23; +static FlatNaviContext *ctx; + +void setup(void) +{ + Service_Data *data = get_pointer_to_service_data_struct(); + data->run_service = 1; + data->tracking_signal_name = HIGHLIGHT_CHANGED_SIG; + + //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); + eina_init(); + atspi_alloc_memory(); + ctx = flat_navi_context_create(root); +} + +void setup_flat_navi2() +{ + setup(); + app = atspi_create_accessible(); + app->role = ATSPI_ROLE_APPLICATION; + + root = app; + win = atspi_create_accessible(); + win->role = ATSPI_ROLE_WINDOW; + + child1 = atspi_create_accessible(); + child2 = atspi_create_accessible(); + child3 = atspi_create_accessible(); + child4 = atspi_create_accessible(); + child5 = atspi_create_accessible(); + child6 = atspi_create_accessible(); + child7 = atspi_create_accessible(); + child8 = atspi_create_accessible(); + + child1->role = ATSPI_ROLE_FILLER; + child2->role = ATSPI_ROLE_FILLER; + child3->role = ATSPI_ROLE_FILLER; + child4->role = ATSPI_ROLE_FILLER; + child7->role = ATSPI_ROLE_FILLER; + child8->role = ATSPI_ROLE_FILLER; + + child5->role = ATSPI_ROLE_PUSH_BUTTON; + child5->name = "btn1"; + child6->role = ATSPI_ROLE_PUSH_BUTTON; + child6->name = "btn2"; + + atspi_accessible_add_child(app, win); + atspi_accessible_add_child(win, child1); + atspi_accessible_add_child(win, child7); + atspi_accessible_add_child(win, child2); + + atspi_accessible_add_child(child1, child3); + atspi_accessible_add_child(child2, child4); + + atspi_accessible_add_child(child7, child8); + + atspi_accessible_add_child(child3, child5); + atspi_accessible_add_child(child4, child6); + eina_init(); + atspi_alloc_memory(); + ctx = flat_navi_context_create(win); +} + +void setup_flat_navi3() +{ + setup(); + app = atspi_create_accessible(); + app->role = ATSPI_ROLE_APPLICATION; + + root = app; + win = atspi_create_accessible(); + win->role = ATSPI_ROLE_WINDOW; + + ly = atspi_create_accessible(); + ly->role = ATSPI_ROLE_FILLER; + + child1 = atspi_create_accessible(); + child2 = atspi_create_accessible(); + child3 = atspi_create_accessible(); + child4 = atspi_create_accessible(); + child5 = atspi_create_accessible(); + child6 = atspi_create_accessible(); + child7 = atspi_create_accessible(); + child8 = atspi_create_accessible(); + child9 = atspi_create_accessible(); + child10 = atspi_create_accessible(); + child11 = atspi_create_accessible(); + child12 = atspi_create_accessible(); + child13 = atspi_create_accessible(); + child14 = atspi_create_accessible(); + child15 = atspi_create_accessible(); + child16 = atspi_create_accessible(); + child17 = atspi_create_accessible(); + child18 = atspi_create_accessible(); + child19 = atspi_create_accessible(); + child20 = atspi_create_accessible(); + child21 = atspi_create_accessible(); + child22 = atspi_create_accessible(); + child23 = atspi_create_accessible(); + + child1->role = ATSPI_ROLE_FILLER; + child3->role = ATSPI_ROLE_FILLER; + child4->role = ATSPI_ROLE_FILLER; + child5->role = ATSPI_ROLE_FILLER; + child6->role = ATSPI_ROLE_FILLER; + child7->role = ATSPI_ROLE_FILLER; + child8->role = ATSPI_ROLE_FILLER; + child9->role = ATSPI_ROLE_FILLER; + child10->role = ATSPI_ROLE_FILLER; + child11->role = ATSPI_ROLE_FILLER; + child12->role = ATSPI_ROLE_FILLER; + child14->role = ATSPI_ROLE_FILLER; + child15->role = ATSPI_ROLE_FILLER; + child17->role = ATSPI_ROLE_FILLER; + child18->role = ATSPI_ROLE_FILLER; + child20->role = ATSPI_ROLE_FILLER; + child21->role = ATSPI_ROLE_FILLER; + child23->role = ATSPI_ROLE_FILLER; + + child2->role = ATSPI_ROLE_PUSH_BUTTON; + child2->name = "btn1"; + child13->role = ATSPI_ROLE_PUSH_BUTTON; + child13->name = "btn2"; + child16->role = ATSPI_ROLE_PUSH_BUTTON; + child16->name = "btn3"; + child19->role = ATSPI_ROLE_PUSH_BUTTON; + child19->name = "btn4"; + child22->role = ATSPI_ROLE_PUSH_BUTTON; + child22->name = "btn5"; + + atspi_accessible_add_child(app, win); + atspi_accessible_add_child(win, ly); + + atspi_accessible_add_child(ly, child1); + atspi_accessible_add_child(ly, child3); + atspi_accessible_add_child(ly, child4); + + atspi_accessible_add_child(child1, child2); + + atspi_accessible_add_child(child4, child5); + + atspi_accessible_add_child(child5, child6); + + atspi_accessible_add_child(child6, child7); + + atspi_accessible_add_child(child7, child8); + atspi_accessible_add_child(child7, child9); + atspi_accessible_add_child(child7, child10); + atspi_accessible_add_child(child7, child11); + + atspi_accessible_add_child(child8, child12); + atspi_accessible_add_child(child8, child13); + atspi_accessible_add_child(child8, child14); + + atspi_accessible_add_child(child9, child15); + atspi_accessible_add_child(child9, child16); + atspi_accessible_add_child(child9, child17); + + atspi_accessible_add_child(child10, child18); + atspi_accessible_add_child(child10, child19); + atspi_accessible_add_child(child10, child20); + + atspi_accessible_add_child(child11, child21); + atspi_accessible_add_child(child11, child22); + atspi_accessible_add_child(child11, child23); + + eina_init(); + atspi_alloc_memory(); + ctx = flat_navi_context_create(win); +} + +void teardown_flat_navi() +{ + flat_navi_context_free(ctx); + atspi_free_memory(); + eina_shutdown(); + atspi_delete_accessible(root); + atspi_delete_accessible(app); + 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; + event.type = "test_event"; + sd->tracking_signal_name = "test_event"; + event.detail1 = 1; + accessible.name = "test_name"; + accessible.role = ATSPI_ROLE_ICON; + accessible.description = NULL; + event.source = &accessible; + char *return_value = spi_event_get_text_to_read(&event, sd); + fail_if(!return_value || strcmp(return_value, "test_name, Icon")); + free(return_value); +} + +END_TEST START_TEST(spi_on_state_change_description) +{ + Service_Data *sd = get_pointer_to_service_data_struct(); + AtspiEvent event; + AtspiAccessible accessible; + event.type = "test_event"; + sd->tracking_signal_name = "test_event"; + event.detail1 = 1; + accessible.name = "test_name"; + accessible.description = "test description"; + accessible.role = ATSPI_ROLE_ICON; + event.source = &accessible; + char *return_value = spi_event_get_text_to_read(&event, sd); + fail_if(!return_value || strcmp(return_value, "test_name, Icon, test description")); + free(return_value); +} + +END_TEST START_TEST(spi_on_state_change_role) +{ + Service_Data *sd = get_pointer_to_service_data_struct(); + AtspiEvent event; + AtspiAccessible 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); + char *role_name = atspi_accessible_get_role_name(&accessible, NULL); + fail_if(!return_value || (role_name && strcmp(return_value, role_name))); + free(return_value); + free(role_name); +} + +END_TEST START_TEST(spi_on_caret_move) +{ + Service_Data *sd = get_pointer_to_service_data_struct(); + AtspiEvent event; + AtspiAccessible 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); +} + +END_TEST START_TEST(spi_on_value_changed) +{ + Service_Data *sd = get_pointer_to_service_data_struct(); + AtspiEvent event; + AtspiAccessible 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); +} + +END_TEST START_TEST(spi_flat_navi_context_create_null_parameter) +{ + FlatNaviContext *test_ctx = flat_navi_context_create(NULL); + fail_if(test_ctx); +} + +END_TEST START_TEST(spi_flat_navi_context_create_valid_parameter) +{ + FlatNaviContext *test_ctx = flat_navi_context_create(win); + 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 != child5); +} + +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 != child6); +} + +END_TEST START_TEST(spi_flat_navi_context_next_valid_parameter2) +{ + AtspiAccessible *next = flat_navi_context_next(ctx); + + fail_if(!next || next != child13); +} + +END_TEST START_TEST(spi_flat_navi_context_next_valid_parameter3) +{ + AtspiAccessible *next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + + fail_if(!next || next != child16); +} + +END_TEST START_TEST(spi_flat_navi_context_next_valid_parameter4) +{ + AtspiAccessible *next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + + fail_if(!next || next != child19); +} + +END_TEST START_TEST(spi_flat_navi_context_next_valid_parameter5) +{ + AtspiAccessible *next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + + fail_if(!next || next != child22); +} + +END_TEST START_TEST(spi_flat_navi_context_next_valid_parameter6) +{ + AtspiAccessible *next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + next = flat_navi_context_next(ctx); + 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 || prev != child6); +} + +END_TEST START_TEST(spi_flat_navi_context_prev_valid_parameter2) +{ + AtspiAccessible *prev = flat_navi_context_prev(ctx); + fail_if(!prev || prev != child22); +} + +END_TEST START_TEST(spi_flat_navi_context_prev_valid_parameter3) +{ + AtspiAccessible *prev = flat_navi_context_prev(ctx); + prev = flat_navi_context_prev(ctx); + fail_if(!prev || prev != child19); +} +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 != child6); +} +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 != child5); +} +END_TEST + +START_TEST(spi_flat_navi_context_current_set_null_parameters) +{ + Eina_Bool ret = flat_navi_context_current_set(NULL, NULL); + fail_if(ret != EINA_FALSE); + ret = flat_navi_context_current_set(ctx, NULL); + fail_if(ret != EINA_FALSE); + AtspiAccessible *last = flat_navi_context_last(ctx); + ret = flat_navi_context_current_set(NULL, last); + fail_if(ret != EINA_FALSE); +} +END_TEST + +START_TEST(spi_flat_navi_context_current_set_valid_parameters) +{ + AtspiAccessible *last = flat_navi_context_last(ctx); + Eina_Bool ret = flat_navi_context_current_set(ctx, last); + AtspiAccessible *current = flat_navi_context_current_get(ctx); + fail_if(ret != EINA_TRUE || current != last); +} +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_flat_navi; + TCase *tc_spi_screen_reader_flat_navi2; + + 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_flat_navi = tcase_create("tc_scpi_screen_reader_flat_navi"); + tc_spi_screen_reader_flat_navi2 = tcase_create("tc_scpi_screen_reader_flat_navi2"); + + 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_flat_navi, setup_flat_navi2, teardown_flat_navi); + tcase_add_checked_fixture(tc_spi_screen_reader_flat_navi2, setup_flat_navi3, 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_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_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_navi2, spi_flat_navi_context_next_valid_parameter2); + tcase_add_test(tc_spi_screen_reader_flat_navi2, spi_flat_navi_context_next_valid_parameter3); + tcase_add_test(tc_spi_screen_reader_flat_navi2, spi_flat_navi_context_next_valid_parameter4); + tcase_add_test(tc_spi_screen_reader_flat_navi2, spi_flat_navi_context_next_valid_parameter5); + tcase_add_test(tc_spi_screen_reader_flat_navi2, spi_flat_navi_context_next_valid_parameter6); + + 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_navi2, spi_flat_navi_context_prev_valid_parameter2); + tcase_add_test(tc_spi_screen_reader_flat_navi2, spi_flat_navi_context_prev_valid_parameter3); + + 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); + + 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); + suite_add_tcase(s, tc_spi_screen_reader_flat_navi2); + + return s; +} + +int main() +{ + int number_failed; + Suite *s; + SRunner *sr; + + s = screen_reader_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_VERBOSE); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} -- 2.7.4