add basic plugin capability
This commit is contained in:
parent
8c21412b54
commit
f17efc3da8
79
m4/build-to-host.m4
Normal file
79
m4/build-to-host.m4
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# build-to-host.m4 serial 3
|
||||||
|
dnl Copyright (C) 2023-2024 Free Software Foundation, Inc.
|
||||||
|
dnl This file is free software; the Free Software Foundation
|
||||||
|
dnl gives unlimited permission to copy and/or distribute it,
|
||||||
|
dnl with or without modifications, as long as this notice is preserved.
|
||||||
|
|
||||||
|
dnl Written by Bruno Haible.
|
||||||
|
|
||||||
|
dnl When the build environment ($build_os) is different from the target runtime
|
||||||
|
dnl environment ($host_os), file names may need to be converted from the build
|
||||||
|
dnl environment syntax to the target runtime environment syntax. This is
|
||||||
|
dnl because the Makefiles are executed (mostly) by build environment tools and
|
||||||
|
dnl therefore expect file names in build environment syntax, whereas the runtime
|
||||||
|
dnl expects file names in target runtime environment syntax.
|
||||||
|
dnl
|
||||||
|
dnl For example, if $build_os = cygwin and $host_os = mingw32, filenames need
|
||||||
|
dnl be converted from Cygwin syntax to native Windows syntax:
|
||||||
|
dnl /cygdrive/c/foo/bar -> C:\foo\bar
|
||||||
|
dnl /usr/local/share -> C:\cygwin64\usr\local\share
|
||||||
|
dnl
|
||||||
|
dnl gl_BUILD_TO_HOST([somedir])
|
||||||
|
dnl This macro takes as input an AC_SUBSTed variable 'somedir', which must
|
||||||
|
dnl already have its final value assigned, and produces two additional
|
||||||
|
dnl AC_SUBSTed variables 'somedir_c' and 'somedir_c_make', that designate the
|
||||||
|
dnl same file name value, just in different syntax:
|
||||||
|
dnl - somedir_c is the file name in target runtime environment syntax,
|
||||||
|
dnl as a C string (starting and ending with a double-quote,
|
||||||
|
dnl and with escaped backslashes and double-quotes in
|
||||||
|
dnl between).
|
||||||
|
dnl - somedir_c_make is the same thing, escaped for use in a Makefile.
|
||||||
|
|
||||||
|
AC_DEFUN([gl_BUILD_TO_HOST],
|
||||||
|
[
|
||||||
|
AC_REQUIRE([AC_CANONICAL_BUILD])
|
||||||
|
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||||
|
AC_REQUIRE([gl_BUILD_TO_HOST_INIT])
|
||||||
|
|
||||||
|
dnl Define somedir_c.
|
||||||
|
gl_final_[$1]="$[$1]"
|
||||||
|
dnl Translate it from build syntax to host syntax.
|
||||||
|
case "$build_os" in
|
||||||
|
cygwin*)
|
||||||
|
case "$host_os" in
|
||||||
|
mingw* | windows*)
|
||||||
|
gl_final_[$1]=`cygpath -w "$gl_final_[$1]"` ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
dnl Convert it to C string syntax.
|
||||||
|
[$1]_c=`printf '%s\n' "$gl_final_[$1]" | sed -e "$gl_sed_double_backslashes" -e "$gl_sed_escape_doublequotes" | tr -d "$gl_tr_cr"`
|
||||||
|
[$1]_c='"'"$[$1]_c"'"'
|
||||||
|
AC_SUBST([$1_c])
|
||||||
|
|
||||||
|
dnl Define somedir_c_make.
|
||||||
|
[$1]_c_make=`printf '%s\n' "$[$1]_c" | sed -e "$gl_sed_escape_for_make_1" -e "$gl_sed_escape_for_make_2" | tr -d "$gl_tr_cr"`
|
||||||
|
dnl Use the substituted somedir variable, when possible, so that the user
|
||||||
|
dnl may adjust somedir a posteriori when there are no special characters.
|
||||||
|
if test "$[$1]_c_make" = '\"'"${gl_final_[$1]}"'\"'; then
|
||||||
|
[$1]_c_make='\"$([$1])\"'
|
||||||
|
fi
|
||||||
|
AC_SUBST([$1_c_make])
|
||||||
|
])
|
||||||
|
|
||||||
|
dnl Some initializations for gl_BUILD_TO_HOST.
|
||||||
|
AC_DEFUN([gl_BUILD_TO_HOST_INIT],
|
||||||
|
[
|
||||||
|
gl_sed_double_backslashes='s/\\/\\\\/g'
|
||||||
|
gl_sed_escape_doublequotes='s/"/\\"/g'
|
||||||
|
changequote(,)dnl
|
||||||
|
gl_sed_escape_for_make_1="s,\\([ \"&'();<>\\\\\`|]\\),\\\\\\1,g"
|
||||||
|
changequote([,])dnl
|
||||||
|
gl_sed_escape_for_make_2='s,\$,\\$$,g'
|
||||||
|
dnl Find out how to remove carriage returns from output. Solaris /usr/ucb/tr
|
||||||
|
dnl does not understand '\r'.
|
||||||
|
case `echo r | tr -d '\r'` in
|
||||||
|
'') gl_tr_cr='\015' ;;
|
||||||
|
*) gl_tr_cr='\r' ;;
|
||||||
|
esac
|
||||||
|
])
|
527
m4/host-cpu-c-abi.m4
Normal file
527
m4/host-cpu-c-abi.m4
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
# host-cpu-c-abi.m4 serial 17
|
||||||
|
dnl Copyright (C) 2002-2024 Free Software Foundation, Inc.
|
||||||
|
dnl This file is free software; the Free Software Foundation
|
||||||
|
dnl gives unlimited permission to copy and/or distribute it,
|
||||||
|
dnl with or without modifications, as long as this notice is preserved.
|
||||||
|
|
||||||
|
dnl From Bruno Haible and Sam Steingold.
|
||||||
|
|
||||||
|
dnl Sets the HOST_CPU variable to the canonical name of the CPU.
|
||||||
|
dnl Sets the HOST_CPU_C_ABI variable to the canonical name of the CPU with its
|
||||||
|
dnl C language ABI (application binary interface).
|
||||||
|
dnl Also defines __${HOST_CPU}__ and __${HOST_CPU_C_ABI}__ as C macros in
|
||||||
|
dnl config.h.
|
||||||
|
dnl
|
||||||
|
dnl This canonical name can be used to select a particular assembly language
|
||||||
|
dnl source file that will interoperate with C code on the given host.
|
||||||
|
dnl
|
||||||
|
dnl For example:
|
||||||
|
dnl * 'i386' and 'sparc' are different canonical names, because code for i386
|
||||||
|
dnl will not run on SPARC CPUs and vice versa. They have different
|
||||||
|
dnl instruction sets.
|
||||||
|
dnl * 'sparc' and 'sparc64' are different canonical names, because code for
|
||||||
|
dnl 'sparc' and code for 'sparc64' cannot be linked together: 'sparc' code
|
||||||
|
dnl contains 32-bit instructions, whereas 'sparc64' code contains 64-bit
|
||||||
|
dnl instructions. A process on a SPARC CPU can be in 32-bit mode or in 64-bit
|
||||||
|
dnl mode, but not both.
|
||||||
|
dnl * 'mips' and 'mipsn32' are different canonical names, because they use
|
||||||
|
dnl different argument passing and return conventions for C functions, and
|
||||||
|
dnl although the instruction set of 'mips' is a large subset of the
|
||||||
|
dnl instruction set of 'mipsn32'.
|
||||||
|
dnl * 'mipsn32' and 'mips64' are different canonical names, because they use
|
||||||
|
dnl different sizes for the C types like 'int' and 'void *', and although
|
||||||
|
dnl the instruction sets of 'mipsn32' and 'mips64' are the same.
|
||||||
|
dnl * The same canonical name is used for different endiannesses. You can
|
||||||
|
dnl determine the endianness through preprocessor symbols:
|
||||||
|
dnl - 'arm': test __ARMEL__.
|
||||||
|
dnl - 'mips', 'mipsn32', 'mips64': test _MIPSEB vs. _MIPSEL.
|
||||||
|
dnl - 'powerpc64': test _BIG_ENDIAN vs. _LITTLE_ENDIAN.
|
||||||
|
dnl * The same name 'i386' is used for CPUs of type i386, i486, i586
|
||||||
|
dnl (Pentium), AMD K7, Pentium II, Pentium IV, etc., because
|
||||||
|
dnl - Instructions that do not exist on all of these CPUs (cmpxchg,
|
||||||
|
dnl MMX, SSE, SSE2, 3DNow! etc.) are not frequently used. If your
|
||||||
|
dnl assembly language source files use such instructions, you will
|
||||||
|
dnl need to make the distinction.
|
||||||
|
dnl - Speed of execution of the common instruction set is reasonable across
|
||||||
|
dnl the entire family of CPUs. If you have assembly language source files
|
||||||
|
dnl that are optimized for particular CPU types (like GNU gmp has), you
|
||||||
|
dnl will need to make the distinction.
|
||||||
|
dnl See <https://en.wikipedia.org/wiki/X86_instruction_listings>.
|
||||||
|
AC_DEFUN([gl_HOST_CPU_C_ABI],
|
||||||
|
[
|
||||||
|
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||||
|
AC_REQUIRE([gl_C_ASM])
|
||||||
|
AC_CACHE_CHECK([host CPU and C ABI], [gl_cv_host_cpu_c_abi],
|
||||||
|
[case "$host_cpu" in
|
||||||
|
|
||||||
|
changequote(,)dnl
|
||||||
|
i[34567]86 )
|
||||||
|
changequote([,])dnl
|
||||||
|
gl_cv_host_cpu_c_abi=i386
|
||||||
|
;;
|
||||||
|
|
||||||
|
x86_64 )
|
||||||
|
# On x86_64 systems, the C compiler may be generating code in one of
|
||||||
|
# these ABIs:
|
||||||
|
# - 64-bit instruction set, 64-bit pointers, 64-bit 'long': x86_64.
|
||||||
|
# - 64-bit instruction set, 64-bit pointers, 32-bit 'long': x86_64
|
||||||
|
# with native Windows (mingw, MSVC).
|
||||||
|
# - 64-bit instruction set, 32-bit pointers, 32-bit 'long': x86_64-x32.
|
||||||
|
# - 32-bit instruction set, 32-bit pointers, 32-bit 'long': i386.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if (defined __x86_64__ || defined __amd64__ \
|
||||||
|
|| defined _M_X64 || defined _M_AMD64)
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __ILP32__ || defined _ILP32
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=x86_64-x32],
|
||||||
|
[gl_cv_host_cpu_c_abi=x86_64])],
|
||||||
|
[gl_cv_host_cpu_c_abi=i386])
|
||||||
|
;;
|
||||||
|
|
||||||
|
changequote(,)dnl
|
||||||
|
alphaev[4-8] | alphaev56 | alphapca5[67] | alphaev6[78] )
|
||||||
|
changequote([,])dnl
|
||||||
|
gl_cv_host_cpu_c_abi=alpha
|
||||||
|
;;
|
||||||
|
|
||||||
|
arm* | aarch64 )
|
||||||
|
# Assume arm with EABI.
|
||||||
|
# On arm64 systems, the C compiler may be generating code in one of
|
||||||
|
# these ABIs:
|
||||||
|
# - aarch64 instruction set, 64-bit pointers, 64-bit 'long': arm64.
|
||||||
|
# - aarch64 instruction set, 32-bit pointers, 32-bit 'long': arm64-ilp32.
|
||||||
|
# - 32-bit instruction set, 32-bit pointers, 32-bit 'long': arm or armhf.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#ifdef __aarch64__
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __ILP32__ || defined _ILP32
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=arm64-ilp32],
|
||||||
|
[gl_cv_host_cpu_c_abi=arm64])],
|
||||||
|
[# Don't distinguish little-endian and big-endian arm, since they
|
||||||
|
# don't require different machine code for simple operations and
|
||||||
|
# since the user can distinguish them through the preprocessor
|
||||||
|
# defines __ARMEL__ vs. __ARMEB__.
|
||||||
|
# But distinguish arm which passes floating-point arguments and
|
||||||
|
# return values in integer registers (r0, r1, ...) - this is
|
||||||
|
# gcc -mfloat-abi=soft or gcc -mfloat-abi=softfp - from arm which
|
||||||
|
# passes them in float registers (s0, s1, ...) and double registers
|
||||||
|
# (d0, d1, ...) - this is gcc -mfloat-abi=hard. GCC 4.6 or newer
|
||||||
|
# sets the preprocessor defines __ARM_PCS (for the first case) and
|
||||||
|
# __ARM_PCS_VFP (for the second case), but older GCC does not.
|
||||||
|
echo 'double ddd; void func (double dd) { ddd = dd; }' > conftest.c
|
||||||
|
# Look for a reference to the register d0 in the .s file.
|
||||||
|
AC_TRY_COMMAND(${CC-cc} $CFLAGS $CPPFLAGS $gl_c_asm_opt conftest.c) >/dev/null 2>&1
|
||||||
|
if LC_ALL=C grep 'd0,' conftest.$gl_asmext >/dev/null; then
|
||||||
|
gl_cv_host_cpu_c_abi=armhf
|
||||||
|
else
|
||||||
|
gl_cv_host_cpu_c_abi=arm
|
||||||
|
fi
|
||||||
|
rm -f conftest*
|
||||||
|
])
|
||||||
|
;;
|
||||||
|
|
||||||
|
hppa1.0 | hppa1.1 | hppa2.0* | hppa64 )
|
||||||
|
# On hppa, the C compiler may be generating 32-bit code or 64-bit
|
||||||
|
# code. In the latter case, it defines _LP64 and __LP64__.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#ifdef __LP64__
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=hppa64],
|
||||||
|
[gl_cv_host_cpu_c_abi=hppa])
|
||||||
|
;;
|
||||||
|
|
||||||
|
ia64* )
|
||||||
|
# On ia64 on HP-UX, the C compiler may be generating 64-bit code or
|
||||||
|
# 32-bit code. In the latter case, it defines _ILP32.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#ifdef _ILP32
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=ia64-ilp32],
|
||||||
|
[gl_cv_host_cpu_c_abi=ia64])
|
||||||
|
;;
|
||||||
|
|
||||||
|
mips* )
|
||||||
|
# We should also check for (_MIPS_SZPTR == 64), but gcc keeps this
|
||||||
|
# at 32.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined _MIPS_SZLONG && (_MIPS_SZLONG == 64)
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=mips64],
|
||||||
|
[# In the n32 ABI, _ABIN32 is defined, _ABIO32 is not defined (but
|
||||||
|
# may later get defined by <sgidefs.h>), and _MIPS_SIM == _ABIN32.
|
||||||
|
# In the 32 ABI, _ABIO32 is defined, _ABIN32 is not defined (but
|
||||||
|
# may later get defined by <sgidefs.h>), and _MIPS_SIM == _ABIO32.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if (_MIPS_SIM == _ABIN32)
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=mipsn32],
|
||||||
|
[gl_cv_host_cpu_c_abi=mips])])
|
||||||
|
;;
|
||||||
|
|
||||||
|
powerpc* )
|
||||||
|
# Different ABIs are in use on AIX vs. Mac OS X vs. Linux,*BSD.
|
||||||
|
# No need to distinguish them here; the caller may distinguish
|
||||||
|
# them based on the OS.
|
||||||
|
# On powerpc64 systems, the C compiler may still be generating
|
||||||
|
# 32-bit code. And on powerpc-ibm-aix systems, the C compiler may
|
||||||
|
# be generating 64-bit code.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __powerpc64__ || defined __LP64__
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[# On powerpc64, there are two ABIs on Linux: The AIX compatible
|
||||||
|
# one and the ELFv2 one. The latter defines _CALL_ELF=2.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined _CALL_ELF && _CALL_ELF == 2
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=powerpc64-elfv2],
|
||||||
|
[gl_cv_host_cpu_c_abi=powerpc64])
|
||||||
|
],
|
||||||
|
[gl_cv_host_cpu_c_abi=powerpc])
|
||||||
|
;;
|
||||||
|
|
||||||
|
rs6000 )
|
||||||
|
gl_cv_host_cpu_c_abi=powerpc
|
||||||
|
;;
|
||||||
|
|
||||||
|
riscv32 | riscv64 )
|
||||||
|
# There are 2 architectures (with variants): rv32* and rv64*.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if __riscv_xlen == 64
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[cpu=riscv64],
|
||||||
|
[cpu=riscv32])
|
||||||
|
# There are 6 ABIs: ilp32, ilp32f, ilp32d, lp64, lp64f, lp64d.
|
||||||
|
# Size of 'long' and 'void *':
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __LP64__
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[main_abi=lp64],
|
||||||
|
[main_abi=ilp32])
|
||||||
|
# Float ABIs:
|
||||||
|
# __riscv_float_abi_double:
|
||||||
|
# 'float' and 'double' are passed in floating-point registers.
|
||||||
|
# __riscv_float_abi_single:
|
||||||
|
# 'float' are passed in floating-point registers.
|
||||||
|
# __riscv_float_abi_soft:
|
||||||
|
# No values are passed in floating-point registers.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __riscv_float_abi_double
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[float_abi=d],
|
||||||
|
[AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __riscv_float_abi_single
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[float_abi=f],
|
||||||
|
[float_abi=''])
|
||||||
|
])
|
||||||
|
gl_cv_host_cpu_c_abi="${cpu}-${main_abi}${float_abi}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
s390* )
|
||||||
|
# On s390x, the C compiler may be generating 64-bit (= s390x) code
|
||||||
|
# or 31-bit (= s390) code.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __LP64__ || defined __s390x__
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=s390x],
|
||||||
|
[gl_cv_host_cpu_c_abi=s390])
|
||||||
|
;;
|
||||||
|
|
||||||
|
sparc | sparc64 )
|
||||||
|
# UltraSPARCs running Linux have `uname -m` = "sparc64", but the
|
||||||
|
# C compiler still generates 32-bit code.
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[#if defined __sparcv9 || defined __arch64__
|
||||||
|
int ok;
|
||||||
|
#else
|
||||||
|
error fail
|
||||||
|
#endif
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi=sparc64],
|
||||||
|
[gl_cv_host_cpu_c_abi=sparc])
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
gl_cv_host_cpu_c_abi="$host_cpu"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
])
|
||||||
|
|
||||||
|
dnl In most cases, $HOST_CPU and $HOST_CPU_C_ABI are the same.
|
||||||
|
HOST_CPU=`echo "$gl_cv_host_cpu_c_abi" | sed -e 's/-.*//'`
|
||||||
|
HOST_CPU_C_ABI="$gl_cv_host_cpu_c_abi"
|
||||||
|
AC_SUBST([HOST_CPU])
|
||||||
|
AC_SUBST([HOST_CPU_C_ABI])
|
||||||
|
|
||||||
|
# This was
|
||||||
|
# AC_DEFINE_UNQUOTED([__${HOST_CPU}__])
|
||||||
|
# AC_DEFINE_UNQUOTED([__${HOST_CPU_C_ABI}__])
|
||||||
|
# earlier, but KAI C++ 3.2d doesn't like this.
|
||||||
|
sed -e 's/-/_/g' >> confdefs.h <<EOF
|
||||||
|
#ifndef __${HOST_CPU}__
|
||||||
|
#define __${HOST_CPU}__ 1
|
||||||
|
#endif
|
||||||
|
#ifndef __${HOST_CPU_C_ABI}__
|
||||||
|
#define __${HOST_CPU_C_ABI}__ 1
|
||||||
|
#endif
|
||||||
|
EOF
|
||||||
|
AH_TOP([/* CPU and C ABI indicator */
|
||||||
|
#ifndef __i386__
|
||||||
|
#undef __i386__
|
||||||
|
#endif
|
||||||
|
#ifndef __x86_64_x32__
|
||||||
|
#undef __x86_64_x32__
|
||||||
|
#endif
|
||||||
|
#ifndef __x86_64__
|
||||||
|
#undef __x86_64__
|
||||||
|
#endif
|
||||||
|
#ifndef __alpha__
|
||||||
|
#undef __alpha__
|
||||||
|
#endif
|
||||||
|
#ifndef __arm__
|
||||||
|
#undef __arm__
|
||||||
|
#endif
|
||||||
|
#ifndef __armhf__
|
||||||
|
#undef __armhf__
|
||||||
|
#endif
|
||||||
|
#ifndef __arm64_ilp32__
|
||||||
|
#undef __arm64_ilp32__
|
||||||
|
#endif
|
||||||
|
#ifndef __arm64__
|
||||||
|
#undef __arm64__
|
||||||
|
#endif
|
||||||
|
#ifndef __hppa__
|
||||||
|
#undef __hppa__
|
||||||
|
#endif
|
||||||
|
#ifndef __hppa64__
|
||||||
|
#undef __hppa64__
|
||||||
|
#endif
|
||||||
|
#ifndef __ia64_ilp32__
|
||||||
|
#undef __ia64_ilp32__
|
||||||
|
#endif
|
||||||
|
#ifndef __ia64__
|
||||||
|
#undef __ia64__
|
||||||
|
#endif
|
||||||
|
#ifndef __loongarch64__
|
||||||
|
#undef __loongarch64__
|
||||||
|
#endif
|
||||||
|
#ifndef __m68k__
|
||||||
|
#undef __m68k__
|
||||||
|
#endif
|
||||||
|
#ifndef __mips__
|
||||||
|
#undef __mips__
|
||||||
|
#endif
|
||||||
|
#ifndef __mipsn32__
|
||||||
|
#undef __mipsn32__
|
||||||
|
#endif
|
||||||
|
#ifndef __mips64__
|
||||||
|
#undef __mips64__
|
||||||
|
#endif
|
||||||
|
#ifndef __powerpc__
|
||||||
|
#undef __powerpc__
|
||||||
|
#endif
|
||||||
|
#ifndef __powerpc64__
|
||||||
|
#undef __powerpc64__
|
||||||
|
#endif
|
||||||
|
#ifndef __powerpc64_elfv2__
|
||||||
|
#undef __powerpc64_elfv2__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv32__
|
||||||
|
#undef __riscv32__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64__
|
||||||
|
#undef __riscv64__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv32_ilp32__
|
||||||
|
#undef __riscv32_ilp32__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv32_ilp32f__
|
||||||
|
#undef __riscv32_ilp32f__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv32_ilp32d__
|
||||||
|
#undef __riscv32_ilp32d__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64_ilp32__
|
||||||
|
#undef __riscv64_ilp32__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64_ilp32f__
|
||||||
|
#undef __riscv64_ilp32f__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64_ilp32d__
|
||||||
|
#undef __riscv64_ilp32d__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64_lp64__
|
||||||
|
#undef __riscv64_lp64__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64_lp64f__
|
||||||
|
#undef __riscv64_lp64f__
|
||||||
|
#endif
|
||||||
|
#ifndef __riscv64_lp64d__
|
||||||
|
#undef __riscv64_lp64d__
|
||||||
|
#endif
|
||||||
|
#ifndef __s390__
|
||||||
|
#undef __s390__
|
||||||
|
#endif
|
||||||
|
#ifndef __s390x__
|
||||||
|
#undef __s390x__
|
||||||
|
#endif
|
||||||
|
#ifndef __sh__
|
||||||
|
#undef __sh__
|
||||||
|
#endif
|
||||||
|
#ifndef __sparc__
|
||||||
|
#undef __sparc__
|
||||||
|
#endif
|
||||||
|
#ifndef __sparc64__
|
||||||
|
#undef __sparc64__
|
||||||
|
#endif
|
||||||
|
])
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
dnl Sets the HOST_CPU_C_ABI_32BIT variable to 'yes' if the C language ABI
|
||||||
|
dnl (application binary interface) is a 32-bit one, to 'no' if it is a 64-bit
|
||||||
|
dnl one.
|
||||||
|
dnl This is a simplified variant of gl_HOST_CPU_C_ABI.
|
||||||
|
AC_DEFUN([gl_HOST_CPU_C_ABI_32BIT],
|
||||||
|
[
|
||||||
|
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||||
|
AC_CACHE_CHECK([32-bit host C ABI], [gl_cv_host_cpu_c_abi_32bit],
|
||||||
|
[case "$host_cpu" in
|
||||||
|
|
||||||
|
# CPUs that only support a 32-bit ABI.
|
||||||
|
arc \
|
||||||
|
| bfin \
|
||||||
|
| cris* \
|
||||||
|
| csky \
|
||||||
|
| epiphany \
|
||||||
|
| ft32 \
|
||||||
|
| h8300 \
|
||||||
|
| m68k \
|
||||||
|
| microblaze | microblazeel \
|
||||||
|
| nds32 | nds32le | nds32be \
|
||||||
|
| nios2 | nios2eb | nios2el \
|
||||||
|
| or1k* \
|
||||||
|
| or32 \
|
||||||
|
| sh | sh[1234] | sh[1234]e[lb] \
|
||||||
|
| tic6x \
|
||||||
|
| xtensa* )
|
||||||
|
gl_cv_host_cpu_c_abi_32bit=yes
|
||||||
|
;;
|
||||||
|
|
||||||
|
# CPUs that only support a 64-bit ABI.
|
||||||
|
changequote(,)dnl
|
||||||
|
alpha | alphaev[4-8] | alphaev56 | alphapca5[67] | alphaev6[78] \
|
||||||
|
| mmix )
|
||||||
|
changequote([,])dnl
|
||||||
|
gl_cv_host_cpu_c_abi_32bit=no
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
if test -n "$gl_cv_host_cpu_c_abi"; then
|
||||||
|
dnl gl_HOST_CPU_C_ABI has already been run. Use its result.
|
||||||
|
case "$gl_cv_host_cpu_c_abi" in
|
||||||
|
i386 | x86_64-x32 | arm | armhf | arm64-ilp32 | hppa | ia64-ilp32 | mips | mipsn32 | powerpc | riscv*-ilp32* | s390 | sparc)
|
||||||
|
gl_cv_host_cpu_c_abi_32bit=yes ;;
|
||||||
|
x86_64 | alpha | arm64 | aarch64c | hppa64 | ia64 | mips64 | powerpc64 | powerpc64-elfv2 | riscv*-lp64* | s390x | sparc64 )
|
||||||
|
gl_cv_host_cpu_c_abi_32bit=no ;;
|
||||||
|
*)
|
||||||
|
gl_cv_host_cpu_c_abi_32bit=unknown ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
gl_cv_host_cpu_c_abi_32bit=unknown
|
||||||
|
fi
|
||||||
|
if test $gl_cv_host_cpu_c_abi_32bit = unknown; then
|
||||||
|
AC_COMPILE_IFELSE(
|
||||||
|
[AC_LANG_SOURCE(
|
||||||
|
[[int test_pointer_size[sizeof (void *) - 5];
|
||||||
|
]])],
|
||||||
|
[gl_cv_host_cpu_c_abi_32bit=no],
|
||||||
|
[gl_cv_host_cpu_c_abi_32bit=yes])
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
])
|
||||||
|
|
||||||
|
HOST_CPU_C_ABI_32BIT="$gl_cv_host_cpu_c_abi_32bit"
|
||||||
|
])
|
62
src/cthulhu/dynamic_api_manager.py
Normal file
62
src/cthulhu/dynamic_api_manager.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import gi
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
from cthulhu import resource_manager
|
||||||
|
|
||||||
|
class DynamicApiManager():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.resourceManager = self.app.getResourceManager()
|
||||||
|
self.api = {'Cthulhu': self.app}
|
||||||
|
def registerAPI(self, key, value, application = '', contextName = None, overwrite = False):
|
||||||
|
if not overwrite:
|
||||||
|
try:
|
||||||
|
d = self.api[application][key]
|
||||||
|
return
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
# add profile
|
||||||
|
try:
|
||||||
|
d = self.api[application]
|
||||||
|
except KeyError:
|
||||||
|
self.api[application]= {}
|
||||||
|
# add dynamic API
|
||||||
|
self.api[application][key] = value
|
||||||
|
resourceContext = self.resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceEntry = resource_manager.ResourceEntry('api', key, value, value, key)
|
||||||
|
resourceContext.addAPI(application, key, resourceEntry)
|
||||||
|
|
||||||
|
def unregisterAPI(self, key, application = '', contextName = None):
|
||||||
|
ok = False
|
||||||
|
try:
|
||||||
|
del self.api[application][key]
|
||||||
|
ok = True
|
||||||
|
except:
|
||||||
|
print('API Key: "{}/{}" not found,'.format(application, key))
|
||||||
|
resourceContext = self.resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceContext.removeAPI(application, key)
|
||||||
|
return ok
|
||||||
|
def getAPI(self, key, application = '', fallback = True):
|
||||||
|
# get dynamic API
|
||||||
|
api = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
api = self.api[application][key]
|
||||||
|
return api
|
||||||
|
except:
|
||||||
|
if not fallback:
|
||||||
|
print('API Key: "{}/{}" not found,'.format(application, key))
|
||||||
|
return None
|
||||||
|
|
||||||
|
# we already tried this
|
||||||
|
if application == '':
|
||||||
|
return api
|
||||||
|
|
||||||
|
try:
|
||||||
|
api = self.api[application]['']
|
||||||
|
except:
|
||||||
|
print('API Key: "{}/{}" not found,'.format(application, key))
|
||||||
|
|
||||||
|
return api
|
160
src/cthulhu/plugin.py
Normal file
160
src/cthulhu/plugin.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import os, inspect
|
||||||
|
import gi
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
class Plugin():
|
||||||
|
#__gtype_name__ = 'BasePlugin'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.API = None
|
||||||
|
self.pluginInfo = None
|
||||||
|
self.moduleDir = ''
|
||||||
|
self.hidden = False
|
||||||
|
self.moduleName = ''
|
||||||
|
self.name = ''
|
||||||
|
self.version = ''
|
||||||
|
self.website = ''
|
||||||
|
self.authors = []
|
||||||
|
self.buildIn = False
|
||||||
|
self.description = ''
|
||||||
|
self.iconName = ''
|
||||||
|
self.copyright = ''
|
||||||
|
self.dependencies = False
|
||||||
|
self.helpUri = ''
|
||||||
|
self.dataDir = ''
|
||||||
|
self.translationContext = None
|
||||||
|
def setApp(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.dynamicApiManager = app.getDynamicApiManager()
|
||||||
|
self.signalManager = app.getSignalManager()
|
||||||
|
|
||||||
|
def getApp(self):
|
||||||
|
return self.app
|
||||||
|
def setPluginInfo(self, pluginInfo):
|
||||||
|
self.pluginInfo = pluginInfo
|
||||||
|
self.updatePluginInfoAttributes()
|
||||||
|
def getPluginInfo(self):
|
||||||
|
return self.pluginInfo
|
||||||
|
def updatePluginInfoAttributes(self):
|
||||||
|
self.moduleDir = ''
|
||||||
|
self.hidden = False
|
||||||
|
self.moduleName = ''
|
||||||
|
self.name = ''
|
||||||
|
self.version = ''
|
||||||
|
self.website = ''
|
||||||
|
self.authors = []
|
||||||
|
self.buildIn = False
|
||||||
|
self.description = ''
|
||||||
|
self.iconName = ''
|
||||||
|
self.copyright = ''
|
||||||
|
self.dependencies = False
|
||||||
|
self.helpUri = ''
|
||||||
|
self.dataDir = ''
|
||||||
|
pluginInfo = self.getPluginInfo()
|
||||||
|
if pluginInfo == None:
|
||||||
|
return
|
||||||
|
self.moduleName = self.getApp().getPluginSystemManager().getPluginModuleName(pluginInfo)
|
||||||
|
self.name = self.getApp().getPluginSystemManager().getPluginName(pluginInfo)
|
||||||
|
self.version = self.getApp().getPluginSystemManager().getPluginVersion(pluginInfo)
|
||||||
|
self.moduleDir = self.getApp().getPluginSystemManager().getPluginModuleDir(pluginInfo)
|
||||||
|
self.buildIn = self.getApp().getPluginSystemManager().isPluginBuildIn(pluginInfo)
|
||||||
|
self.description = self.getApp().getPluginSystemManager().getPluginDescription(pluginInfo)
|
||||||
|
self.hidden = self.getApp().getPluginSystemManager().isPluginHidden(pluginInfo)
|
||||||
|
self.website = self.getApp().getPluginSystemManager().getPluginWebsite(pluginInfo)
|
||||||
|
self.authors = self.getApp().getPluginSystemManager().getPluginAuthors(pluginInfo)
|
||||||
|
self.iconName = self.getApp().getPluginSystemManager().getPluginIconName(pluginInfo)
|
||||||
|
self.copyright = self.getApp().getPluginSystemManager().getPluginCopyright(pluginInfo)
|
||||||
|
self.dependencies = self.getApp().getPluginSystemManager().getPluginDependencies(pluginInfo)
|
||||||
|
|
||||||
|
#settings = self.getApp().getPluginSystemManager().getPluginSettings(pluginInfo)
|
||||||
|
#hasDependencies = self.getApp().getPluginSystemManager().hasPluginDependency(pluginInfo)
|
||||||
|
|
||||||
|
#externalData = self.getApp().getPluginSystemManager().getPluginExternalData(pluginInfo)
|
||||||
|
self.helpUri = self.getApp().getPluginSystemManager().getPlugingetHelpUri(pluginInfo)
|
||||||
|
self.dataDir = self.getApp().getPluginSystemManager().getPluginDataDir(pluginInfo)
|
||||||
|
self.updateTranslationContext()
|
||||||
|
|
||||||
|
def updateTranslationContext(self, domain = None, localeDir = None, language = None, fallbackToCthulhuTranslation = True):
|
||||||
|
self.translationContext = None
|
||||||
|
useLocaleDir = '{}/locale/'.format(self.getModuleDir())
|
||||||
|
if localeDir:
|
||||||
|
if os.path.isdir(localeDir):
|
||||||
|
useLocaleDir = localeDir
|
||||||
|
useName = self.getModuleName()
|
||||||
|
useDomain = useName
|
||||||
|
if domain:
|
||||||
|
useDomain = domain
|
||||||
|
useLanguage = None
|
||||||
|
if language:
|
||||||
|
useLanguage = language
|
||||||
|
self.translationContext = self.getApp().getTranslationManager().initTranslation(useName, domain=useDomain, localeDir=useLocaleDir, language=useLanguage, fallbackToCthulhuTranslation=fallbackToCthulhuTranslation)
|
||||||
|
# Point _ to the translation object in the globals namespace of the caller frame
|
||||||
|
try:
|
||||||
|
callerFrame = inspect.currentframe().f_back
|
||||||
|
# Install our gettext and ngettext function to the upper frame
|
||||||
|
callerFrame.f_globals['_'] = self.translationContext.gettext
|
||||||
|
callerFrame.f_globals['ngettext'] = self.translationContext.ngettext
|
||||||
|
finally:
|
||||||
|
del callerFrame # Avoid reference problems with frames (per python docs)
|
||||||
|
def getTranslationContext(self):
|
||||||
|
return self.translationContext
|
||||||
|
def isPluginBuildIn(self):
|
||||||
|
return self.buildIn
|
||||||
|
def isPluginHidden(self):
|
||||||
|
return self.hidden
|
||||||
|
def getAuthors(self):
|
||||||
|
return self.authors
|
||||||
|
def getCopyright(self):
|
||||||
|
return self.copyright
|
||||||
|
def getDataDir(self):
|
||||||
|
return self.dataDir
|
||||||
|
def getDependencies(self):
|
||||||
|
return self.dependencies
|
||||||
|
def getDescription(self):
|
||||||
|
return self.description
|
||||||
|
def getgetHelpUri(self):
|
||||||
|
return self.helpUri
|
||||||
|
def getIconName(self):
|
||||||
|
return self.iconName
|
||||||
|
def getModuleDir(self):
|
||||||
|
return self.moduleDir
|
||||||
|
def getModuleName(self):
|
||||||
|
return self.moduleName
|
||||||
|
def getName(self):
|
||||||
|
return self.name
|
||||||
|
def getVersion(self):
|
||||||
|
return self.version
|
||||||
|
def getWebsite(self):
|
||||||
|
return self.website
|
||||||
|
def getSetting(key):
|
||||||
|
#self.getModuleName())
|
||||||
|
return None
|
||||||
|
def setSetting(key, value):
|
||||||
|
#self.getModuleName())
|
||||||
|
pass
|
||||||
|
def registerGestureByString(self, function, name, gestureString, learnModeEnabled = True):
|
||||||
|
keybinding = self.getApp().getAPIHelper().registerGestureByString(function, name, gestureString, 'default', 'cthulhu', learnModeEnabled, contextName = self.getModuleName())
|
||||||
|
return keybinding
|
||||||
|
def unregisterShortcut(self, function, name, gestureString, learnModeEnabled = True):
|
||||||
|
ok = self.getApp().getAPIHelper().unregisterShortcut(keybinding, contextName = self.getModuleName())
|
||||||
|
return ok
|
||||||
|
def registerSignal(self, signalName, signalFlag = GObject.SignalFlags.RUN_LAST, closure = GObject.TYPE_NONE, accumulator=()):
|
||||||
|
ok = self.signalManager.registerSignal(signalName, signalFlag, closure, accumulator, contextName = self.getModuleName())
|
||||||
|
return ok
|
||||||
|
def unregisterSignal(self, signalName):
|
||||||
|
# how to unregister?
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connectSignal(self, signalName, function, param = None):
|
||||||
|
signalID = self.signalManager.connectSignal(signalName, function, param, contextName = self.getModuleName())
|
||||||
|
return signalID
|
||||||
|
def disconnectSignalByFunction(self, function):
|
||||||
|
# need get mapped function
|
||||||
|
mappedFunction = function
|
||||||
|
self.signalManager.disconnectSignalByFunction(mappedFunction, contextName = self.getModuleName())
|
||||||
|
|
||||||
|
def registerAPI(self, key, value, application = ''):
|
||||||
|
ok = self.dynamicApiManager.registerAPI(key, value, application = application, contextName = self.getModuleName())
|
||||||
|
return ok
|
||||||
|
def unregisterAPI(self, key, application = ''):
|
||||||
|
self.dynamicApiManager.unregisterAPI(key, application = application, contextName = self.getModuleName())
|
520
src/cthulhu/plugin_system_manager.py
Normal file
520
src/cthulhu/plugin_system_manager.py
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
#!/bin/python
|
||||||
|
"""PluginManager for loading cthulhu plugins."""
|
||||||
|
import os, inspect, sys, tarfile, shutil
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
version = sys.version[:3] # we only need major.minor version.
|
||||||
|
if version in ["3.3","3.4"]:
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
|
else: # Python 3.5+, no support for python < 3.3.
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
gi.require_version('Atspi', '2.0')
|
||||||
|
from gi.repository import Atspi
|
||||||
|
|
||||||
|
from cthulhu import resource_manager
|
||||||
|
|
||||||
|
class API(GObject.GObject):
|
||||||
|
"""Interface that gives access to all the objects of Cthulhu."""
|
||||||
|
def __init__(self, app):
|
||||||
|
GObject.GObject.__init__(self)
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
class PluginType(IntEnum):
|
||||||
|
"""Types of plugins we support, depending on their directory location."""
|
||||||
|
# pylint: disable=comparison-with-callable,inconsistent-return-statements,no-else-return
|
||||||
|
# SYSTEM: provides system wide plugins
|
||||||
|
SYSTEM = 1
|
||||||
|
# USER: provides per user plugin
|
||||||
|
USER = 2
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.value == PluginType.SYSTEM:
|
||||||
|
return _("System plugin")
|
||||||
|
elif self.value == PluginType.USER:
|
||||||
|
return _("User plugin")
|
||||||
|
|
||||||
|
def get_root_dir(self):
|
||||||
|
"""Returns the directory where this type of plugins can be found."""
|
||||||
|
if self.value == PluginType.SYSTEM:
|
||||||
|
return os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe())))) + '/plugins'
|
||||||
|
elif self.value == PluginType.USER:
|
||||||
|
return os.path.expanduser('~') + '/.local/share/cthulhu/plugins'
|
||||||
|
|
||||||
|
|
||||||
|
class PluginSystemManager():
|
||||||
|
"""Orca Plugin Manager to handle a set of plugins.
|
||||||
|
Attributes:
|
||||||
|
DEFAULT_LOADERS (tuple): Default loaders used by the plugin manager. For
|
||||||
|
possible values see
|
||||||
|
https://developer.gnome.org/libpeas/stable/PeasEngine.html#peas-engine-enable-loader
|
||||||
|
"""
|
||||||
|
DEFAULT_LOADERS = ("python3", )
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.engine = Peas.Engine.get_default()
|
||||||
|
|
||||||
|
for loader in self.DEFAULT_LOADERS:
|
||||||
|
self.engine.enable_loader(loader)
|
||||||
|
|
||||||
|
self._setupPluginsDir()
|
||||||
|
self._setupExtensionSet()
|
||||||
|
|
||||||
|
if self.app:
|
||||||
|
self.gsettingsManager = self.app.getSettingsManager()
|
||||||
|
# settings else:
|
||||||
|
# settings self.gsettingsManager = gsettings_manager.getSettingsManager(self.app)
|
||||||
|
self._activePlugins = []
|
||||||
|
self._ignorePluginModulePath = []
|
||||||
|
@property
|
||||||
|
def plugins(self):
|
||||||
|
"""Gets the engine's plugin list."""
|
||||||
|
return self.engine.get_plugin_list()
|
||||||
|
@classmethod
|
||||||
|
def getPluginType(cls, pluginInfo):
|
||||||
|
"""Gets the PluginType for the specified Peas.PluginInfo."""
|
||||||
|
paths = [pluginInfo.get_data_dir(), PluginType.SYSTEM.get_root_dir()]
|
||||||
|
if os.path.commonprefix(paths) == PluginType.SYSTEM.get_root_dir():
|
||||||
|
return PluginType.SYSTEM
|
||||||
|
return PluginType.USER
|
||||||
|
|
||||||
|
def getExtension(self, pluginInfo):
|
||||||
|
if not pluginInfo:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.extension_set.get_extension(pluginInfo)
|
||||||
|
def rescanPlugins(self):
|
||||||
|
self.engine.garbage_collect()
|
||||||
|
self.engine.rescan_plugins()
|
||||||
|
def getApp(self):
|
||||||
|
return self.app
|
||||||
|
def getPluginInfoByName(self, pluginName, pluginType=PluginType.USER):
|
||||||
|
"""Gets the plugin info for the specified plugin name.
|
||||||
|
Args:
|
||||||
|
pluginName (str): The name from the .plugin file of the module.
|
||||||
|
Returns:
|
||||||
|
Peas.PluginInfo: The plugin info if it exists. Otherwise, `None`.
|
||||||
|
"""
|
||||||
|
for pluginInfo in self.plugins:
|
||||||
|
if pluginInfo.get_module_name() == pluginName and PluginSystemManager.getPluginType(pluginInfo) == pluginType:
|
||||||
|
return pluginInfo
|
||||||
|
return None
|
||||||
|
def getActivePlugins(self):
|
||||||
|
return self._activePlugins
|
||||||
|
def setActivePlugins(self, activePlugins):
|
||||||
|
self._activePlugins = activePlugins
|
||||||
|
self.syncAllPluginsActive()
|
||||||
|
def isPluginBuildIn(self, pluginInfo):
|
||||||
|
return pluginInfo.is_builtin()
|
||||||
|
def isPluginHidden(self, pluginInfo):
|
||||||
|
return pluginInfo.is_hidden()
|
||||||
|
def getPluginAuthors(self, pluginInfo):
|
||||||
|
return pluginInfo.get_authors()
|
||||||
|
def getPluginCopyright(self, pluginInfo):
|
||||||
|
return pluginInfo.get_copyright()
|
||||||
|
def getPluginDataDir(self, pluginInfo):
|
||||||
|
return pluginInfo.get_data_dir()
|
||||||
|
def getPluginDependencies(self, pluginInfo):
|
||||||
|
return pluginInfo.get_dependencies()
|
||||||
|
def getPluginDescription(self, pluginInfo):
|
||||||
|
return pluginInfo.get_description()
|
||||||
|
def getPlugingetHelpUri(self, pluginInfo):
|
||||||
|
return pluginInfo.get_help_uri()
|
||||||
|
def getPluginIconName(self, pluginInfo):
|
||||||
|
return pluginInfo.get_icon_name()
|
||||||
|
def getPluginModuleDir(self, pluginInfo):
|
||||||
|
return pluginInfo.get_module_dir()
|
||||||
|
def getPluginModuleName(self, pluginInfo):
|
||||||
|
return pluginInfo.get_module_name()
|
||||||
|
def getPluginName(self, pluginInfo):
|
||||||
|
return pluginInfo.get_name()
|
||||||
|
def getPluginSettings(self, pluginInfo):
|
||||||
|
return pluginInfo.get_settings()
|
||||||
|
def getPluginVersion(self, pluginInfo):
|
||||||
|
return pluginInfo.get_version()
|
||||||
|
def getPluginWebsite(self, pluginInfo):
|
||||||
|
return pluginInfo.get_website()
|
||||||
|
# has_dependency and get_external_data seems broken-> takes exactly 2 arguments (1 given) but documentation doesnt say any parameter
|
||||||
|
#def hasPluginDependency(self, pluginInfo):
|
||||||
|
# return pluginInfo.has_dependency()
|
||||||
|
#def getPluginExternalData(self, pluginInfo):
|
||||||
|
# return pluginInfo.get_external_data()
|
||||||
|
def isPluginAvailable(self, pluginInfo):
|
||||||
|
try:
|
||||||
|
return pluginInfo.is_available()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
def isPluginLoaded(self, pluginInfo):
|
||||||
|
try:
|
||||||
|
return pluginInfo.is_loaded()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getIgnoredPlugins(self):
|
||||||
|
return self._ignorePluginModulePath
|
||||||
|
def setIgnoredPlugins(self, pluginModulePath, ignored):
|
||||||
|
if pluginModulePath.endswith('/'):
|
||||||
|
pluginModulePath = pluginModulePath[:-1]
|
||||||
|
if ignored:
|
||||||
|
if not pluginModulePath in self.getIgnoredPlugins():
|
||||||
|
self._ignorePluginModulePath.append(pluginModulePath)
|
||||||
|
else:
|
||||||
|
if pluginModulePath in self.getIgnoredPlugins():
|
||||||
|
self._ignorePluginModulePath.remove(pluginModulePath)
|
||||||
|
|
||||||
|
def setPluginActive(self, pluginInfo, active):
|
||||||
|
if self.isPluginBuildIn(pluginInfo):
|
||||||
|
active = True
|
||||||
|
pluginName = self.getPluginModuleName(pluginInfo)
|
||||||
|
if active:
|
||||||
|
if not pluginName in self.getActivePlugins():
|
||||||
|
if self.loadPlugin(pluginInfo):
|
||||||
|
self._activePlugins.append(pluginName )
|
||||||
|
else:
|
||||||
|
if pluginName in self.getActivePlugins():
|
||||||
|
if self.unloadPlugin(pluginInfo):
|
||||||
|
self._activePlugins.remove(pluginName )
|
||||||
|
def isPluginActive(self, pluginInfo):
|
||||||
|
if self.isPluginBuildIn(pluginInfo):
|
||||||
|
return True
|
||||||
|
if self.isPluginLoaded(pluginInfo):
|
||||||
|
return True
|
||||||
|
active_plugin_names = self.getActivePlugins()
|
||||||
|
return self.getPluginModuleName(pluginInfo) in active_plugin_names
|
||||||
|
def syncAllPluginsActive(self, ForceAllPlugins=False):
|
||||||
|
self.unloadAllPlugins(ForceAllPlugins)
|
||||||
|
self.loadAllPlugins(ForceAllPlugins)
|
||||||
|
|
||||||
|
def loadAllPlugins(self, ForceAllPlugins=False):
|
||||||
|
"""Loads plugins from settings."""
|
||||||
|
for pluginInfo in self.plugins:
|
||||||
|
if self.isPluginActive(pluginInfo) or ForceAllPlugins:
|
||||||
|
self.loadPlugin(pluginInfo)
|
||||||
|
|
||||||
|
def loadPlugin(self, pluginInfo):
|
||||||
|
resourceManager = self.getApp().getResourceManager()
|
||||||
|
moduleName = pluginInfo.get_module_name()
|
||||||
|
try:
|
||||||
|
if pluginInfo not in self.plugins:
|
||||||
|
print("Plugin missing: {}".format(moduleName))
|
||||||
|
return False
|
||||||
|
resourceManager.addResourceContext(moduleName)
|
||||||
|
self.engine.load_plugin(pluginInfo)
|
||||||
|
except Exception as e:
|
||||||
|
print('loadPlugin:',e)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def unloadAllPlugins(self, ForceAllPlugins=False):
|
||||||
|
"""Loads plugins from settings."""
|
||||||
|
for pluginInfo in self.plugins:
|
||||||
|
if not self.isPluginActive(pluginInfo) or ForceAllPlugins:
|
||||||
|
self.unloadPlugin(pluginInfo)
|
||||||
|
|
||||||
|
def unloadPlugin(self, pluginInfo):
|
||||||
|
resourceManager = self.getApp().getResourceManager()
|
||||||
|
moduleName = pluginInfo.get_module_name()
|
||||||
|
try:
|
||||||
|
if pluginInfo not in self.plugins:
|
||||||
|
print("Plugin missing: {}".format(moduleName))
|
||||||
|
return False
|
||||||
|
if self.isPluginBuildIn(pluginInfo):
|
||||||
|
return False
|
||||||
|
self.engine.unload_plugin(pluginInfo)
|
||||||
|
self.getApp().getResourceManager().removeResourceContext(moduleName)
|
||||||
|
self.engine.garbage_collect()
|
||||||
|
except Exception as e:
|
||||||
|
print('unloadPlugin:',e)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
def installPlugin(self, pluginFilePath, pluginType=PluginType.USER):
|
||||||
|
if not self.isValidPluginFile(pluginFilePath):
|
||||||
|
return False
|
||||||
|
pluginFolder = pluginType.get_root_dir()
|
||||||
|
if not pluginFolder.endswith('/'):
|
||||||
|
pluginFolder += '/'
|
||||||
|
if not os.path.exists(pluginFolder):
|
||||||
|
os.mkdir(pluginFolder)
|
||||||
|
else:
|
||||||
|
if not os.path.isdir(pluginFolder):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
with tarfile.open(pluginFilePath) as tar:
|
||||||
|
tar.extractall(path=pluginFolder)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
pluginModulePath = self.getModuleDirByPluginFile(pluginFilePath)
|
||||||
|
if pluginModulePath != '':
|
||||||
|
pluginModulePath = pluginFolder + pluginModulePath
|
||||||
|
self.setIgnoredPlugins(pluginModulePath[:-1], False) # without ending /
|
||||||
|
print('install', pluginFilePath)
|
||||||
|
self.callPackageTriggers(pluginModulePath, 'onPostInstall')
|
||||||
|
self.rescanPlugins()
|
||||||
|
|
||||||
|
return True
|
||||||
|
def getModuleDirByPluginFile(self, pluginFilePath):
|
||||||
|
if not isinstance(pluginFilePath, str):
|
||||||
|
return ''
|
||||||
|
if pluginFilePath == '':
|
||||||
|
return ''
|
||||||
|
if not os.path.exists(pluginFilePath):
|
||||||
|
return ''
|
||||||
|
try:
|
||||||
|
with tarfile.open(pluginFilePath) as tar:
|
||||||
|
tarMembers = tar.getmembers()
|
||||||
|
for tarMember in tarMembers:
|
||||||
|
if tarMember.isdir():
|
||||||
|
return tarMember.name
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return ''
|
||||||
|
def isValidPluginFile(self, pluginFilePath):
|
||||||
|
if not isinstance(pluginFilePath, str):
|
||||||
|
return False
|
||||||
|
if pluginFilePath == '':
|
||||||
|
return False
|
||||||
|
if not os.path.exists(pluginFilePath):
|
||||||
|
return False
|
||||||
|
pluginFolder = ''
|
||||||
|
pluginFileExists = False
|
||||||
|
packageFileExists = False
|
||||||
|
try:
|
||||||
|
with tarfile.open(pluginFilePath) as tar:
|
||||||
|
tarMembers = tar.getmembers()
|
||||||
|
for tarMember in tarMembers:
|
||||||
|
if tarMember.isdir():
|
||||||
|
if pluginFolder == '':
|
||||||
|
pluginFolder = tarMember.name
|
||||||
|
if tarMember.isfile():
|
||||||
|
if tarMember.name.endswith('.plugin'):
|
||||||
|
pluginFileExists = True
|
||||||
|
if tarMember.name.endswith('package.py'):
|
||||||
|
pluginFileExists = True
|
||||||
|
if not tarMember.name.startswith(pluginFolder):
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
return pluginFileExists
|
||||||
|
def uninstallPlugin(self, pluginInfo):
|
||||||
|
if self.isPluginBuildIn(pluginInfo):
|
||||||
|
return False
|
||||||
|
# do we want to allow removing system plugins?
|
||||||
|
if PluginSystemManager.getPluginType(pluginInfo) == PluginType.SYSTEM:
|
||||||
|
return False
|
||||||
|
pluginFolder = pluginInfo.get_data_dir()
|
||||||
|
if not pluginFolder.endswith('/'):
|
||||||
|
pluginFolder += '/'
|
||||||
|
if not os.path.isdir(pluginFolder):
|
||||||
|
return False
|
||||||
|
if self.isPluginActive(pluginInfo):
|
||||||
|
self.setPluginActive(pluginInfo, False)
|
||||||
|
SettingsManager = self.app.getSettingsManager()
|
||||||
|
# TODO SettingsManager.set_settings_value_list('active-plugins', self.getActivePlugins())
|
||||||
|
self.callPackageTriggers(pluginFolder, 'onPreUninstall')
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.rmtree(pluginFolder, ignore_errors=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
self.setIgnoredPlugins(pluginFolder, True)
|
||||||
|
self.rescanPlugins()
|
||||||
|
|
||||||
|
return True
|
||||||
|
def callPackageTriggers(self, pluginPath, trigger):
|
||||||
|
if not os.path.exists(pluginPath):
|
||||||
|
return
|
||||||
|
if not pluginPath.endswith('/'):
|
||||||
|
pluginPath += '/'
|
||||||
|
packageModulePath = pluginPath + 'package.py'
|
||||||
|
if not os.path.isfile(packageModulePath):
|
||||||
|
return
|
||||||
|
if not os.access(packageModulePath, os.R_OK):
|
||||||
|
return
|
||||||
|
package = self.getApp().getAPIHelper().importModule('package', packageModulePath)
|
||||||
|
|
||||||
|
if trigger == 'onPostInstall':
|
||||||
|
try:
|
||||||
|
package.onPostInstall(pluginPath, self.getApp())
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
elif trigger == 'onPreUninstall':
|
||||||
|
try:
|
||||||
|
package.onPreUninstall(pluginPath, self.getApp())
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def _setupExtensionSet(self):
|
||||||
|
plugin_iface = API(self.getApp())
|
||||||
|
self.extension_set = Peas.ExtensionSet.new(self.engine,
|
||||||
|
Peas.Activatable,
|
||||||
|
["object"],
|
||||||
|
[plugin_iface])
|
||||||
|
self.extension_set.connect("extension-removed",
|
||||||
|
self.__extensionRemoved)
|
||||||
|
self.extension_set.connect("extension-added",
|
||||||
|
self.__extensionAdded)
|
||||||
|
|
||||||
|
def _setupPluginsDir(self):
|
||||||
|
system_plugins_dir = PluginType.SYSTEM.get_root_dir()
|
||||||
|
user_plugins_dir = PluginType.USER.get_root_dir()
|
||||||
|
if os.path.exists(user_plugins_dir):
|
||||||
|
self.engine.add_search_path(user_plugins_dir)
|
||||||
|
if os.path.exists(system_plugins_dir):
|
||||||
|
self.engine.add_search_path(system_plugins_dir)
|
||||||
|
|
||||||
|
def __extensionRemoved(self, unusedSet, pluginInfo, extension):
|
||||||
|
extension.deactivate()
|
||||||
|
|
||||||
|
def __extensionAdded(self, unusedSet, pluginInfo, extension):
|
||||||
|
extension.setApp(self.getApp())
|
||||||
|
extension.setPluginInfo(pluginInfo)
|
||||||
|
extension.activate()
|
||||||
|
def __loadedPlugins(self, engine, unusedSet):
|
||||||
|
"""Handles the changing of the loaded plugin list."""
|
||||||
|
self.getApp().settings.ActivePlugins = engine.get_property("loaded-plugins")
|
||||||
|
|
||||||
|
class APIHelper():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.cthulhuKeyBindings = None
|
||||||
|
|
||||||
|
'''
|
||||||
|
_pluginAPIManager.seCthulhuAPI('Logger', _logger)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('SettingsManager', _settingsManager)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('ScriptManager', _scriptManager)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('EventManager', _eventManager)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Speech', speech)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Sound', sound)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Braille', braille)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Debug', debug)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Messages', messages)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('MouseReview', mouse_review)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('NotificationMessages', notification_messages)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('CthulhuState', cthulhu_state)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('CthulhuPlatform', cthulhu_platform)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Settings', settings)
|
||||||
|
_pluginAPIManager.setCthulhuAPI('Keybindings', keybindings)
|
||||||
|
'''
|
||||||
|
def outputMessage(self, Message, interrupt=False):
|
||||||
|
settings = self.app.getDynamicApiManager().getAPI('Settings')
|
||||||
|
braille = self.app.getDynamicApiManager().getAPI('Braille')
|
||||||
|
speech = self.app.getDynamicApiManager().getAPI('Speech')
|
||||||
|
if speech != None:
|
||||||
|
if (settings.enableSpeech):
|
||||||
|
if interrupt:
|
||||||
|
speech.cancel()
|
||||||
|
if Message != '':
|
||||||
|
speech.speak(Message)
|
||||||
|
if braille != None:
|
||||||
|
if (settings.enableBraille):
|
||||||
|
braille.displayMessage(Message)
|
||||||
|
def createInputEventHandler(self, function, name, learnModeEnabled=True):
|
||||||
|
EventManager = self.app.getDynamicApiManager().getAPI('EventManager')
|
||||||
|
newInputEventHandler = EventManager.input_event.InputEventHandler(function, name, learnModeEnabled)
|
||||||
|
return newInputEventHandler
|
||||||
|
def registerGestureByString(self, function, name, gestureString, profile, application, learnModeEnabled = True, contextName = None):
|
||||||
|
gestureList = gestureString.split(',')
|
||||||
|
registeredGestures = []
|
||||||
|
for gesture in gestureList:
|
||||||
|
if gesture.startswith('kb:'):
|
||||||
|
shortcutString = gesture[3:]
|
||||||
|
registuredGesture = self.registerShortcutByString(function, name, shortcutString, profile, application, learnModeEnabled, contextName=contextName)
|
||||||
|
if registuredGesture:
|
||||||
|
registeredGestures.append(registuredGesture)
|
||||||
|
return registeredGestures
|
||||||
|
def registerShortcutByString(self, function, name, shortcutString, profile, application, learnModeEnabled = True, contextName = None):
|
||||||
|
keybindings = self.app.getDynamicApiManager().getAPI('Keybindings')
|
||||||
|
settings = self.app.getDynamicApiManager().getAPI('Settings')
|
||||||
|
resourceManager = self.app.getResourceManager()
|
||||||
|
|
||||||
|
clickCount = 0
|
||||||
|
cthulhuKey = False
|
||||||
|
shiftKey = False
|
||||||
|
ctrlKey = False
|
||||||
|
altKey = False
|
||||||
|
key = ''
|
||||||
|
shortcutList = shortcutString.split('+')
|
||||||
|
for shortcutElement in shortcutList:
|
||||||
|
shortcutElementLower = shortcutElement.lower()
|
||||||
|
if shortcutElementLower == 'press':
|
||||||
|
clickCount += 1
|
||||||
|
elif shortcutElement == 'cthulhu':
|
||||||
|
cthulhuKey = True
|
||||||
|
elif shortcutElementLower == 'shift':
|
||||||
|
shiftKey = True
|
||||||
|
elif shortcutElementLower == 'control':
|
||||||
|
ctrlKey = True
|
||||||
|
elif shortcutElementLower == 'alt':
|
||||||
|
altKey = True
|
||||||
|
else:
|
||||||
|
key = shortcutElementLower
|
||||||
|
if clickCount == 0:
|
||||||
|
clickCount = 1
|
||||||
|
|
||||||
|
if self.cthulhuKeyBindings == None:
|
||||||
|
self.cthulhuKeyBindings = keybindings.KeyBindings()
|
||||||
|
|
||||||
|
tryFunction = resource_manager.TryFunction(function)
|
||||||
|
newInputEventHandler = self.createInputEventHandler(tryFunction.runInputEvent, name, learnModeEnabled)
|
||||||
|
|
||||||
|
currModifierMask = keybindings.NO_MODIFIER_MASK
|
||||||
|
if cthulhuKey:
|
||||||
|
currModifierMask = currModifierMask | 1 << keybindings.MODIFIER_ORCA
|
||||||
|
if shiftKey:
|
||||||
|
currModifierMask = currModifierMask | 1 << Atspi.ModifierType.SHIFT
|
||||||
|
if altKey:
|
||||||
|
currModifierMask = currModifierMask | 1 << Atspi.ModifierType.ALT
|
||||||
|
if ctrlKey:
|
||||||
|
currModifierMask = currModifierMask | 1 << Atspi.ModifierType.CONTROL
|
||||||
|
|
||||||
|
newKeyBinding = keybindings.KeyBinding(key, keybindings.defaultModifierMask, currModifierMask, newInputEventHandler, clickCount)
|
||||||
|
self.cthulhuKeyBindings.add(newKeyBinding)
|
||||||
|
|
||||||
|
settings.keyBindingsMap["default"] = self.cthulhuKeyBindings
|
||||||
|
|
||||||
|
if contextName:
|
||||||
|
resourceContext = resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceEntry = resource_manager.ResourceEntry('keyboard', newKeyBinding, function, tryFunction, shortcutString)
|
||||||
|
resourceContext.addGesture(profile, application, newKeyBinding, resourceEntry)
|
||||||
|
|
||||||
|
return newKeyBinding
|
||||||
|
|
||||||
|
def unregisterShortcut(self, KeyBindingToRemove, contextName = None):
|
||||||
|
ok = False
|
||||||
|
keybindings = self.app.getDynamicApiManager().getAPI('Keybindings')
|
||||||
|
settings = self.app.getDynamicApiManager().getAPI('Settings')
|
||||||
|
resourceManager = self.app.getResourceManager()
|
||||||
|
|
||||||
|
if self.cthulhuKeyBindings == None:
|
||||||
|
self.cthulhuKeyBindings = keybindings.KeyBindings()
|
||||||
|
try:
|
||||||
|
self.cthulhuKeyBindings.remove(KeyBindingToRemove)
|
||||||
|
settings.keyBindingsMap["default"] = self.cthulhuKeyBindings
|
||||||
|
ok = True
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if contextName:
|
||||||
|
resourceContext = resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceContext.removeGesture(KeyBindingToRemove)
|
||||||
|
|
||||||
|
return ok
|
||||||
|
def importModule(self, moduleName, moduleLocation):
|
||||||
|
if version in ["3.3","3.4"]:
|
||||||
|
return SourceFileLoader(moduleName, moduleLocation).load_module()
|
||||||
|
else:
|
||||||
|
spec = importlib.util.spec_from_file_location(moduleName, moduleLocation)
|
||||||
|
driver_mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(driver_mod)
|
||||||
|
return driver_mod
|
6
src/cthulhu/plugins/ByeCthulhu/ByeCthulhu.plugin
Normal file
6
src/cthulhu/plugins/ByeCthulhu/ByeCthulhu.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=ByeCthulhu
|
||||||
|
Loader=python3
|
||||||
|
Name=Stop announcement for cthulhu
|
||||||
|
Description=Test plugin for cthulhu
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
27
src/cthulhu/plugins/ByeCthulhu/ByeCthulhu.py
Normal file
27
src/cthulhu/plugins/ByeCthulhu/ByeCthulhu.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class ByeOrca(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'ByeCthulhu'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.connectSignal("stop-application-completed", self.process)
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def process(self, app):
|
||||||
|
messages = app.getDynamicApiManager().getAPI('Messages')
|
||||||
|
activeScript = app.getDynamicApiManager().getAPI('CthulhuState').activeScript
|
||||||
|
activeScript.presentationInterrupt()
|
||||||
|
activeScript.presentMessage(messages.STOP_ORCA, resetStyles=False)
|
7
src/cthulhu/plugins/ByeCthulhu/Makefile.am
Normal file
7
src/cthulhu/plugins/ByeCthulhu/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
ByeCthulhu.plugin \
|
||||||
|
ByeCthulhu.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/ByeCthulhu
|
||||||
|
|
0
src/cthulhu/plugins/ByeCthulhu/__init__.py
Normal file
0
src/cthulhu/plugins/ByeCthulhu/__init__.py
Normal file
6
src/cthulhu/plugins/CapsLockHack/CapsLockHack.plugin
Normal file
6
src/cthulhu/plugins/CapsLockHack/CapsLockHack.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=CapsLockHack
|
||||||
|
Loader=python3
|
||||||
|
Name=Caps Lock Hack
|
||||||
|
Description=Fix Capslock sometimes switch on / off when its used as modifier
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
119
src/cthulhu/plugins/CapsLockHack/CapsLockHack.py
Normal file
119
src/cthulhu/plugins/CapsLockHack/CapsLockHack.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
from threading import Thread, Lock
|
||||||
|
import subprocess, time, re, os
|
||||||
|
|
||||||
|
class CapsLockHack(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
__gtype_name__ = 'CapsLockHack'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
self.lock = Lock()
|
||||||
|
self.active = False
|
||||||
|
self.workerThread = Thread(target=self.worker)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
"""Enable or disable use of the caps lock key as an Cthulhu modifier key."""
|
||||||
|
self.interpretCapsLineProg = re.compile(
|
||||||
|
r'^\s*interpret\s+Caps[_+]Lock[_+]AnyOfOrNone\s*\(all\)\s*{\s*$', re.I)
|
||||||
|
self.normalCapsLineProg = re.compile(
|
||||||
|
r'^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Lock\s*\)\s*;\s*$', re.I)
|
||||||
|
self.interpretShiftLineProg = re.compile(
|
||||||
|
r'^\s*interpret\s+Shift[_+]Lock[_+]AnyOf\s*\(\s*Shift\s*\+\s*Lock\s*\)\s*{\s*$', re.I)
|
||||||
|
self.normalShiftLineProg = re.compile(
|
||||||
|
r'^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Shift\s*\)\s*;\s*$', re.I)
|
||||||
|
self.disabledModLineProg = re.compile(
|
||||||
|
r'^\s*action\s*=\s*NoAction\s*\(\s*\)\s*;\s*$', re.I)
|
||||||
|
self.normalCapsLine = ' action= LockMods(modifiers=Lock);'
|
||||||
|
self.normalShiftLine = ' action= LockMods(modifiers=Shift);'
|
||||||
|
self.disabledModLine = ' action= NoAction();'
|
||||||
|
self.activateWorker()
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
self.deactivateWorker()
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def deactivateWorker(self):
|
||||||
|
with self.lock:
|
||||||
|
self.active = False
|
||||||
|
self.workerThread.join()
|
||||||
|
def activateWorker(self):
|
||||||
|
with self.lock:
|
||||||
|
self.active = True
|
||||||
|
self.workerThread.start()
|
||||||
|
def isActive(self):
|
||||||
|
with self.lock:
|
||||||
|
return self.active
|
||||||
|
def worker(self):
|
||||||
|
"""Makes an Cthulhu-specific Xmodmap so that the keys behave as we
|
||||||
|
need them to do. This is especially the case for the Cthulhu modifier.
|
||||||
|
"""
|
||||||
|
API = self.object
|
||||||
|
capsLockCleared = False
|
||||||
|
settings = API.app.getDynamicApiManager().getAPI('Settings')
|
||||||
|
time.sleep(3)
|
||||||
|
while self.isActive():
|
||||||
|
if "Caps_Lock" in settings.orcaModifierKeys \
|
||||||
|
or "Shift_Lock" in settings.orcaModifierKeys:
|
||||||
|
self.setCapsLockAsOrcaModifier(True)
|
||||||
|
capsLockCleared = True
|
||||||
|
elif capsLockCleared:
|
||||||
|
self.setCapsLockAsOrcaModifier(False)
|
||||||
|
capsLockCleared = False
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def setCapsLockAsOrcaModifier(self, enable):
|
||||||
|
originalXmodmap = None
|
||||||
|
lines = None
|
||||||
|
try:
|
||||||
|
originalXmodmap = subprocess.check_output(['xkbcomp', os.environ['DISPLAY'], '-'])
|
||||||
|
lines = originalXmodmap.decode('UTF-8').split('\n')
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
foundCapsInterpretSection = False
|
||||||
|
foundShiftInterpretSection = False
|
||||||
|
modified = False
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if not foundCapsInterpretSection and not foundShiftInterpretSection:
|
||||||
|
if self.interpretCapsLineProg.match(line):
|
||||||
|
foundCapsInterpretSection = True
|
||||||
|
elif self.interpretShiftLineProg.match(line):
|
||||||
|
foundShiftInterpretSection = True
|
||||||
|
elif foundCapsInterpretSection:
|
||||||
|
if enable:
|
||||||
|
if self.normalCapsLineProg.match(line):
|
||||||
|
lines[i] = self.disabledModLine
|
||||||
|
modified = True
|
||||||
|
else:
|
||||||
|
if self.disabledModLineProg.match(line):
|
||||||
|
lines[i] = self.normalCapsLine
|
||||||
|
modified = True
|
||||||
|
if line.find('}'):
|
||||||
|
foundCapsInterpretSection = False
|
||||||
|
else: # foundShiftInterpretSection
|
||||||
|
if enable:
|
||||||
|
if self.normalShiftLineProg.match(line):
|
||||||
|
lines[i] = self.disabledModLine
|
||||||
|
modified = True
|
||||||
|
else:
|
||||||
|
if self.disabledModLineProg.match(line):
|
||||||
|
lines[i] = self.normalShiftLine
|
||||||
|
modified = True
|
||||||
|
if line.find('}'):
|
||||||
|
foundShiftInterpretSection = False
|
||||||
|
if modified:
|
||||||
|
newXmodMap = bytes('\n'.join(lines), 'UTF-8')
|
||||||
|
self.setXmodmap(newXmodMap)
|
||||||
|
def setXmodmap(self, xkbmap):
|
||||||
|
"""Set the keyboard map using xkbcomp."""
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen(['xkbcomp', '-w0', '-', os.environ['DISPLAY']],
|
||||||
|
stdin=subprocess.PIPE, stdout=None, stderr=None)
|
||||||
|
p.communicate(xkbmap)
|
||||||
|
except:
|
||||||
|
pass
|
7
src/cthulhu/plugins/CapsLockHack/Makefile.am
Normal file
7
src/cthulhu/plugins/CapsLockHack/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
CapsLockHack.plugin \
|
||||||
|
CapsLockHack.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/CapsLockHack
|
||||||
|
|
0
src/cthulhu/plugins/CapsLockHack/__init__.py
Normal file
0
src/cthulhu/plugins/CapsLockHack/__init__.py
Normal file
6
src/cthulhu/plugins/Clipboard/Clipboard.plugin
Normal file
6
src/cthulhu/plugins/Clipboard/Clipboard.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=Clipboard
|
||||||
|
Loader=python3
|
||||||
|
Name=Clipboard
|
||||||
|
Description=Present the content of the current clipboard
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
76
src/cthulhu/plugins/Clipboard/Clipboard.py
Normal file
76
src/cthulhu/plugins/Clipboard/Clipboard.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi, os
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
|
class Clipboard(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'Clipboard'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.registerGestureByString(self.speakClipboard, _('clipboard'), 'kb:cthulhu+c')
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def speakClipboard(self, script=None, inputEvent=None):
|
||||||
|
API = self.object
|
||||||
|
Message = self.getClipboard()
|
||||||
|
API.app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(Message, resetStyles=False)
|
||||||
|
return True
|
||||||
|
def getClipboard(self):
|
||||||
|
Message = ""
|
||||||
|
FoundClipboardContent = False
|
||||||
|
# Get Clipboard
|
||||||
|
ClipboardObj = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||||
|
|
||||||
|
ClipboardText = ClipboardObj.wait_for_text()
|
||||||
|
ClipboardImage = ClipboardObj.wait_for_image()
|
||||||
|
ClipboardURI = ClipboardObj.wait_for_uris()
|
||||||
|
if (ClipboardText != None):
|
||||||
|
FoundClipboardContent = True
|
||||||
|
if (ClipboardObj.wait_is_uris_available()):
|
||||||
|
noOfObjects = 0
|
||||||
|
noOfFolder = 0
|
||||||
|
noOfFiles = 0
|
||||||
|
noOfDisks = 0
|
||||||
|
noOfLinks = 0
|
||||||
|
for Uri in ClipboardURI:
|
||||||
|
if Uri == '':
|
||||||
|
continue
|
||||||
|
noOfObjects += 1
|
||||||
|
uriWithoutProtocoll = Uri[Uri.find('://') + 3:]
|
||||||
|
Message += " " + Uri[Uri.rfind('/') + 1:] + " "
|
||||||
|
if (os.path.isdir(uriWithoutProtocoll)):
|
||||||
|
noOfFolder += 1
|
||||||
|
Message = Message + _("Folder") #Folder
|
||||||
|
if (os.path.isfile(uriWithoutProtocoll)):
|
||||||
|
noOfFiles += 1
|
||||||
|
Message = Message + _("File") #File
|
||||||
|
if (os.path.ismount(uriWithoutProtocoll)):
|
||||||
|
noOfDisks += 1
|
||||||
|
Message = Message + _("Disk") #Mountpoint
|
||||||
|
if (os.path.islink(uriWithoutProtocoll)):
|
||||||
|
noOfLinks += 1
|
||||||
|
Message = Message + _("Link") #Link
|
||||||
|
if (noOfObjects > 1):
|
||||||
|
Message = str(noOfObjects) + _(" Objects in clipboard ") + Message # X Objects in Clipboard Object Object
|
||||||
|
else:
|
||||||
|
Message = str(noOfObjects) + _(" Object in clipboard ") + Message # 1 Object in Clipboard Object
|
||||||
|
else:
|
||||||
|
Message = _("Text in clipboard ") + ClipboardText # Text in Clipboard
|
||||||
|
|
||||||
|
if (ClipboardImage != None):
|
||||||
|
FoundClipboardContent = True
|
||||||
|
Message = _("The clipboard contains a image") # Image is in Clipboard
|
||||||
|
|
||||||
|
if (not FoundClipboardContent):
|
||||||
|
Message = _("The clipboard is empty")
|
||||||
|
return Message
|
7
src/cthulhu/plugins/Clipboard/Makefile.am
Normal file
7
src/cthulhu/plugins/Clipboard/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
Clipboard.plugin \
|
||||||
|
Clipboard.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/Clipboard
|
||||||
|
|
0
src/cthulhu/plugins/Clipboard/__init__.py
Normal file
0
src/cthulhu/plugins/Clipboard/__init__.py
Normal file
6
src/cthulhu/plugins/Date/Date.plugin
Normal file
6
src/cthulhu/plugins/Date/Date.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=Date
|
||||||
|
Loader=python3
|
||||||
|
Name=Date
|
||||||
|
Description=Present the current date
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
33
src/cthulhu/plugins/Date/Date.py
Normal file
33
src/cthulhu/plugins/Date/Date.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi, time
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
class Date(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'Date'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.connectSignal("setup-inputeventhandlers-completed", self.setupCompatBinding)
|
||||||
|
def setupCompatBinding(self, app):
|
||||||
|
cmdnames = app.getDynamicApiManager().getAPI('Cmdnames')
|
||||||
|
inputEventHandlers = app.getDynamicApiManager().getAPI('inputEventHandlers')
|
||||||
|
inputEventHandlers['presentDateHandler'] = app.getAPIHelper().createInputEventHandler(self.presentDate, cmdnames.PRESENT_CURRENT_DATE)
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
inputEventHandlers = API.app.getDynamicApiManager().getAPI('inputEventHandlers')
|
||||||
|
del inputEventHandlers['presentDateHandler']
|
||||||
|
def presentDate(self, script=None, inputEvent=None):
|
||||||
|
""" Presents the current time. """
|
||||||
|
API = self.object
|
||||||
|
settings_manager = API.app.getDynamicApiManager().getAPI('SettingsManager')
|
||||||
|
_settingsManager = settings_manager.getManager()
|
||||||
|
dateFormat = _settingsManager.getSetting('presentDateFormat')
|
||||||
|
message = time.strftime(dateFormat, time.localtime())
|
||||||
|
API.app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(message, resetStyles=False)
|
||||||
|
return True
|
7
src/cthulhu/plugins/Date/Makefile.am
Normal file
7
src/cthulhu/plugins/Date/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
Date.plugin \
|
||||||
|
Date.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/Date
|
||||||
|
|
0
src/cthulhu/plugins/Date/__init__.py
Normal file
0
src/cthulhu/plugins/Date/__init__.py
Normal file
6
src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.plugin
Normal file
6
src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=HelloCthulhu
|
||||||
|
Loader=python3
|
||||||
|
Name=Cthulhu say hello
|
||||||
|
Description=startup announcement for Cthulhu
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
23
src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.py
Normal file
23
src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
class HelloCthulhu(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'HelloCthulhu'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.connectSignal("start-application-completed", self.process)
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def process(self, app):
|
||||||
|
messages = app.getDynamicApiManager().getAPI('Messages')
|
||||||
|
app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(messages.START_ORCA, resetStyles=False)
|
7
src/cthulhu/plugins/HelloCthulhu/Makefile.am
Normal file
7
src/cthulhu/plugins/HelloCthulhu/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
HelloCthulhu.plugin \
|
||||||
|
HelloCthulhu.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/HelloCthulhu
|
||||||
|
|
0
src/cthulhu/plugins/HelloCthulhu/__init__.py
Normal file
0
src/cthulhu/plugins/HelloCthulhu/__init__.py
Normal file
6
src/cthulhu/plugins/HelloWorld/HelloWorld.plugin
Normal file
6
src/cthulhu/plugins/HelloWorld/HelloWorld.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=HelloWorld
|
||||||
|
Loader=python3
|
||||||
|
Name=Hello World (python3)
|
||||||
|
Description=Test plugin for orca
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
24
src/cthulhu/plugins/HelloWorld/HelloWorld.py
Normal file
24
src/cthulhu/plugins/HelloWorld/HelloWorld.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
class HelloWorld(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
__gtype_name__ = 'helloworld'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.registerGestureByString(self.speakTest, _('hello world'), 'kb:cthulhu+z')
|
||||||
|
print('activate hello world plugin')
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
print('deactivate hello world plugin')
|
||||||
|
def speakTest(self, script=None, inputEvent=None):
|
||||||
|
API = self.object
|
||||||
|
API.app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage('hello world', resetStyles=False)
|
||||||
|
return True
|
7
src/cthulhu/plugins/HelloWorld/Makefile.am
Normal file
7
src/cthulhu/plugins/HelloWorld/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
HelloWorld.plugin \
|
||||||
|
HelloWorld.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/HelloWorld
|
||||||
|
|
0
src/cthulhu/plugins/HelloWorld/__init__.py
Normal file
0
src/cthulhu/plugins/HelloWorld/__init__.py
Normal file
4
src/cthulhu/plugins/Makefile.am
Normal file
4
src/cthulhu/plugins/Makefile.am
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
SUBDIRS = Clipboard HelloWorld SelfVoice Time MouseReview Date ByeCthulhu HelloCthulhu PluginManager CapsLockHack
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins
|
||||||
|
|
7
src/cthulhu/plugins/MouseReview/Makefile.am
Normal file
7
src/cthulhu/plugins/MouseReview/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
MouseReview.plugin \
|
||||||
|
MouseReview.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/MouseReview
|
||||||
|
|
6
src/cthulhu/plugins/MouseReview/MouseReview.plugin
Normal file
6
src/cthulhu/plugins/MouseReview/MouseReview.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=MouseReview
|
||||||
|
Loader=python3
|
||||||
|
Name=Mouse Review
|
||||||
|
Description=Review whats below the mouse coursor
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
754
src/cthulhu/plugins/MouseReview/MouseReview.py
Normal file
754
src/cthulhu/plugins/MouseReview/MouseReview.py
Normal file
@ -0,0 +1,754 @@
|
|||||||
|
# Mouse reviewer for Orca
|
||||||
|
#
|
||||||
|
# Copyright 2008 Eitan Isaacson
|
||||||
|
# Copyright 2016 Igalia, S.L.
|
||||||
|
#
|
||||||
|
# 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., Franklin Street, Fifth Floor,
|
||||||
|
# Boston MA 02110-1301 USA.
|
||||||
|
|
||||||
|
"""Mouse review mode."""
|
||||||
|
|
||||||
|
__id__ = "$Id$"
|
||||||
|
__version__ = "$Revision$"
|
||||||
|
__date__ = "$Date$"
|
||||||
|
__copyright__ = "Copyright (c) 2008 Eitan Isaacson" \
|
||||||
|
"Copyright (c) 2016 Igalia, S.L."
|
||||||
|
__license__ = "LGPL"
|
||||||
|
|
||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi, math, time
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
gi.require_version("Atspi", "2.0")
|
||||||
|
from gi.repository import Atspi
|
||||||
|
|
||||||
|
from gi.repository import Gdk
|
||||||
|
try:
|
||||||
|
gi.require_version("Wnck", "3.0")
|
||||||
|
from gi.repository import Wnck
|
||||||
|
_mouseReviewCapable = True
|
||||||
|
except Exception:
|
||||||
|
_mouseReviewCapable = False
|
||||||
|
|
||||||
|
# compatibility layer, see MouseReview.do_activate
|
||||||
|
debug = None
|
||||||
|
event_manager = None
|
||||||
|
cthulhu = None
|
||||||
|
cthulhu_state = None
|
||||||
|
script_manager = None
|
||||||
|
settings_manager = None
|
||||||
|
speech = None
|
||||||
|
messages = None
|
||||||
|
cmdnames = None
|
||||||
|
emitRegionChanged = None
|
||||||
|
_scriptManager = None
|
||||||
|
_settingsManager = None
|
||||||
|
AXObject = None
|
||||||
|
AXUtilities = None
|
||||||
|
keybindings = None
|
||||||
|
input_event = None
|
||||||
|
|
||||||
|
class MouseReview(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'MouseReview'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
global _mouseReviewCapable
|
||||||
|
if not _mouseReviewCapable:
|
||||||
|
return
|
||||||
|
global debug
|
||||||
|
global event_manager
|
||||||
|
global cthulhu_state
|
||||||
|
global script_manager
|
||||||
|
global settings_manager
|
||||||
|
global speech
|
||||||
|
global _scriptManager
|
||||||
|
global _settingsManager
|
||||||
|
global emitRegionChanged
|
||||||
|
global messages
|
||||||
|
global cmdnames
|
||||||
|
global AXObject
|
||||||
|
global AXUtilities
|
||||||
|
global keybindings
|
||||||
|
global input_event
|
||||||
|
debug= API.app.getDynamicApiManager().getAPI('Debug')
|
||||||
|
event_manager = API.app.getDynamicApiManager().getAPI('EventManager')
|
||||||
|
messages = API.app.getDynamicApiManager().getAPI('Messages')
|
||||||
|
cmdnames = API.app.getDynamicApiManager().getAPI('Cmdnames')
|
||||||
|
cthulhu_state = API.app.getDynamicApiManager().getAPI('CthulhuState')
|
||||||
|
script_manager = API.app.getDynamicApiManager().getAPI('ScriptManager')
|
||||||
|
settings_manager = API.app.getDynamicApiManager().getAPI('SettingsManager')
|
||||||
|
speech = API.app.getDynamicApiManager().getAPI('Speech')
|
||||||
|
emitRegionChanged = API.app.getDynamicApiManager().getAPI('EmitRegionChanged')
|
||||||
|
_scriptManager = script_manager.getManager()
|
||||||
|
_settingsManager = settings_manager.getManager()
|
||||||
|
AXObject = API.app.getDynamicApiManager().getAPI('AXObject')
|
||||||
|
AXUtilities = API.app.getDynamicApiManager().getAPI('AXUtilities')
|
||||||
|
keybindings = API.app.getDynamicApiManager().getAPI('Keybindings')
|
||||||
|
input_event = API.app.getDynamicApiManager().getAPI('InputEvent')
|
||||||
|
mouse_review = MouseReviewer()
|
||||||
|
self.registerAPI('MouseReview', mouse_review)
|
||||||
|
self.Initialize(API.app)
|
||||||
|
self.connectSignal("setup-inputeventhandlers-completed", self.setupCompatBinding)
|
||||||
|
self.connectSignal("load-setting-completed", self.Initialize)
|
||||||
|
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
global _mouseReviewCapable
|
||||||
|
if not _mouseReviewCapable:
|
||||||
|
return
|
||||||
|
mouse_review = API.app.getDynamicApiManager().getAPI('MouseReview')
|
||||||
|
|
||||||
|
mouse_review.deactivate()
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def setupCompatBinding(self, app):
|
||||||
|
API = self.object
|
||||||
|
mouse_review = API.app.getDynamicApiManager().getAPI('MouseReview')
|
||||||
|
cmdnames = API.app.getDynamicApiManager().getAPI('Cmdnames')
|
||||||
|
inputEventHandlers = API.app.getDynamicApiManager().getAPI('inputEventHandlers')
|
||||||
|
inputEventHandlers['toggleMouseReviewHandler'] = API.app.getAPIHelper().createInputEventHandler(mouse_review.toggle, cmdnames.MOUSE_REVIEW_TOGGLE)
|
||||||
|
def Initialize(self, app):
|
||||||
|
mouse_review = app.getDynamicApiManager().getAPI('MouseReview')
|
||||||
|
settings_manager = app.getDynamicApiManager().getAPI('SettingsManager')
|
||||||
|
_settingsManager = settings_manager.getManager()
|
||||||
|
if _settingsManager.getSetting('enableMouseReview'):
|
||||||
|
mouse_review.activate()
|
||||||
|
else:
|
||||||
|
mouse_review.deactivate()
|
||||||
|
|
||||||
|
class _StringContext:
|
||||||
|
"""The textual information associated with an _ItemContext."""
|
||||||
|
|
||||||
|
def __init__(self, obj, script=None, string="", start=0, end=0):
|
||||||
|
"""Initialize the _StringContext.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- string: The human-consumable string
|
||||||
|
- obj: The accessible object associated with this string
|
||||||
|
- start: The start offset with respect to entire text, if one exists
|
||||||
|
- end: The end offset with respect to the entire text, if one exists
|
||||||
|
- script: The script associated with the accessible object
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._obj = obj
|
||||||
|
self._script = script
|
||||||
|
self._string = string
|
||||||
|
self._start = start
|
||||||
|
self._end = end
|
||||||
|
self._boundingBox = 0, 0, 0, 0
|
||||||
|
if script:
|
||||||
|
self._boundingBox = script.utilities.getTextBoundingBox(obj, start, end)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return other is not None \
|
||||||
|
and self._obj == other._obj \
|
||||||
|
and self._string == other._string \
|
||||||
|
and self._start == other._start \
|
||||||
|
and self._end == other._end
|
||||||
|
|
||||||
|
def isSubstringOf(self, other):
|
||||||
|
"""Returns True if this is a substring of other."""
|
||||||
|
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not (self._obj and other._obj):
|
||||||
|
return False
|
||||||
|
|
||||||
|
thisBox = self.getBoundingBox()
|
||||||
|
if thisBox == (0, 0, 0, 0):
|
||||||
|
return False
|
||||||
|
|
||||||
|
otherBox = other.getBoundingBox()
|
||||||
|
if otherBox == (0, 0, 0, 0):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# We get various and sundry results for the bounding box if the implementor
|
||||||
|
# included newline characters as part of the word or line at offset. Try to
|
||||||
|
# detect this and adjust the bounding boxes before getting the intersection.
|
||||||
|
if thisBox[3] != otherBox[3] and self._obj == other._obj:
|
||||||
|
thisNewLineCount = self._string.count("\n")
|
||||||
|
if thisNewLineCount and thisBox[3] / thisNewLineCount == otherBox[3]:
|
||||||
|
thisBox = *thisBox[0:3], otherBox[3]
|
||||||
|
|
||||||
|
if self._script.utilities.intersection(thisBox, otherBox) != thisBox:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not (self._string and self._string.strip() in other._string):
|
||||||
|
return False
|
||||||
|
|
||||||
|
msg = f"MOUSE REVIEW: '{self._string}' is substring of '{other._string}'"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getBoundingBox(self):
|
||||||
|
"""Returns the bounding box associated with this context's range."""
|
||||||
|
|
||||||
|
return self._boundingBox
|
||||||
|
|
||||||
|
def getString(self):
|
||||||
|
"""Returns the string associated with this context."""
|
||||||
|
|
||||||
|
return self._string
|
||||||
|
|
||||||
|
def present(self):
|
||||||
|
"""Presents this context to the user."""
|
||||||
|
|
||||||
|
if not self._script:
|
||||||
|
msg = "MOUSE REVIEW: Not presenting due to lack of script"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._string:
|
||||||
|
msg = "MOUSE REVIEW: Not presenting due to lack of string"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
voice = self._script.speechGenerator.voice(obj=self._obj, string=self._string)
|
||||||
|
string = self._script.utilities.adjustForRepeats(self._string)
|
||||||
|
# TODO
|
||||||
|
#orca.emitRegionChanged(self._obj, self._start, self._end, orca.MOUSE_REVIEW)
|
||||||
|
emitRegionChanged(self._obj, self._start, self._end, "mouse-review")
|
||||||
|
|
||||||
|
|
||||||
|
self._script.speakMessage(string, voice=voice, interrupt=False)
|
||||||
|
self._script.displayBrailleMessage(self._string, -1)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class _ItemContext:
|
||||||
|
"""Holds all the information of the item at a specified point."""
|
||||||
|
|
||||||
|
def __init__(self, x=0, y=0, obj=None, boundary=None, frame=None, script=None):
|
||||||
|
"""Initialize the _ItemContext.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- x: The X coordinate
|
||||||
|
- y: The Y coordinate
|
||||||
|
- obj: The accessible object of interest at that coordinate
|
||||||
|
- boundary: The accessible-text boundary type
|
||||||
|
- frame: The containing accessible object (often a top-level window)
|
||||||
|
- script: The script associated with the accessible object
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._x = x
|
||||||
|
self._y = y
|
||||||
|
self._obj = obj
|
||||||
|
self._boundary = boundary
|
||||||
|
self._frame = frame
|
||||||
|
self._script = script
|
||||||
|
self._string = self._getStringContext()
|
||||||
|
self._time = time.time()
|
||||||
|
self._boundingBox = 0, 0, 0, 0
|
||||||
|
if script:
|
||||||
|
self._boundingBox = script.utilities.getBoundingBox(obj)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return other is not None \
|
||||||
|
and self._frame == other._frame \
|
||||||
|
and self._obj == other._obj \
|
||||||
|
and self._string == other._string
|
||||||
|
|
||||||
|
def _treatAsDuplicate(self, prior):
|
||||||
|
if self._obj != prior._obj or self._frame != prior._frame:
|
||||||
|
msg = "MOUSE REVIEW: Not a duplicate: different objects"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.getString() and prior.getString() and not self._isSubstringOf(prior):
|
||||||
|
msg = "MOUSE REVIEW: Not a duplicate: not a substring of"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._x == prior._x and self._y == prior._y:
|
||||||
|
msg = "MOUSE REVIEW: Treating as duplicate: mouse didn't move"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
interval = self._time - prior._time
|
||||||
|
if interval > 0.5:
|
||||||
|
msg = f"MOUSE REVIEW: Not a duplicate: was {interval:.2f}s ago"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
msg = "MOUSE REVIEW: Treating as duplicate"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _treatAsSingleObject(self):
|
||||||
|
if not AXObject.supports_text(self._obj):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not self._obj.queryText().characterCount:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _getStringContext(self):
|
||||||
|
"""Returns the _StringContext associated with the specified point."""
|
||||||
|
|
||||||
|
if not (self._script and self._obj):
|
||||||
|
return _StringContext(self._obj)
|
||||||
|
|
||||||
|
if self._treatAsSingleObject():
|
||||||
|
return _StringContext(self._obj, self._script)
|
||||||
|
|
||||||
|
string, start, end = self._script.utilities.textAtPoint(
|
||||||
|
self._obj, self._x, self._y, boundary=self._boundary)
|
||||||
|
if string:
|
||||||
|
string = self._script.utilities.expandEOCs(self._obj, start, end)
|
||||||
|
|
||||||
|
return _StringContext(self._obj, self._script, string, start, end)
|
||||||
|
|
||||||
|
def _getContainer(self):
|
||||||
|
roles = [Atspi.Role.DIALOG,
|
||||||
|
Atspi.Role.FRAME,
|
||||||
|
Atspi.Role.LAYERED_PANE,
|
||||||
|
Atspi.Role.MENU,
|
||||||
|
Atspi.Role.PAGE_TAB,
|
||||||
|
Atspi.Role.TOOL_BAR,
|
||||||
|
Atspi.Role.WINDOW]
|
||||||
|
return AXObject.find_ancestor(self._obj, lambda x: AXObject.get_role(x) in roles)
|
||||||
|
|
||||||
|
def _isSubstringOf(self, other):
|
||||||
|
"""Returns True if this is a substring of other."""
|
||||||
|
|
||||||
|
return self._string.isSubstringOf(other._string)
|
||||||
|
|
||||||
|
def getObject(self):
|
||||||
|
"""Returns the accessible object associated with this context."""
|
||||||
|
|
||||||
|
return self._obj
|
||||||
|
|
||||||
|
def getBoundingBox(self):
|
||||||
|
"""Returns the bounding box associated with this context."""
|
||||||
|
|
||||||
|
x, y, width, height = self._string.getBoundingBox()
|
||||||
|
if not (width or height):
|
||||||
|
return self._boundingBox
|
||||||
|
|
||||||
|
return x, y, width, height
|
||||||
|
|
||||||
|
def getString(self):
|
||||||
|
"""Returns the string associated with this context."""
|
||||||
|
|
||||||
|
return self._string.getString()
|
||||||
|
|
||||||
|
def getTime(self):
|
||||||
|
"""Returns the time associated with this context."""
|
||||||
|
|
||||||
|
return self._time
|
||||||
|
|
||||||
|
def _isInlineChild(self, prior):
|
||||||
|
if not self._obj or not prior._obj:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if AXObject.get_parent(prior._obj) != self._obj:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._treatAsSingleObject():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return AXUtilities.is_link(prior._obj)
|
||||||
|
|
||||||
|
def present(self, prior):
|
||||||
|
"""Presents this context to the user."""
|
||||||
|
|
||||||
|
if self == prior or self._treatAsDuplicate(prior):
|
||||||
|
msg = "MOUSE REVIEW: Not presenting due to no change"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
interrupt = self._obj and self._obj != prior._obj \
|
||||||
|
or math.sqrt((self._x - prior._x)**2 + (self._y - prior._y)**2) > 25
|
||||||
|
|
||||||
|
if interrupt:
|
||||||
|
self._script.presentationInterrupt()
|
||||||
|
|
||||||
|
if self._frame and self._frame != prior._frame:
|
||||||
|
self._script.presentObject(self._frame,
|
||||||
|
alreadyFocused=True,
|
||||||
|
inMouseReview=True,
|
||||||
|
interrupt=True)
|
||||||
|
|
||||||
|
if self._script.utilities.containsOnlyEOCs(self._obj):
|
||||||
|
msg = "MOUSE REVIEW: Not presenting object which contains only EOCs"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._obj and self._obj != prior._obj and not self._isInlineChild(prior):
|
||||||
|
priorObj = prior._obj or self._getContainer()
|
||||||
|
# TODO
|
||||||
|
#orca.emitRegionChanged(self._obj, mode=orca.MOUSE_REVIEW)
|
||||||
|
emitRegionChanged(self._obj, mode="mouse-review")
|
||||||
|
|
||||||
|
self._script.presentObject(self._obj, priorObj=priorObj, inMouseReview=True)
|
||||||
|
if self._string.getString() == AXObject.get_name(self._obj):
|
||||||
|
return True
|
||||||
|
if not self._script.utilities.isEditableTextArea(self._obj):
|
||||||
|
return True
|
||||||
|
if AXUtilities.is_table_cell(self._obj) \
|
||||||
|
and self._string.getString() == self._script.utilities.displayedText(self._obj):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self._string != prior._string and self._string.present():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MouseReviewer:
|
||||||
|
"""Main class for the mouse-review feature."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._active = _settingsManager.getSetting("enableMouseReview")
|
||||||
|
self._currentMouseOver = _ItemContext()
|
||||||
|
self._pointer = None
|
||||||
|
self._workspace = None
|
||||||
|
self._windows = []
|
||||||
|
self._all_windows = []
|
||||||
|
self._handlerIds = {}
|
||||||
|
self._eventListener = Atspi.EventListener.new(self._listener)
|
||||||
|
self.inMouseEvent = False
|
||||||
|
self._handlers = self._setup_handlers()
|
||||||
|
self._bindings = self._setup_bindings()
|
||||||
|
|
||||||
|
if not _mouseReviewCapable:
|
||||||
|
msg = "MOUSE REVIEW ERROR: Wnck is not available"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
display = Gdk.Display.get_default()
|
||||||
|
try:
|
||||||
|
seat = Gdk.Display.get_default_seat(display)
|
||||||
|
self._pointer = seat.get_pointer()
|
||||||
|
except AttributeError:
|
||||||
|
msg = "MOUSE REVIEW ERROR: Gtk+ 3.20 is not available"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
msg = "MOUSE REVIEW ERROR: Exception getting pointer for default seat."
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._pointer:
|
||||||
|
msg = "MOUSE REVIEW ERROR: No pointer for default seat."
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._active:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.activate()
|
||||||
|
|
||||||
|
def get_bindings(self):
|
||||||
|
"""Returns the mouse-review keybindings."""
|
||||||
|
|
||||||
|
return self._bindings
|
||||||
|
|
||||||
|
def get_handlers(self):
|
||||||
|
"""Returns the mouse-review handlers."""
|
||||||
|
|
||||||
|
return self._handlers
|
||||||
|
|
||||||
|
def _setup_handlers(self):
|
||||||
|
"""Sets up and returns the mouse-review input event handlers."""
|
||||||
|
|
||||||
|
handlers = {}
|
||||||
|
|
||||||
|
handlers["toggleMouseReviewHandler"] = \
|
||||||
|
input_event.InputEventHandler(
|
||||||
|
self.toggle,
|
||||||
|
cmdnames.MOUSE_REVIEW_TOGGLE)
|
||||||
|
|
||||||
|
return handlers
|
||||||
|
|
||||||
|
def _setup_bindings(self):
|
||||||
|
"""Sets up and returns the mouse-review key bindings."""
|
||||||
|
|
||||||
|
bindings = keybindings.KeyBindings()
|
||||||
|
|
||||||
|
bindings.add(
|
||||||
|
keybindings.KeyBinding(
|
||||||
|
"",
|
||||||
|
keybindings.defaultModifierMask,
|
||||||
|
keybindings.NO_MODIFIER_MASK,
|
||||||
|
self._handlers.get("toggleMouseReviewHandler")))
|
||||||
|
|
||||||
|
return bindings
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
"""Activates mouse review."""
|
||||||
|
|
||||||
|
if not _mouseReviewCapable:
|
||||||
|
msg = "MOUSE REVIEW ERROR: Wnck is not available"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set up the initial object as the one with the focus to avoid
|
||||||
|
# presenting irrelevant info the first time.
|
||||||
|
obj = cthulhu_state.locusOfFocus
|
||||||
|
script = None
|
||||||
|
frame = None
|
||||||
|
if obj:
|
||||||
|
script = _scriptManager.getScript(AXObject.get_application(obj), obj)
|
||||||
|
if script:
|
||||||
|
frame = script.utilities.topLevelObject(obj)
|
||||||
|
self._currentMouseOver = _ItemContext(obj=obj, frame=frame, script=script)
|
||||||
|
|
||||||
|
self._eventListener.register("mouse:abs")
|
||||||
|
screen = Wnck.Screen.get_default()
|
||||||
|
if screen:
|
||||||
|
# On first startup windows and workspace are likely to be None,
|
||||||
|
# but the signals we connect to will get emitted when proper values
|
||||||
|
# become available; but in case we got disabled and re-enabled we
|
||||||
|
# have to get the initial values manually.
|
||||||
|
stacked = screen.get_windows_stacked()
|
||||||
|
if stacked:
|
||||||
|
stacked.reverse()
|
||||||
|
self._all_windows = stacked
|
||||||
|
self._workspace = screen.get_active_workspace()
|
||||||
|
if self._workspace:
|
||||||
|
self._update_workspace_windows()
|
||||||
|
|
||||||
|
i = screen.connect("window-stacking-changed", self._on_stacking_changed)
|
||||||
|
self._handlerIds[i] = screen
|
||||||
|
i = screen.connect("active-workspace-changed", self._on_workspace_changed)
|
||||||
|
self._handlerIds[i] = screen
|
||||||
|
|
||||||
|
self._active = True
|
||||||
|
|
||||||
|
def deactivate(self):
|
||||||
|
"""Deactivates mouse review."""
|
||||||
|
|
||||||
|
self._eventListener.deregister("mouse:abs")
|
||||||
|
for key, value in self._handlerIds.items():
|
||||||
|
value.disconnect(key)
|
||||||
|
self._handlerIds = {}
|
||||||
|
self._workspace = None
|
||||||
|
self._windows = []
|
||||||
|
self._all_windows = []
|
||||||
|
|
||||||
|
self._active = False
|
||||||
|
|
||||||
|
def getCurrentItem(self):
|
||||||
|
"""Returns the accessible object being reviewed."""
|
||||||
|
|
||||||
|
if not _mouseReviewCapable:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not self._active:
|
||||||
|
return None
|
||||||
|
|
||||||
|
obj = self._currentMouseOver.getObject()
|
||||||
|
|
||||||
|
if time.time() - self._currentMouseOver.getTime() > 0.1:
|
||||||
|
msg = f"MOUSE REVIEW: Treating {obj} as stale"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def toggle(self, script=None, event=None):
|
||||||
|
"""Toggle mouse reviewing on or off."""
|
||||||
|
|
||||||
|
if not _mouseReviewCapable:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._active = not self._active
|
||||||
|
_settingsManager.setSetting("enableMouseReview", self._active)
|
||||||
|
|
||||||
|
if not self._active:
|
||||||
|
self.deactivate()
|
||||||
|
msg = messages.MOUSE_REVIEW_DISABLED
|
||||||
|
else:
|
||||||
|
self.activate()
|
||||||
|
msg = messages.MOUSE_REVIEW_ENABLED
|
||||||
|
|
||||||
|
if cthulhu_state.activeScript:
|
||||||
|
cthulhu_state.activeScript.presentMessage(msg)
|
||||||
|
|
||||||
|
def _update_workspace_windows(self):
|
||||||
|
self._windows = [w for w in self._all_windows
|
||||||
|
if w.is_on_workspace(self._workspace)]
|
||||||
|
|
||||||
|
def _on_stacking_changed(self, screen):
|
||||||
|
"""Callback for Wnck's window-stacking-changed signal."""
|
||||||
|
|
||||||
|
stacked = screen.get_windows_stacked()
|
||||||
|
stacked.reverse()
|
||||||
|
self._all_windows = stacked
|
||||||
|
if self._workspace:
|
||||||
|
self._update_workspace_windows()
|
||||||
|
|
||||||
|
def _on_workspace_changed(self, screen, prev_ws=None):
|
||||||
|
"""Callback for Wnck's active-workspace-changed signal."""
|
||||||
|
|
||||||
|
self._workspace = screen.get_active_workspace()
|
||||||
|
self._update_workspace_windows()
|
||||||
|
|
||||||
|
def _contains_point(self, obj, x, y, coordType=None):
|
||||||
|
if coordType is None:
|
||||||
|
coordType = Atspi.CoordType.SCREEN
|
||||||
|
|
||||||
|
try:
|
||||||
|
return obj.queryComponent().contains(x, y, coordType)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _has_bounds(self, obj, bounds, coordType=None):
|
||||||
|
"""Returns True if the bounding box of obj is bounds."""
|
||||||
|
|
||||||
|
if coordType is None:
|
||||||
|
coordType = Atspi.CoordType.SCREEN
|
||||||
|
|
||||||
|
try:
|
||||||
|
extents = obj.queryComponent().getExtents(coordType)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return list(extents) == list(bounds)
|
||||||
|
|
||||||
|
def _accessible_window_at_point(self, pX, pY):
|
||||||
|
"""Returns the accessible window at the specified coordinates."""
|
||||||
|
|
||||||
|
window = None
|
||||||
|
for w in self._windows:
|
||||||
|
if w.is_minimized():
|
||||||
|
continue
|
||||||
|
|
||||||
|
x, y, width, height = w.get_geometry()
|
||||||
|
if x <= pX <= x + width and y <= pY <= y + height:
|
||||||
|
window = w
|
||||||
|
break
|
||||||
|
|
||||||
|
if not window:
|
||||||
|
return None
|
||||||
|
|
||||||
|
windowApp = window.get_application()
|
||||||
|
if not windowApp:
|
||||||
|
return None
|
||||||
|
|
||||||
|
app = AXUtilities.get_application_with_pid(windowApp.get_pid())
|
||||||
|
if not app:
|
||||||
|
return None
|
||||||
|
|
||||||
|
candidates = [o for o in AXObject.iter_children(
|
||||||
|
app, lambda x: self._contains_point(x, pX, pY))]
|
||||||
|
if len(candidates) == 1:
|
||||||
|
return candidates[0]
|
||||||
|
|
||||||
|
name = window.get_name()
|
||||||
|
matches = [o for o in candidates if AXObject.get_name(o) == name]
|
||||||
|
if len(matches) == 1:
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
bbox = window.get_client_window_geometry()
|
||||||
|
matches = [o for o in candidates if self._has_bounds(o, bbox)]
|
||||||
|
if len(matches) == 1:
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _on_mouse_moved(self, event):
|
||||||
|
"""Callback for mouse:abs events."""
|
||||||
|
|
||||||
|
screen, pX, pY = self._pointer.get_position()
|
||||||
|
window = self._accessible_window_at_point(pX, pY)
|
||||||
|
msg = "MOUSE REVIEW: Window at (%i, %i) is %s" % (pX, pY, window)
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
if not window:
|
||||||
|
return
|
||||||
|
|
||||||
|
script = _scriptManager.getScript(AXObject.get_application(window))
|
||||||
|
if not script:
|
||||||
|
return
|
||||||
|
|
||||||
|
if script.utilities.isDead(cthulhu_state.locusOfFocus):
|
||||||
|
menu = None
|
||||||
|
elif AXUtilities.is_menu(cthulhu_state.locusOfFocus):
|
||||||
|
menu = cthulhu_state.locusOfFocus
|
||||||
|
else:
|
||||||
|
menu = AXObject.find_ancestor(cthulhu_state.locusOfFocus, AXUtilities.is_menu)
|
||||||
|
|
||||||
|
screen, nowX, nowY = self._pointer.get_position()
|
||||||
|
if (pX, pY) != (nowX, nowY):
|
||||||
|
msg = "MOUSE REVIEW: Pointer moved again: (%i, %i)" % (nowX, nowY)
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
obj = script.utilities.descendantAtPoint(menu, pX, pY) \
|
||||||
|
or script.utilities.descendantAtPoint(window, pX, pY)
|
||||||
|
msg = "MOUSE REVIEW: Object at (%i, %i) is %s" % (pX, pY, obj)
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
|
script = _scriptManager.getScript(AXObject.get_application(window), obj)
|
||||||
|
if menu and obj and not AXObject.find_ancestor(obj, AXUtilities.is_menu):
|
||||||
|
if script.utilities.intersectingRegion(obj, menu) != (0, 0, 0, 0):
|
||||||
|
msg = f"MOUSE REVIEW: {obj} believed to be under {menu}"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
objDocument = script.utilities.getTopLevelDocumentForObject(obj)
|
||||||
|
if objDocument and script.utilities.inDocumentContent():
|
||||||
|
document = script.utilities.activeDocument()
|
||||||
|
if document != objDocument:
|
||||||
|
msg = f"MOUSE REVIEW: {obj} is not in active document {document}"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
screen, nowX, nowY = self._pointer.get_position()
|
||||||
|
if (pX, pY) != (nowX, nowY):
|
||||||
|
msg = "MOUSE REVIEW: Pointer moved again: (%i, %i)" % (nowX, nowY)
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
boundary = None
|
||||||
|
x, y, width, height = self._currentMouseOver.getBoundingBox()
|
||||||
|
if y <= pY <= y + height and self._currentMouseOver.getString():
|
||||||
|
boundary = Atspi.TextBoundaryType.WORD_START
|
||||||
|
elif obj == self._currentMouseOver.getObject():
|
||||||
|
boundary = Atspi.TextBoundaryType.LINE_START
|
||||||
|
elif AXUtilities.is_selectable(obj):
|
||||||
|
boundary = Atspi.TextBoundaryType.LINE_START
|
||||||
|
elif script.utilities.isMultiParagraphObject(obj):
|
||||||
|
boundary = Atspi.TextBoundaryType.LINE_START
|
||||||
|
|
||||||
|
new = _ItemContext(pX, pY, obj, boundary, window, script)
|
||||||
|
if new.present(self._currentMouseOver):
|
||||||
|
self._currentMouseOver = new
|
||||||
|
|
||||||
|
def _listener(self, event):
|
||||||
|
"""Generic listener, mainly to output debugging info."""
|
||||||
|
|
||||||
|
startTime = time.time()
|
||||||
|
msg = f"\nvvvvv PROCESS OBJECT EVENT {event.type} vvvvv"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, False)
|
||||||
|
|
||||||
|
if event.type.startswith("mouse:abs"):
|
||||||
|
self.inMouseEvent = True
|
||||||
|
self._on_mouse_moved(event)
|
||||||
|
self.inMouseEvent = False
|
||||||
|
|
||||||
|
msg = f"TOTAL PROCESSING TIME: {time.time() - startTime:.4f}\n"
|
||||||
|
msg += f"^^^^^ PROCESS OBJECT EVENT {event.type} ^^^^^\n"
|
||||||
|
debug.println(debug.LEVEL_INFO, msg, False)
|
0
src/cthulhu/plugins/MouseReview/__init__.py
Normal file
0
src/cthulhu/plugins/MouseReview/__init__.py
Normal file
8
src/cthulhu/plugins/PluginManager/Makefile.am
Normal file
8
src/cthulhu/plugins/PluginManager/Makefile.am
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
PluginManager.plugin \
|
||||||
|
PluginManager.py \
|
||||||
|
PluginManagerUi.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/PluginManager
|
||||||
|
|
14
src/cthulhu/plugins/PluginManager/PluginManager.plugin
Normal file
14
src/cthulhu/plugins/PluginManager/PluginManager.plugin
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=PluginManager
|
||||||
|
Loader=python3
|
||||||
|
Name=Plugin Manager
|
||||||
|
Description=Activate and Deactivate plugins
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
||||||
|
Website=
|
||||||
|
Version=1.0
|
||||||
|
Copyright=
|
||||||
|
Builtin=true
|
||||||
|
Hidden=true
|
||||||
|
Depends=
|
||||||
|
Icon=
|
||||||
|
Help=
|
36
src/cthulhu/plugins/PluginManager/PluginManager.py
Normal file
36
src/cthulhu/plugins/PluginManager/PluginManager.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
import PluginManagerUi
|
||||||
|
|
||||||
|
class PluginManager(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'PluginManager'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
self.pluginManagerUi = None
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.registerGestureByString(self.startPluginManagerUi, _('plugin manager'), 'kb:cthulhu+e')
|
||||||
|
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
|
||||||
|
def startPluginManagerUi(self, script=None, inputEvent=None):
|
||||||
|
self.showUI()
|
||||||
|
return True
|
||||||
|
def showUI(self):
|
||||||
|
API = self.object
|
||||||
|
if self.pluginManagerUi == None:
|
||||||
|
self.pluginManagerUi = PluginManagerUi.PluginManagerUi(API.app)
|
||||||
|
self.pluginManagerUi.setTranslationContext(self.getTranslationContext())
|
||||||
|
self.pluginManagerUi.createUI()
|
||||||
|
self.pluginManagerUi.run()
|
||||||
|
self.pluginManagerUi = None
|
||||||
|
else:
|
||||||
|
self.pluginManagerUi.present()
|
283
src/cthulhu/plugins/PluginManager/PluginManagerUi.py
Executable file
283
src/cthulhu/plugins/PluginManager/PluginManagerUi.py
Executable file
@ -0,0 +1,283 @@
|
|||||||
|
#!/bin/python
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
|
class PluginManagerUi(Gtk.ApplicationWindow):
|
||||||
|
def __init__(self, app, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs, title=_("Cthulhu Plugin Manager"))
|
||||||
|
self.app = app
|
||||||
|
self.translationContext = None
|
||||||
|
self.connect("destroy", self._onCancelButtonClicked)
|
||||||
|
self.connect('key-press-event', self._onKeyPressWindow)
|
||||||
|
def createUI(self):
|
||||||
|
self.set_default_size(650, 650)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
|
||||||
|
|
||||||
|
# pluginInfo (object) = 0
|
||||||
|
# name (str) = 1
|
||||||
|
# active (bool) = 2
|
||||||
|
# buildIn (bool) = 3
|
||||||
|
# dataDir (str) = 4
|
||||||
|
# moduleDir (str) = 5
|
||||||
|
# dependencies (object) = 6
|
||||||
|
# moduleName (str) = 7
|
||||||
|
# description (str) = 8
|
||||||
|
# authors (object) = 9
|
||||||
|
# website (str) = 10
|
||||||
|
# copyright (str) = 11
|
||||||
|
# version (str) = 12
|
||||||
|
# helpUri (str) = 13
|
||||||
|
# iconName (str) = 14
|
||||||
|
self.listStore = Gtk.ListStore(object,str, bool, bool, str, str,object,str,str,object,str,str,str,str,str)
|
||||||
|
|
||||||
|
self.treeView = Gtk.TreeView(model=self.listStore)
|
||||||
|
self.treeView.connect("row-activated", self._rowActivated)
|
||||||
|
self.treeView.connect('key-press-event', self._onKeyPressTreeView)
|
||||||
|
|
||||||
|
self.rendererText = Gtk.CellRendererText()
|
||||||
|
self.columnText = Gtk.TreeViewColumn(_("Name"), self.rendererText, text=1)
|
||||||
|
self.treeView.append_column(self.columnText)
|
||||||
|
|
||||||
|
self.rendererToggle = Gtk.CellRendererToggle()
|
||||||
|
self.rendererToggle.connect("toggled", self._onCellToggled)
|
||||||
|
|
||||||
|
self.columnToggle = Gtk.TreeViewColumn(_("Active"), self.rendererToggle, active=2)
|
||||||
|
self.treeView.append_column(self.columnToggle)
|
||||||
|
|
||||||
|
|
||||||
|
self.buttomBox = Gtk.Box(spacing=6)
|
||||||
|
self.mainVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
|
self.mainVBox.pack_start(self.treeView, True, True, 0)
|
||||||
|
self.mainVBox.pack_start(self.buttomBox, False, True, 0)
|
||||||
|
|
||||||
|
self.add(self.mainVBox)
|
||||||
|
self.oKButton = Gtk.Button.new_with_mnemonic(_("_Details"))
|
||||||
|
self.oKButton.connect("clicked", self._onDetailsButtonClicked)
|
||||||
|
self.buttomBox.pack_start(self.oKButton, True, True, 0)
|
||||||
|
|
||||||
|
self.oKButton = Gtk.Button.new_with_mnemonic(_("_OK"))
|
||||||
|
self.oKButton.connect("clicked", self._onOkButtonClicked)
|
||||||
|
self.buttomBox.pack_start(self.oKButton, True, True, 0)
|
||||||
|
|
||||||
|
self.applyButton = Gtk.Button.new_with_mnemonic(_("_Apply"))
|
||||||
|
self.applyButton.connect("clicked", self._onApplyButtonClicked)
|
||||||
|
self.buttomBox.pack_start(self.applyButton, True, True, 0)
|
||||||
|
|
||||||
|
self.applyButton = Gtk.Button.new_with_mnemonic(_("_Install"))
|
||||||
|
self.applyButton.connect("clicked", self._onInstallButtonClicked)
|
||||||
|
self.buttomBox.pack_start(self.applyButton, True, True, 0)
|
||||||
|
|
||||||
|
self.applyButton = Gtk.Button.new_with_mnemonic(_("_Uninstall"))
|
||||||
|
self.applyButton.connect("clicked", self._onUninstallButtonClicked)
|
||||||
|
self.buttomBox.pack_start(self.applyButton, True, True, 0)
|
||||||
|
|
||||||
|
self.cancelButton = Gtk.Button.new_with_mnemonic(_("_Cancel"))
|
||||||
|
self.cancelButton.connect("clicked", self._onCancelButtonClicked)
|
||||||
|
self.buttomBox.pack_start(self.cancelButton, True, True, 0)
|
||||||
|
def setTranslationContext(self, translationContext):
|
||||||
|
self.translationContext = translationContext
|
||||||
|
global _
|
||||||
|
_ = translationContext.gettext
|
||||||
|
def closeWindow(self):
|
||||||
|
Gtk.main_quit()
|
||||||
|
def uninstallPlugin(self):
|
||||||
|
selection = self.treeView.get_selection()
|
||||||
|
model, list_iter = selection.get_selected()
|
||||||
|
try:
|
||||||
|
if model.get_value(list_iter,0):
|
||||||
|
pluginInfo = model.get_value(list_iter,0)
|
||||||
|
pluginName = self.app.getPluginSystemManager().getPluginName(pluginInfo)
|
||||||
|
dialog = Gtk.MessageDialog(None,
|
||||||
|
Gtk.DialogFlags.MODAL,
|
||||||
|
type=Gtk.MessageType.INFO,
|
||||||
|
buttons=Gtk.ButtonsType.YES_NO)
|
||||||
|
|
||||||
|
dialog.set_markup("<b>%s</b>" % _('Remove Plugin {}?').format(pluginName))
|
||||||
|
dialog.format_secondary_markup(_('Do you really want to remove Plugin {}?').format(pluginName))
|
||||||
|
response = dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
if response != Gtk.ResponseType.YES:
|
||||||
|
return
|
||||||
|
self.app.getPluginSystemManager().uninstallPlugin(model.get_value(list_iter,0))
|
||||||
|
self.refreshPluginList()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def installPlugin(self):
|
||||||
|
ok, filePath = self.chooseFile()
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
self.app.getPluginSystemManager().installPlugin(filePath)
|
||||||
|
self.refreshPluginList()
|
||||||
|
|
||||||
|
def _onKeyPressWindow(self, _, event):
|
||||||
|
_, key_val = event.get_keyval()
|
||||||
|
if key_val == Gdk.KEY_Escape:
|
||||||
|
self.closeWindow()
|
||||||
|
def _onKeyPressTreeView(self, _, event):
|
||||||
|
_, key_val = event.get_keyval()
|
||||||
|
if key_val == Gdk.KEY_Return:
|
||||||
|
self.applySettings()
|
||||||
|
self.closeWindow()
|
||||||
|
if key_val == Gdk.KEY_Escape:
|
||||||
|
self.closeWindow()
|
||||||
|
# CTRL + Q
|
||||||
|
#modifiers = event.get_state()
|
||||||
|
#if modifiers == Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD2_MASK:
|
||||||
|
# if key_val == Gdk.KEY_q:
|
||||||
|
# self._on_scan()
|
||||||
|
def applySettings(self):
|
||||||
|
for row in self.listStore:
|
||||||
|
pluginInfo = row[0]
|
||||||
|
isActive = row[2]
|
||||||
|
self.app.getPluginSystemManager().setPluginActive(pluginInfo, isActive)
|
||||||
|
gsettingsManager = self.app.getGsettingsManager()
|
||||||
|
gsettingsManager.set_settings_value_list('active-plugins', self.app.getPluginSystemManager().getActivePlugins())
|
||||||
|
|
||||||
|
self.app.getPluginSystemManager().syncAllPluginsActive()
|
||||||
|
self.refreshPluginList()
|
||||||
|
|
||||||
|
|
||||||
|
def _rowActivated(self, tree_view, path, column):
|
||||||
|
print('rowActivated')
|
||||||
|
def showDetails(self):
|
||||||
|
selection = self.treeView.get_selection()
|
||||||
|
model, list_iter = selection.get_selected()
|
||||||
|
try:
|
||||||
|
if model.get_value(list_iter,0):
|
||||||
|
pluginInfo = model.get_value(list_iter,0)
|
||||||
|
name = self.app.getPluginSystemManager().getPluginName(pluginInfo)
|
||||||
|
description = self.app.getPluginSystemManager().getPluginDescription(pluginInfo)
|
||||||
|
authors = self.app.getPluginSystemManager().getPluginAuthors(pluginInfo)
|
||||||
|
website =self.app.getPluginSystemManager().getPluginWebsite(pluginInfo)
|
||||||
|
copyright = self.app.getPluginSystemManager().getPluginCopyright(pluginInfo)
|
||||||
|
license = '' #self.app.getPluginSystemManager().getPluginName(pluginInfo)
|
||||||
|
version = self.app.getPluginSystemManager().getPluginVersion(pluginInfo)
|
||||||
|
dialog = Gtk.AboutDialog(self)
|
||||||
|
dialog.set_authors(authors)
|
||||||
|
dialog.set_website(website)
|
||||||
|
dialog.set_copyright(copyright)
|
||||||
|
dialog.set_license(license)
|
||||||
|
dialog.set_version(version)
|
||||||
|
dialog.set_program_name(name)
|
||||||
|
dialog.set_comments(description)
|
||||||
|
dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _onDetailsButtonClicked(self, widget):
|
||||||
|
self.showDetails()
|
||||||
|
|
||||||
|
def _onOkButtonClicked(self, widget):
|
||||||
|
self.applySettings()
|
||||||
|
self.closeWindow()
|
||||||
|
def _onApplyButtonClicked(self, widget):
|
||||||
|
self.applySettings()
|
||||||
|
def _onInstallButtonClicked(self, widget):
|
||||||
|
self.installPlugin()
|
||||||
|
def _onUninstallButtonClicked(self, widget):
|
||||||
|
self.uninstallPlugin()
|
||||||
|
def _onCancelButtonClicked(self, widget):
|
||||||
|
self.closeWindow()
|
||||||
|
def refreshPluginList(self):
|
||||||
|
self.clearPluginList()
|
||||||
|
pluginList = self.app.getPluginSystemManager().plugins
|
||||||
|
for pluginInfo in pluginList:
|
||||||
|
self.addPlugin(pluginInfo)
|
||||||
|
def clearPluginList(self):
|
||||||
|
self.listStore.clear()
|
||||||
|
|
||||||
|
def addPlugin(self, pluginInfo):
|
||||||
|
ignoredPlugins = self.app.getPluginSystemManager().getIgnoredPlugins()
|
||||||
|
moduleDir = self.app.getPluginSystemManager().getPluginModuleDir(pluginInfo)
|
||||||
|
if moduleDir in ignoredPlugins:
|
||||||
|
return
|
||||||
|
|
||||||
|
hidden = self.app.getPluginSystemManager().isPluginHidden(pluginInfo)
|
||||||
|
if hidden:
|
||||||
|
return
|
||||||
|
|
||||||
|
moduleName = self.app.getPluginSystemManager().getPluginModuleName(pluginInfo)
|
||||||
|
name = self.app.getPluginSystemManager().getPluginName(pluginInfo)
|
||||||
|
version = self.app.getPluginSystemManager().getPluginVersion(pluginInfo)
|
||||||
|
website = self.app.getPluginSystemManager().getPluginWebsite(pluginInfo)
|
||||||
|
authors = self.app.getPluginSystemManager().getPluginAuthors(pluginInfo)
|
||||||
|
buildIn = self.app.getPluginSystemManager().isPluginBuildIn(pluginInfo)
|
||||||
|
description = self.app.getPluginSystemManager().getPluginDescription(pluginInfo)
|
||||||
|
iconName = self.app.getPluginSystemManager().getPluginIconName(pluginInfo)
|
||||||
|
copyright = self.app.getPluginSystemManager().getPluginCopyright(pluginInfo)
|
||||||
|
dependencies = self.app.getPluginSystemManager().getPluginDependencies(pluginInfo)
|
||||||
|
|
||||||
|
#settings = self.app.getPluginSystemManager().getPluginSettings(pluginInfo)
|
||||||
|
#hasDependencies = self.app.getPluginSystemManager().hasPluginDependency(pluginInfo)
|
||||||
|
loaded = self.app.getPluginSystemManager().isPluginLoaded(pluginInfo)
|
||||||
|
available = self.app.getPluginSystemManager().isPluginAvailable(pluginInfo)
|
||||||
|
active = self.app.getPluginSystemManager().isPluginActive(pluginInfo)
|
||||||
|
|
||||||
|
#externalData = self.app.getPluginSystemManager().getPluginExternalData(pluginInfo)
|
||||||
|
helpUri = self.app.getPluginSystemManager().getPlugingetHelpUri(pluginInfo)
|
||||||
|
dataDir = self.app.getPluginSystemManager().getPluginDataDir(pluginInfo)
|
||||||
|
|
||||||
|
# pluginInfo (object) = 0
|
||||||
|
# name (str) = 1
|
||||||
|
# active (bool) = 2
|
||||||
|
# buildIn (bool) = 3
|
||||||
|
# dataDir (str) = 4
|
||||||
|
# moduleDir (str) = 5
|
||||||
|
# dependencies (object) = 6
|
||||||
|
# moduleName (str) = 7
|
||||||
|
# description (str) = 8
|
||||||
|
# authors (object) = 9
|
||||||
|
# website (str) = 10
|
||||||
|
# copyright (str) = 11
|
||||||
|
# version (str) = 12
|
||||||
|
# helpUri (str) = 13
|
||||||
|
# iconName (str) = 14
|
||||||
|
self.listStore.append([pluginInfo, name, active, buildIn, dataDir, moduleDir, dependencies, moduleName, description, authors, website, copyright, version, helpUri, iconName])
|
||||||
|
def chooseFile(self):
|
||||||
|
dialog = Gtk.FileChooserDialog(
|
||||||
|
title=_("Please choose a file"), parent=self, action=Gtk.FileChooserAction.OPEN
|
||||||
|
)
|
||||||
|
dialog.add_buttons(
|
||||||
|
Gtk.STOCK_CANCEL,
|
||||||
|
Gtk.ResponseType.CANCEL,
|
||||||
|
Gtk.STOCK_OPEN,
|
||||||
|
Gtk.ResponseType.OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_plugin = Gtk.FileFilter()
|
||||||
|
filter_plugin.set_name(_("Plugin Archive"))
|
||||||
|
filter_plugin.add_mime_type("application/gzip")
|
||||||
|
dialog.add_filter(filter_plugin)
|
||||||
|
|
||||||
|
response = dialog.run()
|
||||||
|
filePath = ''
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if response == Gtk.ResponseType.OK:
|
||||||
|
ok = True
|
||||||
|
filePath = dialog.get_filename()
|
||||||
|
|
||||||
|
dialog.destroy()
|
||||||
|
return ok, filePath
|
||||||
|
def _onCellToggled(self, widget, path):
|
||||||
|
|
||||||
|
self.listStore[path][2] = not self.listStore[path][2]
|
||||||
|
def present(self):
|
||||||
|
cthulhu_state = self.app.getDynamicApiManager().getAPI('CthulhuState')
|
||||||
|
ts = 0
|
||||||
|
try:
|
||||||
|
ts = cthulhu_state.lastInputEvent.timestamp
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if ts == 0:
|
||||||
|
ts = Gtk.get_current_event_time()
|
||||||
|
self.present_with_time(ts)
|
||||||
|
def run(self):
|
||||||
|
self.refreshPluginList()
|
||||||
|
self.present()
|
||||||
|
self.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
self.destroy()
|
83
src/cthulhu/plugins/PluginManager/PluginManagerUiListBox.py
Executable file
83
src/cthulhu/plugins/PluginManager/PluginManagerUiListBox.py
Executable file
@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/python
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
class ListBoxRowWithData(Gtk.ListBoxRow):
|
||||||
|
def __init__(self, data):
|
||||||
|
super(Gtk.ListBoxRow, self).__init__()
|
||||||
|
self.data = data
|
||||||
|
self.add(Gtk.Label(label=data))
|
||||||
|
|
||||||
|
class PluginManagerUi(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
Gtk.Window.__init__(self)
|
||||||
|
self.pluginList = []
|
||||||
|
self.set_default_size(200, -1)
|
||||||
|
self.connect("destroy", Gtk.main_quit)
|
||||||
|
self.listBox = Gtk.ListBox()
|
||||||
|
|
||||||
|
self.buttomBox = Gtk.Box(spacing=6)
|
||||||
|
self.mainVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
|
||||||
|
self.mainVBox.pack_start(self.listBox, True, True, 0)
|
||||||
|
self.mainVBox.pack_start(self.buttomBox, True, True, 0)
|
||||||
|
|
||||||
|
self.add(self.mainVBox)
|
||||||
|
|
||||||
|
self.oKButton = Gtk.Button(label="OK")
|
||||||
|
self.oKButton.connect("clicked", self.on_oKButton_clicked)
|
||||||
|
self.buttomBox.pack_start(self.oKButton, True, True, 0)
|
||||||
|
|
||||||
|
self.applyButton = Gtk.Button(label="Apply")
|
||||||
|
self.applyButton.connect("clicked", self.on_applyButton_clicked)
|
||||||
|
self.buttomBox.pack_start(self.applyButton, True, True, 0)
|
||||||
|
|
||||||
|
self.cancelButton = Gtk.Button(label="Cancel")
|
||||||
|
self.cancelButton.connect("clicked", self.on_cancelButton_clicked)
|
||||||
|
self.buttomBox.pack_start(self.cancelButton, True, True, 0)
|
||||||
|
|
||||||
|
self.listBox.connect("row-activated", self.on_row_activated)
|
||||||
|
|
||||||
|
def on_row_activated(self, listBox, listboxrow):
|
||||||
|
print("Row %i activated" % (listboxrow.get_index()))
|
||||||
|
|
||||||
|
def on_oKButton_clicked(self, widget):
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
def on_applyButton_clicked(self, widget):
|
||||||
|
print("Apply")
|
||||||
|
|
||||||
|
def on_cancelButton_clicked(self, widget):
|
||||||
|
print("Cancel")
|
||||||
|
|
||||||
|
|
||||||
|
def addPlugin(self, Name, Active, Description = ''):
|
||||||
|
self.pluginList.append([Name, Active, Description])
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
for plugin in self.pluginList:
|
||||||
|
print(plugin)
|
||||||
|
box = Gtk.Box(spacing=10)
|
||||||
|
pluginNameLabel = Gtk.Label(plugin[0])
|
||||||
|
#pluginActiveCheckButton = Gtk.CheckButton(label="_Active", use_underline=True)
|
||||||
|
#pluginActiveCheckButton.set_active(plugin[1])
|
||||||
|
pluginActiveSwitch = Gtk.Switch()
|
||||||
|
pluginActiveSwitch.set_active(plugin[1])
|
||||||
|
|
||||||
|
|
||||||
|
pluginDescriptionLabel = Gtk.Label(plugin[2])
|
||||||
|
|
||||||
|
box.pack_start(pluginNameLabel, True, True, 0)
|
||||||
|
box.pack_start(pluginActiveSwitch, True, True, 0)
|
||||||
|
box.pack_start(pluginDescriptionLabel, True, True, 0)
|
||||||
|
|
||||||
|
self.listBox.add(box)
|
||||||
|
self.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ui = PluginManagerUi()
|
||||||
|
ui.addPlugin('plugin1', True, 'bla')
|
||||||
|
ui.addPlugin('plugin2', True, 'bla')
|
||||||
|
ui.addPlugin('plugin3', True, 'bla')
|
||||||
|
ui.run()
|
93
src/cthulhu/plugins/PluginManager/PluginManagerUiListBox_tut.py
Executable file
93
src/cthulhu/plugins/PluginManager/PluginManagerUiListBox_tut.py
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/python
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
|
||||||
|
class ListBoxRowWithData(Gtk.ListBoxRow):
|
||||||
|
def __init__(self, data):
|
||||||
|
super(Gtk.ListBoxRow, self).__init__()
|
||||||
|
self.data = data
|
||||||
|
self.add(Gtk.Label(label=data))
|
||||||
|
|
||||||
|
|
||||||
|
class ListBoxWindow(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
Gtk.Window.__init__(self, title="ListBox Demo")
|
||||||
|
self.set_border_width(10)
|
||||||
|
|
||||||
|
box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
|
self.add(box_outer)
|
||||||
|
|
||||||
|
listbox = Gtk.ListBox()
|
||||||
|
listbox.set_selection_mode(Gtk.SelectionMode.NONE)
|
||||||
|
box_outer.pack_start(listbox, True, True, 0)
|
||||||
|
|
||||||
|
row = Gtk.ListBoxRow()
|
||||||
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
|
||||||
|
row.add(hbox)
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
hbox.pack_start(vbox, True, True, 0)
|
||||||
|
|
||||||
|
label1 = Gtk.Label(label="Automatic Date & Time", xalign=0)
|
||||||
|
label2 = Gtk.Label(label="Requires internet access", xalign=0)
|
||||||
|
vbox.pack_start(label1, True, True, 0)
|
||||||
|
vbox.pack_start(label2, True, True, 0)
|
||||||
|
|
||||||
|
switch = Gtk.Switch()
|
||||||
|
switch.props.valign = Gtk.Align.CENTER
|
||||||
|
hbox.pack_start(switch, False, True, 0)
|
||||||
|
|
||||||
|
listbox.add(row)
|
||||||
|
|
||||||
|
row = Gtk.ListBoxRow()
|
||||||
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
|
||||||
|
row.add(hbox)
|
||||||
|
label = Gtk.Label(label="Enable Automatic Update", xalign=0)
|
||||||
|
check = Gtk.CheckButton()
|
||||||
|
hbox.pack_start(label, True, True, 0)
|
||||||
|
hbox.pack_start(check, False, True, 0)
|
||||||
|
|
||||||
|
listbox.add(row)
|
||||||
|
|
||||||
|
row = Gtk.ListBoxRow()
|
||||||
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
|
||||||
|
row.add(hbox)
|
||||||
|
label = Gtk.Label(label="Date Format", xalign=0)
|
||||||
|
combo = Gtk.ComboBoxText()
|
||||||
|
combo.insert(0, "0", "24-hour")
|
||||||
|
combo.insert(1, "1", "AM/PM")
|
||||||
|
hbox.pack_start(label, True, True, 0)
|
||||||
|
hbox.pack_start(combo, False, True, 0)
|
||||||
|
|
||||||
|
listbox.add(row)
|
||||||
|
|
||||||
|
listbox_2 = Gtk.ListBox()
|
||||||
|
items = "This is a sorted ListBox Fail".split()
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
listbox_2.add(ListBoxRowWithData(item))
|
||||||
|
|
||||||
|
def sort_func(row_1, row_2, data, notify_destroy):
|
||||||
|
return row_1.data.lower() > row_2.data.lower()
|
||||||
|
|
||||||
|
def filter_func(row, data, notify_destroy):
|
||||||
|
return False if row.data == "Fail" else True
|
||||||
|
|
||||||
|
listbox_2.set_sort_func(sort_func, None, False)
|
||||||
|
listbox_2.set_filter_func(filter_func, None, False)
|
||||||
|
|
||||||
|
def on_row_activated(listbox_widget, row):
|
||||||
|
print(row.data)
|
||||||
|
|
||||||
|
listbox_2.connect("row-activated", on_row_activated)
|
||||||
|
|
||||||
|
box_outer.pack_start(listbox_2, True, True, 0)
|
||||||
|
listbox_2.show_all()
|
||||||
|
|
||||||
|
|
||||||
|
win = ListBoxWindow()
|
||||||
|
win.connect("destroy", Gtk.main_quit)
|
||||||
|
win.show_all()
|
||||||
|
Gtk.main()
|
0
src/cthulhu/plugins/PluginManager/__init__.py
Normal file
0
src/cthulhu/plugins/PluginManager/__init__.py
Normal file
7
src/cthulhu/plugins/SelfVoice/Makefile.am
Normal file
7
src/cthulhu/plugins/SelfVoice/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
SelfVoice.plugin \
|
||||||
|
SelfVoice.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/SelfVoice
|
||||||
|
|
6
src/cthulhu/plugins/SelfVoice/SelfVoice.plugin
Normal file
6
src/cthulhu/plugins/SelfVoice/SelfVoice.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=SelfVoice
|
||||||
|
Loader=python3
|
||||||
|
Name=Self Voice Plugin
|
||||||
|
Description=use cthulhu text / braile from using unix sockets
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
117
src/cthulhu/plugins/SelfVoice/SelfVoice.py
Normal file
117
src/cthulhu/plugins/SelfVoice/SelfVoice.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# Example usage:
|
||||||
|
# echo "This is a test." | socat - UNIX-CLIENT:/tmp/cthulhu-PID.sock
|
||||||
|
# Where PID is cthulhu's process id.
|
||||||
|
# Prepend text to be spoken with <!#APPEND#!> to make it not interrupt, for inaccessible windows.
|
||||||
|
# Append message to be spoken with <#PERSISTENT#> to present a persistent message in braille
|
||||||
|
# <#APPEND#> is only usable for a persistent message
|
||||||
|
|
||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
import select, socket, os, os.path
|
||||||
|
from threading import Thread, Lock
|
||||||
|
|
||||||
|
APPEND_CODE = '<#APPEND#>'
|
||||||
|
PERSISTENT_CODE = '<#PERSISTENT#>'
|
||||||
|
|
||||||
|
class SelfVoice(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
__gtype_name__ = 'SelfVoice'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
self.lock = Lock()
|
||||||
|
self.active = False
|
||||||
|
self.voiceThread = Thread(target=self.voiceWorker)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.activateWorker()
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
self.deactivateWorker()
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def deactivateWorker(self):
|
||||||
|
with self.lock:
|
||||||
|
self.active = False
|
||||||
|
self.voiceThread.join()
|
||||||
|
def activateWorker(self):
|
||||||
|
with self.lock:
|
||||||
|
self.active = True
|
||||||
|
self.voiceThread.start()
|
||||||
|
def isActive(self):
|
||||||
|
with self.lock:
|
||||||
|
return self.active
|
||||||
|
def outputMessage(self, Message):
|
||||||
|
# Prepare
|
||||||
|
API = self.object
|
||||||
|
append = Message.startswith(APPEND_CODE)
|
||||||
|
if append:
|
||||||
|
Message = Message[len(APPEND_CODE):]
|
||||||
|
if Message.endswith(PERSISTENT_CODE):
|
||||||
|
Message = Message[:len(Message)-len(PERSISTENT_CODE)]
|
||||||
|
API.app.getAPIHelper().outputMessage(Message, not append)
|
||||||
|
else:
|
||||||
|
script_manager = API.app.getDynamicApiManager().getAPI('ScriptManager')
|
||||||
|
scriptManager = script_manager.getManager()
|
||||||
|
scriptManager.getDefaultScript().presentMessage(Message, resetStyles=False)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
settings = API.app.getDynamicApiManager().getAPI('Settings')
|
||||||
|
braille = API.app.getDynamicApiManager().getAPI('Braille')
|
||||||
|
speech = API.app.getDynamicApiManager().getAPI('Speech')
|
||||||
|
# Speak
|
||||||
|
if speech != None:
|
||||||
|
if (settings.enableSpeech):
|
||||||
|
if not append:
|
||||||
|
speech.cancel()
|
||||||
|
if Message != '':
|
||||||
|
speech.speak(Message)
|
||||||
|
# Braille
|
||||||
|
if braille != None:
|
||||||
|
if (settings.enableBraille):
|
||||||
|
braille.displayMessage(Message)
|
||||||
|
except e as Exception:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def voiceWorker(self):
|
||||||
|
socketFile = '/tmp/cthulhu-' + str(os.getppid()) + '.sock'
|
||||||
|
# for testing purposes
|
||||||
|
#socketFile = '/tmp/cthulhu-plugin.sock'
|
||||||
|
|
||||||
|
if os.path.exists(socketFile):
|
||||||
|
os.unlink(socketFile)
|
||||||
|
cthulhuSock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
cthulhuSock.bind(socketFile)
|
||||||
|
os.chmod(socketFile, 0o222)
|
||||||
|
cthulhuSock.listen(1)
|
||||||
|
while self.isActive():
|
||||||
|
# Check if the client is still connected and if data is available:
|
||||||
|
try:
|
||||||
|
r, _, _ = select.select([cthulhuSock], [], [], 0.8)
|
||||||
|
except select.error:
|
||||||
|
break
|
||||||
|
if r == []:
|
||||||
|
continue
|
||||||
|
if cthulhuSock in r:
|
||||||
|
client_sock, client_addr = cthulhuSock.accept()
|
||||||
|
try:
|
||||||
|
rawdata = client_sock.recv(8129)
|
||||||
|
data = rawdata.decode("utf-8").rstrip().lstrip()
|
||||||
|
self.outputMessage(data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
client_sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if cthulhuSock:
|
||||||
|
cthulhuSock.close()
|
||||||
|
cthulhuSock = None
|
||||||
|
if os.path.exists(socketFile):
|
||||||
|
os.unlink(socketFile)
|
||||||
|
|
||||||
|
|
0
src/cthulhu/plugins/SelfVoice/__init__.py
Normal file
0
src/cthulhu/plugins/SelfVoice/__init__.py
Normal file
7
src/cthulhu/plugins/Time/Makefile.am
Normal file
7
src/cthulhu/plugins/Time/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
orca_python_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
Time.plugin \
|
||||||
|
Time.py
|
||||||
|
|
||||||
|
orca_pythondir=$(pkgpythondir)/plugins/Time
|
||||||
|
|
6
src/cthulhu/plugins/Time/Time.plugin
Normal file
6
src/cthulhu/plugins/Time/Time.plugin
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Plugin]
|
||||||
|
Module=Time
|
||||||
|
Loader=python3
|
||||||
|
Name=Time
|
||||||
|
Description=Present current time
|
||||||
|
Authors=Chrys chrys@linux-a11y.org
|
35
src/cthulhu/plugins/Time/Time.py
Normal file
35
src/cthulhu/plugins/Time/Time.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import gi, time
|
||||||
|
gi.require_version('Peas', '1.0')
|
||||||
|
from gi.repository import GObject
|
||||||
|
from gi.repository import Peas
|
||||||
|
|
||||||
|
from cthulhu import plugin
|
||||||
|
|
||||||
|
class Time(GObject.Object, Peas.Activatable, plugin.Plugin):
|
||||||
|
#__gtype_name__ = 'Time'
|
||||||
|
|
||||||
|
object = GObject.Property(type=GObject.Object)
|
||||||
|
def __init__(self):
|
||||||
|
plugin.Plugin.__init__(self)
|
||||||
|
def do_activate(self):
|
||||||
|
API = self.object
|
||||||
|
self.connectSignal("setup-inputeventhandlers-completed", self.setupCompatBinding)
|
||||||
|
def setupCompatBinding(self, app):
|
||||||
|
cmdnames = app.getDynamicApiManager().getAPI('Cmdnames')
|
||||||
|
inputEventHandlers = app.getDynamicApiManager().getAPI('inputEventHandlers')
|
||||||
|
inputEventHandlers['presentTimeHandler'] = app.getAPIHelper().createInputEventHandler(self.presentTime, cmdnames.PRESENT_CURRENT_TIME)
|
||||||
|
def do_deactivate(self):
|
||||||
|
API = self.object
|
||||||
|
inputEventHandlers = API.app.getDynamicApiManager().getAPI('inputEventHandlers')
|
||||||
|
del inputEventHandlers['presentTimeHandler']
|
||||||
|
def do_update_state(self):
|
||||||
|
API = self.object
|
||||||
|
def presentTime(self, script=None, inputEvent=None):
|
||||||
|
""" Presents the current time. """
|
||||||
|
API = self.object
|
||||||
|
settings_manager = API.app.getDynamicApiManager().getAPI('SettingsManager')
|
||||||
|
_settingsManager = settings_manager.getManager()
|
||||||
|
timeFormat = _settingsManager.getSetting('presentTimeFormat')
|
||||||
|
message = time.strftime(timeFormat, time.localtime())
|
||||||
|
API.app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(message, resetStyles=False)
|
||||||
|
return True
|
0
src/cthulhu/plugins/Time/__init__.py
Normal file
0
src/cthulhu/plugins/Time/__init__.py
Normal file
349
src/cthulhu/resource_manager.py
Normal file
349
src/cthulhu/resource_manager.py
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
|
class TryFunction():
|
||||||
|
def __init__(self, function):
|
||||||
|
self.function = function
|
||||||
|
def runSignal(self, app):
|
||||||
|
try:
|
||||||
|
return self.function(app)
|
||||||
|
except Exception as e:
|
||||||
|
print('try signal',e , traceback.print_exc())
|
||||||
|
def runInputEvent(self, script=None, inputEvent=None):
|
||||||
|
try:
|
||||||
|
return self.function(script, inputEvent)
|
||||||
|
except Exception as e:
|
||||||
|
print('try input event',e , traceback.print_exc())
|
||||||
|
def getFunction(self):
|
||||||
|
return self.function
|
||||||
|
def setFunction(self, function):
|
||||||
|
self.function = function
|
||||||
|
|
||||||
|
class ResourceEntry():
|
||||||
|
def __init__(self, entryType, resource1 = None, function = None, tryFunction= None, resourceText = '', resource2 = None, resource3 = None, resource4 = None):
|
||||||
|
# function based init
|
||||||
|
self.entryType = entryType # 'keyboard' = Keyboard, 'subscription' = Subscription, 'signal' = Signal, 'api'= Dynamic API, 'settings' = gSetttings
|
||||||
|
self.resource1 = resource1
|
||||||
|
self.resource2 = resource2
|
||||||
|
self.resource3 = resource3
|
||||||
|
self.resource4 = resource4
|
||||||
|
self.function = function
|
||||||
|
self.tryFunction = tryFunction
|
||||||
|
self.resourceText = resourceText
|
||||||
|
|
||||||
|
def getEntryType(self):
|
||||||
|
return self.entryType
|
||||||
|
def getResourceText(self):
|
||||||
|
return self.resourceText
|
||||||
|
def getResource1(self):
|
||||||
|
return self.resource1
|
||||||
|
def getResource2(self):
|
||||||
|
return self.resource2
|
||||||
|
def getResource3(self):
|
||||||
|
return self.resource3
|
||||||
|
def getResource4(self):
|
||||||
|
return self.resource4
|
||||||
|
def getFunction(self):
|
||||||
|
return self.function
|
||||||
|
def getTryFunction(self):
|
||||||
|
return self.tryFunction
|
||||||
|
|
||||||
|
class ResourceContext():
|
||||||
|
def __init__(self, app, name):
|
||||||
|
self.app = app
|
||||||
|
self.name = name
|
||||||
|
self.gestures = {} # gestures added by the context
|
||||||
|
self.subscriptions = {} # subscription to signals by the context
|
||||||
|
self.apis = {}
|
||||||
|
self.signals = {}
|
||||||
|
self.settings = {}
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self.name
|
||||||
|
def getGestures(self):
|
||||||
|
return self.gestures
|
||||||
|
def getSubscriptions(self):
|
||||||
|
return self.subscriptions
|
||||||
|
def getSignals(self):
|
||||||
|
return self.signals
|
||||||
|
def getAPIs(self):
|
||||||
|
return self.apis
|
||||||
|
def getSettings(self):
|
||||||
|
return self.settings
|
||||||
|
def hasSettings(self, profile, application, sub_setting_name):
|
||||||
|
try:
|
||||||
|
d = self.getSettings()[profile][application][sub_setting_name]
|
||||||
|
return True
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
def addSetting(self, profile, application, sub_setting_name, entry):
|
||||||
|
# add profile
|
||||||
|
try:
|
||||||
|
d = self.settings[profile]
|
||||||
|
except KeyError:
|
||||||
|
self.settings[profile]= {}
|
||||||
|
# add application
|
||||||
|
try:
|
||||||
|
d = self.settings[profile][application]
|
||||||
|
except KeyError:
|
||||||
|
self.settings[profile][application] = {}
|
||||||
|
# add entry
|
||||||
|
self.settings[profile][application][sub_setting_name] = entry
|
||||||
|
|
||||||
|
print('add', 'settings', self.getName(), profile, application, entry.getResourceText())
|
||||||
|
|
||||||
|
|
||||||
|
def hasAPI(self, application, api):
|
||||||
|
try:
|
||||||
|
d = self.getAPIs()[application][api]
|
||||||
|
return True
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
def addAPI(self, application, api, entry):
|
||||||
|
# add application
|
||||||
|
try:
|
||||||
|
d = self.apis[application]
|
||||||
|
except KeyError:
|
||||||
|
self.apis[application] = {}
|
||||||
|
# add entry
|
||||||
|
self.apis[application][api] = entry
|
||||||
|
print('add', 'api', self.getName(), application, api)
|
||||||
|
|
||||||
|
def removeAPI(self, application, api):
|
||||||
|
try:
|
||||||
|
del self.apis[application][api]
|
||||||
|
except KeyError as e:
|
||||||
|
print(e)
|
||||||
|
try:
|
||||||
|
if len(self.getAPIs()[application]) == 0:
|
||||||
|
del self.apis[application]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
print('remove', 'apis', self.getName(), application, api)
|
||||||
|
|
||||||
|
def addGesture(self, profile, application, gesture, entry):
|
||||||
|
# add profile
|
||||||
|
try:
|
||||||
|
d = self.gestures[profile]
|
||||||
|
except KeyError:
|
||||||
|
self.gestures[profile]= {}
|
||||||
|
# add application
|
||||||
|
try:
|
||||||
|
d = self.gestures[profile][application]
|
||||||
|
except KeyError:
|
||||||
|
self.gestures[profile][application] = {}
|
||||||
|
# add entry
|
||||||
|
self.gestures[profile][application][gesture] = entry
|
||||||
|
print('add', 'gesture', self.getName(), profile, application, entry.getResourceText())
|
||||||
|
def removeGesture(self, gesture):
|
||||||
|
gestureCopy = self.getGestures().copy()
|
||||||
|
for profile, applicationDict in gestureCopy.items():
|
||||||
|
for application, keyDict in applicationDict.copy().items():
|
||||||
|
try:
|
||||||
|
del self.getGestures()[profile][application][gesture]
|
||||||
|
if len(self.getGestures()[profile][application]) == 0:
|
||||||
|
del self.getGestures()[profile][application]
|
||||||
|
if len(self.getGestures()[profile]) == 0:
|
||||||
|
del self.getGestures()[profile]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print('remove', 'gesture', self.getName(), profile, application, gesture)
|
||||||
|
|
||||||
|
def addSubscription(self, signalName, function, entry):
|
||||||
|
# add entry
|
||||||
|
try:
|
||||||
|
e = self.subscriptions[signalName]
|
||||||
|
except KeyError:
|
||||||
|
self.subscriptions[signalName] = {}
|
||||||
|
self.subscriptions[signalName][function] = entry
|
||||||
|
print('add', 'subscription', self.getName(), entry.getResourceText(), entry.function)
|
||||||
|
|
||||||
|
def removeSubscriptionByFunction(self, function):
|
||||||
|
for signalName, functionDict in self.getSubscriptions().copy().items():
|
||||||
|
for functionKey, entry in functionDict.copy().items():
|
||||||
|
try:
|
||||||
|
if function == entry.function:
|
||||||
|
del self.getSubscriptions()[signalName][entry.function]
|
||||||
|
elif function == entry.tryFunction:
|
||||||
|
del self.getSubscriptions()[signalName][entry.function]
|
||||||
|
except KeyError as e:
|
||||||
|
print(e)
|
||||||
|
try:
|
||||||
|
if len(self.getSubscriptions()[signalName]) == 0:
|
||||||
|
del self.getSubscriptions()[signalName]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
print('remove', 'subscription', self.getName(), function)
|
||||||
|
def addSignal(self, signal, entry):
|
||||||
|
# add entry
|
||||||
|
self.signals[signal] = entry
|
||||||
|
print('add', 'signal', self.getName(), entry.getResourceText())
|
||||||
|
def removeSignal(self, signal):
|
||||||
|
print('remove', 'signal', self.getName(), entry.getResourceText())
|
||||||
|
|
||||||
|
def unregisterAllResources(self):
|
||||||
|
try:
|
||||||
|
self.unregisterAllGestures()
|
||||||
|
self.unregisterAllSubscriptions()
|
||||||
|
self.unregisterAllSignals()
|
||||||
|
self.unregisterAllAPI()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def unregisterAllAPI(self):
|
||||||
|
dynamicApiManager = self.app.getDynamicApiManager()
|
||||||
|
for application, value in self.getAPIs().copy().items():
|
||||||
|
for key, entry in value.copy().items():
|
||||||
|
try:
|
||||||
|
dynamicApiManager.unregisterAPI(key, application, self.getName())
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print('unregister api ', self.getName(), entry.getEntryType(), entry.getResourceText())
|
||||||
|
def unregisterAllGestures(self):
|
||||||
|
APIHelper = self.app.getAPIHelper()
|
||||||
|
|
||||||
|
for profile, profileValue in self.getGestures().copy().items():
|
||||||
|
for application, applicationValue in profileValue.copy().items():
|
||||||
|
for gesture, entry in applicationValue.copy().items():
|
||||||
|
if entry.getEntryType() == 'keyboard':
|
||||||
|
try:
|
||||||
|
APIHelper.unregisterShortcut(entry.getResource1(), self.getName())
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print('unregister gesture', self.getName(), entry.getEntryType(), profile, application, entry.getResourceText())
|
||||||
|
def unregisterAllSignals(self):
|
||||||
|
pass
|
||||||
|
# how to remove signals????
|
||||||
|
|
||||||
|
def unregisterAllSubscriptions(self):
|
||||||
|
SignalManager = self.app.getSignalManager()
|
||||||
|
|
||||||
|
for signalName, entryDict in self.getSubscriptions().copy().items():
|
||||||
|
for function, entry in entryDict.copy().items():
|
||||||
|
try:
|
||||||
|
SignalManager.disconnectSignalByFunction(entry.tryFunction, self.getName())
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print('unregister subscription', self.getName(), entry.getEntryType(), entry.getResourceText())
|
||||||
|
|
||||||
|
class ResourceManager():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.resourceContextDict = {}
|
||||||
|
def getResourceContextDict(self):
|
||||||
|
return self.resourceContextDict
|
||||||
|
|
||||||
|
def addResourceContext(self, contextName, overwrite = False):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
if not overwrite:
|
||||||
|
return
|
||||||
|
resourceContext = ResourceContext(self.app, contextName)
|
||||||
|
self.resourceContextDict[contextName] = resourceContext
|
||||||
|
print('add {}'.format(contextName))
|
||||||
|
|
||||||
|
def removeResourceContext(self, contextName):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.resourceContextDict[contextName].unregisterAllResources()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# temp
|
||||||
|
try:
|
||||||
|
print('_________', 'summery', self.resourceContextDict[contextName].getName(), '_________')
|
||||||
|
print('api', self.resourceContextDict[contextName].getAPIs())
|
||||||
|
print('signals', self.resourceContextDict[contextName].getSignals())
|
||||||
|
print('subscriptions', self.resourceContextDict[contextName].getSubscriptions())
|
||||||
|
print('gestrues ', self.resourceContextDict[contextName].getGestures())
|
||||||
|
print('_________', self.resourceContextDict[contextName].getName(), '_________')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# temp
|
||||||
|
try:
|
||||||
|
del self.resourceContextDict[contextName]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
print('rm {}'.format(contextName))
|
||||||
|
|
||||||
|
def getResourceContext(self, contextName):
|
||||||
|
if not contextName:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return self.resourceContextDict[contextName]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def addAPI(self, application, api, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
entry = None
|
||||||
|
resourceContext.addAPI(application, api, entry)
|
||||||
|
def removeAPI(self, application, api, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
resourceContext.removeAPI(application, api)
|
||||||
|
def addGesture(self, profile, application, gesture, entry, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
entry = None
|
||||||
|
resourceContext.addGesture(profile, application, gesture, entry)
|
||||||
|
def removeGesture(self, gesture, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
resourceContext.removeGesture(gesture)
|
||||||
|
def addSubscription(self, subscription, entry, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
entry = None
|
||||||
|
resourceContext.addSubscription(subscription, entry)
|
||||||
|
def removeSubscriptionByFunction(self, function, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
resourceContext.removeSubscriptionByFunction(function)
|
||||||
|
def addSignal(self, signal, entry, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
entry = None
|
||||||
|
resourceContext.addSignal(signal, entry)
|
||||||
|
def removeSignal(self, signal, contextName = None):
|
||||||
|
if not contextName:
|
||||||
|
return
|
||||||
|
resourceContext = self.getResourceContext(contextName)
|
||||||
|
if not resourceContext:
|
||||||
|
return
|
||||||
|
resourceContext.removeSignal(signal)
|
||||||
|
def printContext(self):
|
||||||
|
for k, v in self.resourceContextDict.items():
|
||||||
|
print('plugin', k)
|
||||||
|
for k1, v1 in v.getGestures().items():
|
||||||
|
print(' profile', k1)
|
||||||
|
for k2, v2 in v1.items():
|
||||||
|
print(' application', k2)
|
||||||
|
for k3, v3 in v2.items():
|
||||||
|
print(' value', k3, v3)
|
58
src/cthulhu/signal_manager.py
Normal file
58
src/cthulhu/signal_manager.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import gi
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
from cthulhu import resource_manager
|
||||||
|
|
||||||
|
class SignalManager():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.resourceManager = self.app.getResourceManager()
|
||||||
|
|
||||||
|
def registerSignal(self, signalName, signalFlag = GObject.SignalFlags.RUN_LAST, closure = GObject.TYPE_NONE, accumulator=(), contextName = None):
|
||||||
|
# register signal
|
||||||
|
ok = False
|
||||||
|
if not self.signalExist(signalName):
|
||||||
|
GObject.signal_new(signalName, self.app, signalFlag, closure,accumulator)
|
||||||
|
ok = True
|
||||||
|
resourceContext = self.resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceEntry = resource_manager.ResourceEntry('signal', signalName, signalName, signalName, signalName)
|
||||||
|
resourceContext.addSignal(signalName, resourceEntry)
|
||||||
|
return ok
|
||||||
|
|
||||||
|
def signalExist(self, signalName):
|
||||||
|
return GObject.signal_lookup(signalName, self.app) != 0
|
||||||
|
def connectSignal(self, signalName, function, profile, param = None, contextName = None):
|
||||||
|
signalID = None
|
||||||
|
try:
|
||||||
|
if self.signalExist(signalName):
|
||||||
|
tryFunction = resource_manager.TryFunction(function)
|
||||||
|
signalID = self.app.connect(signalName, tryFunction.runSignal)
|
||||||
|
resourceContext = self.resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceEntry = resource_manager.ResourceEntry('subscription', signalID, function, tryFunction, signalName)
|
||||||
|
resourceContext.addSubscription(signalName, function, resourceEntry)
|
||||||
|
else:
|
||||||
|
print('signal {} does not exist'.format(signalName))
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
return signalID
|
||||||
|
def disconnectSignalByFunction(self, function, contextName = None):
|
||||||
|
ok = False
|
||||||
|
try:
|
||||||
|
self.app.disconnect_by_func(function)
|
||||||
|
ok = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
resourceContext = self.resourceManager.getResourceContext(contextName)
|
||||||
|
if resourceContext:
|
||||||
|
resourceContext.removeSubscriptionByFunction(function)
|
||||||
|
return ok
|
||||||
|
def emitSignal(self, signalName):
|
||||||
|
# emit an signal
|
||||||
|
try:
|
||||||
|
self.app.emit(signalName)
|
||||||
|
print('after Emit Signal: {}'.format(signalName))
|
||||||
|
except:
|
||||||
|
print('Signal "{}" does not exist.'.format(signalName))
|
93
src/cthulhu/translation_context.py
Normal file
93
src/cthulhu/translation_context.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import gi, os, locale, gettext
|
||||||
|
from gi.repository import GObject
|
||||||
|
import gettext
|
||||||
|
|
||||||
|
from cthulhu import cthulhu_i18n
|
||||||
|
|
||||||
|
class TranslationContext():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.localeDir = cthulhu_i18n.localedir
|
||||||
|
self.domain = 'cthulhu'
|
||||||
|
self.language = 'en'
|
||||||
|
self.fallbackToCthulhuTranslation = True
|
||||||
|
self.domainTranslation = None
|
||||||
|
self.cthulhuMainTranslation = None
|
||||||
|
|
||||||
|
def setDomain(self, domain):
|
||||||
|
self.domain = domain
|
||||||
|
def setLanguage(self, language):
|
||||||
|
self.language = language
|
||||||
|
def setLocaleDir(self, localeDir):
|
||||||
|
self.localeDir = localeDir
|
||||||
|
def getCurrentDefaultLocale(self):
|
||||||
|
return locale.getdefaultlocale()[0]
|
||||||
|
def setFallbackToCthulhuTranslation(self, fallback):
|
||||||
|
self.fallback = fallback
|
||||||
|
def getFallbackToCthulhuTranslation(self):
|
||||||
|
return self.fallback
|
||||||
|
def getLocaleDir(self):
|
||||||
|
return self.localeDir
|
||||||
|
def getLanguage(self):
|
||||||
|
return self.language
|
||||||
|
def getDomain(self):
|
||||||
|
return self.domain
|
||||||
|
def getDomainTranslation(self):
|
||||||
|
return self.domainTranslation
|
||||||
|
def getCthulhuMainTranslation(self):
|
||||||
|
return self.cthulhuMainTranslation
|
||||||
|
def setDomainTranslation(self, domainTranslation):
|
||||||
|
self.domainTranslation = domainTranslation
|
||||||
|
def setCthulhuMainTranslation(self, cthulhuMainTranslation):
|
||||||
|
self.cthulhuMainTranslation = cthulhuMainTranslation
|
||||||
|
def updateTranslation(self):
|
||||||
|
self.setDomainTranslation(None)
|
||||||
|
self.setCthulhuMainTranslation(None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.setLanguage(self.getCurrentDefaultLocale())
|
||||||
|
except:
|
||||||
|
self.setLanguage('en')
|
||||||
|
print(e)
|
||||||
|
if self.getFallbackToCthulhuTranslation():
|
||||||
|
cthulhuaMainTranslation = cthulhu_i18n
|
||||||
|
self.setCthulhuMainTranslation(cthulhuaMainTranslation)
|
||||||
|
try:
|
||||||
|
domainTranslation = gettext.translation(self.getDomain(), self.getLocaleDir(), languages=[self.getLanguage()])
|
||||||
|
self.setDomainTranslation(domainTranslation)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def gettext(self, text):
|
||||||
|
translatedText = text
|
||||||
|
if self.getDomainTranslation() != None:
|
||||||
|
try:
|
||||||
|
translatedText = self.getDomainTranslation().gettext(text)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
if translatedText == text:
|
||||||
|
if self.getFallbackToCthulhuTranslation() or self.getDomainTranslation() == None:
|
||||||
|
if self.getCthulhuMainTranslation() != None:
|
||||||
|
try:
|
||||||
|
translatedText = self.getCthulhuMainTranslation().cgettext(text) # gettext from cthulhu_i18n
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return translatedText
|
||||||
|
|
||||||
|
def ngettext(self, singular, plural, n):
|
||||||
|
translatedText = singular
|
||||||
|
if n > 1:
|
||||||
|
translatedText = plural
|
||||||
|
if self.getDomainTranslation() != None:
|
||||||
|
try:
|
||||||
|
translatedText = self.getDomainTranslation().ngettext(singular, plural, n)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if translatedText in [singular, plural]: # not translated
|
||||||
|
if self.getFallbackToCthulhuTranslation() or self.getDomainTranslation() == None:
|
||||||
|
if self.getCthulhuMainTranslation() != None:
|
||||||
|
try:
|
||||||
|
translatedText = self.getCthulhuMainTranslation().ngettext(singular, plural, n)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return translatedText
|
52
src/cthulhu/translation_manager.py
Normal file
52
src/cthulhu/translation_manager.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import gi, os, locale, gettext
|
||||||
|
from gi.repository import GObject
|
||||||
|
import gettext
|
||||||
|
|
||||||
|
from cthulhu import cthulhu_i18n
|
||||||
|
from cthulhu import translation_context
|
||||||
|
|
||||||
|
class TranslationManager():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.translatioContextnDict = {}
|
||||||
|
def initTranslation(self, name, domain = None, localeDir = None, language = None, fallbackToCthulhuTranslation = True):
|
||||||
|
translationContext = translation_context.TranslationContext(self.app)
|
||||||
|
if os.path.isdir(localeDir):
|
||||||
|
translationContext.setDomain(domain)
|
||||||
|
translationContext.setLocaleDir(localeDir)
|
||||||
|
translationContext.setLanguage(language)
|
||||||
|
translationContext.setFallbackToCthulhuTranslation(fallbackToCthulhuTranslation)
|
||||||
|
translationContext.updateTranslation()
|
||||||
|
self.addTranslationDict(name, translationContext)
|
||||||
|
return translationContext
|
||||||
|
def addTranslationDict(self, name, translationContext):
|
||||||
|
self.translatioContextnDict[name] = translationContext
|
||||||
|
def getTranslationContextByName(name):
|
||||||
|
try:
|
||||||
|
return self.translatioContextnDict[name]
|
||||||
|
except KeyError:
|
||||||
|
return self.translatioContextnDict['cthulhu']
|
||||||
|
#def getTranslationsInstance(self, domain='cthulhu'):
|
||||||
|
""" Gets the gettext translation instance for this add-on.
|
||||||
|
<addon-path>\\locale will be used to find .mo files, if exists.
|
||||||
|
If a translation file is not found the default fallback null translation is returned.
|
||||||
|
@param domain: the translation domain to retrieve. The 'Cthulhu' default should be used in most cases.
|
||||||
|
@returns: the gettext translation class.
|
||||||
|
"""
|
||||||
|
# localedir = os.path.join(self.path, "locale")
|
||||||
|
# return gettext.translation(domain, localedir=localedir, languages=[languageHandler.getLanguage()], fallback=True)
|
||||||
|
"""
|
||||||
|
def initTranslation():
|
||||||
|
addon = getCodeAddon(frameDist=2)
|
||||||
|
translations = addon.getTranslationsInstance()
|
||||||
|
# Point _ to the translation object in the globals namespace of the caller frame
|
||||||
|
# FIXME: should we retrieve the caller module object explicitly?
|
||||||
|
try:
|
||||||
|
callerFrame = inspect.currentframe().f_back
|
||||||
|
callerFrame.f_globals['_'] = translations.gettext
|
||||||
|
# Install our pgettext function.
|
||||||
|
callerFrame.f_globals['pgettext'] = languageHandler.makePgettext(translations)
|
||||||
|
finally:
|
||||||
|
del callerFrame # Avoid reference problems with frames (per python docs)
|
||||||
|
"""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user