diff --git a/.github/workflows/build-cross-compile.yml b/.github/workflows/build-cross-compile.yml index 1c1aee9061f0..1491ca15dc70 100644 --- a/.github/workflows/build-cross-compile.yml +++ b/.github/workflows/build-cross-compile.yml @@ -113,7 +113,7 @@ jobs: g++-${{ inputs.gcc-major-version }} \ gcc-${{ inputs.gcc-major-version }}-${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}} \ g++-${{ inputs.gcc-major-version }}-${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}} \ - libxrandr-dev libxtst-dev libcups2-dev libasound2-dev + libxrandr-dev libxtst-dev libcups2-dev libasound2-dev libatspi2.0-dev libatk1.0-dev libglib2.0-dev libatk-bridge2.0-dev sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ inputs.gcc-major-version }} 100 --slave /usr/bin/g++ g++ /usr/bin/g++-${{ inputs.gcc-major-version }} - name: 'Check cache for sysroot' @@ -133,7 +133,7 @@ jobs: sudo debootstrap --arch=${{ matrix.debian-arch }} --verbose - --include=fakeroot,symlinks,build-essential,libx11-dev,libxext-dev,libxrender-dev,libxrandr-dev,libxtst-dev,libxt-dev,libcups2-dev,libfontconfig1-dev,libasound2-dev,libfreetype-dev,libpng-dev + --include=fakeroot,symlinks,build-essential,libx11-dev,libxext-dev,libxrender-dev,libxrandr-dev,libxtst-dev,libxt-dev,libcups2-dev,libfontconfig1-dev,libasound2-dev,libfreetype-dev,libpng-dev,libatspi2.0-dev,libatk1.0-dev,libglib2.0-dev,libatk-bridge2.0-dev --resolve-deps --variant=minbase ${{ matrix.debian-version }} diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index f3ea4e4fb6ad..e6914f4acbd2 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -111,7 +111,7 @@ jobs: fi sudo apt-get update sudo apt-get install --only-upgrade apt - sudo apt-get install gcc-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} g++-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} libxrandr-dev${{ steps.arch.outputs.suffix }} libxtst-dev${{ steps.arch.outputs.suffix }} libcups2-dev${{ steps.arch.outputs.suffix }} libasound2-dev${{ steps.arch.outputs.suffix }} ${{ inputs.apt-extra-packages }} + sudo apt-get install gcc-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} g++-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} libxrandr-dev${{ steps.arch.outputs.suffix }} libxtst-dev${{ steps.arch.outputs.suffix }} libcups2-dev${{ steps.arch.outputs.suffix }} libasound2-dev${{ steps.arch.outputs.suffix }} libatspi2.0-dev${{ steps.arch.outputs.suffix }} libatk1.0-dev${{ steps.arch.outputs.suffix }} libglib2.0-dev${{ steps.arch.outputs.suffix }} ${{ inputs.apt-extra-packages }} sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ inputs.gcc-major-version }} 100 --slave /usr/bin/g++ g++ /usr/bin/g++-${{ inputs.gcc-major-version }} - name: 'Configure' diff --git a/README.md b/README.md index 02c8523fc3e0..4dd352ab6543 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,15 @@ $ make images ``` This will build the release configuration under `./build/linux-x86_64-server-release/`. +#### Java ATK Wrapper + +To enable [Java ATK Wrapper](https://wiki.gnome.org/Accessibility/JavaAtkWrapper): +1. Install the necessary libraries and headers with: +``` +$ sudo apt-get install libatspi2.0-dev libatk1.0-dev libglib2.0-dev libatk-bridge2.0-dev +``` +2. Run `configure` using `--enable-java-atk-wrapper` + ### Windows Install the following: diff --git a/make/autoconf/help.m4 b/make/autoconf/help.m4 index 4d1b3c37bbb0..81f5d9cc2444 100644 --- a/make/autoconf/help.m4 +++ b/make/autoconf/help.m4 @@ -119,6 +119,8 @@ apt_help() { PKGHANDLER_COMMAND="sudo apt-get install systemtap-sdt-dev" ;; capstone) PKGHANDLER_COMMAND="sudo apt-get install libcapstone-dev" ;; + atk) + PKGHANDLER_COMMAND="sudo apt-get install libatspi2.0-dev libatk1.0-dev libglib2.0-dev" ;; esac } diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index 946af99a9b71..b0cbd914e0b7 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -270,6 +270,28 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JDK_OPTIONS], A11Y_JAWS_ANNOUNCING_ENABLED=false fi AC_SUBST(A11Y_JAWS_ANNOUNCING_ENABLED) + + # Should we build support for Java ATK Wrapper? + if test "x$OPENJDK_TARGET_OS" = xlinux; then + AC_MSG_CHECKING([if Java ATK Wrapper support is enabled]) + + A11Y_JAVA_ATK_WRAPPER_ENABLED=false + AC_ARG_ENABLE( + [java-atk-wrapper], + [AS_HELP_STRING([--enable-java-atk-wrapper], [Set to enable the Java ATK Wrapper])], + [ + if test "x$ENABLE_HEADLESS_ONLY" = xtrue; then + AC_MSG_WARN([--[enable|disable]-java-atk-wrapper[=*] flags are ignored for headless builds]) + elif test "x$enableval" == xyes; then + A11Y_JAVA_ATK_WRAPPER_ENABLED=true + fi + ] + ) + AC_MSG_RESULT([$A11Y_JAVA_ATK_WRAPPER_ENABLED]) + else + A11Y_JAVA_ATK_WRAPPER_ENABLED=false + fi + AC_SUBST(A11Y_JAVA_ATK_WRAPPER_ENABLED) ]) ############################################################################### diff --git a/make/autoconf/lib-at-spi2-atk.m4 b/make/autoconf/lib-at-spi2-atk.m4 new file mode 100644 index 000000000000..feb236f54318 --- /dev/null +++ b/make/autoconf/lib-at-spi2-atk.m4 @@ -0,0 +1,70 @@ +################################################################################ +# Setup at-spi2-atk +################################################################################ +AC_DEFUN_ONCE([LIB_SETUP_AT_SPI2_ATK], +[ + AC_ARG_WITH(at-spi2-atk, [AS_HELP_STRING([--with-at-spi2-atk], + [specify prefix directory for the at-spi2-atk package + (expecting the headers under PATH/include); required for atk-wrapper to work])]) + AC_ARG_WITH(at-spi2-atk-include, [AS_HELP_STRING([--with-at-spi2-atk-include], + [specify directory for the at-spi2-atk include files])]) + AC_ARG_WITH(at-spi2-atk-version, [AS_HELP_STRING([--with-at-spi2-atk-version], + [specify version for the at-spi2-atk package])]) + + if test "x$NEEDS_LIB_AT_SPI2_ATK" = xfalse || test "x${with_at_spi2_atk}" = xno || \ + test "x${with_at_spi2_atk_include}" = xno; then + if (test "x${with_at_spi2_atk}" != x && test "x${with_at_spi2_atk}" != xno) || \ + (test "x${with_at_spi2_atk_include}" != x && test "x${with_at_spi2_atk_include}" != xno); then + AC_MSG_WARN([[at-spi2-atk not used, so --with-at-spi2-atk[-*] is ignored]]) + fi + AT_SPI2_ATK_CFLAGS= + AT_SPI2_ATK_LIBS= + else + AT_SPI2_ATK_FOUND=no + if test "x${with_at_spi2_atk}" != x && test "x${with_at_spi2_atk}" != xyes; then + AC_MSG_CHECKING([for at-spi2-atk header and library]) + if test "x${with_at_spi2_atk_version}" != x && "x${with_at_spi2_atk_version}" != xyes; then + AT_SPI2_ATK_VERSION="${with_at_spi2_atk_version}" + else + AC_MSG_ERROR([Define at-spi2-atk package version using --with-at-spi2-atk-version if you use --with-at-spi2-atk option.]) + fi + if test -s "${with_at_spi2_atk}/include/at-spi2-atk/2.0/atk-bridge.h"; then + AT_SPI2_ATK_CFLAGS="-I${with_at_spi2_atk}/include/at-spi2-atk/2.0" + AT_SPI2_ATK_LIBS="-L${with_at_spi2_atk}/lib -latk-bridge-2.0" + AT_SPI2_ATK_FOUND=yes + AC_MSG_RESULT([$AT_SPI2_ATK_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/at-spi2-atk/2.0/atk-bridge.h' under ${with_at_spi2_atk} given with the --with-at-spi2-atk option.]) + fi + fi + if test "x${with_at_spi2_atk_include}" != x; then + AC_MSG_CHECKING([for at-spi2-atk headers]) + if test "x${with_at_spi2_atk_version}" != x && "x${with_at_spi2_atk_version}" != xyes; then + AT_SPI2_ATK_VERSION="${with_at_spi2_atk_version}" + else + AC_MSG_ERROR([Define at-spi2-atk package version using --with-at-spi2-atk-version if you use --with-at-spi2-atk-include option.]) + fi + if test -s "${with_at_spi2_atk_include}/at-spi2-atk/2.0/atk-bridge.h"; then + AT_SPI2_ATK_CFLAGS="-I${with_at_spi2_atk_include}/at-spi2-atk/2.0" + AT_SPI2_ATK_FOUND=yes + AC_MSG_RESULT([$AT_SPI2_ATK_FOUND]) + else + AC_MSG_ERROR([Can't find 'include/at-spi2-atk-2.0/atk-bridge.h' under ${with_at_spi2_atk_include} given with the --with-at-spi2-atk-include option.]) + fi + fi + if test "x$AT_SPI2_ATK_FOUND" = xno; then + # Are the at-spi2-atk headers installed in the default /usr/include location? + PKG_CHECK_MODULES([AT_SPI2_ATK], [atk-bridge-2.0], + [AT_SPI2_ATK_FOUND=yes; AT_SPI2_ATK_VERSION=$("$PKG_CONFIG" --modversion atk-bridge-2.0)], + [AT_SPI2_ATK_FOUND=no; break] + ) + fi + if test "x$AT_SPI2_ATK_FOUND" = xno; then + HELP_MSG_MISSING_DEPENDENCY([at-spi2-atk]) + AC_MSG_ERROR([Could not find at-spi2-atk! $HELP_MSG ]) + fi + fi + AT_SPI2_ATK_CFLAGS="$AT_SPI2_ATK_CFLAGS -DATSPI_MAJOR_VERSION=$(echo $AT_SPI2_ATK_VERSION | cut -d. -f1) -DATSPI_MINOR_VERSION=$(echo $AT_SPI2_ATK_VERSION | cut -d. -f2) -DATSPI_MICRO_VERSION=$(echo $AT_SPI2_ATK_VERSION | cut -d. -f3)" + AC_SUBST(AT_SPI2_ATK_CFLAGS) + AC_SUBST(AT_SPI2_ATK_LIBS) +]) \ No newline at end of file diff --git a/make/autoconf/lib-atk.m4 b/make/autoconf/lib-atk.m4 new file mode 100644 index 000000000000..b7e859be6c2e --- /dev/null +++ b/make/autoconf/lib-atk.m4 @@ -0,0 +1,53 @@ +################################################################################ +# Setup atk +################################################################################ +AC_DEFUN_ONCE([LIB_SETUP_ATK], +[ + AC_ARG_WITH(atk, [AS_HELP_STRING([--with-atk], + [specify prefix directory for the atk package + (expecting the headers under PATH/include); required for atk-wrapper to work])]) + AC_ARG_WITH(atk-include, [AS_HELP_STRING([--with-atk-include], + [specify directory for the atk include files])]) + if test "x$NEEDS_LIB_ATK" = xfalse || test "x${with_atk}" = xno || \ + test "x${with_atk_include}" = xno; then + if (test "x${with_atk}" != x && test "x${with_atk}" != xno) || \ + (test "x${with_atk_include}" != x && test "x${with_atk_include}" != xno); then + AC_MSG_WARN([[atk not used, so --with-atk[-*] is ignored]]) + fi + ATK_CFLAGS= + ATK_LIBS= + else + ATK_FOUND=no + if test "x${with_atk}" != x && test "x${with_atk}" != xyes; then + AC_MSG_CHECKING([for atk header and library]) + if test -s "${with_atk}/include/atk-1.0/atk/atk.h"; then + ATK_CFLAGS="-I${with_atk}/include/atk-1.0" + ATK_LIBS="-L${with_atk}/lib -latk-1.0" + ATK_FOUND=yes + AC_MSG_RESULT([$ATK_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/atk-1.0/atk/atk.h' under ${with_atk} given with the --with-atk option.]) + fi + fi + if test "x${with_atk_include}" != x; then + AC_MSG_CHECKING([for atk headers]) + if test -s "${with_atk_include}/atk-1.0/atk/atk.h"; then + ATK_CFLAGS="-I${with_atk_include}/atk-1.0" + ATK_FOUND=yes + AC_MSG_RESULT([$ATK_FOUND]) + else + AC_MSG_ERROR([Can't find 'include/atk-1.0/atk/atk.h' under ${with_atk_include} given with the --with-atk-include option.]) + fi + fi + if test "x$ATK_FOUND" = xno; then + # Are the atk headers installed in the default /usr/include location? + PKG_CHECK_MODULES([ATK], [atk], [ATK_FOUND=yes;], [ATK_FOUND=no; break]) + fi + if test "x$ATK_FOUND" = xno; then + HELP_MSG_MISSING_DEPENDENCY([atk]) + AC_MSG_ERROR([Could not find atk! $HELP_MSG ]) + fi + fi + AC_SUBST(ATK_CFLAGS) + AC_SUBST(ATK_LIBS) +]) \ No newline at end of file diff --git a/make/autoconf/lib-glib.m4 b/make/autoconf/lib-glib.m4 new file mode 100644 index 000000000000..3ff378d85958 --- /dev/null +++ b/make/autoconf/lib-glib.m4 @@ -0,0 +1,86 @@ +################################################################################ +# Setup glib +################################################################################ +AC_DEFUN_ONCE([LIB_SETUP_GLIB], +[ + AC_ARG_WITH(glib, [AS_HELP_STRING([--with-glib], + [specify prefix directory for the glib package + (expecting the headers under PATH/include); required for atk-wrapper to work])]) + AC_ARG_WITH(glib-include, [AS_HELP_STRING([--with-glib-include], + [specify directory for the glib include files])]) + AC_ARG_WITH(glibconfig, [AS_HELP_STRING([--with-glibconfig], + [specify prefix directory for the glibconfig package + (expecting the headers under PATH/include); required for atk-wrapper to work])]) + AC_ARG_WITH(glibconfig-include, [AS_HELP_STRING([--with-glibconfig-include], + [specify directory for the glibconfig include files])]) + + if test "x$NEEDS_LIB_GLIB" = xfalse || test "x${with_glib}" = xno || \ + test "x${with_glib_include}" = xno || \ + test "x${with_glibconfig}" = xno || test "x${with_glibconfig_include}" = xno; then + if (test "x${with_glib}" != x && test "x${with_glib}" != xno) || \ + (test "x${with_glib_include}" != x && test "x${with_glib_include}" != xno) || \ + (test "x${with_glibconfig}" != x && test "x${with_glibconfig}" != xno) || \ + (test "x${with_glibconfig_include}" != x && test "x${with_glibconfig_include}" != xno); then + AC_MSG_WARN([[glib not used, so --with-glib[-*] and --with-glibconfig[-*] are ignored]]) + fi + GLIB_CFLAGS= + GLIB_LIBS= + GLIBCONFIG_CFLAGS= + else + GLIB_FOUND=no + GLIBCONFIG_FOUND=no + GLIBCONFIG_CFLAGS= + if test "x${with_glib}" != x && test "x${with_glib}" != xyes; then + AC_MSG_CHECKING([for glib header and library]) + if test -s "${with_glib}/include/glib-2.0/glib.h"; then + GLIB_CFLAGS="-I${with_glib}/include/glib-2.0" + GLIB_LIBS="-L${with_glib}/lib -lglib-2.0" + GLIB_FOUND=yes + AC_MSG_RESULT([$GLIB_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/glib-2.0/glib.h' under ${with_glib} given with the --with-glib option.]) + fi + fi + if test "x${with_glib_include}" != x; then + AC_MSG_CHECKING([for glib headers]) + if test -s "${with_glib_include}/glib-2.0/glib.h"; then + GLIB_CFLAGS="-I${with_glib_include}" + GLIB_FOUND=yes + AC_MSG_RESULT([$GLIB_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/glib-2.0/glib.h' under ${with_glib_include} given with the --with-glib-include option.]) + fi + fi + if test "x${with_glibconfig}" != x && test "x${with_glibconfig}" != xyes; then + AC_MSG_CHECKING([for glibconfig header]) + if test -s "${with_glibconfig}/include/glibconfig.h"; then + GLIBCONFIG_CFLAGS="-I${with_glibconfig}/include" + GLIBCONFIG_FOUND=yes + AC_MSG_RESULT([$GLIBCONFIG_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/glibconfig.h' under ${with_glibconfig} given with the --with-glibconfig option.]) + fi + fi + if test "x${with_glibconfig_include}" != x; then + AC_MSG_CHECKING([for glibconfig headers]) + if test -s "${with_glibconfig_include}/glibconfig.h"; then + GLIBCONFIG_CFLAGS="-I${with_glibconfig_include}" + GLIBCONFIG_FOUND=yes + AC_MSG_RESULT([$GLIBCONFIG_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/glibconfig.h' under ${with_glibconfig_include} given with the --with-glibconfig-include option.]) + fi + fi + if test "x$GLIB_FOUND" = xno || test "x$GLIBCONFIG_FOUND" = xno; then + # Are the glib headers installed in the default /usr/include location? + PKG_CHECK_MODULES([GLIB], [glib-2.0], [GLIB_FOUND=yes; GLIBCONFIG_FOUND=yes], [GLIB_FOUND=no; GLIBCONFIG_FOUND=no; break]) + fi + if test "x$GLIB_FOUND" = xno || test "x$GLIBCONFIG_FOUND" = xno; then + HELP_MSG_MISSING_DEPENDENCY([glib]) + AC_MSG_ERROR([Could not find glib! $HELP_MSG]) + fi + fi + GLIB_CFLAGS="$GLIB_CFLAGS $GLIBCONFIG_CFLAGS" + AC_SUBST(GLIB_CFLAGS) + AC_SUBST(GLIB_LIBS) +]) \ No newline at end of file diff --git a/make/autoconf/lib-gobject.m4 b/make/autoconf/lib-gobject.m4 new file mode 100644 index 000000000000..fe4fd60b309e --- /dev/null +++ b/make/autoconf/lib-gobject.m4 @@ -0,0 +1,58 @@ +################################################################################ +# Setup gobject +################################################################################ +AC_DEFUN_ONCE([LIB_SETUP_GOBJECT], +[ + AC_ARG_WITH(gobject, [AS_HELP_STRING([--with-gobject], + [specify prefix directory for the gobject package + (expecting the headers under PATH/include); required for atk-wrapper to work])]) + AC_ARG_WITH(gobject-include, [AS_HELP_STRING([--with-gobject-include], + [specify directory for the gobject include files])]) + + if test "x$NEEDS_LIB_GOBJECT" = xfalse || test "x${with_gobject}" = xno || \ + test "x${with_gobject_include}" = xno; then + if (test "x${with_gobject}" != x && test "x${with_gobject}" != xno) || \ + (test "x${with_gobject_include}" != x && test "x${with_gobject_include}" != xno); then + AC_MSG_WARN([[gobject not used, so --with-gobject[-*] is ignored]]) + fi + GOBJECT_CFLAGS= + GOBJECT_LIBS= + else + GOBJECT_FOUND=no + + if test "x${with_gobject}" != x && test "x${with_gobject}" != xyes; then + AC_MSG_CHECKING([for gobject header and library]) + if test -s "${with_gobject}/include/glib-2.0/gobject/gobject.h"; then + GOBJECT_CFLAGS="-I${with_gobject}/include/glib-2.0/gobject" + GOBJECT_LIBS="-L${with_gobject}/lib -lgobject-2.0" + GOBJECT_FOUND=yes + AC_MSG_RESULT([$GOBJECT_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/glib-2.0/gobject/gobject.h' under ${with_gobject} given with the --with-gobject option.]) + fi + fi + if test "x${with_gobject_include}" != x; then + AC_MSG_CHECKING([for gobject headers]) + if test -s "${with_gobject_include}/glib-2.0/gobject/gobject.h"; then + GOBJECT_CFLAGS="-I${with_gobject_include}/glib-2.0/gobject" + GOBJECT_FOUND=yes + AC_MSG_RESULT([$GOBJECT_FOUND]) + else + AC_MSG_ERROR([Can't find '/include/glib-2.0/gobject/gobject.h' under ${with_gobject_include} given with the --with-gobject-include option.]) + fi + fi + if test "x$GOBJECT_FOUND" = xno; then + # Are the gobject headers installed in the default /usr/include location? + PKG_CHECK_MODULES([GOBJECT], [gobject-2.0], + [ GOBJECT_FOUND=yes;], + [ GOBJECT_FOUND=no; break ] + ) + fi + if test "x$GOBJECT_FOUND" = xno; then + HELP_MSG_MISSING_DEPENDENCY([gobject]) + AC_MSG_ERROR([Could not find gobject! $HELP_MSG ]) + fi + fi + AC_SUBST(GOBJECT_CFLAGS) + AC_SUBST(GOBJECT_LIBS) +]) \ No newline at end of file diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index eea477f2d423..1de9b1734c1e 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -39,6 +39,10 @@ m4_include([lib-nvdacontrollerclient.m4]) m4_include([lib-wayland.m4]) m4_include([lib-dbus.m4]) m4_include([lib-tests.m4]) +m4_include([lib-atk.m4]) +m4_include([lib-at-spi2-atk.m4]) +m4_include([lib-glib.m4]) +m4_include([lib-gobject.m4]) ################################################################################ # Determine which libraries are needed for this configuration @@ -112,6 +116,19 @@ AC_DEFUN_ONCE([LIB_DETERMINE_DEPENDENCIES], else NEEDS_LIB_NVDACONTROLLERCLIENT=false fi + + # Check if atk, at-spi2-atk, glib, gobject is needed + if test "x$A11Y_JAVA_ATK_WRAPPER_ENABLED" = xtrue; then + NEEDS_LIB_ATK=true + NEEDS_LIB_AT_SPI2_ATK=true + NEEDS_LIB_GLIB=true + NEEDS_LIB_GOBJECT=true + else + NEEDS_LIB_ATK=false + NEEDS_LIB_AT_SPI2_ATK=false + NEEDS_LIB_GLIB=false + NEEDS_LIB_GOBJECT=false + fi ]) ################################################################################ @@ -185,6 +202,12 @@ AC_DEFUN_ONCE([LIB_SETUP_LIBRARIES], BASIC_JVM_LIBS="$BASIC_JVM_LIBS -lrt" fi + # libraries for Java ATK Wrapper + LIB_SETUP_AT_SPI2_ATK + LIB_SETUP_ATK + LIB_SETUP_GLIB + LIB_SETUP_GOBJECT + # perfstat lib if test "x$OPENJDK_TARGET_OS" = xaix; then BASIC_JVM_LIBS="$BASIC_JVM_LIBS -lperfstat" diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in index ff26cdaa98a6..992759b60e5c 100644 --- a/make/autoconf/spec.gmk.in +++ b/make/autoconf/spec.gmk.in @@ -485,6 +485,17 @@ NVDACONTROLLERCLIENT_LIB:=@NVDACONTROLLERCLIENT_LIB@ # Windows the client for the JAWS screen reader A11Y_JAWS_ANNOUNCING_ENABLED:=@A11Y_JAWS_ANNOUNCING_ENABLED@ +# Linux java-atk-wrapper library +A11Y_JAVA_ATK_WRAPPER_ENABLED:=@A11Y_JAVA_ATK_WRAPPER_ENABLED@ +AT_SPI2_ATK_CFLAGS:=@AT_SPI2_ATK_CFLAGS@ +AT_SPI2_ATK_LIBS:=@AT_SPI2_ATK_LIBS@ +ATK_CFLAGS:=@ATK_CFLAGS@ +ATK_LIBS:=@ATK_LIBS@ +GLIB_CFLAGS:=@GLIB_CFLAGS@ +GLIB_LIBS:=@GLIB_LIBS@ +GOBJECT_CFLAGS:=@GOBJECT_CFLAGS@ +GOBJECT_LIBS:=@GOBJECT_LIBS@ + WAYLAND_CFLAGS:=@WAYLAND_CFLAGS@ WAYLAND_LIBS:=@WAYLAND_LIBS@ VULKAN_FLAGS:=@VULKAN_FLAGS@ diff --git a/make/modules/java.desktop/lib/Awt2dLibraries.gmk b/make/modules/java.desktop/lib/Awt2dLibraries.gmk index 6f925f3d9027..bd0ac88e1581 100644 --- a/make/modules/java.desktop/lib/Awt2dLibraries.gmk +++ b/make/modules/java.desktop/lib/Awt2dLibraries.gmk @@ -382,6 +382,13 @@ ifeq ($(call isTargetOs, windows macosx), false) common/wayland \ # + # Setup speech dispatcher for Orca announcer + ifeq ($(A11Y_SPEECHD_ANNOUNCING_ENABLED), true) + A11Y_SPEECHD_ANNOUNCING_CFLAGS := + else + A11Y_SPEECHD_ANNOUNCING_CFLAGS := -DNO_A11Y_SPEECHD_ANNOUNCING + endif + LIBAWT_WLAWT_EXCLUDES := medialib debug opengl x11 LIBAWT_WLAWT_EXCLUDE_FILES := common/awt/X11Color.c common/awt/awt_Font.c @@ -412,11 +419,13 @@ ifeq ($(call isTargetOs, windows macosx), false) $(WAKEFIELD_ROBOT_CFLAGS) \ $(FONTCONFIG_CFLAGS) \ $(VULKAN_FLAGS) \ - $(CUPS_CFLAGS) + $(CUPS_CFLAGS) \ + $(SPEECHD_CFLAGS) \ + $(A11Y_SPEECHD_ANNOUNCING_CFLAGS) LIBAWT_WLAWT_CXXFLAGS += $(VULKAN_FLAGS) - LIBAWT_WLAWT_LIBS := $(LIBM) -lawt $(WAYLAND_LIBS) $(LIBDL) -ljava -ljvm -lrt + LIBAWT_WLAWT_LIBS := $(LIBM) -lawt $(WAYLAND_LIBS) $(SPEECHD_LIBS) $(LIBDL) -ljava -ljvm -lrt ifeq ($(call isTargetOs, linux), true) LIBAWT_WLAWT_LIBS += -lpthread diff --git a/make/modules/jdk.accessibility/Copy.gmk b/make/modules/jdk.accessibility/Copy.gmk index cd8aeba5a11d..a26a1f6c75ee 100644 --- a/make/modules/jdk.accessibility/Copy.gmk +++ b/make/modules/jdk.accessibility/Copy.gmk @@ -27,3 +27,17 @@ # Include CopyCommon.gmk to get exported header files to be properly copied. include CopyCommon.gmk + +################################################################################ + +ifeq ($(call isTargetOs, linux), true) + ifeq ($(A11Y_JAVA_ATK_WRAPPER_ENABLED), true) + $(eval $(call SetupCopyFiles, COPY_ACCESSIBILITY_CONF, \ + DEST := $(CONF_DST_DIR), \ + FILES := $(TOPDIR)/src/jdk.accessibility/linux/conf/accessibility.properties, \ + )) + TARGETS := $(COPY_ACCESSIBILITY_CONF) + endif +endif + +################################################################################ \ No newline at end of file diff --git a/make/modules/jdk.accessibility/Lib.gmk b/make/modules/jdk.accessibility/Lib.gmk index 28666b973257..31dc89c1a944 100644 --- a/make/modules/jdk.accessibility/Lib.gmk +++ b/make/modules/jdk.accessibility/Lib.gmk @@ -112,3 +112,25 @@ ifeq ($(call isTargetOs, windows), true) endif ################################################################################ + +ifeq ($(call isTargetOs, linux), true) + ifeq ($(ENABLE_HEADLESS_ONLY), false) + ifeq ($(A11Y_JAVA_ATK_WRAPPER_ENABLED), true) + $(eval $(call SetupJdkLibrary, BUILD_LIBATKWRAPPER, \ + NAME := atk-wrapper, \ + OPTIMIZATION := LOW, \ + CFLAGS := $(CFLAGS_JDKLIB) \ + $(ATK_CFLAGS) \ + $(AT_SPI2_ATK_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GOBJECT_CFLAGS), \ + LDFLAGS := $(LDFLAGS_JDKLIB), \ + LIBS_linux := -ljvm $(ATK_LIBS) $(AT_SPI2_ATK_LIBS) $(GLIB_LIBS) $(GOBJECT_LIBS), \ + )) + $(BUILD_LIBATKWRAPPER): $(call FindLib, java.base, java) + TARGETS += $(BUILD_LIBATKWRAPPER) + endif + endif +endif + +################################################################################ \ No newline at end of file diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index a93a8835619c..8fe33e89f594 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -228,7 +228,8 @@ jdk.jshell, jdk.nio.mapmode, jdk.unsupported, - jdk.internal.vm.ci; + jdk.internal.vm.ci, + jdk.accessibility; exports jdk.internal.module to java.instrument, java.management.rmi, @@ -384,7 +385,8 @@ exports sun.util.logging to java.desktop, java.logging, - java.prefs; + java.prefs, + jdk.accessibility; exports sun.util.resources to jdk.localedata; diff --git a/src/java.desktop/share/classes/java/awt/event/KeyEvent.java b/src/java.desktop/share/classes/java/awt/event/KeyEvent.java index d83d4fef184e..bee48bd51330 100644 --- a/src/java.desktop/share/classes/java/awt/event/KeyEvent.java +++ b/src/java.desktop/share/classes/java/awt/event/KeyEvent.java @@ -1206,6 +1206,10 @@ public void setRawCode(KeyEvent ev, long rawCode) { ev.rawCode = rawCode; } + public long getRawCode(KeyEvent ev) { + return ev.rawCode; + } + public void setPrimaryLevelUnicode(KeyEvent ev, long primaryLevelUnicode) { ev.primaryLevelUnicode = primaryLevelUnicode; diff --git a/src/java.desktop/share/classes/sun/awt/AWTAccessor.java b/src/java.desktop/share/classes/sun/awt/AWTAccessor.java index 9b1cb6c682df..59e652297da9 100644 --- a/src/java.desktop/share/classes/sun/awt/AWTAccessor.java +++ b/src/java.desktop/share/classes/sun/awt/AWTAccessor.java @@ -727,6 +727,11 @@ public interface KeyEventAccessor { */ void setRawCode(KeyEvent ev, long rawCode); + /** + * Gets rawCode field for KeyEvent + */ + long getRawCode(KeyEvent ev); + /** * Sets primaryLevelUnicode field for KeyEvent */ diff --git a/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/AccessibleAnnouncer.c b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/AccessibleAnnouncer.c new file mode 100644 index 000000000000..eacb036f5971 --- /dev/null +++ b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/AccessibleAnnouncer.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "sun_swing_AccessibleAnnouncer.h" +#include "OrcaAnnouncer.h" + +/* + * Class: sun_swing_AccessibleAnnouncer + * Method: nativeAnnounce + * Signature: (Ljavax/accessibility/Accessible;Ljava/lang/String;I)V + */ +JNIEXPORT void JNICALL Java_sun_swing_AccessibleAnnouncer_nativeAnnounce +(JNIEnv *env, jclass cls, jobject accessible, jstring str, jint priority) +{ +#ifndef NO_A11Y_SPEECHD_ANNOUNCING + if (OrcaAnnounce(env, str, priority) == 0) + { + return; + } +#endif + +#ifdef DEBUG + fprintf(stderr, "Each announcer has failed or the build was made without any of them\n"); +#endif +} diff --git a/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/AccessibleAnnouncerJNIUtils.h b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/AccessibleAnnouncerJNIUtils.h new file mode 100644 index 000000000000..1c3861cf7d10 --- /dev/null +++ b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/AccessibleAnnouncerJNIUtils.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ACCESSIBLEANNOUNCERJNIUTILS_H +#define ACCESSIBLEANNOUNCERJNIUTILS_H + +#ifndef NO_A11Y_SPEECHD_ANNOUNCING + +#include "jni.h" + +#define GET_AccessibleAnnouncerUtilities()\ +if (jc_AccessibleAnnouncerUtilities == NULL) {\ +jclass cls = (*env)->FindClass(env, "sun/awt/AccessibleAnnouncerUtilities");\ +if (cls == NULL) {\ +return;\ +}\ +jc_AccessibleAnnouncerUtilities = (*env)->NewGlobalRef(env, cls);\ +(*env)->DeleteLocalRef(env, cls);\ +}\ + +#define GET_AccessibleAnnouncerUtilitiesReturn(ret)\ +if (jc_AccessibleAnnouncerUtilities == NULL) {\ +jclass cls = (*env)->FindClass(env, "sun/awt/AccessibleAnnouncerUtilities");\ +if (cls == NULL) {\ +return ret;\ +}\ +jc_AccessibleAnnouncerUtilities = (*env)->NewGlobalRef(env, cls);\ +(*env)->DeleteLocalRef(env, cls);\ +}\ + +#define GET_getOrcaConf()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getOrcaConf == NULL) {\ +jsm_getOrcaConf = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getOrcaConf", "()Ljava/lang/Object;");\ +if (jsm_getOrcaConf == NULL) {\ +return;\ +}\ +}\ + +#define GET_getOrcaConfReturn(ret)\ +GET_AccessibleAnnouncerUtilitiesReturn(ret);\ +if (jsm_getOrcaConf == NULL) {\ +jsm_getOrcaConf = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getOrcaConf", "()Ljava/lang/Object;");\ +if (jsm_getOrcaConf == NULL) {\ +return ret;\ +}\ +}\ + +#define GET_getSpeechServerInfo()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getSpeechServerInfo == NULL) {\ +jsm_getSpeechServerInfo = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getSpeechServerInfo", "(Ljava/lang/Object;)Ljava/lang/String;");\ +if (jsm_getSpeechServerInfo == NULL) {\ +return;\ +}\ +}\ + +#define GET_getGain()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getGain == NULL) {\ +jsm_getGain = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getGain", "(Ljava/lang/Object;)D");\ +if (jsm_getGain == NULL) {\ +return;\ +}\ +}\ + +#define GET_getVariant()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getVariant == NULL) {\ +jsm_getVariant = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getVariant", "(Ljava/lang/Object;)Ljava/lang/String;");\ +if (jsm_getVariant == NULL) {\ +return;\ +}\ +}\ + +#define GET_getDialect()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getDialect == NULL) {\ +jsm_getDialect = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getDialect", "(Ljava/lang/Object;)Ljava/lang/String;");\ +if (jsm_getDialect == NULL) {\ +return;\ +}\ +}\ + +#define GET_getLang()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getLang == NULL) {\ +jsm_getLang = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getLang", "(Ljava/lang/Object;)Ljava/lang/String;");\ +if (jsm_getLang == NULL) {\ +return;\ +}\ +}\ + +#define GET_getName()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getName == NULL) {\ +jsm_getName = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getName", "(Ljava/lang/Object;)Ljava/lang/String;");\ +if (jsm_getName == NULL) {\ +return;\ +}\ +}\ + +#define GET_getAveragePitch()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getAveragePitch == NULL) {\ +jsm_getAveragePitch = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getAveragePitch", "(Ljava/lang/Object;)D");\ +if (jsm_getAveragePitch == NULL) {\ +return;\ +}\ +}\ + +#define GET_getRate()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getRate == NULL) {\ +jsm_getRate = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getRate", "(Ljava/lang/Object;)D");\ +if (jsm_getRate == NULL) {\ +return;\ +}\ +}\ + +#define GET_getActiveProfile()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getActiveProfile == NULL) {\ +jsm_getActiveProfile = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getActiveProfile", "(Ljava/lang/Object;)Ljava/lang/String;");\ +if (jsm_getActiveProfile == NULL) {\ +return;\ +}\ +}\ + +#define GET_getVerbalizePunctuationStyle()\ +GET_AccessibleAnnouncerUtilities();\ +if (jsm_getVerbalizePunctuationStyle == NULL) {\ +jsm_getVerbalizePunctuationStyle = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getVerbalizePunctuationStyle", "(Ljava/lang/Object;)I");\ +if (jsm_getVerbalizePunctuationStyle == NULL) {\ +return;\ +}\ +}\ + +#define GET_getEnableSpeech(ret)\ +GET_AccessibleAnnouncerUtilitiesReturn(ret);\ +if (jsm_getEnableSpeech == NULL) {\ +jsm_getEnableSpeech = (*env)->GetStaticMethodID(env, jc_AccessibleAnnouncerUtilities, "getEnableSpeech", "(Ljava/lang/Object;)Z");\ +if (jsm_getEnableSpeech == NULL) {\ +return ret;\ +}\ +}\ + +#endif // #ifndef NO_A11Y_SPEECHD_ANNOUNCING + + +#endif //ACCESSIBLEANNOUNCERJNIUTILS_H + diff --git a/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/OrcaAnnouncer.c b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/OrcaAnnouncer.c new file mode 100644 index 000000000000..acf24f40cf5b --- /dev/null +++ b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/OrcaAnnouncer.c @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef NO_A11Y_SPEECHD_ANNOUNCING + +#include "sun_swing_AccessibleAnnouncer.h" +#include "OrcaAnnouncer.h" +#include "AccessibleAnnouncerJNIUtils.h" +#include "jni_util.h" +#include "debug_assert.h" + +static jclass jc_AccessibleAnnouncerUtilities = NULL; +static jmethodID jsm_getOrcaConf = NULL; +static jmethodID jsm_getSpeechServerInfo = NULL; +static jmethodID jsm_getGain = NULL; +static jmethodID jsm_getVariant = NULL; +static jmethodID jsm_getDialect = NULL; +static jmethodID jsm_getLang = NULL; +static jmethodID jsm_getName = NULL; +static jmethodID jsm_getAveragePitch = NULL; +static jmethodID jsm_getRate = NULL; +static jmethodID jsm_getEstablished = NULL; +static jmethodID jsm_getActiveProfile = NULL; +static jmethodID jsm_getVerbalizePunctuationStyle = NULL; +static jmethodID jsm_getOnlySpeakDisplayedText = NULL; +static jmethodID jsm_getEnableSpeech = NULL; + +int OrcaAnnounce(JNIEnv *env, jstring str, jint priority) +{ + DASSERT(env != NULL); + DASSERT(str != NULL) + + jobject conf = OrcaGetConf(env); + if (conf == NULL) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read Orca configuration file\n"); +#endif + return -1; + } + + if (OrcaGetEnableSpeech(env, conf) <= 0) + { +#ifdef DEBUG + fprintf(stderr, "Speech is disable\n"); +#endif + (*env)->DeleteLocalRef(env, conf); + return -1; + } + + SPDConnection *connection = spd_open("Java announcer", NULL, NULL, SPD_MODE_SINGLE); + if (connection == NULL) + { +#ifdef DEBUG + fprintf(stderr, "Speech dispatcher connection is null\n"); +#endif + (*env)->DeleteLocalRef(env, conf); + return -1; + } + + const char *msg = JNU_GetStringPlatformChars(env, str, NULL); + if (msg == NULL) + { + if ((*env)->ExceptionCheck(env) == JNI_FALSE) + { + JNU_ThrowOutOfMemoryError(env, "OrcaAnnounce: failed to obtain chars from the announcing string"); + } + + spd_close(connection); + (*env)->DeleteLocalRef(env, conf); + return -1; + } + + OrcaSetSpeechConf(env, connection, conf); + (*env)->DeleteLocalRef(env, conf); + int p = SPD_TEXT; + if (priority == sun_swing_AccessibleAnnouncer_ANNOUNCE_WITH_INTERRUPTING_CURRENT_OUTPUT) + { + p = SPD_MESSAGE; + } + int err = spd_say(connection, p, msg); + spd_close(connection); + JNU_ReleaseStringPlatformChars(env, str, msg); + + if (err < 0) + { +#ifdef DEBUG + fprintf(stderr, "Failed to say message\n"); +#endif + return -1; + } + + return 0; +} + +void OrcaSetSpeechConf(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + OrcaSetOutputModule(env, connection, conf); + OrcaSetSynthesisVoice(env, connection, conf); + OrcaSetLanguage(env, connection, conf); + OrcaSetPunctuation(env, connection, conf); + OrcaSetVoiceRate(env, connection, conf); + OrcaSetVoicePitch(env, connection, conf); + OrcaSetVolume(env, connection, conf); +} + +void OrcaSetVolume(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getGain(); + jdouble gain = (*env)->CallStaticDoubleMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getGain, conf); + JNU_CHECK_EXCEPTION(env); + if (gain < 0) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of gain from config\n"); +#endif + return; + } + + int volume = (int)((gain - 5) * 20); + spd_set_volume(connection, volume); +} + +void OrcaSetVoiceRate(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getRate(); + jdouble rate = (*env)->CallStaticDoubleMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getRate, conf); + JNU_CHECK_EXCEPTION(env); + if (rate < 0) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of rate from config\n"); +#endif + return; + } + + int iRate = (int)((rate - 50) * 2); + spd_set_voice_rate(connection, iRate); +} + +void OrcaSetPunctuation(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getVerbalizePunctuationStyle(); + jint punctuation = (*env)->CallStaticIntMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getVerbalizePunctuationStyle, conf); + JNU_CHECK_EXCEPTION(env); + if (punctuation < 0) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of punctuation from config\n"); +#endif + return; + } + + spd_set_punctuation(connection, punctuation); +} + +void OrcaSetVoicePitch(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getAveragePitch(); + jdouble pitch = (*env)->CallStaticDoubleMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getAveragePitch, conf); + JNU_CHECK_EXCEPTION(env); + if (pitch < 0) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of pitch from config\n"); +#endif + return; + } + + int iPitch = (int)((pitch - 5) * 20); + spd_set_voice_pitch(connection, iPitch); +} + +void OrcaSetOutputModule(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getSpeechServerInfo(); + jobject jStr = (*env)->CallStaticObjectMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getSpeechServerInfo, conf); + JNU_CHECK_EXCEPTION(env); + if (jStr == NULL) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of speech server info from config\n"); +#endif + return; + } + + const char *sintName = JNU_GetStringPlatformChars(env, jStr, NULL); + if (sintName == NULL) + { + if ((*env)->ExceptionCheck(env) == JNI_FALSE) + { + JNU_ThrowOutOfMemoryError(env, "OrcaAnnounce: failed to obtain chars from the sintName string"); + } + + (*env)->DeleteLocalRef(env, jStr); + return; + } + + spd_set_output_module(connection, sintName); + JNU_ReleaseStringPlatformChars(env, jStr, sintName); + (*env)->DeleteLocalRef(env, jStr); +} + +void OrcaSetLanguage(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getLang(); + jobject jStr = (*env)->CallStaticObjectMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getLang, conf); + JNU_CHECK_EXCEPTION(env); + if (jStr == NULL) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of lang from config\n"); +#endif + return; + } + + const char *lang = JNU_GetStringPlatformChars(env, jStr, NULL); + if (lang == NULL) + { + if ((*env)->ExceptionCheck(env) == JNI_FALSE) + { + JNU_ThrowOutOfMemoryError(env, "OrcaAnnounce: failed to obtain chars from the lang string"); + } + + (*env)->DeleteLocalRef(env, jStr); + return; + } + + spd_set_language(connection, lang); + JNU_ReleaseStringPlatformChars(env, jStr, lang); + (*env)->DeleteLocalRef(env, jStr); +} + +int OrcaGetEnableSpeech(JNIEnv *env, jobject conf) +{ + GET_getEnableSpeech(-1); + int es = (*env)->CallStaticBooleanMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getEnableSpeech, conf); + JNU_CHECK_EXCEPTION_RETURN(env, -1); + return es; +} + +void OrcaSetSynthesisVoice(JNIEnv *env, SPDConnection *connection, jobject conf) +{ + GET_getName(); + jobject jStr = (*env)->CallStaticObjectMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getName, conf); + JNU_CHECK_EXCEPTION(env); + if (jStr == NULL) + { +#ifdef DEBUG + fprintf(stderr, "Failed to read value of voice name from config\n"); +#endif + return; + } + + const char *voiceName = JNU_GetStringPlatformChars(env, jStr, NULL); + if (voiceName == NULL) + { + if ((*env)->ExceptionCheck(env) == JNI_FALSE) + { + JNU_ThrowOutOfMemoryError(env, "OrcaAnnounce: failed to obtain chars from the voiceName string"); + } + + (*env)->DeleteLocalRef(env, jStr); + return; + } + + spd_set_synthesis_voice(connection, voiceName); + JNU_ReleaseStringPlatformChars(env, jStr, voiceName); + (*env)->DeleteLocalRef(env, jStr); +} + +jobject OrcaGetConf(JNIEnv *env) +{ + GET_getOrcaConfReturn(NULL); + jobject o = (*env)->CallStaticObjectMethod(env, jc_AccessibleAnnouncerUtilities, jsm_getOrcaConf); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + return o; +} + +#endif // #ifndef NO_A11Y_SPEECHD_ANNOUNCING diff --git a/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/OrcaAnnouncer.h b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/OrcaAnnouncer.h new file mode 100644 index 000000000000..8eac261482cd --- /dev/null +++ b/src/java.desktop/unix/native/libawt_wlawt/AccessibleAnnouncer/OrcaAnnouncer.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ORCACONF_H +#define ORCACONF_H + +#ifndef NO_A11Y_SPEECHD_ANNOUNCING + +#include +#include "jni.h" + +int OrcaAnnounce(JNIEnv *env, jstring str, jint priority); +jobject OrcaGetConf(JNIEnv *env); +void OrcaSetSpeechConf(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetLanguage(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetOutputModule(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetPunctuation(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetSynthesisVoice(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetVoiceRate(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetVoicePitch(JNIEnv *env, SPDConnection *connection, jobject conf); +void OrcaSetVolume(JNIEnv *env, SPDConnection *connection, jobject conf); +int OrcaGetEnableSpeech(JNIEnv *env, jobject conf); + +#endif // #ifndef NO_A11Y_SPEECHD_ANNOUNCING + +#endif //ORCACONF_H + diff --git a/src/jdk.accessibility/linux/classes/module-info.java.extra b/src/jdk.accessibility/linux/classes/module-info.java.extra new file mode 100644 index 000000000000..d4c2cabf24d2 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/module-info.java.extra @@ -0,0 +1 @@ +provides javax.accessibility.AccessibilityProvider with org.GNOME.Accessibility.AtkProvider; \ No newline at end of file diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkAction.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkAction.java new file mode 100644 index 000000000000..ddc8dd226613 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkAction.java @@ -0,0 +1,214 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Action interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleAction interface + * and the ATK (Accessibility Toolkit) action interface. + */ +public class AtkAction { + + private final WeakReference accessibleContextWeakRef; + private final WeakReference accessibleActionWeakRef; + + private final String[] actionDescriptions; + private final String[] actionLocalizedNames; + private final int actionCount; // the number of accessible actions available on the object + + private final Object actionDescriptionsLock = new Object(); + private final Object actionLocalizedNamesLock = new Object(); + + private AtkAction(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + AccessibleAction accessibleAction = ac.getAccessibleAction(); + if (accessibleAction == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleAction"); + } + + this.accessibleContextWeakRef = new WeakReference(ac); + this.accessibleActionWeakRef = new WeakReference(accessibleAction); + this.actionCount = accessibleAction.getAccessibleActionCount(); + this.actionDescriptions = new String[actionCount]; + this.actionLocalizedNames = new String[actionCount]; + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkAction instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkAction instance, or null if creation fails + */ + private static AtkAction create_atk_action(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkAction(ac); + }, null); + } + + /** + * Performs the specified action on the object. + * Called from native code via JNI. + * + * @param index the action index corresponding to the action to be performed + * @return true if the action was successfully performed, false otherwise + */ + private boolean do_action(int index) { + if (index < 0 || index >= actionCount) { + return false; + } + AccessibleAction accessibleAction = accessibleActionWeakRef.get(); + if (accessibleAction == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleAction.doAccessibleAction(index); + }, false); + } + + /** + * Gets the number of accessible actions available on the object. + * Called from native code via JNI. + * + * @return the number of actions, or 0 if this object does not implement actions + */ + private int get_n_actions() { + return this.actionCount; + } + + /** + * Returns a description of the specified action of the object. + * Called from native code via JNI. + * + * @param index the action index corresponding to the action + * @return a description string, or null if the action does not exist + */ + private String get_description(int index) { + if (index < 0 || index >= actionCount) { + return null; + } + AccessibleAction accessibleAction = accessibleActionWeakRef.get(); + if (accessibleAction == null) { + return null; + } + + String desc; + + synchronized (actionDescriptionsLock) { + desc = actionDescriptions[index]; + if (desc != null) { + return desc; + } + } + + String computedActionDesc = AtkUtil.invokeInSwingAndWait(() -> { + return accessibleAction.getAccessibleActionDescription(index); + }, null); + + synchronized (actionDescriptionsLock) { + desc = actionDescriptions[index]; + if (desc == null) { + actionDescriptions[index] = computedActionDesc; + desc = computedActionDesc; + } + } + + return desc; + } + + /** + * Sets a description of the specified action of the object. + * Called from native code via JNI. + * + * @param index the action index corresponding to the action + * @param description the description to be assigned to this action + * @return true if the description was successfully set, false otherwise + */ + private boolean set_description(int index, String description) { + if (index < 0 || index >= actionCount) { + return false; + } + synchronized (actionDescriptionsLock) { + actionDescriptions[index] = description; + } + return true; + } + + /** + * Returns the localized name of the specified action of the object. + * Called from native code via JNI. + * + * @param index the action index corresponding to the action + * @return a localized name string, or null if the action does not exist + */ + private String get_localized_name(int index) { + if (index < 0 || index >= actionCount) { + return null; + } + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return null; + } + AccessibleAction accessibleAction = accessibleActionWeakRef.get(); + if (accessibleAction == null) { + return null; + } + + String localizedName; + + synchronized (actionLocalizedNamesLock) { + localizedName = actionLocalizedNames[index]; + if (localizedName != null) { + return localizedName; + } + } + + String computedLocalizedName = AtkUtil.invokeInSwingAndWait(() -> { + String description = accessibleAction.getAccessibleActionDescription(index); + if (description != null) { + return description; + } + String name = accessibleContext.getAccessibleName(); + if (name != null) { + return name; + } + return ""; + }, null); + + synchronized (actionLocalizedNamesLock) { + localizedName = actionLocalizedNames[index]; + if (localizedName == null) { + actionLocalizedNames[index] = computedLocalizedName; + localizedName = computedLocalizedName; + } + } + + return localizedName; + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkComponent.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkComponent.java new file mode 100644 index 000000000000..9250d3a14cbc --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkComponent.java @@ -0,0 +1,407 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Dimension; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Component interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleComponent interface + * and the ATK (Accessibility Toolkit) component interface. + */ +public class AtkComponent { + + private final WeakReference accessibleContextWeakRef; + private final WeakReference accessibleComponentWeakRef; + + private AtkComponent(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + AccessibleComponent accessibleComponent = ac.getAccessibleComponent(); + if (accessibleComponent == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleComponent"); + } + + this.accessibleContextWeakRef = new WeakReference(ac); + this.accessibleComponentWeakRef = new WeakReference(accessibleComponent); + } + + private static Point getWindowLocation(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + while (ac != null) { + AccessibleRole accessibleRole = ac.getAccessibleRole(); + if (accessibleRole == AccessibleRole.DIALOG || + accessibleRole == AccessibleRole.FRAME || + accessibleRole == AccessibleRole.WINDOW) { + AccessibleComponent accessibleComponent = ac.getAccessibleComponent(); + if (accessibleComponent == null) { + return null; + } + return accessibleComponent.getLocationOnScreen(); + } + Accessible accessibleParent = ac.getAccessibleParent(); + if (accessibleParent == null) { + return null; + } + ac = accessibleParent.getAccessibleContext(); + } + return null; + } + + /** + * Return the position of the object relative to the coordinate type + * 0 - Coordinates are relative to the screen. + * 1 - Coordinates are relative to the component's toplevel window. + * 2 - Coordinates are relative to the component's immediate parent. + * + * @param ac + * @param coordType + * @return + */ + public static Point getLocationByCoordinateType(AccessibleContext ac, int coordType) { + assert EventQueue.isDispatchThread(); + + AccessibleComponent accessibleComponent = ac.getAccessibleComponent(); + if (accessibleComponent == null) { + return null; + } + + if (coordType == AtkCoordType.SCREEN) { + // Returns the location of the object on the screen. + return accessibleComponent.getLocationOnScreen(); + } + + if (coordType == AtkCoordType.WINDOW) { + Point windowLocation = getWindowLocation(ac); + if (windowLocation == null) { + return null; + } + Point componentLocationOnScreen = accessibleComponent.getLocationOnScreen(); + if (componentLocationOnScreen == null) { + return null; + } + componentLocationOnScreen.translate(-windowLocation.x, -windowLocation.y); + return componentLocationOnScreen; + } + + if (coordType == AtkCoordType.PARENT) { + // Gets the location of the object relative to the parent + // in the form of a point specifying the object's top-left + // corner in the screen's coordinate space. + return accessibleComponent.getLocation(); + } + + return null; + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkComponent instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkComponent instance, or null if creation fails + */ + private static AtkComponent create_atk_component(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkComponent(ac); + }, null); + } + + /** + * Checks whether the specified point is within the extent of the component. + * Called from native code via JNI. + * + * @param x x coordinate + * @param y y coordinate + * @param coordType specifies whether the coordinates are relative to the screen, + * the component's toplevel window, or the component's parent + * @return true if the specified point is within the extent of the component + */ + private boolean contains(int x, int y, int coordType) { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return false; + } + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleComponent.isVisible()) { + Point componentLocation = getLocationByCoordinateType(accessibleContext, coordType); + if (componentLocation == null) { + return false; + } + + return accessibleComponent.contains(new Point(x - componentLocation.x, y - componentLocation.y)); + } + return false; + }, false); + } + + /** + * Gets a reference to the accessible child, if one exists, at the coordinate point + * specified by x and y. + * Called from native code via JNI. + * + * @param x x coordinate + * @param y y coordinate + * @param coordType specifies whether the coordinates are relative to the screen, + * the component's toplevel window, or the component's parent + * @return the AccessibleContext of the child at the specified point, or null if none exists + */ + private AccessibleContext get_accessible_at_point(int x, int y, int coordType) { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return null; + } + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleComponent.isVisible()) { + Point componentLocation = getLocationByCoordinateType(accessibleContext, coordType); + if (componentLocation == null) { + return null; + } + + Accessible accessibleAt = accessibleComponent.getAccessibleAt(new Point(x - componentLocation.x, y - componentLocation.y)); + if (accessibleAt == null) { + return null; + } + AccessibleContext accessibleContextAt = accessibleAt.getAccessibleContext(); + if (accessibleContextAt != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContextAt); + } + return accessibleContextAt; + } + return null; + }, null); + } + + /** + * Grabs focus for this component. + * Called from native code via JNI. + * + * @return true if successful, false otherwise + */ + private boolean grab_focus() { + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (!accessibleComponent.isFocusTraversable()) { + return false; + } + accessibleComponent.requestFocus(); + return true; + }, false); + } + + /** + * Sets the extents of the component. + * Called from native code via JNI. + * + * @param newXByCoordType x coordinate + * @param newYByCoordType y coordinate + * @param width width to set for the component + * @param height height to set for the component + * @param coordType specifies whether the coordinates are relative to the screen, + * the component's toplevel window, or the component's parent + * @return true if the extents were set successfully, false otherwise + */ + private boolean set_extents(int newXByCoordType, int newYByCoordType, int width, int height, int coordType) { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return false; + } + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleComponent.isVisible()) { + Point locationByCoordType = getLocationByCoordinateType(accessibleContext, coordType); + if (locationByCoordType == null) { + return false; + } + + Point locationByParent = accessibleComponent.getLocation(); + if (locationByParent == null) { + return false; + } + + accessibleComponent.setBounds(new Rectangle(locationByParent.x + (newXByCoordType - locationByCoordType.x), locationByParent.y + (newYByCoordType - locationByCoordType.y), width, height)); + return true; + } + return false; + }, false); + } + + /** + * Gets the rectangle which gives the extent of the component. + * Called from native code via JNI. + * + * @param coordType specifies whether the coordinates are relative to the screen, + * the component's toplevel window, or the component's parent + * @return the Rectangle representing the component's extent, or null if it cannot be obtained + */ + private Rectangle get_extents(int coordType) { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return null; + } + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleComponent.isVisible()) { + Dimension dimension = accessibleComponent.getSize(); + if (dimension == null) { + return null; + } + Point componentLocation = getLocationByCoordinateType(accessibleContext, coordType); + if (componentLocation == null) { + return null; + } + + return new Rectangle(componentLocation.x, componentLocation.y, dimension.width, dimension.height); + } + return null; + }, null); + } + + /** + * Sets the position of the component. + * Called from native code via JNI. + * + * @param newXByCoordType x coordinate + * @param newYByCoordType y coordinate + * @param coordType specifies whether the coordinates are relative to the screen, + * the component's toplevel window, or the component's parent + * @return true if the extents were set successfully, false otherwise + */ + private boolean set_position(int newXByCoordType, int newYByCoordType, int coordType) { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return false; + } + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleComponent.isVisible()) { + Point locationByCoordType = getLocationByCoordinateType(accessibleContext, coordType); + if (locationByCoordType == null) { + return false; + } + + Point locationByParent = accessibleComponent.getLocation(); + if (locationByParent == null) { + return false; + } + + accessibleComponent.setLocation(new Point(locationByParent.x + (newXByCoordType - locationByCoordType.x), locationByParent.y + (newYByCoordType - locationByCoordType.y))); + return true; + } + return false; + }, false); + } + + /** + * Sets the size of the component. + * Called from native code via JNI. + * + * @param width width to set for the component + * @param height height to set for the component + * @return true if the size was set successfully, false otherwise + */ + private boolean set_size(int width, int height) { + AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get(); + if (accessibleComponent == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleComponent.isVisible()) { + accessibleComponent.setSize(new Dimension(width, height)); + return true; + } + return false; + }, false); + } + + /** + * Gets the AtkLayer of the component based on AccessibleRole. + * Called from native code via JNI. + * + * @return an int representing the AtkLayer of the component, or AtkLayer.INVALID if an error occurs + */ + private int get_layer() { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return AtkLayer.INVALID; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleRole accessibleRole = accessibleContext.getAccessibleRole(); + if (accessibleRole == AccessibleRole.MENU || + accessibleRole == AccessibleRole.MENU_ITEM || + accessibleRole == AccessibleRole.POPUP_MENU) { + return AtkLayer.POPUP; + } + if (accessibleRole == AccessibleRole.INTERNAL_FRAME) { + return AtkLayer.MDI; + } + if (accessibleRole == AccessibleRole.GLASS_PANE) { + return AtkLayer.OVERLAY; + } + if (accessibleRole == AccessibleRole.CANVAS || + accessibleRole == AccessibleRole.ROOT_PANE || + accessibleRole == AccessibleRole.LAYERED_PANE) { + return AtkLayer.CANVAS; + } + if (accessibleRole == AccessibleRole.WINDOW) { + return AtkLayer.WINDOW; + } + return AtkLayer.WIDGET; + }, AtkLayer.INVALID); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkCoordType.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkCoordType.java new file mode 100644 index 000000000000..d2d27837aad6 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkCoordType.java @@ -0,0 +1,40 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +public final class AtkCoordType { + /** + * Coordinates are relative to the screen. + */ + public static final int SCREEN = 0; + + /** + * Coordinates are relative to the component's toplevel window. + */ + public static final int WINDOW = 1; + + /** + * Coordinates are relative to the component's immediate parent. + */ + public static final int PARENT = 2; + + private AtkCoordType() { + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkEditableText.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkEditableText.java new file mode 100644 index 000000000000..d8a2d0964173 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkEditableText.java @@ -0,0 +1,212 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import javax.swing.text.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK EditableText interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleEditableText interface + * and the ATK (Accessibility Toolkit) editable text interface. + */ +public class AtkEditableText extends AtkText { + + private final WeakReference accessibleEditableTextWeakRef; + + private AtkEditableText(AccessibleContext ac) { + super(ac); + + assert EventQueue.isDispatchThread(); + + AccessibleEditableText accessibleEditableText = ac.getAccessibleEditableText(); + if (accessibleEditableText == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleEditableText"); + } + + accessibleEditableTextWeakRef = new WeakReference(accessibleEditableText); + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkEditableText instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkEditableText instance, or null if creation fails + */ + private static AtkEditableText create_atk_editable_text(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkEditableText(ac); + }, null); + } + + /** + * Sets the text contents to the specified string. + * Called from native code via JNI. + * + * @param textContent the string to set as the text contents + */ + private void set_text_contents(String textContent) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleEditableText.setTextContents(textContent); + }); + } + + /** + * Inserts text at the specified position. + * Called from native code via JNI. + * + * @param textToInsert the string to insert + * @param position the position at which to insert the text + */ + private void insert_text(String textToInsert, int position) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return; + } + + if (position < 0) { + position = 0; + } + + final int finalPosition = position; + AtkUtil.invokeInSwing(() -> { + accessibleEditableText.insertTextAtIndex(finalPosition, textToInsert); + }); + } + + /** + * Copies text from the specified start and end positions to the system clipboard. + * Called from native code via JNI. + * + * @param start the start position + * @param end the end position + */ + private void copy_text(int start, int end) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return; + } + + int charCount = AtkUtil.invokeInSwingAndWait(() -> { + return accessibleEditableText.getCharCount(); + }, -1); + + if (charCount == -1) { + return; + } + + final int rightStart = getRightStart(start); + final int rightEnd = getRightEnd(rightStart, end, charCount); + AtkUtil.invokeInSwing(() -> { + String textRange = accessibleEditableText.getTextRange(rightStart, rightEnd); + if (textRange != null) { + StringSelection stringSelection = new StringSelection(textRange); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, stringSelection); + } + }); + } + + /** + * Cuts text from the specified start and end positions. + * Called from native code via JNI. + * + * @param start the start position + * @param end the end position + */ + private void cut_text(int start, int end) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleEditableText.cut(start, end); + }); + } + + /** + * Deletes text from the specified start and end positions. + * Called from native code via JNI. + * + * @param start the start position + * @param end the end position + */ + private void delete_text(int start, int end) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleEditableText.delete(start, end); + }); + } + + /** + * Pastes text from the system clipboard at the specified position. + * Called from native code via JNI. + * + * @param position the position at which to paste the text + */ + private void paste_text(int position) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleEditableText.paste(position); + }); + } + + /** + * Sets attributes for the text between two indices. + * Called from native code via JNI. + * + * @param attributeSet the AttributeSet for the text + * @param start the start index of the text + * @param end the end index of the text + * @return true if attributes were successfully set, false otherwise + */ + private boolean set_run_attributes(AttributeSet attributeSet, int start, int end) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + accessibleEditableText.setAttributes(start, end, attributeSet); + return true; + }, false); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHyperlink.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHyperlink.java new file mode 100644 index 000000000000..9590ee41880c --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHyperlink.java @@ -0,0 +1,171 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.awt.EventQueue; +import java.lang.ref.WeakReference; + +/** + * The ATK Hyperlink implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleHyperlink interface + * and the ATK (Accessibility Toolkit) hyperlink interface. + */ +public class AtkHyperlink { + + private final WeakReference accessibleHyperlinkWeakRef; + + private AtkHyperlink(AccessibleHyperlink accessibleHyperlink) { + assert EventQueue.isDispatchThread(); + accessibleHyperlinkWeakRef = new WeakReference(accessibleHyperlink); + } + + static AtkHyperlink createAtkHyperlink(AccessibleHyperlink accessibleHyperlink) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkHyperlink(accessibleHyperlink); + }, null); + } + + /* JNI upcalls section */ + + /** + * Gets the URI associated with the anchor specified by the index. + * Called from native code via JNI. + * + * @param index the zero-based index specifying the desired anchor + * @return a string specifying the URI, or null if not available + */ + private String get_uri(int index) { + AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get(); + if (accessibleHyperlink == null || index < 0) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Object accessibleActionObject = accessibleHyperlink.getAccessibleActionObject(index); + if (accessibleActionObject != null) { + return accessibleActionObject.toString(); + } + return null; + }, null); + } + + /** + * Returns the accessible object associated with this hyperlink's nth anchor. + * Called from native code via JNI. + * + * @param index the zero-based index specifying the desired anchor + * @return the AccessibleContext associated with this hyperlink's index-th anchor, + * or null if not available + */ + private AccessibleContext get_object(int index) { + AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get(); + if (accessibleHyperlink == null || index < 0) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Object anchor = accessibleHyperlink.getAccessibleActionAnchor(index); + if (anchor instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + } + return null; + }, null); + } + + /** + * Gets the index with the hypertext document at which this link ends. + * Called from native code via JNI. + * + * @return the index with the hypertext document at which this link ends, + * or 0 if an error happened. + */ + private int get_end_index() { + AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get(); + if (accessibleHyperlink == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleHyperlink.getEndIndex(); + }, 0); + } + + /** + * Gets the index with the hypertext document at which this link begins. + * Called from native code via JNI. + * + * @return the index with the hypertext document at which this link begins, + * or 0 if an error happened + */ + private int get_start_index() { + AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get(); + if (accessibleHyperlink == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleHyperlink.getStartIndex(); + }, 0); + } + + /** + * Determines whether this link is still valid. + * Called from native code via JNI. + *

+ * Since the document that a link is associated with may have changed, + * this method returns true if the link is still valid (with respect to + * the document it references) and false otherwise. + * + * @return true if the link is still valid, false otherwise + */ + private boolean is_valid() { + AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get(); + if (accessibleHyperlink == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleHyperlink.isValid(); + }, false); + } + + /** + * Gets the number of anchors associated with this hyperlink. + * Called from native code via JNI (jaw_hyperlink_get_n_anchors in jawhyperlink.c). + * + * @return the number of anchors associated with this hyperlink + */ + private int get_n_anchors() { + AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get(); + if (accessibleHyperlink == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleHyperlink.getAccessibleActionCount(); + }, 0); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHypertext.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHypertext.java new file mode 100644 index 000000000000..9b99e60ca5e7 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHypertext.java @@ -0,0 +1,133 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Hypertext interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleHypertext interface + * and the ATK (Accessibility Toolkit) hypertext interface. + * Hypertext allows navigation through documents containing + * embedded links or hyperlinks. + */ +public class AtkHypertext extends AtkText { + + private final WeakReference accessibleHypertextRef; + + private AtkHypertext(AccessibleContext ac) { + super(ac); + + assert EventQueue.isDispatchThread(); + + AccessibleText accessibleText = ac.getAccessibleText(); + if (accessibleText instanceof AccessibleHypertext accessibleHypertext) { + accessibleHypertextRef = new WeakReference(accessibleHypertext); + } else { + throw new IllegalArgumentException("AccessibleContext must have AccessibleHypertext"); + } + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkHypertext instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkHypertext instance, or null if creation fails + */ + private static AtkHypertext create_atk_hypertext(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkHypertext(ac); + }, null); + } + + /** + * Gets the link in this hypertext document at the specified index. + * Called from native code via JNI. + * + * @param linkIndex an integer specifying the desired link (zero-based) + * @return the AtkHyperlink at the specified index, or null if not available + */ + private AtkHyperlink get_link(int linkIndex) { + if (accessibleHypertextRef == null) { + return null; + } + AccessibleHypertext accessibleHypertext = accessibleHypertextRef.get(); + if (accessibleHypertext == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleHyperlink link = accessibleHypertext.getLink(linkIndex); + if (link != null) { + return AtkHyperlink.createAtkHyperlink(link); + } + return null; + }, null); + } + + /** + * Gets the number of links within this hypertext document. + * Called from native code via JNI. + * + * @return the number of links within this hypertext document + */ + private int get_n_links() { + if (accessibleHypertextRef == null) { + return 0; + } + AccessibleHypertext accessibleHypertext = accessibleHypertextRef.get(); + if (accessibleHypertext == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleHypertext.getLinkCount(); + }, 0); + } + + /** + * Gets the index into the array of hyperlinks that is associated with + * the character specified by charIndex. + * Called from native code via JNI. + * + * @param charIndex a character index + * @return an index into the array of hyperlinks in this hypertext, + * or -1 if there is no hyperlink associated with this character + */ + private int get_link_index(int charIndex) { + if (accessibleHypertextRef == null) { + return -1; + } + AccessibleHypertext accessibleHypertext = accessibleHypertextRef.get(); + if (accessibleHypertext == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleHypertext.getLinkIndex(charIndex); + }, -1); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkImage.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkImage.java new file mode 100644 index 000000000000..fdcc0754aa0b --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkImage.java @@ -0,0 +1,149 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.awt.Point; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Image interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleIcon interface + * and the ATK (Accessibility Toolkit) image interface. + * AtkImage should be implemented by components which display + * image/pixmap content on-screen, such as icons, buttons with icons, + * toolbar elements, and image viewing panes. + *

+ * This interface provides two types of information: coordinate information + * (useful for screen review mode and onscreen magnifiers) and descriptive + * information (for alternative text-only presentation). + */ +public class AtkImage { + + private final WeakReference accessibleContextWeakRef; + private final WeakReference accessibleIcons; + + private AtkImage(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + this.accessibleContextWeakRef = new WeakReference(ac); + this.accessibleIcons = new WeakReference(ac.getAccessibleIcon()); + } + + // JNI upcalls section + + /** + * Factory method to create an AtkImage instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkImage instance, or null if creation fails + */ + private static AtkImage create_atk_image(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkImage(ac); + }, null); + } + + /** + * Gets the position of the image in the form of a point specifying the + * image's top-left corner. + * Called from native code via JNI. + * + * @param coordType specifies whether the coordinates are relative to the screen + * or to the component's top level window + * @return a Point representing the image position (x, y coordinates), or null + * if the position cannot be obtained (e.g., missing support). + */ + private Point get_image_position(int coordType) { + AccessibleContext ac = accessibleContextWeakRef.get(); + if (ac == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return AtkComponent.getLocationByCoordinateType(ac, coordType); + }, null); + } + + /** + * Gets a textual description of this image. + * Called from native code via JNI. + * + * @return a string representing the image description, or null if there is + * no description available or an error occurs + */ + private String get_image_description() { + AccessibleIcon[] accessibleIcons = this.accessibleIcons.get(); + if (accessibleIcons == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + String description = null; + if (accessibleIcons.length > 0) { + description = accessibleIcons[0].getAccessibleIconDescription(); + } + return description; + }, null); + } + + /** + * Gets the width and height in pixels for the specified image. + * Called from native code via JNI. + * + * @return a Dimension containing the width and height of the image. + * Returns a Dimension with width and height set to -1 if the values + * cannot be obtained (for instance, if the object is not onscreen). + * If AccessibleIcon information is available, uses that; otherwise, + * falls back to the component's bounds. + */ + private Dimension get_image_size() { + Dimension dimension = new Dimension(-1, -1); + + AccessibleContext ac = accessibleContextWeakRef.get(); + if (ac == null) { + return dimension; + } + + AccessibleIcon[] accessibleIcons = this.accessibleIcons.get(); + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleIcons != null && accessibleIcons.length > 0) { + dimension.height = accessibleIcons[0].getAccessibleIconHeight(); + dimension.width = accessibleIcons[0].getAccessibleIconWidth(); + } else { + AccessibleComponent accessibleComponent = ac.getAccessibleComponent(); + if (accessibleComponent != null) { + Rectangle rectangle = accessibleComponent.getBounds(); + if (rectangle != null) { + dimension.height = rectangle.height; + dimension.width = rectangle.width; + } + } + } + return dimension; + }, dimension); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkInterface.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkInterface.java new file mode 100644 index 000000000000..93b49266b252 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkInterface.java @@ -0,0 +1,98 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import java.lang.annotation.Native; + +public interface AtkInterface { + /** + * The ATK interface provided by UI components + * which the user can activate/interact with. + */ + @Native + int INTERFACE_ACTION = 0x00000001; + + /** + * The ATK interface provided by UI components + * which occupy a physical area on the screen. + * which the user can activate/interact with. + */ + @Native + int INTERFACE_COMPONENT = 0x00000002; + + /** + * The ATK interface which represents the toplevel container for document content. + */ + @Native + int INTERFACE_DOCUMENT = 0x00000004; + + /** + * The ATK interface implemented by components containing user-editable text content. + */ + @Native + int INTERFACE_EDITABLE_TEXT = 0x00000008; + + /** + * The ATK interface which provides standard mechanism for manipulating hyperlinks. + */ + @Native + int INTERFACE_HYPERTEXT = 0x00000020; + + /** + * The ATK Interface implemented by components + * which expose image or pixmap content on-screen. + */ + @Native + int INTERFACE_IMAGE = 0x00000040; + + /** + * The ATK interface implemented by container objects + * whose AtkObject children can be selected. + */ + @Native + int INTERFACE_SELECTION = 0x00000080; + + /** + * The ATK interface implemented for UI components + * which contain tabular or row/column information. + */ + @Native + int INTERFACE_TABLE = 0x00000200; + + /** + * The ATK interface implemented for a cell inside a two-dimentional AtkTable. + */ + @Native + int INTERFACE_TABLE_CELL = 0x00000400; + + /** + * The ATK interface implemented by components with text content. + */ + @Native + int INTERFACE_TEXT = 0x00000800; + + /** + * The ATK interface implemented by valuators and components + * which display or select a value from a bounded range of values. + */ + @Native + int INTERFACE_VALUE = 0x00001000; +} + diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkKeyEvent.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkKeyEvent.java new file mode 100644 index 000000000000..9358247d97bc --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkKeyEvent.java @@ -0,0 +1,330 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import java.awt.event.*; +import java.util.HashMap; + +import sun.awt.AWTAccessor; + +/** + * Repeats structure of Atk.KeyEventStruct encapsulating information about a key event. + * Using to create Atk.KeyEventStruct instance. + *

+ * struct AtkKeyEventStruct { + * gint type; + * guint state; + * guint keyval; + * gint length; + * gchar* string; + * guint16 keycode; + * guint32 timestamp; + * } + */ +public class AtkKeyEvent { + + // Symbols mapped to X11 keysym names + private static final HashMap nonAlphaNumericMap; + + private static final int ATK_KEY_EVENT_PRESSED = 0; + private static final int ATK_KEY_EVENT_RELEASED = 1; + + static { + // Non-alphanumeric symbols that need to be mapped to X11 keysym names + nonAlphaNumericMap = new HashMap<>(40); + nonAlphaNumericMap.put("!", "exclam"); + nonAlphaNumericMap.put("@", "at"); + nonAlphaNumericMap.put("#", "numbersign"); + nonAlphaNumericMap.put("$", "dollar"); + nonAlphaNumericMap.put("%", "percent"); + nonAlphaNumericMap.put("^", "asciicircum"); + nonAlphaNumericMap.put("&", "ampersand"); + nonAlphaNumericMap.put("*", "asterisk"); + nonAlphaNumericMap.put("(", "parenleft"); + nonAlphaNumericMap.put(")", "parenright"); + nonAlphaNumericMap.put("-", "minus"); + nonAlphaNumericMap.put("_", "underscore"); + nonAlphaNumericMap.put("=", "equal"); + nonAlphaNumericMap.put("+", "plus"); + nonAlphaNumericMap.put("\\", "backslash"); + nonAlphaNumericMap.put("|", "bar"); + nonAlphaNumericMap.put("`", "grave"); + nonAlphaNumericMap.put("~", "asciitilde"); + nonAlphaNumericMap.put("[", "bracketleft"); + nonAlphaNumericMap.put("{", "braceleft"); + nonAlphaNumericMap.put("]", "bracketright"); + nonAlphaNumericMap.put("}", "braceright"); + nonAlphaNumericMap.put(";", "semicolon"); + nonAlphaNumericMap.put(":", "colon"); + nonAlphaNumericMap.put("'", "apostrophe"); + nonAlphaNumericMap.put("\"", "quotedbl"); + nonAlphaNumericMap.put(",", "comma"); + nonAlphaNumericMap.put("<", "less"); + nonAlphaNumericMap.put(".", "period"); + nonAlphaNumericMap.put(">", "greater"); + nonAlphaNumericMap.put("/", "slash"); + nonAlphaNumericMap.put("?", "question"); + } + + private final int type; + private final boolean isShiftKeyDown; + private final boolean isCtrlKeyDown; + private final boolean isAltKeyDown; + private final boolean isMetaKeyDown; + private final boolean isAltGrKeyDown; + /* A keysym value corresponding to those used by GDK and X11. see /usr/X11/include/keysymdef.h. + * FIXME: in current implementation we get this value from GNOMEKeyInfo.gdkKeyCode that are defined manually GNOMEKeyMapping.initializeMap, + * doesn't look good. + */ + private final int keyval; + /* + * Either a string approximating the text that would result + * from this keypress, or a symbolic name for this keypress. + */ + private final String string; + // The raw hardware code that generated the key event. + private final int keycode; + // A timestamp in milliseconds indicating when the event occurred. + private final long timestamp; + + public AtkKeyEvent(KeyEvent e) { + //type + switch (e.getID()) { + case KeyEvent.KEY_PRESSED: + case KeyEvent.KEY_TYPED: + type = ATK_KEY_EVENT_PRESSED; // 0 + break; + case KeyEvent.KEY_RELEASED: + type = ATK_KEY_EVENT_RELEASED; // 1 + break; + default: + type = ATK_KEY_EVENT_PRESSED; // 0 + } + + //modifiers + int modifierMask = e.getModifiersEx(); + isShiftKeyDown = (modifierMask & InputEvent.SHIFT_DOWN_MASK) != 0; + isCtrlKeyDown = (modifierMask & InputEvent.CTRL_DOWN_MASK) != 0; + isAltKeyDown = (modifierMask & InputEvent.ALT_DOWN_MASK) != 0; + isMetaKeyDown = (modifierMask & InputEvent.META_DOWN_MASK) != 0; + isAltGrKeyDown = (modifierMask & InputEvent.ALT_GRAPH_DOWN_MASK) != 0; + + GNOMEKeyMapping.GNOMEKeyInfo keyInfo = GNOMEKeyMapping.getKey(e); + + int tempKeyval; + String tempString; + if (keyInfo != null) { + tempKeyval = keyInfo.gdkKeyCode(); + tempString = keyInfo.gdkKeyString(); + } else { + char keyChar = e.getKeyChar(); + if (keyChar == KeyEvent.CHAR_UNDEFINED) { + tempKeyval = 0; + tempString = KeyEvent.getKeyText(e.getKeyCode()); + if (tempString == null) tempString = ""; + } else { + tempKeyval = keyChar; + tempString = String.valueOf(keyChar); + } + } + + keycode = keyInfo != null ? keyInfo.gdkKeyCode() : e.getKeyCode(); + timestamp = e.getWhen(); + + String nonAlphaNumericString = nonAlphaNumericMap.get(tempString); + if (nonAlphaNumericString != null) { + tempString = nonAlphaNumericString; + } + + keyval = tempKeyval; + string = tempString; + } +} + +class GNOMEKeyMapping { + + private static HashMap keyMap = null; + + /* Used to offset VK for NUMPAD keys that don't have a VK_KP_* equivalent. + * At present max VK_* value is 0x0000FFFF + * Also need to support Left/Right variations. + */ + private static final int NUMPAD_OFFSET = 0xFEFE0000; + private static final int LEFT_OFFSET = 0xFEFD0000; + private static final int RIGHT_OFFSET = 0xFEFC0000; + + static { + initializeMap(); + } + + private GNOMEKeyMapping() { + } + + public static GNOMEKeyInfo getKey(KeyEvent e) { + GNOMEKeyInfo gdkKeyInfo; + int javaKeyCode = e.getKeyCode(); + int javaKeyLocation = e.getKeyLocation(); + + if (javaKeyLocation == KeyEvent.KEY_LOCATION_NUMPAD) + javaKeyCode += NUMPAD_OFFSET; + else if (javaKeyLocation == KeyEvent.KEY_LOCATION_LEFT) + javaKeyCode += LEFT_OFFSET; + else if (javaKeyLocation == KeyEvent.KEY_LOCATION_RIGHT) + javaKeyCode += RIGHT_OFFSET; + + if ((gdkKeyInfo = keyMap.get(javaKeyCode)) != null) { + return gdkKeyInfo; + } else { + return null; + } + } + + private static void initializeMap() { + keyMap = new HashMap<>(146); // Currently only 110, so allocate 110 / 0.75 + + keyMap.put(KeyEvent.VK_COLON, new GNOMEKeyInfo(0x20a1, "ColonSign")); // GDK_ColonSign + keyMap.put(KeyEvent.VK_EURO_SIGN, new GNOMEKeyInfo(0x20ac, "EuroSign")); // GDK_EuroSign + keyMap.put(KeyEvent.VK_BACK_SPACE, new GNOMEKeyInfo(0xFF08, "BackSpace")); // GDK_BackSpace + keyMap.put(KeyEvent.VK_TAB, new GNOMEKeyInfo(0xFF09, "Tab")); // GDK_Tab + keyMap.put(KeyEvent.VK_CLEAR, new GNOMEKeyInfo(0xFF0B, "Clear")); // GDK_Clear + keyMap.put(KeyEvent.VK_ENTER, new GNOMEKeyInfo(0xFF0D, "Return")); // GDK_Return + keyMap.put(KeyEvent.VK_PAUSE, new GNOMEKeyInfo(0xFF13, "Pause")); // GDK_Pause + keyMap.put(KeyEvent.VK_SCROLL_LOCK, new GNOMEKeyInfo(0xFF14, "Scroll_Lock")); // GDK_Scroll_Lock + keyMap.put(KeyEvent.VK_ESCAPE, new GNOMEKeyInfo(0xFF1B, "Escape")); // GDK_Escape + keyMap.put(KeyEvent.VK_KANJI, new GNOMEKeyInfo(0xFF21, "Kanji")); // GDK_Kanji + keyMap.put(KeyEvent.VK_HIRAGANA, new GNOMEKeyInfo(0xFF25, "Hiragana")); // GDK_Hiragana + keyMap.put(KeyEvent.VK_KATAKANA, new GNOMEKeyInfo(0xFF26, "Katakana")); // GDK_Katakana + keyMap.put(KeyEvent.VK_KANA_LOCK, new GNOMEKeyInfo(0xFF2D, "Kana_Lock")); // GDK_Kana_Lock + keyMap.put(KeyEvent.VK_KANA, new GNOMEKeyInfo(0xFF2E, "Kana_Shift")); // GDK_Kana_Shift + keyMap.put(KeyEvent.VK_KANJI, new GNOMEKeyInfo(0xFF37, "Kanji_Bangou")); // GDK_Kanji_Bangou + + keyMap.put(KeyEvent.VK_HOME, new GNOMEKeyInfo(0xFF50, "Home")); // GDK_Home + keyMap.put(KeyEvent.VK_LEFT, new GNOMEKeyInfo(0xFF51, "Left")); // GDK_Left + keyMap.put(KeyEvent.VK_UP, new GNOMEKeyInfo(0xFF52, "Up")); // GDK_Up + keyMap.put(KeyEvent.VK_RIGHT, new GNOMEKeyInfo(0xFF53, "Right")); // GDK_Right + keyMap.put(KeyEvent.VK_DOWN, new GNOMEKeyInfo(0xFF54, "Down")); // GDK_Down + keyMap.put(KeyEvent.VK_PAGE_UP, new GNOMEKeyInfo(0xFF55, "Page_Up")); // GDK_Page_Up + keyMap.put(KeyEvent.VK_PAGE_DOWN, new GNOMEKeyInfo(0xFF56, "Page_Down")); // GDK_Page_Down + keyMap.put(KeyEvent.VK_END, new GNOMEKeyInfo(0xFF57, "End")); // GDK_End + keyMap.put(KeyEvent.VK_PRINTSCREEN, new GNOMEKeyInfo(0xFF61, "Print")); // GDK_Print + keyMap.put(KeyEvent.VK_INSERT, new GNOMEKeyInfo(0xFF63, "Insert")); // GDK_Insert + keyMap.put(KeyEvent.VK_UNDO, new GNOMEKeyInfo(0xFF65, "Undo")); // GDK_Undo + keyMap.put(KeyEvent.VK_AGAIN, new GNOMEKeyInfo(0xFF66, "Redo")); // GDK_Redo + keyMap.put(KeyEvent.VK_FIND, new GNOMEKeyInfo(0xFF68, "Find")); // GDK_Find + keyMap.put(KeyEvent.VK_CANCEL, new GNOMEKeyInfo(0xFF69, "Cancel")); // GDK_Cancel + keyMap.put(KeyEvent.VK_HELP, new GNOMEKeyInfo(0xFF6A, "Help")); // GDK_Help + keyMap.put(KeyEvent.VK_ALT_GRAPH, new GNOMEKeyInfo(0xFF7E, "Mode_Switch")); // GDK_Mode_Switch + keyMap.put(KeyEvent.VK_NUM_LOCK, new GNOMEKeyInfo(0xFF7F, "Num_Lock")); // GDK_Num_Lock + keyMap.put(KeyEvent.VK_KP_LEFT, new GNOMEKeyInfo(0xFF96, "KP_Left")); // GDK_KP_Left + keyMap.put(KeyEvent.VK_KP_UP, new GNOMEKeyInfo(0xFF97, "KP_Up")); // GDK_KP_Up + keyMap.put(KeyEvent.VK_KP_RIGHT, new GNOMEKeyInfo(0xFF98, "KP_Right")); // GDK_KP_Right + keyMap.put(KeyEvent.VK_KP_DOWN, new GNOMEKeyInfo(0xFF99, "KP_Down")); // GDK_KP_Dow + + // For Key's that are NUMPAD, but no VK_KP_* equivalent exists + // NOTE: Some syms do have VK_KP equivalents, but may or may not have + // KeyLocation() set to NUMPAD - so these are in twice with and + // without the offset.. + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUM_LOCK, new GNOMEKeyInfo(0xFF7F, "Num_Lock")); // GDK_Num_Lock + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_ENTER, new GNOMEKeyInfo(0xFF8D, "KP_Enter")); // GDK_KP_Enter + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_HOME, new GNOMEKeyInfo(0xFF95, "KP_Home")); // GDK_KP_Home + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_LEFT, new GNOMEKeyInfo(0xFF96, "KP_Left")); // GDK_KP_Left + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_UP, new GNOMEKeyInfo(0xFF97, "KP_Up")); // GDK_KP_Up + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_RIGHT, new GNOMEKeyInfo(0xFF98, "KP_Right")); // GDK_KP_Right + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_DOWN, new GNOMEKeyInfo(0xFF99, "KP_Down")); // GDK_KP_Down + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_PAGE_UP, new GNOMEKeyInfo(0xFF9A, "KP_Page_Up")); // GDK_KP_Page_Up + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_PAGE_DOWN, new GNOMEKeyInfo(0xFF9B, "KP_Page_Down")); // GDK_KP_Page_Down + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_END, new GNOMEKeyInfo(0xFF9C, "KP_End")); // GDK_KP_End + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_BEGIN, new GNOMEKeyInfo(0xFF9D, "KP_Begin")); // GDK_KP_Begin + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_INSERT, new GNOMEKeyInfo(0xFF9E, "KP_Insert")); // GDK_KP_Insert + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_DELETE, new GNOMEKeyInfo(0xFF9F, "KP_Delete")); // GDK_KP_Delete + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_MULTIPLY, new GNOMEKeyInfo(0xFFAA, "KP_Multiply")); // GDK_KP_Multiply + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_ADD, new GNOMEKeyInfo(0xFFAB, "KP_Add")); // GDK_KP_Add + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_SEPARATOR, new GNOMEKeyInfo(0xFFAC, "KP_Separator")); // GDK_KP_Separator + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_SUBTRACT, new GNOMEKeyInfo(0xFFAD, "KP_Subtract")); // GDK_KP_Subtract + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_DECIMAL, new GNOMEKeyInfo(0xFFAE, "KP_Decimal")); // GDK_KP_Decimal + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_DIVIDE, new GNOMEKeyInfo(0xFFAF, "KP_Divide")); // GDK_KP_Divide + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD0, new GNOMEKeyInfo(0xFFB0, "KP_0")); // GDK_KP_0 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD1, new GNOMEKeyInfo(0xFFB1, "KP_1")); // GDK_KP_1 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD2, new GNOMEKeyInfo(0xFFB2, "KP_2")); // GDK_KP_2 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD3, new GNOMEKeyInfo(0xFFB3, "KP_3")); // GDK_KP_3 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD4, new GNOMEKeyInfo(0xFFB4, "KP_4")); // GDK_KP_4 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD5, new GNOMEKeyInfo(0xFFB5, "KP_5")); // GDK_KP_5 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD6, new GNOMEKeyInfo(0xFFB6, "KP_6")); // GDK_KP_6 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD7, new GNOMEKeyInfo(0xFFB7, "KP_7")); // GDK_KP_7 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD8, new GNOMEKeyInfo(0xFFB8, "KP_8")); // GDK_KP_8 + keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD9, new GNOMEKeyInfo(0xFFB9, "KP_9")); // GDK_KP_9 + + keyMap.put(KeyEvent.VK_NUMPAD0, new GNOMEKeyInfo(0xFFB0, "KP_0")); // GDK_KP_0 + keyMap.put(KeyEvent.VK_NUMPAD1, new GNOMEKeyInfo(0xFFB1, "KP_1")); // GDK_KP_1 + keyMap.put(KeyEvent.VK_NUMPAD2, new GNOMEKeyInfo(0xFFB2, "KP_2")); // GDK_KP_2 + keyMap.put(KeyEvent.VK_NUMPAD3, new GNOMEKeyInfo(0xFFB3, "KP_3")); // GDK_KP_3 + keyMap.put(KeyEvent.VK_NUMPAD4, new GNOMEKeyInfo(0xFFB4, "KP_4")); // GDK_KP_4 + keyMap.put(KeyEvent.VK_NUMPAD5, new GNOMEKeyInfo(0xFFB5, "KP_5")); // GDK_KP_5 + keyMap.put(KeyEvent.VK_NUMPAD6, new GNOMEKeyInfo(0xFFB6, "KP_6")); // GDK_KP_6 + keyMap.put(KeyEvent.VK_NUMPAD7, new GNOMEKeyInfo(0xFFB7, "KP_7")); // GDK_KP_7 + keyMap.put(KeyEvent.VK_NUMPAD8, new GNOMEKeyInfo(0xFFB8, "KP_8")); // GDK_KP_8 + keyMap.put(KeyEvent.VK_NUMPAD9, new GNOMEKeyInfo(0xFFB9, "KP_9")); // GDK_KP_9 + keyMap.put(KeyEvent.VK_F1, new GNOMEKeyInfo(0xFFBE, "F1")); // GDK_F1 + keyMap.put(KeyEvent.VK_F2, new GNOMEKeyInfo(0xFFBF, "F2")); // GDK_F2 + keyMap.put(KeyEvent.VK_F3, new GNOMEKeyInfo(0xFFC0, "F3")); // GDK_F3 + keyMap.put(KeyEvent.VK_F4, new GNOMEKeyInfo(0xFFC1, "F4")); // GDK_F4 + keyMap.put(KeyEvent.VK_F5, new GNOMEKeyInfo(0xFFC2, "F5")); // GDK_F5 + keyMap.put(KeyEvent.VK_F6, new GNOMEKeyInfo(0xFFC3, "F6")); // GDK_F6 + keyMap.put(KeyEvent.VK_F7, new GNOMEKeyInfo(0xFFC4, "F7")); // GDK_F7 + keyMap.put(KeyEvent.VK_F8, new GNOMEKeyInfo(0xFFC5, "F8")); // GDK_F8 + keyMap.put(KeyEvent.VK_F9, new GNOMEKeyInfo(0xFFC6, "F9")); // GDK_F9 + keyMap.put(KeyEvent.VK_F10, new GNOMEKeyInfo(0xFFC7, "F10")); // GDK_F10 + keyMap.put(KeyEvent.VK_F11, new GNOMEKeyInfo(0xFFC8, "F11")); // GDK_F11 + keyMap.put(KeyEvent.VK_F12, new GNOMEKeyInfo(0xFFC9, "F12")); // GDK_F12 + keyMap.put(KeyEvent.VK_F13, new GNOMEKeyInfo(0xFFCA, "F13")); // GDK_F13 + keyMap.put(KeyEvent.VK_F14, new GNOMEKeyInfo(0xFFCB, "F14")); // GDK_F14 + keyMap.put(KeyEvent.VK_F15, new GNOMEKeyInfo(0xFFCC, "F15")); // GDK_F15 + keyMap.put(KeyEvent.VK_F16, new GNOMEKeyInfo(0xFFCD, "F16")); // GDK_F16 + keyMap.put(KeyEvent.VK_F17, new GNOMEKeyInfo(0xFFCE, "F17")); // GDK_F17 + keyMap.put(KeyEvent.VK_F18, new GNOMEKeyInfo(0xFFCF, "F18")); // GDK_F18 + keyMap.put(KeyEvent.VK_F19, new GNOMEKeyInfo(0xFFD0, "F19")); // GDK_F19 + keyMap.put(KeyEvent.VK_F20, new GNOMEKeyInfo(0xFFD1, "F20")); // GDK_F20 + keyMap.put(KeyEvent.VK_F21, new GNOMEKeyInfo(0xFFD2, "F21")); // GDK_F21 + keyMap.put(KeyEvent.VK_F22, new GNOMEKeyInfo(0xFFD3, "F22")); // GDK_F22 + keyMap.put(KeyEvent.VK_F23, new GNOMEKeyInfo(0xFFD4, "F23")); // GDK_F23 + keyMap.put(KeyEvent.VK_F24, new GNOMEKeyInfo(0xFFD5, "F24")); // GDK_F24 + + keyMap.put(KeyEvent.VK_SHIFT, new GNOMEKeyInfo(0xFFE2, "Shift_R")); // GDK_Shift_R + keyMap.put(KeyEvent.VK_CONTROL, new GNOMEKeyInfo(0xFFE4, "Control_R")); // GDK_Control_R + keyMap.put(KeyEvent.VK_CAPS_LOCK, new GNOMEKeyInfo(0xFFE5, "Caps_Lock")); // GDK_Caps_Lock + keyMap.put(KeyEvent.VK_META, new GNOMEKeyInfo(0xFFE8, "Meta_R")); // GDK_Meta_R + keyMap.put(KeyEvent.VK_ALT, new GNOMEKeyInfo(0xFFEA, "Alt_R")); // GDK_Alt_R + keyMap.put(KeyEvent.VK_DELETE, new GNOMEKeyInfo(0xFFFF, "Delete")); // GDK_Delete + + // Left & Right Variations, default (set above) will be right... + keyMap.put(LEFT_OFFSET + KeyEvent.VK_SHIFT, new GNOMEKeyInfo(0xFFE1, "Shift_L")); // GDK_Shift_L + keyMap.put(RIGHT_OFFSET + KeyEvent.VK_SHIFT, new GNOMEKeyInfo(0xFFE2, "Shift_R")); // GDK_Shift_R + keyMap.put(LEFT_OFFSET + KeyEvent.VK_CONTROL, new GNOMEKeyInfo(0xFFE3, "Control_L")); // GDK_Control_L + keyMap.put(RIGHT_OFFSET + KeyEvent.VK_CONTROL, new GNOMEKeyInfo(0xFFE4, "Control_R")); // GDK_Control_R + keyMap.put(LEFT_OFFSET + KeyEvent.VK_META, new GNOMEKeyInfo(0xFFE7, "Meta_L")); // GDK_Meta_L + keyMap.put(RIGHT_OFFSET + KeyEvent.VK_META, new GNOMEKeyInfo(0xFFE8, "Meta_R")); // GDK_Meta_R + keyMap.put(LEFT_OFFSET + KeyEvent.VK_ALT, new GNOMEKeyInfo(0xFFE9, "Alt_L")); // GDK_Alt_L + keyMap.put(RIGHT_OFFSET + KeyEvent.VK_ALT, new GNOMEKeyInfo(0xFFEA, "Alt_R")); // GDK_Alt_R + } + + record GNOMEKeyInfo(int gdkKeyCode, String gdkKeyString) { + } +} + diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkLayer.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkLayer.java new file mode 100644 index 000000000000..2e95580b6f55 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkLayer.java @@ -0,0 +1,34 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +public final class AtkLayer { + public static final int INVALID = 0; + public static final int BACKGROUND = 1; + public static final int CANVAS = 2; + public static final int WIDGET = 3; + public static final int MDI = 4; + public static final int POPUP = 5; + public static final int OVERLAY = 6; + public static final int WINDOW = 7; + + private AtkLayer() { + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkObject.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkObject.java new file mode 100644 index 000000000000..ce8cd4d4e34e --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkObject.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2018, Red Hat, Inc. + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.util.Locale; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; +import java.awt.event.KeyEvent; +import java.awt.event.InputEvent; +import java.awt.EventQueue; + +/** + * AtkObject: + *

+ * Java-side utility class used by the GNOME Accessibility Bridge. + *

+ * That class is used to wrap AccessibleContext Java object + * to avoid the concurrency of AWT objects. + * + * @autor Giuseppe Capaldo + */ +public class AtkObject { + + // need to fix [missing-explicit-ctor] + private AtkObject() { + } + + /** + * Returns the JMenuItem accelerator. Similar implementation is used on + * macOS, see CAccessibility.getAcceleratorText(AccessibleContext) in OpenJDK, and + * on Windows, see AccessBridge.getAccelerator(AccessibleContext) in OpenJDK. + */ + private static String getAcceleratorText(AccessibleContext ac) { + assert (EventQueue.isDispatchThread()); + + String acceleratorText = ""; + Accessible parent = ac.getAccessibleParent(); + if (parent != null) { + int indexInParent = ac.getAccessibleIndexInParent(); + Accessible child = parent.getAccessibleContext() + .getAccessibleChild(indexInParent); + if (child instanceof JMenuItem menuItem) { + KeyStroke keyStroke = menuItem.getAccelerator(); + if (keyStroke != null) { + int modifiers = keyStroke.getModifiers(); + String modifiersText = modifiers > 0 ? InputEvent.getModifiersExText(modifiers) : ""; + + int keyCode = keyStroke.getKeyCode(); + String keyCodeText = keyCode != 0 ? KeyEvent.getKeyText(keyCode) : String.valueOf(keyStroke.getKeyChar()); + + acceleratorText += modifiersText; + if (!modifiersText.isEmpty() && !keyCodeText.isEmpty()) { + acceleratorText += "+"; + } + acceleratorText += keyCodeText; + } + } + } + return acceleratorText; + } + + // JNI upcalls section + + /** + * Gets the ATK interface flags for the given accessible object. + * Called from native code via JNI. + * + * @param o the accessible object (AccessibleContext or Accessible) + * @return bitwise OR of ATK interface flags from {@link AtkInterface} + */ + private static int get_tflag_from_obj(Object o) { + return AtkUtil.invokeInSwingAndWait(() -> { + int flags = 0; + AccessibleContext ac; + + if (o instanceof AccessibleContext accessibleContext) { + ac = accessibleContext; + } else if (o instanceof Accessible accessible) { + ac = accessible.getAccessibleContext(); + } else { + return flags; + } + + if (ac.getAccessibleAction() != null) { + flags |= AtkInterface.INTERFACE_ACTION; + } + if (ac.getAccessibleComponent() != null) { + flags |= AtkInterface.INTERFACE_COMPONENT; + } + AccessibleText text = ac.getAccessibleText(); + if (text != null) { + flags |= AtkInterface.INTERFACE_TEXT; + if (text instanceof AccessibleHypertext) { + flags |= AtkInterface.INTERFACE_HYPERTEXT; + } + if (ac.getAccessibleEditableText() != null) { + flags |= AtkInterface.INTERFACE_EDITABLE_TEXT; + } + } + if (ac.getAccessibleIcon() != null) { + flags |= AtkInterface.INTERFACE_IMAGE; + } + if (ac.getAccessibleSelection() != null) { + flags |= AtkInterface.INTERFACE_SELECTION; + } + AccessibleTable table = ac.getAccessibleTable(); + if (table != null) { + flags |= AtkInterface.INTERFACE_TABLE; + } + Accessible parent = ac.getAccessibleParent(); + if (parent != null) { + AccessibleContext parentAccessibleContext = parent.getAccessibleContext(); + if (parentAccessibleContext != null) { + table = parentAccessibleContext.getAccessibleTable(); + if (table != null && table instanceof AccessibleExtendedTable) { + flags |= AtkInterface.INTERFACE_TABLE_CELL; + } + } + } + if (ac.getAccessibleValue() != null) { + flags |= AtkInterface.INTERFACE_VALUE; + } + return flags; + }, 0); + } + + /** + * Gets the parent AccessibleContext of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the parent accessible context, or null if no parent exists + */ + private static AccessibleContext get_accessible_parent(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible accessibleParent = ac.getAccessibleParent(); + if (accessibleParent == null) { + return null; + } + AccessibleContext accessibleContext = accessibleParent.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + }, null); + } + + /** + * Sets the parent of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context whose parent should be set + * @param parentAccessibleContext the new parent accessible context (must be Accessible) + */ + private static void set_accessible_parent(AccessibleContext ac, AccessibleContext parentAccessibleContext) { + AtkUtil.invokeInSwing(() -> { + if (parentAccessibleContext instanceof Accessible parentAccessible) { + ac.setAccessibleParent(parentAccessible); + } + }); + } + + /** + * Gets the accessible name of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the accessible name, with accelerator text appended, or null if no name is set + */ + private static String get_accessible_name(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + String accessibleName = ac.getAccessibleName(); + if (accessibleName == null) { + return null; + } + String acceleratorText = getAcceleratorText(ac); + if (!acceleratorText.isEmpty()) { + return accessibleName + " " + acceleratorText; + } + return accessibleName; + }, null); + } + + /** + * Sets the accessible name of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @param name the new accessible name + */ + private static void set_accessible_name(AccessibleContext ac, String name) { + AtkUtil.invokeInSwing(() -> { + ac.setAccessibleName(name); + }); + } + + /** + * Gets the accessible description of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the accessible description, or empty string if no description is set + */ + private static String get_accessible_description(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return ac.getAccessibleDescription(); + }, null); + } + + /** + * Sets the accessible description of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @param description the new accessible description + */ + private static void set_accessible_description(AccessibleContext ac, String description) { + AtkUtil.invokeInSwing(() -> { + ac.setAccessibleDescription(description); + }); + } + + /** + * Gets the number of accessible children of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the number of accessible children, or 0 if there are no children + */ + private static int get_accessible_children_count(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return ac.getAccessibleChildrenCount(); + }, 0); + } + + /** + * Gets the index of this accessible context within its parent's children. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the zero-based index in parent, or -1 if no parent exists or index cannot be determined + */ + private static int get_accessible_index_in_parent(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return ac.getAccessibleIndexInParent(); + }, -1); + } + + /** + * Gets the accessible role of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the accessible role, or null if the role cannot be determined + */ + private static AccessibleRole get_accessible_role(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return ac.getAccessibleRole(); + }, null); + } + + /** + * Gets an array of accessible states for the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return an array of accessible states, or null if no state set exists + */ + private static AccessibleState[] get_array_accessible_state(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleStateSet stateSet = ac.getAccessibleStateSet(); + if (stateSet == null) { + return null; + } else { + return stateSet.toArray(); + } + }, null); + } + + /** + * Gets the locale of the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return the locale string in the format "language_country@script@variant", or null if locale cannot be determined + */ + private static String get_locale(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + Locale l = ac.getLocale(); + String locale = l.getLanguage(); + String country = l.getCountry(); + String script = l.getScript(); + String variant = l.getVariant(); + if (!country.isEmpty()) { + locale += "_" + country; + } + if (!script.isEmpty()) { + locale += "@" + script; + } + if (!variant.isEmpty()) { + locale += "@" + variant; + } + return locale; + }, null); + } + + /** + * Gets an array of accessible relations for the given accessible context. + * Called from native code via JNI. + * + * @param ac the accessible context + * @return an array of WrapKeyAndTarget records containing relation keys and targets, + * or an empty array if no relations exist + */ + private static WrapKeyAndTarget[] get_array_accessible_relation(AccessibleContext ac) { + WrapKeyAndTarget[] d = new WrapKeyAndTarget[0]; + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleRelationSet relationSet = ac.getAccessibleRelationSet(); + if (relationSet == null) { + return d; + } else { + AccessibleRelation[] array = relationSet.toArray(); + WrapKeyAndTarget[] result = new WrapKeyAndTarget[array.length]; + for (int i = 0; i < array.length; i++) { + String key = array[i].getKey(); + Object[] objs = array[i].getTarget(); + AccessibleContext[] contexts = new AccessibleContext[objs.length]; + for (int j = 0; j < objs.length; j++) { + if (objs[j] instanceof Accessible accessible) { + contexts[j] = accessible.getAccessibleContext(); + } else { + contexts[j] = null; + } + } + result[i] = new WrapKeyAndTarget(key, contexts); + } + return result; + } + }, d); + } + + /** + * Gets the accessible child at the specified index. + * Called from native code via JNI. + * + * @param ac the parent accessible context + * @param index the zero-based index of the child + * @return the child accessible context at the given index, or null if no child exists at that index + */ + private static AccessibleContext get_accessible_child(AccessibleContext ac, int index) { + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible child = ac.getAccessibleChild(index); + if (child == null) { + return null; + } + AccessibleContext accessibleContext = child.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + }, null); + } + + /** + * A record that wraps an accessible relation key with its target accessible contexts. + * Used to pass relation information from Java to native code. + */ + private record WrapKeyAndTarget(String key, AccessibleContext[] relations) { + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkProvider.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkProvider.java new file mode 100644 index 000000000000..f5252f35d4b8 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkProvider.java @@ -0,0 +1,45 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2017 Oracle and/or its affiliates. + * Copyright (C) 2017 Fridrich Strba + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.AccessibilityProvider; + +public final class AtkProvider extends AccessibilityProvider { + + private static final String name = "org.GNOME.Accessibility.AtkWrapper"; + + public AtkProvider() { + } + + public String getName() { + return name; + } + + public void activate() { + if (isEnabled()) { + new AtkWrapper(); + } + } + + private boolean isEnabled() { + return !java.lang.Boolean.parseBoolean(System.getProperty("linux.jdk.accessibility.atkwrapper.block", "false")); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSelection.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSelection.java new file mode 100644 index 000000000000..aeafcdcb946e --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSelection.java @@ -0,0 +1,216 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Selection interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleSelection interface + * and the ATK (Accessibility Toolkit) selection interface. + */ +public class AtkSelection { + + private final WeakReference accessibleContextWeakRef; + private final WeakReference accessibleSelectionWeakRef; + + private AtkSelection(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + AccessibleSelection accessibleSelection = ac.getAccessibleSelection(); + if (accessibleSelection == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleSelection"); + } + + this.accessibleContextWeakRef = new WeakReference(ac); + this.accessibleSelectionWeakRef = new WeakReference(accessibleSelection); + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkSelection instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkSelection instance, or null if creation fails + */ + private static AtkSelection create_atk_selection(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkSelection(ac); + }, null); + } + + /** + * Adds the specified accessible child to the object's selection. + * Called from native code via JNI. + * + * @param index the index of the child in the object's list of children + * @return true if the child was successfully selected, false otherwise + */ + private boolean add_selection(int index) { + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + accessibleSelection.addAccessibleSelection(index); + return is_child_selected(index); + }, false); + } + + /** + * Clears the selection in the object so that no children in the object are selected. + * Called from native code via JNI. + * + * @return true if the selection was successfully cleared, false otherwise + */ + private boolean clear_selection() { + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + accessibleSelection.clearAccessibleSelection(); + return true; + }, false); + } + + /** + * Gets a reference to the accessible object representing the specified selected child of the object. + * Called from native code via JNI. + * + * @param index the index of the selected child in the selection + * @return the AccessibleContext of the selected child, or null if none + */ + private AccessibleContext ref_selection(int index) { + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible selectedChild = accessibleSelection.getAccessibleSelection(index); + if (selectedChild == null) { + return null; + } + AccessibleContext selectedChildAccessibleContext = selectedChild.getAccessibleContext(); + if (selectedChildAccessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(selectedChildAccessibleContext); + } + return selectedChildAccessibleContext; + }, null); + } + + /** + * Gets the number of accessible children currently selected. + * Called from native code via JNI. + * + * @return the number of currently selected children, or 0 if none are selected + */ + private int get_selection_count() { + AccessibleContext ac = accessibleContextWeakRef.get(); + if (ac == null) { + return 0; + } + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + int count = 0; + for (int i = 0; i < ac.getAccessibleChildrenCount(); i++) { + if (accessibleSelection.isAccessibleChildSelected(i)) { + count++; + } + } + return count; + }, 0); + } + + /** + * Determines if the specified child of the object is included in the object's selection. + * Called from native code via JNI. + * + * @param i the index of the child in the object's list of children + * @return true if the child is selected, false otherwise + */ + private boolean is_child_selected(int i) { + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleSelection.isAccessibleChildSelected(i); + }, false); + } + + /** + * Removes the specified child of the object from the object's selection. + * Called from native code via JNI. + * + * @param i the index of the selected child in the selection + * @return true if the child was successfully removed from the selection, false otherwise + */ + private boolean remove_selection(int i) { + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + accessibleSelection.removeAccessibleSelection(i); + return !is_child_selected(i); + }, false); + } + + /** + * Causes every child of the object to be selected if the object supports multiple selection. + * Called from native code via JNI. + * + * @return true if all children were successfully selected (object supports multiple selection), false otherwise + */ + private boolean select_all_selection() { + AccessibleContext accessibleContext = accessibleContextWeakRef.get(); + if (accessibleContext == null) { + return false; + } + AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get(); + if (accessibleSelection == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleStateSet stateSet = accessibleContext.getAccessibleStateSet(); + if (stateSet.contains(AccessibleState.MULTISELECTABLE)) { + accessibleSelection.selectAllAccessibleSelection(); + return true; + } + return false; + }, false); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSignal.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSignal.java new file mode 100644 index 000000000000..d41d43eb869b --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSignal.java @@ -0,0 +1,70 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import java.lang.annotation.Native; + +public interface AtkSignal { + @Native + int TEXT_CARET_MOVED = 0; + @Native + int TEXT_PROPERTY_CHANGED_INSERT = 1; + @Native + int TEXT_PROPERTY_CHANGED_DELETE = 2; + @Native + int TEXT_PROPERTY_CHANGED_REPLACE = 3; + @Native + int OBJECT_CHILDREN_CHANGED_ADD = 4; + @Native + int OBJECT_CHILDREN_CHANGED_REMOVE = 5; + @Native + int OBJECT_ACTIVE_DESCENDANT_CHANGED = 6; + @Native + int OBJECT_SELECTION_CHANGED = 7; + @Native + int OBJECT_VISIBLE_DATA_CHANGED = 8; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS = 9; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE = 10; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION = 11; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME = 12; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET = 13; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION = 14; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY = 15; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER = 16; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION = 17; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER = 18; + @Native + int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION = 19; + @Native + int TABLE_MODEL_CHANGED = 20; + @Native + int TEXT_PROPERTY_CHANGED = 21; +} + diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTable.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTable.java new file mode 100644 index 000000000000..c72b9cd09568 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTable.java @@ -0,0 +1,572 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Table interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleTable interface + * and the ATK (Accessibility Toolkit) table interface. + */ +public class AtkTable { + + private final WeakReference accessibleContextWeakRef; + private final WeakReference accessibleTableWeakRef; + + private AtkTable(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + AccessibleTable accessibleTable = ac.getAccessibleTable(); + if (accessibleTable == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleTable"); + } + + this.accessibleContextWeakRef = new WeakReference(ac); + this.accessibleTableWeakRef = new WeakReference(ac.getAccessibleTable()); + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkTable instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkTable instance, or null if creation fails + */ + private static AtkTable create_atk_table(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkTable(ac); + }, null); + } + + /** + * Gets an accessible context at the specified row and column in the table. + * Called from native code via JNI. + * + * @param row the row index + * @param column the column index + * @return the AccessibleContext of the cell at the specified position, or null if none + */ + private AccessibleContext ref_at(int row, int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible accessible = accessibleTable.getAccessibleAt(row, column); + if (accessible == null) { + return null; + } + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + }, null); + } + + /** + * Gets the index of the accessible child at the specified row and column. + * Called from native code via JNI. + * + * @param row the row index + * @param column the column index + * @return the child index, or -1 if no child exists at that position + */ + private int get_index_at(int row, int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) { + return accessibleExtendedTable.getAccessibleIndex(row, column); + } + Accessible child = accessibleTable.getAccessibleAt(row, column); + if (child == null) { + return -1; + } + AccessibleContext childAccessibleContext = child.getAccessibleContext(); + if (childAccessibleContext == null) { + return -1; + } + return childAccessibleContext.getAccessibleIndexInParent(); + }, -1); + } + + /** + * Gets the column index at the specified child index. + * Called from native code via JNI. + * + * @param index the child index + * @return the column index, or -1 if not available + */ + private int get_column_at_index(int index) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + int column = -1; + if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) { + column = accessibleExtendedTable.getAccessibleColumn(index); + } + return column; + }, -1); + } + + /** + * Gets the row index at the specified child index. + * Called from native code via JNI. + * + * @param index the child index + * @return the row index, or -1 if not available + */ + private int get_row_at_index(int index) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + int row = -1; + if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) { + row = accessibleExtendedTable.getAccessibleRow(index); + } + return row; + }, -1); + } + + /** + * Gets the number of columns in the table. + * Called from native code via JNI. + * + * @return the number of columns, or 0 if the table is not available + */ + private int get_n_columns() { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.getAccessibleColumnCount(); + }, 0); + } + + /** + * Gets the number of rows in the table. + * Called from native code via JNI. + * + * @return the number of rows, or 0 if the table is not available + */ + private int get_n_rows() { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.getAccessibleRowCount(); + }, 0); + } + + /** + * Gets the number of columns occupied by the accessible object at the specified row and column. + * Called from native code via JNI. + * + * @param row the row index + * @param column the column index + * @return the column extent (colspan), or 0 if not available + */ + private int get_column_extent_at(int row, int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.getAccessibleColumnExtentAt(row, column); + }, 0); + } + + /** + * Gets the number of rows occupied by the accessible object at the specified row and column. + * Called from native code via JNI. + * + * @param row the row index + * @param column the column index + * @return the row extent (rowspan), or 0 if not available + */ + private int get_row_extent_at(int row, int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.getAccessibleRowExtentAt(row, column); + }, 0); + } + + /** + * Gets the caption for the table. + * Called from native code via JNI. + * + * @return the AccessibleContext of the table caption, or null if none + */ + private AccessibleContext get_caption() { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible accessible = accessibleTable.getAccessibleCaption(); + if (accessible == null) { + return null; + } + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + }, null); + } + + /** + * Sets the caption for the table. + * Called from native code via JNI. + * + * @param a the Accessible to use as the table caption + */ + private void set_caption(Accessible a) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleTable.setAccessibleCaption(a); + }); + } + + /** + * Gets the description text of the specified column in the table. + * Called from native code via JNI. + * + * @param column an int representing a column in the table + * @return a String representing the column description, or null if the table doesn't implement + * this interface or if no description is available for the specified column + */ + private String get_column_description(int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible accessible = accessibleTable.getAccessibleColumnDescription(column); + if (accessible != null) { + AccessibleContext ac = accessible.getAccessibleContext(); + if (ac != null) { + return ac.getAccessibleDescription(); + } + } + return null; + }, null); + } + + /** + * Sets the description text of the specified column in the table. + * Called from native code via JNI. + * + * @param column an int representing a column in table + * @param description a String object representing the description text to set for the + * specified column of the table + */ + private void set_column_description(int column, String description) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + Accessible accessible = accessibleTable.getAccessibleColumnDescription(column); + if (accessible != null) { + AccessibleContext ac = accessible.getAccessibleContext(); + if (ac != null) { + ac.setAccessibleDescription(description); + } + } + }); + } + + /** + * Gets the description text of the specified row in the table. + * Called from native code via JNI. + * + * @param row an int representing a row in the table + * @return a String representing the row description, or null if the table doesn't implement + * this interface or if no description is available for the specified row + */ + private String get_row_description(int row) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible accessible = accessibleTable.getAccessibleRowDescription(row); + if (accessible != null) { + AccessibleContext ac = accessible.getAccessibleContext(); + if (ac != null) { + return ac.getAccessibleDescription(); + } + } + return null; + }, null); + } + + /** + * Sets the description text of the specified row in the table. + * Called from native code via JNI. + * + * @param row an int representing a row in table + * @param description a String object representing the description text to set for the + * specified row of the table + */ + private void set_row_description(int row, String description) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + Accessible accessible = accessibleTable.getAccessibleRowDescription(row); + if (accessible != null) { + AccessibleContext ac = accessible.getAccessibleContext(); + if (ac != null) { + ac.setAccessibleDescription(description); + } + } + }); + } + + /** + * Gets the column header at the specified column index. + * Called from native code via JNI. + * + * @param column the column index + * @return the AccessibleContext of the column header, or null if none + */ + private AccessibleContext get_column_header(int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleTable accessibleColumnHeader = accessibleTable.getAccessibleColumnHeader(); + if (accessibleColumnHeader != null) { + Accessible accessible = accessibleColumnHeader.getAccessibleAt(0, column); + if (accessible == null) { + return null; + } + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + } + return null; + }, null); + } + + /** + * Gets the row header at the specified row index. + * Called from native code via JNI. + * + * @param row the row index + * @return the AccessibleContext of the row header, or null if none + */ + private AccessibleContext get_row_header(int row) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleTable accessibleRowHeader = accessibleTable.getAccessibleRowHeader(); + if (accessibleRowHeader != null) { + Accessible accessible = accessibleRowHeader.getAccessibleAt(row, 0); + if (accessible == null) { + return null; + } + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + } + return null; + }, null); + } + + /** + * Gets the summary description of the table. + * Called from native code via JNI. + * + * @return the AccessibleContext of the table summary, or null if none + */ + private AccessibleContext get_summary() { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Accessible accessibleSummary = accessibleTable.getAccessibleSummary(); + if (accessibleSummary == null) { + return null; + } + AccessibleContext accessibleContext = accessibleSummary.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + return accessibleContext; + }, null); + } + + /** + * Sets the summary description of the table. + * Called from native code via JNI. + * + * @param a the Accessible to use as the table summary + */ + private void set_summary(Accessible a) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleTable.setAccessibleSummary(a); + }); + } + + /** + * Gets the selected columns in the table. + * Called from native code via JNI. + * + * @return an array of column indices that are selected, or null if none are selected + */ + private int[] get_selected_columns() { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.getSelectedAccessibleColumns(); + }, null); + } + + /** + * Gets the selected rows in the table. + * Called from native code via JNI. + * + * @return an array of row indices that are selected, or null if none are selected + */ + private int[] get_selected_rows() { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.getSelectedAccessibleRows(); + }, null); + } + + /** + * Determines whether the specified column is selected. + * Called from native code via JNI. + * + * @param column the column index + * @return true if the column is selected, false otherwise + */ + private boolean is_column_selected(int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.isAccessibleColumnSelected(column); + }, false); + } + + /** + * Determines whether the specified row is selected. + * Called from native code via JNI. + * + * @param row the row index + * @return true if the row is selected, false otherwise + */ + private boolean is_row_selected(int row) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.isAccessibleRowSelected(row); + }, false); + } + + /** + * Determines whether the accessible object at the specified row and column is selected. + * Called from native code via JNI. + * + * @param row the row index + * @param column the column index + * @return true if the cell at the specified position is selected, false otherwise + */ + private boolean is_selected(int row, int column) { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleTable.isAccessibleSelected(row, column); + }, false); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTableCell.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTableCell.java new file mode 100644 index 000000000000..3828377841e5 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTableCell.java @@ -0,0 +1,171 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK TableCell interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleTable interface + * and the ATK (Accessibility Toolkit) table cell interface, representing + * individual cells within an accessible table. + */ +public class AtkTableCell { + private final WeakReference accessibleTableWeakRef; + private final int row; // the row index of this table cell, used by native code + private final int rowSpan; // the number of rows occupied by this table cell, used by native code + private final int column; // the column index of this table cell, used by native code + private final int columnSpan; // the number of columns occupied by this table cell, used by native code + + private AtkTableCell(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + Accessible accessibleParent = ac.getAccessibleParent(); + if (accessibleParent == null) { + throw new IllegalArgumentException("AccessibleContext must have accessibleParent"); + } + + AccessibleContext parentAccessibleContext = accessibleParent.getAccessibleContext(); + if (parentAccessibleContext == null) { + throw new IllegalArgumentException("AccessibleContext must have accessibleParent with AccessibleContext"); + } + + AccessibleTable accessibleTable = parentAccessibleContext.getAccessibleTable(); + if (accessibleTable == null) { + throw new IllegalArgumentException("AccessibleContext must have accessibleParent with AccessibleTable"); + } + accessibleTableWeakRef = new WeakReference(accessibleTable); + + if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) { + int index = ac.getAccessibleIndexInParent(); + row = accessibleExtendedTable.getAccessibleRow(index); + column = accessibleExtendedTable.getAccessibleColumn(index); + } else { + row = -1; + column = -1; + } + + rowSpan = accessibleTable.getAccessibleRowExtentAt(row, column); + columnSpan = accessibleTable.getAccessibleColumnExtentAt(row, column); + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkTableCell instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext representing a table cell + * @return a new AtkTableCell instance, or null if creation fails + */ + private static AtkTableCell create_atk_table_cell(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkTableCell(ac); + }, null); + } + + /** + * Gets the table containing this cell. + * Called from native code via JNI. + * + * @return the AccessibleTable containing this cell, or null if unavailable + */ + private AccessibleTable get_table() { + if (accessibleTableWeakRef == null) { + return null; + } + return accessibleTableWeakRef.get(); + } + + /** + * Returns the column headers as an array of AccessibleContext objects. + * Called from native code via JNI. + * + * @return an array of AccessibleContext objects representing the column headers, + * or null if column headers are not available + */ + private AccessibleContext[] get_accessible_column_header() { + if (accessibleTableWeakRef == null) { + return null; + } + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + AccessibleTable iteration = accessibleTable.getAccessibleColumnHeader(); + if (iteration != null) { + int length = iteration.getAccessibleColumnCount(); + AccessibleContext[] result = new AccessibleContext[length]; + for (int i = 0; i < length; i++) { + Accessible accessible = iteration.getAccessibleAt(0, i); + if (accessible != null) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + result[i] = accessibleContext; + } + } + } + return result; + } + return null; + }, null); + } + + /** + * Returns the row headers as an array of AccessibleContext objects. + * Called from native code via JNI. + * + * @return an array of AccessibleContext objects representing the row headers, + * or null if row headers are not available + */ + private AccessibleContext[] get_accessible_row_header() { + if (accessibleTableWeakRef == null) { + return null; + } + return AtkUtil.invokeInSwingAndWait(() -> { + AccessibleTable accessibleTable = accessibleTableWeakRef.get(); + if (accessibleTable == null) { + return null; + } + AccessibleTable iteration = accessibleTable.getAccessibleRowHeader(); + if (iteration != null) { + int length = iteration.getAccessibleRowCount(); + AccessibleContext[] result = new AccessibleContext[length]; + for (int i = 0; i < length; i++) { + Accessible accessible = iteration.getAccessibleAt(0, i); + if (accessible != null) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + result[i] = accessibleContext; + } + } + } + return result; + } + return null; + }, null); + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkText.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkText.java new file mode 100644 index 000000000000..f417719020ef --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkText.java @@ -0,0 +1,1006 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.text.*; +import java.awt.Rectangle; +import java.awt.Point; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; + +/** + * The ATK Text interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleText interface + * and the ATK (Accessibility Toolkit) text interface used by assistive + * technologies. + */ +public class AtkText { + + private final WeakReference accessibleContextWeakRef; + private final WeakReference accessibleTextWeakRef; + private final WeakReference accessibleEditableTextWeakRef; + + protected AtkText(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + this.accessibleContextWeakRef = new WeakReference(ac); + + AccessibleText accessibleText = ac.getAccessibleText(); + if (accessibleText == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleText"); + } + this.accessibleTextWeakRef = new WeakReference(accessibleText); + + this.accessibleEditableTextWeakRef = new WeakReference(ac.getAccessibleEditableText()); + } + + public static int getRightStart(int start) { + return Math.max(start, 0); + } + + /** + * Returns a valid end position based on start, end, and text length constraints. + * + * @param start The starting position in the text, which may be invalid. + * @param end The ending position, which may be undefined (-1) or invalid. + * @param charCountInText The total character count in the text. + * @return A corrected end position. + */ + public static int getRightEnd(int start, int end, int charCountInText) { + int rightStart = getRightStart(start); + + // unique case : the end is undefined or an error happened, + // let's define the right end as charCountInText + if (end == -1) { + return charCountInText; + } + + if (end < 0) { // we processed end == -1 in another statement + return rightStart; + } else if (end < rightStart) { + return rightStart; + } else if (charCountInText < end) { + return charCountInText; + } else { + return end; + } + } + + private int getCurrentWordStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + int length = text.length(); + if (offset < 0) offset = 0; + if (offset >= length) offset = length - 1; + + if (!Character.isLetter(text.codePointAt(offset))) { + return BreakIterator.DONE; + } + + BreakIterator words = BreakIterator.getWordInstance(); + words.setText(text); + + int wordEnd = words.following(offset); + if (wordEnd == BreakIterator.DONE) return BreakIterator.DONE; + + int wordStart = words.previous(); + return wordStart; + } + + // Currently unused. + private int getNextWordStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + int length = text.length(); + + if (offset < 0) { + offset = 0; + } else if (offset > length) { + offset = length; + } + + BreakIterator words = BreakIterator.getWordInstance(); + words.setText(text); + int segmentStart = words.following(offset); + int segmentEnd = words.next(); + + while (segmentEnd != BreakIterator.DONE) { + for (int i = segmentStart; i < segmentEnd; i++) { + if (Character.isLetter(text.codePointAt(i))) { + return segmentStart; + } + } + segmentStart = segmentEnd; + segmentEnd = words.next(); + } + + return BreakIterator.DONE; + } + + private int getPreviousWordStart(int offset, String text) { + if (text == null || text.isEmpty()) return BreakIterator.DONE; + + int length = text.length(); + if (offset < 0) offset = 0; + if (offset > length) offset = length; + + BreakIterator words = BreakIterator.getWordInstance(); + words.setText(text); + + int segmentEnd = words.preceding(offset); + if (segmentEnd == BreakIterator.DONE) return BreakIterator.DONE; + + int segmentStart = words.previous(); + + while (segmentStart != BreakIterator.DONE) { + for (int i = segmentStart; i < segmentEnd; i++) { + if (Character.isLetter(text.codePointAt(i))) { + return segmentStart; + } + } + segmentEnd = segmentStart; + segmentStart = words.previous(); + } + + return BreakIterator.DONE; + } + + private int getWordEndFromStart(int start, String text) { + if (start == BreakIterator.DONE || text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + BreakIterator words = BreakIterator.getWordInstance(); + words.setText(text); + + int end = words.following(start); + return (end == BreakIterator.DONE) ? text.length() : end; + } + + /** + * Gets the start position of the sentence that contains the given offset. + * + * @param offset The character offset within the text + * @param text The full text to search within + * @return The start position of the current sentence, or BreakIterator.DONE if not found + */ + private int getCurrentSentenceStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + int length = text.length(); + if (offset < 0) offset = 0; + if (offset >= length) offset = length - 1; + + BreakIterator sentences = BreakIterator.getSentenceInstance(); + sentences.setText(text); + + int sentenceEnd = sentences.following(offset); + if (sentenceEnd == BreakIterator.DONE) return BreakIterator.DONE; + + int sentenceStart = sentences.previous(); + return sentenceStart; + } + + /** + * Gets the start position of the next sentence after the given offset. + * + * @param offset The character offset within the text + * @param text The full text to search within + * @return The start position of the next sentence, or BreakIterator.DONE if not found + */ + private int getNextSentenceStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + BreakIterator sentences = BreakIterator.getSentenceInstance(); + sentences.setText(text); + + return sentences.following(offset); + } + + /** + * Gets the start position of the previous sentence before the given offset. + * + * @param offset The character offset within the text + * @param text The full text to search within + * @return The start position of the previous sentence, or BreakIterator.DONE if not found + */ + private int getPreviousSentenceStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + BreakIterator sentences = BreakIterator.getSentenceInstance(); + sentences.setText(text); + + return sentences.preceding(offset); + } + + /** + * Gets the end position of a sentence given its start position. + * + * @param start The start position of the sentence + * @param text The full text to search within + * @return The end position of the sentence, or BreakIterator.DONE if not found + */ + private int getSentenceEndFromStart(int start, String text) { + if (start == BreakIterator.DONE || text == null || text.isEmpty()) { + return BreakIterator.DONE; + } + + BreakIterator sentences = BreakIterator.getSentenceInstance(); + sentences.setText(text); + + int end = sentences.following(start); + return (end == BreakIterator.DONE) ? text.length() : end; + } + + /** + * Gets the start position of the line that contains the given offset. + * + * @param offset The character offset within the text + * @param text The full text to search within + * @return The start position of the current line + */ + private int getCurrentLineStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return 0; + } + + int length = text.length(); + if (offset < 0) offset = 0; + if (offset >= length) offset = length - 1; + + int pos = offset; + while (pos > 0) { + if (text.charAt(pos - 1) == '\n') { + return pos; + } + pos--; + } + return 0; + } + + private int getNextLineStart(int offset, String str) { + int max = str.length(); + while (offset < max) { + if (str.charAt(offset) == '\n') + return offset + 1; + offset += 1; + } + return offset; + } + + private int getPreviousLineStart(int offset, String str) { + offset -= 2; + while (offset >= 0) { + if (str.charAt(offset) == '\n') + return offset + 1; + offset -= 1; + } + return 0; + } + + /** + * Gets the end position of a line given its start position. + * + * @param start The start position of the line + * @param text The full text to search within + * @return The end position of the line (position of newline or end of text) + */ + private int getLineEndFromStart(int start, String text) { + if (text == null || text.isEmpty()) { + return 0; + } + + int length = text.length(); + if (start < 0) start = 0; + if (start >= length) return length; + + int pos = start; + while (pos < length) { + if (text.charAt(pos) == '\n') { + return pos; + } + pos++; + } + return length; + } + + private int getNextLineEnd(int offset, String str) { + int max = str.length(); + offset += 1; + while (offset < max) { + if (str.charAt(offset) == '\n') + return offset; + offset += 1; + } + return offset; + } + + private int getPreviousLineEnd(int offset, String str) { + offset -= 1; + while (offset >= 0) { + if (str.charAt(offset) == '\n') + return offset; + offset -= 1; + } + return 0; + } + + /** + * Gets the start position of the paragraph that contains the given offset. + * Paragraphs are defined as text blocks separated by newlines. + * + * @param offset The character offset within the text + * @param text The full text to search within + * @return The start position of the current paragraph + */ + private int getCurrentParagraphStart(int offset, String text) { + if (text == null || text.isEmpty()) { + return 0; + } + + int length = text.length(); + if (offset < 0) offset = 0; + if (offset >= length) offset = length - 1; + + // Search backwards from offset to find the start of the current paragraph + int pos = offset; + while (pos > 0) { + if (text.charAt(pos - 1) == '\n') { + return pos; + } + pos--; + } + return 0; + } + + private int getNextParagraphStart(int offset, String str) { + int max = str.length(); + while (offset < max) { + if (offset < max - 1 && str.charAt(offset) == '\n' && str.charAt(offset + 1) == '\n') { + return offset + 2; + } + offset += 1; + } + return offset; + } + + private int getPreviousParagraphStart(int offset, String str) { + offset -= 2; + while (offset >= 0) { + if (offset > 0 && str.charAt(offset) == '\n' && str.charAt(offset - 1) == '\n') { + return offset + 1; + } + offset -= 1; + } + return 0; + } + + /** + * Gets the end position of a paragraph given its start position. + * Paragraphs are defined as text blocks separated by newlines. + * + * @param start The start position of the paragraph + * @param text The full text to search within + * @return The end position of the paragraph (position of newline or end of text) + */ + private int getParagraphEndFromStart(int start, String text) { + if (text == null || text.isEmpty()) { + return 0; + } + + int length = text.length(); + if (start < 0) start = 0; + if (start >= length) return length; + + int pos = start; + while (pos < length) { + if (text.charAt(pos) == '\n') { + return pos; + } + pos++; + } + return length; + } + + @Deprecated + private StringSequence getTextAtOffset(int offset, + int boundaryType) { + int characterCount = get_character_count(); + if (offset < 0 || offset > characterCount) { + return null; + } + + switch (boundaryType) { + case AtkTextBoundary.CHAR: { + if (offset == characterCount) { + return null; + } + String str = get_text(offset, offset + 1); + return new StringSequence(str, offset, offset + 1); + } + case AtkTextBoundary.WORD_START: + case AtkTextBoundary.WORD_END: { + // The returned string will contain the word at the offset if the offset is + // inside a word and will contain the word before the offset if the offset is not inside a word + if (offset == characterCount) { + return null; + } + + String fullText = get_text(0, characterCount); + + int start = getCurrentWordStart(offset, fullText); + int end = getWordEndFromStart(start, fullText); + if (start == BreakIterator.DONE || end == BreakIterator.DONE) { + start = getPreviousWordStart(offset, fullText); + end = getWordEndFromStart(start, fullText); + if (start == BreakIterator.DONE || end == BreakIterator.DONE) { + return null; + } + } + String str = get_text(start, end); + return new StringSequence(str, start, end); + } + case AtkTextBoundary.SENTENCE_START: + case AtkTextBoundary.SENTENCE_END: { + // The returned string will contain the sentence at the offset if the offset + // is inside a sentence and will contain the sentence before the offset if the offset is not inside a sentence. + if (offset == characterCount) { + return null; + } + + String fullText = get_text(0, characterCount); + + int start = getCurrentSentenceStart(offset, fullText); + int end = getSentenceEndFromStart(start, fullText); + if (start == BreakIterator.DONE || end == BreakIterator.DONE) { + start = getPreviousSentenceStart(offset, fullText); + end = getSentenceEndFromStart(start, fullText); + if (start == BreakIterator.DONE || end == BreakIterator.DONE) { + return null; + } + } + String str = get_text(start, end); + return new StringSequence(str, start, end); + } + case AtkTextBoundary.LINE_START: + case AtkTextBoundary.LINE_END: { + // Returned string is from the line start at or before the offset to the line start after the offset. + if (offset == characterCount) { + return null; + } + + String fullText = get_text(0, characterCount); + + int start = getCurrentLineStart(offset, fullText); + int end = getLineEndFromStart(start, fullText); + + String str = get_text(start, end); + return new StringSequence(str, start, end); + } + default: { + return null; + } + } + } + + // JNI upcalls section + + /** + * Factory method to create an AtkText instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkText instance, or null if creation fails + */ + private static AtkText create_atk_text(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkText(ac); + }, null); + } + + /** + * Gets the specified text from start to end offset. + * Called from native code via JNI. + * + * @param start a starting character offset within the text + * @param end an ending character offset within the text, or -1 for the end of the string + * @return a string containing the text from start up to, but not including end, or null if retrieval fails + */ + private String get_text(int start, int end) { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + int rightStart = getRightStart(start); + int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount()); + + if (accessibleText instanceof AccessibleExtendedText accessibleExtendedText) { + return accessibleExtendedText.getTextRange(rightStart, rightEnd); + } + StringBuilder builder = new StringBuilder(); + for (int i = rightStart; i <= rightEnd - 1; i++) { + String textAtIndex = accessibleText.getAtIndex(AccessibleText.CHARACTER, i); + builder.append(textAtIndex); + } + return builder.toString(); + }, null); + } + + /** + * Gets the character at the specified offset. + * Called from native code via JNI. + * + * @param offset a character offset within the text + * @return the character at the specified offset, or '\0' in case of failure + */ + private char get_character_at_offset(int offset) { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return '\0'; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + String textAtOffset = accessibleText.getAtIndex(AccessibleText.CHARACTER, offset); + if (textAtOffset == null || textAtOffset.isEmpty()) { + return '\0'; + } + return textAtOffset.charAt(0); + }, '\0'); + } + + /** + * Gets the offset of the position of the caret (cursor). + * Called from native code via JNI. + * + * @return the character offset of the position of the caret, or -1 if the caret is not located + * inside the element or in the case of any other failure + */ + private int get_caret_offset() { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleText.getCaretPosition(); + }, -1); + } + + /** + * Gets the bounding box containing the glyph representing the character at a particular text offset. + * Called from native code via JNI. + * + * @param offset the offset of the text character for which bounding information is required + * @param coordType specifies whether coordinates are relative to the screen or widget window + * @return a Rectangle containing the bounding box (x, y, width, height), or null if the extent + * cannot be obtained. Returns null if all coordinates are set to -1. + */ + private Rectangle get_character_extents(int offset, int coordType) { + AccessibleContext ac = accessibleContextWeakRef.get(); + if (ac == null) { + return null; + } + + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Rectangle characterBounds = accessibleText.getCharacterBounds(offset); + if (characterBounds == null) { + return null; + } + Point componentLocation = AtkComponent.getLocationByCoordinateType(ac, coordType); + if (componentLocation == null) { + return null; + } + characterBounds.x += componentLocation.x; + characterBounds.y += componentLocation.y; + return characterBounds; + }, null); + } + + /** + * Gets the character count. + * Called from native code via JNI. + * + * @return the number of characters, or -1 in case of failure + */ + private int get_character_count() { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return 0; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleText.getCharCount(); + }, 0); + } + + /** + * Gets the offset of the character located at coordinates @x and @y. @x and @y + * are interpreted as being relative to the screen or this widget's window + * depending on @coords. + * + * @param x int screen x-position of character + * @param y int screen y-position of character + * @param coord_type int specify whether coordinates are relative to the screen or + * widget window + * @return the offset to the character which is located at the specified + * @x and @y coordinates or -1 in case of failure. + */ + private int get_offset_at_point(int x, int y, int coord_type) { + AccessibleContext ac = accessibleContextWeakRef.get(); + if (ac == null) { + return -1; + } + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Point componentLocation = AtkComponent.getLocationByCoordinateType(ac, coord_type); + if (componentLocation == null) { + return -1; + } + return accessibleText.getIndexAtPoint(new Point(x - componentLocation.x, y - componentLocation.y)); + }, -1); + } + + /** + * Gets the bounding box for text within the specified range. + * Called from native code via JNI. + * + * @param start the offset of the first text character for which boundary information is required + * @param end the offset of the text character after the last character for which boundary information is required + * @param coordType specifies whether coordinates are relative to the screen or widget window + * @return a Rectangle filled in with the bounding box, or null if the extents cannot be obtained. + * Returns null if all rectangle fields are set to -1. + */ + private Rectangle get_range_extents(int start, int end, int coordType) { + AccessibleContext ac = accessibleContextWeakRef.get(); + if (ac == null) { + return null; + } + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + if (accessibleText instanceof AccessibleExtendedText accessibleExtendedText) { + final int rightStart = getRightStart(start); + final int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount()); + + Rectangle textBounds = accessibleExtendedText.getTextBounds(rightStart, rightEnd); + if (textBounds == null) { + return null; + } + Point componentLocation = AtkComponent.getLocationByCoordinateType(ac, coordType); + if (componentLocation == null) { + return null; + } + textBounds.x += componentLocation.x; + textBounds.y += componentLocation.y; + return textBounds; + } + return null; + }, null); + } + + /** + * Gets the number of selected regions. + * + * @param text an #AtkText + * @return The number of selected regions, or -1 in the case of failure. + */ + private int get_n_selections() { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return -1; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + String selectedText = accessibleText.getSelectedText(); + if (selectedText != null) { + return 1; + } + return 0; + }, -1); + } + + /** + * Gets the text from the specified selection. + * Called from native code via JNI. + * + * @return a StringSequence containing the selected text and its start and end offsets, + * or null if there is no selection or retrieval fails + */ + private StringSequence get_selection() { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + int start = accessibleText.getSelectionStart(); + int end = accessibleText.getSelectionEnd(); + String text = accessibleText.getSelectedText(); + if (text == null) { + return null; + } + return new StringSequence(text, start, end); + }, null); + } + + /** + * Adds a selection bounded by the specified offsets. + * Called from native code via JNI. + * + * @param start the starting character offset of the selected region + * @param end the offset of the first character after the selected region + * @return true if successful, false otherwise. Note that Java AccessibleText only supports + * a single selection, so this will return false if a selection already exists. + */ + private boolean add_selection(int start, int end) { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return false; + } + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + + // Java AccessibleText only supports a single selection, so reject if one already exists + if (accessibleEditableText == null || get_n_selections() > 0) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + final int rightStart = getRightStart(start); + final int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount()); + + return set_selection(0, rightStart, rightEnd); + }, false); + } + + /** + * Removes the specified selection. + * Called from native code via JNI. + * + * @param selectionNum the selection number. The selected regions are assigned numbers + * that correspond to how far the region is from the start of the text. + * Since Java only supports a single selection, only 0 is valid. + * @return true if successful, false otherwise + */ + private boolean remove_selection(int selectionNum) { + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null || selectionNum > 0) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + accessibleEditableText.selectText(0, 0); + return true; + }, false); + } + + /** + * Changes the start and end offset of the specified selection. + * Called from native code via JNI. + * + * @param selectionNum the selection number. The selected regions are assigned numbers + * that correspond to how far the region is from the start of the text. + * Since Java only supports a single selection, only 0 is valid. + * @param start the new starting character offset of the selection + * @param end the new end position (offset immediately past) of the selection + * @return true if successful, false otherwise + */ + private boolean set_selection(int selectionNum, int start, int end) { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return false; + } + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null || selectionNum > 0) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + final int rightStart = getRightStart(start); + final int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount()); + + accessibleEditableText.selectText(rightStart, rightEnd); + return true; + }, false); + } + + /** + * Sets the caret (cursor) position to the specified offset. + * Called from native code via JNI. + * + * @param offset the character offset of the new caret position + * @return true if successful, false otherwise + */ + private boolean set_caret_offset(int offset) { + AccessibleText accessibleText = accessibleTextWeakRef.get(); + if (accessibleText == null) { + return false; + } + AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get(); + if (accessibleEditableText == null) { + return false; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + int rightOffset = getRightEnd(0, offset, accessibleText.getCharCount()); + accessibleEditableText.selectText(rightOffset, rightOffset); + return true; + }, false); + } + + /** + * @param offset Position. + * @param boundaryType An AtkTextBoundary. + * @return A newly allocated string containing the text at offset bounded by the specified boundary_type. + * @deprecated Please use get_string_at_offset() instead. + *

+ * Returns a newly allocated string containing the text at offset bounded by the specified boundary_type. + */ + @Deprecated + private StringSequence get_text_at_offset(int offset, int boundaryType) { + return getTextAtOffset(offset, boundaryType); + } + + /** + * @param offset Position. + * @param boundaryType An AtkTextBoundary. + * @return A newly allocated string containing the text before offset bounded by the specified boundary_type + * @deprecated Please use get_string_at_offset() instead. + *

+ * Returns a newly allocated string containing the text before offset bounded by the specified boundary_type. + */ + @Deprecated + private StringSequence get_text_before_offset(int offset, int boundaryType) { + // TODO: Implement if required. + return getTextAtOffset(offset, boundaryType); + } + + /** + * @param offset Position. + * @param boundaryType An AtkTextBoundary. + * @return A newly allocated string containing the text after offset bounded by the specified boundary_type. + * @deprecated Please use get_string_at_offset() instead. + *

+ * Returns newly allocated string containing the text after offset bounded by the specified boundary_type. + */ + @Deprecated + private StringSequence get_text_after_offset(int offset, int boundaryType) { + // TODO: Implement if required. + return getTextAtOffset(offset, boundaryType); + } + + /** + * Gets a portion of the text exposed through an {@link AtkText} according to a given + * offset and a specific granularity, along with the start and end offsets defining the + * boundaries of such a portion of text. + * + * @param offset The position in the text where the extraction starts. + * @param granularity The granularity of the text to extract, which can be one of the following: + * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_CHAR}: returns the character at the offset. + * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_WORD}: returns the word that contains the offset. + * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_SENTENCE}: returns the sentence that contains the offset. + * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_LINE}: returns the line that contains the offset. + * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_PARAGRAPH}: returns the paragraph that contains the offset. + * @param start_offset (out) The starting character offset of the returned string, or -1 if there is an error (e.g., invalid offset, not implemented). + * @param end_offset (out) The offset of the first character after the returned string, or -1 in the case of an error (e.g., invalid offset, not implemented). + * @return A newly allocated string containing the text at the specified offset, bounded by the specified granularity. + * The caller is responsible for freeing the returned string using {@code g_free()}. + * Returns {@code null} if the offset is invalid or no implementation is available. + * @since 2.10 (in atk) + */ + private StringSequence get_string_at_offset(int offset, int granularity) { + int characterCount = get_character_count(); + if (offset < 0 || offset >= characterCount) { + return null; + } + + switch (granularity) { + case AtkTextGranularity.CHAR: { + String resultText = get_text(offset, offset + 1); + return new StringSequence(resultText, offset, offset + 1); + } + case AtkTextGranularity.WORD: { + // Granularity is defined by the boundaries of a word, + // starting at the beginning of the current word and finishing + // at the beginning of the following one, if present. + String fullText = get_text(0, characterCount); + + int start = getCurrentWordStart(offset, fullText); + int end = getWordEndFromStart(start, fullText); + if (start == BreakIterator.DONE || end == BreakIterator.DONE) { + return null; + } else { + String resultText = get_text(start, end); + return new StringSequence(resultText, start, end); + } + } + case AtkTextGranularity.SENTENCE: { + // Granularity is defined by the boundaries of a sentence, + // starting at the beginning of the current sentence and finishing + // at the end of the current sentence. + String fullText = get_text(0, characterCount); + + int start = getCurrentSentenceStart(offset, fullText); + int end = getSentenceEndFromStart(start, fullText); + if (start == BreakIterator.DONE || end == BreakIterator.DONE) { + return null; + } else { + String resultText = get_text(start, end); + return new StringSequence(resultText, start, end); + } + } + case AtkTextGranularity.LINE: { + // Granularity is defined by the boundaries of a line, + // starting at the beginning of the current line and finishing + // at the beginning of the following one, if present. + String fullText = get_text(0, characterCount); + + int start = getCurrentLineStart(offset, fullText); + int end = getLineEndFromStart(start, fullText); + + String resultText = get_text(start, end); + return new StringSequence(resultText, start, end); + } + case AtkTextGranularity.PARAGRAPH: { + // Granularity is defined by the boundaries of a paragraph, + // starting at the beginning of the current paragraph and finishing + // at the end of the current paragraph (newline or end of text). + String fullText = get_text(0, characterCount); + + int start = getCurrentParagraphStart(offset, fullText); + int end = getParagraphEndFromStart(start, fullText); + + String resultText = get_text(start, end); + return new StringSequence(resultText, start, end); + } + default: { + return null; + } + } + } + + private record StringSequence(String str, int start_offset, int end_offset) { + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextBoundary.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextBoundary.java new file mode 100644 index 000000000000..a99eb053e196 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextBoundary.java @@ -0,0 +1,34 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +public final class AtkTextBoundary { + public static final int CHAR = 0; + public static final int WORD_START = 1; + public static final int WORD_END = 2; + public static final int SENTENCE_START = 3; + public static final int SENTENCE_END = 4; + public static final int LINE_START = 5; + public static final int LINE_END = 6; + + private AtkTextBoundary() { + } +} + diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextGranularity.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextGranularity.java new file mode 100644 index 000000000000..256bafc608b1 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextGranularity.java @@ -0,0 +1,31 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +public final class AtkTextGranularity { + public static final int CHAR = 0; + public static final int WORD = 1; + public static final int SENTENCE = 2; + public static final int LINE = 3; + public static final int PARAGRAPH = 4; + + private AtkTextGranularity() { + } +} \ No newline at end of file diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkUtil.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkUtil.java new file mode 100644 index 000000000000..f0e3e3def191 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkUtil.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018, Red Hat, Inc. + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.GNOME.Accessibility; + +import javax.swing.*; +import java.util.concurrent.*; + +import sun.util.logging.PlatformLogger; + +/** + * Utility class for safely executing code on the event dispatching thread. + * Is used to wrap the callback on Java object to avoid the concurrency of AWT objects. + * + * @autor Giuseppe Capaldo + */ +public final class AtkUtil { + private static final PlatformLogger log = PlatformLogger.getLogger("org.GNOME.Accessibility.AtkUtil"); + + private AtkUtil() { + } + + /** + * Executes a Callable on event dispatching thread and returns its result. + * If called from the EDT, it executes the function directly. + * Otherwise, it schedules asynchronous execution on the EDT and waits for the result. + * + * @param function The Callable task to execute. + * @param d A default value to return if an exception occurs. + * @param The return type of the Callable. + * @return The result of the Callable, or the default value in case of an exception. + */ + public static T invokeInSwingAndWait(Callable function, T d) { + if (SwingUtilities.isEventDispatchThread()) { + // We are already running in the EDT, we can call it directly + try { + return function.call(); + } catch (Exception ex) { + if (log.isLoggable(PlatformLogger.Level.WARNING)) { + log.severe("Error occurred while executing function. Returning default value.", ex); + } + return d; + } + } + + RunnableFuture wf = new FutureTask<>(function); + SwingUtilities.invokeLater(wf); + try { + return wf.get(); + } catch (InterruptedException | ExecutionException ex) { + if (log.isLoggable(PlatformLogger.Level.WARNING)) { + log.severe("Swing task execution interrupted or failed, returning default value.", ex); + } + return d; + } + } + + /** + * Executes a Runnable on the event dispatching thread. + * If called from the EDT, it runs the function directly. + * Otherwise, it executed asynchronously on the AWT event dispatching thread. + * + * @param function The Runnable task to execute. + */ + public static void invokeInSwing(Runnable function) { + if (SwingUtilities.isEventDispatchThread()) { + // We are already running in the EDT, we can call it directly + function.run(); + return; + } + + SwingUtilities.invokeLater(function); + } + +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkValue.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkValue.java new file mode 100644 index 000000000000..de1c68b5b4a1 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkValue.java @@ -0,0 +1,152 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.lang.ref.WeakReference; +import java.awt.EventQueue; +import java.lang.Double; + +/** + * The ATK Value interface implementation for Java accessibility. + *

+ * This class provides a bridge between Java's AccessibleValue interface + * and the ATK (Accessibility Toolkit) value interface, enabling access to + * numeric values and their ranges for accessible objects. + */ +public class AtkValue { + private final WeakReference accessibleValueWeakRef; + + private AtkValue(AccessibleContext ac) { + assert EventQueue.isDispatchThread(); + + AccessibleValue accessibleValue = ac.getAccessibleValue(); + if (accessibleValue == null) { + throw new IllegalArgumentException("AccessibleContext must have AccessibleValue"); + } + + this.accessibleValueWeakRef = new WeakReference(accessibleValue); + } + + /* JNI upcalls section */ + + /** + * Factory method to create an AtkValue instance from an AccessibleContext. + * Called from native code via JNI. + * + * @param ac the AccessibleContext to wrap + * @return a new AtkValue instance, or null if creation fails + */ + private static AtkValue create_atk_value(AccessibleContext ac) { + return AtkUtil.invokeInSwingAndWait(() -> { + return new AtkValue(ac); + }, null); + } + + /** + * Gets the current value of this object. + * Called from native code via JNI. + * + * @return a Number representing the current accessible value, or null if the value + * is unavailable or the object doesn't implement this interface + */ + private Number get_current_value() { + AccessibleValue accessibleValue = accessibleValueWeakRef.get(); + if (accessibleValue == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + return accessibleValue.getCurrentAccessibleValue(); + }, null); + } + + /** + * Gets the maximum value of this object. + * Called from native code via JNI. + * + * @return a Double representing the maximum accessible value, or null if the value + * is unavailable or the object doesn't implement this interface + */ + private Double get_maximum_value() { + AccessibleValue accessibleValue = accessibleValueWeakRef.get(); + if (accessibleValue == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Number max = accessibleValue.getMaximumAccessibleValue(); + if (max == null) { + return null; + } + return Double.valueOf(max.doubleValue()); + }, null); + } + + /** + * Gets the minimum value of this object. + * Called from native code via JNI. + * + * @return a Double representing the minimum accessible value, or null if the value + * is unavailable or the object doesn't implement this interface + */ + private Double get_minimum_value() { + AccessibleValue accessibleValue = accessibleValueWeakRef.get(); + if (accessibleValue == null) { + return null; + } + + return AtkUtil.invokeInSwingAndWait(() -> { + Number min = accessibleValue.getMinimumAccessibleValue(); + if (min == null) { + return null; + } + return Double.valueOf(min.doubleValue()); + }, null); + } + + /** + * Sets the current value of this object. + * Called from native code via JNI. + * + * @param n the Number value to set as the current accessible value + */ + private void set_value(Number n) { + AccessibleValue accessibleValue = accessibleValueWeakRef.get(); + if (accessibleValue == null) { + return; + } + + AtkUtil.invokeInSwing(() -> { + accessibleValue.setCurrentAccessibleValue(n); + }); + } + + /** + * Gets the minimum increment by which the value may be changed. + * Called from native code via JNI. + * + * @return the minimum increment value, returns Double.MIN_VALUE + */ + private double get_increment() { + return Double.MIN_VALUE; + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapper.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapper.java new file mode 100644 index 000000000000..3e4dd483065a --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapper.java @@ -0,0 +1,710 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.GNOME.Accessibility; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import java.io.*; +import javax.accessibility.*; +import java.awt.Toolkit; +import javax.swing.JComboBox; +import java.util.*; + +import sun.util.logging.PlatformLogger; + +public class AtkWrapper { + private static final PlatformLogger log = PlatformLogger.getLogger("org.GNOME.Accessibility.AtkWrapper"); + private static boolean accessibilityEnabled = false; + private static boolean nativeLibraryInited = false; + // Previously focused accessible context + private static AccessibleContext oldSourceContext = null; + // Last saved focused accessible context (excluding JRootPane) + private static AccessibleContext savedSourceContext = null; + // Previously focused JRootPane's AccessibleContext + private static AccessibleContext oldPaneContext = null; + + private static final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent e) { + Object o = e.getSource(); + AccessibleContext ac; + if (o instanceof AccessibleContext accessibleContext) { + ac = accessibleContext; + } else if (o instanceof Accessible accessible) { + ac = accessible.getAccessibleContext(); + } else { + return; + } + + if (ac == null) { + return; + } + + Object oldValue = e.getOldValue(); + Object newValue = e.getNewValue(); + String propertyName = e.getPropertyName(); + + if (propertyName.equals(AccessibleContext.ACCESSIBLE_CARET_PROPERTY)) { + Object[] args = new Object[1]; + args[0] = newValue; + + if (newValue != null && newValue instanceof AccessibleContext accessibleContext) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } else if (newValue != null && newValue instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + } + } + + emitSignal(ac, AtkSignal.TEXT_CARET_MOVED, args); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TEXT_PROPERTY)) { + /** + * The documentation for AccessibleContext.ACCESSIBLE_TEXT_PROPERTY states that newValue + * is an instance of AccessibleTextSequence in an insertion event, and oldValue is an + * instance of AccessibleTextSequence in a deletion event. This allows us to use + * AtkSignal.TEXT_PROPERTY_CHANGED_INSERT and AtkSignal.TEXT_PROPERTY_CHANGED_DELETE. + * However, Swing components send a signal where oldValue is null and newValue is an + * instance of Integer for both insertion and deletion events. As a result, we continue + * using the deprecated in atk AtkSignal.TEXT_PROPERTY_CHANGED. + */ + if (oldValue == null && newValue != null) { + if (newValue instanceof AccessibleTextSequence newSeq) { // insertion event according to the Swing documentation + Object[] args = new Object[3]; + args[0] = Integer.valueOf(newSeq.startIndex); // the position (character offset) of the insertion + args[1] = Integer.valueOf(newSeq.endIndex - newSeq.startIndex); // the length (in characters) of text inserted + args[2] = newSeq.text; // the new text inserted + + /* + * `text_insert` has 3 parameters: + * 1. arg1 - the position (character offset) of the insertion. + * 2. arg2 - the length (in characters) of text inserted. + * 3. arg3 - the new text inserted. + */ + emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED_INSERT, args); + } else if (newValue instanceof Integer) { // real insertion or deletion event that Swing components send + Object[] args = new Object[1]; + args[0] = newValue; // the position (character offset) of the insertion or deletion + /* + * `text-changed` has 2 parameters: + * 1. arg1 - the position (character offset) of the insertion or deletion. + * 2. arg2 - the length (in characters) of text inserted or deleted. + * + * In our case, arg2 is unknown. + */ + emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED, args); + } + } else if (oldValue != null && newValue == null) { + if (oldValue instanceof AccessibleTextSequence oldSeq) { // deletion event according to the Swing documentation + Object[] args = new Object[3]; + args[0] = Integer.valueOf(oldSeq.startIndex); // the position (character offset) of the removal + args[1] = Integer.valueOf(oldSeq.endIndex - oldSeq.startIndex); // the length (in characters) of text removed + args[2] = oldSeq.text; // the old text removed + + /* + * `text_remove` has 3 parameters: + * 1. arg1 - the position (character offset) of the removal. + * 2. arg2 - the length (in characters) of text removed. + * 3. arg3 - the new text removed. + */ + emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED_DELETE, args); + } + } + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY)) { + if (oldValue == null && newValue != null) { //child added + AccessibleContext child_ac; + if (newValue instanceof Accessible accessible) { + child_ac = accessible.getAccessibleContext(); + } else { + return; + } + + if (child_ac == null) { + return; + } + + Object[] args = new Object[2]; + args[0] = Integer.valueOf(child_ac.getAccessibleIndexInParent()); + args[1] = child_ac; + + AtkWrapperDisposer.getInstance().addRecord(child_ac); + + emitSignal(ac, AtkSignal.OBJECT_CHILDREN_CHANGED_ADD, args); + } else if (oldValue != null && newValue == null) { //child removed + AccessibleContext child_ac; + if (oldValue instanceof Accessible accessible) { + child_ac = accessible.getAccessibleContext(); + } else { + return; + } + + if (child_ac == null) { + return; + } + + Object[] args = new Object[2]; + args[0] = Integer.valueOf(child_ac.getAccessibleIndexInParent()); + args[1] = child_ac; + + AtkWrapperDisposer.getInstance().addRecord(child_ac); + + emitSignal(ac, AtkSignal.OBJECT_CHILDREN_CHANGED_REMOVE, args); + } + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY)) { + AccessibleContext child_ac; + if (newValue instanceof Accessible accessible) { + child_ac = accessible.getAccessibleContext(); + } else { + return; + } + if (child_ac == null) { + return; + } + + /* + * JComboBox sends two signals: object:active-descendant-changed and object:selection-changed. + * Information about the component is announced when processing the object:selection-changed signal. + * The object:active-descendant-changed signal interrupts this announcement in earlier versions of GNOME. + * Skipping the emission of object:active-descendant-changed signal resolves this issue. + * See: https://gitlab.gnome.org/GNOME/java-atk-wrapper/-/issues/29 + */ + if (ac.getAccessibleRole() == AccessibleRole.COMBO_BOX) { + return; + } + Object[] args = new Object[1]; + args[0] = child_ac; + + AtkWrapperDisposer.getInstance().addRecord(child_ac); + + emitSignal(ac, AtkSignal.OBJECT_ACTIVE_DESCENDANT_CHANGED, args); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY)) { + boolean isTextEvent = false; + AccessibleRole role = ac.getAccessibleRole(); + if ((role == AccessibleRole.TEXT) || + role.toDisplayString(java.util.Locale.US).equalsIgnoreCase("paragraph")) { + isTextEvent = true; + } else if (role == AccessibleRole.MENU_BAR) { + dispatchFocusEvent(o); + } else if (role == AccessibleRole.PAGE_TAB_LIST) { + AccessibleSelection selection = ac.getAccessibleSelection(); + if (selection != null && + selection.getAccessibleSelectionCount() > 0) { + java.lang.Object child = selection.getAccessibleSelection(0); + dispatchFocusEvent(child); + } + } + + /* + * When navigating through nodes, JTree sends two signals: object:selection-changed and object:active-descendant-changed. + * The object:selection-changed signal is emitted before object:active-descendant-changed, leading to the following issues: + * 1. When focusing on the root of the tree, object:active-descendant-changed interrupts the announcement and does not + * provide any output because the FOCUS MANAGER doesn't update the focus - it is already set to the same object. + * 2. For other nodes, the correct behavior happens because of the bug in JTree: + * AccessibleJTree incorrectly reports the selected children and object:selection-changed sets focus on the tree. + * + * Removing the object:selection-changed signal ensures that the locus of focus is updated during object:active-descendant-changed, + * allowing elements to be announced correctly. + * See: https://gitlab.gnome.org/GNOME/orca/-/issues/552 + */ + if (ac.getAccessibleRole() == AccessibleRole.TREE) { + return; + } + + if (!isTextEvent) { + emitSignal(ac, AtkSignal.OBJECT_SELECTION_CHANGED, null); + } + + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY)) { + emitSignal(ac, AtkSignal.OBJECT_VISIBLE_DATA_CHANGED, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_ACTION_PROPERTY)) { + Object[] args = new Object[2]; + args[0] = oldValue; + args[1] = newValue; + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS, args); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_VALUE_PROPERTY)) { + if (oldValue instanceof Number oldValueNamber && newValue instanceof Number newValueNumber) { + Object[] args = new Object[2]; + args[0] = Double.valueOf(oldValueNamber.doubleValue()); + args[1] = Double.valueOf(newValueNumber.doubleValue()); + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE, args); + } + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_NAME_PROPERTY)) { + if (!Objects.equals(newValue, oldValue)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME, null); + } + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED)) { + emitSignal(ac, AtkSignal.TABLE_MODEL_CHANGED, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_CAPTION_CHANGED)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_SUMMARY_CHANGED)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_COLUMN_HEADER_CHANGED)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_COLUMN_DESCRIPTION_CHANGED)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_ROW_HEADER_CHANGED)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_ROW_DESCRIPTION_CHANGED)) { + emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION, null); + } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_STATE_PROPERTY)) { + Accessible parent = ac.getAccessibleParent(); + AccessibleRole role = ac.getAccessibleRole(); + if (role != null) { + if (newValue == AccessibleState.FOCUSED || + newValue == AccessibleState.SELECTED) { + dispatchFocusEvent(o); + } + } + AccessibleState state; + boolean value = false; + if (newValue != null) { + state = (AccessibleState) newValue; + value = true; + } else { + state = (AccessibleState) oldValue; + value = false; + } + if (state == AccessibleState.COLLAPSED) { + state = AccessibleState.EXPANDED; + value = false; + } + if (parent instanceof JComboBox && oldValue == + AccessibleState.VISIBLE) { + objectStateChange(ac, AccessibleState.SHOWING, value); + } + objectStateChange(ac, state, value); + if (parent instanceof JComboBox && newValue == + AccessibleState.VISIBLE && oldValue == null) { + objectStateChange(ac, AccessibleState.SHOWING, value); + } + } + } + }; + + static { + try { + String xpropPath = findXPropPath(); + if (xpropPath == null) { + throw new RuntimeException("No xprop found"); + } + System.loadLibrary("atk-wrapper"); + ProcessBuilder builder = new ProcessBuilder(xpropPath, "-root"); + Process p = builder.start(); + BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream())); + String result; + while ((result = b.readLine()) != null) { + if (result.indexOf("AT_SPI_IOR") >= 0 || result.indexOf("AT_SPI_BUS") >= 0) { + if (AtkWrapper.initNativeLibrary()) { + nativeLibraryInited = true; + } + break; + } + } + + if (!nativeLibraryInited) { + builder = new ProcessBuilder("dbus-send", "--session", "--dest=org.a11y.Bus", "--print-reply", "/org/a11y/bus", "org.a11y.Bus.GetAddress"); + p = builder.start(); + var ignoredOutput = p.getInputStream(); + while (ignoredOutput.skip(Long.MAX_VALUE) == Long.MAX_VALUE) ; + p.waitFor(); + if (p.exitValue() == 0) { + if (AtkWrapper.initNativeLibrary()) { + nativeLibraryInited = true; + } + } + } + + if (!nativeLibraryInited) { + throw new IllegalStateException("Accessibility is disabled due to an error in initNativeLibrary."); + } + + if (!AtkWrapper.loadAtkBridge()) { + throw new IllegalStateException("Accessibility is disabled due to an error in loadAtkBridge."); + } + + accessibilityEnabled = true; + + } catch (Exception e) { + if (log.isLoggable(PlatformLogger.Level.SEVERE)) { + log.severe("Error during ATK accessibility initialization: ", e); + } + } + } + + private final WindowAdapter windowAdapter = new WindowAdapter() { + public void windowActivated(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowActivate(accessibleContext); + } + } + } + + public void windowDeactivated(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowDeactivate(accessibleContext); + } + } + } + + public void windowStateChanged(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowStateChange(accessibleContext); + if ((e.getNewState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) { + AtkWrapper.windowMaximize(accessibleContext); + } + } + } + } + + public void windowDeiconified(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowRestore(accessibleContext); + } + } + } + + public void windowIconified(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowMinimize(accessibleContext); + } + } + } + + public void windowOpened(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + boolean isToplevel = isToplevel(o); + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowOpen(accessibleContext, isToplevel); + } + } + } + + public void windowClosed(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + boolean isToplevel = isToplevel(o); + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowClose(accessibleContext, isToplevel); + } + } + } + + public void windowClosing(WindowEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + boolean isToplevel = isToplevel(o); + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.windowClose(accessibleContext, isToplevel); + } + } + } + + public void windowGainedFocus(WindowEvent e) { + } + + public void windowLostFocus(WindowEvent e) { + } + }; + private final ComponentAdapter componentAdapter = new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.boundsChanged(accessibleContext); + } + } + } + + public void componentMoved(ComponentEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.boundsChanged(accessibleContext); + } + } + } + + public void componentShown(ComponentEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.componentAdded(accessibleContext); + } + } + } + + public void componentHidden(ComponentEvent e) { + Object o = e.getSource(); + if (o instanceof Accessible accessible) { + AccessibleContext accessibleContext = accessible.getAccessibleContext(); + if (accessibleContext != null) { + AtkWrapperDisposer.getInstance().addRecord(accessibleContext); + AtkWrapper.componentRemoved(accessibleContext); + } + } + } + }; + private final AWTEventListener globalListener = new AWTEventListener() { + private boolean firstEvent = true; + + public void eventDispatched(AWTEvent e) { + if (e instanceof WindowEvent windowEvent) { + switch (e.getID()) { + case WindowEvent.WINDOW_OPENED: + Window win = windowEvent.getWindow(); + win.addWindowListener(windowAdapter); + win.addWindowStateListener(windowAdapter); + win.addWindowFocusListener(windowAdapter); + break; + case WindowEvent.WINDOW_LOST_FOCUS: + AtkWrapper.dispatchFocusEvent(null); + break; + default: + break; + } + } else if (e instanceof ContainerEvent containerEvent) { + switch (e.getID()) { + case ContainerEvent.COMPONENT_ADDED: { + Component c = containerEvent.getChild(); + c.addComponentListener(componentAdapter); + break; + } + case ContainerEvent.COMPONENT_REMOVED: { + Component c = containerEvent.getChild(); + c.removeComponentListener(componentAdapter); + break; + } + + default: + break; + } + } else if (e instanceof FocusEvent) { + switch (e.getID()) { + case FocusEvent.FOCUS_GAINED: + AtkWrapper.dispatchFocusEvent(e.getSource()); + break; + default: + break; + } + } else if (e instanceof KeyEvent keyEvent) { + AtkWrapper.dispatchKeyEvent(new AtkKeyEvent(keyEvent)); + } + } + }; + private final Toolkit toolkit = Toolkit.getDefaultToolkit(); + + public AtkWrapper() { + if (!accessibilityEnabled) { + throw new IllegalStateException("AtkWrapper not initialized due to disabled accessibility."); + } + + toolkit.addAWTEventListener(globalListener, + AWTEvent.WINDOW_EVENT_MASK | + AWTEvent.FOCUS_EVENT_MASK | + AWTEvent.CONTAINER_EVENT_MASK | + AWTEvent.KEY_EVENT_MASK); + } + + public static void main(String args[]) { + new AtkWrapper(); + } + + private static String findXPropPath() { + String pathEnv = System.getenv().get("PATH"); + if (pathEnv != null) { + String pathSeparator = File.pathSeparator; + java.util.List paths = Arrays.asList(pathEnv.split(File.pathSeparator)); + for (String path : paths) { + File xpropFile = new File(path, "xprop"); + if (xpropFile.exists()) { + return xpropFile.getAbsolutePath(); + } + } + } + return null; + } + + private static boolean isToplevel(Object o) { + boolean isToplevel = false; + if (o instanceof java.awt.Window || + o instanceof java.awt.Frame || + o instanceof java.awt.Dialog) { + isToplevel = true; + } + return isToplevel; + } + + private static void dispatchFocusEvent(Object eventSource) { + if (eventSource == null) { + oldSourceContext = null; + return; + } + + AccessibleContext eventSourceContext; + + try { + if (eventSource instanceof AccessibleContext accessibleContext) { + eventSourceContext = accessibleContext; + } else if (eventSource instanceof Accessible accessible) { + eventSourceContext = accessible.getAccessibleContext(); + } else { + return; + } + + if (eventSourceContext == oldSourceContext) { + return; + } + + if (oldSourceContext != null) { + AccessibleRole role = oldSourceContext.getAccessibleRole(); + if (role == AccessibleRole.MENU || role == AccessibleRole.MENU_ITEM) { + String jrootpane = "javax.swing.JRootPane$AccessibleJRootPane"; + String name = eventSourceContext.getClass().getName(); + + if (jrootpane.compareTo(name) == 0) { + oldPaneContext = eventSourceContext; + return; + } + } + savedSourceContext = eventSourceContext; + } else if (oldPaneContext == eventSourceContext) { + eventSourceContext = savedSourceContext; + } else { + savedSourceContext = eventSourceContext; + } + + oldSourceContext = eventSourceContext; + AccessibleRole role = eventSourceContext.getAccessibleRole(); + if (role == AccessibleRole.PAGE_TAB_LIST) { + AccessibleSelection accSelection = eventSourceContext.getAccessibleSelection(); + if (accSelection != null && accSelection.getAccessibleSelectionCount() > 0) { + Object child = accSelection.getAccessibleSelection(0); + if (child instanceof AccessibleContext accessibleContext) { + eventSourceContext = accessibleContext; + } else if (child instanceof Accessible accessible) { + eventSourceContext = accessible.getAccessibleContext(); + } else { + return; + } + } + } + if (eventSourceContext != null) { + AtkWrapperDisposer.getInstance().addRecord(eventSourceContext); + focusNotify(eventSourceContext); + } + } catch (Exception e) { + if (log.isLoggable(PlatformLogger.Level.SEVERE)) { + log.severe("Error in dispatchFocusEvent: ", e); + } + } + } + + + private native static boolean initNativeLibrary(); + + private native static boolean loadAtkBridge(); + + native static long createNativeResources(AccessibleContext ac); + + native static void releaseNativeResources(long ref); + + private native static void focusNotify(AccessibleContext ac); + + private native static void windowOpen(AccessibleContext ac, + boolean isToplevel); + + private native static void windowClose(AccessibleContext ac, + boolean isToplevel); + + private native static void windowMinimize(AccessibleContext ac); + + private native static void windowMaximize(AccessibleContext ac); + + private native static void windowRestore(AccessibleContext ac); + + private native static void windowActivate(AccessibleContext ac); + + private native static void windowDeactivate(AccessibleContext ac); + + private native static void windowStateChange(AccessibleContext ac); + + private native static void emitSignal(AccessibleContext ac, int id, Object[] args); + + private native static void objectStateChange(AccessibleContext ac, + Object state, boolean value); + + private native static void componentAdded(AccessibleContext ac); + + private native static void componentRemoved(AccessibleContext ac); + + private native static void boundsChanged(AccessibleContext ac); + + private native static void dispatchKeyEvent(AtkKeyEvent e); + + + // JNI upcalls section + + private static void register_property_change_listener(AccessibleContext ac) { + if (ac != null) { + AtkUtil.invokeInSwing(() -> { + ac.addPropertyChangeListener(propertyChangeListener); + }); + } + } +} diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapperDisposer.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapperDisposer.java new file mode 100644 index 000000000000..37f4082bb2c7 --- /dev/null +++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapperDisposer.java @@ -0,0 +1,158 @@ +/* + * Copyright 2025 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.GNOME.Accessibility; + +import javax.accessibility.*; +import java.util.*; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.PhantomReference; +import java.security.PrivilegedAction; + +import jdk.internal.misc.InnocuousThread; +import sun.util.logging.PlatformLogger; + +/** + * Manages the association between an AccessibleContext + * and native resources. Can be used to create such + * associations and ensures that the native resource associated + * with the AccessibleContext is released when the AccessibleContext + * is garbage collected. + *

+ * Java classes are fully responsible for creating associations between + * an AccessibleContext and native resources. For example, when a JNI upcall method + * returns an AccessibleContext, native assumes that the corresponding native resource + * has already been created and the association between the AccessibleContext and the + * native resource exists. + */ +@SuppressWarnings("removal") +public class AtkWrapperDisposer implements Runnable { + // Reference queue that holds objects ready for garbage collection + private static final ReferenceQueue queue = new ReferenceQueue<>(); + + // Maps PhantomReferences and their associated native resource pointer + private static final Map, Long> phantomMap = new HashMap<>(); + + // Maps AccessibleContext object with native resource pointer + private static final WeakHashMap weakHashMap = new WeakHashMap<>(); + + private static final Object lock = new Object(); + private static final PlatformLogger log = PlatformLogger.getLogger("org.GNOME.Accessibility.AtkWrapperDisposer"); + private static volatile AtkWrapperDisposer INSTANCE = null; + + private AtkWrapperDisposer() { + } + + private void init() { + Thread t = InnocuousThread.newThread("Atk Wrapper Disposer", INSTANCE, Thread.MAX_PRIORITY); + t.setContextClassLoader(null); + t.setDaemon(true); + t.start(); + } + + public static synchronized AtkWrapperDisposer getInstance() { + if (INSTANCE == null) { + synchronized (AtkWrapperDisposer.class) { + if (INSTANCE == null) { + INSTANCE = new AtkWrapperDisposer(); + INSTANCE.init(); + } + } + } + return INSTANCE; + } + + /** + * Monitors the reference queue and releases native resources when an associated + * AccessibleContext is garbage collected. The native resource is released using + * {@link AtkWrapper#releaseNativeResources}. + */ + public void run() { + while (true) { + try { + // When an AccessibleContext is freed, release the associated native resource + Reference obj = queue.remove(); + long nativeReference; + synchronized (lock) { + nativeReference = phantomMap.remove(obj); + } + AtkWrapper.releaseNativeResources(nativeReference); + obj.clear(); + obj = null; + } catch (Exception e) { + if (log.isLoggable(PlatformLogger.Level.SEVERE)) { + log.severe("Exception while removing reference: ", e); + } + } + } + } + + /** + * Associates an AccessibleContext with a newly created native resource. If the AccessibleContext + * is not already registered, a new native resource pointer is created using + * {@link AtkWrapper#createNativeResources} + * + * @param ac The AccessibleContext to associate with a native resource. + */ + public void addRecord(AccessibleContext ac) { + if (ac == null) { + return; + } + synchronized (lock) { + if (!weakHashMap.containsKey(ac)) { + long nativeReference = AtkWrapper.createNativeResources(ac); + if (nativeReference != -1) { + PhantomReference phantomReference = new PhantomReference<>(ac, queue); + phantomMap.put(phantomReference, nativeReference); + weakHashMap.put(ac, nativeReference); + } + } + } + } + + private long getRecord(AccessibleContext ac) { + synchronized (lock) { + if (weakHashMap.containsKey(ac)) { + return weakHashMap.get(ac); + } + } + return -1; + } + + // JNI upcalls section + + /** + * Retrieves the native resource associated with the given AccessibleContext. + * If no record exists, returns -1. + * + * @param ac The AccessibleContext whose native resource is requested. + * @return The native resource pointer associated with the given AccessibleContext, + * or -1 if no record exists. + */ + private static long get_resource(AccessibleContext ac) { + return AtkWrapperDisposer.getInstance().getRecord(ac); + } +} \ No newline at end of file diff --git a/src/jdk.accessibility/linux/conf/accessibility.properties b/src/jdk.accessibility/linux/conf/accessibility.properties new file mode 100644 index 000000000000..0bbed565f6ad --- /dev/null +++ b/src/jdk.accessibility/linux/conf/accessibility.properties @@ -0,0 +1 @@ +assistive_technologies=org.GNOME.Accessibility.AtkWrapper \ No newline at end of file diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkInterface.h b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkInterface.h new file mode 100644 index 000000000000..d31659b0b0a5 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkInterface.h @@ -0,0 +1,35 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_GNOME_Accessibility_AtkInterface */ + +#ifndef _Included_org_GNOME_Accessibility_AtkInterface +#define _Included_org_GNOME_Accessibility_AtkInterface +#ifdef __cplusplus +extern "C" { +#endif +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION 1L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT 2L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_DOCUMENT +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_DOCUMENT 4L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT 8L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT 32L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE 64L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION 128L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE 512L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL 1024L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT 2048L +#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE +#define org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE 4096L +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkSignal.h b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkSignal.h new file mode 100644 index 000000000000..cfce36781006 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkSignal.h @@ -0,0 +1,68 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_GNOME_Accessibility_AtkSignal */ + +#ifndef _Included_org_GNOME_Accessibility_AtkSignal +#define _Included_org_GNOME_Accessibility_AtkSignal +#ifdef __cplusplus +extern "C" { +#endif +#undef org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED +#define org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED 0L +#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT +#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT 1L +#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE +#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE 2L +#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_REPLACE +#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_REPLACE 3L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD +#define org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD 4L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE +#define org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE 5L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED +#define org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED 6L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED +#define org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED 7L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED +#define org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED 8L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS \ + 9L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE \ + 10L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION \ + 11L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME \ + 12L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET \ + 13L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION \ + 14L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY \ + 15L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER \ + 16L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION \ + 17L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER \ + 18L +#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION +#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION \ + 19L +#undef org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED +#define org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED 20L +#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED +#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED 21L +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.c b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.c new file mode 100644 index 000000000000..3e87b2f40da4 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.c @@ -0,0 +1,1784 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "AtkWrapper.h" +#include "AtkSignal.h" +#include "jawcache.h" +#include "jawimpl.h" +#include "jawtoplevel.h" +#include "jawutil.h" +#include +#include +#include +#include +#include +#include +#include + +int jaw_debug = 0; +FILE *jaw_log_file; +time_t jaw_start_time; + +#ifdef __cplusplus +extern "C" { +#endif + +#define KEY_DISPATCH_NOT_DISPATCHED 0 +#define KEY_DISPATCH_CONSUMED 1 +#define KEY_DISPATCH_NOT_CONSUMED 2 + +#define GDK_SHIFT_MASK (1 << 0) +#define GDK_CONTROL_MASK (1 << 2) +#define GDK_MOD1_MASK (1 << 3) +#define GDK_MOD5_MASK (1 << 7) +#define GDK_META_MASK (1 << 28) + +#define JAW_LOG_FILE "jaw_log.txt" +#define JAW_LOG_FILE2 "/tmp/" JAW_LOG_FILE + +#define ATSPI_CHECK_VERSION(major, minor, micro) \ + (((ATSPI_MAJOR_VERSION) > (major)) || \ + ((ATSPI_MAJOR_VERSION) == (major) && (ATSPI_MINOR_VERSION) > (minor)) || \ + ((ATSPI_MAJOR_VERSION) == (major) && (ATSPI_MINOR_VERSION) == (minor) && \ + (ATSPI_MICRO_VERSION) >= (micro))) + +gboolean jaw_accessibility_init(void); +void jaw_accessibility_shutdown(void); + +static GMainLoop *jni_main_loop; +static GMainContext *jni_main_context; + +static gboolean jaw_initialized = FALSE; + +static jclass cachedWrapperIntegerClass = NULL; +static jmethodID cachedWrapperIntValueMethod = NULL; +static jclass cachedWrapperAtkKeyEventClass = NULL; +static jfieldID cachedWrapperTypeFieldID = NULL; +static jfieldID cachedWrapperTypePressedFieldID = NULL; +static jfieldID cachedWrapperTypeReleasedFieldID = NULL; +static jfieldID cachedWrapperShiftFieldID = NULL; +static jfieldID cachedWrapperCtrlFieldID = NULL; +static jfieldID cachedWrapperAltFieldID = NULL; +static jfieldID cachedWrapperMetaFieldID = NULL; +static jfieldID cachedWrapperAltGrFieldID = NULL; +static jfieldID cachedWrapperKeyvalFieldID = NULL; +static jfieldID cachedWrapperStringFieldID = NULL; +static jfieldID cachedWrapperKeycodeFieldID = NULL; +static jfieldID cachedWrapperTimestampFieldID = NULL; + +static GMutex wrapper_cache_mutex; +static gboolean wrapper_cache_initialized = FALSE; + +static gboolean atk_wrapper_init_jni_cache(JNIEnv *jniEnv); + +/* + * OpenJDK seems to be sending flurries of visible data changed events, which + * overloads us. They are however usually just for the same object, so we can + * compact them: there is no need to queue another one if the previous hasn't + * even been sent! + */ +static pthread_mutex_t jaw_vdc_dup_mutex = PTHREAD_MUTEX_INITIALIZER; +static jobject jaw_vdc_last_ac = NULL; + +static void jaw_vdc_clear_last_ac(JNIEnv *jniEnv) { + if (jaw_vdc_last_ac != NULL && jniEnv != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_vdc_last_ac); + } + jaw_vdc_last_ac = NULL; +} + +gboolean jaw_accessibility_init(void) { + JAW_DEBUG(""); + if (atk_bridge_adaptor_init(NULL, NULL) < 0) { + g_warning("%s: atk_bridge_adaptor_init failed", G_STRFUNC); + return FALSE; + } + g_debug("Atk Bridge Initialized"); + return TRUE; +} + +void jaw_accessibility_shutdown(void) { + JAW_DEBUG(""); + + pthread_mutex_lock(&jaw_vdc_dup_mutex); + JNIEnv *jniEnv = jaw_util_get_jni_env(); + jaw_vdc_clear_last_ac(jniEnv); + pthread_mutex_unlock(&jaw_vdc_dup_mutex); + + atk_bridge_adaptor_cleanup(); +} + +static gpointer jni_loop_callback(void *data) { + JAW_DEBUG("%p", data); + if (!g_main_loop_is_running((GMainLoop *)data)) + g_main_loop_run((GMainLoop *)data); + else { + g_debug("Running JNI already"); + } + return 0; +} + +JNIEXPORT jboolean JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_initNativeLibrary(void) { + JAW_DEBUG(""); + const gchar *debug_env = g_getenv("JAW_DEBUG"); + if (debug_env != NULL) { + int val_debug = atoi(debug_env); + if (val_debug > 4) + jaw_debug = 4; + else + jaw_debug = val_debug; + } + if (jaw_debug != 0) { + jaw_log_file = fopen(JAW_LOG_FILE, "w+"); + if (!jaw_log_file) { + perror("Error opening log file " JAW_LOG_FILE + ", trying " JAW_LOG_FILE2); + jaw_log_file = fopen(JAW_LOG_FILE2, "w+"); + } + + if (!jaw_log_file) { + perror("Error opening log file " JAW_LOG_FILE2); + return JNI_FALSE; + } + jaw_start_time = time(NULL); + } + + // Java app with GTK Look And Feel will load gail + // Set NO_GAIL to "1" to prevent gail from executing + + g_setenv("NO_GAIL", "1", TRUE); + + // Disable ATK Bridge temporarily to aoid the loading + // of ATK Bridge by GTK look and feel + g_setenv("NO_AT_BRIDGE", "1", TRUE); + + g_type_class_unref(g_type_class_ref(JAW_TYPE_UTIL)); + // Force to invoke base initialization function of each ATK interfaces + g_type_class_unref(g_type_class_ref(ATK_TYPE_NO_OP_OBJECT)); + + return JNI_TRUE; +} + +static guint jni_main_idle_add(GSourceFunc function, gpointer data) { + JAW_DEBUG("%p, %p", function, data); + GSource *source; + guint id; + + source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + id = g_source_attach(source, jni_main_context); + g_source_unref(source); + + return id; +} + +JNIEXPORT jboolean JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_loadAtkBridge(void) { + JAW_DEBUG(""); + // Enable ATK Bridge so we can load it now + g_unsetenv("NO_AT_BRIDGE"); + + GThread *thread; + GError *err; + const char *message; + message = "JavaAtkWrapper-MainLoop"; + err = NULL; + + jaw_initialized = jaw_accessibility_init(); + g_debug("%s: Jaw Initialization STATUS = %d", G_STRFUNC, jaw_initialized); + if (!jaw_initialized) { + g_warning("%s: loadAtkBridge: jaw_initialized == NULL", G_STRFUNC); + return JNI_FALSE; + } + +#if ATSPI_CHECK_VERSION(2, 33, 1) + jni_main_context = g_main_context_new(); + jni_main_loop = + g_main_loop_new(jni_main_context, FALSE); /*main loop NOT running*/ + atk_bridge_set_event_context(jni_main_context); +#else + jni_main_loop = g_main_loop_new(NULL, FALSE); +#endif + + thread = g_thread_try_new(message, jni_loop_callback, (void *)jni_main_loop, + &err); + if (thread == NULL) { + g_warning("%s: g_thread_try_new failed: %s", G_STRFUNC, err->message); + g_main_loop_unref(jni_main_loop); +#if ATSPI_CHECK_VERSION(2, 33, 1) + atk_bridge_set_event_context(NULL); // set default context + g_main_context_unref(jni_main_context); +#endif + g_error_free(err); + jaw_accessibility_shutdown(); + return JNI_FALSE; + } else { + /* We won't join it */ + g_thread_unref(thread); + } + return JNI_TRUE; +} + +typedef struct _CallbackPara { + jobject global_ac; + JawImpl *jaw_impl; + JawImpl *child_impl; + gboolean is_toplevel; + gint signal_id; + jobjectArray args; + AtkStateType atk_state; + gboolean state_value; +} CallbackPara; + +typedef struct _CallbackParaEvent { + jobject global_event; +} CallbackParaEvent; + +JNIEXPORT jlong JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_createNativeResources(JNIEnv *jniEnv, + jclass jClass, + jobject ac) { + JawImpl *jaw_impl = jaw_impl_create_instance(jniEnv, ac); + JAW_DEBUG("%p", jaw_impl); + if (jaw_impl == NULL) { + g_warning("%s: jaw_impl_create_instance failed", G_STRFUNC); + return -1; + } + return (jlong)jaw_impl; +} + +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_releaseNativeResources( + JNIEnv *jniEnv, jclass jClass, jlong reference) { + JawImpl *jaw_impl = (JawImpl *)reference; + JAW_DEBUG("%p", jaw_impl); + if (jaw_impl == NULL) { + g_warning("%s: jaw_impl is NULL", G_STRFUNC); + return; + } + g_object_unref(G_OBJECT(jaw_impl)); +} + +static CallbackPara *alloc_callback_para(JNIEnv *jniEnv, jobject ac) { + JAW_DEBUG("%p, %p", jniEnv, ac); + if (ac == NULL) { + g_warning("%s: ac is NULL", G_STRFUNC); + return NULL; + } + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, ac); + if (jaw_impl == NULL) { + g_warning("%s: jaw_impl_find_instance failed", G_STRFUNC); + return NULL; + } + g_object_ref(G_OBJECT(jaw_impl)); + CallbackPara *para = g_new(CallbackPara, 1); + para->global_ac = ac; + para->jaw_impl = jaw_impl; + para->child_impl = NULL; + para->args = NULL; + + return para; +} + +static CallbackParaEvent *alloc_callback_para_event(JNIEnv *jniEnv, + jobject event) { + JAW_DEBUG("%p, %p", jniEnv, event); + if (event == NULL) { + g_warning("%s: event is NULL", G_STRFUNC); + return NULL; + } + CallbackParaEvent *para = g_new(CallbackParaEvent, 1); + para->global_event = event; + return para; +} + +static void free_callback_para(CallbackPara *para) { + JAW_DEBUG("%p", para); + + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jaw_util_get_jni_env returned NULL", G_STRFUNC); + } + + if (jniEnv != NULL) { + if (para->global_ac != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, para->global_ac); + } else { + g_debug("%s: para->global_ac == NULL", G_STRFUNC); + } + } + + if (para->jaw_impl != NULL) { + g_object_unref(G_OBJECT(para->jaw_impl)); + } else { + g_debug("%s: para->jaw_impl == NULL", G_STRFUNC); + } + + if (para->child_impl != NULL) { + g_object_unref(G_OBJECT(para->child_impl)); + } + + if (jniEnv != NULL) { + if (para->args != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, para->args); + } else { + g_debug("%s: para->args == NULL", G_STRFUNC); + } + } + + g_free(para); +} + +static void free_callback_para_event(CallbackParaEvent *para) { + JAW_DEBUG("%p", para); + + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jaw_util_get_jni_env returned NULL", G_STRFUNC); + } + + if (jniEnv != NULL) { + if (para->global_event != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, para->global_event); + } else { + g_debug("para->global_event == NULL"); + } + } + + g_free(para); +} + +/* List of callback params to be freed */ +static GSList *callback_para_frees; +static GMutex callback_para_frees_mutex; +static GSList *callback_para_event_frees; +static GMutex callback_para_event_frees_mutex; + +/* Add a note that this callback param should be freed from the application */ +static void queue_free_callback_para(CallbackPara *para) { + JAW_DEBUG("%p", para); + g_mutex_lock(&callback_para_frees_mutex); + callback_para_frees = g_slist_prepend(callback_para_frees, para); + g_mutex_unlock(&callback_para_frees_mutex); +} + +static void queue_free_callback_para_event(CallbackParaEvent *para) { + JAW_DEBUG("%p", para); + g_mutex_lock(&callback_para_event_frees_mutex); + callback_para_event_frees = + g_slist_prepend(callback_para_event_frees, para); + g_mutex_unlock(&callback_para_event_frees_mutex); +} + +/* Process the unreference requests */ +static void callback_para_process_frees(void) { + JAW_DEBUG(""); + GSList *list, *cur, *next; + + g_mutex_lock(&callback_para_frees_mutex); + list = callback_para_frees; + callback_para_frees = NULL; + g_mutex_unlock(&callback_para_frees_mutex); + + for (cur = list; cur != NULL; cur = next) { + free_callback_para(cur->data); + next = g_slist_next(cur); + g_slist_free_1(cur); + } +} + +static void callback_para_event_process_frees(void) { + JAW_DEBUG(""); + GSList *list, *cur, *next; + + g_mutex_lock(&callback_para_event_frees_mutex); + list = callback_para_event_frees; + callback_para_event_frees = NULL; + g_mutex_unlock(&callback_para_event_frees_mutex); + + for (cur = list; cur != NULL; cur = next) { + free_callback_para_event(cur->data); + next = g_slist_next(cur); + g_slist_free_1(cur); + } +} + +static gboolean focus_notify_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + atk_object_notify_state_change(atk_obj, ATK_STATE_FOCUSED, 1); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_focusNotify( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(focus_notify_handler, para); +} + +static gboolean window_open_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + gboolean is_toplevel = para->is_toplevel; + + if (!g_strcmp0(atk_role_get_name(atk_object_get_role(atk_obj)), + "redundant object")) { + queue_free_callback_para(para); + return G_SOURCE_REMOVE; + } + + if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) { + queue_free_callback_para(para); + return G_SOURCE_REMOVE; + } + + if (is_toplevel != FALSE) { + gint n = jaw_toplevel_add_window(JAW_TOPLEVEL(atk_get_root()), atk_obj); + if (n != -1) { + g_object_notify(G_OBJECT(atk_get_root()), "accessible-name"); + g_signal_emit_by_name(ATK_OBJECT(atk_get_root()), + "children-changed::add", n, atk_obj); + g_signal_emit_by_name(atk_obj, "create"); + } + } + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowOpen( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext, jboolean jIsToplevel) { + JAW_DEBUG("%p, %p, %p, %d", jniEnv, jClass, jAccContext, jIsToplevel); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + para->is_toplevel = jIsToplevel; + jni_main_idle_add(window_open_handler, para); +} + +static gboolean window_close_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + gboolean is_toplevel = para->is_toplevel; + + if (!g_strcmp0(atk_role_get_name(atk_object_get_role(atk_obj)), + "redundant object")) { + queue_free_callback_para(para); + return G_SOURCE_REMOVE; + } + + if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) { + queue_free_callback_para(para); + return G_SOURCE_REMOVE; + } + + if (is_toplevel != FALSE) { + gint n = + jaw_toplevel_remove_window(JAW_TOPLEVEL(atk_get_root()), atk_obj); + if (n != -1) { + g_object_notify(G_OBJECT(atk_get_root()), "accessible-name"); + g_signal_emit_by_name(ATK_OBJECT(atk_get_root()), + "children-changed::remove", n, atk_obj); + g_signal_emit_by_name(atk_obj, "destroy"); + } + } + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowClose( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext, jboolean jIsToplevel) { + JAW_DEBUG("%p, %p, %p, %d", jniEnv, jClass, jAccContext, jIsToplevel); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + para->is_toplevel = jIsToplevel; + jni_main_idle_add(window_close_handler, para); +} + +static gboolean window_minimize_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + g_signal_emit_by_name(atk_obj, "minimize"); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMinimize( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(window_minimize_handler, para); +} + +static gboolean window_maximize_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + + if (!atk_obj) { + g_warning("%s: atk_obj is NULL", G_STRFUNC); + queue_free_callback_para(para); + return FALSE; + } + + g_signal_emit_by_name(atk_obj, "maximize"); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMaximize( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(window_maximize_handler, para); +} + +static gboolean window_restore_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + g_signal_emit_by_name(atk_obj, "restore"); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowRestore( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(window_restore_handler, para); +} + +static gboolean window_activate_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + g_warning("%s: atk_obj is NULL", G_STRFUNC); + queue_free_callback_para(para); + return FALSE; + } + + g_signal_emit_by_name(atk_obj, "activate"); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowActivate( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(window_activate_handler, para); +} + +static gboolean window_deactivate_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + g_signal_emit_by_name(atk_obj, "deactivate"); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowDeactivate( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(window_deactivate_handler, para); +} + +static gboolean window_state_change_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + g_signal_emit_by_name(atk_obj, "state-change", 0, 0); + + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_windowStateChange(JNIEnv *jniEnv, + jclass jClass, + jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(window_state_change_handler, para); +} + +static gint get_int_value(JNIEnv *jniEnv, jobject o) { + JAW_DEBUG("%p, %p", jniEnv, o); + + if (!atk_wrapper_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize cache", G_STRFUNC); + return 0; + } + + return (gint)(*jniEnv)->CallIntMethod(jniEnv, o, + cachedWrapperIntValueMethod); +} + +static gchar *get_string_value(JNIEnv *jniEnv, jobject o) { + JAW_DEBUG("%p, %p", jniEnv, o); + if (o == NULL) { + g_warning("%s: o is NULL", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("Failed to create a new local reference frame"); + return NULL; + } + + jclass objClass = (*jniEnv)->GetObjectClass(jniEnv, o); + if (!objClass) { + g_warning("%s: GetObjectClass failed", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jmethodID jmid = (*jniEnv)->GetMethodID(jniEnv, objClass, "toString", + "()Ljava/lang/String;"); + if (!jmid) { + g_warning("%s: GetMethodID for toString failed", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jstring jstr = (jstring)(*jniEnv)->CallObjectMethod(jniEnv, o, jmid); + if (!jstr) { + g_warning("%s: CallObjectMethod failed", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + const char *nativeStr = (*jniEnv)->GetStringUTFChars(jniEnv, jstr, NULL); + if (!nativeStr) { + g_warning("%s: nativeStr is NULL", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + gchar *result = g_strdup(nativeStr); + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, nativeStr); + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +static gboolean signal_emit_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (!jniEnv) { + g_warning("%s: jaw_util_get_jni_env failed", G_STRFUNC); + queue_free_callback_para(para); + return FALSE; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("Failed to create a new local reference frame"); + queue_free_callback_para(para); + return FALSE; + } + + jobjectArray args = para->args; + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + + if (para->signal_id == + org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED) { + pthread_mutex_lock(&jaw_vdc_dup_mutex); + if ((*jniEnv)->IsSameObject(jniEnv, jaw_vdc_last_ac, para->global_ac)) { + /* So we will be sending the visible data changed event. If any + * other comes, we will want to send it */ + jaw_vdc_clear_last_ac(jniEnv); + } + pthread_mutex_unlock(&jaw_vdc_dup_mutex); + } + + switch (para->signal_id) { + case org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED: { + jobject objectArrayElement = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement) { + break; + } + gint cursor_pos = get_int_value(jniEnv, objectArrayElement); + + g_signal_emit_by_name(atk_obj, "text_caret_moved", cursor_pos); + break; + } + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT: { + jobject objectArrayElement0 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement0) { + break; + } + gint insert_position = get_int_value(jniEnv, objectArrayElement0); + + jobject objectArrayElement1 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1); + if (!objectArrayElement1) { + break; + } + gint insert_length = get_int_value(jniEnv, objectArrayElement1); + + jobject objectArrayElement2 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 2); + if (!objectArrayElement2) { + break; + } + gchar *insert_text = get_string_value(jniEnv, objectArrayElement2); + + g_signal_emit_by_name(atk_obj, "text_insert", insert_position, + insert_length, insert_text); + + g_free(insert_text); + break; + } + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE: { + jobject objectArrayElement0 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement0) { + break; + } + gint delete_position = get_int_value(jniEnv, objectArrayElement0); + + jobject objectArrayElement1 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1); + if (!objectArrayElement1) { + break; + } + gint delete_length = get_int_value(jniEnv, objectArrayElement1); + + jobject objectArrayElement2 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 2); + if (!objectArrayElement2) { + break; + } + gchar *delete_text = get_string_value(jniEnv, objectArrayElement2); + + g_signal_emit_by_name(atk_obj, "text_remove", delete_position, + delete_length, delete_text); + + g_free(delete_text); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD: { + jobject objectArrayElement0 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement0) { + break; + } + gint child_index = get_int_value(jniEnv, objectArrayElement0); + + g_signal_emit_by_name(atk_obj, "children_changed::add", child_index, + para->child_impl); + + if (atk_obj != NULL) { + g_object_ref(G_OBJECT(atk_obj)); + } + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE: { + jobject objectArrayElement0 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement0) { + break; + } + gint child_index = get_int_value(jniEnv, objectArrayElement0); + + jobject child_ac = (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1); + if (!child_ac) { + break; + } + JawImpl *child_impl = jaw_impl_find_instance(jniEnv, child_ac); + if (!child_impl) { + break; + } + + g_signal_emit_by_name(atk_obj, "children_changed::remove", child_index, + child_impl); + + if (atk_obj != NULL) { + g_object_unref(G_OBJECT(atk_obj)); + } + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED: { + g_signal_emit_by_name(atk_obj, "active_descendant_changed", + para->child_impl); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED: { + g_signal_emit_by_name(atk_obj, "selection_changed"); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED: { + g_signal_emit_by_name(atk_obj, "visible_data_changed"); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS: { + jobject objectArrayElement0 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement0) { + break; + } + gint oldValue = get_int_value(jniEnv, objectArrayElement0); + jobject objectArrayElement1 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1); + if (!objectArrayElement1) { + break; + } + gint newValue = get_int_value(jniEnv, objectArrayElement1); + + AtkPropertyValues values = {NULL}; + + // GValues must be initialized + g_assert(!G_VALUE_HOLDS_INT(&values.old_value)); + g_value_init(&values.old_value, G_TYPE_INT); + g_assert(G_VALUE_HOLDS_INT(&values.old_value)); + g_value_set_int(&values.old_value, oldValue); + if (jaw_debug) + printf("%d\n", g_value_get_int(&values.old_value)); + + g_assert(!G_VALUE_HOLDS_INT(&values.new_value)); + g_value_init(&values.new_value, G_TYPE_INT); + g_assert(G_VALUE_HOLDS_INT(&values.new_value)); + g_value_set_int(&values.new_value, newValue); + if (jaw_debug) + printf("%d\n", g_value_get_int(&values.new_value)); + + values.property_name = "accessible-actions"; + + g_signal_emit_by_name(atk_obj, "property_change::accessible-actions", + &values); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE: { + g_object_notify(G_OBJECT(atk_obj), "accessible-value"); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION: { + g_object_notify(G_OBJECT(atk_obj), "accessible-description"); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME: { + g_object_notify(G_OBJECT(atk_obj), "accessible-name"); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-hypertext-offset", NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-table-caption", NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-table-summary", NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-table-column-header", NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-table-column-description", + NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-table-row-header", NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION: { + g_signal_emit_by_name( + atk_obj, "property_change::accessible-table-row-description", NULL); + break; + } + case org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED: { + g_signal_emit_by_name(atk_obj, "model_changed"); + break; + } + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED: { + JawObject *jaw_obj = JAW_OBJECT(atk_obj); + + jobject objectArrayElement0 = + (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (!objectArrayElement0) { + break; + } + gint newValue = get_int_value(jniEnv, objectArrayElement0); + + gint prevCount = GPOINTER_TO_INT( + g_hash_table_lookup(jaw_obj->storedData, "Previous_Count")); + gint curCount = atk_text_get_character_count(ATK_TEXT(jaw_obj)); + + g_hash_table_insert(jaw_obj->storedData, (gpointer) "Previous_Count", + GINT_TO_POINTER(curCount)); + + /* + * The "text_changed" signal was deprecated, but only for performance + * reasons: + * https://mail.gnome.org/archives/gnome-accessibility-devel/2010-December/msg00007.html. + * + * Since there is no information about the string in this case, we + * cannot use "AtkObject::text-insert" or "AtkObject::text-remove", so + * we continue using the "text_changed" signal. + */ + if (curCount > prevCount) { + g_signal_emit_by_name(atk_obj, "text_changed::insert", newValue, + curCount - prevCount); + } else if (curCount < prevCount) { + g_signal_emit_by_name(atk_obj, "text_changed::delete", newValue, + prevCount - curCount); + } + break; + } + default: + break; + } + + queue_free_callback_para(para); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_emitSignal( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext, jint id, + jobjectArray args) { + JAW_DEBUG("%p, %p, %p, %d, %p", jniEnv, jClass, jAccContext, id, args); + + pthread_mutex_lock(&jaw_vdc_dup_mutex); + if (id != org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED) { + /* Something may have happened since the last visible data changed + * event, so we want to sent it again */ + jaw_vdc_clear_last_ac(jniEnv); + } else { + if ((*jniEnv)->IsSameObject(jniEnv, jaw_vdc_last_ac, jAccContext)) { + /* We have already queued to send one and nothing happened in + * between, this one is really useless */ + pthread_mutex_unlock(&jaw_vdc_dup_mutex); + return; + } + + jaw_vdc_clear_last_ac(jniEnv); + jaw_vdc_last_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext); + } + pthread_mutex_unlock(&jaw_vdc_dup_mutex); + + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + + jobject global_ac = (*jniEnv)->NewGlobalRef( + jniEnv, + jAccContext); // `free_callback_para` is responsible for deleting it + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jobjectArray global_args = + (jobjectArray)(*jniEnv)->NewGlobalRef(jniEnv, args); + para->signal_id = (gint)id; + para->args = global_args; + + switch (para->signal_id) { + case org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED: + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT: + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE: + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_REPLACE: + case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE: + case org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED: + case org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER: + case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION: + case org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED: + case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED: + default: + break; + case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD: { + jobject child_ac = (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1); + if (child_ac == NULL) { + g_warning("%s: GetObjectArrayElement failed for child_ac", + G_STRFUNC); + queue_free_callback_para(para); + return; + } + JawImpl *child_impl = jaw_impl_find_instance(jniEnv, child_ac); + (*jniEnv)->DeleteLocalRef(jniEnv, child_ac); + if (child_impl == NULL) { + g_warning("%s: child_impl == NULL, return NULL", G_STRFUNC); + queue_free_callback_para(para); + return; + } + g_object_ref(G_OBJECT(child_impl)); + para->child_impl = child_impl; + break; + } + case org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED: { + jobject child_ac = (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0); + if (child_ac == NULL) { + g_warning("%s: child_ac == NULL, return NULL", G_STRFUNC); + queue_free_callback_para(para); + return; + } + JawImpl *child_impl = jaw_impl_find_instance(jniEnv, child_ac); + (*jniEnv)->DeleteLocalRef(jniEnv, child_ac); + if (child_impl == NULL) { + g_warning("%s: child_impl == NULL, return NULL", G_STRFUNC); + queue_free_callback_para(para); + return; + } + g_object_ref(G_OBJECT(child_impl)); + para->child_impl = child_impl; + break; + } + } + + jni_main_idle_add(signal_emit_handler, + para); // calls `queue_free_callback_para` +} + +static gboolean object_state_change_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + g_warning("%s: atk_obj is NULL", G_STRFUNC); + queue_free_callback_para(para); + return FALSE; + } + + atk_object_notify_state_change(atk_obj, para->atk_state, para->state_value); + + queue_free_callback_para(para); + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_objectStateChange(JNIEnv *jniEnv, + jclass jClass, + jobject jAccContext, + jobject state, + jboolean value) { + JAW_DEBUG("%p, %p, %p, %p, %d", jniEnv, jClass, jAccContext, state, value); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext); + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + AtkStateType state_type = + jaw_util_get_atk_state_type_from_java_state(jniEnv, state); + para->atk_state = state_type; + para->state_value = value; + jni_main_idle_add(object_state_change_handler, para); +} + +static gboolean component_added_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) { + atk_object_notify_state_change(atk_obj, ATK_STATE_SHOWING, 1); + } + + queue_free_callback_para(para); + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentAdded( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext); + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(component_added_handler, para); +} + +static gboolean component_removed_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + g_warning("%s: atk_obj is NULL", G_STRFUNC); + queue_free_callback_para(para); + return FALSE; + } + + if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) + atk_object_notify_state_change(atk_obj, ATK_STATE_SHOWING, FALSE); + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentRemoved( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext); + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(component_removed_handler, para); +} + +/** + * Signal is emitted when the position or size of the component changes. + */ +static gboolean bounds_changed_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackPara *para = (CallbackPara *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + + AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl); + if (!atk_obj) { + queue_free_callback_para(para); + return FALSE; + } + + AtkRectangle rect; + rect.x = -1; + rect.y = -1; + rect.width = -1; + rect.height = -1; + g_signal_emit_by_name(atk_obj, "bounds_changed", &rect); + queue_free_callback_para(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_boundsChanged( + JNIEnv *jniEnv, jclass jClass, jobject jAccContext) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext); + if (!jAccContext) { + g_warning("%s: jAccContext is NULL", G_STRFUNC); + return; + } + jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext); + callback_para_process_frees(); + CallbackPara *para = alloc_callback_para(jniEnv, global_ac); + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac); + return; + } + jni_main_idle_add(bounds_changed_handler, para); +} + +static gboolean key_dispatch_handler(gpointer p) { + JAW_DEBUG("%p", p); + CallbackParaEvent *para = (CallbackParaEvent *)p; + if (para == NULL) { + g_warning("%s: para is NULL", G_STRFUNC); + return FALSE; + } + jobject jAtkKeyEvent = para->global_event; + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jaw_util_get_jni_env == NULL", G_STRFUNC); + queue_free_callback_para_event(para); + return G_SOURCE_REMOVE; + } + + AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1); + + if (!atk_wrapper_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize cache", G_STRFUNC); + g_free(event); + queue_free_callback_para_event(para); + return G_SOURCE_REMOVE; + } + + jint type = + (*jniEnv)->GetIntField(jniEnv, jAtkKeyEvent, cachedWrapperTypeFieldID); + if (type == -1) { + g_warning("%s: Unknown key event type (-1) received; cleaning up and " + "removing source", + G_STRFUNC); + g_free(event); + queue_free_callback_para_event(para); + return G_SOURCE_REMOVE; + } + + jint type_pressed = (*jniEnv)->GetStaticIntField( + jniEnv, cachedWrapperAtkKeyEventClass, cachedWrapperTypePressedFieldID); + + jint type_released = + (*jniEnv)->GetStaticIntField(jniEnv, cachedWrapperAtkKeyEventClass, + cachedWrapperTypeReleasedFieldID); + + if (type == type_pressed) { + event->type = ATK_KEY_EVENT_PRESS; + } else if (type == type_released) { + event->type = ATK_KEY_EVENT_RELEASE; + } else { + g_warning("%s: Unknown key event type (%d) received; cleaning up and " + "removing source", + G_STRFUNC, type); + g_free(event); + queue_free_callback_para_event(para); + return G_SOURCE_REMOVE; + } + + // state: shift + jboolean jShiftKeyDown = (*jniEnv)->GetBooleanField( + jniEnv, jAtkKeyEvent, cachedWrapperShiftFieldID); + if (jShiftKeyDown != FALSE) { + event->state |= GDK_SHIFT_MASK; + } + + // state: ctrl + jboolean jCtrlKeyDown = (*jniEnv)->GetBooleanField( + jniEnv, jAtkKeyEvent, cachedWrapperCtrlFieldID); + if (jCtrlKeyDown != FALSE) { + event->state |= GDK_CONTROL_MASK; + } + + // state: alt + jboolean jAltKeyDown = (*jniEnv)->GetBooleanField(jniEnv, jAtkKeyEvent, + cachedWrapperAltFieldID); + if (jAltKeyDown != FALSE) { + event->state |= GDK_MOD1_MASK; + } + + // state: meta + jboolean jMetaKeyDown = (*jniEnv)->GetBooleanField( + jniEnv, jAtkKeyEvent, cachedWrapperMetaFieldID); + if (jMetaKeyDown != FALSE) { + event->state |= GDK_META_MASK; + } + + // state: alt gr + jboolean jAltGrKeyDown = (*jniEnv)->GetBooleanField( + jniEnv, jAtkKeyEvent, cachedWrapperAltGrFieldID); + if (jAltGrKeyDown != FALSE) { + event->state |= GDK_MOD5_MASK; + } + + // keyval + event->keyval = (*jniEnv)->GetIntField(jniEnv, jAtkKeyEvent, + cachedWrapperKeyvalFieldID); + + // string + jstring jstr = (jstring)(*jniEnv)->GetObjectField( + jniEnv, jAtkKeyEvent, cachedWrapperStringFieldID); + if (jstr != NULL) { + event->length = (gint)(*jniEnv)->GetStringLength(jniEnv, jstr); + + const gchar *tmp_string = (*jniEnv)->GetStringUTFChars(jniEnv, jstr, 0); + if (tmp_string != NULL) { + event->string = g_strdup(tmp_string); + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, tmp_string); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jstr); + jstr = NULL; + } + + // keycode + event->keycode = (gint)(*jniEnv)->GetIntField(jniEnv, jAtkKeyEvent, + cachedWrapperKeycodeFieldID); + + // timestamp + event->timestamp = (gint64)(*jniEnv)->GetIntField( + jniEnv, jAtkKeyEvent, cachedWrapperTimestampFieldID); + + jaw_util_dispatch_key_event(event); + + // clean up + g_free(event->string); + g_free(event); + queue_free_callback_para_event(para); + + return G_SOURCE_REMOVE; +} + +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_dispatchKeyEvent( + JNIEnv *jniEnv, jclass jClass, jobject jAtkKeyEvent) { + JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAtkKeyEvent); + jobject global_key_event = (*jniEnv)->NewGlobalRef(jniEnv, jAtkKeyEvent); + callback_para_event_process_frees(); + CallbackParaEvent *para = + alloc_callback_para_event(jniEnv, global_key_event); + if (para == NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, global_key_event); + return; + } + jni_main_idle_add(key_dispatch_handler, para); +} + +static gboolean atk_wrapper_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&wrapper_cache_mutex); + + if (wrapper_cache_initialized) { + g_mutex_unlock(&wrapper_cache_mutex); + return TRUE; + } + + jclass localIntegerClass = + (*jniEnv)->FindClass(jniEnv, "java/lang/Integer"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localIntegerClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Integer class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedWrapperIntegerClass = + (*jniEnv)->NewGlobalRef(jniEnv, localIntegerClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localIntegerClass); + + if (cachedWrapperIntegerClass == NULL) { + g_warning("%s: Failed to create global reference for Integer class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedWrapperIntValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedWrapperIntegerClass, "intValue", "()I"); + + jclass localAtkKeyEventClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkKeyEvent"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkKeyEventClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkKeyEvent class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedWrapperAtkKeyEventClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAtkKeyEventClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localAtkKeyEventClass); + + if (cachedWrapperAtkKeyEventClass == NULL) { + g_warning("%s: Failed to create global reference for AtkKeyEvent class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedWrapperTypeFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "type", "I"); + cachedWrapperTypePressedFieldID = (*jniEnv)->GetStaticFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "ATK_KEY_EVENT_PRESSED", "I"); + cachedWrapperTypeReleasedFieldID = (*jniEnv)->GetStaticFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "ATK_KEY_EVENT_RELEASED", "I"); + cachedWrapperShiftFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "isShiftKeyDown", "Z"); + cachedWrapperCtrlFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "isCtrlKeyDown", "Z"); + cachedWrapperAltFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "isAltKeyDown", "Z"); + cachedWrapperMetaFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "isMetaKeyDown", "Z"); + cachedWrapperAltGrFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "isAltGrKeyDown", "Z"); + cachedWrapperKeyvalFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "keyval", "I"); + cachedWrapperStringFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "string", "Ljava/lang/String;"); + cachedWrapperKeycodeFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "keycode", "I"); + cachedWrapperTimestampFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedWrapperAtkKeyEventClass, "timestamp", "J"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedWrapperIntValueMethod == NULL || + cachedWrapperTypeFieldID == NULL || + cachedWrapperTypePressedFieldID == NULL || + cachedWrapperTypeReleasedFieldID == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to cache one or more method/field IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + wrapper_cache_initialized = TRUE; + g_mutex_unlock(&wrapper_cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + return TRUE; + +cleanup_and_fail: + if (cachedWrapperIntegerClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperIntegerClass); + cachedWrapperIntegerClass = NULL; + } + cachedWrapperIntValueMethod = NULL; + + if (cachedWrapperAtkKeyEventClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperAtkKeyEventClass); + cachedWrapperAtkKeyEventClass = NULL; + } + cachedWrapperTypeFieldID = NULL; + cachedWrapperTypePressedFieldID = NULL; + cachedWrapperTypeReleasedFieldID = NULL; + cachedWrapperShiftFieldID = NULL; + cachedWrapperCtrlFieldID = NULL; + cachedWrapperAltFieldID = NULL; + cachedWrapperMetaFieldID = NULL; + cachedWrapperAltGrFieldID = NULL; + cachedWrapperKeyvalFieldID = NULL; + cachedWrapperStringFieldID = NULL; + cachedWrapperKeycodeFieldID = NULL; + cachedWrapperTimestampFieldID = NULL; + + g_mutex_unlock(&wrapper_cache_mutex); + return FALSE; +} + +void atk_wrapper_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&wrapper_cache_mutex); + + if (cachedWrapperIntegerClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperIntegerClass); + cachedWrapperIntegerClass = NULL; + } + cachedWrapperIntValueMethod = NULL; + + if (cachedWrapperAtkKeyEventClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperAtkKeyEventClass); + cachedWrapperAtkKeyEventClass = NULL; + } + cachedWrapperTypeFieldID = NULL; + cachedWrapperTypePressedFieldID = NULL; + cachedWrapperTypeReleasedFieldID = NULL; + cachedWrapperShiftFieldID = NULL; + cachedWrapperCtrlFieldID = NULL; + cachedWrapperAltFieldID = NULL; + cachedWrapperMetaFieldID = NULL; + cachedWrapperAltGrFieldID = NULL; + cachedWrapperKeyvalFieldID = NULL; + cachedWrapperStringFieldID = NULL; + cachedWrapperKeycodeFieldID = NULL; + cachedWrapperTimestampFieldID = NULL; + wrapper_cache_initialized = FALSE; + + g_mutex_unlock(&wrapper_cache_mutex); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.h b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.h new file mode 100644 index 000000000000..f517e6cc7a38 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.h @@ -0,0 +1,178 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_GNOME_Accessibility_AtkWrapper */ + +#ifndef _Included_org_GNOME_Accessibility_AtkWrapper +#define _Included_org_GNOME_Accessibility_AtkWrapper +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: initNativeLibrary + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_initNativeLibrary(void); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: loadAtkBridge + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_loadAtkBridge(void); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: createNativeResources + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT jlong JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_createNativeResources(JNIEnv *, jclass, + jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: releaseNativeResources + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_releaseNativeResources(JNIEnv *, jclass, + jlong); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: focusNotify + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_focusNotify(JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowOpen + * Signature: (Ljavax/accessibility/AccessibleContext;Z)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowOpen( + JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowClose + * Signature: (Ljavax/accessibility/AccessibleContext;Z)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowClose( + JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowMinimize + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMinimize( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowMaximize + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMaximize( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowRestore + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowRestore( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowActivate + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowActivate( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowDeactivate + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowDeactivate( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: windowStateChange + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_windowStateChange(JNIEnv *, jclass, + jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: emitSignal + * Signature: (Ljavax/accessibility/AccessibleContext;I[Ljava/lang/Object;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_emitSignal( + JNIEnv *, jclass, jobject, jint, jobjectArray); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: objectStateChange + * Signature: (Ljavax/accessibility/AccessibleContext;Ljava/lang/Object;Z)V + */ +JNIEXPORT void JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_objectStateChange(JNIEnv *, jclass, + jobject, jobject, + jboolean); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: componentAdded + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentAdded( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: componentRemoved + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentRemoved( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: boundsChanged + * Signature: (Ljavax/accessibility/AccessibleContext;)V + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_boundsChanged( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: dispatchKeyEvent + * Signature: (Lorg/GNOME/Accessibility/AtkKeyEvent;)Z + */ +JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_dispatchKeyEvent( + JNIEnv *, jclass, jobject); + +/* + * Class: org_GNOME_Accessibility_AtkWrapper + * Method: getInstance + * Signature: (Ljavax/accessibility/AccessibleContext;)J + */ +JNIEXPORT jlong JNICALL +Java_org_GNOME_Accessibility_AtkWrapper_getInstance(JNIEnv *, jclass, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawaction.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawaction.c new file mode 100644 index 000000000000..e4e5fe8eaf51 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawaction.c @@ -0,0 +1,633 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkAction: + * + * The ATK interface provided by UI components + * which the user can activate/interact with. + * + * #AtkAction should be implemented by instances of #AtkObject classes + * with which the user can interact directly, i.e. buttons, + * checkboxes, scrollbars, e.g. components which are not "passive" + * providers of UI information. + * + * Exceptions: when the user interaction is already covered by another + * appropriate interface such as #AtkEditableText (insert/delete text, + * etc.) or #AtkValue (set value) then these actions should not be + * exposed by #AtkAction as well. + * + * Though most UI interactions on components should be invocable via + * keyboard as well as mouse, there will generally be a close mapping + * between "mouse actions" that are possible on a component and the + * AtkActions. Where mouse and keyboard actions are redundant in + * effect, #AtkAction should expose only one action rather than + * exposing redundant actions if possible. By convention we have been + * using "mouse centric" terminology for #AtkAction names. + * + */ + +static gboolean jaw_action_do_action(AtkAction *action, gint i); +static gint jaw_action_get_n_actions(AtkAction *action); +static const gchar *jaw_action_get_description(AtkAction *action, gint i); +static gboolean jaw_action_set_description(AtkAction *action, gint i, + const gchar *description); +static const gchar *jaw_action_get_localized_name(AtkAction *action, gint i); + +static jclass cachedActionAtkActionClass = NULL; +static jmethodID cachedActionCreateAtkActionMethod = NULL; +static jmethodID cachedActionDoActionMethod = NULL; +static jmethodID cachedActionGetNActionsMethod = NULL; +static jmethodID cachedActionGetDescriptionMethod = NULL; +static jmethodID cachedActionSetDescriptionMethod = NULL; +static jmethodID cachedActionGetLocalizedNameMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +/** + * jaw_action_init_jni_cache: + * @jniEnv: JNI environment + * + * Initializes and caches JNI class and method references for performance. + * This avoids repeated expensive JNI lookups on every method call. + * + * Returns: %TRUE if initialization succeeded, %FALSE otherwise + **/ +static gboolean jaw_action_init_jni_cache(JNIEnv *jniEnv); + +typedef struct _ActionData { + jobject atk_action; + const gchar *localized_name; + jstring jstrLocalizedName; + const gchar *action_description; + jstring jstrActionDescription; + GMutex mutex; +} ActionData; + +#define JAW_GET_ACTION(action, def_ret) \ + JAW_GET_OBJ_IFACE(action, \ + org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION, \ + ActionData, atk_action, jniEnv, atk_action, def_ret) + +/** + * AtkActionIface: + * @do_action: + * @get_n_actions: + * @get_description: + * @get_name: + * @get_keybinding: + * @set_description: + * @get_localized_name: + **/ +void jaw_action_interface_init(AtkActionIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument iface passed to the function", G_STRFUNC); + return; + } + + iface->do_action = jaw_action_do_action; + iface->get_n_actions = jaw_action_get_n_actions; + iface->get_description = jaw_action_get_description; + iface->get_name = jaw_action_get_description; + iface->get_keybinding = + NULL; // missing java support: There is no dependency between + // javax.accessibility.AccessibleAction and keybindings, + // so there is no way to return the correct keybinding based on + // AccessibleContext. + iface->set_description = jaw_action_set_description; + iface->get_localized_name = jaw_action_get_localized_name; +} + +gpointer jaw_action_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument ac passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_action_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_action = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedActionAtkActionClass, cachedActionCreateAtkActionMethod, + ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_action == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create AtkAction Java object via " + "create_atk_action()", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + ActionData *data = g_new0(ActionData, 1); + g_mutex_init(&data->mutex); + data->atk_action = (*jniEnv)->NewGlobalRef(jniEnv, jatk_action); + if (data->atk_action == NULL) { + g_warning("%s: Failed to create global ref for atk_action", G_STRFUNC); + g_mutex_clear(&data->mutex); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_action_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_debug("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + ActionData *data = (ActionData *)p; + if (data == NULL) { + g_warning("%s: data is null after cast", G_STRFUNC); + return; + } + + g_mutex_lock(&data->mutex); + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->jstrLocalizedName != NULL) { + if (data->localized_name != NULL) { + (*jniEnv)->ReleaseStringUTFChars( + jniEnv, data->jstrLocalizedName, data->localized_name); + data->localized_name = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrLocalizedName); + data->jstrLocalizedName = NULL; + } + + if (data->jstrActionDescription != NULL) { + if (data->action_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, + data->jstrActionDescription, + data->action_description); + data->action_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrActionDescription); + data->jstrActionDescription = NULL; + } + + if (data->atk_action != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_action); + data->atk_action = NULL; + } + } + + g_mutex_unlock(&data->mutex); + g_mutex_clear(&data->mutex); + g_free(data); +} + +/** + * jaw_action_do_action: + * @action: a #GObject instance that implements AtkActionIface + * @i: the action index corresponding to the action to be performed + * + * Perform the specified action on the object. + * + * Returns: %TRUE if success, %FALSE otherwise + **/ +static gboolean jaw_action_do_action(AtkAction *action, gint i) { + JAW_DEBUG("%p, %d", action, i); + + if (action == NULL) { + g_warning("%s: Null action passed (index=%d)", G_STRFUNC, i); + return FALSE; + } + + JAW_GET_ACTION(action, + FALSE); // create local JNI reference `jobject atk_action` + + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_action, cachedActionDoActionMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + + return jresult; +} + +/** + * jaw_action_get_n_actions: + * @action: a #GObject instance that implements AtkActionIface + * + * Gets the number of accessible actions available on the object. + * If there are more than one, the first one is considered the + * "default" action of the object. + * + * Returns: the number of actions, or 0 if @action does not + * implement this interface. + **/ +static gint jaw_action_get_n_actions(AtkAction *action) { + JAW_DEBUG("%p", action); + + if (action == NULL) { + g_warning("%s: Null action passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_ACTION(action, + 0); // create local JNI reference `jobject atk_action` + + gint ret = (gint)(*jniEnv)->CallIntMethod(jniEnv, atk_action, + cachedActionGetNActionsMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + + return ret; +} + +/** + * jaw_action_get_description: + * @action: a #GObject instance that implements AtkActionIface + * @i: the action index corresponding to the action to be performed + * + * Returns a description of the specified action of the object. + * + * Returns: (nullable): a description string for action @i, or %NULL if + * @action does not implement this interface or if an error occurs. + **/ +static const gchar *jaw_action_get_description(AtkAction *action, gint i) { + JAW_DEBUG("%p, %d", action, i); + + if (action == NULL) { + g_warning("%s: Null action passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_ACTION(action, + NULL); // create local JNI reference `jobject atk_action` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallObjectMethod( + jniEnv, atk_action, cachedActionGetDescriptionMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_debug("%s: No description available for action (index=%d, action=%p)", + G_STRFUNC, i, action); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&data->mutex); + if (data->jstrActionDescription != NULL) { + if (data->action_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars( + jniEnv, data->jstrActionDescription, data->action_description); + data->action_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrActionDescription); + data->jstrActionDescription = NULL; + } + + data->jstrActionDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (data->jstrActionDescription == NULL) { + g_mutex_unlock(&data->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + data->action_description = + (*jniEnv)->GetStringUTFChars(jniEnv, data->jstrActionDescription, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || data->action_description == NULL) { + jaw_jni_clear_exception(jniEnv); + + // data->jstrActionDescription != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrActionDescription); + data->jstrActionDescription = NULL; + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + g_mutex_unlock(&data->mutex); + return NULL; + } + + g_mutex_unlock(&data->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data->action_description; +} + +/** + * jaw_action_set_description: + * @action: a #GObject instance that implements AtkActionIface + * @i: the action index corresponding to the action to be performed + * @description: the description to be assigned to this action + * + * Returns: %TRUE if the description was successfully set, %FALSE otherwise. + **/ +static gboolean jaw_action_set_description(AtkAction *action, gint i, + const gchar *description) { + JAW_DEBUG("%p, %d, %s", action, i, description); + + if (action == NULL) { + g_warning("%s: Null action passed (index=%d)", G_STRFUNC, i); + return FALSE; + } + if (description == NULL) { + g_warning("%s: Null description passed (index=%d)", G_STRFUNC, i); + return FALSE; + } + + JAW_GET_ACTION(action, + FALSE); // create local JNI reference `jobject atk_action` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return FALSE; + } + + jstring jdescription = (*jniEnv)->NewStringUTF(jniEnv, description); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jdescription == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create Java string for description", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; + } + + jboolean jisset = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_action, cachedActionSetDescriptionMethod, (jint)i, + (jstring)jdescription); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return jisset; +} + +/** + * jaw_action_get_localized_name: + * @action: a #GObject instance that implements AtkActionIface + * @i: the action index corresponding to the action to be performed + * + * Returns: (nullable): a localized name string for action @i, or %NULL + * if @action does not implement this interface or if an error occurs. + **/ +static const gchar *jaw_action_get_localized_name(AtkAction *action, gint i) { + JAW_DEBUG("%p, %d", action, i); + + if (action == NULL) { + g_warning("%s: Null argument action passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_ACTION(action, + NULL); // create local JNI reference `jobject atk_action` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallObjectMethod( + jniEnv, atk_action, cachedActionGetLocalizedNameMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_debug( + "%s: No localized name available for action (index=%d, action=%p)", + G_STRFUNC, i, action); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&data->mutex); + if (data->jstrLocalizedName != NULL) { + if (data->localized_name != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, data->jstrLocalizedName, + data->localized_name); + data->localized_name = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrLocalizedName); + data->jstrLocalizedName = NULL; + } + data->jstrLocalizedName = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (data->jstrLocalizedName == NULL) { + g_mutex_unlock(&data->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + data->localized_name = + (*jniEnv)->GetStringUTFChars(jniEnv, data->jstrLocalizedName, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || data->localized_name == NULL) { + jaw_jni_clear_exception(jniEnv); + + // data->jstrLocalizedName != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrLocalizedName); + data->jstrLocalizedName = NULL; + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + g_mutex_unlock(&data->mutex); + return NULL; + } + + g_mutex_unlock(&data->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_action); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data->localized_name; +} + +static gboolean jaw_action_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkAction"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkAction class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedActionAtkActionClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedActionAtkActionClass == NULL) { + g_warning("%s: Failed to create global reference for AtkAction class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedActionCreateAtkActionMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedActionAtkActionClass, "create_atk_action", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkAction;"); + + cachedActionDoActionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedActionAtkActionClass, "do_action", "(I)Z"); + + cachedActionGetNActionsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedActionAtkActionClass, "get_n_actions", "()I"); + + cachedActionGetDescriptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedActionAtkActionClass, + "get_description", "(I)Ljava/lang/String;"); + + cachedActionSetDescriptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedActionAtkActionClass, + "set_description", "(ILjava/lang/String;)Z"); + + cachedActionGetLocalizedNameMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedActionAtkActionClass, + "get_localized_name", "(I)Ljava/lang/String;"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedActionCreateAtkActionMethod == NULL || + cachedActionDoActionMethod == NULL || + cachedActionGetNActionsMethod == NULL || + cachedActionGetDescriptionMethod == NULL || + cachedActionSetDescriptionMethod == NULL || + cachedActionGetLocalizedNameMethod == NULL) { + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkAction method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedActionAtkActionClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedActionAtkActionClass); + cachedActionAtkActionClass = NULL; + } + cachedActionCreateAtkActionMethod = NULL; + cachedActionDoActionMethod = NULL; + cachedActionGetNActionsMethod = NULL; + cachedActionGetDescriptionMethod = NULL; + cachedActionSetDescriptionMethod = NULL; + cachedActionGetLocalizedNameMethod = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_action_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedActionAtkActionClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedActionAtkActionClass); + cachedActionAtkActionClass = NULL; + } + cachedActionCreateAtkActionMethod = NULL; + cachedActionDoActionMethod = NULL; + cachedActionGetNActionsMethod = NULL; + cachedActionGetDescriptionMethod = NULL; + cachedActionSetDescriptionMethod = NULL; + cachedActionGetLocalizedNameMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} \ No newline at end of file diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawcache.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcache.h new file mode 100644 index 000000000000..ab676066ccd6 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcache.h @@ -0,0 +1,74 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _JAW_CACHE_H_ +#define _JAW_CACHE_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void atk_wrapper_cache_cleanup(JNIEnv *jniEnv); +void jaw_action_cache_cleanup(JNIEnv *jniEnv); +void jaw_component_cache_cleanup(JNIEnv *jniEnv); +void jaw_editable_text_cache_cleanup(JNIEnv *jniEnv); +void jaw_hyperlink_cache_cleanup(JNIEnv *jniEnv); +void jaw_hypertext_cache_cleanup(JNIEnv *jniEnv); +void jaw_image_cache_cleanup(JNIEnv *jniEnv); +void jaw_impl_cache_cleanup(JNIEnv *jniEnv); +void jaw_object_cache_cleanup(JNIEnv *jniEnv); +void jaw_selection_cache_cleanup(JNIEnv *jniEnv); +void jaw_table_cache_cleanup(JNIEnv *jniEnv); +void jaw_tablecell_cache_cleanup(JNIEnv *jniEnv); +void jaw_text_cache_cleanup(JNIEnv *jniEnv); +void jaw_value_cache_cleanup(JNIEnv *jniEnv); +void jaw_util_cache_cleanup(JNIEnv *jniEnv); + +// TODO: currently leaking +static inline void jaw_cache_cleanup(JNIEnv *jniEnv) { + if (jniEnv == NULL) { + g_warning("%s: Null argument jniEnv passed to the function", G_STRFUNC); + return; + } + + atk_wrapper_cache_cleanup(jniEnv); + jaw_action_cache_cleanup(jniEnv); + jaw_component_cache_cleanup(jniEnv); + jaw_editable_text_cache_cleanup(jniEnv); + jaw_hyperlink_cache_cleanup(jniEnv); + jaw_hypertext_cache_cleanup(jniEnv); + jaw_image_cache_cleanup(jniEnv); + jaw_impl_cache_cleanup(jniEnv); + jaw_object_cache_cleanup(jniEnv); + jaw_selection_cache_cleanup(jniEnv); + jaw_table_cache_cleanup(jniEnv); + jaw_tablecell_cache_cleanup(jniEnv); + jaw_text_cache_cleanup(jniEnv); + jaw_value_cache_cleanup(jniEnv); + jaw_util_cache_cleanup(jniEnv); +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawcomponent.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcomponent.c new file mode 100644 index 000000000000..1a19e9027610 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcomponent.c @@ -0,0 +1,801 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include +#include + +/** + * (From Atk documentation) + * + * AtkComponent: + * + * The ATK interface provided by UI components + * which occupy a physical area on the screen. + * which the user can activate/interact with. + * + * #AtkComponent should be implemented by most if not all UI elements + * with an actual on-screen presence, i.e. components which can be + * said to have a screen-coordinate bounding box. Virtually all + * widgets will need to have #AtkComponent implementations provided + * for their corresponding #AtkObject class. In short, only UI + * elements which are *not* GUI elements will omit this ATK interface. + * + * A possible exception might be textual information with a + * transparent background, in which case text glyph bounding box + * information is provided by #AtkText. + */ + +static jclass cachedComponentAtkComponentClass = NULL; +static jmethodID cachedComponentCreateAtkComponentMethod = NULL; +static jmethodID cachedComponentContainsMethod = NULL; +static jmethodID cachedComponentGetAccessibleAtPointMethod = NULL; +static jmethodID cachedComponentGetExtentsMethod = NULL; +static jmethodID cachedComponentSetExtentsMethod = NULL; +static jmethodID cachedComponentSetPositionMethod = NULL; +static jmethodID cachedComponentSetSizeMethod = NULL; +static jmethodID cachedComponentGrabFocusMethod = NULL; +static jmethodID cachedComponentGetLayerMethod = NULL; +static jclass cachedComponentRectangleClass = NULL; +static jfieldID cachedComponentRectangleXField = NULL; +static jfieldID cachedComponentRectangleYField = NULL; +static jfieldID cachedComponentRectangleWidthField = NULL; +static jfieldID cachedComponentRectangleHeightField = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_component_init_jni_cache(JNIEnv *jniEnv); + +static gboolean jaw_component_contains(AtkComponent *component, gint x, gint y, + AtkCoordType coord_type); + +static AtkObject * +jaw_component_ref_accessible_at_point(AtkComponent *component, gint x, gint y, + AtkCoordType coord_type); + +static void jaw_component_get_extents(AtkComponent *component, gint *x, gint *y, + gint *width, gint *height, + AtkCoordType coord_type); + +static gboolean jaw_component_set_extents(AtkComponent *component, gint x, + gint y, gint width, gint height, + AtkCoordType coord_type); + +static gboolean jaw_component_set_size(AtkComponent *component, gint width, + gint height); + +static gboolean jaw_component_set_position(AtkComponent *component, gint x, + gint y, AtkCoordType coord_type); + +static gboolean jaw_component_grab_focus(AtkComponent *component); +static AtkLayer jaw_component_get_layer(AtkComponent *component); + +typedef struct _ComponentData { + jobject atk_component; +} ComponentData; + +#define JAW_GET_COMPONENT(component, def_ret) \ + JAW_GET_OBJ_IFACE( \ + component, org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT, \ + ComponentData, atk_component, jniEnv, atk_component, def_ret) + +/* + * Atk.Component Methods + * contains (x, y, coord_type) + * get_alpha () + * get_extents (coord_type) + * get_layer () + * get_mdi_zorder () + * get_position (coord_type) + * get_size () + * grab_focus () + * ref_accessible_at_point (x, y, coord_type) + * remove_focus_handler (handler_id) + * scroll_to (type) + * scroll_to_point (coords, x, y) + * set_extents (x, y, width, height, coord_type) + * set_position (x, y, coord_type) + * set_size (width, height) + */ +void jaw_component_interface_init(AtkComponentIface *iface, gpointer data) { + JAW_DEBUG("%p,%p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument passed to function", G_STRFUNC); + return; + } + + iface->contains = jaw_component_contains; + iface->get_alpha = NULL; // missing java support for iface->get_alpha + iface->get_layer = jaw_component_get_layer; + iface->get_extents = jaw_component_get_extents; + iface->get_mdi_zorder = NULL; // TODO: jaw_component_get_mdi_zorder; + // done by atk: iface->get_position (atk_component_real_get_position) + // done by atk: iface->get_size (atk_component_real_get_size) + iface->grab_focus = jaw_component_grab_focus; + iface->ref_accessible_at_point = jaw_component_ref_accessible_at_point; + iface->remove_focus_handler = + NULL; // deprecated: iface->remove_focus_handler + iface->scroll_to = NULL; // missing java support for iface->scroll_to + iface->scroll_to_point = + NULL; // missing java support for iface->scroll_to_point + iface->set_extents = jaw_component_set_extents; + iface->set_position = jaw_component_set_position; + iface->set_size = jaw_component_set_size; +} + +gpointer jaw_component_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument passed to function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_component_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_component = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedComponentAtkComponentClass, + cachedComponentCreateAtkComponentMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_component == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jatk_component using " + "create_atk_component method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + ComponentData *data = g_new0(ComponentData, 1); + data->atk_component = (*jniEnv)->NewGlobalRef(jniEnv, jatk_component); + if (data->atk_component == NULL) { + g_warning("%s: Failed to create global ref for atk_component", + G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_component_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_debug("%s: Null argument passed to function", G_STRFUNC); + return; + } + + ComponentData *data = (ComponentData *)p; + if (data == NULL) { + g_warning("%s: data is null after cast", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->atk_component != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_component); + data->atk_component = NULL; + } + } + + g_free(data); +} + +/** + * jaw_component_contains: + * @component: the #AtkComponent + * @x: x coordinate + * @y: y coordinate + * @coord_type: specifies whether the coordinates are relative to the screen + * or to the components top level window + * + * Checks whether the specified point is within the extent of the @component. + * + * Toolkit implementor note: ATK provides a default implementation for + * this virtual method. In general there are little reason to + * re-implement it. + * + * Returns: %TRUE or %FALSE indicating whether the specified point is within + * the extent of the @component or not + **/ +static gboolean jaw_component_contains(AtkComponent *component, gint x, gint y, + AtkCoordType coord_type) { + JAW_DEBUG("%p, %d, %d, %d", component, x, y, coord_type); + + if (component == NULL) { + g_warning("%s: Null argument passed to function", G_STRFUNC); + return FALSE; + } + + JAW_GET_COMPONENT( + component, + FALSE); // create local JNI reference `jobject atk_component` + + jboolean jcontains = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_component, cachedComponentContainsMethod, (jint)x, (jint)y, + (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + return jcontains; +} + +/** + * jaw_component_ref_accessible_at_point: + * @component: the #AtkComponent + * @x: x coordinate + * @y: y coordinate + * @coord_type: specifies whether the coordinates are relative to the screen + * or to the components top level window + * + * Gets a reference to the accessible child, if one exists, at the + * coordinate point specified by @x and @y. + * + * Returns: (nullable) (transfer full): a reference to the accessible + * child, if one exists + **/ +static AtkObject * +jaw_component_ref_accessible_at_point(AtkComponent *component, gint x, gint y, + AtkCoordType coord_type) { + JAW_DEBUG("%p, %d, %d, %d", component, x, y, coord_type); + + if (component == NULL) { + g_warning("%s: Null argument passed to function ", G_STRFUNC); + return NULL; + } + + JAW_GET_COMPONENT( + component, NULL); // create local JNI reference `jobject atk_component` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject child_ac = (*jniEnv)->CallObjectMethod( + jniEnv, atk_component, cachedComponentGetAccessibleAtPointMethod, + (jint)x, (jint)y, (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || child_ac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_accessible_at_point method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, child_ac); + + // From the documentation of the `ref_accessible_at_point`: + // "The caller of the method takes ownership of the returned data, and is + // responsible for freeing it." (transfer full annotation), so + // we have to ref the `jaw_impl` + if (jaw_impl != NULL) { + g_object_ref(G_OBJECT(jaw_impl)); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * jaw_component_get_extents: + * @component: an #AtkComponent + * @x: (out) (optional): address of #gint to put x coordinate + * @y: (out) (optional): address of #gint to put y coordinate + * @width: (out) (optional): address of #gint to put width + * @height: (out) (optional): address of #gint to put height + * @coord_type: specifies whether the coordinates are relative to the screen + * or to the components top level window + * + * Gets the rectangle which gives the extent of the @component. + * + * If the extent can not be obtained (e.g. a non-embedded plug or missing + * support), all of x, y, width, height are set to -1. + * + **/ +static void jaw_component_get_extents(AtkComponent *component, gint *x, gint *y, + gint *width, gint *height, + AtkCoordType coord_type) { + JAW_DEBUG("%p, %p, %p, %p, %p, %d", component, x, y, width, height, + coord_type); + + if (component == NULL) { + g_warning("%s: Null component passed to function", G_STRFUNC); + return; + } + + if (x != NULL) + (*x) = -1; + if (y != NULL) + (*y) = -1; + if (width != NULL) + (*width) = -1; + if (height != NULL) + (*height) = -1; + + JAW_GET_COMPONENT( + component, ); // create local JNI reference `jobject atk_component` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef( + jniEnv, + atk_component); // deleting ref that was created in + // JAW_GET_COMPONENT + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jobject jrectangle = (*jniEnv)->CallObjectMethod( + jniEnv, atk_component, cachedComponentGetExtentsMethod, + (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jrectangle == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jrectangle using get_extents method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + if (x != NULL) { + (*x) = (gint)(*jniEnv)->GetIntField(jniEnv, jrectangle, + cachedComponentRectangleXField); + } + if (y != NULL) { + (*y) = (gint)(*jniEnv)->GetIntField(jniEnv, jrectangle, + cachedComponentRectangleYField); + } + if (width != NULL) { + (*width) = (gint)(*jniEnv)->GetIntField( + jniEnv, jrectangle, cachedComponentRectangleWidthField); + } + if (height != NULL) { + (*height) = (gint)(*jniEnv)->GetIntField( + jniEnv, jrectangle, cachedComponentRectangleHeightField); + } + + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_component_set_extents: + * @component: an #AtkComponent + * @x: x coordinate + * @y: y coordinate + * @width: width to set for @component + * @height: height to set for @component + * @coord_type: specifies whether the coordinates are relative to the screen + * or to the components top level window + * + * Sets the extents of @component. + * + * Returns: %TRUE or %FALSE whether the extents were set or not + **/ +static gboolean jaw_component_set_extents(AtkComponent *component, gint x, + gint y, gint width, gint height, + AtkCoordType coord_type) { + JAW_DEBUG("%p, %d, %d, %d, %d, %d", component, x, y, width, height, + coord_type); + + if (component == NULL) { + g_warning("%s: Null argument passed to function", G_STRFUNC); + return FALSE; + } + + JAW_GET_COMPONENT( + component, + FALSE); // create local JNI reference `jobject atk_component` + + jboolean assigned = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_component, cachedComponentSetExtentsMethod, (jint)x, + (jint)y, (jint)width, (jint)height, (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + return assigned; +} + +/** + * jaw_component_set_position: + * @component: an #AtkComponent + * @x: x coordinate + * @y: y coordinate + * @coord_type: specifies whether the coordinates are relative to the screen + * or to the components top level window + * + * Sets the position of @component. + * + * Returns: %TRUE or %FALSE whether the position were set or not + **/ +static gboolean jaw_component_set_position(AtkComponent *component, gint x, + gint y, AtkCoordType coord_type) { + JAW_DEBUG("%p, %d, %d, %d", component, x, y, coord_type); + + if (component == NULL) { + g_warning("%s: Null argument passed to function", G_STRFUNC); + return FALSE; + } + + JAW_GET_COMPONENT( + component, + FALSE); // create local JNI reference `jobject atk_component` + + jboolean assigned = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_component, cachedComponentSetPositionMethod, (jint)x, + (jint)y, (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + return assigned; +} + +/** + * jaw_component_set_size: + * @component: an #AtkComponent + * @width: width to set for @component + * @height: height to set for @component + * + * Sets the size of @component. + * + * Returns: %TRUE or %FALSE whether the size were set or not + **/ +static gboolean jaw_component_set_size(AtkComponent *component, gint width, + gint height) { + JAW_DEBUG("%p, %d, %d", component, width, height); + + if (component == NULL) { + g_warning("%s: Null argument passed to function", G_STRFUNC); + return FALSE; + } + + JAW_GET_COMPONENT( + component, + FALSE); // create local JNI reference `jobject atk_component` + + jboolean assigned = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_component, cachedComponentSetSizeMethod, (jint)width, + (jint)height); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + return assigned; +} + +/** + * jaw_component_grab_focus: + * @component: an #AtkComponent + * + * Grabs focus for this @component. + * + * Returns: %TRUE if successful, %FALSE otherwise. + **/ +static gboolean jaw_component_grab_focus(AtkComponent *component) { + JAW_DEBUG("%p", component); + + if (component == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_COMPONENT( + component, + FALSE); // create local JNI reference `jobject atk_component` + + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_component, cachedComponentGrabFocusMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + return jresult; +} + +/** + * jaw_component_get_layer: + * @component: an #AtkComponent + * + * Gets the layer of the component. + * + * Returns: an #AtkLayer which is the layer of the component, 0 if an error + *happened. + **/ +static AtkLayer jaw_component_get_layer(AtkComponent *component) { + JAW_DEBUG("%p", component); + + if (component == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_COMPONENT(component, + 0); // create local JNI reference `jobject atk_component` + + jint jlayer = (*jniEnv)->CallIntMethod(jniEnv, atk_component, + cachedComponentGetLayerMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_component); + + return (AtkLayer)jlayer; +} + +static gboolean jaw_component_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkComponent"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkComponent class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedComponentAtkComponentClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedComponentAtkComponentClass == NULL) { + g_warning( + "%s: Failed to create global reference for AtkComponent class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedComponentCreateAtkComponentMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedComponentAtkComponentClass, "create_atk_component", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkComponent;"); + + cachedComponentContainsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "contains", "(III)Z"); + + cachedComponentGetAccessibleAtPointMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "get_accessible_at_point", + "(III)Ljavax/accessibility/AccessibleContext;"); + + cachedComponentGetExtentsMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedComponentAtkComponentClass, + "get_extents", "(I)Ljava/awt/Rectangle;"); + + cachedComponentSetExtentsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "set_extents", "(IIIII)Z"); + + cachedComponentSetPositionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "set_position", "(III)Z"); + + cachedComponentSetSizeMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "set_size", "(II)Z"); + + cachedComponentGrabFocusMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "grab_focus", "()Z"); + + cachedComponentGetLayerMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedComponentAtkComponentClass, "get_layer", "()I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedComponentCreateAtkComponentMethod == NULL || + cachedComponentContainsMethod == NULL || + cachedComponentGetAccessibleAtPointMethod == NULL || + cachedComponentGetExtentsMethod == NULL || + cachedComponentSetExtentsMethod == NULL || + cachedComponentSetPositionMethod == NULL || + cachedComponentSetSizeMethod == NULL || + cachedComponentGrabFocusMethod == NULL || + cachedComponentGetLayerMethod == NULL) { + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkComponent method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localRectangleClass = + (*jniEnv)->FindClass(jniEnv, "java/awt/Rectangle"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localRectangleClass == NULL) { + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to find Rectangle class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedComponentRectangleClass = + (*jniEnv)->NewGlobalRef(jniEnv, localRectangleClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localRectangleClass); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedComponentRectangleClass == NULL) { + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to create global reference for Rectangle class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedComponentRectangleXField = + (*jniEnv)->GetFieldID(jniEnv, cachedComponentRectangleClass, "x", "I"); + cachedComponentRectangleYField = + (*jniEnv)->GetFieldID(jniEnv, cachedComponentRectangleClass, "y", "I"); + cachedComponentRectangleWidthField = (*jniEnv)->GetFieldID( + jniEnv, cachedComponentRectangleClass, "width", "I"); + cachedComponentRectangleHeightField = (*jniEnv)->GetFieldID( + jniEnv, cachedComponentRectangleClass, "height", "I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedComponentRectangleXField == NULL || + cachedComponentRectangleYField == NULL || + cachedComponentRectangleWidthField == NULL || + cachedComponentRectangleHeightField == NULL) { + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more Rectangle field IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedComponentAtkComponentClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentAtkComponentClass); + cachedComponentAtkComponentClass = NULL; + } + if (cachedComponentRectangleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentRectangleClass); + cachedComponentRectangleClass = NULL; + } + cachedComponentCreateAtkComponentMethod = NULL; + cachedComponentContainsMethod = NULL; + cachedComponentGetAccessibleAtPointMethod = NULL; + cachedComponentGetExtentsMethod = NULL; + cachedComponentSetExtentsMethod = NULL; + cachedComponentSetPositionMethod = NULL; + cachedComponentSetSizeMethod = NULL; + cachedComponentGrabFocusMethod = NULL; + cachedComponentGetLayerMethod = NULL; + cachedComponentRectangleXField = NULL; + cachedComponentRectangleYField = NULL; + cachedComponentRectangleWidthField = NULL; + cachedComponentRectangleHeightField = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_component_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedComponentAtkComponentClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentAtkComponentClass); + cachedComponentAtkComponentClass = NULL; + } + if (cachedComponentRectangleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentRectangleClass); + cachedComponentRectangleClass = NULL; + } + cachedComponentCreateAtkComponentMethod = NULL; + cachedComponentContainsMethod = NULL; + cachedComponentGetAccessibleAtPointMethod = NULL; + cachedComponentGetExtentsMethod = NULL; + cachedComponentSetExtentsMethod = NULL; + cachedComponentSetPositionMethod = NULL; + cachedComponentSetSizeMethod = NULL; + cachedComponentGrabFocusMethod = NULL; + cachedComponentGetLayerMethod = NULL; + cachedComponentRectangleXField = NULL; + cachedComponentRectangleYField = NULL; + cachedComponentRectangleWidthField = NULL; + cachedComponentRectangleHeightField = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jaweditabletext.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jaweditabletext.c new file mode 100644 index 000000000000..faed3ed28c9c --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jaweditabletext.c @@ -0,0 +1,551 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawobject.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkEditableText: + * + * The ATK interface implemented by components containing user-editable text + * content. + * + * #AtkEditableText should be implemented by UI components which + * contain text which the user can edit, via the #AtkObject + * corresponding to that component (see #AtkObject). + * + * #AtkEditableText is a subclass of #AtkText, and as such, an object + * which implements #AtkEditableText is by definition an #AtkText + * implementor as well. + * + * See [iface@AtkText] + */ + +static jclass cachedEditableTextAtkEditableTextClass = NULL; +static jmethodID cachedEditableTextCreateAtkEditableTextMethod = NULL; +static jmethodID cachedEditableTextSetTextContentsMethod = NULL; +static jmethodID cachedEditableTextInsertTextMethod = NULL; +static jmethodID cachedEditableTextCopyTextMethod = NULL; +static jmethodID cachedEditableTextCutTextMethod = NULL; +static jmethodID cachedEditableTextDeleteTextMethod = NULL; +static jmethodID cachedEditableTextPasteTextMethod = NULL; +static jmethodID cachedEditableTextSetRunAttributesMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_editable_text_init_jni_cache(JNIEnv *jniEnv); + +static void jaw_editable_text_set_text_contents(AtkEditableText *text, + const gchar *string); +static void jaw_editable_text_insert_text(AtkEditableText *text, + const gchar *string, gint length, + gint *position); +static void jaw_editable_text_copy_text(AtkEditableText *text, gint start_pos, + gint end_pos); +static void jaw_editable_text_cut_text(AtkEditableText *text, gint start_pos, + gint end_pos); +static void jaw_editable_text_delete_text(AtkEditableText *text, gint start_pos, + gint end_pos); +static void jaw_editable_text_paste_text(AtkEditableText *text, gint position); + +static gboolean +jaw_editable_text_set_run_attributes(AtkEditableText *text, + AtkAttributeSet *attrib_set, + gint start_offset, gint end_offset); + +typedef struct _EditableTextData { + jobject atk_editable_text; +} EditableTextData; + +#define JAW_GET_EDITABLETEXT(text, def_ret) \ + JAW_GET_OBJ_IFACE( \ + text, org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT, \ + EditableTextData, atk_editable_text, jniEnv, atk_editable_text, \ + def_ret) + +/** + * AtkEditableTextIface: + * @set_run_attributes: + * @set_text_contents: + * @copy_text: + * @cut_text: + * @delete_text: + * @paste_text: + **/ +void jaw_editable_text_interface_init(AtkEditableTextIface *iface, + gpointer data) { + JAW_DEBUG("%p,%p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + iface->set_run_attributes = jaw_editable_text_set_run_attributes; + iface->set_text_contents = jaw_editable_text_set_text_contents; + iface->insert_text = jaw_editable_text_insert_text; + iface->copy_text = jaw_editable_text_copy_text; + iface->cut_text = jaw_editable_text_cut_text; + iface->delete_text = jaw_editable_text_delete_text; + iface->paste_text = jaw_editable_text_paste_text; +} + +gpointer jaw_editable_text_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_editable_text_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_editable_text = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedEditableTextAtkEditableTextClass, + cachedEditableTextCreateAtkEditableTextMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_editable_text == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jatk_editable_text using " + "create_atk_editable_text method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + EditableTextData *data = g_new0(EditableTextData, 1); + data->atk_editable_text = + (*jniEnv)->NewGlobalRef(jniEnv, jatk_editable_text); + if (data->atk_editable_text == NULL) { + g_warning("%s: Failed to create global ref for atk_editable_text", + G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_editable_text_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_debug("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + EditableTextData *data = (EditableTextData *)p; + if (data == NULL) { + g_debug("%s: data is null after cast", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->atk_editable_text != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_editable_text); + data->atk_editable_text = NULL; + } + } + + g_free(data); +} + +void jaw_editable_text_set_text_contents(AtkEditableText *text, + const gchar *string) { + JAW_DEBUG("%p, %s", text, string); + + if (text == NULL || string == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_EDITABLETEXT( + text, ); // create local JNI reference `jobject atk_editable_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, string); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jstr using NewStringUTF", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text, + cachedEditableTextSetTextContentsMethod, jstr); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +void jaw_editable_text_insert_text(AtkEditableText *text, const gchar *string, + gint length, gint *position) { + JAW_DEBUG("%p, %s, %d, %p", text, string, length, position); + + if (text == NULL || string == NULL || position == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_EDITABLETEXT( + text, ); // create local JNI reference `jobject atk_editable_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, string); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jstr using NewStringUTF", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text, + cachedEditableTextInsertTextMethod, jstr, + (jint)*position); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + *position = *position + length; + atk_text_set_caret_offset(ATK_TEXT(text), *position); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +void jaw_editable_text_copy_text(AtkEditableText *text, gint start_pos, + gint end_pos) { + JAW_DEBUG("%p, %d, %d", text, start_pos, end_pos); + + if (text == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_EDITABLETEXT( + text, ); // create local JNI reference `jobject atk_editable_text` + + (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text, + cachedEditableTextCopyTextMethod, (jint)start_pos, + (jint)end_pos); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); +} + +void jaw_editable_text_cut_text(AtkEditableText *text, gint start_pos, + gint end_pos) { + JAW_DEBUG("%p, %d, %d", text, start_pos, end_pos); + + if (text == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_EDITABLETEXT( + text, ); // create local JNI reference `jobject atk_editable_text` + + (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text, + cachedEditableTextCutTextMethod, (jint)start_pos, + (jint)end_pos); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); +} + +void jaw_editable_text_delete_text(AtkEditableText *text, gint start_pos, + gint end_pos) { + JAW_DEBUG("%p, %d, %d", text, start_pos, end_pos); + + if (text == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_EDITABLETEXT( + text, ); // create local JNI reference `jobject atk_editable_text` + + (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text, + cachedEditableTextDeleteTextMethod, + (jint)start_pos, (jint)end_pos); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); +} + +void jaw_editable_text_paste_text(AtkEditableText *text, gint position) { + JAW_DEBUG("%p, %d", text, position); + + if (text == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_EDITABLETEXT( + text, ); // create local JNI reference `jobject atk_editable_text` + + (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text, + cachedEditableTextPasteTextMethod, + (jint)position); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); +} + +/** + *jaw_editable_text_set_run_attributes: + *@text: an #AtkEditableText + *@attrib_set: an #AtkAttributeSet + *@start_offset: start of range in which to set attributes + *@end_offset: end of range in which to set attributes + * + *Returns: %TRUE if attributes successfully set for the specified + *range, otherwise %FALSE + **/ +static gboolean +jaw_editable_text_set_run_attributes(AtkEditableText *text, + AtkAttributeSet *attrib_set, + gint start_offset, gint end_offset) { + JAW_DEBUG("%p, %p, %d, %d", text, attrib_set, start_offset, end_offset); + + if (text == NULL || attrib_set == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_EDITABLETEXT( + text, FALSE); // create local JNI reference `jobject atk_editable_text` + + // TODO: make a proper conversion between attrib_set and swing AttributeSet, + // current implementation is incorrect + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_editable_text, cachedEditableTextSetRunAttributesMethod, + (jobject)attrib_set, (jint)start_offset, (jint)end_offset); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text); + + return jresult; +} + +static gboolean jaw_editable_text_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkEditableText"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkEditableText class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedEditableTextAtkEditableTextClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedEditableTextAtkEditableTextClass == NULL) { + g_warning( + "%s: Failed to create global reference for AtkEditableText class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedEditableTextCreateAtkEditableTextMethod = + (*jniEnv)->GetStaticMethodID( + jniEnv, cachedEditableTextAtkEditableTextClass, + "create_atk_editable_text", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkEditableText;"); + + cachedEditableTextSetTextContentsMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedEditableTextAtkEditableTextClass, + "set_text_contents", "(Ljava/lang/String;)V"); + + cachedEditableTextInsertTextMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedEditableTextAtkEditableTextClass, + "insert_text", "(Ljava/lang/String;I)V"); + + cachedEditableTextCopyTextMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedEditableTextAtkEditableTextClass, "copy_text", "(II)V"); + + cachedEditableTextCutTextMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedEditableTextAtkEditableTextClass, "cut_text", "(II)V"); + + cachedEditableTextDeleteTextMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedEditableTextAtkEditableTextClass, "delete_text", "(II)V"); + + cachedEditableTextPasteTextMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedEditableTextAtkEditableTextClass, "paste_text", "(I)V"); + + cachedEditableTextSetRunAttributesMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedEditableTextAtkEditableTextClass, "set_run_attributes", + "(Ljavax/swing/text/AttributeSet;II)Z"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedEditableTextCreateAtkEditableTextMethod == NULL || + cachedEditableTextSetTextContentsMethod == NULL || + cachedEditableTextInsertTextMethod == NULL || + cachedEditableTextCopyTextMethod == NULL || + cachedEditableTextCutTextMethod == NULL || + cachedEditableTextDeleteTextMethod == NULL || + cachedEditableTextPasteTextMethod == NULL || + cachedEditableTextSetRunAttributesMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkEditableText method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedEditableTextAtkEditableTextClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, + cachedEditableTextAtkEditableTextClass); + cachedEditableTextAtkEditableTextClass = NULL; + } + cachedEditableTextCreateAtkEditableTextMethod = NULL; + cachedEditableTextSetTextContentsMethod = NULL; + cachedEditableTextInsertTextMethod = NULL; + cachedEditableTextCopyTextMethod = NULL; + cachedEditableTextCutTextMethod = NULL; + cachedEditableTextDeleteTextMethod = NULL; + cachedEditableTextPasteTextMethod = NULL; + cachedEditableTextSetRunAttributesMethod = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_editable_text_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedEditableTextAtkEditableTextClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, + cachedEditableTextAtkEditableTextClass); + cachedEditableTextAtkEditableTextClass = NULL; + } + cachedEditableTextCreateAtkEditableTextMethod = NULL; + cachedEditableTextSetTextContentsMethod = NULL; + cachedEditableTextInsertTextMethod = NULL; + cachedEditableTextCopyTextMethod = NULL; + cachedEditableTextCutTextMethod = NULL; + cachedEditableTextDeleteTextMethod = NULL; + cachedEditableTextPasteTextMethod = NULL; + cachedEditableTextSetRunAttributesMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.c new file mode 100644 index 000000000000..32340cffbace --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.c @@ -0,0 +1,667 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawhyperlink.h" +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include + +/** + * (From Atk documentation) + * + * AtkHyperlink: + * + * An ATK object which encapsulates a link or set of links in a hypertext + * document. + * + * An ATK object which encapsulates a link or set of links (for + * instance in the case of client-side image maps) in a hypertext + * document. It may implement the AtkAction interface. AtkHyperlink + * may also be used to refer to inline embedded content, since it + * allows specification of a start and end offset within the host + * AtkHypertext object. + */ + +static jclass cachedHyperlinkAtkHyperlinkClass = NULL; +static jmethodID cachedHyperlinkGetUriMethod = NULL; +static jmethodID cachedHyperlinkGetObjectMethod = NULL; +static jmethodID cachedHyperlinkGetEndIndexMethod = NULL; +static jmethodID cachedHyperlinkGetStartIndexMethod = NULL; +static jmethodID cachedHyperlinkIsValidMethod = NULL; +static jmethodID cachedHyperlinkGetNAnchorsMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_hyperlink_init_jni_cache(JNIEnv *jniEnv); + +static void jaw_hyperlink_dispose(GObject *gobject); +static void jaw_hyperlink_finalize(GObject *gobject); + +static gchar *jaw_hyperlink_get_uri(AtkHyperlink *atk_hyperlink, gint i); +static AtkObject *jaw_hyperlink_get_object(AtkHyperlink *atk_hyperlink, gint i); +static gint jaw_hyperlink_get_end_index(AtkHyperlink *atk_hyperlink); +static gint jaw_hyperlink_get_start_index(AtkHyperlink *atk_hyperlink); +static gboolean jaw_hyperlink_is_valid(AtkHyperlink *atk_hyperlink); +static gint jaw_hyperlink_get_n_anchors(AtkHyperlink *atk_hyperlink); + +G_DEFINE_TYPE(JawHyperlink, jaw_hyperlink, ATK_TYPE_HYPERLINK) + +#define JAW_GET_HYPERLINK(atk_hyperlink, def_ret) \ + JAW_GET_OBJ(atk_hyperlink, JAW_HYPERLINK, JawHyperlink, jaw_hyperlink, \ + jhyperlink, jniEnv, jhyperlink, def_ret) + +JawHyperlink *jaw_hyperlink_new(jobject jhyperlink) { + JAW_DEBUG("%p", jhyperlink); + + if (jhyperlink == NULL) { + g_warning("%s: NULL jhyperlink parameter", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: Failed to get JNI environment", G_STRFUNC); + return NULL; + } + + JawHyperlink *jaw_hyperlink = g_object_new(JAW_TYPE_HYPERLINK, NULL); + if (jaw_hyperlink == NULL) { + g_warning("%s: Failed to create JawHyperlink object", G_STRFUNC); + return NULL; + } + + jaw_hyperlink->jhyperlink = (*jniEnv)->NewGlobalRef(jniEnv, jhyperlink); + if ((*jniEnv)->ExceptionCheck(jniEnv) || + jaw_hyperlink->jhyperlink == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create global reference", G_STRFUNC); + g_object_unref(jaw_hyperlink); + return NULL; + } + + return jaw_hyperlink; +} + +/** + * _AtkHyperlinkClass: + * @get_uri: + * @get_object: + * @get_end_index: + * @get_start_index: + * @is_valid: + * @get_n_anchors: + * @link_state: + * @is_selected_link: + * @link_activated -- The signal link-activated is emitted when a link is + *activated. + **/ +static void jaw_hyperlink_class_init(JawHyperlinkClass *klass) { + JAW_DEBUG("%p", klass); + + if (klass == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + gobject_class->dispose = jaw_hyperlink_dispose; + gobject_class->finalize = jaw_hyperlink_finalize; + + AtkHyperlinkClass *atk_hyperlink_class = ATK_HYPERLINK_CLASS(klass); + atk_hyperlink_class->get_uri = jaw_hyperlink_get_uri; + atk_hyperlink_class->get_object = jaw_hyperlink_get_object; + atk_hyperlink_class->get_end_index = jaw_hyperlink_get_end_index; + atk_hyperlink_class->get_start_index = jaw_hyperlink_get_start_index; + atk_hyperlink_class->is_valid = jaw_hyperlink_is_valid; + atk_hyperlink_class->get_n_anchors = jaw_hyperlink_get_n_anchors; + atk_hyperlink_class->link_state = + NULL; // missing java support for atk_hyperlink_class->link_state + atk_hyperlink_class->is_selected_link = + NULL; // missing java support for + // atk_hyperlink_class->is_selected_link +} + +static void jaw_hyperlink_init(JawHyperlink *link) { + JAW_DEBUG("%p", link); + g_mutex_init(&link->mutex); +} + +static void jaw_hyperlink_dispose(GObject *gobject) { + JAW_DEBUG("%p", gobject); + + if (gobject == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + /* Chain up to parent's dispose */ + G_OBJECT_CLASS(jaw_hyperlink_parent_class)->dispose(gobject); +} + +static void jaw_hyperlink_finalize(GObject *gobject) { + JAW_DEBUG("%p", gobject); + + if (gobject == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JawHyperlink *jaw_hyperlink = JAW_HYPERLINK(gobject); + if (jaw_hyperlink == NULL) { + g_debug("%s: jaw_hyperlink is NULL", G_STRFUNC); + G_OBJECT_CLASS(jaw_hyperlink_parent_class)->finalize(gobject); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_debug("%s: jniEnv is NULL", G_STRFUNC); + g_mutex_clear(&jaw_hyperlink->mutex); + G_OBJECT_CLASS(jaw_hyperlink_parent_class)->finalize(gobject); + return; + } + + g_mutex_lock(&jaw_hyperlink->mutex); + + if (jaw_hyperlink->jstrUri != NULL) { + if (jaw_hyperlink->uri != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_hyperlink->jstrUri, + jaw_hyperlink->uri); + jaw_hyperlink->uri = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jstrUri); + jaw_hyperlink->jstrUri = NULL; + } + + if (jaw_hyperlink->jhyperlink != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jhyperlink); + jaw_hyperlink->jhyperlink = NULL; + } + + g_mutex_unlock(&jaw_hyperlink->mutex); + g_mutex_clear(&jaw_hyperlink->mutex); + + /* Chain up to parent's finalize */ + G_OBJECT_CLASS(jaw_hyperlink_parent_class)->finalize(gobject); +} + +/** + * jaw_hyperlink_get_uri: + * @link_: an #AtkHyperlink + * @i: a (zero-index) integer specifying the desired anchor + * + * Get a the URI associated with the anchor specified + * by @i of @link_. + * + * Multiple anchors are primarily used by client-side image maps. + * + * Returns: a string specifying the URI + **/ +static gchar *jaw_hyperlink_get_uri(AtkHyperlink *atk_hyperlink, gint i) { + JAW_DEBUG("%p, %d", atk_hyperlink, i); + + if (atk_hyperlink == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_HYPERLINK(atk_hyperlink, + NULL); // create local JNI reference `jobject jhyperlink` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + if (!jaw_hyperlink_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jstring jstr = (*jniEnv)->CallObjectMethod( + jniEnv, jhyperlink, cachedHyperlinkGetUriMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&jaw_hyperlink->mutex); + + if (jaw_hyperlink->jstrUri != NULL) { + if (jaw_hyperlink->uri != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_hyperlink->jstrUri, + jaw_hyperlink->uri); + jaw_hyperlink->uri = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jstrUri); + jaw_hyperlink->jstrUri = NULL; + } + + jaw_hyperlink->jstrUri = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jaw_hyperlink->jstrUri == NULL) { + jaw_jni_clear_exception(jniEnv); + g_mutex_unlock(&jaw_hyperlink->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jaw_hyperlink->uri = (gchar *)(*jniEnv)->GetStringUTFChars( + jniEnv, jaw_hyperlink->jstrUri, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jaw_hyperlink->uri == NULL) { + jaw_jni_clear_exception(jniEnv); + + // jaw_hyperlink->jstrUri != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jstrUri); + jaw_hyperlink->jstrUri = NULL; + + g_mutex_unlock(&jaw_hyperlink->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + gchar *result = jaw_hyperlink->uri; + g_mutex_unlock(&jaw_hyperlink->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_hyperlink_get_object: + * @atk_hyperlink: an #AtkHyperlink + * @i: a (zero-index) integer specifying the desired anchor + * + * Returns the item associated with this hyperlinks nth anchor. + * + * Returns: (transfer none): an #AtkObject associated with this hyperlinks + * i-th anchor + **/ +static AtkObject *jaw_hyperlink_get_object(AtkHyperlink *atk_hyperlink, + gint i) { + JAW_DEBUG("%p, %d", atk_hyperlink, i); + + if (atk_hyperlink == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_HYPERLINK(atk_hyperlink, NULL); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + if (!jaw_hyperlink_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jobject ac = (*jniEnv)->CallObjectMethod( + jniEnv, jhyperlink, cachedHyperlinkGetObjectMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv) || ac == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + AtkObject *obj = (AtkObject *)jaw_impl_find_instance(jniEnv, ac); + if (obj == NULL) { + g_warning("%s: No AtkObject found for AccessibleContext", G_STRFUNC); + } + + // From documentation of the `atk_hyperlink_get_object`: + // The returned data is owned by the instance (transfer none annotation), so + // we don't ref the obj before returning it + + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return obj; +} + +/** + * atk_hyperlink_get_end_index: + * @link_: an #AtkHyperlink + * + * Gets the index with the hypertext document at which this link ends. + * + * Returns: the index with the hypertext document at which this link ends, 0 if + *an error happened. + **/ +static gint jaw_hyperlink_get_end_index(AtkHyperlink *atk_hyperlink) { + JAW_DEBUG("%p", atk_hyperlink); + + if (atk_hyperlink == NULL) { + g_warning("%s: Null argument atk_hyperlink passed to the function", + G_STRFUNC); + return 0; + } + + JAW_GET_HYPERLINK(atk_hyperlink, 0); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + if (!jaw_hyperlink_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jint jindex = (*jniEnv)->CallIntMethod(jniEnv, jhyperlink, + cachedHyperlinkGetEndIndexMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return jindex; +} + +/** + * jaw_hyperlink_get_start_index: + * @link_: an #AtkHyperlink + * + * Gets the index with the hypertext document at which this link begins. + * + * Returns: the index with the hypertext document at which this link begins, 0 + * if an error happened + **/ +static gint jaw_hyperlink_get_start_index(AtkHyperlink *atk_hyperlink) { + JAW_DEBUG("%p", atk_hyperlink); + + if (atk_hyperlink == NULL) { + g_warning("%s: Null argument atk_hyperlink passed to the function", + G_STRFUNC); + return 0; + } + + JAW_GET_HYPERLINK(atk_hyperlink, 0); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + if (!jaw_hyperlink_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jint jindex = (*jniEnv)->CallIntMethod(jniEnv, jhyperlink, + cachedHyperlinkGetStartIndexMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return jindex; +} + +/** + * jaw_hyperlink_is_valid: + * @link_: an #AtkHyperlink + * + * Since the document that a link is associated with may have changed + * this method returns %TRUE if the link is still valid (with + * respect to the document it references) and %FALSE otherwise. + * + * Returns: whether or not this link is still valid + **/ +static gboolean jaw_hyperlink_is_valid(AtkHyperlink *atk_hyperlink) { + JAW_DEBUG("%p", atk_hyperlink); + + if (atk_hyperlink == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_HYPERLINK(atk_hyperlink, FALSE); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return FALSE; + } + + if (!jaw_hyperlink_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; + } + + jboolean jvalid = (*jniEnv)->CallBooleanMethod( + jniEnv, jhyperlink, cachedHyperlinkIsValidMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return jvalid; +} + +/** + * jaw_hyperlink_get_n_anchors: + * @link_: an #AtkHyperlink + * + * Gets the number of anchors associated with this hyperlink. + * + * Returns: the number of anchors associated with this hyperlink + **/ +static gint jaw_hyperlink_get_n_anchors(AtkHyperlink *atk_hyperlink) { + JAW_DEBUG("%p", atk_hyperlink); + + if (atk_hyperlink == NULL) { + g_warning("%s: Null argument atk_hyperlink passed to the function", + G_STRFUNC); + return 0; + } + + JAW_GET_HYPERLINK(atk_hyperlink, 0); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + if (!jaw_hyperlink_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jint janchors = (*jniEnv)->CallIntMethod(jniEnv, jhyperlink, + cachedHyperlinkGetNAnchorsMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return janchors; +} + +static gboolean jaw_hyperlink_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkHyperlink"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkHyperlink class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedHyperlinkAtkHyperlinkClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedHyperlinkAtkHyperlinkClass == NULL) { + g_warning( + "%s: Failed to create global reference for AtkHyperlink class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedHyperlinkGetUriMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedHyperlinkAtkHyperlinkClass, + "get_uri", "(I)Ljava/lang/String;"); + + cachedHyperlinkGetObjectMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_object", + "(I)Ljavax/accessibility/AccessibleContext;"); + + cachedHyperlinkGetEndIndexMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_end_index", "()I"); + + cachedHyperlinkGetStartIndexMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_start_index", "()I"); + + cachedHyperlinkIsValidMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHyperlinkAtkHyperlinkClass, "is_valid", "()Z"); + + cachedHyperlinkGetNAnchorsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_n_anchors", "()I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedHyperlinkGetUriMethod == NULL || + cachedHyperlinkGetObjectMethod == NULL || + cachedHyperlinkGetEndIndexMethod == NULL || + cachedHyperlinkGetStartIndexMethod == NULL || + cachedHyperlinkIsValidMethod == NULL || + cachedHyperlinkGetNAnchorsMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkHyperlink method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedHyperlinkAtkHyperlinkClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHyperlinkAtkHyperlinkClass); + cachedHyperlinkAtkHyperlinkClass = NULL; + } + cachedHyperlinkGetUriMethod = NULL; + cachedHyperlinkGetObjectMethod = NULL; + cachedHyperlinkGetEndIndexMethod = NULL; + cachedHyperlinkGetStartIndexMethod = NULL; + cachedHyperlinkIsValidMethod = NULL; + cachedHyperlinkGetNAnchorsMethod = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; + g_mutex_unlock(&cache_mutex); + return TRUE; +} + +void jaw_hyperlink_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedHyperlinkAtkHyperlinkClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHyperlinkAtkHyperlinkClass); + cachedHyperlinkAtkHyperlinkClass = NULL; + } + cachedHyperlinkGetUriMethod = NULL; + cachedHyperlinkGetObjectMethod = NULL; + cachedHyperlinkGetEndIndexMethod = NULL; + cachedHyperlinkGetStartIndexMethod = NULL; + cachedHyperlinkIsValidMethod = NULL; + cachedHyperlinkGetNAnchorsMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.h new file mode 100644 index 000000000000..2f9fe119ea48 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.h @@ -0,0 +1,62 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _JAW_HYPERLINK_H_ +#define _JAW_HYPERLINK_H_ + +#include +#include + +G_BEGIN_DECLS + +#define JAW_TYPE_HYPERLINK (jaw_hyperlink_get_type()) +#define JAW_HYPERLINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_HYPERLINK, JawHyperlink)) +#define JAW_HYPERLINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_HYPERLINK, JawHyperlinkClass)) +#define JAW_IS_HYPERLINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_HYPERLINK)) +#define JAW_IS_HYPERLINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_HYPERLINK)) +#define JAW_HYPERLINK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_HYPERLINK, JawHyperlinkClass)) + +typedef struct _JawHyperlink JawHyperlink; +typedef struct _JawHyperlinkClass JawHyperlinkClass; + +struct _JawHyperlink { + AtkHyperlink parent; + jobject jhyperlink; + + jstring jstrUri; + gchar *uri; + GMutex mutex; +}; + +GType jaw_hyperlink_get_type(void); + +struct _JawHyperlinkClass { + AtkHyperlinkClass parent_class; +}; + +JawHyperlink *jaw_hyperlink_new(jobject jhyperlink); + +G_END_DECLS + +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawhypertext.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhypertext.c new file mode 100644 index 000000000000..08114adc0a61 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhypertext.c @@ -0,0 +1,397 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawhyperlink.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkHypertext: + * + * The ATK interface which provides standard mechanism for manipulating + * hyperlinks. + * + * An interface used for objects which implement linking between + * multiple resource or content locations, or multiple 'markers' + * within a single document. A Hypertext instance is associated with + * one or more Hyperlinks, which are associated with particular + * offsets within the Hypertext's included content. While this + * interface is derived from Text, there is no requirement that + * Hypertext instances have textual content; they may implement Image + * as well, and Hyperlinks need not have non-zero text offsets. + */ + +static jclass cachedHypertextAtkHypertextClass = NULL; +static jmethodID cachedHypertextCreateAtkHypertextMethod = NULL; +static jmethodID cachedHypertextGetLinkMethod = NULL; +static jmethodID cachedHypertextGetNLinksMethod = NULL; +static jmethodID cachedHypertextGetLinkIndexMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_hypertext_init_jni_cache(JNIEnv *jniEnv); + +static AtkHyperlink *jaw_hypertext_get_link(AtkHypertext *hypertext, + gint link_index); +static gint jaw_hypertext_get_n_links(AtkHypertext *hypertext); +static gint jaw_hypertext_get_link_index(AtkHypertext *hypertext, + gint char_index); + +typedef struct _HypertextData { + jobject atk_hypertext; +} HypertextData; + +#define JAW_GET_HYPERTEXT(hypertext, def_ret) \ + JAW_GET_OBJ_IFACE( \ + hypertext, org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT, \ + HypertextData, atk_hypertext, jniEnv, atk_hypertext, def_ret) + +/** + * AtkHypertextIface: + * @get_link: + * @get_n_links: + * @get_link_index: + **/ +void jaw_hypertext_interface_init(AtkHypertextIface *iface, gpointer data) { + if (iface == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + iface->get_link = jaw_hypertext_get_link; + iface->get_n_links = jaw_hypertext_get_n_links; + iface->get_link_index = jaw_hypertext_get_link_index; +} + +gpointer jaw_hypertext_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_hypertext_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_hypertext = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedHypertextAtkHypertextClass, + cachedHypertextCreateAtkHypertextMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_hypertext == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jatk_hypertext using " + "create_atk_hypertext method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + HypertextData *data = g_new0(HypertextData, 1); + data->atk_hypertext = (*jniEnv)->NewGlobalRef(jniEnv, jatk_hypertext); + if (data->atk_hypertext == NULL) { + g_warning("%s: Failed to create global ref for atk_hypertext", + G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_hypertext_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + HypertextData *data = (HypertextData *)p; + if (data == NULL) { + g_warning("%s: data is null after cast", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->atk_hypertext != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_hypertext); + data->atk_hypertext = NULL; + } + } + + g_free(data); +} + +/** + * jaw_hypertext_get_link: + * @hypertext: an #AtkHypertext + * @link_index: an integer specifying the desired link + * + * Gets the link in this hypertext document at index + * @link_index + * + * Returns: (transfer none): the link in this hypertext document at + * index @link_index + **/ +static AtkHyperlink *jaw_hypertext_get_link(AtkHypertext *hypertext, + gint link_index) { + JAW_DEBUG("%p, %d", hypertext, link_index); + + if (hypertext == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_HYPERTEXT( + hypertext, NULL); // create local JNI reference `jobject atk_hypertext` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jhyperlink = (*jniEnv)->CallObjectMethod( + jniEnv, atk_hypertext, cachedHypertextGetLinkMethod, (jint)link_index); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jhyperlink == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jhyperlink using get_link method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawHyperlink *jaw_hyperlink = jaw_hyperlink_new(jhyperlink); + if (jaw_hyperlink == NULL) { + g_warning("%s: Failed to create JawHyperlink object for link_index %d", + G_STRFUNC, link_index); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_HYPERLINK(jaw_hyperlink); +} + +/** + * jaw_hypertext_get_n_links: + * @hypertext: an #AtkHypertext + * + * Gets the number of links within this hypertext document. + * + * Returns: the number of links within this hypertext document + **/ +static gint jaw_hypertext_get_n_links(AtkHypertext *hypertext) { + JAW_DEBUG("%p", hypertext); + + if (hypertext == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_HYPERTEXT(hypertext, + 0); // create local JNI reference `jobject atk_hypertext` + + gint ret = (gint)(*jniEnv)->CallIntMethod(jniEnv, atk_hypertext, + cachedHypertextGetNLinksMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + + return ret; +} + +/** + * jaw_hypertext_get_link_index: + * @hypertext: an #AtkHypertext + * @char_index: a character index + * + * Gets the index into the array of hyperlinks that is associated with + * the character specified by @char_index. + * + * Returns: an index into the array of hyperlinks in @hypertext, + * or -1 if there is no hyperlink associated with this character. + **/ +static gint jaw_hypertext_get_link_index(AtkHypertext *hypertext, + gint char_index) { + JAW_DEBUG("%p, %d", hypertext, char_index); + + if (hypertext == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_HYPERTEXT(hypertext, + -1); // create local JNI reference `jobject atk_hypertext` + + gint ret = (gint)(*jniEnv)->CallIntMethod(jniEnv, atk_hypertext, + cachedHypertextGetLinkIndexMethod, + (jint)char_index); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext); + + return ret; +} + +static gboolean jaw_hypertext_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkHypertext"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkHypertext class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedHypertextAtkHypertextClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedHypertextAtkHypertextClass == NULL) { + g_warning( + "%s: Failed to create global reference for AtkHypertext class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedHypertextCreateAtkHypertextMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedHypertextAtkHypertextClass, "create_atk_hypertext", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkHypertext;"); + + cachedHypertextGetLinkMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHypertextAtkHypertextClass, "get_link", + "(I)Lorg/GNOME/Accessibility/AtkHyperlink;"); + + cachedHypertextGetNLinksMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHypertextAtkHypertextClass, "get_n_links", "()I"); + + cachedHypertextGetLinkIndexMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedHypertextAtkHypertextClass, "get_link_index", "(I)I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedHypertextCreateAtkHypertextMethod == NULL || + cachedHypertextGetLinkMethod == NULL || + cachedHypertextGetNLinksMethod == NULL || + cachedHypertextGetLinkIndexMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkHypertext method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedHypertextAtkHypertextClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHypertextAtkHypertextClass); + cachedHypertextAtkHypertextClass = NULL; + } + cachedHypertextCreateAtkHypertextMethod = NULL; + cachedHypertextGetLinkMethod = NULL; + cachedHypertextGetNLinksMethod = NULL; + cachedHypertextGetLinkIndexMethod = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_hypertext_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedHypertextAtkHypertextClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHypertextAtkHypertextClass); + cachedHypertextAtkHypertextClass = NULL; + } + cachedHypertextCreateAtkHypertextMethod = NULL; + cachedHypertextGetLinkMethod = NULL; + cachedHypertextGetNLinksMethod = NULL; + cachedHypertextGetLinkIndexMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawimage.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimage.c new file mode 100644 index 000000000000..ad0875f49a73 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimage.c @@ -0,0 +1,577 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkImage: + * + * The ATK Interface implemented by components + * which expose image or pixmap content on-screen. + * + * #AtkImage should be implemented by #AtkObject subtypes on behalf of + * components which display image/pixmap information onscreen, and + * which provide information (other than just widget borders, etc.) + * via that image content. For instance, icons, buttons with icons, + * toolbar elements, and image viewing panes typically should + * implement #AtkImage. + * + * #AtkImage primarily provides two types of information: coordinate + * information (useful for screen review mode of screenreaders, and + * for use by onscreen magnifiers), and descriptive information. The + * descriptive information is provided for alternative, text-only + * presentation of the most significant information present in the + * image. + */ + +static jclass cachedImageAtkImageClass = NULL; +static jmethodID cachedImageCreateAtkImageMethod = NULL; +static jmethodID cachedImageGetImagePositionMethod = NULL; +static jmethodID cachedImageGetImageDescriptionMethod = NULL; +static jmethodID cachedImageGetImageSizeMethod = NULL; +static jclass cachedImagePointClass = NULL; +static jfieldID cachedImagePointXFieldID = NULL; +static jfieldID cachedImagePointYFieldID = NULL; +static jclass cachedImageDimensionClass = NULL; +static jfieldID cachedImageDimensionWidthFieldID = NULL; +static jfieldID cachedImageDimensionHeightFieldID = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_image_init_jni_cache(JNIEnv *jniEnv); + +static void jaw_image_get_image_position(AtkImage *image, gint *x, gint *y, + AtkCoordType coord_type); +static const gchar *jaw_image_get_image_description(AtkImage *image); +static void jaw_image_get_image_size(AtkImage *image, gint *width, + gint *height); + +typedef struct _ImageData { + jobject atk_image; + const gchar *image_description; + jstring jstrImageDescription; + GMutex mutex; +} ImageData; + +#define JAW_GET_IMAGE(image, def_ret) \ + JAW_GET_OBJ_IFACE(image, \ + org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE, \ + ImageData, atk_image, jniEnv, atk_image, def_ret) + +/** + * AtkImageIface: + * @get_image_position: + * @get_image_description: + * @get_image_size + * @set_image_description: + * @get_image_locale: + **/ +void jaw_image_interface_init(AtkImageIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + iface->get_image_position = jaw_image_get_image_position; + iface->get_image_description = jaw_image_get_image_description; + iface->get_image_size = jaw_image_get_image_size; + iface->set_image_description = NULL; // TODO: iface->set_image_description + iface->get_image_locale = NULL; // TODO: iface->get_image_locale from + // AccessibleContext.getLocale() +} + +gpointer jaw_image_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + if (!jaw_image_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jobject jatk_image = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedImageAtkImageClass, cachedImageCreateAtkImageMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_image == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jatk_image using create_atk_image method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + ImageData *data = g_new0(ImageData, 1); + g_mutex_init(&data->mutex); + data->atk_image = (*jniEnv)->NewGlobalRef(jniEnv, jatk_image); + if (data->atk_image == NULL) { + g_warning("%s: Failed to create global ref for atk_image", G_STRFUNC); + g_mutex_clear(&data->mutex); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_image_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + ImageData *data = (ImageData *)p; + if (data == NULL) { + g_warning("%s: data is null after cast", G_STRFUNC); + return; + } + + g_mutex_lock(&data->mutex); + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->jstrImageDescription != NULL) { + if (data->image_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, + data->jstrImageDescription, + data->image_description); + data->image_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrImageDescription); + data->jstrImageDescription = NULL; + } + + if (data->atk_image != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_image); + data->atk_image = NULL; + } + } + + g_mutex_unlock(&data->mutex); + g_mutex_clear(&data->mutex); + g_free(data); +} + +/** + * jaw_image_get_image_position: + * @image: a #GObject instance that implements AtkImageIface + * @x: (out) (optional): address of #gint to put x coordinate position; + *otherwise, -1 if value cannot be obtained. + * @y: (out) (optional): address of #gint to put y coordinate position; + *otherwise, -1 if value cannot be obtained. + * @coord_type: specifies whether the coordinates are relative to the screen + * or to the components top level window + * + * Gets the position of the image in the form of a point specifying the + * images top-left corner. + * + * If the position can not be obtained (e.g. missing support), x and y are set + * to -1. + **/ +static void jaw_image_get_image_position(AtkImage *image, gint *x, gint *y, + AtkCoordType coord_type) { + JAW_DEBUG("%p, %p, %p, %d", image, x, y, coord_type); + + if (image == NULL || x == NULL || y == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_IMAGE(image, ); // create local JNI reference `jobject atk_image` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + (*x) = -1; + (*y) = -1; + + jobject jpoint = (*jniEnv)->CallObjectMethod( + jniEnv, atk_image, cachedImageGetImagePositionMethod, (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jpoint == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jpoint using get_image_position method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*x) = + (gint)(*jniEnv)->GetIntField(jniEnv, jpoint, cachedImagePointXFieldID); + (*y) = + (gint)(*jniEnv)->GetIntField(jniEnv, jpoint, cachedImagePointYFieldID); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_image_get_image_description: + * @image: a #GObject instance that implements AtkImageIface + * + * Get a textual description of this image. + * + * Returns: a string representing the image description or NULL + **/ +static const gchar *jaw_image_get_image_description(AtkImage *image) { + JAW_DEBUG("%p", image); + + if (image == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_IMAGE(image, + NULL); // create local JNI reference `jobject atk_image` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallObjectMethod( + jniEnv, atk_image, cachedImageGetImageDescriptionMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jstr using get_image_description method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&data->mutex); + if (data->jstrImageDescription != NULL) { + if (data->image_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, data->jstrImageDescription, + data->image_description); + data->image_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrImageDescription); + data->jstrImageDescription = NULL; + } + + data->jstrImageDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (data->jstrImageDescription == NULL) { + g_mutex_unlock(&data->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + data->image_description = + (*jniEnv)->GetStringUTFChars(jniEnv, data->jstrImageDescription, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || data->image_description == NULL) { + jaw_jni_clear_exception(jniEnv); + + // data->jstrImageDescription != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrImageDescription); + data->jstrImageDescription = NULL; + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + g_mutex_unlock(&data->mutex); + return NULL; + } + + g_mutex_unlock(&data->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data->image_description; +} + +/** + * jaw_image_get_image_size: + * @image: a #GObject instance that implements AtkImageIface + * @width: (out) (optional): filled with the image width, or -1 if the value + *cannot be obtained. + * @height: (out) (optional): filled with the image height, or -1 if the value + *cannot be obtained. + * + * Get the width and height in pixels for the specified image. + * The values of @width and @height are returned as -1 if the + * values cannot be obtained (for instance, if the object is not onscreen). + * + * If the size can not be obtained (e.g. missing support), x and y are set + * to -1. + **/ + +static void jaw_image_get_image_size(AtkImage *image, gint *width, + gint *height) { + JAW_DEBUG("%p, %p, %p", image, width, height); + + if (image == NULL || width == NULL || height == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_IMAGE(image, ); // create local JNI reference `jobject atk_image` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + (*width) = -1; + (*height) = -1; + + jobject jdimension = (*jniEnv)->CallObjectMethod( + jniEnv, atk_image, cachedImageGetImageSizeMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jdimension == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jdimension using get_image_size method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_image); + + (*width) = (gint)(*jniEnv)->GetIntField(jniEnv, jdimension, + cachedImageDimensionWidthFieldID); + (*height) = (gint)(*jniEnv)->GetIntField(jniEnv, jdimension, + cachedImageDimensionHeightFieldID); + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +static gboolean jaw_image_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkImage"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkImage class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImageAtkImageClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedImageAtkImageClass == NULL) { + g_warning("%s: Failed to create global reference for AtkImage class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImageCreateAtkImageMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedImageAtkImageClass, "create_atk_image", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkImage;"); + + cachedImageGetImagePositionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedImageAtkImageClass, + "get_image_position", "(I)Ljava/awt/Point;"); + + cachedImageGetImageDescriptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedImageAtkImageClass, + "get_image_description", "()Ljava/lang/String;"); + + cachedImageGetImageSizeMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedImageAtkImageClass, + "get_image_size", "()Ljava/awt/Dimension;"); + + jclass localPointClass = (*jniEnv)->FindClass(jniEnv, "java/awt/Point"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localPointClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Point class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImagePointClass = (*jniEnv)->NewGlobalRef(jniEnv, localPointClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localPointClass); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || cachedImagePointClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create global reference for Point class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImagePointXFieldID = + (*jniEnv)->GetFieldID(jniEnv, cachedImagePointClass, "x", "I"); + cachedImagePointYFieldID = + (*jniEnv)->GetFieldID(jniEnv, cachedImagePointClass, "y", "I"); + + jclass localDimensionClass = + (*jniEnv)->FindClass(jniEnv, "java/awt/Dimension"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localDimensionClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Dimension class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImageDimensionClass = + (*jniEnv)->NewGlobalRef(jniEnv, localDimensionClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localDimensionClass); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedImageDimensionClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create global reference for Dimension class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImageDimensionWidthFieldID = + (*jniEnv)->GetFieldID(jniEnv, cachedImageDimensionClass, "width", "I"); + cachedImageDimensionHeightFieldID = + (*jniEnv)->GetFieldID(jniEnv, cachedImageDimensionClass, "height", "I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedImageCreateAtkImageMethod == NULL || + cachedImageGetImagePositionMethod == NULL || + cachedImageGetImageDescriptionMethod == NULL || + cachedImageGetImageSizeMethod == NULL || + cachedImagePointXFieldID == NULL || cachedImagePointYFieldID == NULL || + cachedImageDimensionWidthFieldID == NULL || + cachedImageDimensionHeightFieldID == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning( + "%s: Failed to cache one or more AtkImage method IDs or field IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + return TRUE; + +cleanup_and_fail: + if (cachedImageAtkImageClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageAtkImageClass); + cachedImageAtkImageClass = NULL; + } + if (cachedImagePointClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImagePointClass); + cachedImagePointClass = NULL; + } + if (cachedImageDimensionClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageDimensionClass); + cachedImageDimensionClass = NULL; + } + cachedImageCreateAtkImageMethod = NULL; + cachedImageGetImagePositionMethod = NULL; + cachedImageGetImageDescriptionMethod = NULL; + cachedImageGetImageSizeMethod = NULL; + cachedImagePointXFieldID = NULL; + cachedImagePointYFieldID = NULL; + cachedImageDimensionWidthFieldID = NULL; + cachedImageDimensionHeightFieldID = NULL; + + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return FALSE; +} + +void jaw_image_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedImageAtkImageClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageAtkImageClass); + cachedImageAtkImageClass = NULL; + } + if (cachedImagePointClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImagePointClass); + cachedImagePointClass = NULL; + } + if (cachedImageDimensionClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageDimensionClass); + cachedImageDimensionClass = NULL; + } + cachedImageCreateAtkImageMethod = NULL; + cachedImageGetImagePositionMethod = NULL; + cachedImageGetImageDescriptionMethod = NULL; + cachedImageGetImageSizeMethod = NULL; + cachedImagePointXFieldID = NULL; + cachedImagePointYFieldID = NULL; + cachedImageDimensionWidthFieldID = NULL; + cachedImageDimensionHeightFieldID = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.c new file mode 100644 index 000000000000..fb4da258425f --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.c @@ -0,0 +1,891 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawimpl.h" +#include "jawobject.h" +#include "jawtoplevel.h" +#include "jawutil.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static void jaw_impl_class_init(JawImplClass *klass); +static void jaw_impl_dispose(GObject *gobject); +static void jaw_impl_finalize(GObject *gobject); +static gpointer jaw_impl_get_interface_data(JawObject *jaw_obj, guint iface); +static void jaw_impl_initialize(AtkObject *atk_obj, gpointer data); + +typedef struct _JawInterfaceInfo { + void (*finalize)(gpointer); + gpointer data; +} JawInterfaceInfo; + +static gpointer jaw_impl_parent_class = NULL; + +static GMutex typeTableMutex; +static GHashTable *typeTable = NULL; + +static jclass cachedImplAtkWrapperDisposerClass = NULL; +static jmethodID cachedImplGetResourceMethod = NULL; +static jclass cachedImplAtkWrapperClass = NULL; +static jmethodID cachedImplRegisterPropertyChangeListenerMethod = NULL; +static jclass cachedImplAccessibleRelationClass = NULL; +static jfieldID cachedImplChildNodeOfFieldID = NULL; +static jfieldID cachedImplControlledByFieldID = NULL; +static jfieldID cachedImplControllerForFieldID = NULL; +static jfieldID cachedImplEmbeddedByFieldID = NULL; +static jfieldID cachedImplEmbedsFieldID = NULL; +static jfieldID cachedImplFlowsFromFieldID = NULL; +static jfieldID cachedImplFlowsToFieldID = NULL; +static jfieldID cachedImplLabelForFieldID = NULL; +static jfieldID cachedImplLabeledByFieldID = NULL; +static jfieldID cachedImplMemberOfFieldID = NULL; +static jfieldID cachedImplParentWindowOfFieldID = NULL; +static jfieldID cachedImplSubwindowOfFieldID = NULL; + +static GMutex cache_mutex; +static gboolean impl_cache_initialized = FALSE; + +static gboolean jaw_impl_init_jni_cache(JNIEnv *jniEnv); + +static void aggregate_interface(JNIEnv *jniEnv, JawObject *jaw_obj, + guint tflag) { + JAW_DEBUG("%p, %p, %u", jniEnv, jaw_obj, tflag); + + if (!jniEnv || !jaw_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JawImpl *jaw_impl = JAW_IMPL(tflag, jaw_obj); + if (jaw_impl == NULL) { + g_warning("%s: jaw_impl is NULL", G_STRFUNC); + return; + } + + jaw_impl->tflag = tflag; + + jobject ac = (*jniEnv)->NewGlobalRef(jniEnv, jaw_obj->acc_context); + if (ac == NULL) { + g_warning("%s: ac is NULL", G_STRFUNC); + return; + } + + jaw_impl->ifaceTable = g_hash_table_new(NULL, NULL); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_action_data_init(ac); + info->finalize = jaw_action_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_component_data_init(ac); + info->finalize = jaw_component_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_text_data_init(ac); + info->finalize = jaw_text_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_editable_text_data_init(ac); + info->finalize = jaw_editable_text_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer) + org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_hypertext_data_init(ac); + info->finalize = jaw_hypertext_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_image_data_init(ac); + info->finalize = jaw_image_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_selection_data_init(ac); + info->finalize = jaw_selection_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_value_data_init(ac); + info->finalize = jaw_value_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_table_data_init(ac); + info->finalize = jaw_table_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE, + (gpointer)info); + } + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL) { + JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1); + info->data = jaw_table_cell_data_init(ac); + info->finalize = jaw_table_cell_data_finalize; + g_hash_table_insert( + jaw_impl->ifaceTable, + (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL, + (gpointer)info); + } + + (*jniEnv)->DeleteGlobalRef(jniEnv, ac); +} + +JawImpl *jaw_impl_create_instance(JNIEnv *jniEnv, jobject ac) { + JAW_DEBUG("%p, %p", jniEnv, ac); + + if (!ac || !jniEnv) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JawImpl *jaw_impl; + + guint tflag = jaw_util_get_tflag_from_jobj(jniEnv, ac); + + jaw_impl = (JawImpl *)g_object_new(JAW_TYPE_IMPL(tflag), NULL); + if (jaw_impl == NULL) { + g_warning("%s: Failed to create new JawImpl object", G_STRFUNC); + (*jniEnv)->DeleteGlobalRef(jniEnv, ac); + return NULL; + } + + JawObject *jaw_obj = JAW_OBJECT(jaw_impl); + if (jaw_obj == NULL) { + g_warning("%s: Failed to create JawObject", G_STRFUNC); + g_object_unref(G_OBJECT(jaw_impl)); + return NULL; + } + + jobject weak_ref = (*jniEnv)->NewWeakGlobalRef(jniEnv, ac); + jaw_obj->acc_context = weak_ref; + jaw_obj->storedData = g_hash_table_new(g_str_hash, g_str_equal); + aggregate_interface(jniEnv, jaw_obj, tflag); + atk_object_initialize(ATK_OBJECT(jaw_impl), NULL); + + return jaw_impl; +} + +JawImpl *jaw_impl_find_instance(JNIEnv *jniEnv, jobject ac) { + JAW_DEBUG("%p, %p", jniEnv, ac); + + if (!ac || !jniEnv) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + if (!jaw_impl_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + // Check if there is JawImpl associated with the accessible context + jlong reference = (*jniEnv)->CallStaticLongMethod( + jniEnv, cachedImplAtkWrapperDisposerClass, cachedImplGetResourceMethod, + ac); + + // If a valid reference exists, return the existing JawImpl instance + if (reference != -1) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return (JawImpl *)reference; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; +} + +static void jaw_impl_class_intern_init(gpointer klass, gpointer data) { + JAW_DEBUG("%p, %p", klass, data); + + if (!klass) { + g_warning( + "Null argument passed to function jaw_impl_class_intern_init"); + return; + } + + if (jaw_impl_parent_class == NULL) { + jaw_impl_parent_class = g_type_class_peek_parent(klass); + } + + jaw_impl_class_init((JawImplClass *)klass); +} + +GType jaw_impl_get_type(guint tflag) { + JAW_DEBUG("%u", tflag); + GType type; + + static const GInterfaceInfo atk_action_info = { + (GInterfaceInitFunc)jaw_action_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_component_info = { + (GInterfaceInitFunc)jaw_component_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_text_info = { + (GInterfaceInitFunc)jaw_text_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_editable_text_info = { + (GInterfaceInitFunc)jaw_editable_text_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_hypertext_info = { + (GInterfaceInitFunc)jaw_hypertext_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_image_info = { + (GInterfaceInitFunc)jaw_image_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_selection_info = { + (GInterfaceInitFunc)jaw_selection_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_value_info = { + (GInterfaceInitFunc)jaw_value_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_table_info = { + (GInterfaceInitFunc)jaw_table_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + static const GInterfaceInfo atk_table_cell_info = { + (GInterfaceInitFunc)jaw_table_cell_interface_init, + (GInterfaceFinalizeFunc)NULL, NULL}; + + g_mutex_lock(&typeTableMutex); + if (typeTable == NULL) { + typeTable = g_hash_table_new(NULL, NULL); + } + + type = GPOINTER_TO_GTYPE( + g_hash_table_lookup(typeTable, GUINT_TO_POINTER(tflag))); + g_mutex_unlock(&typeTableMutex); + if (type == 0) { + GTypeInfo tinfo = { + sizeof(JawImplClass), + (GBaseInitFunc)NULL, /* base init */ + (GBaseFinalizeFunc)NULL, /* base finalize */ + (GClassInitFunc)jaw_impl_class_intern_init, /*class init */ + (GClassFinalizeFunc)NULL, /* class finalize */ + NULL, /* class data */ + sizeof(JawImpl), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc)NULL, /* instance init */ + NULL /* value table */ + }; + + gchar className[20]; + g_sprintf(className, "JawImpl_%d", tflag); + + type = g_type_register_static(JAW_TYPE_OBJECT, className, &tinfo, 0); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION) + g_type_add_interface_static(type, ATK_TYPE_ACTION, + &atk_action_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT) + g_type_add_interface_static(type, ATK_TYPE_COMPONENT, + &atk_component_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT) + g_type_add_interface_static(type, ATK_TYPE_TEXT, &atk_text_info); + + if (tflag & + org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT) + g_type_add_interface_static(type, ATK_TYPE_EDITABLE_TEXT, + &atk_editable_text_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT) + g_type_add_interface_static(type, ATK_TYPE_HYPERTEXT, + &atk_hypertext_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE) + g_type_add_interface_static(type, ATK_TYPE_IMAGE, &atk_image_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION) + g_type_add_interface_static(type, ATK_TYPE_SELECTION, + &atk_selection_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE) + g_type_add_interface_static(type, ATK_TYPE_VALUE, &atk_value_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE) + g_type_add_interface_static(type, ATK_TYPE_TABLE, &atk_table_info); + + if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL) + g_type_add_interface_static(type, ATK_TYPE_TABLE_CELL, + &atk_table_cell_info); + + g_mutex_lock(&typeTableMutex); + g_hash_table_insert(typeTable, GINT_TO_POINTER(tflag), + JAW_TYPE_TO_POINTER(type)); + g_mutex_unlock(&typeTableMutex); + } + + return type; +} + +static void jaw_impl_class_init(JawImplClass *klass) { + JAW_DEBUG("%p", klass); + + if (!klass) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + if (gobject_class == NULL) { + g_warning("%s: gobject_class is NULL", G_STRFUNC); + return; + } + gobject_class->dispose = jaw_impl_dispose; + gobject_class->finalize = jaw_impl_finalize; + + AtkObjectClass *atk_class = ATK_OBJECT_CLASS(klass); + if (atk_class == NULL) { + g_warning("%s: atk_class is NULL", G_STRFUNC); + return; + } + atk_class->initialize = jaw_impl_initialize; + + JawObjectClass *jaw_class = JAW_OBJECT_CLASS(klass); + if (jaw_class == NULL) { + g_warning("%s: jaw_class is NULL", G_STRFUNC); + return; + } + jaw_class->get_interface_data = jaw_impl_get_interface_data; +} + +static void jaw_impl_dispose(GObject *gobject) { + JAW_DEBUG("%p", gobject); + /* Chain up to parent's dispose */ + G_OBJECT_CLASS(jaw_impl_parent_class)->dispose(gobject); +} + +static void jaw_impl_finalize(GObject *gobject) { + JAW_DEBUG("%p", gobject); + + if (!gobject) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JawObject *jaw_obj = JAW_OBJECT(gobject); + if (jaw_obj == NULL) { + g_warning("%s: jaw_obj is NULL", G_STRFUNC); + return; + } + JawImpl *jaw_impl = (JawImpl *)jaw_obj; + if (jaw_impl == NULL) { + g_warning("%s: jaw_impl is NULL", G_STRFUNC); + return; + } + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return; + } + + (*jniEnv)->DeleteWeakGlobalRef(jniEnv, jaw_obj->acc_context); + jaw_obj->acc_context = NULL; + + /* Interface finalize */ + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init(&iter, jaw_impl->ifaceTable); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + JawInterfaceInfo *info = (JawInterfaceInfo *)value; + if (info != NULL) { + info->finalize(info->data); + g_free(info); + } + + g_hash_table_iter_remove(&iter); + } + if (jaw_impl->ifaceTable != NULL) { + g_hash_table_unref(jaw_impl->ifaceTable); + } + if (jaw_obj->storedData != NULL) { + g_hash_table_destroy(jaw_obj->storedData); + } + + /* Chain up to parent's finalize */ + G_OBJECT_CLASS(jaw_impl_parent_class)->finalize(gobject); +} + +static gpointer jaw_impl_get_interface_data(JawObject *jaw_obj, guint iface) { + JAW_DEBUG("%p, %u", jaw_obj, iface); + + if (!jaw_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JawImpl *jaw_impl = (JawImpl *)jaw_obj; + if (jaw_obj == NULL) { + g_warning("%s: jaw_obj is NULL", G_STRFUNC); + return NULL; + } + + if (jaw_impl == NULL || jaw_impl->ifaceTable == NULL) { + return NULL; + } + + JawInterfaceInfo *info = + g_hash_table_lookup(jaw_impl->ifaceTable, GUINT_TO_POINTER(iface)); + + if (info != NULL) { + return info->data; + } + + return NULL; +} + +static void jaw_impl_initialize(AtkObject *atk_obj, gpointer data) { + JAW_DEBUG("%p, %p", atk_obj, data); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + ATK_OBJECT_CLASS(jaw_impl_parent_class)->initialize(atk_obj, data); + + JawObject *jaw_obj = JAW_OBJECT(atk_obj); + if (jaw_obj == NULL) { + g_warning("%s: jaw_obj is NULL", G_STRFUNC); + return; + } + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return; + } + + if (!jaw_impl_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jobject ac = (*jniEnv)->NewGlobalRef(jniEnv, jaw_obj->acc_context); + if (ac == NULL) { + g_warning("%s: Failed to create global reference to acc_context", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallStaticVoidMethod( + jniEnv, cachedImplAtkWrapperClass, + cachedImplRegisterPropertyChangeListenerMethod, ac); + + (*jniEnv)->DeleteGlobalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * Checks if the given jKey (name of the relation) matches the value of static + * field in AccessibleRelation identified by fieldID. + * + * @param jniEnv JNI environment pointer + * @param jKey key of AccessibleRelation, the name of the relation + * @param fieldID cached field ID for the relation field + * @return TRUE if jKey equals the corresponding static field, FALSE + * otherwise + */ +static gboolean is_java_relation_key(JNIEnv *jniEnv, jstring jKey, + jfieldID fieldID) { + JAW_DEBUG("%p, %p, %p", jniEnv, jKey, fieldID); + + if (!jniEnv || !fieldID) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + if (!jaw_impl_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return FALSE; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return FALSE; + } + + jstring jConstKey = (*jniEnv)->GetStaticObjectField( + jniEnv, cachedImplAccessibleRelationClass, fieldID); + + // jKey and jConstKey may be null + jboolean result = (*jniEnv)->IsSameObject(jniEnv, jKey, jConstKey); + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * Compares the given key of Java AccessibleRelation (jrel_key) with some of + * AccessibleRelation fields. If a match is found, returns the + * corresponding AtkRelationType; otherwise, returns ATK_RELATION_NULL. + * + * @param jniEnv JNI environment pointer + * @param jrel_key key of AccessibleRelation, the name of the relation + * @return Corresponding AtkRelationType or ATK_RELATION_NULL + */ +AtkRelationType jaw_impl_get_atk_relation_type(JNIEnv *jniEnv, + jstring jrel_key) { + JAW_DEBUG("%p, %p", jniEnv, jrel_key); + + if (!jaw_impl_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return FALSE; + } + + if (is_java_relation_key(jniEnv, jrel_key, cachedImplChildNodeOfFieldID)) + return ATK_RELATION_NODE_CHILD_OF; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplControlledByFieldID)) + return ATK_RELATION_CONTROLLED_BY; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplControllerForFieldID)) + return ATK_RELATION_CONTROLLER_FOR; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplEmbeddedByFieldID)) + return ATK_RELATION_EMBEDDED_BY; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplEmbedsFieldID)) + return ATK_RELATION_EMBEDS; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplFlowsFromFieldID)) + return ATK_RELATION_FLOWS_FROM; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplFlowsToFieldID)) + return ATK_RELATION_FLOWS_TO; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplLabelForFieldID)) + return ATK_RELATION_LABEL_FOR; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplLabeledByFieldID)) + return ATK_RELATION_LABELLED_BY; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplMemberOfFieldID)) + return ATK_RELATION_MEMBER_OF; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplParentWindowOfFieldID)) + return ATK_RELATION_PARENT_WINDOW_OF; + if (is_java_relation_key(jniEnv, jrel_key, cachedImplSubwindowOfFieldID)) + return ATK_RELATION_SUBWINDOW_OF; + return ATK_RELATION_NULL; +} + +static gboolean jaw_impl_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (impl_cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localAtkWrapperDisposer = (*jniEnv)->FindClass( + jniEnv, "org/GNOME/Accessibility/AtkWrapperDisposer"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkWrapperDisposer == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkWrapperDisposer class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImplAtkWrapperDisposerClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAtkWrapperDisposer); + (*jniEnv)->DeleteLocalRef(jniEnv, localAtkWrapperDisposer); + + if (cachedImplAtkWrapperDisposerClass == NULL) { + g_warning("%s: Failed to create global reference for " + "AtkWrapperDisposer class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImplGetResourceMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedImplAtkWrapperDisposerClass, "get_resource", + "(Ljavax/accessibility/AccessibleContext;)J"); + + jclass localAtkWrapper = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkWrapper"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkWrapper == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkWrapper class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImplAtkWrapperClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAtkWrapper); + (*jniEnv)->DeleteLocalRef(jniEnv, localAtkWrapper); + + if (cachedImplAtkWrapperClass == NULL) { + g_warning("%s: Failed to create global reference for AtkWrapper class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImplRegisterPropertyChangeListenerMethod = + (*jniEnv)->GetStaticMethodID( + jniEnv, cachedImplAtkWrapperClass, + "register_property_change_listener", + "(Ljavax/accessibility/AccessibleContext;)V"); + + jclass localAccessibleRelation = + (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleRelation"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleRelation == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AccessibleRelation class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImplAccessibleRelationClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleRelation); + (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleRelation); + + if (cachedImplAccessibleRelationClass == NULL) { + g_warning("%s: Failed to create global reference for " + "AccessibleRelation class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedImplChildNodeOfFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "CHILD_NODE_OF", "Ljava/lang/String;"); + + cachedImplControlledByFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "CONTROLLED_BY", "Ljava/lang/String;"); + + cachedImplControllerForFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "CONTROLLER_FOR", "Ljava/lang/String;"); + + cachedImplEmbeddedByFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "EMBEDDED_BY", "Ljava/lang/String;"); + + cachedImplEmbedsFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "EMBEDS", "Ljava/lang/String;"); + + cachedImplFlowsFromFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "FLOWS_FROM", "Ljava/lang/String;"); + + cachedImplFlowsToFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "FLOWS_TO", "Ljava/lang/String;"); + + cachedImplLabelForFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "LABEL_FOR", "Ljava/lang/String;"); + + cachedImplLabeledByFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "LABELED_BY", "Ljava/lang/String;"); + + cachedImplMemberOfFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "MEMBER_OF", "Ljava/lang/String;"); + + cachedImplParentWindowOfFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "PARENT_WINDOW_OF", "Ljava/lang/String;"); + + cachedImplSubwindowOfFieldID = + (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass, + "SUBWINDOW_OF", "Ljava/lang/String;"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedImplGetResourceMethod == NULL || + cachedImplRegisterPropertyChangeListenerMethod == NULL || + cachedImplChildNodeOfFieldID == NULL || + cachedImplControlledByFieldID == NULL || + cachedImplControllerForFieldID == NULL || + cachedImplEmbeddedByFieldID == NULL || + cachedImplEmbedsFieldID == NULL || cachedImplFlowsFromFieldID == NULL || + cachedImplFlowsToFieldID == NULL || cachedImplLabelForFieldID == NULL || + cachedImplLabeledByFieldID == NULL || + cachedImplMemberOfFieldID == NULL || + cachedImplParentWindowOfFieldID == NULL || + cachedImplSubwindowOfFieldID == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more JawImpl classes or " + "method/field IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + impl_cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedImplAtkWrapperDisposerClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperDisposerClass); + cachedImplAtkWrapperDisposerClass = NULL; + } + cachedImplGetResourceMethod = NULL; + if (cachedImplAtkWrapperClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperClass); + cachedImplAtkWrapperClass = NULL; + } + cachedImplRegisterPropertyChangeListenerMethod = NULL; + if (cachedImplAccessibleRelationClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAccessibleRelationClass); + cachedImplAccessibleRelationClass = NULL; + } + cachedImplChildNodeOfFieldID = NULL; + cachedImplControlledByFieldID = NULL; + cachedImplControllerForFieldID = NULL; + cachedImplEmbeddedByFieldID = NULL; + cachedImplEmbedsFieldID = NULL; + cachedImplFlowsFromFieldID = NULL; + cachedImplFlowsToFieldID = NULL; + cachedImplLabelForFieldID = NULL; + cachedImplLabeledByFieldID = NULL; + cachedImplMemberOfFieldID = NULL; + cachedImplParentWindowOfFieldID = NULL; + cachedImplSubwindowOfFieldID = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_impl_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedImplAtkWrapperDisposerClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperDisposerClass); + cachedImplAtkWrapperDisposerClass = NULL; + } + cachedImplGetResourceMethod = NULL; + if (cachedImplAtkWrapperClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperClass); + cachedImplAtkWrapperClass = NULL; + } + cachedImplRegisterPropertyChangeListenerMethod = NULL; + if (cachedImplAccessibleRelationClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAccessibleRelationClass); + cachedImplAccessibleRelationClass = NULL; + } + cachedImplChildNodeOfFieldID = NULL; + cachedImplControlledByFieldID = NULL; + cachedImplControllerForFieldID = NULL; + cachedImplEmbeddedByFieldID = NULL; + cachedImplEmbedsFieldID = NULL; + cachedImplFlowsFromFieldID = NULL; + cachedImplFlowsToFieldID = NULL; + cachedImplLabelForFieldID = NULL; + cachedImplLabeledByFieldID = NULL; + cachedImplMemberOfFieldID = NULL; + cachedImplParentWindowOfFieldID = NULL; + cachedImplSubwindowOfFieldID = NULL; + impl_cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.h new file mode 100644 index 000000000000..f843d0f6a628 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.h @@ -0,0 +1,115 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _JAW_IMPL_H_ +#define _JAW_IMPL_H_ + +#include "jawobject.h" +#include + +G_BEGIN_DECLS + +#define JAW_TYPE_IMPL(tf) (jaw_impl_get_type(tf)) +#define JAW_IMPL(tf, obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_IMPL(tf), JawImpl)) +#define JAW_IMPL_CLASS(tf, klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_IMPL(tf), JawImplClass)) +#define JAW_IS_IMPL(tf, obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_IMPL(tf))) +#define JAW_IS_IMPL_CLASS(tf, klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_IMPL(tf))) +#define JAW_IMPL_GET_CLASS(tf, obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_IMPL(tf), JawImplClass)) + +#ifdef GPOINTER_TO_SIZE +#define GPOINTER_TO_GTYPE(gpointer) (GPOINTER_TO_SIZE(gpointer)) +#endif +#ifdef GSIZE_TO_POINTER +#define JAW_TYPE_TO_POINTER(gtype) (GSIZE_TO_POINTER(gtype)) +#endif + +typedef struct _JawImpl JawImpl; +typedef struct _JawImplClass JawImplClass; + +/** + * JawImpl: + * A subclass of JawObject that represents an implementation + * of an accessibility object. + **/ +struct _JawImpl { + JawObject parent; + + GHashTable *ifaceTable; + unsigned tflag; +}; + +JawImpl *jaw_impl_create_instance(JNIEnv *, jobject); +JawImpl *jaw_impl_find_instance(JNIEnv *, jobject); + +GType jaw_impl_get_type(guint); +AtkRelationType jaw_impl_get_atk_relation_type(JNIEnv *jniEnv, + jstring jrel_key); + +struct _JawImplClass { + JawObjectClass parent_class; +}; + +extern void jaw_action_interface_init(AtkActionIface *, gpointer); +extern gpointer jaw_action_data_init(jobject); +extern void jaw_action_data_finalize(gpointer); + +extern void jaw_component_interface_init(AtkComponentIface *, gpointer); +extern gpointer jaw_component_data_init(jobject); +extern void jaw_component_data_finalize(gpointer); + +extern void jaw_editable_text_interface_init(AtkEditableTextIface *, gpointer); +extern gpointer jaw_editable_text_data_init(jobject); +extern void jaw_editable_text_data_finalize(gpointer); + +extern void jaw_hypertext_interface_init(AtkHypertextIface *, gpointer); +extern gpointer jaw_hypertext_data_init(jobject); +extern void jaw_hypertext_data_finalize(gpointer); + +extern void jaw_image_interface_init(AtkImageIface *, gpointer); +extern gpointer jaw_image_data_init(jobject); +extern void jaw_image_data_finalize(gpointer); + +extern void jaw_selection_interface_init(AtkSelectionIface *, gpointer); +extern gpointer jaw_selection_data_init(jobject); +extern void jaw_selection_data_finalize(gpointer); + +extern void jaw_table_interface_init(AtkTableIface *, gpointer); +extern gpointer jaw_table_data_init(jobject); +extern void jaw_table_data_finalize(gpointer); + +extern void jaw_table_cell_interface_init(AtkTableCellIface *, gpointer); +extern gpointer jaw_table_cell_data_init(jobject ac); +extern void jaw_table_cell_data_finalize(gpointer); + +extern void jaw_text_interface_init(AtkTextIface *, gpointer); +extern gpointer jaw_text_data_init(jobject); +extern void jaw_text_data_finalize(gpointer); + +extern void jaw_value_interface_init(AtkValueIface *, gpointer); +extern gpointer jaw_value_data_init(jobject); +extern void jaw_value_data_finalize(gpointer); + +G_END_DECLS + +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.c new file mode 100644 index 000000000000..3e61320be836 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.c @@ -0,0 +1,1497 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawobject.h" +#include "jawcache.h" +#include "jawimpl.h" +#include "jawtoplevel.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkObject: + * + * The base object class for the Accessibility Toolkit API. + * + * This class is the primary class for accessibility support via the + * Accessibility ToolKit (ATK). Objects which are instances of + * #AtkObject (or instances of AtkObject-derived types) are queried + * for properties which relate basic (and generic) properties of a UI + * component such as name and description. Instances of #AtkObject + * may also be queried as to whether they implement other ATK + * interfaces (e.g. #AtkAction, #AtkComponent, etc.), as appropriate + * to the role which a given UI component plays in a user interface. + * + * All UI components in an application which provide useful + * information or services to the user must provide corresponding + * #AtkObject instances on request (in GTK+, for instance, usually on + * a call to #gtk_widget_get_accessible ()), either via ATK support + * built into the toolkit for the widget class or ancestor class, or + * in the case of custom widgets, if the inherited #AtkObject + * implementation is insufficient, via instances of a new #AtkObject + * subclass. + * + * See [class@AtkObjectFactory], [class@AtkRegistry]. (GTK+ users see also + * #GtkAccessible). + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +static jclass cachedObjectAtkObjectClass = NULL; +static jmethodID cachedObjectGetAccessibleParentMethod = NULL; +static jmethodID cachedObjectSetAccessibleParentMethod = NULL; +static jmethodID cachedObjectGetAccessibleNameMethod = NULL; +static jmethodID cachedObjectSetAccessibleNameMethod = NULL; +static jmethodID cachedObjectGetAccessibleDescriptionMethod = NULL; +static jmethodID cachedObjectSetAccessibleDescriptionMethod = NULL; +static jmethodID cachedObjectGetAccessibleChildrenCountMethod = NULL; +static jmethodID cachedObjectGetAccessibleIndexInParentMethod = NULL; +static jmethodID cachedObjectGetArrayAccessibleStateMethod = NULL; +static jmethodID cachedObjectGetLocaleMethod = NULL; +static jmethodID cachedObjectGetArrayAccessibleRelationMethod = NULL; +static jmethodID cachedObjectGetAccessibleChildMethod = NULL; +static jclass cachedObjectAccessibleStateClass = NULL; +static jfieldID cachedObjectCollapsedFieldID = NULL; +static jclass cachedObjectWrapKeyAndTargetClass = NULL; +static jfieldID cachedObjectRelationsFieldID = NULL; +static jfieldID cachedObjectKeyFieldID = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_object_init_jni_cache(JNIEnv *jniEnv); + +static void jaw_object_initialize(AtkObject *jaw_obj, gpointer data); +static void jaw_object_dispose(GObject *gobject); +static void jaw_object_finalize(GObject *gobject); + +static const gchar *jaw_object_get_name(AtkObject *atk_obj); +static const gchar *jaw_object_get_description(AtkObject *atk_obj); +static gint jaw_object_get_n_children(AtkObject *atk_obj); +static gint jaw_object_get_index_in_parent(AtkObject *atk_obj); +static AtkRole jaw_object_get_role(AtkObject *atk_obj); +static AtkStateSet *jaw_object_ref_state_set(AtkObject *atk_obj); +static AtkObject *jaw_object_get_parent(AtkObject *obj); +static void jaw_object_set_name(AtkObject *atk_obj, const gchar *name); +static void jaw_object_set_description(AtkObject *atk_obj, + const gchar *description); +static void jaw_object_set_parent(AtkObject *atk_obj, AtkObject *parent); +static void jaw_object_set_role(AtkObject *atk_obj, AtkRole role); +static const gchar *jaw_object_get_object_locale(AtkObject *atk_obj); +static AtkRelationSet *jaw_object_ref_relation_set(AtkObject *atk_obj); +static AtkObject *jaw_object_ref_child(AtkObject *atk_obj, gint i); + +static gpointer parent_class = NULL; + +enum { + ACTIVATE, + CREATE, + DEACTIVATE, + DESTROY, + MAXIMIZE, + MINIMIZE, + MOVE, + RESIZE, + RESTORE, + LAST_SIGNAL +}; + +static guint jaw_window_signals[LAST_SIGNAL] = { + 0, +}; + +G_DEFINE_TYPE(JawObject, jaw_object, ATK_TYPE_OBJECT); + +#define JAW_GET_OBJECT(atk_obj, def_ret) \ + JAW_GET_OBJ(atk_obj, JAW_OBJECT, JawObject, jaw_obj, acc_context, jniEnv, \ + ac, def_ret) + +static guint jaw_window_add_signal(const gchar *name, JawObjectClass *klass) { + JAW_DEBUG("%s, %p", name, klass); + return g_signal_new(name, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, + (GSignalAccumulator)NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +static void jaw_object_class_init(JawObjectClass *klass) { + JAW_DEBUG("%p", klass); + + if (klass == NULL) { + g_warning("%s: Null argument klass passed to the function", G_STRFUNC); + return; + } + + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + gobject_class->dispose = jaw_object_dispose; + gobject_class->finalize = jaw_object_finalize; + + AtkObjectClass *atk_class = ATK_OBJECT_CLASS(klass); + parent_class = g_type_class_peek_parent(klass); + + atk_class->get_name = jaw_object_get_name; + atk_class->get_description = jaw_object_get_description; + atk_class->get_parent = jaw_object_get_parent; + atk_class->get_n_children = jaw_object_get_n_children; + atk_class->ref_child = jaw_object_ref_child; + atk_class->get_index_in_parent = jaw_object_get_index_in_parent; + atk_class->ref_relation_set = jaw_object_ref_relation_set; + atk_class->get_role = jaw_object_get_role; + // atk_class->get_layer is done by atk + atk_class->get_mdi_zorder = + NULL; // missing java support for atk_class->get_mdi_zorder + atk_class->ref_state_set = jaw_object_ref_state_set; + atk_class->set_name = jaw_object_set_name; + atk_class->set_description = jaw_object_set_description; + atk_class->set_parent = jaw_object_set_parent; + atk_class->set_role = jaw_object_set_role; + atk_class->initialize = jaw_object_initialize; + atk_class->get_attributes = NULL; // TODO: atk_class->get_attributes + atk_class->get_object_locale = jaw_object_get_object_locale; + + jaw_window_signals[ACTIVATE] = jaw_window_add_signal("activate", klass); + jaw_window_signals[CREATE] = jaw_window_add_signal("create", klass); + jaw_window_signals[DEACTIVATE] = jaw_window_add_signal("deactivate", klass); + jaw_window_signals[DESTROY] = jaw_window_add_signal("destroy", klass); + jaw_window_signals[MAXIMIZE] = jaw_window_add_signal("maximize", klass); + jaw_window_signals[MINIMIZE] = jaw_window_add_signal("minimize", klass); + jaw_window_signals[MOVE] = jaw_window_add_signal("move", klass); + jaw_window_signals[RESIZE] = jaw_window_add_signal("resize", klass); + jaw_window_signals[RESTORE] = jaw_window_add_signal("restore", klass); + + klass->get_interface_data = NULL; +} + +static void jaw_object_initialize(AtkObject *atk_obj, gpointer data) { + JAW_DEBUG("%p, %p", atk_obj, data); + + if (atk_obj == NULL) { + g_warning("%s: Null argument atk_obj passed to the function", + G_STRFUNC); + return; + } + + ATK_OBJECT_CLASS(jaw_object_parent_class)->initialize(atk_obj, data); +} + +gpointer jaw_object_get_interface_data(JawObject *jaw_obj, guint iface) { + JAW_DEBUG("%p, %u", jaw_obj, iface); + + if (jaw_obj == NULL) { + g_warning("%s: Null argument jaw_obj passed to the function", + G_STRFUNC); + return NULL; + } + + JawObjectClass *klass = JAW_OBJECT_GET_CLASS(jaw_obj); + if (klass == NULL) { + g_warning("%s: klass is NULL", G_STRFUNC); + return NULL; + } + if (klass->get_interface_data) + return klass->get_interface_data(jaw_obj, iface); + + return NULL; +} + +static void jaw_object_init(JawObject *object) { + JAW_DEBUG("%p", object); + + if (object == NULL) { + g_warning("%s: Null argument object passed to the function", G_STRFUNC); + return; + } + + AtkObject *atk_obj = ATK_OBJECT(object); + if (atk_obj == NULL) { + g_warning("%s: atk_obj is NULL", G_STRFUNC); + return; + } + atk_obj->description = NULL; + + g_mutex_init(&object->mutex); + object->state_set = atk_state_set_new(); +} + +static void jaw_object_dispose(GObject *gobject) { + JAW_DEBUG("%p", gobject); + + if (gobject == NULL) { + g_warning("%s: Null argument gobject passed to the function", + G_STRFUNC); + G_OBJECT_CLASS(jaw_object_parent_class)->dispose(gobject); + return; + } + + G_OBJECT_CLASS(jaw_object_parent_class)->dispose(gobject); +} + +static void jaw_object_finalize(GObject *gobject) { + JAW_DEBUG("%p", gobject); + + if (gobject == NULL) { + g_warning("%s: Null argument gobject passed to the function", + G_STRFUNC); + return; + } + + /* Customized finalize code */ + JawObject *jaw_obj = JAW_OBJECT(gobject); + if (jaw_obj == NULL) { + g_debug("%s: jaw_obj is NULL", G_STRFUNC); + G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject); + return; + } + AtkObject *atk_obj = ATK_OBJECT(gobject); + if (atk_obj == NULL) { + g_debug("%s: atk_obj is NULL", G_STRFUNC); + G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject); + return; + } + g_mutex_lock(&jaw_obj->mutex); + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_debug("%s: jniEnv is NULL", G_STRFUNC); + g_mutex_unlock(&jaw_obj->mutex); + g_mutex_clear(&jaw_obj->mutex); + G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject); + return; + } + + if (jaw_obj->jstrName != NULL) { + if (atk_obj->name != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrName, + atk_obj->name); + atk_obj->name = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrName); + jaw_obj->jstrName = NULL; + } + + if (jaw_obj->jstrDescription != NULL) { + if (atk_obj->description != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrDescription, + atk_obj->description); + atk_obj->description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrDescription); + jaw_obj->jstrDescription = NULL; + } + + if (jaw_obj->jstrLocale != NULL) { + if (jaw_obj->locale != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrLocale, + jaw_obj->locale); + jaw_obj->locale = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrLocale); + jaw_obj->jstrLocale = NULL; + } + + if (jaw_obj->state_set != NULL) { + g_object_unref(G_OBJECT(jaw_obj->state_set)); + } + + g_mutex_unlock(&jaw_obj->mutex); + g_mutex_clear(&jaw_obj->mutex); + + /* Chain up to parent's finalize method */ + G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject); +} + +/** + * jaw_object_get_parent: + * @accessible: an #AtkObject + * + * Gets the accessible parent of the accessible. + * + * Returns: (transfer none): an #AtkObject representing the accessible + * parent of the accessible + **/ +static AtkObject *jaw_object_get_parent(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + AtkObject *root = atk_get_root(); + int toplevel_child_index = + jaw_toplevel_get_child_index(JAW_TOPLEVEL(root), atk_obj); + if (toplevel_child_index != -1) { + return ATK_OBJECT(root); + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jparent = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetAccessibleParentMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jparent == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + AtkObject *parent_obj = + (AtkObject *)jaw_impl_find_instance(jniEnv, jparent); + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + if (parent_obj != NULL) { + return parent_obj; + } + + g_warning( + "jaw_object_get_parent: didn't find jaw for parent, returning null"); + return NULL; +} + +/** + * jaw_object_set_parent: + * @accessible: an #AtkObject + * + * Gets the accessible parent of the accessible. + * + * Returns: (transfer none): an #AtkObject representing the accessible + * parent of the accessible + **/ +static void jaw_object_set_parent(AtkObject *atk_obj, AtkObject *parent) { + JAW_DEBUG("%p, %p", atk_obj, parent); + + if (!atk_obj || !parent) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_OBJECT(atk_obj, ); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + JawObject *jaw_par = JAW_OBJECT(parent); + if (jaw_par == NULL) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + jobject pa = (*jniEnv)->NewLocalRef(jniEnv, jaw_par->acc_context); + if (pa == NULL) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallStaticVoidMethod(jniEnv, cachedObjectAtkObjectClass, + cachedObjectSetAccessibleParentMethod, ac, + pa); + jaw_jni_clear_exception(jniEnv); + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->DeleteLocalRef(jniEnv, pa); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_object_get_name: + * @role: The #AtkRole whose name is required + * + * Gets the description string describing the #AtkRole @role. + * + * Returns: the string describing the AtkRole + */ +static const gchar *jaw_object_get_name(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + atk_obj->name = (gchar *)ATK_OBJECT_CLASS(parent_class)->get_name(atk_obj); + + if (atk_object_get_role(atk_obj) == ATK_ROLE_COMBO_BOX && + atk_object_get_n_accessible_children(atk_obj) == 1) { + AtkSelection *selection = ATK_SELECTION(atk_obj); + if (selection != NULL) { + // The caller of the method takes ownership of the returned data, + // and is responsible for freeing it. + AtkObject *child = atk_selection_ref_selection(selection, 0); + if (child != NULL) { + const gchar *name = atk_object_get_name(child); + g_object_unref(G_OBJECT(child)); + if (name) + JAW_DEBUG("-> %s", name); + return name; + } + } + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, cachedObjectGetAccessibleNameMethod, + ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&jaw_obj->mutex); + if (jaw_obj->jstrName != NULL) { + if (atk_obj->name != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrName, + atk_obj->name); + atk_obj->name = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrName); + jaw_obj->jstrName = NULL; + } + + if (jstr != NULL) { + jaw_obj->jstrName = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (jaw_obj->jstrName == NULL) { + g_mutex_unlock(&jaw_obj->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + atk_obj->name = (gchar *)(*jniEnv)->GetStringUTFChars( + jniEnv, jaw_obj->jstrName, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || atk_obj->name == NULL) { + jaw_jni_clear_exception(jniEnv); + + // jaw_obj->jstrName != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrName); + jaw_obj->jstrName = NULL; + + g_mutex_unlock(&jaw_obj->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + } + g_mutex_unlock(&jaw_obj->mutex); + + if (atk_obj->name != NULL) { + JAW_DEBUG("-> %s", atk_obj->name); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return atk_obj->name; +} + +/** + * jaw_object_set_name: + * @accessible: an #AtkObject + * @name: a character string to be set as the accessible name + * + * Sets the accessible name of the accessible. You can't set the name + * to NULL. This is reserved for the initial value. In this aspect + * NULL is similar to ATK_ROLE_UNKNOWN. If you want to set the name to + * a empty value you can use "". + **/ +static void jaw_object_set_name(AtkObject *atk_obj, const gchar *name) { + JAW_DEBUG("%p, %s", atk_obj, name); + + if (!atk_obj || !name) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_OBJECT(atk_obj, ); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jstring jstr = NULL; + if (name != NULL) { + jstr = (*jniEnv)->NewStringUTF(jniEnv, name); + } + + (*jniEnv)->CallStaticVoidMethod(jniEnv, cachedObjectAtkObjectClass, + cachedObjectSetAccessibleNameMethod, ac, + jstr); + jaw_jni_clear_exception(jniEnv); + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_object_get_description: + * @accessible: an #AtkObject + * + * Gets the accessible description of the accessible. + * + * Returns: a character string representing the accessible description + * of the accessible. + * + **/ +static const gchar *jaw_object_get_description(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetAccessibleDescriptionMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&jaw_obj->mutex); + if (jaw_obj->jstrDescription != NULL) { + if (atk_obj->description != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrDescription, + atk_obj->description); + atk_obj->description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrDescription); + jaw_obj->jstrDescription = NULL; + } + + if (jstr != NULL) { + jaw_obj->jstrDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (jaw_obj->jstrDescription == NULL) { + g_mutex_unlock(&jaw_obj->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + atk_obj->description = (gchar *)(*jniEnv)->GetStringUTFChars( + jniEnv, jaw_obj->jstrDescription, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || atk_obj->description == NULL) { + jaw_jni_clear_exception(jniEnv); + + // jaw_obj->jstrDescription != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrDescription); + jaw_obj->jstrDescription = NULL; + + g_mutex_unlock(&jaw_obj->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + } + g_mutex_unlock(&jaw_obj->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return atk_obj->description; +} + +/** + * jaw_object_set_description: + * @accessible: an #AtkObject + * @description: a character string to be set as the accessible description + * + * Sets the accessible description of the accessible. You can't set + * the description to NULL. This is reserved for the initial value. In + * this aspect NULL is similar to ATK_ROLE_UNKNOWN. If you want to set + * the name to a empty value you can use "". + **/ +static void jaw_object_set_description(AtkObject *atk_obj, + const gchar *description) { + JAW_DEBUG("%p, %s", atk_obj, description); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_OBJECT(atk_obj, ); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jstring jstr = NULL; + if (description != NULL) { + jstr = (*jniEnv)->NewStringUTF(jniEnv, description); + } + + (*jniEnv)->CallStaticVoidMethod(jniEnv, cachedObjectAtkObjectClass, + cachedObjectSetAccessibleDescriptionMethod, + ac, jstr); + jaw_jni_clear_exception(jniEnv); + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_object_get_n_children: + * @accessible: an #AtkObject + * + * Gets the number of accessible children of the accessible. + * + * Returns: an integer representing the number of accessible children + * of the accessible. + **/ +static gint jaw_object_get_n_children(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_OBJECT(atk_obj, 0); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return 0; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + jint count = (*jniEnv)->CallStaticIntMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetAccessibleChildrenCountMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return (gint)count; +} + +/** + * jaw_object_get_index_in_parent: + * @accessible: an #AtkObject + * + * Gets the 0-based index of this accessible in its parent; returns -1 if the + * accessible does not have an accessible parent. + * + * Returns: an integer which is the index of the accessible in its parent + **/ +static gint jaw_object_get_index_in_parent(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return -1; + } + + int toplevel_child_index = + jaw_toplevel_get_child_index(JAW_TOPLEVEL(atk_get_root()), atk_obj); + if (toplevel_child_index != -1) { + return toplevel_child_index; + } + + JAW_GET_OBJECT(atk_obj, -1); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return -1; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return -1; + } + + jint index = (*jniEnv)->CallStaticIntMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetAccessibleIndexInParentMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return (gint)index; +} + +/** + * jaw_object_get_role: + * @accessible: an #AtkObject + * + * Gets the role of the accessible. + * + * Returns: an #AtkRole which is the role of the accessible + **/ +static AtkRole jaw_object_get_role(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return ATK_ROLE_INVALID; + } + + if (atk_obj->role != ATK_ROLE_INVALID && + atk_obj->role != ATK_ROLE_UNKNOWN) { + return atk_obj->role; + } + + JAW_GET_OBJECT(atk_obj, + ATK_ROLE_INVALID); // create local JNI reference `jobject ac` + AtkRole role = jaw_util_get_atk_role_from_AccessibleContext(ac); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + + return role; +} + +/** + * jaw_object_set_role: + * @accessible: an #AtkObject + * @role: an #AtkRole to be set as the role + * + * Sets the role of the accessible. + **/ +static void jaw_object_set_role(AtkObject *atk_obj, AtkRole role) { + JAW_DEBUG("%p, %d", atk_obj, role); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + atk_obj->role = role; +} + +#if !ATK_CHECK_VERSION(2, 38, 0) +static gboolean is_collapsed_java_state(JNIEnv *jniEnv, jobject jobj) { + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return FALSE; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return FALSE; + } + + jobject jstate = (*jniEnv)->GetStaticObjectField( + jniEnv, cachedObjectAccessibleStateClass, cachedObjectCollapsedFieldID); + + // jobj and jstate may be null + if ((*jniEnv)->IsSameObject(jniEnv, jobj, jstate)) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return TRUE; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; +} +#endif + +/** + * atk_object_ref_state_set: + * @accessible: an #AtkObject + * + * Gets a reference to the state set of the accessible; the caller must + * unreference it when it is no longer needed. + * + * Returns: (transfer full): a reference to an #AtkStateSet which is the state + * set of the accessible + **/ +static AtkStateSet *jaw_object_ref_state_set(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + AtkStateSet *state_set = jaw_obj->state_set; + if (state_set == NULL) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + atk_state_set_clear_states(state_set); + + jobject jstate_arr = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetArrayAccessibleStateMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstate_arr == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jsize jarr_size = (*jniEnv)->GetArrayLength(jniEnv, jstate_arr); + jsize i; + for (i = 0; i < jarr_size; i++) { + jobject jstate = + (*jniEnv)->GetObjectArrayElement(jniEnv, jstate_arr, i); +#if !ATK_CHECK_VERSION(2, 38, 0) + if (jstate && is_collapsed_java_state(jniEnv, jstate)) { + (*jniEnv)->DeleteLocalRef(jniEnv, jstate); + continue; + } +#endif + AtkStateType state_type = + jaw_util_get_atk_state_type_from_java_state(jniEnv, jstate); + atk_state_set_add_state(state_set, state_type); + if (state_type == ATK_STATE_ENABLED) { + atk_state_set_add_state(state_set, ATK_STATE_SENSITIVE); + } + (*jniEnv)->DeleteLocalRef(jniEnv, jstate); + } + + g_object_ref(G_OBJECT(state_set)); // because transfer full + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return state_set; +} + +/** + * jaw_object_get_object_locale: + * @accessible: an #AtkObject + * + * Gets a UTF-8 string indicating the POSIX-style LC_MESSAGES locale + * of @accessible. + * + * Returns: a UTF-8 string indicating the POSIX-style LC_MESSAGES + * locale of @accessible. + **/ +static const gchar *jaw_object_get_object_locale(AtkObject *atk_obj) { + JAW_DEBUG("%p", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jstr = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, cachedObjectGetLocaleMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&jaw_obj->mutex); + if (jaw_obj->jstrLocale != NULL) { + if (jaw_obj->locale != NULL) { + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrLocale, + jaw_obj->locale); + jaw_obj->locale = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrLocale); + jaw_obj->jstrLocale = NULL; + } + + if (jstr != NULL) { + jaw_obj->jstrLocale = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (jaw_obj->jstrLocale == NULL) { + g_mutex_unlock(&jaw_obj->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + jaw_obj->locale = + (*jniEnv)->GetStringUTFChars(jniEnv, jaw_obj->jstrLocale, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jaw_obj->locale == NULL) { + jaw_jni_clear_exception(jniEnv); + + // jaw_obj->jstrLocale != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrLocale); + jaw_obj->jstrLocale = NULL; + + g_mutex_unlock(&jaw_obj->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + } + g_mutex_unlock(&jaw_obj->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return jaw_obj->locale; +} + +/** + * jaw_object_ref_relation_set: + * @accessible: an #AtkObject + * + * Gets the #AtkRelationSet associated with the object. + * + * Returns: (transfer full) : an #AtkRelationSet representing the relation set + * of the object. + **/ +static AtkRelationSet *jaw_object_ref_relation_set(AtkObject *atk_obj) { + JAW_DEBUG("%p)", atk_obj); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + if (atk_obj->relation_set != NULL) { + g_object_unref(G_OBJECT(atk_obj->relation_set)); + } + atk_obj->relation_set = atk_relation_set_new(); + + jobject jwrap_key_target_arr = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetArrayAccessibleRelationMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jwrap_key_target_arr == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + + jsize jarr_size = (*jniEnv)->GetArrayLength(jniEnv, jwrap_key_target_arr); + if (!jarr_size) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jsize i; + for (i = 0; i < jarr_size; i++) { + jobject jwrap_key_target = + (*jniEnv)->GetObjectArrayElement(jniEnv, jwrap_key_target_arr, i); + if (!jwrap_key_target) { + continue; + } + jstring jrel_key = (*jniEnv)->GetObjectField(jniEnv, jwrap_key_target, + cachedObjectKeyFieldID); + if (!jrel_key) { + (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target); + continue; + } + AtkRelationType rel_type = + jaw_impl_get_atk_relation_type(jniEnv, jrel_key); + if (!rel_type) { + (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target); + (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key); + continue; + } + jobjectArray jtarget_arr = (*jniEnv)->GetObjectField( + jniEnv, jwrap_key_target, cachedObjectRelationsFieldID); + if (!jtarget_arr) { + (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target); + (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key); + continue; + } + jsize jtarget_size = (*jniEnv)->GetArrayLength(jniEnv, jtarget_arr); + if (!jtarget_size) { + (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target); + (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key); + (*jniEnv)->DeleteLocalRef(jniEnv, jtarget_arr); + continue; + } + + jsize j; + for (j = 0; j < jtarget_size; j++) { + jobject jtarget = + (*jniEnv)->GetObjectArrayElement(jniEnv, jtarget_arr, j); + if (!jtarget) { + continue; + } + JawImpl *target_obj = jaw_impl_find_instance(jniEnv, jtarget); + if (target_obj == NULL) { + g_warning( + "jaw_object_ref_relation_set: target_obj == NULL occurs\n"); + } else { + atk_object_add_relationship(atk_obj, rel_type, + ATK_OBJECT(target_obj)); + } + (*jniEnv)->DeleteLocalRef(jniEnv, jtarget); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target); + (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key); + (*jniEnv)->DeleteLocalRef(jniEnv, jtarget_arr); + } + + if (atk_obj->relation_set == NULL) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + if (atk_obj->relation_set != NULL) { // because transfer full + g_object_ref(G_OBJECT(atk_obj->relation_set)); + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return atk_obj->relation_set; +} + +/** + * jaw_object_ref_child: + * @accessible: an #AtkObject + * @i: a gint representing the position of the child, starting from 0 + * + * Gets a reference to the specified accessible child of the object. + * + * Returns: an #AtkObject representing the specified + * accessible child of the accessible. + **/ +static AtkObject *jaw_object_ref_child(AtkObject *atk_obj, gint i) { + JAW_DEBUG("%p, %d", atk_obj, i); + + if (!atk_obj) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac` + + if (!jaw_object_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject child_ac = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedObjectAtkObjectClass, + cachedObjectGetAccessibleChildMethod, ac, i); + if ((*jniEnv)->ExceptionCheck(jniEnv) || child_ac == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + AtkObject *obj = (AtkObject *)jaw_impl_find_instance(jniEnv, child_ac); + // From the documentation of `ref_child` in AtkObject + // (https://docs.gtk.org/atk/vfunc.Object.ref_child.html): The returned data + // is owned by the instance, so the object is not referenced before being + // returned. Documentation in the repository states nothing about it. In + // fact, `ref_child` is used in `atk_object_ref_accessible_child`, where the + // caller takes ownership of the returned data and is responsible for + // freeing it. `atk_object_ref_accessible_child` does not reference the + // object. Therefore, I assume that `ref_child` should reference the object. + if (obj != NULL) { + g_object_ref(G_OBJECT(obj)); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, ac); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return obj; +} + +static gboolean jaw_object_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkObject"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkObject class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedObjectAtkObjectClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedObjectAtkObjectClass == NULL) { + g_warning("%s: Failed to create global reference for AtkObject class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedObjectGetAccessibleParentMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_accessible_parent", + "(Ljavax/accessibility/AccessibleContext;)Ljavax/accessibility/" + "AccessibleContext;"); + + cachedObjectSetAccessibleParentMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "set_accessible_parent", + "(Ljavax/accessibility/AccessibleContext;Ljavax/accessibility/" + "AccessibleContext;)V"); + + cachedObjectGetAccessibleNameMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_accessible_name", + "(Ljavax/accessibility/AccessibleContext;)Ljava/lang/String;"); + + cachedObjectSetAccessibleNameMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "set_accessible_name", + "(Ljavax/accessibility/AccessibleContext;Ljava/lang/String;)V"); + + cachedObjectGetAccessibleDescriptionMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_accessible_description", + "(Ljavax/accessibility/AccessibleContext;)Ljava/lang/String;"); + + cachedObjectSetAccessibleDescriptionMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "set_accessible_description", + "(Ljavax/accessibility/AccessibleContext;Ljava/lang/String;)V"); + + cachedObjectGetAccessibleChildrenCountMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_accessible_children_count", + "(Ljavax/accessibility/AccessibleContext;)I"); + + cachedObjectGetAccessibleIndexInParentMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_accessible_index_in_parent", + "(Ljavax/accessibility/AccessibleContext;)I"); + + cachedObjectGetArrayAccessibleStateMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_array_accessible_state", + "(Ljavax/accessibility/AccessibleContext;)[Ljavax/accessibility/" + "AccessibleState;"); + + cachedObjectGetLocaleMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_locale", + "(Ljavax/accessibility/AccessibleContext;)Ljava/lang/String;"); + + cachedObjectGetArrayAccessibleRelationMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_array_accessible_relation", + "(Ljavax/accessibility/AccessibleContext;)[Lorg/GNOME/Accessibility/" + "AtkObject$WrapKeyAndTarget;"); + + cachedObjectGetAccessibleChildMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedObjectAtkObjectClass, "get_accessible_child", + "(Ljavax/accessibility/AccessibleContext;I)Ljavax/accessibility/" + "AccessibleContext;"); + + jclass localAccessibleState = + (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleState"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleState == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AccessibleState class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedObjectAccessibleStateClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleState); + (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleState); + + if (cachedObjectAccessibleStateClass == NULL) { + g_warning( + "%s: Failed to create global reference for AccessibleState class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedObjectCollapsedFieldID = (*jniEnv)->GetStaticFieldID( + jniEnv, cachedObjectAccessibleStateClass, "COLLAPSED", + "Ljavax/accessibility/AccessibleState;"); + + jclass localWrapKeyAndTarget = (*jniEnv)->FindClass( + jniEnv, "org/GNOME/Accessibility/AtkObject$WrapKeyAndTarget"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localWrapKeyAndTarget == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find WrapKeyAndTarget class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedObjectWrapKeyAndTargetClass = + (*jniEnv)->NewGlobalRef(jniEnv, localWrapKeyAndTarget); + (*jniEnv)->DeleteLocalRef(jniEnv, localWrapKeyAndTarget); + + if (cachedObjectWrapKeyAndTargetClass == NULL) { + g_warning("%s: Failed to create global reference for WrapKeyAndTarget " + "class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedObjectRelationsFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedObjectWrapKeyAndTargetClass, "relations", + "[Ljavax/accessibility/AccessibleContext;"); + + cachedObjectKeyFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedObjectWrapKeyAndTargetClass, "key", "Ljava/lang/String;"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedObjectGetAccessibleParentMethod == NULL || + cachedObjectSetAccessibleParentMethod == NULL || + cachedObjectGetAccessibleNameMethod == NULL || + cachedObjectSetAccessibleNameMethod == NULL || + cachedObjectGetAccessibleDescriptionMethod == NULL || + cachedObjectSetAccessibleDescriptionMethod == NULL || + cachedObjectGetAccessibleChildrenCountMethod == NULL || + cachedObjectGetAccessibleIndexInParentMethod == NULL || + cachedObjectGetArrayAccessibleStateMethod == NULL || + cachedObjectGetLocaleMethod == NULL || + cachedObjectGetArrayAccessibleRelationMethod == NULL || + cachedObjectGetAccessibleChildMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkObject method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedObjectAtkObjectClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAtkObjectClass); + cachedObjectAtkObjectClass = NULL; + } + cachedObjectGetAccessibleParentMethod = NULL; + cachedObjectSetAccessibleParentMethod = NULL; + cachedObjectGetAccessibleNameMethod = NULL; + cachedObjectSetAccessibleNameMethod = NULL; + cachedObjectGetAccessibleDescriptionMethod = NULL; + cachedObjectSetAccessibleDescriptionMethod = NULL; + cachedObjectGetAccessibleChildrenCountMethod = NULL; + cachedObjectGetAccessibleIndexInParentMethod = NULL; + cachedObjectGetArrayAccessibleStateMethod = NULL; + cachedObjectGetLocaleMethod = NULL; + cachedObjectGetArrayAccessibleRelationMethod = NULL; + cachedObjectGetAccessibleChildMethod = NULL; + if (cachedObjectAccessibleStateClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAccessibleStateClass); + cachedObjectAccessibleStateClass = NULL; + } + cachedObjectCollapsedFieldID = NULL; + if (cachedObjectWrapKeyAndTargetClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectWrapKeyAndTargetClass); + cachedObjectWrapKeyAndTargetClass = NULL; + } + cachedObjectRelationsFieldID = NULL; + cachedObjectKeyFieldID = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_object_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedObjectAtkObjectClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAtkObjectClass); + cachedObjectAtkObjectClass = NULL; + } + cachedObjectGetAccessibleParentMethod = NULL; + cachedObjectSetAccessibleParentMethod = NULL; + cachedObjectGetAccessibleNameMethod = NULL; + cachedObjectSetAccessibleNameMethod = NULL; + cachedObjectGetAccessibleDescriptionMethod = NULL; + cachedObjectSetAccessibleDescriptionMethod = NULL; + cachedObjectGetAccessibleChildrenCountMethod = NULL; + cachedObjectGetAccessibleIndexInParentMethod = NULL; + cachedObjectGetArrayAccessibleStateMethod = NULL; + cachedObjectGetLocaleMethod = NULL; + cachedObjectGetArrayAccessibleRelationMethod = NULL; + cachedObjectGetAccessibleChildMethod = NULL; + if (cachedObjectAccessibleStateClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAccessibleStateClass); + cachedObjectAccessibleStateClass = NULL; + } + cachedObjectCollapsedFieldID = NULL; + if (cachedObjectWrapKeyAndTargetClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectWrapKeyAndTargetClass); + cachedObjectWrapKeyAndTargetClass = NULL; + } + cachedObjectRelationsFieldID = NULL; + cachedObjectKeyFieldID = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.h new file mode 100644 index 000000000000..70934bf146bb --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.h @@ -0,0 +1,72 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _JAW_OBJECT_H_ +#define _JAW_OBJECT_H_ + +#include +#include + +G_BEGIN_DECLS + +#define JAW_TYPE_OBJECT (jaw_object_get_type()) +#define JAW_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_OBJECT, JawObject)) +#define JAW_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_OBJECT, JawObjectClass)) +#define JAW_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_OBJECT)) +#define JAW_IS_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_OBJECT)) +#define JAW_OBJECT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_OBJECT, JawObjectClass)) + +typedef struct _JawObject JawObject; +typedef struct _JawObjectClass JawObjectClass; + +/** + * JawObject: + * A base structure wrapping an AtkObject. + **/ +struct _JawObject { + AtkObject parent; + + jobject acc_context; + jstring jstrName; + jstring jstrDescription; + jstring jstrLocale; + const gchar *locale; + AtkStateSet *state_set; + + GHashTable *storedData; + GMutex mutex; +}; + +GType jaw_object_get_type(void); + +struct _JawObjectClass { + AtkObjectClass parent_class; + + gpointer (*get_interface_data)(JawObject *, guint); +}; + +gpointer jaw_object_get_interface_data(JawObject *, guint); + +G_END_DECLS + +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawselection.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawselection.c new file mode 100644 index 000000000000..b78a9d3b53a9 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawselection.c @@ -0,0 +1,583 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkSelection: + * + * The ATK interface implemented by container objects whose #AtkObject children + * can be selected. + * + * #AtkSelection should be implemented by UI components with children + * which are exposed by #atk_object_ref_child and + * #atk_object_get_n_children, if the use of the parent UI component + * ordinarily involves selection of one or more of the objects + * corresponding to those #AtkObject children - for example, + * selectable lists. + * + * Note that other types of "selection" (for instance text selection) + * are accomplished a other ATK interfaces - #AtkSelection is limited + * to the selection/deselection of children. + */ + +static jclass cachedSelectionAtkSelectionClass = NULL; +static jmethodID cachedSelectionCreateAtkSelectionMethod = NULL; +static jmethodID cachedSelectionAddSelectionMethod = NULL; +static jmethodID cachedSelectionClearSelectionMethod = NULL; +static jmethodID cachedSelectionRefSelectionMethod = NULL; +static jmethodID cachedSelectionGetSelectionCountMethod = NULL; +static jmethodID cachedSelectionIsChildSelectedMethod = NULL; +static jmethodID cachedSelectionRemoveSelectionMethod = NULL; +static jmethodID cachedSelectionSelectAllSelectionMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_selection_init_jni_cache(JNIEnv *jniEnv); + +static gboolean jaw_selection_add_selection(AtkSelection *selection, gint i); +static gboolean jaw_selection_clear_selection(AtkSelection *selection); +static AtkObject *jaw_selection_ref_selection(AtkSelection *selection, gint i); +static gint jaw_selection_get_selection_count(AtkSelection *selection); +static gboolean jaw_selection_is_child_selected(AtkSelection *selection, + gint i); +static gboolean jaw_selection_remove_selection(AtkSelection *selection, gint i); +static gboolean jaw_selection_select_all_selection(AtkSelection *selection); + +typedef struct _SelectionData { + jobject atk_selection; +} SelectionData; + +#define JAW_GET_SELECTION(selection, def_ret) \ + JAW_GET_OBJ_IFACE( \ + selection, org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION, \ + SelectionData, atk_selection, jniEnv, atk_selection, def_ret) + +/** + * AtkSelectionIface: + * @add_selection: + * @clear_selection: + * @ref_selection: + * @get_selection_count: + * @is_child_selected: + * @remove_selection: + * @select_all_selection: + **/ +void jaw_selection_interface_init(AtkSelectionIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + iface->add_selection = jaw_selection_add_selection; + iface->clear_selection = jaw_selection_clear_selection; + iface->ref_selection = jaw_selection_ref_selection; + iface->get_selection_count = jaw_selection_get_selection_count; + iface->is_child_selected = jaw_selection_is_child_selected; + iface->remove_selection = jaw_selection_remove_selection; + iface->select_all_selection = jaw_selection_select_all_selection; +} + +gpointer jaw_selection_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_selection_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_selection = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedSelectionAtkSelectionClass, + cachedSelectionCreateAtkSelectionMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_selection == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jatk_selection using " + "create_atk_selection method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + SelectionData *data = g_new0(SelectionData, 1); + data->atk_selection = (*jniEnv)->NewGlobalRef(jniEnv, jatk_selection); + if (data->atk_selection == NULL) { + g_warning("%s: Failed to create global ref for atk_selection", + G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_selection_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning( + "Null argument passed to function jaw_selection_data_finalize"); + return; + } + + SelectionData *data = (SelectionData *)p; + if (data == NULL) { + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->atk_selection != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_selection); + data->atk_selection = NULL; + } + } + + g_free(data); +} + +/** + * jaw_selection_add_selection: + * @selection: a #GObject instance that implements AtkSelectionIface + * @i: a #gint specifying the child index. + * + * Adds the specified accessible child of the object to the + * object's selection. + * + * Returns: TRUE if success, FALSE otherwise. + **/ +static gboolean jaw_selection_add_selection(AtkSelection *selection, gint i) { + JAW_DEBUG("%p, %d", selection, i); + + if (selection == NULL) { + g_warning( + "Null argument passed to function jaw_selection_add_selection"); + return FALSE; + } + + JAW_GET_SELECTION( + selection, + FALSE); // create local JNI reference `jobject atk_selection` + + jboolean jbool = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_selection, cachedSelectionAddSelectionMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + + return jbool; +} + +/** + * jaw_selection_clear_selection: + * @selection: a #GObject instance that implements AtkSelectionIface + * + * Clears the selection in the object so that no children in the object + * are selected. + * + * Returns: TRUE if success, FALSE otherwise. + **/ +static gboolean jaw_selection_clear_selection(AtkSelection *selection) { + JAW_DEBUG("%p", selection); + + if (selection == NULL) { + g_warning( + "Null argument passed to function jaw_selection_clear_selection"); + return FALSE; + } + + JAW_GET_SELECTION( + selection, + FALSE); // create local JNI reference `jobject atk_selection` + + jboolean jbool = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_selection, cachedSelectionClearSelectionMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + + return jbool; +} + +/** + * jaw_selection_ref_selection: + * @selection: a #GObject instance that implements AtkSelectionIface + * @i: a #gint specifying the index in the selection set. (e.g. the + * ith selection as opposed to the ith child). + * + * Gets a reference to the accessible object representing the specified + * selected child of the object. + * + * Returns: (nullable) (transfer full): an #AtkObject representing the + * selected accessible, or %NULL if @selection does not implement this + * interface. + **/ +static AtkObject *jaw_selection_ref_selection(AtkSelection *selection, gint i) { + JAW_DEBUG("%p, %d", selection, i); + + if (selection == NULL) { + g_warning( + "Null argument passed to function jaw_selection_ref_selection"); + return NULL; + } + + JAW_GET_SELECTION( + selection, NULL); // create local JNI reference `jobject atk_selection` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject child_ac = (*jniEnv)->CallObjectMethod( + jniEnv, atk_selection, cachedSelectionRefSelectionMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv) || child_ac == NULL) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + AtkObject *obj = (AtkObject *)jaw_impl_find_instance(jniEnv, child_ac); + + // From the documentation of the `ref_selection`: + // "The caller of the method takes ownership of the returned data, and is + // responsible for freeing it." (transfer full) + if (obj != NULL) { + g_object_ref(G_OBJECT(obj)); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return obj; +} + +/** + * jaw_selection_get_selection_count: + * @selection: a #GObject instance that implements AtkSelectionIface + * + * Gets the number of accessible children currently selected. + * + * Returns: a gint representing the number of items selected, or 0 + * if @selection does not implement this interface. + **/ +static gint jaw_selection_get_selection_count(AtkSelection *selection) { + JAW_DEBUG("%p", selection); + + if (selection == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_SELECTION(selection, + 0); // create local JNI reference `jobject atk_selection` + + jint jcount = (*jniEnv)->CallIntMethod( + jniEnv, atk_selection, cachedSelectionGetSelectionCountMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + + return (gint)jcount; +} + +/** + * jaw_selection_is_child_selected: + * @selection: a #GObject instance that implements AtkSelectionIface + * @i: a #gint specifying the child index. + * + * Determines if the current child of this object is selected + * + * Returns: a gboolean representing the specified child is selected, or 0 + * if @selection does not implement this interface. + **/ +static gboolean jaw_selection_is_child_selected(AtkSelection *selection, + gint i) { + JAW_DEBUG("%p, %d", selection, i); + + if (selection == NULL) { + g_warning( + "Null argument passed to function jaw_selection_is_child_selected"); + return FALSE; + } + + JAW_GET_SELECTION( + selection, + FALSE); // create local JNI reference `jobject atk_selection` + + jboolean jbool = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_selection, cachedSelectionIsChildSelectedMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + + return jbool; +} + +/** + * jaw_selection_remove_selection: + * @selection: a #GObject instance that implements AtkSelectionIface + * @i: a #gint specifying the index in the selection set. (e.g. the + * ith selection as opposed to the ith child). + * + * Removes the specified child of the object from the object's selection. + * + * Returns: TRUE if success, FALSE otherwise. + **/ +static gboolean jaw_selection_remove_selection(AtkSelection *selection, + gint i) { + JAW_DEBUG("%p, %d", selection, i); + + if (selection == NULL) { + g_warning( + "Null argument passed to function jaw_selection_remove_selection"); + return FALSE; + } + + JAW_GET_SELECTION( + selection, + FALSE); // create local JNI reference `jobject atk_selection` + + jboolean jbool = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_selection, cachedSelectionRemoveSelectionMethod, (jint)i); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + + return jbool; +} + +/** + * jaw_selection_select_all_selection: + * @selection: a #GObject instance that implements AtkSelectionIface + * + * Causes every child of the object to be selected if the object + * supports multiple selections. + * + * Returns: TRUE if success, FALSE otherwise. + **/ +static gboolean jaw_selection_select_all_selection(AtkSelection *selection) { + JAW_DEBUG("%p", selection); + + if (selection == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_SELECTION( + selection, + FALSE); // create local JNI reference `jobject atk_selection` + + jboolean jbool = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_selection, cachedSelectionSelectAllSelectionMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection); + + return jbool; +} + +static gboolean jaw_selection_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkSelection"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkSelection class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedSelectionAtkSelectionClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedSelectionAtkSelectionClass == NULL) { + g_warning( + "%s: Failed to create global reference for AtkSelection class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedSelectionCreateAtkSelectionMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "create_atk_selection", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkSelection;"); + + cachedSelectionAddSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "add_selection", "(I)Z"); + + cachedSelectionClearSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "clear_selection", "()Z"); + + cachedSelectionRefSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "ref_selection", + "(I)Ljavax/accessibility/AccessibleContext;"); + + cachedSelectionGetSelectionCountMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "get_selection_count", "()I"); + + cachedSelectionIsChildSelectedMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "is_child_selected", "(I)Z"); + + cachedSelectionRemoveSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedSelectionAtkSelectionClass, "remove_selection", "(I)Z"); + + cachedSelectionSelectAllSelectionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedSelectionAtkSelectionClass, + "select_all_selection", "()Z"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedSelectionCreateAtkSelectionMethod == NULL || + cachedSelectionAddSelectionMethod == NULL || + cachedSelectionClearSelectionMethod == NULL || + cachedSelectionRefSelectionMethod == NULL || + cachedSelectionGetSelectionCountMethod == NULL || + cachedSelectionIsChildSelectedMethod == NULL || + cachedSelectionRemoveSelectionMethod == NULL || + cachedSelectionSelectAllSelectionMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkSelection method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedSelectionAtkSelectionClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedSelectionAtkSelectionClass); + cachedSelectionAtkSelectionClass = NULL; + } + cachedSelectionCreateAtkSelectionMethod = NULL; + cachedSelectionAddSelectionMethod = NULL; + cachedSelectionClearSelectionMethod = NULL; + cachedSelectionRefSelectionMethod = NULL; + cachedSelectionGetSelectionCountMethod = NULL; + cachedSelectionIsChildSelectedMethod = NULL; + cachedSelectionRemoveSelectionMethod = NULL; + cachedSelectionSelectAllSelectionMethod = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_selection_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedSelectionAtkSelectionClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedSelectionAtkSelectionClass); + cachedSelectionAtkSelectionClass = NULL; + } + cachedSelectionCreateAtkSelectionMethod = NULL; + cachedSelectionAddSelectionMethod = NULL; + cachedSelectionClearSelectionMethod = NULL; + cachedSelectionRefSelectionMethod = NULL; + cachedSelectionGetSelectionCountMethod = NULL; + cachedSelectionIsChildSelectedMethod = NULL; + cachedSelectionRemoveSelectionMethod = NULL; + cachedSelectionSelectAllSelectionMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtable.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtable.c new file mode 100644 index 000000000000..16258d045795 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtable.c @@ -0,0 +1,1692 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkTable: + * + * The ATK interface implemented for UI components which contain tabular or + * row/column information. + * + * #AtkTable should be implemented by components which present + * elements ordered via rows and columns. It may also be used to + * present tree-structured information if the nodes of the trees can + * be said to contain multiple "columns". Individual elements of an + * #AtkTable are typically referred to as "cells". Those cells should + * implement the interface #AtkTableCell, but #Atk doesn't require + * them to be direct children of the current #AtkTable. They can be + * grand-children, grand-grand-children etc. #AtkTable provides the + * API needed to get a individual cell based on the row and column + * numbers. + * + * Children of #AtkTable are frequently "lightweight" objects, that + * is, they may not have backing widgets in the host UI toolkit. They + * are therefore often transient. + * + * Since tables are often very complex, #AtkTable includes provision + * for offering simplified summary information, as well as row and + * column headers and captions. Headers and captions are #AtkObjects + * which may implement other interfaces (#AtkText, #AtkImage, etc.) as + * appropriate. #AtkTable summaries may themselves be (simplified) + * #AtkTables, etc. + * + * Note for implementors: in the past, #AtkTable required that all the + * cells should be direct children of #AtkTable, and provided some + * index based methods to request the cells. The practice showed that + * that forcing made #AtkTable implementation complex, and hard to + * expose other kind of children, like rows or captions. Right now, + * index-based methods are deprecated. + */ + +static jclass cachedTableAtkTableClass = NULL; +static jmethodID cachedTableCreateAtkTableMethod = NULL; +static jmethodID cachedTableRefAtMethod = NULL; +static jmethodID cachedTableGetIndexAtMethod = NULL; +static jmethodID cachedTableGetColumnAtIndexMethod = NULL; +static jmethodID cachedTableGetRowAtIndexMethod = NULL; +static jmethodID cachedTableGetNColumnsMethod = NULL; +static jmethodID cachedTableGetNRowsMethod = NULL; +static jmethodID cachedTableGetColumnExtentAtMethod = NULL; +static jmethodID cachedTableGetRowExtentAtMethod = NULL; +static jmethodID cachedTableGetCaptionMethod = NULL; +static jmethodID cachedTableGetColumnDescriptionMethod = NULL; +static jmethodID cachedTableGetRowDescriptionMethod = NULL; +static jmethodID cachedTableGetColumnHeaderMethod = NULL; +static jmethodID cachedTableGetRowHeaderMethod = NULL; +static jmethodID cachedTableGetSummaryMethod = NULL; +static jmethodID cachedTableGetSelectedColumnsMethod = NULL; +static jmethodID cachedTableGetSelectedRowsMethod = NULL; +static jmethodID cachedTableIsColumnSelectedMethod = NULL; +static jmethodID cachedTableIsRowSelectedMethod = NULL; +static jmethodID cachedTableIsSelectedMethod = NULL; +static jmethodID cachedTableSetRowDescriptionMethod = NULL; +static jmethodID cachedTableSetColumnDescriptionMethod = NULL; +static jmethodID cachedTableSetCaptionMethod = NULL; +static jmethodID cachedTableSetSummaryMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_table_init_jni_cache(JNIEnv *jniEnv); + +static AtkObject *jaw_table_ref_at(AtkTable *table, gint row, gint column); +static gint jaw_table_get_index_at(AtkTable *table, gint row, gint column); +static gint jaw_table_get_column_at_index(AtkTable *table, gint index); +static gint jaw_table_get_row_at_index(AtkTable *table, gint index); +static gint jaw_table_get_n_columns(AtkTable *table); +static gint jaw_table_get_n_rows(AtkTable *table); +static gint jaw_table_get_column_extent_at(AtkTable *table, gint row, + gint column); +static gint jaw_table_get_row_extent_at(AtkTable *table, gint row, gint column); +static AtkObject *jaw_table_get_caption(AtkTable *table); +static const gchar *jaw_table_get_column_description(AtkTable *table, + gint column); +static const gchar *jaw_table_get_row_description(AtkTable *table, gint row); +static AtkObject *jaw_table_get_column_header(AtkTable *table, gint column); +static AtkObject *jaw_table_get_row_header(AtkTable *table, gint row); +static AtkObject *jaw_table_get_summary(AtkTable *table); +static gint jaw_table_get_selected_columns(AtkTable *table, gint **selected); +static gint jaw_table_get_selected_rows(AtkTable *table, gint **selected); +static gboolean jaw_table_is_column_selected(AtkTable *table, gint column); +static gboolean jaw_table_is_row_selected(AtkTable *table, gint row); +static gboolean jaw_table_is_selected(AtkTable *table, gint row, gint column); + +static void jaw_table_set_row_description(AtkTable *table, gint row, + const gchar *description); +static void jaw_table_set_column_description(AtkTable *table, gint column, + const gchar *description); + +static void jaw_table_set_caption(AtkTable *table, AtkObject *caption); +static void jaw_table_set_summary(AtkTable *table, AtkObject *summary); + +typedef struct _TableData { + jobject atk_table; + const gchar *row_or_column_description; + jstring jstrRowOrColumnDescription; + GMutex mutex; +} TableData; + +#define JAW_GET_TABLE(table, def_ret) \ + JAW_GET_OBJ_IFACE(table, \ + org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE, \ + TableData, atk_table, jniEnv, atk_table, def_ret) + +void jaw_table_interface_init(AtkTableIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + iface->ref_at = jaw_table_ref_at; + iface->get_index_at = jaw_table_get_index_at; + iface->get_column_at_index = jaw_table_get_column_at_index; + iface->get_row_at_index = jaw_table_get_row_at_index; + iface->get_n_columns = jaw_table_get_n_columns; + iface->get_n_rows = jaw_table_get_n_rows; + iface->get_column_extent_at = jaw_table_get_column_extent_at; + iface->get_row_extent_at = jaw_table_get_row_extent_at; + iface->get_caption = jaw_table_get_caption; + iface->get_column_description = jaw_table_get_column_description; + iface->get_column_header = jaw_table_get_column_header; + iface->get_row_description = jaw_table_get_row_description; + iface->get_row_header = jaw_table_get_row_header; + iface->get_summary = jaw_table_get_summary; + iface->set_caption = jaw_table_set_caption; + iface->set_column_description = jaw_table_set_column_description; + iface->set_column_header = + NULL; // impossible to do a on AccessibleTable Java Object + iface->set_row_description = jaw_table_set_row_description; + iface->set_row_header = + NULL; // impossible to do a on AccessibleTable Java Object + iface->set_summary = jaw_table_set_summary; + iface->get_selected_columns = jaw_table_get_selected_columns; + iface->get_selected_rows = jaw_table_get_selected_rows; + iface->is_column_selected = jaw_table_is_column_selected; + iface->is_row_selected = jaw_table_is_row_selected; + iface->is_selected = jaw_table_is_selected; + iface->add_row_selection = NULL; + iface->remove_row_selection = NULL; + iface->add_column_selection = + NULL; // impossible to do a on AccessibleTable Java Object + iface->remove_column_selection = + NULL; // impossible to do a on AccessibleTable Java Object +} + +gpointer jaw_table_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument ac passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_table_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_table = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedTableAtkTableClass, cachedTableCreateAtkTableMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_table == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jatk_table using create_atk_table method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + TableData *data = g_new0(TableData, 1); + g_mutex_init(&data->mutex); + data->atk_table = (*jniEnv)->NewGlobalRef(jniEnv, jatk_table); + if (data->atk_table == NULL) { + g_warning("%s: Failed to create global ref for atk_table", G_STRFUNC); + g_mutex_clear(&data->mutex); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_table_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning("%s: Null argument p passed to the function", G_STRFUNC); + return; + } + + TableData *data = (TableData *)p; + if (data == NULL) { + g_warning("%s: data is NULL in finalize", G_STRFUNC); + return; + } + + g_mutex_lock(&data->mutex); + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->jstrRowOrColumnDescription != NULL) { + if (data->row_or_column_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars( + jniEnv, data->jstrRowOrColumnDescription, + data->row_or_column_description); + data->row_or_column_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription); + data->jstrRowOrColumnDescription = NULL; + } + if (data->atk_table != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_table); + data->atk_table = NULL; + } + } + + g_mutex_unlock(&data->mutex); + g_mutex_clear(&data->mutex); + g_free(data); +} + +/** + * jaw_table_ref_at: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * @column: a #gint representing a column in @table + * + * Get a reference to the table cell at @row, @column. This cell + * should implement the interface #AtkTableCell + * + * Returns: (transfer full): an #AtkObject representing the referred + * to accessible + **/ +static AtkObject *jaw_table_ref_at(AtkTable *table, gint row, gint column) { + JAW_DEBUG("%p, %d, %d", table, row, column); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return NULL; + } + + JawObject *jaw_obj = JAW_OBJECT(table); + if (jaw_obj == NULL) { + g_warning("%s: jaw_obj is NULL", G_STRFUNC); + return NULL; + } + TableData *data = jaw_object_get_interface_data( + jaw_obj, org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE); + if (data == NULL) { + g_warning("%s: data is NULL", G_STRFUNC); + return NULL; + } + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + jobject atk_table = (*jniEnv)->NewGlobalRef(jniEnv, data->atk_table); + if (atk_table == NULL) { + g_warning("%s: atk_table is NULL", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jac = (*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableRefAtMethod, (jint)row, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jac using ref_at method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + + // From the documentation of the `ref_at`: + // "The caller of the method takes ownership of the returned data, and is + // responsible for freeing it." (transfer full) + if (jaw_impl != NULL) { + g_object_ref(G_OBJECT(jaw_impl)); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * jaw_table_get_index_at: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * @column: a #gint representing a column in @table + * + * Gets a #gint representing the index at the specified @row and + * @column. + * + * Deprecated in atk: Since 2.12. Use atk_table_ref_at() in order to get the + * accessible that represents the cell at (@row, @column) + * + * Returns: a #gint representing the index at specified position. + * The value -1 is returned if the object at row,column is not a child + * of table or table does not implement this interface. + **/ +static gint jaw_table_get_index_at(AtkTable *table, gint row, gint column) { + JAW_DEBUG("%p, %d, %d", table, row, column); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TABLE(table, -1); // create local JNI reference `jobject atk_table` + + jint jindex = + (*jniEnv)->CallIntMethod(jniEnv, atk_table, cachedTableGetIndexAtMethod, + (jint)row, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_index_at method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jindex; +} + +/** + * jaw_table_get_column_at_index: + * @table: a GObject instance that implements AtkTableInterface + * @index_: a #gint representing an index in @table + * + * Gets a #gint representing the column at the specified @index_. + * + * Deprecated in atk: Since 2.12. + * + * Returns: a gint representing the column at the specified index, + * or -1 if the table does not implement this method. + **/ +static gint jaw_table_get_column_at_index(AtkTable *table, gint index) { + JAW_DEBUG("%p, %d", table, index); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table` + + jint jcolumn = (*jniEnv)->CallIntMethod( + jniEnv, atk_table, cachedTableGetColumnAtIndexMethod, (jint)index); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_column_at_index method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jcolumn; +} + +/** + * atk_table_get_row_at_index: + * @table: a GObject instance that implements AtkTableInterface + * @index_: a #gint representing an index in @table + * + * Gets a #gint representing the row at the specified @index_. + * + * Deprecated in atk: since 2.12. + * + * Returns: a gint representing the row at the specified index, + * or -1 if the table does not implement this method. + **/ +static gint jaw_table_get_row_at_index(AtkTable *table, gint index) { + JAW_DEBUG("%p, %d", table, index); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TABLE(table, -1); // create local JNI reference `jobject atk_table` + + jint jrow = (*jniEnv)->CallIntMethod( + jniEnv, atk_table, cachedTableGetRowAtIndexMethod, (jint)index); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_row_at_index method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jrow; +} + +/** + * atk_table_get_n_columns: + * @table: a GObject instance that implements AtkTableIface + * + * Gets the number of columns in the table. + * + * Returns: a gint representing the number of columns, or 0 + * if value does not implement this interface. + **/ +static gint jaw_table_get_n_columns(AtkTable *table) { + JAW_DEBUG("%p", table); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table` + + jint jcolumns = (*jniEnv)->CallIntMethod(jniEnv, atk_table, + cachedTableGetNColumnsMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_n_columns method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jcolumns; +} + +/** + * jaw_table_get_n_rows: + * @table: a GObject instance that implements AtkTableIface + * + * Gets the number of rows in the table. + * + * Returns: a gint representing the number of rows, or 0 + * if value does not implement this interface. + **/ +static gint jaw_table_get_n_rows(AtkTable *table) { + JAW_DEBUG("%p", table); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table` + + jint jrows = + (*jniEnv)->CallIntMethod(jniEnv, atk_table, cachedTableGetNRowsMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_n_rows method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jrows; +} + +/** + * jaw_table_get_column_extent_at: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * @column: a #gint representing a column in @table + * + * Gets the number of columns occupied by the accessible object + * at the specified @row and @column in the @table. + * + * Returns: a gint representing the column extent at specified position, or 0 + * if value does not implement this interface. + **/ +static gint jaw_table_get_column_extent_at(AtkTable *table, gint row, + gint column) { + JAW_DEBUG("%p, %d, %d", table, row, column); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table` + + jint jextent = (*jniEnv)->CallIntMethod(jniEnv, atk_table, + cachedTableGetColumnExtentAtMethod, + (jint)row, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_column_extent_at method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jextent; +} + +/** + * jaw_table_get_row_extent_at: + * @table: a GObject instance that implements AtkTableIface + * @column: a #gint representing a column in @table + * + * Gets the description text of the specified @column in the table + * + * Returns: a gchar* representing the column description, or %NULL + * if value does not implement this interface. + **/ +static gint jaw_table_get_row_extent_at(AtkTable *table, gint row, + gint column) { + JAW_DEBUG("%p, %d, %d", table, row, column); + + if (table == NULL) { + g_warning("Null argument table passed to function " + "jaw_table_get_row_extent_at"); + return 0; + } + + JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table` + + jint jextent = (*jniEnv)->CallIntMethod(jniEnv, atk_table, + cachedTableGetRowExtentAtMethod, + (jint)row, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_row_extent_at method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jextent; +} + +/** + * jaw_table_get_caption: + * @table: a GObject instance that implements AtkTableInterface + * + * Gets the caption for the @table. + * + * Returns: (nullable) (transfer none): a AtkObject* representing the + * table caption, or %NULL if value does not implement this interface. + **/ +static AtkObject *jaw_table_get_caption(AtkTable *table) { + JAW_DEBUG("%p", table); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLE(table, + NULL); // create local JNI reference `jobject atk_table` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jac = (*jniEnv)->CallObjectMethod(jniEnv, atk_table, + cachedTableGetCaptionMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_caption method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + + // From documentation of the `get_caption` in AtkObject: + // The returned data is owned by the instance (transfer none), so we don't + // ref the obj before returning it. + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * jaw_table_get_column_description: + * @table: a GObject instance that implements AtkTableIface + * @column: a #gint representing a column in @table + * @description: a #gchar representing the description text + * to set for the specified @column of the @table + * + * Sets the description text for the specified @column of the @table. + **/ +static const gchar *jaw_table_get_column_description(AtkTable *table, + gint column) { + JAW_DEBUG("%p, %d", table, column); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLE(table, + NULL); // create local JNI reference `jobject atk_table` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableGetColumnDescriptionMethod, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_column_description method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&data->mutex); + if (data->jstrRowOrColumnDescription != NULL) { + if (data->row_or_column_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars( + jniEnv, data->jstrRowOrColumnDescription, + data->row_or_column_description); + data->row_or_column_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription); + data->jstrRowOrColumnDescription = NULL; + } + + data->jstrRowOrColumnDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (data->jstrRowOrColumnDescription == NULL) { + g_mutex_unlock(&data->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + data->row_or_column_description = (*jniEnv)->GetStringUTFChars( + jniEnv, data->jstrRowOrColumnDescription, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || + data->row_or_column_description == NULL) { + jaw_jni_clear_exception(jniEnv); + + // data->jstrRowOrColumnDescription != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription); + data->jstrRowOrColumnDescription = NULL; + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + g_mutex_unlock(&data->mutex); + return NULL; + } + + g_mutex_unlock(&data->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data->row_or_column_description; +} + +/** + * jaw_table_get_row_description: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * + * Gets the description text of the specified row in the table + * + * Returns: (nullable): a gchar* representing the row description, or + * %NULL if value does not implement this interface. + **/ +static const gchar *jaw_table_get_row_description(AtkTable *table, gint row) { + JAW_DEBUG("%p, %d", table, row); + + if (table == NULL) { + g_warning("Null argument passed table to function " + "jaw_table_get_row_description"); + return NULL; + } + + JAW_GET_TABLE(table, + NULL); // create local JNI reference `jobject atk_table` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = (*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableGetRowDescriptionMethod, (jint)row); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_row_description method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + g_mutex_lock(&data->mutex); + if (data->jstrRowOrColumnDescription != NULL) { + if (data->row_or_column_description != NULL) { + (*jniEnv)->ReleaseStringUTFChars( + jniEnv, data->jstrRowOrColumnDescription, + data->row_or_column_description); + data->row_or_column_description = NULL; + } + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription); + data->jstrRowOrColumnDescription = NULL; + } + + data->jstrRowOrColumnDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr); + if (data->jstrRowOrColumnDescription == NULL) { + g_mutex_unlock(&data->mutex); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + data->row_or_column_description = (*jniEnv)->GetStringUTFChars( + jniEnv, data->jstrRowOrColumnDescription, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || + data->row_or_column_description == NULL) { + jaw_jni_clear_exception(jniEnv); + + // data->jstrRowOrColumnDescription != NULL + (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription); + data->jstrRowOrColumnDescription = NULL; + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + g_mutex_unlock(&data->mutex); + return NULL; + } + + g_mutex_unlock(&data->mutex); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data->row_or_column_description; +} + +/** + * jaw_table_get_column_header: + * @table: a GObject instance that implements AtkTableIface + * @column: a #gint representing a column in the table + * + * Gets the column header of a specified column in an accessible table. + * + * Returns: (nullable) (transfer none): a AtkObject* representing the + * specified column header, or %NULL if value does not implement this + * interface. + **/ +static AtkObject *jaw_table_get_column_header(AtkTable *table, gint column) { + JAW_DEBUG("%p, %d", table, column); + + if (table == NULL) { + g_warning("Null argument table passed to function " + "jaw_table_get_column_header"); + return NULL; + } + + JAW_GET_TABLE(table, + NULL); // create local JNI reference `jobject atk_table` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jac = (*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableGetColumnHeaderMethod, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_column_header method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + // From documentation of the `atk_table_get_column_header` in AtkObject: + // The returned data is owned by the instance (transfer none), so we don't + // ref the obj before returning it. + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * jaw_table_get_row_header: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in the table + * + * Gets the row header of a specified row in an accessible table. + * + * Returns: (nullable) (transfer none): a AtkObject* representing the + * specified row header, or %NULL if value does not implement this + * interface. + **/ +static AtkObject *jaw_table_get_row_header(AtkTable *table, gint row) { + JAW_DEBUG("%p, %d", table, row); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLE(table, + NULL); // create local JNI reference `jobject atk_table` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jac = (*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableGetRowHeaderMethod, (jint)row); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_row_header method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + // From documentation of the `atk_table_get_row_header` in AtkObject: + // The returned data is owned by the instance (transfer none), so we don't + // ref the jaw_impl before returning it. + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * jaw_table_get_summary: + * @table: a GObject instance that implements AtkTableIface + * + * Gets the summary description of the table. + * + * Returns: (transfer full): a AtkObject* representing a summary description + * of the table, or zero if value does not implement this interface. + **/ +static AtkObject *jaw_table_get_summary(AtkTable *table) { + JAW_DEBUG("%p", table); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLE(table, + NULL); // create local JNI reference `jobject atk_table` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jac = (*jniEnv)->CallObjectMethod(jniEnv, atk_table, + cachedTableGetSummaryMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_summary method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + // From the documentation of the `get_summary`: + // "The caller of the method takes ownership of the returned data, and is + // responsible for freeing it." (transfer full) + if (jaw_impl != NULL) { + g_object_ref(G_OBJECT(jaw_impl)); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * jaw_table_get_selected_columns: + * @table: a GObject instance that implements AtkTableIface + * @selected: a #gint** that is to contain the selected columns numbers + * + * Gets the selected columns of the table by initializing **selected with + * the selected column numbers. This array should be freed by the caller. + * + * Returns: a gint representing the number of selected columns, + * or %0 if value does not implement this interface. + **/ +static gint jaw_table_get_selected_columns(AtkTable *table, gint **selected) { + JAW_DEBUG("%p, %p", table, selected); + + if (selected == NULL) { + g_warning("%s: Null argument passed selected to function", G_STRFUNC); + return 0; + } + *selected = NULL; + + if (table == NULL) { + g_warning("%s: Null argument passed table to function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLE(table, 0); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + jintArray jcolumnArray = (jintArray)((*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableGetSelectedColumnsMethod)); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jcolumnArray == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_selected_columns method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jsize length = (*jniEnv)->GetArrayLength(jniEnv, jcolumnArray); + if (length <= 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jint *tmp = (*jniEnv)->GetIntArrayElements(jniEnv, jcolumnArray, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || tmp == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to read selected columns array", G_STRFUNC); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + *selected = g_new(gint, (gsize)length); + + for (jsize i = 0; i < length; i++) { + (*selected)[i] = (gint)tmp[i]; + } + + (*jniEnv)->ReleaseIntArrayElements(jniEnv, jcolumnArray, tmp, JNI_ABORT); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return (gint)length; +} + +/** + * jaw_table_get_selected_rows: + * @table: a GObject instance that implements AtkTableIface + * @selected: a #gint** that is to contain the selected row numbers + * + * Gets the selected rows of the table by initializing **selected with + * the selected row numbers. This array should be freed by the caller. + * + * Returns: a gint representing the number of selected rows, + * or zero if value does not implement this interface. + **/ +static gint jaw_table_get_selected_rows(AtkTable *table, gint **selected) { + JAW_DEBUG("%p, %p", table, selected); + + if (selected == NULL) { + g_warning("%s: Null argument passed selected to function", G_STRFUNC); + return 0; + } + *selected = NULL; + + if (table == NULL) { + g_warning("%s: Null argument passed table to function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLE(table, 0); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + jintArray jrowArray = (jintArray)((*jniEnv)->CallObjectMethod( + jniEnv, atk_table, cachedTableGetSelectedRowsMethod)); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || jrowArray == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_selected_rows method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jsize length = (*jniEnv)->GetArrayLength(jniEnv, jrowArray); + if (length <= 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + jint *tmp = (*jniEnv)->GetIntArrayElements(jniEnv, jrowArray, NULL); + if ((*jniEnv)->ExceptionCheck(jniEnv) || tmp == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to read selected rows array", G_STRFUNC); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + *selected = g_new(gint, (gsize)length); + for (jsize i = 0; i < length; i++) { + (*selected)[i] = (gint)tmp[i]; + } + + (*jniEnv)->ReleaseIntArrayElements(jniEnv, jrowArray, tmp, JNI_ABORT); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return (gint)length; +} + +/** + * jaw_table_is_column_selected: + * @table: a GObject instance that implements AtkTableIface + * @column: a #gint representing a column in @table + * + * Gets a boolean value indicating whether the specified @column + * is selected + * + * Returns: a gboolean representing if the column is selected, or 0 + * if value does not implement this interface. + **/ +static gboolean jaw_table_is_column_selected(AtkTable *table, gint column) { + JAW_DEBUG("%p, %d", table, column); + + if (table == NULL) { + g_warning("Null argument table passed to function " + "jaw_table_is_column_selected"); + return FALSE; + } + + JAW_GET_TABLE(table, FALSE); + + jboolean jselected = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_table, cachedTableIsColumnSelectedMethod, (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call is_column_selected method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jselected; +} + +/** + * jaw_table_is_row_selected: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * + * Gets a boolean value indicating whether the specified @row + * is selected + * + * Returns: a gboolean representing if the row is selected, or 0 + * if value does not implement this interface. + **/ +static gboolean jaw_table_is_row_selected(AtkTable *table, gint row) { + JAW_DEBUG("%p, %d", table, row); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_TABLE(table, FALSE); + + jboolean jselected = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_table, cachedTableIsRowSelectedMethod, (jint)row); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call is_row_selected method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jselected; +} + +/** + * jaw_table_is_selected: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * @column: a #gint representing a column in @table + * + * Gets a boolean value indicating whether the accessible object + * at the specified @row and @column is selected + * + * Returns: a gboolean representing if the cell is selected, or 0 + * if value does not implement this interface. + **/ +static gboolean jaw_table_is_selected(AtkTable *table, gint row, gint column) { + JAW_DEBUG("%p, %d, %d", table, row, column); + + if (table == NULL) { + g_warning("%s: Null argument table passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_TABLE(table, FALSE); + + jboolean jselected = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_table, cachedTableIsSelectedMethod, (jint)row, + (jint)column); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call is_selected method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + + return jselected; +} + +/** + * jaw_table_set_row_description: + * @table: a GObject instance that implements AtkTableIface + * @row: a #gint representing a row in @table + * @description: a #gchar representing the description text + * to set for the specified @row of @table + * + * Sets the description text for the specified @row of @table. + **/ +static void jaw_table_set_row_description(AtkTable *table, gint row, + const gchar *description) { + JAW_DEBUG("%p, %d, %s", table, row, description); + + if (!table || !description) { + g_warning( + "Null argument passed to function jaw_table_set_row_description"); + return; + } + + JAW_GET_TABLE(table, ); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, description); + if (jstr == NULL) { + g_warning("%s: Failed to create jstr from description", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallVoidMethod( + jniEnv, atk_table, cachedTableSetRowDescriptionMethod, (jint)row, jstr); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call set_row_description method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_table_set_column_description: + * @table: a GObject instance that implements AtkTableIface + * @column: a #gint representing a column in @table + * @description: a #gchar representing the description text + * to set for the specified @column of the @table + * + * Sets the description text for the specified @column of the @table. + **/ +static void jaw_table_set_column_description(AtkTable *table, gint column, + const gchar *description) { + JAW_DEBUG("%p, %d, %s", table, column, description); + + if (!table || !description) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_TABLE(table, ); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, description); + if (jstr == NULL) { + g_warning("%s: Failed to create jstr from description", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallVoidMethod(jniEnv, atk_table, + cachedTableSetColumnDescriptionMethod, + (jint)column, jstr); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call set_column_description method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_table_set_caption: + * @table: a GObject instance that implements AtkTableIface + * @caption: a #AtkObject representing the caption to set for @table + * + * Sets the caption for the table. + **/ +static void jaw_table_set_caption(AtkTable *table, AtkObject *caption) { + JAW_DEBUG("%p, %p", table, caption); + + if (!table || !caption) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_TABLE(table, ); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + JawObject *jcaption = JAW_OBJECT(caption); + if (jcaption == NULL) { + g_warning("%s: jcaption == NULL", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + jclass accessible = + (*jniEnv)->FindClass(jniEnv, "javax/accessibility/Accessible"); + if (accessible == NULL) { + g_warning("%s: Failed to find Accessible class", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + if (!(*jniEnv)->IsInstanceOf(jniEnv, jcaption->acc_context, accessible)) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + jobject obj = (*jniEnv)->NewLocalRef(jniEnv, jcaption->acc_context); + if (obj == NULL) { + g_warning("%s: jcaption obj == NULL", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallVoidMethod(jniEnv, atk_table, cachedTableSetCaptionMethod, + obj); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call set_caption method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_table_set_summary: + * @table: a GObject instance that implements AtkTableIface + * @accessible: an #AtkObject representing the summary description + * to set for @table + * + * Sets the summary description of the table. + **/ +static void jaw_table_set_summary(AtkTable *table, AtkObject *summary) { + JAW_DEBUG("%p, %p", table, summary); + + if (!table || !summary) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + JAW_GET_TABLE(table, ); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + JawObject *jsummary = JAW_OBJECT(summary); + if (jsummary == NULL) { + g_warning("%s: jsummary == NULL", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + jclass accessible = + (*jniEnv)->FindClass(jniEnv, "javax/accessibility/Accessible"); + if (accessible == NULL) { + g_warning("%s: Failed to find Accessible class", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + if (!(*jniEnv)->IsInstanceOf(jniEnv, jsummary->acc_context, accessible)) { + g_warning("%s: jsummary->acc_context is not instance of accessible", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + jobject obj = (*jniEnv)->NewLocalRef(jniEnv, jsummary->acc_context); + if (obj == NULL) { + g_warning("%s: jsummary obj == NULL", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallVoidMethod(jniEnv, atk_table, cachedTableSetSummaryMethod, + obj); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call set_summary method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_table); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +static gboolean jaw_table_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkTable"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkTable class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTableAtkTableClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedTableAtkTableClass == NULL) { + g_warning("%s: Failed to create global reference for AtkTable class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTableCreateAtkTableMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedTableAtkTableClass, "create_atk_table", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkTable;"); + + cachedTableRefAtMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "ref_at", + "(II)Ljavax/accessibility/AccessibleContext;"); + + cachedTableGetIndexAtMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_index_at", "(II)I"); + + cachedTableGetColumnAtIndexMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_column_at_index", "(I)I"); + + cachedTableGetRowAtIndexMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_row_at_index", "(I)I"); + + cachedTableGetNColumnsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_n_columns", "()I"); + + cachedTableGetNRowsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_n_rows", "()I"); + + cachedTableGetColumnExtentAtMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_column_extent_at", "(II)I"); + + cachedTableGetRowExtentAtMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_row_extent_at", "(II)I"); + + cachedTableGetCaptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "get_caption", + "()Ljavax/accessibility/AccessibleContext;"); + + cachedTableGetColumnDescriptionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_column_description", + "(I)Ljava/lang/String;"); + + cachedTableGetRowDescriptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, + "get_row_description", "(I)Ljava/lang/String;"); + + cachedTableGetColumnHeaderMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_column_header", + "(I)Ljavax/accessibility/AccessibleContext;"); + + cachedTableGetRowHeaderMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_row_header", + "(I)Ljavax/accessibility/AccessibleContext;"); + + cachedTableGetSummaryMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "get_summary", + "()Ljavax/accessibility/AccessibleContext;"); + + cachedTableGetSelectedColumnsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_selected_columns", "()[I"); + + cachedTableGetSelectedRowsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "get_selected_rows", "()[I"); + + cachedTableIsColumnSelectedMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "is_column_selected", "(I)Z"); + + cachedTableIsRowSelectedMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "is_row_selected", "(I)Z"); + + cachedTableIsSelectedMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "is_selected", "(II)Z"); + + cachedTableSetRowDescriptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, + "set_row_description", "(ILjava/lang/String;)V"); + + cachedTableSetColumnDescriptionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableAtkTableClass, "set_column_description", + "(ILjava/lang/String;)V"); + + cachedTableSetCaptionMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "set_caption", + "(Ljavax/accessibility/Accessible;)V"); + + cachedTableSetSummaryMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "set_summary", + "(Ljavax/accessibility/Accessible;)V"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedTableCreateAtkTableMethod == NULL || + cachedTableRefAtMethod == NULL || cachedTableGetIndexAtMethod == NULL || + cachedTableGetColumnAtIndexMethod == NULL || + cachedTableGetRowAtIndexMethod == NULL || + cachedTableGetNColumnsMethod == NULL || + cachedTableGetNRowsMethod == NULL || + cachedTableGetColumnExtentAtMethod == NULL || + cachedTableGetRowExtentAtMethod == NULL || + cachedTableGetCaptionMethod == NULL || + cachedTableGetColumnDescriptionMethod == NULL || + cachedTableGetRowDescriptionMethod == NULL || + cachedTableGetColumnHeaderMethod == NULL || + cachedTableGetRowHeaderMethod == NULL || + cachedTableGetSummaryMethod == NULL || + cachedTableGetSelectedColumnsMethod == NULL || + cachedTableGetSelectedRowsMethod == NULL || + cachedTableIsColumnSelectedMethod == NULL || + cachedTableIsRowSelectedMethod == NULL || + cachedTableIsSelectedMethod == NULL || + cachedTableSetRowDescriptionMethod == NULL || + cachedTableSetColumnDescriptionMethod == NULL || + cachedTableSetCaptionMethod == NULL || + cachedTableSetSummaryMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkTable method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedTableAtkTableClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableAtkTableClass); + cachedTableAtkTableClass = NULL; + } + cachedTableCreateAtkTableMethod = NULL; + cachedTableRefAtMethod = NULL; + cachedTableGetIndexAtMethod = NULL; + cachedTableGetColumnAtIndexMethod = NULL; + cachedTableGetRowAtIndexMethod = NULL; + cachedTableGetNColumnsMethod = NULL; + cachedTableGetNRowsMethod = NULL; + cachedTableGetColumnExtentAtMethod = NULL; + cachedTableGetRowExtentAtMethod = NULL; + cachedTableGetCaptionMethod = NULL; + cachedTableGetColumnDescriptionMethod = NULL; + cachedTableGetRowDescriptionMethod = NULL; + cachedTableGetColumnHeaderMethod = NULL; + cachedTableGetRowHeaderMethod = NULL; + cachedTableGetSummaryMethod = NULL; + cachedTableGetSelectedColumnsMethod = NULL; + cachedTableGetSelectedRowsMethod = NULL; + cachedTableIsColumnSelectedMethod = NULL; + cachedTableIsRowSelectedMethod = NULL; + cachedTableIsSelectedMethod = NULL; + cachedTableSetRowDescriptionMethod = NULL; + cachedTableSetColumnDescriptionMethod = NULL; + cachedTableSetCaptionMethod = NULL; + cachedTableSetSummaryMethod = NULL; + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_table_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedTableAtkTableClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableAtkTableClass); + cachedTableAtkTableClass = NULL; + } + cachedTableCreateAtkTableMethod = NULL; + cachedTableRefAtMethod = NULL; + cachedTableGetIndexAtMethod = NULL; + cachedTableGetColumnAtIndexMethod = NULL; + cachedTableGetRowAtIndexMethod = NULL; + cachedTableGetNColumnsMethod = NULL; + cachedTableGetNRowsMethod = NULL; + cachedTableGetColumnExtentAtMethod = NULL; + cachedTableGetRowExtentAtMethod = NULL; + cachedTableGetCaptionMethod = NULL; + cachedTableGetColumnDescriptionMethod = NULL; + cachedTableGetRowDescriptionMethod = NULL; + cachedTableGetColumnHeaderMethod = NULL; + cachedTableGetRowHeaderMethod = NULL; + cachedTableGetSummaryMethod = NULL; + cachedTableGetSelectedColumnsMethod = NULL; + cachedTableGetSelectedRowsMethod = NULL; + cachedTableIsColumnSelectedMethod = NULL; + cachedTableIsRowSelectedMethod = NULL; + cachedTableIsSelectedMethod = NULL; + cachedTableSetRowDescriptionMethod = NULL; + cachedTableSetColumnDescriptionMethod = NULL; + cachedTableSetCaptionMethod = NULL; + cachedTableSetSummaryMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtablecell.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtablecell.c new file mode 100644 index 000000000000..02d09ecafc95 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtablecell.c @@ -0,0 +1,775 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 021101301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkTableCell: + * + * The ATK interface implemented for a cell inside a two-dimentional #AtkTable + * + * Being #AtkTable a component which present elements ordered via rows + * and columns, an #AtkTableCell is the interface which each of those + * elements, so "cells" should implement. + * + * See [iface@AtkTable] + */ + +static jclass cachedTableCellAtkTableCellClass = NULL; +static jmethodID cachedTableCellCreateAtkTableCellMethod = NULL; +static jmethodID cachedTableCellGetTableMethod = NULL; +static jmethodID cachedTableCellGetAccessibleColumnHeaderMethod = NULL; +static jmethodID cachedTableCellGetAccessibleRowHeaderMethod = NULL; +static jfieldID cachedTableCellRowFieldID = NULL; +static jfieldID cachedTableCellColumnFieldID = NULL; +static jfieldID cachedTableCellRowSpanFieldID = NULL; +static jfieldID cachedTableCellColumnSpanFieldID = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_table_cell_init_jni_cache(JNIEnv *jniEnv); + +static AtkObject *jaw_table_cell_get_table(AtkTableCell *cell); +static GPtrArray *jaw_table_cell_get_column_header_cells(AtkTableCell *cell); +static gboolean jaw_table_cell_get_position(AtkTableCell *cell, gint *row, + gint *column); +static gboolean jaw_table_cell_get_row_column_span(AtkTableCell *cell, + gint *row, gint *column, + gint *row_span, + gint *column_span); +static gint jaw_table_cell_get_row_span(AtkTableCell *cell); +static GPtrArray *jaw_table_cell_get_row_header_cells(AtkTableCell *cell); +static gint jaw_table_cell_get_column_span(AtkTableCell *cell); + +typedef struct _TableCellData { + jobject atk_table_cell; +} TableCellData; + +#define JAW_GET_TABLECELL(cell, def_ret) \ + JAW_GET_OBJ_IFACE( \ + cell, org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL, \ + TableCellData, atk_table_cell, jniEnv, jatk_table_cell, def_ret) + +/** + * AtkTableCellIface: + * @get_column_span: virtual function that returns the number of + * columns occupied by this cell accessible + * @get_column_header_cells: virtual function that returns the column + * headers as an array of cell accessibles + * @get_position: virtual function that retrieves the tabular position + * of this cell + * @get_row_span: virtual function that returns the number of rows + * occupied by this cell + * @get_row_header_cells: virtual function that returns the row + * headers as an array of cell accessibles + * @get_row_column_span: virtual function that get the row an column + * indexes and span of this cell + * @get_table: virtual function that returns a reference to the + * accessible of the containing table + * + * AtkTableCell is an interface for cells inside an #AtkTable. + * + * Since: 2.12 + */ +void jaw_table_cell_interface_init(AtkTableCellIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument iface passed to the function", G_STRFUNC); + return; + } + + iface->get_column_span = jaw_table_cell_get_column_span; + iface->get_column_header_cells = jaw_table_cell_get_column_header_cells; + iface->get_position = jaw_table_cell_get_position; + iface->get_row_span = jaw_table_cell_get_row_span; + iface->get_row_header_cells = jaw_table_cell_get_row_header_cells; + iface->get_row_column_span = jaw_table_cell_get_row_column_span; + iface->get_table = jaw_table_cell_get_table; +} + +gpointer jaw_table_cell_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument ac passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_table_cell_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_table_cell = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedTableCellAtkTableCellClass, + cachedTableCellCreateAtkTableCellMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_table_cell == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jatk_table_cell using " + "create_atk_table_cell method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + TableCellData *data = g_new0(TableCellData, 1); + data->atk_table_cell = (*jniEnv)->NewGlobalRef(jniEnv, jatk_table_cell); + if (data->atk_table_cell == NULL) { + g_warning("%s: Failed to create global ref for atk_table_cell", + G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_table_cell_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + TableCellData *data = (TableCellData *)p; + if (data == NULL) { + g_warning("%s: TableCellData is NULL after cast", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize, leaking JNI resources", + G_STRFUNC); + } else { + if (data->atk_table_cell != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_table_cell); + data->atk_table_cell = NULL; + } + } + + g_free(data); +} + +/** + * jaw_table_cell_get_table: + * @cell: a GObject instance that implements AtkTableCellIface + * + * Returns a reference to the accessible of the containing table. + * + * Returns: (transfer full): the atk object for the containing table. + */ +static AtkObject *jaw_table_cell_get_table(AtkTableCell *cell) { + JAW_DEBUG("%p", cell); + + if (cell == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLECELL( + cell, NULL); // create local JNI reference `jobject jatk_table_cell` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jac = (*jniEnv)->CallObjectMethod(jniEnv, jatk_table_cell, + cachedTableCellGetTableMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_table method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + // From the documentation of the `cell_get_table`: + // "The caller of the method takes ownership of the returned data, and is + // responsible for freeing it." (transfer full) + if (jaw_impl != NULL) { + g_object_ref(G_OBJECT(jaw_impl)); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_OBJECT(jaw_impl); +} + +/** + * getPosition: + * @jniEnv: JNI environment pointer. + * @jatk_table_cell: a Java object implementing the AtkTableCell interface. + * @classAtkTableCell: the Java class of @jatk_table_cell. + * @row: (out): return location for the zero-based row index of the cell. + * @column: (out): return location for the zero-based column index of the cell. + * + * Retrieves the row and column index of the cell. + * If the operation succeeds, the values of @row and @column are updated. + * If it fails, the output arguments are left unchanged. + * + * Returns: %TRUE if successful; %FALSE otherwise. + */ +static gboolean getPosition(JNIEnv *jniEnv, jobject jatk_table_cell, gint *row, + gint *column) { + JAW_DEBUG("%p, %p, %p, %p", jniEnv, jatk_table_cell, row, column); + + if (jniEnv == NULL || row == NULL || column == NULL) { + g_warning("%s: Null argument. jniEnv=%p, row=%p, column=%p", G_STRFUNC, + (void *)jniEnv, (void *)row, (void *)column); + return FALSE; + } + + jint jrow = (*jniEnv)->GetIntField(jniEnv, jatk_table_cell, + cachedTableCellRowFieldID); + if (jrow < 0) { + g_warning("%s: Invalid row value (%d) retrieved", G_STRFUNC, jrow); + return FALSE; + } + + jint jcolumn = (*jniEnv)->GetIntField(jniEnv, jatk_table_cell, + cachedTableCellColumnFieldID); + if (jcolumn < 0) { + g_warning("%s: Invalid column value (%d) retrieved", G_STRFUNC, + jcolumn); + return FALSE; + } + + (*row) = (gint)jrow; + (*column) = (gint)jcolumn; + return TRUE; +} + +/** + * jaw_table_cell_get_position: + * @cell: an #AtkTableCell. + * @row: (out): the row of the given cell. + * @column: (out): the column of the given cell. + * + * Retrieves the tabular position of this cell. + * + * Returns: %TRUE if successful; %FALSE otherwise. + * + * Since: 2.12 + */ +static gboolean jaw_table_cell_get_position(AtkTableCell *cell, gint *row, + gint *column) { + JAW_DEBUG("%p, %p, %p", cell, row, column); + + if (cell == NULL || row == NULL || column == NULL) { + g_warning("%s: Null argument. cell=%p, row=%p, column=%p", G_STRFUNC, + (void *)cell, (void *)row, (void *)column); + return FALSE; + } + + JAW_GET_TABLECELL(cell, FALSE); + + if (!getPosition(jniEnv, jatk_table_cell, row, column)) { + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + + return TRUE; +} + +/** + * getRowSpan: + * @jniEnv: JNI environment pointer. + * @jatk_table_cell: a Java object implementing the AtkTableCell interface. + * @classAtkTableCell: the Java class of @jatk_table_cell. + * @row_span: (out): return location for the row-span value. + * + * Retrieves the `rowSpan` field from a Java ATK table cell object. + * On success, the value is stored in @row_span. + * On failure, @row_span is left unchanged. + * + * Returns: %TRUE if the value was successfully retrieved; %FALSE otherwise. + */ +static gboolean getRowSpan(JNIEnv *jniEnv, jobject jatk_table_cell, + gint *row_span) { + JAW_DEBUG("%p, %p, %p", jniEnv, jatk_table_cell, row_span); + + if (jniEnv == NULL || row_span == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + jint jrow_span = (*jniEnv)->GetIntField(jniEnv, jatk_table_cell, + cachedTableCellRowSpanFieldID); + if (jrow_span < 0) { + g_warning("%s: Invalid row span value (%d) retrieved", G_STRFUNC, + jrow_span); + return FALSE; + } + + (*row_span) = (gint)jrow_span; + return TRUE; +} + +/** + * getColumnSpan: + * @jniEnv: a valid JNI environment pointer. + * @jatk_table_cell: a Java object implementing the AtkTableCell interface. + * @classAtkTableCell: the Java class of @jatk_table_cell. + * @column_span: (out): return location for the column-span value. + * + * Retrieves the `columnSpan` field from a Java ATK table cell object. + * On success, the value is stored in @column_span. + * On failure, @column_span is left unchanged. + * + * Returns: %TRUE if the column span value was successfully retrieved and + * stored in @column_span; %FALSE on error. + */ +static gboolean getColumnSpan(JNIEnv *jniEnv, jobject jatk_table_cell, + gint *column_span) { + JAW_DEBUG("%p, %p, %p", jniEnv, jatk_table_cell, column_span); + + if (jniEnv == NULL || column_span == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return FALSE; + } + + jint jcolumn_span = (*jniEnv)->GetIntField( + jniEnv, jatk_table_cell, cachedTableCellColumnSpanFieldID); + if (jcolumn_span < 0) { + g_warning("%s: Invalid column span value (%d) retrieved", G_STRFUNC, + jcolumn_span); + return FALSE; + } + + (*column_span) = (gint)jcolumn_span; + return TRUE; +} + +/** + * jaw_table_cell_get_row_column_span: + * @cell: an #AtkTableCell. + * @row: (out): the row index of the given cell. + * @column: (out): the column index of the given cell. + * @row_span: (out): the number of rows occupied by this cell. + * @column_span: (out): the number of columns occupied by this cell. + * + * Gets the row and column indexes and span of this cell accessible. + * + * Note: Even if the function returns %FALSE, some of the output arguments + * may have been partially updated before the failure occurred. + * + * Returns: %TRUE if successful; %FALSE otherwise. + * + * Since: 2.12 + */ +static gboolean jaw_table_cell_get_row_column_span(AtkTableCell *cell, + gint *row, gint *column, + gint *row_span, + gint *column_span) { + JAW_DEBUG("%p, %p, %p, %p, %p", cell, row, column, row_span, column_span); + + if (cell == NULL || row == NULL || column == NULL || row_span == NULL || + column_span == NULL) { + g_warning("%s: Null argument. cell=%p, row=%p, column=%p, row_span=%p, " + "column_span=%p", + G_STRFUNC, (void *)cell, (void *)row, (void *)column, + (void *)row_span, (void *)column_span); + return FALSE; + } + + JAW_GET_TABLECELL(cell, FALSE); + + if (!getPosition(jniEnv, jatk_table_cell, row, column)) { + g_warning("%s: getPosition failed", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return FALSE; + } + + if (!getRowSpan(jniEnv, jatk_table_cell, row_span)) { + g_warning("%s: getRowSpan failed", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return FALSE; + } + + if (!getColumnSpan(jniEnv, jatk_table_cell, column_span)) { + g_warning("%s: getColumnSpan failed", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + + return TRUE; +} + +/** + * jaw_table_cell_get_row_span: + * @cell: (nullable): an #AtkTableCell instance + * + * Returns the number of rows occupied by this cell accessible. + * + * Returns: (type gint): + * A gint representing the number of rows occupied by this cell, or 0 if the + * cell does not implement this method. + * + * Since: 2.12 + */ +static gint jaw_table_cell_get_row_span(AtkTableCell *cell) { + JAW_DEBUG("%p", cell); + + if (cell == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLECELL(cell, 0); + + gint row_span = 0; + if (!getRowSpan(jniEnv, jatk_table_cell, &row_span)) { + g_warning("%s: getRowSpan failed", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return row_span; +} + +/** + * jaw_table_cell_get_column_span: + * @cell: (nullable): an #AtkTableCell instance + * + * Returns the number of columns occupied by this table cell. + * + * Returns: (type gint): + * A gint representing the number of columns occupied by this cell, + * or 0 if the cell does not implement this method. + * + * Since: 2.12 + */ +static gint jaw_table_cell_get_column_span(AtkTableCell *cell) { + JAW_DEBUG("%p", cell); + + if (cell == NULL) { + g_warning("%s: Null argument cell passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_TABLECELL(cell, 0); + + gint column_span = 0; + if (!getColumnSpan(jniEnv, jatk_table_cell, &column_span)) { + g_warning("%s: getColumnSpan failed", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + + return column_span; +} + +/** + * jaw_table_cell_get_column_header_cells: + * @cell: a GObject instance that implements AtkTableCellIface + * + * Returns the column headers as an array of cell accessibles. + * + * Returns: (element-type AtkObject) (transfer full): a GPtrArray of AtkObjects + * representing the column header cells. + */ +static GPtrArray *jaw_table_cell_get_column_header_cells(AtkTableCell *cell) { + JAW_DEBUG("%p", cell); + + if (cell == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLECELL( + cell, NULL); // create local JNI reference `jobject jatk_table_cell` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobjectArray columnHeaders = (jobjectArray)(*jniEnv)->CallObjectMethod( + jniEnv, jatk_table_cell, + cachedTableCellGetAccessibleColumnHeaderMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || columnHeaders == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_accessible_column_header method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jsize length = (*jniEnv)->GetArrayLength(jniEnv, columnHeaders); + GPtrArray *result = g_ptr_array_sized_new((guint)length); + if (result == NULL) { + g_warning("%s: Failed to allocate GPtrArray", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + for (jsize i = 0; i < length; i++) { + jobject jac = + (*jniEnv)->GetObjectArrayElement(jniEnv, columnHeaders, i); + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + if (jaw_impl != NULL) { + g_ptr_array_add(result, g_object_ref(G_OBJECT(jaw_impl))); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jac); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_table_cell_get_row_header_cells: + * @cell: a GObject instance that implements AtkTableCellIface + * + * Returns the row headers as an array of cell accessibles. + * + * Returns: (element-type AtkObject) (transfer full): a GPtrArray of AtkObjects + * representing the row header cells. + */ +static GPtrArray *jaw_table_cell_get_row_header_cells(AtkTableCell *cell) { + JAW_DEBUG("%p", cell); + + if (cell == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TABLECELL( + cell, NULL); // create local JNI reference `jobject jatk_table_cell` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobjectArray rowHeaders = (jobjectArray)(*jniEnv)->CallObjectMethod( + jniEnv, jatk_table_cell, cachedTableCellGetAccessibleRowHeaderMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || rowHeaders == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to call get_accessible_row_header method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jsize length = (*jniEnv)->GetArrayLength(jniEnv, rowHeaders); + GPtrArray *result = g_ptr_array_sized_new((guint)length); + if (result == NULL) { + g_warning("%s: Failed to allocate GPtrArray", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + for (jsize i = 0; i < length; i++) { + jobject jac = (*jniEnv)->GetObjectArrayElement(jniEnv, rowHeaders, i); + JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac); + if (jaw_impl != NULL) { + g_ptr_array_add(result, g_object_ref(G_OBJECT(jaw_impl))); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jac); + } + + (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +static gboolean jaw_table_cell_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClass = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkTableCell"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkTableCell class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTableCellAtkTableCellClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClass); + (*jniEnv)->DeleteLocalRef(jniEnv, localClass); + + if (cachedTableCellAtkTableCellClass == NULL) { + g_warning( + "%s: Failed to create global reference for AtkTableCell class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTableCellCreateAtkTableCellMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedTableCellAtkTableCellClass, "create_atk_table_cell", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkTableCell;"); + + cachedTableCellGetTableMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableCellAtkTableCellClass, "get_table", + "()Ljavax/accessibility/AccessibleTable;"); + + cachedTableCellGetAccessibleColumnHeaderMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedTableCellAtkTableCellClass, + "get_accessible_column_header", + "()[Ljavax/accessibility/AccessibleContext;"); + + cachedTableCellGetAccessibleRowHeaderMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTableCellAtkTableCellClass, "get_accessible_row_header", + "()[Ljavax/accessibility/AccessibleContext;"); + + cachedTableCellRowFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTableCellAtkTableCellClass, "row", "I"); + + cachedTableCellColumnFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTableCellAtkTableCellClass, "column", "I"); + + cachedTableCellRowSpanFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTableCellAtkTableCellClass, "rowSpan", "I"); + + cachedTableCellColumnSpanFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTableCellAtkTableCellClass, "columnSpan", "I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedTableCellCreateAtkTableCellMethod == NULL || + cachedTableCellGetTableMethod == NULL || + cachedTableCellGetAccessibleColumnHeaderMethod == NULL || + cachedTableCellGetAccessibleRowHeaderMethod == NULL || + cachedTableCellRowFieldID == NULL || + cachedTableCellColumnFieldID == NULL || + cachedTableCellRowSpanFieldID == NULL || + cachedTableCellColumnSpanFieldID == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning( + "%s: Failed to cache one or more AtkTableCell method/field IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedTableCellAtkTableCellClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableCellAtkTableCellClass); + cachedTableCellAtkTableCellClass = NULL; + } + cachedTableCellCreateAtkTableCellMethod = NULL; + cachedTableCellGetTableMethod = NULL; + cachedTableCellGetAccessibleColumnHeaderMethod = NULL; + cachedTableCellGetAccessibleRowHeaderMethod = NULL; + cachedTableCellRowFieldID = NULL; + cachedTableCellColumnFieldID = NULL; + cachedTableCellRowSpanFieldID = NULL; + cachedTableCellColumnSpanFieldID = NULL; + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_tablecell_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedTableCellAtkTableCellClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableCellAtkTableCellClass); + cachedTableCellAtkTableCellClass = NULL; + } + cachedTableCellCreateAtkTableCellMethod = NULL; + cachedTableCellGetTableMethod = NULL; + cachedTableCellGetAccessibleColumnHeaderMethod = NULL; + cachedTableCellGetAccessibleRowHeaderMethod = NULL; + cachedTableCellRowFieldID = NULL; + cachedTableCellColumnFieldID = NULL; + cachedTableCellRowSpanFieldID = NULL; + cachedTableCellColumnSpanFieldID = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtext.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtext.c new file mode 100644 index 000000000000..7d361f9baa76 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtext.c @@ -0,0 +1,1450 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +/** + * (From Atk documentation) + * + * AtkText: + * + * The ATK interface implemented by components with text content. + * + * #AtkText should be implemented by #AtkObjects on behalf of widgets + * that have text content which is either attributed or otherwise + * non-trivial. #AtkObjects whose text content is simple, + * unattributed, and very brief may expose that content via + * #atk_object_get_name instead; however if the text is editable, + * multi-line, typically longer than three or four words, attributed, + * selectable, or if the object already uses the 'name' ATK property + * for other information, the #AtkText interface should be used to + * expose the text content. In the case of editable text content, + * #AtkEditableText (a subtype of the #AtkText interface) should be + * implemented instead. + * + * #AtkText provides not only traversal facilities and change + * notification for text content, but also caret tracking and glyph + * bounding box calculations. Note that the text strings are exposed + * as UTF-8, and are therefore potentially multi-byte, and + * caret-to-byte offset mapping makes no assumptions about the + * character length; also bounding box glyph-to-offset mapping may be + * complex for languages which use ligatures. + */ + +static jclass cachedTextAtkTextClass = NULL; +static jmethodID cachedTextCreateAtkTextMethod = NULL; +static jmethodID cachedTextGetTextMethod = NULL; +static jmethodID cachedTextGetCharacterAtOffsetMethod = NULL; +static jmethodID cachedTextGetTextAfterOffsetMethod = NULL; +static jmethodID cachedTextGetTextAtOffsetMethod = NULL; +static jmethodID cachedTextGetTextBeforeOffsetMethod = NULL; +static jmethodID cachedTextGetStringAtOffsetMethod = NULL; +static jmethodID cachedTextGetCaretOffsetMethod = NULL; +static jmethodID cachedTextGetCharacterExtentsMethod = NULL; +static jmethodID cachedTextGetCharacterCountMethod = NULL; +static jmethodID cachedTextGetOffsetAtPointMethod = NULL; +static jmethodID cachedTextGetRangeExtentsMethod = NULL; +static jmethodID cachedTextGetNSelectionsMethod = NULL; +static jmethodID cachedTextGetSelectionMethod = NULL; +static jmethodID cachedTextAddSelectionMethod = NULL; +static jmethodID cachedTextRemoveSelectionMethod = NULL; +static jmethodID cachedTextSetSelectionMethod = NULL; +static jmethodID cachedTextSetCaretOffsetMethod = NULL; +static jclass cachedTextStringSequenceClass = NULL; +static jfieldID cachedTextStrFieldID = NULL; +static jfieldID cachedTextStartOffsetFieldID = NULL; +static jfieldID cachedTextEndOffsetFieldID = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_text_init_jni_cache(JNIEnv *jniEnv); + +static gchar *jaw_text_get_text(AtkText *text, gint start_offset, + gint end_offset); +static gunichar jaw_text_get_character_at_offset(AtkText *text, gint offset); +static gchar *jaw_text_get_text_at_offset(AtkText *text, gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, gint *end_offset); +static gchar *jaw_text_get_text_before_offset(AtkText *text, gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset); +static gchar *jaw_text_get_text_after_offset(AtkText *text, gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset); +static gchar *jaw_text_get_string_at_offset(AtkText *text, gint offset, + AtkTextGranularity granularity, + gint *start_offset, + gint *end_offset); +static gint jaw_text_get_caret_offset(AtkText *text); +static void jaw_text_get_character_extents(AtkText *text, gint offset, gint *x, + gint *y, gint *width, gint *height, + AtkCoordType coords); +static gint jaw_text_get_character_count(AtkText *text); +static gint jaw_text_get_offset_at_point(AtkText *text, gint x, gint y, + AtkCoordType coords); +static void jaw_text_get_range_extents(AtkText *text, gint start_offset, + gint end_offset, AtkCoordType coord_type, + AtkTextRectangle *rect); +static gint jaw_text_get_n_selections(AtkText *text); +static gchar *jaw_text_get_selection(AtkText *text, gint selection_num, + gint *start_offset, gint *end_offset); +static gboolean jaw_text_add_selection(AtkText *text, gint start_offset, + gint end_offset); +static gboolean jaw_text_remove_selection(AtkText *text, gint selection_num); +static gboolean jaw_text_set_selection(AtkText *text, gint selection_num, + gint start_offset, gint end_offset); +static gboolean jaw_text_set_caret_offset(AtkText *text, gint offset); + +typedef struct _TextData { + jobject atk_text; +} TextData; + +#define JAW_GET_TEXT(text, def_ret) \ + JAW_GET_OBJ_IFACE(text, \ + org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT, \ + TextData, atk_text, jniEnv, atk_text, def_ret) + +void jaw_text_interface_init(AtkTextIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + iface->get_text = jaw_text_get_text; + iface->get_text_after_offset = jaw_text_get_text_after_offset; + iface->get_text_at_offset = jaw_text_get_text_at_offset; + iface->get_character_at_offset = jaw_text_get_character_at_offset; + iface->get_text_before_offset = jaw_text_get_text_before_offset; + iface->get_string_at_offset = jaw_text_get_string_at_offset; + iface->get_caret_offset = jaw_text_get_caret_offset; + iface->get_run_attributes = + NULL; // TODO: iface->get_run_attributes by iterating + // getCharacterAttribute or using getTextSequenceAt with + // ATTRIBUTE_RUN + iface->get_default_attributes = NULL; // TODO: iface->get_default_attributes + iface->get_character_extents = jaw_text_get_character_extents; + iface->get_character_count = jaw_text_get_character_count; + iface->get_offset_at_point = jaw_text_get_offset_at_point; + iface->get_n_selections = jaw_text_get_n_selections; + iface->get_selection = jaw_text_get_selection; + iface->add_selection = jaw_text_add_selection; + iface->remove_selection = jaw_text_remove_selection; + iface->set_selection = jaw_text_set_selection; + iface->set_caret_offset = jaw_text_set_caret_offset; + + /* + * signal handlers + */ + // iface->text_changed implemented by atk + // iface->text_caret_moved implemented by atk + // iface->text_selection_changed implemented by atk + // iface->text_attributes_changed implemented by atk + iface->get_range_extents = jaw_text_get_range_extents; + iface->get_bounded_ranges = + NULL; // TODO: iface->get_bounded_ranges from getTextBounds + + /* + * Scrolls this text range so it becomes visible on the screen. + * + * scroll_substring_to lets the implementation compute an appropriate target + * position on the screen, with type used as a positioning hint. + * + * scroll_substring_to_point lets the client specify a precise target + * position on the screen for the top-left of the substring. + * + * Since ATK 2.32 + */ + iface->scroll_substring_to = NULL; // missing java support + iface->scroll_substring_to_point = NULL; // missing java support +} + +gpointer jaw_text_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_text_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_text = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedTextAtkTextClass, cachedTextCreateAtkTextMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_text == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jatk_text using create_atk_text method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + TextData *data = g_new0(TextData, 1); + data->atk_text = (*jniEnv)->NewGlobalRef(jniEnv, jatk_text); + if (data->atk_text == NULL) { + g_warning("%s: Failed to create global ref for atk_text", G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_text_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return; + } + + TextData *data = (TextData *)p; + if (data == NULL) { + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->atk_text != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_text); + data->atk_text = NULL; + } + } + + g_free(data); +} + +static gchar *private_jaw_text_get_gtext_from_jstr(JNIEnv *jniEnv, + jstring jstr) { + JAW_DEBUG("%p, %p", jniEnv, jstr); + + if (jniEnv == NULL || jstr == NULL) { + g_warning("%s: Null argument passed. jniEnv=%p, jstr=%p", G_STRFUNC, + (void *)jniEnv, (void *)jstr); + return NULL; + } + + gchar *tmp_text = (gchar *)(*jniEnv)->GetStringUTFChars(jniEnv, jstr, NULL); + if (tmp_text == NULL) { + g_warning("%s: tmp_text is NULL", G_STRFUNC); + return NULL; + } + gchar *text = g_strdup(tmp_text); + (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, tmp_text); + + return text; +} + +static gchar *private_jaw_text_get_gtext_from_string_seq(JNIEnv *jniEnv, + jobject jStrSeq, + gint *start_offset, + gint *end_offset) { + if (jniEnv == NULL || start_offset == NULL || end_offset == NULL) { + g_warning( + "%s: Null argument. jniEnv=%p, start_offset=%p, end_offset=%p", + G_STRFUNC, (void *)jniEnv, (void *)start_offset, + (void *)end_offset); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jStr = + (*jniEnv)->GetObjectField(jniEnv, jStrSeq, cachedTextStrFieldID); + if (jStr == NULL) { + g_warning("%s: Failed to get jStr field", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*start_offset) = (gint)(*jniEnv)->GetIntField( + jniEnv, jStrSeq, cachedTextStartOffsetFieldID); + (*end_offset) = (gint)(*jniEnv)->GetIntField(jniEnv, jStrSeq, + cachedTextEndOffsetFieldID); + + gchar *result = private_jaw_text_get_gtext_from_jstr(jniEnv, jStr); + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * atk_text_get_text: + * @text: an #AtkText + * @start_offset: a starting character offset within @text + * @end_offset: an ending character offset within @text, or -1 for the end of + *the string. + * + * Gets the specified text. + * + * Returns: a newly allocated string containing the text from @start_offset up + * to, but not including @end_offset. Use g_free() to free the returned + * string. + **/ +static gchar *jaw_text_get_text(AtkText *text, gint start_offset, + gint end_offset) { + JAW_DEBUG("%p, %d, %d", text, start_offset, end_offset); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jstring jstr = + (*jniEnv)->CallObjectMethod(jniEnv, atk_text, cachedTextGetTextMethod, + (jint)start_offset, (jint)end_offset); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jstr using get_text method", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + gchar *result = private_jaw_text_get_gtext_from_jstr(jniEnv, jstr); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_text_get_character_at_offset: + * @text: an #AtkText + * @offset: a character offset within @text + * + * Gets the specified text. + * + * Returns: the character at @offset or 0 in the case of failure. + **/ +static gunichar jaw_text_get_character_at_offset(AtkText *text, gint offset) { + JAW_DEBUG("%p, %d", text, offset); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_TEXT(text, 0); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return 0; + } + + jchar jcharacter = (*jniEnv)->CallCharMethod( + jniEnv, atk_text, cachedTextGetCharacterAtOffsetMethod, (jint)offset); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + if (jcharacter == '\0') { + g_warning("%s: jcharacter is '\\0'", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return (gunichar)jcharacter; +} + +/** + * jaw_text_get_text_after_offset: + * @text: an #AtkText + * @offset: position + * @boundary_type: An #AtkTextBoundary + * @start_offset: (out): the starting character offset of the returned string + * @end_offset: (out): the offset of the first character after the + * returned substring + * + * Gets the specified text. + * + * Deprecated: 2.9.3 in atk: Please use atk_text_get_string_at_offset() instead. + * + * Returns: a newly allocated string containing the text after @offset bounded + * by the specified @boundary_type. Use g_free() to free the returned + * string. + **/ +static gchar *jaw_text_get_text_after_offset(AtkText *text, gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) { + JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, boundary_type, start_offset, + end_offset); + + if (text == NULL || start_offset == NULL || end_offset == NULL) { + g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p", + G_STRFUNC, (void *)text, (void *)start_offset, + (void *)end_offset); + return NULL; + } + + JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jStrSeq = (*jniEnv)->CallObjectMethod( + jniEnv, atk_text, cachedTextGetTextAfterOffsetMethod, (jint)offset, + (jint)boundary_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jStrSeq using get_text_after_offset method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + gchar *result = private_jaw_text_get_gtext_from_string_seq( + jniEnv, jStrSeq, start_offset, end_offset); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_text_get_text_at_offset: + * @text: an #AtkText + * @offset: position + * @boundary_type: An #AtkTextBoundary + * @start_offset: (out): the starting character offset of the returned string + * @end_offset: (out): the offset of the first character after the + * returned substring + * + * Deprecated: This method is deprecated since ATK version + * 2.9.4. Please use atk_text_get_string_at_offset() instead. + * + * Returns: a newly allocated string containing the text at @offset bounded + * by the specified @boundary_type. Use g_free() to free the returned + * string. + **/ +static gchar *jaw_text_get_text_at_offset(AtkText *text, gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) { + JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, boundary_type, start_offset, + end_offset); + + if (text == NULL || start_offset == NULL || end_offset == NULL) { + g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p", + G_STRFUNC, (void *)text, (void *)start_offset, + (void *)end_offset); + return NULL; + } + + JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jStrSeq = (*jniEnv)->CallObjectMethod( + jniEnv, atk_text, cachedTextGetTextAtOffsetMethod, (jint)offset, + (jint)boundary_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jStrSeq using get_text_at_offset method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + char *result = private_jaw_text_get_gtext_from_string_seq( + jniEnv, jStrSeq, start_offset, end_offset); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_text_get_text_before_offset: + * @text: an #AtkText + * @offset: position + * @boundary_type: An #AtkTextBoundary + * @start_offset: (out): the starting character offset of the returned string + * @end_offset: (out): the offset of the first character after the + * returned substring + * + * Gets the specified text. + * + * Deprecated in atk: 2.9.3: Please use atk_text_get_string_at_offset() instead. + * + * Returns: a newly allocated string containing the text before @offset bounded + * by the specified @boundary_type. Use g_free() to free the returned + * string. + **/ +static gchar *jaw_text_get_text_before_offset(AtkText *text, gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) { + JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, boundary_type, start_offset, + end_offset); + + if (text == NULL || start_offset == NULL || end_offset == NULL) { + g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p", + G_STRFUNC, (void *)text, (void *)start_offset, + (void *)end_offset); + return NULL; + } + + JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jStrSeq = (*jniEnv)->CallObjectMethod( + jniEnv, atk_text, cachedTextGetTextBeforeOffsetMethod, (jint)offset, + (jint)boundary_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jStrSeq using get_text_before_offset method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + char *result = private_jaw_text_get_gtext_from_string_seq( + jniEnv, jStrSeq, start_offset, end_offset); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_text_get_string_at_offset: + * @text: an #AtkText + * @offset: position + * @granularity: An #AtkTextGranularity + * @start_offset: (out): the starting character offset of the returned string, + *or -1 in the case of error (e.g. invalid offset, not implemented) + * @end_offset: (out): the offset of the first character after the returned + *string, or -1 in the case of error (e.g. invalid offset, not implemented) + * + * Gets a portion of the text exposed through an #AtkText according to a given + *@offset and a specific @granularity, along with the start and end offsets + *defining the boundaries of such a portion of text. + * + * If @granularity is ATK_TEXT_GRANULARITY_CHAR the character at the + * offset is returned. + * + * If @granularity is ATK_TEXT_GRANULARITY_WORD the returned string + * is from the word start at or before the offset to the word start after + * the offset. + * + * The returned string will contain the word at the offset if the offset + * is inside a word and will contain the word before the offset if the + * offset is not inside a word. + * + * If @granularity is ATK_TEXT_GRANULARITY_SENTENCE the returned string + * is from the sentence start at or before the offset to the sentence + * start after the offset. + * + * The returned string will contain the sentence at the offset if the offset + * is inside a sentence and will contain the sentence before the offset + * if the offset is not inside a sentence. + * + * If @granularity is ATK_TEXT_GRANULARITY_LINE the returned string + * is from the line start at or before the offset to the line + * start after the offset. + * + * If @granularity is ATK_TEXT_GRANULARITY_PARAGRAPH the returned string + * is from the start of the paragraph at or before the offset to the start + * of the following paragraph after the offset. + * + * Since: 2.10 (in atk) + * + * Returns: (nullable): a newly allocated string containing the text at + * the @offset bounded by the specified @granularity. Use g_free() + * to free the returned string. Returns %NULL if the offset is invalid + * or no implementation is available. + **/ +static gchar *jaw_text_get_string_at_offset(AtkText *text, gint offset, + AtkTextGranularity granularity, + gint *start_offset, + gint *end_offset) { + JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, granularity, start_offset, + end_offset); + + if (text == NULL || start_offset == NULL || end_offset == NULL) { + g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p", + G_STRFUNC, (void *)text, (void *)start_offset, + (void *)end_offset); + return NULL; + } + + JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jStrSeq = (*jniEnv)->CallObjectMethod( + jniEnv, atk_text, cachedTextGetStringAtOffsetMethod, (jint)offset, + (jint)granularity); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jStrSeq using get_string_at_offset method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + char *result = private_jaw_text_get_gtext_from_string_seq( + jniEnv, jStrSeq, start_offset, end_offset); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_text_get_caret_offset: + * @text: an #AtkText + * + * Gets the offset of the position of the caret (cursor). + * + * Returns: the character offset of the position of the caret or -1 if + * the caret is not located inside the element or in the case of + * any other failure. + **/ +static gint jaw_text_get_caret_offset(AtkText *text) { + JAW_DEBUG("%p", text); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TEXT(text, -1); + + jint joffset = (*jniEnv)->CallIntMethod(jniEnv, atk_text, + cachedTextGetCaretOffsetMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return (gint)joffset; +} + +/** + * jaw_text_get_character_extents: + * @text: an #AtkText + * @offset: The offset of the text character for which bounding information is + *required. + * @x: (out) (optional): Pointer for the x coordinate of the bounding box + * @y: (out) (optional): Pointer for the y coordinate of the bounding box + * @width: (out) (optional): Pointer for the width of the bounding box + * @height: (out) (optional): Pointer for the height of the bounding box + * @coords: specify whether coordinates are relative to the screen or widget + *window + * + * If the extent can not be obtained (e.g. missing support), all of x, y, width, + * height are set to -1. + * + * Get the bounding box containing the glyph representing the character at + * a particular text offset. + **/ +static void jaw_text_get_character_extents(AtkText *text, gint offset, gint *x, + gint *y, gint *width, gint *height, + AtkCoordType coords) { + JAW_DEBUG("%p, %d, %p, %p, %p, %p, %d", text, offset, x, y, width, height, + coords); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return; + } + + if (x != NULL) + *x = -1; + if (y != NULL) + *y = -1; + if (width != NULL) + *width = -1; + if (height != NULL) + *height = -1; + + JAW_GET_TEXT(text, ); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jobject jrect = (*jniEnv)->CallObjectMethod( + jniEnv, atk_text, cachedTextGetCharacterExtentsMethod, (jint)offset, + (jint)coords); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jrect == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jrect using get_character_extents method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + gint temp_x, temp_y, temp_width, temp_height; + jaw_util_get_rect_info(jniEnv, jrect, &temp_x, &temp_y, &temp_width, + &temp_height); + + if (x != NULL) + *x = temp_x; + if (y != NULL) + *y = temp_y; + if (width != NULL) + *width = temp_width; + if (height != NULL) + *height = temp_height; + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_text_get_character_count: + * @text: an #AtkText + * + * Gets the character count. + * + * Returns: the number of characters or -1 in case of failure. + **/ +static gint jaw_text_get_character_count(AtkText *text) { + JAW_DEBUG("%p", text); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TEXT(text, -1); + + jint jcount = (*jniEnv)->CallIntMethod(jniEnv, atk_text, + cachedTextGetCharacterCountMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return (gint)jcount; +} + +/** + * jaw_text_get_offset_at_point: + * @text: an #AtkText + * @x: screen x-position of character + * @y: screen y-position of character + * @coords: specify whether coordinates are relative to the screen or + * widget window + * + * Gets the offset of the character located at coordinates @x and @y. @x and @y + * are interpreted as being relative to the screen or this widget's window + * depending on @coords. + * + * Returns: the offset to the character which is located at the specified + * @x and @y coordinates of -1 in case of failure. + **/ +static gint jaw_text_get_offset_at_point(AtkText *text, gint x, gint y, + AtkCoordType coords) { + JAW_DEBUG("%p, %d, %d, %d", text, x, y, coords); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TEXT(text, -1); + + jint joffset = (*jniEnv)->CallIntMethod(jniEnv, atk_text, + cachedTextGetOffsetAtPointMethod, + (jint)x, (jint)y, (jint)coords); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return (gint)joffset; +} + +/** + * jaw_text_get_range_extents: + * @text: an #AtkText + * @start_offset: The offset of the first text character for which boundary + * information is required. + * @end_offset: The offset of the text character after the last character + * for which boundary information is required. + * @coord_type: Specify whether coordinates are relative to the screen or widget + *window. + * @rect: (out): A pointer to a AtkTextRectangle which is filled in by this + *function. + * + * Get the bounding box for text within the specified range. + * + * If the extents can not be obtained (e.g. or missing support), the rectangle + * fields are set to -1. + * + * In Atk Since: 1.3 + **/ +static void jaw_text_get_range_extents(AtkText *text, gint start_offset, + gint end_offset, AtkCoordType coord_type, + AtkTextRectangle *rect) { + JAW_DEBUG("%p, %d, %d, %d, %p", text, start_offset, end_offset, coord_type, + rect); + + if (text == NULL || rect == NULL) { + g_warning("%s: Null argument. text=%p, rect=%p", G_STRFUNC, + (void *)text, (void *)rect); + return; + } + + rect->x = -1; + rect->y = -1; + rect->width = -1; + rect->height = -1; + + JAW_GET_TEXT(text, ); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jobject jrect = (*jniEnv)->CallObjectMethod( + jniEnv, atk_text, cachedTextGetRangeExtentsMethod, (jint)start_offset, + (jint)end_offset, (jint)coord_type); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jrect == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jrect using get_range_extents method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + jaw_util_get_rect_info(jniEnv, jrect, &(rect->x), &(rect->y), + &(rect->width), &(rect->height)); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_text_get_n_selections: + * @text: an #AtkText + * + * Gets the number of selected regions. + * + * Returns: The number of selected regions, or -1 in the case of failure. + **/ +static gint jaw_text_get_n_selections(AtkText *text) { + JAW_DEBUG("%p", text); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return -1; + } + + JAW_GET_TEXT(text, -1); + + jint jselections = (*jniEnv)->CallIntMethod(jniEnv, atk_text, + cachedTextGetNSelectionsMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return -1; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return (gint)jselections; +} + +/** + * jaw_text_get_selection: + * @text: an #AtkText + * @selection_num: The selection number. The selected regions are + * assigned numbers that correspond to how far the region is from the + * start of the text. The selected region closest to the beginning + * of the text region is assigned the number 0, etc. Note that adding, + * moving or deleting a selected region can change the numbering. + * @start_offset: (out): passes back the starting character offset of the + *selected region + * @end_offset: (out): passes back the ending character offset (offset + *immediately past) of the selected region + * + * Gets the text from the specified selection. + * + * Returns: a newly allocated string containing the selected text. Use g_free() + * to free the returned string. + **/ +static gchar *jaw_text_get_selection(AtkText *text, gint selection_num, + gint *start_offset, gint *end_offset) { + JAW_DEBUG("%p, %d, %p, %p", text, selection_num, start_offset, end_offset); + + if (text == NULL || start_offset == NULL || end_offset == NULL) { + g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p", + G_STRFUNC, (void *)text, (void *)start_offset, + (void *)end_offset); + return NULL; + } + + JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + // Java AccessibleText only supports a single selection, so selection_num is + // not used. + jobject jStrSeq = (*jniEnv)->CallObjectMethod(jniEnv, atk_text, + cachedTextGetSelectionMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create jStrSeq using get_selection method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + jstring jStr = + (*jniEnv)->GetObjectField(jniEnv, jStrSeq, cachedTextStrFieldID); + if (jStr == NULL) { + g_warning("%s: Failed to get jStr field", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + *start_offset = (gint)(*jniEnv)->GetIntField(jniEnv, jStrSeq, + cachedTextStartOffsetFieldID); + *end_offset = (gint)(*jniEnv)->GetIntField(jniEnv, jStrSeq, + cachedTextEndOffsetFieldID); + + gchar *result = private_jaw_text_get_gtext_from_jstr(jniEnv, jStr); + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return result; +} + +/** + * jaw_text_add_selection: + * @text: an #AtkText + * @start_offset: the starting character offset of the selected region + * @end_offset: the offset of the first character after the selected region. + * + * Adds a selection bounded by the specified offsets. + * + * Returns: %TRUE if successful, %FALSE otherwise + **/ +static gboolean jaw_text_add_selection(AtkText *text, gint start_offset, + gint end_offset) { + JAW_DEBUG("%p, %d, %d", text, start_offset, end_offset); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_TEXT(text, FALSE); + + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_text, cachedTextAddSelectionMethod, (jint)start_offset, + (jint)end_offset); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return jresult; +} + +/** + * jaw_text_remove_selection: + * @text: an #AtkText + * @selection_num: The selection number. The selected regions are + * assigned numbers that correspond to how far the region is from the + * start of the text. The selected region closest to the beginning + * of the text region is assigned the number 0, etc. Note that adding, + * moving or deleting a selected region can change the numbering. + * + * Removes the specified selection. + * + * Returns: %TRUE if successful, %FALSE otherwise + **/ +static gboolean jaw_text_remove_selection(AtkText *text, gint selection_num) { + JAW_DEBUG("%p, %d", text, selection_num); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_TEXT(text, FALSE); + + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_text, cachedTextRemoveSelectionMethod, (jint)selection_num); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return jresult; +} + +/** + * jaw_text_set_selection: + * @text: an #AtkText + * @selection_num: The selection number. The selected regions are + * assigned numbers that correspond to how far the region is from the + * start of the text. The selected region closest to the beginning + * of the text region is assigned the number 0, etc. Note that adding, + * moving or deleting a selected region can change the numbering. + * @start_offset: the new starting character offset of the selection + * @end_offset: the new end position of (e.g. offset immediately past) + * the selection + * + * Changes the start and end offset of the specified selection. + * + * Returns: %TRUE if successful, %FALSE otherwise + **/ +static gboolean jaw_text_set_selection(AtkText *text, gint selection_num, + gint start_offset, gint end_offset) { + JAW_DEBUG("%p, %d, %d, %d", text, selection_num, start_offset, end_offset); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_TEXT(text, FALSE); + + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_text, cachedTextSetSelectionMethod, (jint)selection_num, + (jint)start_offset, (jint)end_offset); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return jresult; +} + +/** + * jaw_text_set_caret_offset: + * @text: an #AtkText + * @offset: the character offset of the new caret position + * + * Sets the caret (cursor) position to the specified @offset. + * + * In the case of rich-text content, this method should either grab focus + * or move the sequential focus navigation starting point (if the application + * supports this concept) as if the user had clicked on the new caret position. + * Typically, this means that the target of this operation is the node + *containing the new caret position or one of its ancestors. In other words, + *after this method is called, if the user advances focus, it should move to the + *first focusable node following the new caret position. + * + * Calling this method should also scroll the application viewport in a way + * that matches the behavior of the application's typical caret motion or tab + * navigation as closely as possible. This also means that if the application's + * caret motion or focus navigation does not trigger a scroll operation, this + * method should not trigger one either. If the application does not have a + *caret motion or focus navigation operation, this method should try to scroll + *the new caret position into view while minimizing unnecessary scroll motion. + * + * Returns: %TRUE if successful, %FALSE otherwise. + **/ +static gboolean jaw_text_set_caret_offset(AtkText *text, gint offset) { + JAW_DEBUG("%p, %d", text, offset); + + if (text == NULL) { + g_warning("%s: Null argument text passed to the function", G_STRFUNC); + return FALSE; + } + + JAW_GET_TEXT(text, FALSE); + + jboolean jresult = (*jniEnv)->CallBooleanMethod( + jniEnv, atk_text, cachedTextSetCaretOffsetMethod, (jint)offset); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + return FALSE; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_text); + + return jresult; +} + +static gboolean jaw_text_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClassAtkText = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkText"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClassAtkText == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkText class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTextAtkTextClass = (*jniEnv)->NewGlobalRef(jniEnv, localClassAtkText); + (*jniEnv)->DeleteLocalRef(jniEnv, localClassAtkText); + + if (cachedTextAtkTextClass == NULL) { + g_warning("%s: Failed to create global reference for AtkText class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTextCreateAtkTextMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedTextAtkTextClass, "create_atk_text", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkText;"); + + cachedTextGetTextMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_text", "(II)Ljava/lang/String;"); + + cachedTextGetCharacterAtOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_character_at_offset", "(I)C"); + + cachedTextGetTextAfterOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_text_after_offset", + "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;"); + + cachedTextGetTextAtOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_text_at_offset", + "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;"); + + cachedTextGetTextBeforeOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_text_before_offset", + "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;"); + + cachedTextGetStringAtOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_string_at_offset", + "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;"); + + cachedTextGetCaretOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_caret_offset", "()I"); + + cachedTextGetCharacterExtentsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_character_extents", + "(II)Ljava/awt/Rectangle;"); + + cachedTextGetCharacterCountMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_character_count", "()I"); + + cachedTextGetOffsetAtPointMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_offset_at_point", "(III)I"); + + cachedTextGetRangeExtentsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_range_extents", + "(III)Ljava/awt/Rectangle;"); + + cachedTextGetNSelectionsMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_n_selections", "()I"); + + cachedTextGetSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "get_selection", + "()Lorg/GNOME/Accessibility/AtkText$StringSequence;"); + + cachedTextAddSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "add_selection", "(II)Z"); + + cachedTextRemoveSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "remove_selection", "(I)Z"); + + cachedTextSetSelectionMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "set_selection", "(III)Z"); + + cachedTextSetCaretOffsetMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedTextAtkTextClass, "set_caret_offset", "(I)Z"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedTextCreateAtkTextMethod == NULL || + cachedTextGetTextMethod == NULL || + cachedTextGetCharacterAtOffsetMethod == NULL || + cachedTextGetTextAfterOffsetMethod == NULL || + cachedTextGetTextAtOffsetMethod == NULL || + cachedTextGetTextBeforeOffsetMethod == NULL || + cachedTextGetStringAtOffsetMethod == NULL || + cachedTextGetCaretOffsetMethod == NULL || + cachedTextGetCharacterExtentsMethod == NULL || + cachedTextGetCharacterCountMethod == NULL || + cachedTextGetOffsetAtPointMethod == NULL || + cachedTextGetRangeExtentsMethod == NULL || + cachedTextGetNSelectionsMethod == NULL || + cachedTextGetSelectionMethod == NULL || + cachedTextAddSelectionMethod == NULL || + cachedTextRemoveSelectionMethod == NULL || + cachedTextSetSelectionMethod == NULL || + cachedTextSetCaretOffsetMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkText method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localClassStringSeq = (*jniEnv)->FindClass( + jniEnv, "org/GNOME/Accessibility/AtkText$StringSequence"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClassStringSeq == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkText$StringSequence class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTextStringSequenceClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClassStringSeq); + (*jniEnv)->DeleteLocalRef(jniEnv, localClassStringSeq); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedTextStringSequenceClass == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create global reference for " + "AtkText$StringSequence class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedTextStrFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTextStringSequenceClass, "str", "Ljava/lang/String;"); + + cachedTextStartOffsetFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTextStringSequenceClass, "start_offset", "I"); + + cachedTextEndOffsetFieldID = (*jniEnv)->GetFieldID( + jniEnv, cachedTextStringSequenceClass, "end_offset", "I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || cachedTextStrFieldID == NULL || + cachedTextStartOffsetFieldID == NULL || + cachedTextEndOffsetFieldID == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache StringSequence field IDs", G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedTextAtkTextClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextAtkTextClass); + cachedTextAtkTextClass = NULL; + } + if (cachedTextStringSequenceClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextStringSequenceClass); + cachedTextStringSequenceClass = NULL; + } + cachedTextCreateAtkTextMethod = NULL; + cachedTextGetTextMethod = NULL; + cachedTextGetCharacterAtOffsetMethod = NULL; + cachedTextGetTextAfterOffsetMethod = NULL; + cachedTextGetTextAtOffsetMethod = NULL; + cachedTextGetTextBeforeOffsetMethod = NULL; + cachedTextGetStringAtOffsetMethod = NULL; + cachedTextGetCaretOffsetMethod = NULL; + cachedTextGetCharacterExtentsMethod = NULL; + cachedTextGetCharacterCountMethod = NULL; + cachedTextGetOffsetAtPointMethod = NULL; + cachedTextGetRangeExtentsMethod = NULL; + cachedTextGetNSelectionsMethod = NULL; + cachedTextGetSelectionMethod = NULL; + cachedTextAddSelectionMethod = NULL; + cachedTextRemoveSelectionMethod = NULL; + cachedTextSetSelectionMethod = NULL; + cachedTextSetCaretOffsetMethod = NULL; + cachedTextStrFieldID = NULL; + cachedTextStartOffsetFieldID = NULL; + cachedTextEndOffsetFieldID = NULL; + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_text_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedTextAtkTextClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextAtkTextClass); + cachedTextAtkTextClass = NULL; + } + if (cachedTextStringSequenceClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextStringSequenceClass); + cachedTextStringSequenceClass = NULL; + } + cachedTextCreateAtkTextMethod = NULL; + cachedTextGetTextMethod = NULL; + cachedTextGetCharacterAtOffsetMethod = NULL; + cachedTextGetTextAfterOffsetMethod = NULL; + cachedTextGetTextAtOffsetMethod = NULL; + cachedTextGetTextBeforeOffsetMethod = NULL; + cachedTextGetStringAtOffsetMethod = NULL; + cachedTextGetCaretOffsetMethod = NULL; + cachedTextGetCharacterExtentsMethod = NULL; + cachedTextGetCharacterCountMethod = NULL; + cachedTextGetOffsetAtPointMethod = NULL; + cachedTextGetRangeExtentsMethod = NULL; + cachedTextGetNSelectionsMethod = NULL; + cachedTextGetSelectionMethod = NULL; + cachedTextAddSelectionMethod = NULL; + cachedTextRemoveSelectionMethod = NULL; + cachedTextSetSelectionMethod = NULL; + cachedTextSetCaretOffsetMethod = NULL; + cachedTextStrFieldID = NULL; + cachedTextStartOffsetFieldID = NULL; + cachedTextEndOffsetFieldID = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.c new file mode 100644 index 000000000000..7e8101e50529 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.c @@ -0,0 +1,383 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawtoplevel.h" +#include "jawutil.h" +#include +#include +#include +#include + +static void jaw_toplevel_initialize(AtkObject *accessible, gpointer data); +static void jaw_toplevel_object_finalize(GObject *obj); + +static const gchar *jaw_toplevel_get_name(AtkObject *obj); +static const gchar *jaw_toplevel_get_description(AtkObject *obj); +static gint jaw_toplevel_get_n_children(AtkObject *obj); +static gint jaw_toplevel_get_index_in_parent(AtkObject *obj); +static AtkRole jaw_toplevel_get_role(AtkObject *obj); +static AtkObject *jaw_toplevel_ref_child(AtkObject *obj, gint i); +static AtkObject *jaw_toplevel_get_parent(AtkObject *obj); + +G_DEFINE_TYPE(JawToplevel, jaw_toplevel, ATK_TYPE_OBJECT) + +static void jaw_toplevel_class_init(JawToplevelClass *klass) { + JAW_DEBUG("%p", klass); + + if (klass == NULL) { + g_warning("%s: Null argument klass passed to the function", G_STRFUNC); + return; + } + + AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS(klass); + GObjectClass *g_object_class = G_OBJECT_CLASS(klass); + + atk_object_class->initialize = jaw_toplevel_initialize; + atk_object_class->get_name = jaw_toplevel_get_name; + atk_object_class->get_description = jaw_toplevel_get_description; + atk_object_class->get_n_children = jaw_toplevel_get_n_children; + atk_object_class->get_index_in_parent = jaw_toplevel_get_index_in_parent; + atk_object_class->get_role = jaw_toplevel_get_role; + atk_object_class->ref_child = jaw_toplevel_ref_child; + atk_object_class->get_parent = jaw_toplevel_get_parent; + + g_object_class->finalize = jaw_toplevel_object_finalize; +} + +static void jaw_toplevel_init(JawToplevel *toplevel) { + JAW_DEBUG("%p", toplevel); + + if (toplevel == NULL) { + g_warning("%s: Null argument toplevel passed to the function", + G_STRFUNC); + return; + } + + g_mutex_init(&toplevel->mutex); + toplevel->windows = NULL; +} + +static void jaw_toplevel_initialize(AtkObject *accessible, gpointer data) { + JAW_DEBUG("%p, %p", accessible, data); + + if (accessible == NULL) { + g_warning("%s: Null argument accessible passed to the function", + G_STRFUNC); + return; + } + + ATK_OBJECT_CLASS(jaw_toplevel_parent_class)->initialize(accessible, data); +} + +static void jaw_toplevel_object_finalize(GObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return; + } + + JawToplevel *jaw_toplevel = JAW_TOPLEVEL(obj); + + if (jaw_toplevel != NULL) { + g_mutex_lock(&jaw_toplevel->mutex); + if (jaw_toplevel->windows != NULL) { + g_list_free(jaw_toplevel->windows); + jaw_toplevel->windows = NULL; + } + g_mutex_unlock(&jaw_toplevel->mutex); + g_mutex_clear(&jaw_toplevel->mutex); + } + + G_OBJECT_CLASS(jaw_toplevel_parent_class)->finalize(obj); +} + +/** + * jaw_toplevel_get_name: + * @obj: an #AtkObject + * + * Gets the accessible name of the obj. + * + * Returns: a character string representing the accessible name of the object: + * the name of the first named child of the object, and if no such child exists, + * returns 'Java Application'. + **/ +static const gchar *jaw_toplevel_get_name(AtkObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return NULL; + } + + gint n_accessible_children = atk_object_get_n_accessible_children(obj); + for (gint i = 0; i < n_accessible_children; i++) { + // The caller of the method `atk_object_ref_accessible_child` takes + // ownership of the returned data, and is responsible for freeing it, so + // child must be freed. + AtkObject *child = atk_object_ref_accessible_child(obj, i); + if (child != NULL) { + const gchar *name = atk_object_get_name(child); + if (name && strlen(name) > 0) { + g_object_unref(G_OBJECT(child)); + return name; + } + g_object_unref(G_OBJECT(child)); + } + } + + return "Java Application"; +} + +/** + * jaw_toplevel_get_description: + * @accessible: an #AtkObject + * + * Gets the accessible description of the accessible. + * + * Returns: a character string representing the accessible description + * of the accessible. + * + **/ +static const gchar *jaw_toplevel_get_description(AtkObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return NULL; + } + + return "Accessible Java application"; +} + +/** + * jaw_toplevel_get_n_children: + * @obj: an #AtkObject + * + * Gets the number of accessible children of the obj. + * + * Returns: an integer representing the number of accessible children + * of the obj. + **/ +static gint jaw_toplevel_get_n_children(AtkObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return 0; + } + + JawToplevel *jaw_toplevel = JAW_TOPLEVEL(obj); + if (jaw_toplevel == NULL) { + g_warning("%s: jaw_toplevel is NULL", G_STRFUNC); + return 0; + } + + g_mutex_lock(&jaw_toplevel->mutex); + if (jaw_toplevel->windows == NULL) { + g_mutex_unlock(&jaw_toplevel->mutex); + return 0; + } + gint children_number = g_list_length(jaw_toplevel->windows); + g_mutex_unlock(&jaw_toplevel->mutex); + + return children_number; +} + +/** + * jaw_toplevel_get_index_in_parent: + * @obj: an #AtkObject + * + * Gets the 0-based index of this obj in its parent; returns -1 if the + * obj does not have an accessible parent. + * + * Returns: an integer which is the index of the obj in its parent + **/ +static gint jaw_toplevel_get_index_in_parent(AtkObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument passed to the function", G_STRFUNC); + return -1; + } + + // toplevel object does not have parent + return -1; +} + +/** + * jaw_toplevel_get_role: + * @obj: an #AtkObject + * + * Gets the role of the accessible. + * + * Returns: an #AtkRole which is the role of the obj (`ATK_ROLE_APPLICATION` for + * toplevel object) + **/ +static AtkRole jaw_toplevel_get_role(AtkObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return ATK_ROLE_INVALID; + } + + return ATK_ROLE_APPLICATION; +} + +/** + * atk_object_ref_accessible_child: + * @obj: an #AtkObject + * @i: a gint representing the position of the child, starting from 0 + * + * Gets a reference to the specified accessible child of the object. + * The accessible children are 0-based so the first accessible child is + * at index 0, the second at index 1 and so on. + * + * Returns: (transfer full): an #AtkObject representing the specified + * accessible child of the obj. + **/ +static AtkObject *jaw_toplevel_ref_child(AtkObject *obj, gint i) { + JAW_DEBUG("%p, %d", obj, i); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return NULL; + } + + JawToplevel *jaw_toplevel = JAW_TOPLEVEL(obj); + if (jaw_toplevel == NULL) { + g_warning("%s: jaw_toplevel is NULL", G_STRFUNC); + return NULL; + } + + g_mutex_lock(&jaw_toplevel->mutex); + if (jaw_toplevel->windows == NULL) { + g_mutex_unlock(&jaw_toplevel->mutex); + return NULL; + } + AtkObject *child = (AtkObject *)g_list_nth_data(jaw_toplevel->windows, i); + + // The caller of the `jaw_toplevel_ref_child` will be responsible for + // freeing the 'child' (because of transfer full annotation) + if (child != NULL) { + g_object_ref(G_OBJECT(child)); + } + g_mutex_unlock(&jaw_toplevel->mutex); + + return child; +} + +/** + * atk_object_get_parent: + * @obj: an #AtkObject + * + * Gets the accessible parent of the accessible. + * + * Returns: (transfer none): an #AtkObject representing the accessible + * parent of the obj + **/ +static AtkObject *jaw_toplevel_get_parent(AtkObject *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + } + + return NULL; +} + +// JawToplevel methods + +gint jaw_toplevel_add_window(JawToplevel *toplevel, AtkObject *child) { + JAW_DEBUG("%p, %p", toplevel, child); + + if (toplevel == NULL || child == NULL) { + g_warning("%s: invalid argument(s) : toplevel=%p, child=%p", G_STRFUNC, + toplevel, child); + return -1; + } + + g_mutex_lock(&toplevel->mutex); + if (toplevel->windows != NULL && + g_list_index(toplevel->windows, child) != -1) { + g_mutex_unlock(&toplevel->mutex); + return -1; + } + + toplevel->windows = g_list_append(toplevel->windows, child); + gint index = g_list_index(toplevel->windows, child); + g_mutex_unlock(&toplevel->mutex); + + return index; +} + +gint jaw_toplevel_remove_window(JawToplevel *toplevel, AtkObject *child) { + JAW_DEBUG("%p, %p", toplevel, child); + + if (toplevel == NULL || child == NULL) { + g_warning("%s: invalid argument(s) : toplevel=%p, child=%p", G_STRFUNC, + toplevel, child); + return -1; + } + + g_mutex_lock(&toplevel->mutex); + if (toplevel->windows == NULL) { + g_warning( + "%s: Cannot remove window %p: the toplevel window list is NULL " + "(not initialized or already cleared)", + G_STRFUNC, child); + g_mutex_unlock(&toplevel->mutex); + return -1; + } + + gint index = (g_list_index(toplevel->windows, child)); + if (index == -1) { + g_warning("%s: Cannot remove window %p: it is not present in the " + "toplevel window list", + G_STRFUNC, child); + g_mutex_unlock(&toplevel->mutex); + return -1; + } + + toplevel->windows = g_list_remove(toplevel->windows, child); + g_mutex_unlock(&toplevel->mutex); + + return index; +} + +gint jaw_toplevel_get_child_index(JawToplevel *toplevel, AtkObject *child) { + JAW_DEBUG("%p, %p", toplevel, child); + + if (toplevel == NULL || child == NULL) { + g_warning("%s: invalid argument(s) : toplevel=%p, child=%p", G_STRFUNC, + toplevel, child); + return -1; + } + + g_mutex_lock(&toplevel->mutex); + if (toplevel->windows == NULL) { + g_mutex_unlock(&toplevel->mutex); + return -1; + } + + gint index = g_list_index(toplevel->windows, child); + g_mutex_unlock(&toplevel->mutex); + return index; +} diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.h new file mode 100644 index 000000000000..d457634ab632 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.h @@ -0,0 +1,60 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _JAW_TOPLEVEL_H_ +#define _JAW_TOPLEVEL_H_ + +#include + +G_BEGIN_DECLS + +#define JAW_TYPE_TOPLEVEL (jaw_toplevel_get_type()) +#define JAW_TOPLEVEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_TOPLEVEL, JawToplevel)) +#define JAW_TOPLEVEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_TOPLEVEL, JawToplevelClass)) +#define JAW_IS_TOPLEVEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_TOPLEVEL)) +#define JAW_IS_TOPLEVEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_TOPLEVEL)) +#define JAW_TOPLEVEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_TOPLEVEL, JawToplevelClass)) + +typedef struct _JawToplevel JawToplevel; +typedef struct _JawToplevelClass JawToplevelClass; + +struct _JawToplevel { + AtkObject parent; + GList *windows; + GMutex mutex; +}; + +GType jaw_toplevel_get_type(void); + +struct _JawToplevelClass { + AtkObjectClass parent_class; +}; + +gint jaw_toplevel_add_window(JawToplevel *, AtkObject *); +gint jaw_toplevel_remove_window(JawToplevel *, AtkObject *); +gint jaw_toplevel_get_child_index(JawToplevel *, AtkObject *); + +G_END_DECLS + +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.c new file mode 100644 index 000000000000..b0a012acaa0a --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.c @@ -0,0 +1,1213 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawutil.h" +#include "jawcache.h" +#include "jawobject.h" +#include "jawtoplevel.h" +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static void jaw_util_class_init(JawUtilClass *klass, void *klass_data); +static guint jaw_util_add_key_event_listener(AtkKeySnoopFunc listener, + gpointer data); +static void jaw_util_remove_key_event_listener(guint remove_listener); +static AtkObject *jaw_util_get_root(void); +static const gchar *jaw_util_get_toolkit_name(void); +static const gchar *jaw_util_get_toolkit_version(void); + +static JavaVM *cachedJVM = NULL; + +static jclass cachedUtilAtkObjectClass = NULL; +static jclass cachedUtilAccessibleRoleClass = NULL; +static jclass cachedUtilAccessibleStateClass = NULL; +static jclass cachedUtilRectangleClass = NULL; +static jmethodID cachedUtilGetTflagFromObjMethod = NULL; +static jmethodID cachedUtilGetAccessibleRoleMethod = NULL; +static jmethodID cachedUtilGetAccessibleParentMethod = NULL; +static jfieldID cachedUtilRectangleXField = NULL; +static jfieldID cachedUtilRectangleYField = NULL; +static jfieldID cachedUtilRectangleWidthField = NULL; +static jfieldID cachedUtilRectangleHeightField = NULL; + +static GMutex cache_mutex; +static gboolean jawutil_cache_initialized = FALSE; + +static gboolean jawutil_init_jni_cache(JNIEnv *jniEnv); + +typedef struct _JawKeyListenerInfo { + AtkKeySnoopFunc listener; + gpointer data; // data that should be sent to the listener +} JawKeyListenerInfo; + +// Maps unique keys to `JawKeyListenerInfo` +static GHashTable *key_listener_list = NULL; + +GType jaw_util_get_type(void) { + JAW_DEBUG(""); + static GType type = 0; + + if (!type) { + static const GTypeInfo tinfo = { + sizeof(JawUtilClass), + (GBaseInitFunc)NULL, /*base init*/ + (GBaseFinalizeFunc)NULL, /*base finalize */ + (GClassInitFunc)jaw_util_class_init, /* class init */ + (GClassFinalizeFunc)NULL, /*class finalize */ + NULL, /* class data */ + sizeof(JawUtil), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc)NULL, /* instance init */ + NULL /* value table */ + }; + + type = g_type_register_static(ATK_TYPE_UTIL, "JawUtil", &tinfo, 0); + } + + return type; +} + +static void jaw_util_class_init(JawUtilClass *kclass, void *klass_data) { + JAW_DEBUG("%p, %p", kclass, klass_data); + + AtkUtilClass *atk_class; + gpointer data; + + data = g_type_class_peek(ATK_TYPE_UTIL); + atk_class = ATK_UTIL_CLASS(data); + + atk_class->add_key_event_listener = jaw_util_add_key_event_listener; + atk_class->remove_key_event_listener = jaw_util_remove_key_event_listener; + atk_class->get_root = jaw_util_get_root; + atk_class->get_toolkit_name = jaw_util_get_toolkit_name; + atk_class->get_toolkit_version = jaw_util_get_toolkit_version; +} + +/** + * Notifies a key event to the registered key listener. + * + * @param key + * @param value Pointer to the `JawKeyListenerInfo` + * @param data Pointer to the `AtkKeyEventStruct` + * + * @return TRUE if the listener processes the event successfully, + * FALSE if any argument is null or the listener returns FALSE. + */ +static gboolean notify_hf(gpointer key, gpointer value, gpointer data) { + JAW_DEBUG("%p, %p, %p", key, value, data); + + if (value == NULL || data == NULL) { + g_warning("%s: Null argument passed. value=%p, data=%p", G_STRFUNC, + (void *)value, (void *)data); + return FALSE; + } + + JawKeyListenerInfo *info = (JawKeyListenerInfo *)value; + if (info == NULL) { + g_warning("%s: info is NULL", G_STRFUNC); + return FALSE; + } + AtkKeyEventStruct *key_event = (AtkKeyEventStruct *)data; + if (key_event == NULL) { + g_warning("%s: key_event is NULL", G_STRFUNC); + return FALSE; + } + + AtkKeySnoopFunc func = info->listener; + gpointer func_data = info->data; + + JAW_DEBUG("key event %d %x %x %d '%s' %u %u", key_event->type, + key_event->state, key_event->keyval, key_event->length, + key_event->string, (unsigned)key_event->keycode, + (unsigned)key_event->timestamp); + + return (*func)(key_event, func_data) ? TRUE : FALSE; +} + +/** + * Inserts key value pair into a hash table. + * + * @param key Pointer to the hash table's key + * @param value Pointer to the associated value + * @param data Pointer to the `GHashTable` + */ +static void insert_hf(gpointer key, gpointer value, gpointer data) { + JAW_DEBUG("%p, %p, %p", key, value, data); + + if (key == NULL || value == NULL || data == NULL) { + g_warning("%s: Null argument passed. key=%p, value=%p, data=%p", + G_STRFUNC, (void *)key, (void *)value, (void *)data); + return; + } + + GHashTable *hash_table = (GHashTable *)data; + if (hash_table == NULL) { + g_warning("%s: hash_table is NULL", G_STRFUNC); + return; + } + g_hash_table_insert(hash_table, key, value); +} + +gboolean jaw_util_dispatch_key_event(AtkKeyEventStruct *event) { + JAW_DEBUG("%p", event); + + if (event == NULL) { + g_warning("%s: Null argument event passed to the function", G_STRFUNC); + return FALSE; + } + + gint consumed = 0; + if (key_listener_list != NULL) { + GHashTable *new_hash = g_hash_table_new(NULL, NULL); + if (new_hash != NULL) { + g_hash_table_foreach(key_listener_list, insert_hf, new_hash); + consumed = g_hash_table_foreach_steal(new_hash, notify_hf, event); + g_hash_table_destroy(new_hash); + } + } + g_debug("%s: consumed: %d", G_STRFUNC, consumed); + + return (consumed > 0) ? TRUE : FALSE; +} + +/** + * jaw_util_add_key_event_listener: + * @listener: the listener to notify + * @data: a #gpointer that points to a block of data that should be sent to the + *registered listeners, along with the event notification, when it occurs. + * + * Adds the specified function to the list of functions to be called + * when a key event occurs. The @data element will be passed to the + * #AtkKeySnoopFunc (@listener) as the @func_data param, on notification. + * + * Returns: added event listener id, or 0 on failure. + **/ +static guint jaw_util_add_key_event_listener(AtkKeySnoopFunc listener, + gpointer data) { + JAW_DEBUG("%p, %p", listener, data); + + if (listener == NULL) { + g_warning("%s: Null argument listener passed to the function", + G_STRFUNC); + return 0; + } + + static guint key = 0; + + if (key_listener_list == NULL) { + key_listener_list = g_hash_table_new(NULL, NULL); + } + + JawKeyListenerInfo *info = g_new0(JawKeyListenerInfo, 1); + info->listener = listener; + info->data = data; + + key++; + g_hash_table_insert(key_listener_list, GUINT_TO_POINTER(key), info); + + return key; +} + +/** + * jaw_util_remove_key_event_listener: + * @listener_id: the id of the event listener to remove + * + * @listener_id is the value returned by #atk_add_key_event_listener + * when you registered that event listener. + * + * Removes the specified event listener. + **/ +static void jaw_util_remove_key_event_listener(guint remove_listener) { + JAW_DEBUG("%u", remove_listener); + gpointer *value = g_hash_table_lookup(key_listener_list, + GUINT_TO_POINTER(remove_listener)); + if (value) { + g_free(value); + } + + g_hash_table_remove(key_listener_list, GUINT_TO_POINTER(remove_listener)); +} + +/** + * jaw_util_get_root: + * + * Gets the root accessible container for the current application. + * + * Returns: (transfer none): the root accessible container for the current + * application + **/ +static AtkObject *jaw_util_get_root(void) { + JAW_DEBUG(""); + static JawToplevel *root = NULL; + + if (root == NULL) { + root = g_object_new(JAW_TYPE_TOPLEVEL, NULL); + atk_object_initialize(ATK_OBJECT(root), NULL); + } + + return ATK_OBJECT(root); +} + +/** + * jaw_util_get_toolkit_name: + * + * Gets name string for the GUI toolkit implementing ATK for this application. + * + * Returns: name string for the GUI toolkit implementing ATK for this + *application + **/ +static const gchar *jaw_util_get_toolkit_name(void) { + JAW_DEBUG(""); + return "J2SE-access-bridge"; +} + +/** + * jaw_util_get_toolkit_version: + * + * Gets version string for the GUI toolkit implementing ATK for this + *application. + * + * Returns: version string for the GUI toolkit implementing ATK for this + *application + **/ +static const gchar *jaw_util_get_toolkit_version(void) { + JAW_DEBUG(""); + return "1.0"; +} + +guint jaw_util_get_tflag_from_jobj(JNIEnv *jniEnv, jobject jObj) { + JAW_DEBUG("%p, %p", jniEnv, jObj); + + if (jniEnv == NULL) { + g_warning("%s: Null argument jniEnv passed to the function", G_STRFUNC); + return 0; + } + + if (!jawutil_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return 0; + } + + guint result = (guint)(*jniEnv)->CallStaticIntMethod( + jniEnv, cachedUtilAtkObjectClass, cachedUtilGetTflagFromObjMethod, + jObj); + + return result; +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserve) { + JAW_DEBUG("%p, %p", jvm, reserve); + if (jvm == NULL) { + g_error("JavaVM pointer was NULL when initializing library"); + return JNI_ERR; + } + cachedJVM = jvm; + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserve) { + JAW_DEBUG("%p, %p", jvm, reserve); + + if (jvm == NULL) { + g_error("JavaVM pointer was NULL when unloading library"); + return; + } + + g_warning("JNI_OnUnload() called but this is not supported yet\n"); +} + +JNIEnv *jaw_util_get_jni_env(void) { + if (cachedJVM == NULL) { + g_warning("%s: cachedJVM == NULL", G_STRFUNC); + return NULL; + } + + JNIEnv *env; + jint res = (*cachedJVM)->GetEnv(cachedJVM, (void **)&env, JNI_VERSION_1_6); + if (res == JNI_OK && env != NULL) { + return env; + } + + switch (res) { + case JNI_EDETACHED: + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_6; + args.name = "JavaAtkWrapper-JNI-Attached-Thread"; + args.group = NULL; + res = + (*cachedJVM) + ->AttachCurrentThreadAsDaemon(cachedJVM, (void **)&env, &args); + if (res == JNI_OK && env != NULL) { + return env; + } + g_printerr("\n *** Attach failed. *** JNIEnv thread is detached.\n"); + break; + case JNI_EVERSION: + g_printerr(" *** Version error *** \n"); + break; + default: + g_printerr(" *** Unknown result %d *** \n", res); + break; + } + + fflush(stderr); + return NULL; +} + +/* Currently unused: our thread lives forever until application termination. */ +void jaw_util_detach(void) { + JAW_DEBUG(""); + JavaVM *jvm; + jvm = cachedJVM; + (*jvm)->DetachCurrentThread(jvm); +} + +static jobject jaw_util_get_java_acc_role(JNIEnv *jniEnv, + const gchar *roleName) { + JAW_DEBUG("%p, %s", jniEnv, roleName); + + if (jniEnv == NULL || roleName == NULL) { + g_warning("%s: Null argument passed (jniEnv=%p, roleName_ptr=%p)", + G_STRFUNC, (void *)jniEnv, (void *)roleName); + return NULL; + } + + if (!jawutil_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jfieldID jfid = (*jniEnv)->GetStaticFieldID( + jniEnv, cachedUtilAccessibleRoleClass, roleName, + "Ljavax/accessibility/AccessibleRole;"); + if (jfid == NULL) { + g_warning("%s: Failed to find field %s in AccessibleRole class", + G_STRFUNC, roleName); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + jobject jrole = (*jniEnv)->GetStaticObjectField( + jniEnv, cachedUtilAccessibleRoleClass, jfid); + + return (*jniEnv)->PopLocalFrame(jniEnv, jrole); +} + +static gboolean jaw_util_is_java_acc_role(JNIEnv *jniEnv, jobject acc_role, + const gchar *roleName) { + JAW_DEBUG("%p, %p, %s", jniEnv, acc_role, roleName); + + if (jniEnv == NULL || roleName == NULL) { + g_warning("%s: Null argument passed (jniEnv=%p, roleName_ptr=%p)", + G_STRFUNC, (void *)jniEnv, (void *)roleName); + return FALSE; + } + + jobject jrole = jaw_util_get_java_acc_role(jniEnv, roleName); + + if ((*jniEnv)->IsSameObject(jniEnv, acc_role, jrole)) { + return TRUE; + } else { + return FALSE; + } +} + +AtkRole +jaw_util_get_atk_role_from_AccessibleContext(jobject jAccessibleContext) { + JAW_DEBUG("%p", jAccessibleContext); + + if (jAccessibleContext == NULL) { + g_warning("%s: Null argument jAccessibleContext passed to the " + "function, return ATK_ROLE_UNKNOWN", + G_STRFUNC); + return ATK_ROLE_UNKNOWN; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is null, return ATK_ROLE_UNKNOWN", G_STRFUNC); + return ATK_ROLE_UNKNOWN; + } + + if (!jawutil_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return ATK_ROLE_UNKNOWN; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame, return " + "ATK_ROLE_UNKNOWN", + G_STRFUNC); + return ATK_ROLE_UNKNOWN; + } + + jobject ac_role = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedUtilAtkObjectClass, cachedUtilGetAccessibleRoleMethod, + jAccessibleContext); + if (ac_role == NULL) { + g_warning("%s: Failed to get accessible role from AccessibleContext", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_UNKNOWN; + } + + if (!(*jniEnv)->IsInstanceOf(jniEnv, ac_role, + cachedUtilAccessibleRoleClass)) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_INVALID; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ALERT")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_ALERT; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "AWT_COMPONENT")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_UNKNOWN; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "CANVAS")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_CANVAS; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "CHECK_BOX")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_CHECK_BOX; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "COLOR_CHOOSER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_COLOR_CHOOSER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "COLUMN_HEADER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_COLUMN_HEADER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "COMBO_BOX")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_COMBO_BOX; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DATE_EDITOR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_DATE_EDITOR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DESKTOP_ICON")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_DESKTOP_ICON; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DESKTOP_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_DESKTOP_FRAME; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DIALOG")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_DIALOG; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DIRECTORY_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_DIRECTORY_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "EDITBAR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_EDITBAR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FILE_CHOOSER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_FILE_CHOOSER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FILLER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_FILLER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FONT_CHOOSER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_FONT_CHOOSER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FOOTER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_FOOTER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FRAME")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_FRAME; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "GLASS_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_GLASS_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "GROUP_BOX")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PANEL; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "HEADER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_HEADER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "HTML_CONTAINER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_HTML_CONTAINER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "HYPERLINK")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_LINK; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ICON")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_ICON; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "INTERNAL_FRAME")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_INTERNAL_FRAME; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LABEL")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_LABEL; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LAYERED_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_LAYERED_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LIST")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_LIST; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LIST_ITEM")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_LIST_ITEM; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "MENU")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_MENU; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "MENU_BAR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_MENU_BAR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "MENU_ITEM")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_MENU_ITEM; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "OPTION_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_OPTION_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PAGE_TAB")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PAGE_TAB; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PAGE_TAB_LIST")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PAGE_TAB_LIST; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PANEL")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PANEL; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PARAGRAPH")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PARAGRAPH; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PASSWORD_TEXT")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PASSWORD_TEXT; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "POPUP_MENU")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_POPUP_MENU; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PROGRESS_BAR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PROGRESS_BAR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PUSH_BUTTON")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_PUSH_BUTTON; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "RADIO_BUTTON")) { + + jobject jparent = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedUtilAtkObjectClass, + cachedUtilGetAccessibleParentMethod, jAccessibleContext); + if (jparent == NULL) { + g_warning("%s: Failed to get accessible parent using " + "get_accessible_parent", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_RADIO_BUTTON; + } + + jobject parent_role = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedUtilAtkObjectClass, cachedUtilGetAccessibleRoleMethod, + jparent); + if (parent_role == NULL) { + g_warning("%s: Failed to get parent role using get_accessible_role", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_RADIO_BUTTON; + } + if (jaw_util_is_java_acc_role(jniEnv, parent_role, "MENU")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_RADIO_MENU_ITEM; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_RADIO_BUTTON; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ROOT_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_ROOT_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ROW_HEADER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_ROW_HEADER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "RULER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_RULER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SCROLL_BAR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_SCROLL_BAR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SCROLL_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_SCROLL_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SEPARATOR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_SEPARATOR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SLIDER")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_SLIDER; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SPIN_BOX")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_SPIN_BUTTON; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SPLIT_PANE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_SPLIT_PANE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "STATUS_BAR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_STATUSBAR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SWING_COMPONENT")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_UNKNOWN; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TABLE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_TABLE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TEXT")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_TEXT; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TOGGLE_BUTTON")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_TOGGLE_BUTTON; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TOOL_BAR")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_TOOL_BAR; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TOOL_TIP")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_TOOL_TIP; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TREE")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_TREE; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "UNKNOWN")) { + jobject jparent = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedUtilAtkObjectClass, + cachedUtilGetAccessibleParentMethod, jAccessibleContext); + if (jparent == NULL) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_APPLICATION; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_ROLE_UNKNOWN; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "VIEWPORT")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_VIEWPORT; + } + + if (jaw_util_is_java_acc_role(jniEnv, ac_role, "WINDOW")) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return ATK_ROLE_WINDOW; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return ATK_ROLE_UNKNOWN; /* ROLE_EXTENDED */ +} + +static gboolean is_same_java_state(JNIEnv *jniEnv, jobject jobj, + const gchar *strState) { + if (jniEnv == NULL || strState == NULL) { + g_warning("%s: Null argument passed. jniEnv=%p, strState=%p", G_STRFUNC, + (void *)jniEnv, (void *)strState); + return FALSE; + } + + if (!jawutil_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return FALSE; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return FALSE; + } + + jfieldID jfid = (*jniEnv)->GetStaticFieldID( + jniEnv, cachedUtilAccessibleStateClass, strState, + "Ljavax/accessibility/AccessibleState;"); + if (jfid == NULL) { + g_warning("%s: Failed to find field %s in AccessibleState class", + G_STRFUNC, strState); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; + } + jobject jstate = (*jniEnv)->GetStaticObjectField( + jniEnv, cachedUtilAccessibleStateClass, jfid); + if (jstate == NULL) { + g_warning("%s: Failed to get static object field", G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return FALSE; + } + if ((*jniEnv)->IsSameObject(jniEnv, jobj, jstate)) { + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return TRUE; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return FALSE; +} + +AtkStateType jaw_util_get_atk_state_type_from_java_state(JNIEnv *jniEnv, + jobject jobj) { + if (jniEnv == NULL) { + g_warning("%s: Null argument jniEnv passed to the function", G_STRFUNC); + return ATK_STATE_INVALID; + } + + if (is_same_java_state(jniEnv, jobj, "ACTIVE")) + return ATK_STATE_ACTIVE; + + if (is_same_java_state(jniEnv, jobj, "ARMED")) + return ATK_STATE_ARMED; + + if (is_same_java_state(jniEnv, jobj, "BUSY")) + return ATK_STATE_BUSY; + + if (is_same_java_state(jniEnv, jobj, "CHECKED")) + return ATK_STATE_CHECKED; + + if (is_same_java_state(jniEnv, jobj, "COLLAPSED")) +#if ATK_CHECK_VERSION(2, 38, 0) + return ATK_STATE_COLLAPSED; +#else + return ATK_STATE_INVALID; +#endif + + if (is_same_java_state(jniEnv, jobj, "EDITABLE")) + return ATK_STATE_EDITABLE; + + if (is_same_java_state(jniEnv, jobj, "ENABLED")) + return ATK_STATE_ENABLED; + + if (is_same_java_state(jniEnv, jobj, "EXPANDABLE")) + return ATK_STATE_EXPANDABLE; + + if (is_same_java_state(jniEnv, jobj, "EXPANDED")) + return ATK_STATE_EXPANDED; + + if (is_same_java_state(jniEnv, jobj, "FOCUSABLE")) + return ATK_STATE_FOCUSABLE; + + if (is_same_java_state(jniEnv, jobj, "FOCUSED")) + return ATK_STATE_FOCUSED; + + if (is_same_java_state(jniEnv, jobj, "HORIZONTAL")) + return ATK_STATE_HORIZONTAL; + + if (is_same_java_state(jniEnv, jobj, "ICONIFIED")) + return ATK_STATE_ICONIFIED; + + if (is_same_java_state(jniEnv, jobj, "INDETERMINATE")) + return ATK_STATE_INDETERMINATE; + + if (is_same_java_state(jniEnv, jobj, "MANAGES_DESCENDANTS")) + return ATK_STATE_MANAGES_DESCENDANTS; + + if (is_same_java_state(jniEnv, jobj, "MODAL")) + return ATK_STATE_MODAL; + + if (is_same_java_state(jniEnv, jobj, "MULTI_LINE")) + return ATK_STATE_MULTI_LINE; + + if (is_same_java_state(jniEnv, jobj, "MULTISELECTABLE")) + return ATK_STATE_MULTISELECTABLE; + + if (is_same_java_state(jniEnv, jobj, "OPAQUE")) + return ATK_STATE_OPAQUE; + + if (is_same_java_state(jniEnv, jobj, "PRESSED")) + return ATK_STATE_PRESSED; + + if (is_same_java_state(jniEnv, jobj, "RESIZABLE")) + return ATK_STATE_RESIZABLE; + + if (is_same_java_state(jniEnv, jobj, "SELECTABLE")) + return ATK_STATE_SELECTABLE; + + if (is_same_java_state(jniEnv, jobj, "SELECTED")) + return ATK_STATE_SELECTED; + + if (is_same_java_state(jniEnv, jobj, "SHOWING")) + return ATK_STATE_SHOWING; + + if (is_same_java_state(jniEnv, jobj, "SINGLE_LINE")) + return ATK_STATE_SINGLE_LINE; + + if (is_same_java_state(jniEnv, jobj, "TRANSIENT")) + return ATK_STATE_TRANSIENT; + + if (is_same_java_state(jniEnv, jobj, "TRUNCATED")) + return ATK_STATE_TRUNCATED; + + if (is_same_java_state(jniEnv, jobj, "VERTICAL")) + return ATK_STATE_VERTICAL; + + if (is_same_java_state(jniEnv, jobj, "VISIBLE")) + return ATK_STATE_VISIBLE; + + return ATK_STATE_INVALID; +} + +void jaw_util_get_rect_info(JNIEnv *jniEnv, jobject jrect, gint *x, gint *y, + gint *width, gint *height) { + JAW_DEBUG("%p, %p, %p, %p, %p, %p", jniEnv, jrect, x, y, width, height); + + if (jniEnv == NULL || x == NULL || y == NULL || width == NULL || + height == NULL || jrect == NULL) { + g_warning("%s: Null argument passed " + "(jniEnv=%p, x=%p, y=%p, width=%p, height=%p, jrect=%p)", + G_STRFUNC, (void *)jniEnv, (void *)x, (void *)y, + (void *)width, (void *)height, (void *)jrect); + return; + } + + if (!jawutil_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return; + } + + (*x) = + (gint)(*jniEnv)->GetIntField(jniEnv, jrect, cachedUtilRectangleXField); + (*y) = + (gint)(*jniEnv)->GetIntField(jniEnv, jrect, cachedUtilRectangleYField); + (*width) = (gint)(*jniEnv)->GetIntField(jniEnv, jrect, + cachedUtilRectangleWidthField); + (*height) = (gint)(*jniEnv)->GetIntField(jniEnv, jrect, + cachedUtilRectangleHeightField); +} + +static gboolean jawutil_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (jawutil_cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localAtkObject = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkObject"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkObject == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkObject class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedUtilAtkObjectClass = (*jniEnv)->NewGlobalRef(jniEnv, localAtkObject); + (*jniEnv)->DeleteLocalRef(jniEnv, localAtkObject); + + if (cachedUtilAtkObjectClass == NULL) { + g_warning("%s: Failed to create global reference for AtkObject class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localAccessibleRole = + (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleRole"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleRole == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AccessibleRole class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedUtilAccessibleRoleClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleRole); + (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleRole); + + if (cachedUtilAccessibleRoleClass == NULL) { + g_warning( + "%s: Failed to create global reference for AccessibleRole class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localAccessibleState = + (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleState"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleState == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AccessibleState class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedUtilAccessibleStateClass = + (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleState); + (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleState); + + if (cachedUtilAccessibleStateClass == NULL) { + g_warning( + "%s: Failed to create global reference for AccessibleState class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localRectangle = (*jniEnv)->FindClass(jniEnv, "java/awt/Rectangle"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localRectangle == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Rectangle class", G_STRFUNC); + goto cleanup_and_fail; + } + + cachedUtilRectangleClass = (*jniEnv)->NewGlobalRef(jniEnv, localRectangle); + (*jniEnv)->DeleteLocalRef(jniEnv, localRectangle); + + if (cachedUtilRectangleClass == NULL) { + g_warning("%s: Failed to create global reference for Rectangle class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedUtilGetTflagFromObjMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedUtilAtkObjectClass, "get_tflag_from_obj", + "(Ljava/lang/Object;)I"); + + cachedUtilGetAccessibleRoleMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedUtilAtkObjectClass, "get_accessible_role", + "(Ljavax/accessibility/AccessibleContext;)Ljavax/accessibility/" + "AccessibleRole;"); + + cachedUtilGetAccessibleParentMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedUtilAtkObjectClass, "get_accessible_parent", + "(Ljavax/accessibility/AccessibleContext;)Ljavax/accessibility/" + "AccessibleContext;"); + + cachedUtilRectangleXField = + (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "x", "I"); + cachedUtilRectangleYField = + (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "y", "I"); + cachedUtilRectangleWidthField = + (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "width", "I"); + cachedUtilRectangleHeightField = + (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "height", "I"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedUtilGetTflagFromObjMethod == NULL || + cachedUtilGetAccessibleRoleMethod == NULL || + cachedUtilGetAccessibleParentMethod == NULL || + cachedUtilRectangleXField == NULL || + cachedUtilRectangleYField == NULL || + cachedUtilRectangleWidthField == NULL || + cachedUtilRectangleHeightField == NULL) { + + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to cache one or more method/field IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + jawutil_cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedUtilAtkObjectClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAtkObjectClass); + cachedUtilAtkObjectClass = NULL; + } + if (cachedUtilAccessibleRoleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleRoleClass); + cachedUtilAccessibleRoleClass = NULL; + } + if (cachedUtilAccessibleStateClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleStateClass); + cachedUtilAccessibleStateClass = NULL; + } + if (cachedUtilRectangleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilRectangleClass); + cachedUtilRectangleClass = NULL; + } + cachedUtilGetTflagFromObjMethod = NULL; + cachedUtilGetAccessibleRoleMethod = NULL; + cachedUtilGetAccessibleParentMethod = NULL; + cachedUtilRectangleXField = NULL; + cachedUtilRectangleYField = NULL; + cachedUtilRectangleWidthField = NULL; + cachedUtilRectangleHeightField = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_util_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedUtilAtkObjectClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAtkObjectClass); + cachedUtilAtkObjectClass = NULL; + } + if (cachedUtilAccessibleRoleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleRoleClass); + cachedUtilAccessibleRoleClass = NULL; + } + if (cachedUtilAccessibleStateClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleStateClass); + cachedUtilAccessibleStateClass = NULL; + } + if (cachedUtilRectangleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilRectangleClass); + cachedUtilRectangleClass = NULL; + } + cachedUtilGetTflagFromObjMethod = NULL; + cachedUtilGetAccessibleRoleMethod = NULL; + cachedUtilGetAccessibleParentMethod = NULL; + cachedUtilRectangleXField = NULL; + cachedUtilRectangleYField = NULL; + cachedUtilRectangleWidthField = NULL; + cachedUtilRectangleHeightField = NULL; + jawutil_cache_initialized = FALSE; + g_mutex_unlock(&cache_mutex); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.h new file mode 100644 index 000000000000..6f114b13fe35 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.h @@ -0,0 +1,128 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _JAW_UTIL_H_ +#define _JAW_UTIL_H_ + +#include +#include +#include +#include +#include + +extern int jaw_debug; +extern FILE *jaw_log_file; +extern time_t jaw_start_time; + +#define PRINT_AND_FLUSH(fmt, ...) \ + do { \ + fprintf(jaw_log_file, "[%lu] %s" fmt "\n", \ + (unsigned long)(time(NULL) - jaw_start_time), __func__, \ + ##__VA_ARGS__); \ + fflush(jaw_log_file); \ + } while (0) + +#define JAW_DEBUG(fmt, ...) \ + do { \ + if (jaw_debug) { \ \ + PRINT_AND_FLUSH("(" fmt ")", ##__VA_ARGS__); \ + } \ + } while (0) + +G_BEGIN_DECLS + +#define JAW_TYPE_UTIL (jaw_util_get_type()) +#define JAW_UTIL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_UTIL, JawUtil)) +#define JAW_UTIL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_UTIL, JawUtilClass)) +#define JAW_IS_UTIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_UTIL)) +#define JAW_IS_UTIL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_UTIL)) +#define JAW_UTIL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_UTIL, JawUtilClass)) + +typedef struct _JawUtil JawUtil; +typedef struct _JawUtilClass JawUtilClass; + +struct _JawUtil { + AtkUtil parent; +}; + +GType jaw_util_get_type(void); + +struct _JawUtilClass { + AtkUtilClass parent_class; +}; + +guint jaw_util_get_tflag_from_jobj(JNIEnv *jniEnv, jobject jObj); +JNIEnv *jaw_util_get_jni_env(void); +AtkRole jaw_util_get_atk_role_from_AccessibleContext(jobject jobj); +AtkStateType jaw_util_get_atk_state_type_from_java_state(JNIEnv *jniEnv, + jobject jobj); +void jaw_util_get_rect_info(JNIEnv *jniEnv, jobject jrect, gint *x, gint *y, + gint *width, gint *height); +gboolean jaw_util_dispatch_key_event(AtkKeyEventStruct *event); + +void jaw_util_detach(void); + +#define JAW_DEFAULT_LOCAL_FRAME_SIZE 10 + +#define JAW_GET_OBJ_IFACE(o, iface, Data, field, env, name, def_ret) \ + JawObject *jaw_obj = JAW_OBJECT(o); \ + if (!jaw_obj) { \ + g_warning("%s: jaw_obj == NULL in JAW_GET_OBJ_IFACE", G_STRFUNC); \ + return def_ret; \ + } \ + Data *data = jaw_object_get_interface_data(jaw_obj, iface); \ + JNIEnv *env = jaw_util_get_jni_env(); \ + if (!env) { \ + g_warning(#env " == NULL"); \ + return def_ret; \ + } \ + jobject name = (*env)->NewLocalRef(env, data->field); \ + if (!name) { \ + g_warning(#name " == NULL"); \ + return def_ret; \ + } + +#define JAW_GET_OBJ(o, CAST, JawObject, object_name, field, env, name, \ + def_ret) \ + JawObject *object_name = CAST(o); \ + if (!object_name) { \ + g_warning(#object_name " == NULL"); \ + return def_ret; \ + } \ + JNIEnv *env = jaw_util_get_jni_env(); \ + jobject name = (*env)->NewLocalRef(env, object_name->field); \ + if (!name) { \ + g_warning(#name " == NULL"); \ + return def_ret; \ + } + +static inline void jaw_jni_clear_exception(JNIEnv *env) { + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +G_END_DECLS + +#endif diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawvalue.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawvalue.c new file mode 100644 index 000000000000..7628e808bf03 --- /dev/null +++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawvalue.c @@ -0,0 +1,790 @@ +/* + * Java ATK Wrapper for GNOME + * Copyright (C) 2009 Sun Microsystems Inc. + * Copyright (C) 2015 Magdalen Berns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "jawcache.h" +#include "jawimpl.h" +#include "jawutil.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static jclass cachedValueAtkValueClass = NULL; +static jmethodID cachedValueCreateAtkValueMethod = NULL; +static jmethodID cachedValueGetCurrentValueMethod = NULL; +static jmethodID cachedValueSetValueMethod = NULL; +static jmethodID cachedValueGetMinimumValueMethod = NULL; +static jmethodID cachedValueGetMaximumValueMethod = NULL; +static jmethodID cachedValueGetIncrementMethod = NULL; +static jclass cachedValueByteClass = NULL; +static jclass cachedValueDoubleClass = NULL; +static jclass cachedValueFloatClass = NULL; +static jclass cachedValueIntegerClass = NULL; +static jclass cachedValueLongClass = NULL; +static jclass cachedValueShortClass = NULL; +static jmethodID cachedValueByteValueMethod = NULL; +static jmethodID cachedValueDoubleValueMethod = NULL; +static jmethodID cachedValueFloatValueMethod = NULL; +static jmethodID cachedValueIntValueMethod = NULL; +static jmethodID cachedValueLongValueMethod = NULL; +static jmethodID cachedValueShortValueMethod = NULL; +static jmethodID cachedValueDoubleConstructorMethod = NULL; + +static GMutex cache_mutex; +static gboolean cache_initialized = FALSE; + +static gboolean jaw_value_init_jni_cache(JNIEnv *jniEnv); + +static void jaw_value_get_current_value(AtkValue *obj, GValue *value); +static void jaw_value_set_value(AtkValue *obj, const gdouble value); +static gdouble jaw_value_get_increment(AtkValue *obj); +static AtkRange *jaw_value_get_range(AtkValue *obj); + +typedef struct _ValueData { + jobject atk_value; +} ValueData; + +#define JAW_GET_VALUE(obj, def_ret) \ + JAW_GET_OBJ_IFACE(obj, \ + org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE, \ + ValueData, atk_value, jniEnv, atk_value, def_ret) + +void jaw_value_interface_init(AtkValueIface *iface, gpointer data) { + JAW_DEBUG("%p, %p", iface, data); + + if (iface == NULL) { + g_warning("%s: Null argument iface passed to the function", G_STRFUNC); + return; + } + + iface->get_current_value = jaw_value_get_current_value; + iface->get_maximum_value = NULL; // deprecated: iface->get_maximum_value + iface->get_minimum_value = NULL; // deprecated: iface->get_minimum_value + iface->set_current_value = NULL; // deprecated: iface->set_current_value + iface->get_minimum_increment = + NULL; // deprecated: iface->get_minimum_increment + iface->get_value_and_text = NULL; // TODO: get_value_and_text + iface->get_range = jaw_value_get_range; + iface->get_increment = jaw_value_get_increment; + iface->get_sub_ranges = + NULL; // missing java support for iface->get_sub_ranges + iface->set_value = jaw_value_set_value; +} + +gpointer jaw_value_data_init(jobject ac) { + JAW_DEBUG("%p", ac); + + if (ac == NULL) { + g_warning("%s: Null argument ac passed to the function", G_STRFUNC); + return NULL; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + if (jniEnv == NULL) { + g_warning("%s: jniEnv is NULL", G_STRFUNC); + return NULL; + } + + if (!jaw_value_init_jni_cache(jniEnv)) { + g_warning("%s: Failed to initialize JNI cache", G_STRFUNC); + return NULL; + } + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jatk_value = (*jniEnv)->CallStaticObjectMethod( + jniEnv, cachedValueAtkValueClass, cachedValueCreateAtkValueMethod, ac); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_value == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning( + "%s: Failed to create jatk_value using create_atk_value method", + G_STRFUNC); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + ValueData *data = g_new0(ValueData, 1); + data->atk_value = (*jniEnv)->NewGlobalRef(jniEnv, jatk_value); + if (data->atk_value == NULL) { + g_warning("%s: Failed to create global ref for atk_value", G_STRFUNC); + g_free(data); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + return data; +} + +void jaw_value_data_finalize(gpointer p) { + JAW_DEBUG("%p", p); + + if (p == NULL) { + g_warning("%s: Null argument p passed to the function", G_STRFUNC); + return; + } + + ValueData *data = (ValueData *)p; + if (data == NULL) { + g_warning("%s: data is null", G_STRFUNC); + return; + } + + JNIEnv *jniEnv = jaw_util_get_jni_env(); + + if (jniEnv == NULL) { + g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC); + } else { + if (data->atk_value != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_value); + data->atk_value = NULL; + } + } + + g_free(data); +} + +static void private_get_g_value_from_java_number(JNIEnv *jniEnv, + jobject jnumber, + GValue *value) { + JAW_DEBUG("%p, %p, %p", jniEnv, jnumber, value); + + if (jniEnv == NULL || value == NULL) { + g_warning( + "%s: Null argument passed to the function (jniEnv=%p, value=%p)", + G_STRFUNC, (void *)jniEnv, (void *)value); + return; + } + + if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueByteClass)) { + g_value_init(value, G_TYPE_CHAR); + g_value_set_schar(value, + (gchar)(*jniEnv)->CallByteMethod( + jniEnv, jnumber, cachedValueByteValueMethod)); + return; + } + + if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueDoubleClass)) { + g_value_init(value, G_TYPE_DOUBLE); + g_value_set_double(value, + (gdouble)(*jniEnv)->CallDoubleMethod( + jniEnv, jnumber, cachedValueDoubleValueMethod)); + return; + } + + if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueFloatClass)) { + g_value_init(value, G_TYPE_FLOAT); + g_value_set_float(value, + (gfloat)(*jniEnv)->CallFloatMethod( + jniEnv, jnumber, cachedValueFloatValueMethod)); + return; + } + + if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueIntegerClass)) { + jint v = (*jniEnv)->CallIntMethod(jniEnv, jnumber, + cachedValueIntValueMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception in Integer.intValue()", G_STRFUNC); + return; + } + + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, (gint)v); + return; + } + + if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueShortClass)) { + jshort v = (*jniEnv)->CallShortMethod(jniEnv, jnumber, + cachedValueShortValueMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception in Short.shortValue()", G_STRFUNC); + return; + } + + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, (gint)v); + return; + } + + if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueLongClass)) { + g_value_init(value, G_TYPE_INT64); + g_value_set_int64(value, + (gint64)(*jniEnv)->CallLongMethod( + jniEnv, jnumber, cachedValueLongValueMethod)); + return; + } +} + +/** + * jaw_value_get_current_value: + * @obj: a GObject instance that implements AtkValueIface + * @value: (out): a #GValue representing the current accessible value + * + * Gets the value of this object. + * + * Deprecated in atk: Since 2.12. Use atk_value_get_value_and_text() + * instead. + **/ +static void jaw_value_get_current_value(AtkValue *obj, GValue *value) { + JAW_DEBUG("%p, %p", obj, value); + + if (obj == NULL || value == NULL) { + g_warning("%s: Null argument passed to the function (obj=%p, value=%p)", + G_STRFUNC, (void *)obj, (void *)value); + return; + } + + if (G_VALUE_TYPE(value) != G_TYPE_INVALID) { + g_value_unset(value); + } + + JAW_GET_VALUE(obj, ); // create local JNI reference `jobject atk_value` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jobject jnumber = (*jniEnv)->CallObjectMethod( + jniEnv, atk_value, cachedValueGetCurrentValueMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception occurred while calling get_current_value", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + if (jnumber == NULL) { + g_warning( + "%s: Failed to get jnumber by calling get_current_value method", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + private_get_g_value_from_java_number(jniEnv, jnumber, value); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * atk_value_set_value: + * @obj: a GObject instance that implements AtkValueIface + * @new_value: a double which is the desired new accessible value. + * + * Sets the value of this object. + * + * This method is intended to provide a way to change the value of the + * object. In any case, it is possible that the value can't be + * modified (ie: a read-only component). If the value changes due this + * call, it is possible that the text could change, and will trigger + * an #AtkValue::value-changed signal emission. + * + * Since: 2.12 + **/ +static void jaw_value_set_value(AtkValue *obj, const gdouble value) { + JAW_DEBUG("%p, %lf", obj, value); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return; + } + + JAW_GET_VALUE(obj, ); // create local JNI reference `jobject atk_value` + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return; + } + + jobject jdoubleValue = (*jniEnv)->NewObject( + jniEnv, cachedValueDoubleClass, cachedValueDoubleConstructorMethod, + (jdouble)value); + if ((*jniEnv)->ExceptionCheck(jniEnv) || jdoubleValue == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to create Double object", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->CallVoidMethod(jniEnv, atk_value, cachedValueSetValueMethod, + jdoubleValue); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception occurred while calling set_value", G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); +} + +/** + * jaw_value_convert_double_to_gdouble: + * @jniEnv: JNI environment + * @jdouble: a Java Double object (jobject) + * @result: (out): pointer to store the converted double value + * + * Converts a Java Double object to a gdouble primitive value. + * + * Returns: TRUE if conversion was successful, FALSE if jdouble is NULL + **/ +static gboolean jaw_value_convert_double_to_gdouble(JNIEnv *jniEnv, + jobject jdouble, + gdouble *result) { + if (jdouble == NULL) { + return FALSE; + } + + *result = (gdouble)(*jniEnv)->CallDoubleMethod( + jniEnv, jdouble, cachedValueDoubleValueMethod); + return TRUE; +} + +/** + * jaw_value_get_range: + * @obj: a GObject instance that implements AtkValueIface + * + * Gets the range of this object. + * + * Returns: (nullable) (transfer full): a newly allocated #AtkRange + * that represents the minimum, maximum and descriptor (if available) + * of @obj. NULL if that range is not defined. + * + * In Atk Since: 2.12 + **/ +static AtkRange *jaw_value_get_range(AtkValue *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return NULL; + } + + JAW_GET_VALUE(obj, NULL); + + if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) { + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + g_warning("%s: Failed to create a new local reference frame", + G_STRFUNC); + return NULL; + } + + jobject jmin = (*jniEnv)->CallObjectMethod( + jniEnv, atk_value, cachedValueGetMinimumValueMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception occurred while calling get_minimum_value", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + jobject jmax = (*jniEnv)->CallObjectMethod( + jniEnv, atk_value, cachedValueGetMaximumValueMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception occurred while calling get_maximum_value", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + return NULL; + } + + gdouble min_value, max_value; + gboolean has_min = + jaw_value_convert_double_to_gdouble(jniEnv, jmin, &min_value); + gboolean has_max = + jaw_value_convert_double_to_gdouble(jniEnv, jmax, &max_value); + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + (*jniEnv)->PopLocalFrame(jniEnv, NULL); + + // If either min or max is NULL, we cannot construct a valid range + if (!has_min || !has_max) { + return NULL; + } + + AtkRange *ret = + atk_range_new(min_value, max_value, NULL); // NULL description + return ret; +} + +/** + * jaw_value_get_increment: + * @obj: a GObject instance that implements AtkValueIface + * + * Gets the minimum increment by which the value of this object may be + * changed. If zero, the minimum increment is undefined, which may + * mean that it is limited only by the floating point precision of the + * platform. + * + * Return value: the minimum increment by which the value of this + * object may be changed. zero if undefined. + * + * In atk Since: 2.12 + **/ +static gdouble jaw_value_get_increment(AtkValue *obj) { + JAW_DEBUG("%p", obj); + + if (obj == NULL) { + g_warning("%s: Null argument obj passed to the function", G_STRFUNC); + return 0; + } + + JAW_GET_VALUE(obj, 0); + + gdouble ret = (*jniEnv)->CallDoubleMethod(jniEnv, atk_value, + cachedValueGetIncrementMethod); + if ((*jniEnv)->ExceptionCheck(jniEnv)) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Exception occurred while calling get_increment", + G_STRFUNC); + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + return 0; + } + + (*jniEnv)->DeleteLocalRef(jniEnv, atk_value); + + return ret; +} + +static gboolean jaw_value_init_jni_cache(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return FALSE; + } + + g_mutex_lock(&cache_mutex); + + if (cache_initialized) { + g_mutex_unlock(&cache_mutex); + return TRUE; + } + + jclass localClassAtkValue = + (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkValue"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localClassAtkValue == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find AtkValue class", G_STRFUNC); + g_mutex_unlock(&cache_mutex); + return FALSE; + } + + cachedValueAtkValueClass = + (*jniEnv)->NewGlobalRef(jniEnv, localClassAtkValue); + (*jniEnv)->DeleteLocalRef(jniEnv, localClassAtkValue); + + if (cachedValueAtkValueClass == NULL) { + g_warning("%s: Failed to create global reference for AtkValue class", + G_STRFUNC); + g_mutex_unlock(&cache_mutex); + return FALSE; + } + + cachedValueCreateAtkValueMethod = (*jniEnv)->GetStaticMethodID( + jniEnv, cachedValueAtkValueClass, "create_atk_value", + "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/" + "AtkValue;"); + + cachedValueGetCurrentValueMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedValueAtkValueClass, + "get_current_value", "()Ljava/lang/Number;"); + + cachedValueSetValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueAtkValueClass, "set_value", "(Ljava/lang/Number;)V"); + + cachedValueGetMinimumValueMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedValueAtkValueClass, + "get_minimum_value", "()Ljava/lang/Double;"); + + cachedValueGetMaximumValueMethod = + (*jniEnv)->GetMethodID(jniEnv, cachedValueAtkValueClass, + "get_maximum_value", "()Ljava/lang/Double;"); + + cachedValueGetIncrementMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueAtkValueClass, "get_increment", "()D"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedValueCreateAtkValueMethod == NULL || + cachedValueGetCurrentValueMethod == NULL || + cachedValueSetValueMethod == NULL || + cachedValueGetMinimumValueMethod == NULL || + cachedValueGetMaximumValueMethod == NULL || + cachedValueGetIncrementMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache one or more AtkValue method IDs", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localByte = (*jniEnv)->FindClass(jniEnv, "java/lang/Byte"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localByte == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Byte class", G_STRFUNC); + goto cleanup_and_fail; + } + cachedValueByteClass = (*jniEnv)->NewGlobalRef(jniEnv, localByte); + (*jniEnv)->DeleteLocalRef(jniEnv, localByte); + if (cachedValueByteClass == NULL) { + g_warning("%s: Failed to create global reference for Byte class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localDouble = (*jniEnv)->FindClass(jniEnv, "java/lang/Double"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localDouble == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Double class", G_STRFUNC); + goto cleanup_and_fail; + } + cachedValueDoubleClass = (*jniEnv)->NewGlobalRef(jniEnv, localDouble); + (*jniEnv)->DeleteLocalRef(jniEnv, localDouble); + if (cachedValueDoubleClass == NULL) { + g_warning("%s: Failed to create global reference for Double class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localFloat = (*jniEnv)->FindClass(jniEnv, "java/lang/Float"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localFloat == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Float class", G_STRFUNC); + goto cleanup_and_fail; + } + cachedValueFloatClass = (*jniEnv)->NewGlobalRef(jniEnv, localFloat); + (*jniEnv)->DeleteLocalRef(jniEnv, localFloat); + if (cachedValueFloatClass == NULL) { + g_warning("%s: Failed to create global reference for Float class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localInteger = (*jniEnv)->FindClass(jniEnv, "java/lang/Integer"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localInteger == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Integer class", G_STRFUNC); + goto cleanup_and_fail; + } + cachedValueIntegerClass = (*jniEnv)->NewGlobalRef(jniEnv, localInteger); + (*jniEnv)->DeleteLocalRef(jniEnv, localInteger); + if (cachedValueIntegerClass == NULL) { + g_warning("%s: Failed to create global reference for Integer class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localLong = (*jniEnv)->FindClass(jniEnv, "java/lang/Long"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localLong == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Long class", G_STRFUNC); + goto cleanup_and_fail; + } + cachedValueLongClass = (*jniEnv)->NewGlobalRef(jniEnv, localLong); + (*jniEnv)->DeleteLocalRef(jniEnv, localLong); + if (cachedValueLongClass == NULL) { + g_warning("%s: Failed to create global reference for Long class", + G_STRFUNC); + goto cleanup_and_fail; + } + + jclass localShort = (*jniEnv)->FindClass(jniEnv, "java/lang/Short"); + if ((*jniEnv)->ExceptionCheck(jniEnv) || localShort == NULL) { + jaw_jni_clear_exception(jniEnv); + g_warning("%s: Failed to find Short class", G_STRFUNC); + goto cleanup_and_fail; + } + cachedValueShortClass = (*jniEnv)->NewGlobalRef(jniEnv, localShort); + (*jniEnv)->DeleteLocalRef(jniEnv, localShort); + if (cachedValueShortClass == NULL) { + g_warning("%s: Failed to create global reference for Short class", + G_STRFUNC); + goto cleanup_and_fail; + } + + cachedValueByteValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueByteClass, "byteValue", "()B"); + cachedValueDoubleValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueDoubleClass, "doubleValue", "()D"); + cachedValueFloatValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueFloatClass, "floatValue", "()F"); + cachedValueIntValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueIntegerClass, "intValue", "()I"); + cachedValueLongValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueLongClass, "longValue", "()J"); + cachedValueShortValueMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueShortClass, "shortValue", "()S"); + cachedValueDoubleConstructorMethod = (*jniEnv)->GetMethodID( + jniEnv, cachedValueDoubleClass, "", "(D)V"); + + if ((*jniEnv)->ExceptionCheck(jniEnv) || + cachedValueByteValueMethod == NULL || + cachedValueDoubleValueMethod == NULL || + cachedValueFloatValueMethod == NULL || + cachedValueIntValueMethod == NULL || + cachedValueLongValueMethod == NULL || + cachedValueShortValueMethod == NULL || + cachedValueDoubleConstructorMethod == NULL) { + + jaw_jni_clear_exception(jniEnv); + + g_warning("%s: Failed to cache Number wrapper method IDs", G_STRFUNC); + goto cleanup_and_fail; + } + + cache_initialized = TRUE; + g_mutex_unlock(&cache_mutex); + + g_debug("%s: classes and methods cached successfully", G_STRFUNC); + + return TRUE; + +cleanup_and_fail: + if (cachedValueAtkValueClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueAtkValueClass); + cachedValueAtkValueClass = NULL; + } + if (cachedValueByteClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueByteClass); + cachedValueByteClass = NULL; + } + if (cachedValueDoubleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueDoubleClass); + cachedValueDoubleClass = NULL; + } + if (cachedValueFloatClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueFloatClass); + cachedValueFloatClass = NULL; + } + if (cachedValueIntegerClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueIntegerClass); + cachedValueIntegerClass = NULL; + } + if (cachedValueLongClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueLongClass); + cachedValueLongClass = NULL; + } + if (cachedValueShortClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueShortClass); + cachedValueShortClass = NULL; + } + + cachedValueCreateAtkValueMethod = NULL; + cachedValueGetCurrentValueMethod = NULL; + cachedValueSetValueMethod = NULL; + cachedValueGetMinimumValueMethod = NULL; + cachedValueGetMaximumValueMethod = NULL; + cachedValueGetIncrementMethod = NULL; + cachedValueByteValueMethod = NULL; + cachedValueDoubleValueMethod = NULL; + cachedValueFloatValueMethod = NULL; + cachedValueIntValueMethod = NULL; + cachedValueLongValueMethod = NULL; + cachedValueShortValueMethod = NULL; + cachedValueDoubleConstructorMethod = NULL; + + g_mutex_unlock(&cache_mutex); + return FALSE; +} + +void jaw_value_cache_cleanup(JNIEnv *jniEnv) { + JAW_DEBUG("JNIEnv: %p", jniEnv); + + if (jniEnv == NULL) { + g_warning("%s: jniEnv == NULL", G_STRFUNC); + return; + } + + g_mutex_lock(&cache_mutex); + + if (cachedValueAtkValueClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueAtkValueClass); + cachedValueAtkValueClass = NULL; + } + if (cachedValueByteClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueByteClass); + cachedValueByteClass = NULL; + } + if (cachedValueDoubleClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueDoubleClass); + cachedValueDoubleClass = NULL; + } + if (cachedValueFloatClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueFloatClass); + cachedValueFloatClass = NULL; + } + if (cachedValueIntegerClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueIntegerClass); + cachedValueIntegerClass = NULL; + } + if (cachedValueLongClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueLongClass); + cachedValueLongClass = NULL; + } + if (cachedValueShortClass != NULL) { + (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueShortClass); + cachedValueShortClass = NULL; + } + cachedValueCreateAtkValueMethod = NULL; + cachedValueGetCurrentValueMethod = NULL; + cachedValueSetValueMethod = NULL; + cachedValueGetMinimumValueMethod = NULL; + cachedValueGetMaximumValueMethod = NULL; + cachedValueGetIncrementMethod = NULL; + cachedValueByteValueMethod = NULL; + cachedValueDoubleValueMethod = NULL; + cachedValueFloatValueMethod = NULL; + cachedValueIntValueMethod = NULL; + cachedValueLongValueMethod = NULL; + cachedValueShortValueMethod = NULL; + cachedValueDoubleConstructorMethod = NULL; + cache_initialized = FALSE; + + g_mutex_unlock(&cache_mutex); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file