add basic plugin capability

This commit is contained in:
Chrys 2024-10-21 02:18:04 +02:00
parent 8c21412b54
commit f17efc3da8
53 changed files with 3746 additions and 0 deletions

79
m4/build-to-host.m4 Normal file
View 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
View 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"
])

View 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
View 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())

View 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

View 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

View 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)

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
ByeCthulhu.plugin \
ByeCthulhu.py
orca_pythondir=$(pkgpythondir)/plugins/ByeCthulhu

View 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

View 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

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
CapsLockHack.plugin \
CapsLockHack.py
orca_pythondir=$(pkgpythondir)/plugins/CapsLockHack

View 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

View 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

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
Clipboard.plugin \
Clipboard.py
orca_pythondir=$(pkgpythondir)/plugins/Clipboard

View File

@ -0,0 +1,6 @@
[Plugin]
Module=Date
Loader=python3
Name=Date
Description=Present the current date
Authors=Chrys chrys@linux-a11y.org

View 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

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
Date.plugin \
Date.py
orca_pythondir=$(pkgpythondir)/plugins/Date

View File

View 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

View 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)

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
HelloCthulhu.plugin \
HelloCthulhu.py
orca_pythondir=$(pkgpythondir)/plugins/HelloCthulhu

View 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

View 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

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
HelloWorld.plugin \
HelloWorld.py
orca_pythondir=$(pkgpythondir)/plugins/HelloWorld

View File

@ -0,0 +1,4 @@
SUBDIRS = Clipboard HelloWorld SelfVoice Time MouseReview Date ByeCthulhu HelloCthulhu PluginManager CapsLockHack
orca_pythondir=$(pkgpythondir)/plugins

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
MouseReview.plugin \
MouseReview.py
orca_pythondir=$(pkgpythondir)/plugins/MouseReview

View 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

View 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)

View File

@ -0,0 +1,8 @@
orca_python_PYTHON = \
__init__.py \
PluginManager.plugin \
PluginManager.py \
PluginManagerUi.py
orca_pythondir=$(pkgpythondir)/plugins/PluginManager

View 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=

View 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()

View 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()

View 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()

View 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()

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
SelfVoice.plugin \
SelfVoice.py
orca_pythondir=$(pkgpythondir)/plugins/SelfVoice

View 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

View 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)

View File

@ -0,0 +1,7 @@
orca_python_PYTHON = \
__init__.py \
Time.plugin \
Time.py
orca_pythondir=$(pkgpythondir)/plugins/Time

View File

@ -0,0 +1,6 @@
[Plugin]
Module=Time
Loader=python3
Name=Time
Description=Present current time
Authors=Chrys chrys@linux-a11y.org

View 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

View File

View 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)

View 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))

View 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

View 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)
"""