Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20e61d6259 | ||
|
|
a750761f61 | ||
|
|
03b3880622 | ||
|
|
f4af54228a | ||
|
|
518d2b3bb6 | ||
|
|
7043f08dab | ||
|
|
d0bc7d8a3a | ||
|
|
438fae4fef | ||
|
|
a7cd1d033a | ||
|
|
c4890f31af | ||
|
|
c44245189a | ||
|
|
c7b8e4a30d | ||
|
|
20fe1a7e30 | ||
|
|
25931758f7 | ||
|
|
daf57ef22c | ||
|
|
1d2d727fa2 | ||
|
|
11bd7107d2 | ||
|
|
95d33047fa | ||
|
|
523b896053 | ||
|
|
9152455227 | ||
|
|
c0fdaca4d0 | ||
|
|
ed78ffc248 |
@@ -14,6 +14,16 @@ This repository is a screen reader. Prioritize accessibility, correctness, and s
|
||||
- `meson compile -C _build`
|
||||
- `meson install -C _build`
|
||||
|
||||
## Runtime target and testing rules
|
||||
- Make source changes in this repo, not in `~/.local/lib/python*/site-packages/cthulhu/`, unless the user explicitly asks for an installed-package hotfix.
|
||||
- If you confirm the active import comes from `~/.local/...`, that does **not** mean you should edit there. It means you should update the repo and then run `./build-local.sh` to replace the installed copy for testing.
|
||||
- Default test/apply workflow for local Cthulhu fixes:
|
||||
- edit repo files
|
||||
- run `./build-local.sh`
|
||||
- reproduce/test against the refreshed `~/.local` install
|
||||
- If repo and installed behavior differ, prefer rebuilding with `./build-local.sh` over patching the installed package directly.
|
||||
- Treat direct edits under `~/.local/.../cthulhu/` as an exception path that requires explicit user approval.
|
||||
|
||||
## Coding guidelines
|
||||
- **When modifying existing code:** follow the surrounding code’s conventions.
|
||||
- **When writing new code from scratch:** prefer
|
||||
@@ -55,3 +65,8 @@ This repository is a screen reader. Prioritize accessibility, correctness, and s
|
||||
## Meson install reminder (important)
|
||||
- If you add new Python modules under `src/cthulhu/`, update `src/cthulhu/meson.build` so they get installed (otherwise imports can fail after install).
|
||||
- If you add a new plugin directory, update `src/cthulhu/plugins/meson.build` and add a `meson.build` in the plugin directory.
|
||||
|
||||
## Common Cthulhu agent mistakes
|
||||
- Checking the import origin, seeing `~/.local/...`, and then editing the installed package instead of the repo.
|
||||
- Forgetting that `./build-local.sh` is the normal way to apply repo changes into the installed copy for testing.
|
||||
- Making repo fixes and then diagnosing the old installed copy without rebuilding.
|
||||
|
||||
@@ -8,13 +8,11 @@ set -e # Exit on error
|
||||
|
||||
# Colors for output (only if stdout is a terminal)
|
||||
if [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
else
|
||||
RED=''
|
||||
GREEN=''
|
||||
YELLOW=''
|
||||
BLUE=''
|
||||
@@ -56,12 +54,6 @@ if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
|
||||
# Remove data files
|
||||
|
||||
# Remove desktop files
|
||||
if [[ -f "$HOME/.local/share/applications/cthulhu-autostart.desktop" ]]; then
|
||||
rm -f "$HOME/.local/share/applications/cthulhu-autostart.desktop"
|
||||
echo " Removed: ~/.local/share/applications/cthulhu-autostart.desktop"
|
||||
fi
|
||||
|
||||
# Remove icons
|
||||
for size in 16x16 22x22 24x24 32x32 48x48 256x256 scalable symbolic; do
|
||||
icon_path="$HOME/.local/share/icons/hicolor/$size/apps"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Cthulhu Screen Reader
|
||||
Exec=cthulhu
|
||||
NoDisplay=true
|
||||
# Desktop-neutral autostart - no GNOME-specific conditions
|
||||
# Users can enable/disable via their desktop environment's accessibility settings
|
||||
# or by adding/removing this file from ~/.config/autostart/
|
||||
X-GNOME-AutoRestart=true
|
||||
Categories=Accessibility;
|
||||
Keywords=screen;reader;accessibility;speech;braille;
|
||||
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||
|
||||
pkgname=cthulhu
|
||||
pkgver=2026.02.17
|
||||
pkgver=2026.03.02
|
||||
pkgrel=1
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
@@ -84,7 +84,7 @@ makedepends=(
|
||||
)
|
||||
install=cthulhu.install
|
||||
source=(
|
||||
"git+https://git.stormux.org/storm/cthulhu.git"
|
||||
"git+https://git.stormux.org/storm/cthulhu.git#tag=${pkgver}"
|
||||
"cthulhu.install"
|
||||
)
|
||||
b2sums=(
|
||||
@@ -92,11 +92,6 @@ b2sums=(
|
||||
'SKIP'
|
||||
)
|
||||
|
||||
pkgver() {
|
||||
cd cthulhu
|
||||
grep "^version = " src/cthulhu/cthulhuVersion.py | sed 's/version = "\(.*\)"/\1/'
|
||||
}
|
||||
|
||||
build() {
|
||||
cd cthulhu
|
||||
arch-meson _build
|
||||
|
||||
+1
-11
@@ -1,5 +1,5 @@
|
||||
project('cthulhu',
|
||||
version: '2026.02.17-master',
|
||||
version: '2026.03.02-master',
|
||||
meson_version: '>= 1.0.0',
|
||||
)
|
||||
|
||||
@@ -93,16 +93,6 @@ else
|
||||
summary += {'sound support': 'no (missing gstreamer)'}
|
||||
endif
|
||||
|
||||
# Integration with session startup
|
||||
i18n.merge_file(
|
||||
input: 'cthulhu-autostart.desktop.in',
|
||||
output: '@BASENAME@',
|
||||
type: 'desktop',
|
||||
po_dir: meson.project_source_root() / 'po',
|
||||
install: true,
|
||||
install_dir: get_option('sysconfdir') / 'xdg' / 'autostart',
|
||||
)
|
||||
|
||||
# Update icon cache manually (desktop-neutral) - optional, ignore failures
|
||||
gtk_update_icon_cache = find_program('gtk4-update-icon-cache', required: false)
|
||||
if gtk_update_icon_cache.found()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# List of source files containing translatable strings.
|
||||
# Please keep this file sorted alphabetically.
|
||||
cthulhu-autostart.desktop.in
|
||||
src/cthulhu/braille_rolenames.py
|
||||
src/cthulhu/brltablenames.py
|
||||
src/cthulhu/chnames.py
|
||||
|
||||
@@ -13,11 +13,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-DamnedLies-Scope: partial\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr ""
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lector de pantalla Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "lector de pantalla; fabla; braille;"
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
||||
"X-Poedit-Language: Arabic\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "قارئ الشاشة Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "شاشة;قارئ;إمكانية الوصول;كلام;برايل;"
|
||||
|
||||
|
||||
@@ -19,11 +19,9 @@ msgstr ""
|
||||
"X-Poedit-Language: Asturian\n"
|
||||
"X-Poedit-SourceCharset: utf-8\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Llector de pantalla d'Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Чытач з экрана Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "чытач з экрана;экран;голас;маўленне;Брайль;"
|
||||
|
||||
@@ -23,11 +23,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu — екранен четец"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "екран;четец;достъпност;говор;брайл;"
|
||||
|
||||
|
||||
@@ -21,12 +21,10 @@ msgstr ""
|
||||
"X-Generator: KBabel 1.9.1\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "অর্কা স্ক্রীন রিডার / বিবর্ধন"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,12 +18,10 @@ msgstr ""
|
||||
"X-Generator: KBabel 1.9.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu'র বৈশিষ্ট্য"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -15,11 +15,9 @@ msgstr ""
|
||||
"X-Launchpad-Export-Date: 2015-02-05 06:46+0000\n"
|
||||
"X-Generator: Poedit 1.7.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu čitač ekrana"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "čitač ekrana;govor;brailleovo pismo;"
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lector de pantalla Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "pantalla;lector;accessibilitat;veu;braille;"
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Poedit 1.8.11\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lector de pantalla Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "lector de pantalla;pronunciació;braille;"
|
||||
|
||||
|
||||
@@ -19,12 +19,10 @@ msgstr ""
|
||||
"X-Launchpad-Export-Date: 2020-05-04 21:32+0000\n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "خوێنەری پەردەی ئۆرکا"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Čtečka obrazovky Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "obrazovka;čtečka;přístupnost;řeč;braille;"
|
||||
|
||||
|
||||
@@ -17,12 +17,10 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != "
|
||||
"11) ? 2 : 3;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Hoffterau Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Skærmlæseren Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "skærm;læser;tilgængelighed;tale;braille;"
|
||||
|
||||
|
||||
@@ -37,11 +37,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu-Bildschirmleser"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "bildschirm;leser;barrierefreiheit;sprache;braille;"
|
||||
|
||||
|
||||
@@ -20,12 +20,10 @@ msgstr ""
|
||||
"X-Poedit-SourceCharset: utf-8\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n!=1)\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ཨོར་ཀ་གསལ་གཞི་ལྷག་བྱེད་/ཆེ་ཤེལ་"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -28,11 +28,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Αναγνώστης οθόνης Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "οθόνη;αναγνώστης;προσβασιμότητα;ομιλία;μπράιγ;"
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr ""
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -21,11 +21,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Gtranslator 45.3\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu Screen Reader"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "screen reader;speech;braille;"
|
||||
|
||||
|
||||
@@ -21,11 +21,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Ekranlegilo Orko"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "ekranlegilo;parolo;brajlo;"
|
||||
|
||||
@@ -28,11 +28,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"X-Generator: Gtranslator 45.alpha0\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lector de pantalla Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "lector;pantalla;accesibilidad;voz;braille;"
|
||||
|
||||
|
||||
@@ -20,12 +20,10 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu ekraanilugeja ja luup"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu pantaila-irakurlea"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "pantaila;irakurlea;irisgarritasuna;mintzamena;braillea;"
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "صفحهخوان اورکا"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
"X-Poedit-Bookmarks: 814,-1,-1,-1,-1,-1,-1,-1,-1,-1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu-näytönlukija"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "näyttö;lukija;saavutettavuus;puhe;pistekirjoitus;"
|
||||
|
||||
|
||||
@@ -31,11 +31,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lecteur d’écran Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "lecteur;écran;accessibilité;parole;braille;"
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr ""
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,12 +18,10 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=5; plural=n==1 ? 0 : (n%10==1 || n%10==2) ? 1 : "
|
||||
"(n%10>=3 && n%10<= 6) ? 2 : ((n%10>=7 && n%10<=9) || n==10) ? 3 : 4;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Léitheoir Scáileáin agus Formhéadaitheoir Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -34,11 +34,9 @@ msgstr ""
|
||||
"X-DL-Domain: po\n"
|
||||
"X-DL-State: Translating\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lector da pantalla Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "pantalla;lector;accesibilidade;fala;braille;"
|
||||
|
||||
|
||||
@@ -33,12 +33,10 @@ msgstr ""
|
||||
"\n"
|
||||
"\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu સ્ક્રીન વાંચક અને વિસ્તૃતકારક"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -14,11 +14,9 @@ msgstr ""
|
||||
"2 : 3);\n"
|
||||
"X-Generator: Poedit 3.4.1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "מקריא המסך Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "מסך;קורא;נגישות;דיבור;ברייל;"
|
||||
|
||||
|
||||
@@ -22,11 +22,9 @@ msgstr ""
|
||||
"\n"
|
||||
"X-Generator: Lokalize 1.5\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu स्क्रीन वाचक"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "स्क्रीन रीडर;भाषण;ब्रेल;"
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"X-Launchpad-Export-Date: 2016-09-15 12:40+0000\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu čitač zaslona"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "čitač zaslona;govor;brajica;"
|
||||
|
||||
|
||||
@@ -22,11 +22,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
|
||||
# Megjegyzés: Mivel mindenhol régóta Gáborral egyeztetve az Cthulhu nevet magyarosan Orkaként írjuk a fordításban, így a fordítást megváltoztattam Orka képernyőolvasóra az Cthulhu képernyőolvasó helyett.
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Orka képernyőolvasó"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "képernyő;olvasó;akadálymentesítés;beszéd;braille;"
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Pembaca Layar Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "layar;pembaca;aksesibilitas;ucapan;braille;"
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Lokalize 1.5\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu skjálestur"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -39,11 +39,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"X-Generator: Gtranslator 42.0\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lettore schermo Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "lettore;schermo;accessibilità;voce;braille;"
|
||||
|
||||
|
||||
@@ -25,11 +25,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu スクリーンリーダー"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "スクリーン;リーダー;アクセシビリティ;音声;点字;"
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu - ეკრანის მკითხველი"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "ეკრანი;მკითხველი;ხელმისაწვდომობა;მეტყველება;ბრაილი;"
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr ""
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu экраннан оқитын қолданбасы"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,12 +18,10 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: KBabel 1.11.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ಆದ್ಯತೆಗಳು"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -15,11 +15,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu 화면 낭독기"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "화면;리더;접근성;음성;점자;"
|
||||
|
||||
|
||||
@@ -25,11 +25,9 @@ msgstr ""
|
||||
"(n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu ekrano skaityklė"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "ekrano skaityklė;kalba;brailio raštas;"
|
||||
|
||||
|
||||
@@ -23,11 +23,9 @@ msgstr ""
|
||||
"2);\n"
|
||||
"X-Generator: Lokalize 21.12.3\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu ekrāna lasītājs"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "ekrāna lasītājs;runa;Brails;"
|
||||
|
||||
|
||||
@@ -19,12 +19,10 @@ msgstr ""
|
||||
"\n"
|
||||
"\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu स्क्रीन पाठक / आवर्द्धक"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural= n==1 || n%10==1 ? 0 : 1\n"
|
||||
"X-Generator: KBabel 1.11.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu читач на екранот"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -22,12 +22,10 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.0.1\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "സ്ക്രീന് വായന അവസാനിപ്പിക്കാം."
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,12 +18,10 @@ msgstr ""
|
||||
"X-Generator: KBabel 1.9.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "वर्धक भिंग"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Pembaca skrin Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "skrin;pembaca;kebolehcapaian;pertuturan;braille;"
|
||||
|
||||
|
||||
@@ -19,11 +19,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu skjermleser"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "skjerm;leser;tilgjengelighet;tale;blindeskrift;"
|
||||
|
||||
|
||||
@@ -13,11 +13,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ओर्का दृष्टि वाचक"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "दृष्टि वाचक; वाचन;ब्रेल;"
|
||||
|
||||
|
||||
@@ -31,11 +31,9 @@ msgstr ""
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
|
||||
# vergrootglas/loep
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu schermlezer"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "scherm;lezer;toegankelijkheid;spraak;braille;"
|
||||
|
||||
|
||||
@@ -18,12 +18,10 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu skjermlesar og forstørringsglas"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -23,11 +23,9 @@ msgstr ""
|
||||
"X-Launchpad-Export-Date: 2015-05-21 18:05+0000\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Lector d'ecran Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
"lector d'ecran;oral;braille;votz;sintèsi vocala;sintesi vocala;lector "
|
||||
|
||||
@@ -22,12 +22,10 @@ msgstr ""
|
||||
"\n"
|
||||
"\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ଓର୍କା ସ୍କ୍ରିନ ପାଠକ /ଛୋଟକୁ ବଡ ଦେଖଉଥିବା ଉପକରଣ "
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-Bookmarks: 589,-1,-1,-1,-1,-1,-1,-1,-1,-1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ਓਰਕਾ ਸਕਰੀਨ ਰੀਡਰ"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Czytnik ekranowy Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "czytnik;ekran;dostępność;mowa;braille;"
|
||||
|
||||
|
||||
@@ -28,11 +28,9 @@ msgstr ""
|
||||
"X-Source-Language: C\n"
|
||||
"X-Project-Style: gnome\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Leitor de ecrã Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "leitor;ecrã;acessibilidade;voz;braille;"
|
||||
|
||||
|
||||
@@ -49,11 +49,9 @@ msgstr ""
|
||||
"X-DL-Domain: po\n"
|
||||
"X-DL-State: Translating\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Leitor de tela Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "leitor;tela;acessibilidade;voz;braille;"
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"X-Project-Style: gnome\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cititorul de ecran Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "ecran;cititor;accesibilitate;vorbire;braille;"
|
||||
|
||||
|
||||
@@ -22,11 +22,9 @@ msgstr ""
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Экранный диктор Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "экран;чтец;доступность;речь;брайль;"
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr ""
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: KBabel 1.11.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr ""
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -19,11 +19,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n"
|
||||
"X-Generator: Gtranslator 2.91.7\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Ukončí čítačku obrazovky"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -22,11 +22,9 @@ msgstr ""
|
||||
"X-Poedit-SourceCharset: utf-8\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Zaslonski bralnik Orka"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "govor;zaslonski bralnik;Braillova pisava;brajica;povečevalnik"
|
||||
|
||||
|
||||
@@ -17,12 +17,10 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
#, fuzzy
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Ekrani Zmadhues (Lupë)"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -21,11 +21,9 @@ msgstr ""
|
||||
"X-Project-Style: gnome\n"
|
||||
"X-Generator: Poedit 3.1.1\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Читач екрана Орка"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "читач екрана;говор;брајева азбука;"
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"X-Project-Style: gnome\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Čitač ekrana Orka"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "čitač ekrana;govor;brajeva azbuka;"
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu skärmläsare"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "skärm;läsare;tillgänglighet;tal;punktskrift;"
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n!=1);\\n\n"
|
||||
"\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ஆர்கா திரை படிப்பி "
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "திரைபடிப்பான்;பேசுதல்;ப்ரெய்ல்;"
|
||||
|
||||
@@ -23,11 +23,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
|
||||
"X-Generator: KBabel 1.11.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "ఓర్కా తెరచదువరి"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.6.3\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Хонандаи экрани Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "хонандаи экран;нутқ;брайл;"
|
||||
|
||||
@@ -19,11 +19,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "โปรแกรมอ่านหน้าจอ Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "หน้าจอ;อ่าน;การเข้าถึง;เสียงพูด;อักษรเบรลล์;"
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.4\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu Ekran Okuyucu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "ekran;okuyucu;erişilebilirlik;konuşma;braille;"
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu ئېكران ئوقۇغۇ"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "screen reader;speech;braille;ئېكران ئوقۇغۇچ;تاۋۇش;ئەمالار ئېلىپبەسى;"
|
||||
|
||||
@@ -26,11 +26,9 @@ msgstr ""
|
||||
"n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Lokalize 20.12.0\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Інструмент читання з екрана «Cthulhu»"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "екран;читач;доступність;мовлення;брайль;"
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: LocFactoryEditor 1.8\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Trình đọc màn hình Cthulhu"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "màn hình;đọc;trợ năng;giọng nói;chữ nổi;"
|
||||
|
||||
|
||||
@@ -29,11 +29,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu 屏幕阅读器"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "屏幕;阅读器;无障碍;语音;盲文;"
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.6.5\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu 螢幕閱讀器"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
#, fuzzy
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "screen reader;speech;braille;螢幕閱讀器;語音;點字;"
|
||||
|
||||
@@ -20,11 +20,9 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.8.12\n"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:3
|
||||
msgid "Cthulhu Screen Reader"
|
||||
msgstr "Cthulhu 螢幕閱讀器"
|
||||
|
||||
#: cthulhu-autostart.desktop.in:11
|
||||
msgid "screen;reader;accessibility;speech;braille;"
|
||||
msgstr "螢幕;閱讀器;無障礙;語音;點字;"
|
||||
|
||||
|
||||
@@ -204,10 +204,21 @@ class AXHypertext:
|
||||
|
||||
@staticmethod
|
||||
def find_child_at_offset(obj: Atspi.Accessible, offset: int) -> Optional[Atspi.Accessible]:
|
||||
"""Attempts to correct for off-by-one brokenness in implementations"""
|
||||
"""Returns the child at offset, correcting for broken hypertext offset mappings."""
|
||||
|
||||
if child := AXHypertext.get_child_at_offset(obj, offset):
|
||||
return child
|
||||
offset_in_parent = AXHypertext.get_character_offset_in_parent(child)
|
||||
if offset_in_parent == offset:
|
||||
return child
|
||||
|
||||
tokens = [
|
||||
f"AXHypertext: Child at offset {offset} in",
|
||||
obj,
|
||||
"is",
|
||||
child,
|
||||
f"but reports offset {offset_in_parent}",
|
||||
]
|
||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
if child_before := AXHypertext.get_child_at_offset(obj, offset - 1):
|
||||
offset_in_parent = AXHypertext.get_character_offset_in_parent(child_before)
|
||||
@@ -225,6 +236,30 @@ class AXHypertext:
|
||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||
return child_after
|
||||
|
||||
for i in range(AXHypertext._get_link_count(obj)):
|
||||
link = AXHypertext._get_link_at_index(obj, i)
|
||||
if link is None or AXHypertext.get_link_start_offset(link) != offset:
|
||||
continue
|
||||
|
||||
try:
|
||||
child = Atspi.Hyperlink.get_object(link, 0)
|
||||
except GLib.GError as error:
|
||||
msg = f"AXHypertext: Exception in find_child_at_offset: {error}"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
continue
|
||||
|
||||
if child is None:
|
||||
continue
|
||||
|
||||
tokens = [
|
||||
f"AXHypertext: Child at offset {offset} in",
|
||||
obj,
|
||||
"found via link enumeration:",
|
||||
child,
|
||||
]
|
||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||
return child
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -85,12 +85,16 @@ class Backend:
|
||||
with open(fileName, 'w', encoding='utf-8') as settingsFile:
|
||||
settingsFile.write(dumps(prefsDoc))
|
||||
|
||||
def _updateTable(self, targetTable, newValues):
|
||||
def _updateTable(self, targetTable, newValues, preserveMissingKeys=None):
|
||||
if not isinstance(newValues, dict):
|
||||
return
|
||||
|
||||
preserveMissingKeys = set(preserveMissingKeys or [])
|
||||
|
||||
for key in list(targetTable.keys()):
|
||||
if key not in newValues:
|
||||
if key in preserveMissingKeys:
|
||||
continue
|
||||
del targetTable[key]
|
||||
continue
|
||||
newValue = newValues[key]
|
||||
@@ -173,7 +177,12 @@ class Backend:
|
||||
profiles[profile] = {}
|
||||
profileTable = profiles[profile]
|
||||
|
||||
self._updateTable(profileTable, general)
|
||||
# Keep plugin persistence keys when callers provide partial updates.
|
||||
self._updateTable(
|
||||
profileTable,
|
||||
general,
|
||||
preserveMissingKeys={"activePlugins", "pluginSources"},
|
||||
)
|
||||
self._writeDocument(self.settingsFile, prefsDoc)
|
||||
|
||||
def _getSettings(self):
|
||||
@@ -213,12 +222,33 @@ class Backend:
|
||||
|
||||
return settingsDict
|
||||
|
||||
def _hasLikelyCustomEchoVoice(self, echoVoice, defaultVoice):
|
||||
"""Return True when echoVoice appears intentionally customized."""
|
||||
|
||||
if not echoVoice:
|
||||
return False
|
||||
|
||||
echoEstablished = bool(echoVoice.get('established', False))
|
||||
if not defaultVoice:
|
||||
return echoEstablished or bool(echoVoice.get(acss.ACSS.FAMILY))
|
||||
|
||||
for key in [acss.ACSS.RATE, acss.ACSS.AVERAGE_PITCH, acss.ACSS.GAIN, acss.ACSS.FAMILY]:
|
||||
echoValue = echoVoice.get(key)
|
||||
defaultValue = defaultVoice.get(key)
|
||||
if echoValue is not None and echoValue != defaultValue:
|
||||
return True
|
||||
|
||||
return echoEstablished and bool(echoVoice.get(acss.ACSS.FAMILY))
|
||||
|
||||
def getGeneral(self, profile=None):
|
||||
""" Get general settings from default settings and
|
||||
override with profile values. """
|
||||
self._getSettings()
|
||||
generalSettings = self.general.copy()
|
||||
generalSettings = self._migrateSettings(generalSettings)
|
||||
# Plugin state is profile-scoped; ignore legacy/global values.
|
||||
generalSettings.pop('activePlugins', None)
|
||||
generalSettings.pop('pluginSources', None)
|
||||
defaultProfile = generalSettings.get('startingProfile',
|
||||
['Default', 'default'])
|
||||
if profile is None:
|
||||
@@ -229,8 +259,24 @@ class Backend:
|
||||
if key == 'voices':
|
||||
for voiceType, voiceDef in value.items():
|
||||
value[voiceType] = acss.ACSS(voiceDef)
|
||||
if key == 'echoVoice' and isinstance(value, dict):
|
||||
value = acss.ACSS(value)
|
||||
if key not in ['startingProfile', 'activeProfile']:
|
||||
generalSettings[key] = value
|
||||
|
||||
# Backward compatibility: recover custom echo behavior when legacy
|
||||
# profiles have echoVoice but are missing custom-echo toggle keys.
|
||||
if 'useCustomEchoVoice' not in profileSettings:
|
||||
voices = generalSettings.get('voices') or {}
|
||||
defaultVoice = voices.get(settings.DEFAULT_VOICE)
|
||||
echoVoice = generalSettings.get('echoVoice')
|
||||
if self._hasLikelyCustomEchoVoice(echoVoice, defaultVoice):
|
||||
generalSettings['useCustomEchoVoice'] = True
|
||||
if 'useCustomEchoForKey' not in profileSettings:
|
||||
generalSettings['useCustomEchoForKey'] = True
|
||||
if 'useCustomEchoForCharacter' not in profileSettings:
|
||||
generalSettings['useCustomEchoForCharacter'] = True
|
||||
|
||||
try:
|
||||
generalSettings['activeProfile'] = profileSettings['profile']
|
||||
except KeyError:
|
||||
|
||||
@@ -136,6 +136,24 @@
|
||||
<property name="step_increment">0.10000000149</property>
|
||||
<property name="page_increment">1</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="echoPitchAdjustment">
|
||||
<property name="upper">10</property>
|
||||
<property name="value">5</property>
|
||||
<property name="step_increment">0.10000000149</property>
|
||||
<property name="page_increment">1</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="echoRateAdjustment">
|
||||
<property name="upper">100</property>
|
||||
<property name="value">50</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="echoVolumeAdjustment">
|
||||
<property name="upper">10</property>
|
||||
<property name="value">10</property>
|
||||
<property name="step_increment">0.10000000149</property>
|
||||
<property name="page_increment">1</property>
|
||||
</object>
|
||||
<object class="GtkDialog" id="cthulhuSetupWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Cthulhu Preferences</property>
|
||||
@@ -553,7 +571,7 @@
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">timeFormatCombo</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="availableProfilesComboBox1"/>
|
||||
<relation type="label-for" target="timeFormatCombo"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -570,7 +588,7 @@
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">dateFormatCombo</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="availableProfilesComboBox2"/>
|
||||
<relation type="label-for" target="dateFormatCombo"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1035,6 +1053,51 @@
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="soundSinkHBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="soundSinkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes" comments="Translators: This is the label for a combo box where users can choose which audio backend Cthulhu should use.">Audio _backend:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">soundSinkCombo</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="soundSinkCombo"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="soundSinkCombo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="changed" handler="soundSinkComboChanged" swapped="no"/>
|
||||
<accessibility>
|
||||
<relation type="labelled-by" target="soundSinkLabel"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="soundThemeHBox">
|
||||
<property name="visible">True</property>
|
||||
@@ -1077,7 +1140,7 @@
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1122,7 +1185,7 @@
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -1133,7 +1196,7 @@
|
||||
<object class="GtkLabel" id="soundThemeTitleLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Translators: This is the title of a section in the preferences dialog containing sound theme options.">Sound Theme</property>
|
||||
<property name="label" translatable="yes" comments="Translators: This is the title of a section in the preferences dialog containing sound options.">Sound</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
@@ -3037,6 +3100,295 @@
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="echoVoiceFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="echoVoiceAlignment">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="echoVoiceGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="row_spacing">10</property>
|
||||
<property name="column_spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="useCustomEchoVoiceCheckButton">
|
||||
<property name="label" translatable="yes">Use c_ustom echo voice settings</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="useCustomEchoVoiceToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="useCustomEchoSpeechServerCheckButton">
|
||||
<property name="label" translatable="yes">Use custom _speech-dispatcher module for echo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="useCustomEchoSpeechServerToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="echoSpeechServersLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Echo _module:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">echoSpeechServers</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="echoSpeechServers"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="echoSpeechServers">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="changed" handler="echoSpeechServersChanged" swapped="no"/>
|
||||
<accessibility>
|
||||
<relation type="labelled-by" target="echoSpeechServersLabel"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="echoSpeechFamiliesLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Echo _voice:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">echoSpeechFamilies</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="echoSpeechFamilies"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="echoSpeechFamilies">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="changed" handler="echoSpeechFamiliesChanged" swapped="no"/>
|
||||
<accessibility>
|
||||
<relation type="labelled-by" target="echoSpeechFamiliesLabel"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="echoRateLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Echo ra_te:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">echoRateScale</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="echoRateScale"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="echoRateScale">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">echoRateAdjustment</property>
|
||||
<property name="round_digits">0</property>
|
||||
<property name="digits">0</property>
|
||||
<property name="value_pos">right</property>
|
||||
<signal name="value-changed" handler="echoRateValueChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="echoPitchLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Echo pi_tch:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">echoPitchScale</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="echoPitchScale"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="echoPitchScale">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">echoPitchAdjustment</property>
|
||||
<property name="round_digits">1</property>
|
||||
<property name="value_pos">right</property>
|
||||
<signal name="value-changed" handler="echoPitchValueChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="echoVolumeLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Echo vo_lume:</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">echoVolumeScale</property>
|
||||
<accessibility>
|
||||
<relation type="label-for" target="echoVolumeScale"/>
|
||||
</accessibility>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="echoVolumeScale">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="adjustment">echoVolumeAdjustment</property>
|
||||
<property name="round_digits">1</property>
|
||||
<property name="value_pos">right</property>
|
||||
<signal name="value-changed" handler="echoVolumeValueChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="useCustomEchoForKeyCheckButton">
|
||||
<property name="label" translatable="yes">Use custom voice for _key echo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">7</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="useCustomEchoForCharacterCheckButton">
|
||||
<property name="label" translatable="yes">Use custom voice for _character echo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">8</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="useCustomEchoForWordCheckButton">
|
||||
<property name="label" translatable="yes">Use custom voice for _word echo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">9</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="useCustomEchoForSentenceCheckButton">
|
||||
<property name="label" translatable="yes">Use custom voice for _sentence echo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">10</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="echoVoiceLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Echo Voice</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="enableEchoByCharacterCheckButton">
|
||||
<property name="label" translatable="yes" comments="Translators: When this option is enabled, inserted text of length 1 is spoken.">Enable echo by cha_racter</property>
|
||||
@@ -3049,7 +3401,7 @@
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -3064,7 +3416,7 @@
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="top_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -3079,7 +3431,7 @@
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="top_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
+10
-2
@@ -769,7 +769,7 @@ def shutdown(script: Optional[Any] = None, inputEvent: Optional[Any] = None) ->
|
||||
cthulhu_state.activeScript.presentationInterrupt()
|
||||
|
||||
cthulhuApp.getSignalManager().emitSignal('stop-application-completed')
|
||||
sound_theme_manager.getManager().playStopSound(wait=True)
|
||||
sound_theme_manager.getManager().playStopSound(wait=True, timeoutSeconds=1)
|
||||
cthulhuApp.getPluginSystemManager().unloadAllPlugins(ForceAllPlugins=True)
|
||||
|
||||
# Deactivate the event manager first so that it clears its queue and will not
|
||||
@@ -893,7 +893,14 @@ def main() -> int:
|
||||
debug.printMessage(debug.LEVEL_INFO, "CTHULHU: Initialized.", True)
|
||||
|
||||
script = cthulhu_state.activeScript
|
||||
sound_theme_manager.getManager().playStartSound(wait=True)
|
||||
sound_theme_manager.getManager().playStartSound(wait=False)
|
||||
soundFailureReason = sound.getSoundSystemFailureReason()
|
||||
if soundFailureReason:
|
||||
debug.printMessage(
|
||||
debug.LEVEL_INFO,
|
||||
f"CTHULHU: Startup sound failed. Continuing without sound. Reason: {soundFailureReason}",
|
||||
True
|
||||
)
|
||||
cthulhuApp.getSignalManager().emitSignal('start-application-completed')
|
||||
if script:
|
||||
window = script.utilities.activeWindow()
|
||||
@@ -944,6 +951,7 @@ class Cthulhu(GObject.Object):
|
||||
self.settingsManager: SettingsManager = settings_manager.SettingsManager(self) # Directly instantiate
|
||||
self.eventManager: EventManager = event_manager.EventManager(self) # Directly instantiate
|
||||
self.scriptManager: ScriptManager = script_manager.ScriptManager(self) # Directly instantiate
|
||||
script_manager._manager = self.scriptManager
|
||||
self.logger: logger.Logger = logger.Logger() # Directly instantiate
|
||||
self.signalManager: SignalManager = signal_manager.SignalManager(self)
|
||||
self.dynamicApiManager: DynamicApiManager = dynamic_api_manager.DynamicApiManager(self)
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
# Forked from Orca screen reader.
|
||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||
|
||||
version = "2026.02.17"
|
||||
version = "2026.03.02"
|
||||
codeName = "master"
|
||||
|
||||
@@ -38,6 +38,9 @@ gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Atspi
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
@@ -57,6 +60,7 @@ from . import cthulhu_gui_profile
|
||||
from . import cthulhu_state
|
||||
from . import settings
|
||||
from . import settings_manager
|
||||
from . import sound_sink
|
||||
from . import input_event
|
||||
from . import input_event_manager
|
||||
from . import keybindings
|
||||
@@ -68,6 +72,7 @@ from . import text_attribute_names
|
||||
from . import sound_theme_manager
|
||||
from . import script_manager
|
||||
from .ax_object import AXObject
|
||||
from .ax_utilities import AXUtilities
|
||||
|
||||
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
||||
|
||||
@@ -173,6 +178,16 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self.speechSystemsChoice = None
|
||||
self.speechSystemsChoices = None
|
||||
self.speechSystemsModel = None
|
||||
self.echoVoice = None
|
||||
self.echoSpeechFamiliesChoice = None
|
||||
self.echoSpeechFamiliesChoices = None
|
||||
self.echoSpeechFamiliesModel = None
|
||||
self.echoSpeechServersChoice = None
|
||||
self.echoSpeechServersChoices = None
|
||||
self.echoSpeechServersModel = None
|
||||
self.initializingEchoSpeech = False
|
||||
self._echoVoiceFetchToken = 0
|
||||
self._updatingEchoSpeechFamilies = False
|
||||
self.systemVoice = None
|
||||
self.uppercaseVoice = None
|
||||
self.window = None
|
||||
@@ -181,13 +196,16 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self.savedPitch = None
|
||||
self.savedRate = None
|
||||
self.soundThemeCombo = None
|
||||
self.soundSinkCombo = None
|
||||
self.roleSoundPresentationCombo = None
|
||||
self._isInitialSetup = False
|
||||
self._updatingSpeechFamilies = False
|
||||
self.selectedFamilyChoices = {}
|
||||
self.selectedLanguageChoices = {}
|
||||
self.profilesCombo = None
|
||||
self.profilesComboModel = None
|
||||
self.startingProfileCombo = None
|
||||
self._initialFocusSyncAttempts = 0
|
||||
self._capturedKey = []
|
||||
self.script = None
|
||||
|
||||
@@ -385,7 +403,12 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self._initComboBox(self.get_widget("speechLanguages"))
|
||||
self.speechFamiliesModel = \
|
||||
self._initComboBox(self.get_widget("speechFamilies"))
|
||||
self.echoSpeechServersModel = \
|
||||
self._initComboBox(self.get_widget("echoSpeechServers"))
|
||||
self.echoSpeechFamiliesModel = \
|
||||
self._initComboBox(self.get_widget("echoSpeechFamilies"))
|
||||
self._initSpeechState()
|
||||
self._initEchoSpeechState()
|
||||
|
||||
# TODO - JD: Will this ever be the case??
|
||||
self._isInitialSetup = \
|
||||
@@ -1271,7 +1294,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
for familyChoice in self.speechFamiliesChoices:
|
||||
name = familyChoice[speechserver.VoiceFamily.NAME]
|
||||
if name == familyName:
|
||||
self.get_widget("speechFamilies").set_active(i)
|
||||
self._setSpeechFamiliesActive(i)
|
||||
self.speechFamiliesChoice = self.speechFamiliesChoices[i]
|
||||
familySet = True
|
||||
break
|
||||
@@ -1295,13 +1318,22 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
if not familySet:
|
||||
tokens = ["PREFERENCES DIALOG: Could not find speech family match for", familyName]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
self.get_widget("speechFamilies").set_active(0)
|
||||
self._setSpeechFamiliesActive(0)
|
||||
self.speechFamiliesChoice = self.speechFamiliesChoices[0]
|
||||
|
||||
if familySet:
|
||||
self.selectedFamilyChoices[self.speechServersChoice,
|
||||
self.speechLanguagesChoice] = i
|
||||
|
||||
def _setSpeechFamiliesActive(self, index):
|
||||
"""Sets speechFamilies active index while suppressing preview chatter."""
|
||||
|
||||
self._updatingSpeechFamilies = True
|
||||
try:
|
||||
self.get_widget("speechFamilies").set_active(index)
|
||||
finally:
|
||||
self._updatingSpeechFamilies = False
|
||||
|
||||
def _setupFamilies(self):
|
||||
"""Gets the list of voice variants for the current speech server and
|
||||
current language.
|
||||
@@ -1353,7 +1385,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
selectedIndex = self.selectedFamilyChoices[self.speechServersChoice,
|
||||
self.speechLanguagesChoice]
|
||||
|
||||
self.get_widget("speechFamilies").set_active(selectedIndex)
|
||||
self._setSpeechFamiliesActive(selectedIndex)
|
||||
|
||||
def _setSpeechLanguagesChoice(self, languageName):
|
||||
"""Sets the active item in the languages ("Language:") combo box
|
||||
@@ -1478,6 +1510,16 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
#
|
||||
if not serverInfo:
|
||||
serverInfo = speech.getInfo()
|
||||
if serverInfo and len(serverInfo) >= 2 and serverInfo[1] == 'default':
|
||||
defaultFamily = None
|
||||
voices = self.prefsDict.get("voices", {})
|
||||
defaultVoice = voices.get(settings.DEFAULT_VOICE) if voices else None
|
||||
if defaultVoice:
|
||||
defaultFamily = acss.ACSS(defaultVoice).get(acss.ACSS.FAMILY)
|
||||
|
||||
resolved = self._resolveSpeechDispatcherServerForFamily(defaultFamily)
|
||||
if resolved is not None:
|
||||
serverInfo = resolved.getInfo()
|
||||
|
||||
valueSet = False
|
||||
i = 0
|
||||
@@ -1661,6 +1703,390 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self._setupSpeechSystems(factories)
|
||||
self.initializingSpeech = False
|
||||
|
||||
def _getSpeechDispatcherFactory(self):
|
||||
"""Returns the Speech Dispatcher factory if available."""
|
||||
|
||||
factories = cthulhu.cthulhuApp.settingsManager.getSpeechServerFactories()
|
||||
for factory in factories:
|
||||
try:
|
||||
if factory.SpeechServer.getFactoryName() == guilabels.SPEECH_DISPATCHER:
|
||||
return factory
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_FINEST)
|
||||
|
||||
return None
|
||||
|
||||
def _setEchoVoiceFamily(self, family):
|
||||
"""Sets echo voice family from the selected server family object."""
|
||||
|
||||
if not family:
|
||||
return
|
||||
|
||||
name = family.get(speechserver.VoiceFamily.NAME)
|
||||
language = family.get(speechserver.VoiceFamily.LANG)
|
||||
dialect = family.get(speechserver.VoiceFamily.DIALECT)
|
||||
variant = family.get(speechserver.VoiceFamily.VARIANT)
|
||||
self.echoVoice[acss.ACSS.FAMILY] = {
|
||||
speechserver.VoiceFamily.NAME: name,
|
||||
speechserver.VoiceFamily.LANG: language,
|
||||
speechserver.VoiceFamily.DIALECT: dialect,
|
||||
speechserver.VoiceFamily.VARIANT: variant,
|
||||
}
|
||||
self.echoVoice['established'] = True
|
||||
|
||||
def _resolveSpeechDispatcherServerForFamily(self, family):
|
||||
"""Returns a concrete Speech Dispatcher server matching the voice family."""
|
||||
|
||||
if not family or not self.speechServersChoices:
|
||||
return None
|
||||
|
||||
name = family.get(speechserver.VoiceFamily.NAME)
|
||||
language = family.get(speechserver.VoiceFamily.LANG)
|
||||
dialect = family.get(speechserver.VoiceFamily.DIALECT)
|
||||
variant = family.get(speechserver.VoiceFamily.VARIANT)
|
||||
if not name:
|
||||
return None
|
||||
|
||||
for server in self.speechServersChoices:
|
||||
info = server.getInfo()
|
||||
if not info or len(info) < 2 or info[1] == 'default':
|
||||
continue
|
||||
|
||||
try:
|
||||
families = server.getVoiceFamilies()
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_FINEST)
|
||||
continue
|
||||
|
||||
for candidate in families:
|
||||
if candidate.get(speechserver.VoiceFamily.NAME) != name:
|
||||
continue
|
||||
if candidate.get(speechserver.VoiceFamily.LANG) != language:
|
||||
continue
|
||||
if candidate.get(speechserver.VoiceFamily.DIALECT) != dialect:
|
||||
continue
|
||||
if candidate.get(speechserver.VoiceFamily.VARIANT) != variant:
|
||||
continue
|
||||
|
||||
tokens = [
|
||||
"PREFERENCES DIALOG: Resolved voice family",
|
||||
name,
|
||||
"to speech server",
|
||||
info,
|
||||
]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
return server
|
||||
|
||||
tokens = [
|
||||
"PREFERENCES DIALOG: Could not resolve speech server for family",
|
||||
family,
|
||||
]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
return None
|
||||
|
||||
def _getSpeechServerChoiceForSave(self):
|
||||
"""Returns the server choice that should be persisted for speech settings."""
|
||||
|
||||
server = self.speechServersChoice
|
||||
if not server or self.speechSystemsChoice != self._getSpeechDispatcherFactory():
|
||||
return server
|
||||
|
||||
info = server.getInfo()
|
||||
if not info or len(info) < 2 or info[1] != 'default':
|
||||
return server
|
||||
|
||||
defaultFamily = None
|
||||
if self.defaultVoice is not None:
|
||||
defaultFamily = self.defaultVoice.get(acss.ACSS.FAMILY)
|
||||
|
||||
resolved = self._resolveSpeechDispatcherServerForFamily(defaultFamily)
|
||||
return resolved or server
|
||||
|
||||
def _getEchoSpeechServerFamilyForSave(self):
|
||||
"""Returns the most specific echo family to use for server resolution."""
|
||||
|
||||
family = None
|
||||
if self.echoVoice is not None:
|
||||
family = self.echoVoice.get(acss.ACSS.FAMILY)
|
||||
|
||||
if family:
|
||||
return family
|
||||
|
||||
if self.defaultVoice is not None:
|
||||
return self.defaultVoice.get(acss.ACSS.FAMILY)
|
||||
|
||||
return None
|
||||
|
||||
def _getEchoSpeechServerChoiceForSave(self):
|
||||
"""Returns the echo speech server choice that should be persisted."""
|
||||
|
||||
server = self.echoSpeechServersChoice
|
||||
if server is None:
|
||||
return None
|
||||
|
||||
info = server.getInfo()
|
||||
if not info or len(info) < 2 or info[1] != 'default':
|
||||
return server
|
||||
|
||||
family = self._getEchoSpeechServerFamilyForSave()
|
||||
resolved = self._resolveSpeechDispatcherServerForFamily(family)
|
||||
if resolved is not None:
|
||||
return resolved
|
||||
|
||||
speechServer = self._getSpeechServerChoiceForSave()
|
||||
if speechServer is not None:
|
||||
speechInfo = speechServer.getInfo()
|
||||
if speechInfo and len(speechInfo) >= 2 and speechInfo[1] != 'default':
|
||||
tokens = [
|
||||
"PREFERENCES DIALOG: Falling back to main speech server for echo",
|
||||
speechInfo,
|
||||
]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
return speechServer
|
||||
|
||||
return server
|
||||
|
||||
def _populateEchoSpeechFamilies(self, families):
|
||||
"""Populate the echo family combobox from the provided families list."""
|
||||
|
||||
combobox = self.get_widget("echoSpeechFamilies")
|
||||
combobox.set_model(None)
|
||||
self.echoSpeechFamiliesModel.clear()
|
||||
self.echoSpeechFamiliesChoices = list(families or [])
|
||||
self.echoSpeechFamiliesChoice = None
|
||||
|
||||
selectedIndex = 0
|
||||
selectedMatchFound = False
|
||||
selectedFamily = self.echoVoice.get(acss.ACSS.FAMILY)
|
||||
selectedName = selectedLanguage = selectedDialect = selectedVariant = None
|
||||
if selectedFamily:
|
||||
selectedName = selectedFamily.get(speechserver.VoiceFamily.NAME)
|
||||
selectedLanguage = selectedFamily.get(speechserver.VoiceFamily.LANG)
|
||||
selectedDialect = selectedFamily.get(speechserver.VoiceFamily.DIALECT)
|
||||
selectedVariant = selectedFamily.get(speechserver.VoiceFamily.VARIANT)
|
||||
|
||||
for i, family in enumerate(self.echoSpeechFamiliesChoices):
|
||||
name = family.get(speechserver.VoiceFamily.NAME) or ""
|
||||
language = family.get(speechserver.VoiceFamily.LANG) or ""
|
||||
dialect = family.get(speechserver.VoiceFamily.DIALECT) or ""
|
||||
variant = family.get(speechserver.VoiceFamily.VARIANT)
|
||||
display = name
|
||||
locale = language
|
||||
if dialect:
|
||||
locale = f"{language}-{dialect}" if language else dialect
|
||||
if locale:
|
||||
display = f"{name} ({locale})"
|
||||
self.echoSpeechFamiliesModel.append((i, display))
|
||||
if selectedName == name and selectedLanguage == language \
|
||||
and selectedDialect == dialect and selectedVariant == variant:
|
||||
selectedIndex = i
|
||||
selectedMatchFound = True
|
||||
|
||||
combobox.set_model(self.echoSpeechFamiliesModel)
|
||||
if self.echoSpeechFamiliesChoices:
|
||||
self._updatingEchoSpeechFamilies = True
|
||||
try:
|
||||
combobox.set_active(selectedIndex)
|
||||
finally:
|
||||
self._updatingEchoSpeechFamilies = False
|
||||
self.echoSpeechFamiliesChoice = self.echoSpeechFamiliesChoices[selectedIndex]
|
||||
|
||||
def _fetchEchoSpeechFamiliesWorker(self, serverInfo, requestToken):
|
||||
"""Fetch module-specific voices in a subprocess and apply asynchronously."""
|
||||
|
||||
queryScript = """
|
||||
import json
|
||||
import sys
|
||||
from cthulhu import speechdispatcherfactory
|
||||
from cthulhu import speechserver
|
||||
|
||||
serverInfo = json.loads(sys.argv[1])
|
||||
if isinstance(serverInfo, list):
|
||||
serverInfo = tuple(serverInfo)
|
||||
|
||||
server = speechdispatcherfactory.SpeechServer.getSpeechServer(serverInfo)
|
||||
families = server.getVoiceFamilies() if server else []
|
||||
|
||||
result = []
|
||||
for family in families:
|
||||
result.append({
|
||||
"name": family.get(speechserver.VoiceFamily.NAME),
|
||||
"lang": family.get(speechserver.VoiceFamily.LANG),
|
||||
"dialect": family.get(speechserver.VoiceFamily.DIALECT),
|
||||
"variant": family.get(speechserver.VoiceFamily.VARIANT),
|
||||
})
|
||||
|
||||
print(json.dumps(result))
|
||||
"""
|
||||
|
||||
familyDefs = None
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[sys.executable, "-c", queryScript, json.dumps(serverInfo)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=4.0,
|
||||
check=False,
|
||||
)
|
||||
if completed.returncode == 0 and completed.stdout.strip():
|
||||
familyDefs = json.loads(completed.stdout.strip())
|
||||
else:
|
||||
tokens = [
|
||||
"PREFERENCES DIALOG: Echo voice fetch failed for",
|
||||
serverInfo,
|
||||
"rc=",
|
||||
completed.returncode,
|
||||
]
|
||||
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
|
||||
except subprocess.TimeoutExpired:
|
||||
tokens = ["PREFERENCES DIALOG: Echo voice fetch timed out for", serverInfo]
|
||||
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_WARNING)
|
||||
|
||||
GLib.idle_add(self._applyFetchedEchoSpeechFamilies, requestToken, familyDefs)
|
||||
|
||||
def _applyFetchedEchoSpeechFamilies(self, requestToken, familyDefs):
|
||||
"""Apply fetched echo families if this is still the active request."""
|
||||
|
||||
if requestToken != self._echoVoiceFetchToken:
|
||||
return False
|
||||
|
||||
if not isinstance(familyDefs, list):
|
||||
return False
|
||||
|
||||
families = []
|
||||
for familyDef in familyDefs:
|
||||
if not isinstance(familyDef, dict):
|
||||
continue
|
||||
families.append(
|
||||
speechserver.VoiceFamily({
|
||||
speechserver.VoiceFamily.NAME: familyDef.get("name"),
|
||||
speechserver.VoiceFamily.LANG: familyDef.get("lang"),
|
||||
speechserver.VoiceFamily.DIALECT: familyDef.get("dialect"),
|
||||
speechserver.VoiceFamily.VARIANT: familyDef.get("variant"),
|
||||
})
|
||||
)
|
||||
|
||||
if not families:
|
||||
return False
|
||||
|
||||
self._populateEchoSpeechFamilies(families)
|
||||
return False
|
||||
|
||||
def _setupEchoSpeechFamilies(self):
|
||||
"""Populate echo voice family list for the current echo server."""
|
||||
self._echoVoiceFetchToken += 1
|
||||
requestToken = self._echoVoiceFetchToken
|
||||
useCustomModule = self.get_widget("useCustomEchoSpeechServerCheckButton").get_active()
|
||||
fallbackFamilies = list(self.speechFamiliesChoices or [])
|
||||
self._populateEchoSpeechFamilies(fallbackFamilies)
|
||||
|
||||
if useCustomModule and self.echoSpeechServersChoice:
|
||||
serverInfo = self.echoSpeechServersChoice.getInfo()
|
||||
thread = threading.Thread(
|
||||
target=self._fetchEchoSpeechFamiliesWorker,
|
||||
args=(serverInfo, requestToken),
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
|
||||
def _setEchoSpeechServersChoice(self, serverInfo):
|
||||
"""Set the active echo module based on stored server info."""
|
||||
|
||||
if not self.echoSpeechServersChoices:
|
||||
self.echoSpeechServersChoice = None
|
||||
self._setupEchoSpeechFamilies()
|
||||
return
|
||||
|
||||
if serverInfo and len(serverInfo) >= 2 and serverInfo[1] == 'default':
|
||||
family = self._getEchoSpeechServerFamilyForSave()
|
||||
resolved = self._resolveSpeechDispatcherServerForFamily(family)
|
||||
if resolved is None:
|
||||
speechServer = self._getSpeechServerChoiceForSave()
|
||||
if speechServer is not None:
|
||||
speechInfo = speechServer.getInfo()
|
||||
if speechInfo and len(speechInfo) >= 2 and speechInfo[1] != 'default':
|
||||
resolved = speechServer
|
||||
tokens = [
|
||||
"PREFERENCES DIALOG: Reusing main speech server for echo",
|
||||
speechInfo,
|
||||
]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
if resolved is not None:
|
||||
serverInfo = resolved.getInfo()
|
||||
|
||||
valueSet = False
|
||||
for i, server in enumerate(self.echoSpeechServersChoices):
|
||||
info = server.getInfo()
|
||||
if serverInfo and info == serverInfo:
|
||||
self.get_widget("echoSpeechServers").set_active(i)
|
||||
self.echoSpeechServersChoice = server
|
||||
valueSet = True
|
||||
break
|
||||
|
||||
if not valueSet:
|
||||
self.get_widget("echoSpeechServers").set_active(0)
|
||||
self.echoSpeechServersChoice = self.echoSpeechServersChoices[0]
|
||||
|
||||
self._setupEchoSpeechFamilies()
|
||||
|
||||
def _setupEchoSpeechServers(self):
|
||||
"""Populate available speech-dispatcher modules for echo."""
|
||||
|
||||
combobox = self.get_widget("echoSpeechServers")
|
||||
combobox.set_model(None)
|
||||
self.echoSpeechServersModel.clear()
|
||||
self.echoSpeechServersChoices = []
|
||||
self.echoSpeechServersChoice = None
|
||||
|
||||
if not self.prefsDict.get('enableSpeech', True):
|
||||
combobox.set_model(self.echoSpeechServersModel)
|
||||
self._setupEchoSpeechFamilies()
|
||||
return
|
||||
|
||||
factory = self._getSpeechDispatcherFactory()
|
||||
if factory is None:
|
||||
combobox.set_model(self.echoSpeechServersModel)
|
||||
self._setupEchoSpeechFamilies()
|
||||
return
|
||||
|
||||
try:
|
||||
self.echoSpeechServersChoices = factory.SpeechServer.getSpeechServers()
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_WARNING)
|
||||
self.echoSpeechServersChoices = []
|
||||
|
||||
for i, server in enumerate(self.echoSpeechServersChoices):
|
||||
name = server.getInfo()[0]
|
||||
self.echoSpeechServersModel.append((i, name))
|
||||
|
||||
combobox.set_model(self.echoSpeechServersModel)
|
||||
self._setEchoSpeechServersChoice(self.prefsDict.get("echoSpeechServerInfo"))
|
||||
|
||||
def _initEchoSpeechState(self):
|
||||
"""Initialize echo voice controls and choices."""
|
||||
|
||||
self.echoVoice = acss.ACSS(
|
||||
self.prefsDict.get("echoVoice", settings.echoVoice) or {})
|
||||
|
||||
baseVoice = self.defaultVoice or acss.ACSS(
|
||||
self.prefsDict.get("voices", {}).get(settings.DEFAULT_VOICE, {}))
|
||||
|
||||
rate = self.echoVoice.get(acss.ACSS.RATE, baseVoice.get(acss.ACSS.RATE, 50.0))
|
||||
pitch = self.echoVoice.get(acss.ACSS.AVERAGE_PITCH, baseVoice.get(acss.ACSS.AVERAGE_PITCH, 5.0))
|
||||
volume = self.echoVoice.get(acss.ACSS.GAIN, baseVoice.get(acss.ACSS.GAIN, 10.0))
|
||||
|
||||
self.get_widget("echoRateScale").set_value(rate)
|
||||
self.get_widget("echoPitchScale").set_value(pitch)
|
||||
self.get_widget("echoVolumeScale").set_value(volume)
|
||||
|
||||
self.initializingEchoSpeech = True
|
||||
self._setupEchoSpeechServers()
|
||||
self.initializingEchoSpeech = False
|
||||
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
def _setSpokenTextAttributes(self, view, setAttributes,
|
||||
state, moveToTop=False):
|
||||
"""Given a set of spoken text attributes, update the model used by the
|
||||
@@ -2394,6 +2820,19 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
prefs["enableEchoByWord"])
|
||||
self.get_widget("enableEchoBySentenceCheckButton").set_active( \
|
||||
prefs["enableEchoBySentence"])
|
||||
self.get_widget("useCustomEchoVoiceCheckButton").set_active(
|
||||
prefs.get("useCustomEchoVoice", settings.useCustomEchoVoice))
|
||||
self.get_widget("useCustomEchoSpeechServerCheckButton").set_active(
|
||||
prefs.get("useCustomEchoSpeechServer", settings.useCustomEchoSpeechServer))
|
||||
self.get_widget("useCustomEchoForKeyCheckButton").set_active(
|
||||
prefs.get("useCustomEchoForKey", settings.useCustomEchoForKey))
|
||||
self.get_widget("useCustomEchoForCharacterCheckButton").set_active(
|
||||
prefs.get("useCustomEchoForCharacter", settings.useCustomEchoForCharacter))
|
||||
self.get_widget("useCustomEchoForWordCheckButton").set_active(
|
||||
prefs.get("useCustomEchoForWord", settings.useCustomEchoForWord))
|
||||
self.get_widget("useCustomEchoForSentenceCheckButton").set_active(
|
||||
prefs.get("useCustomEchoForSentence", settings.useCustomEchoForSentence))
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
# Text attributes pane.
|
||||
#
|
||||
@@ -2553,22 +2992,20 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self._updateAIControlsState(enabled)
|
||||
|
||||
def _updateAIControlsState(self, enabled):
|
||||
"""Enable or disable AI controls based on AI enabled state."""
|
||||
self.aiProviderCombo.set_sensitive(enabled)
|
||||
self.aiApiKeyEntry.set_sensitive(enabled)
|
||||
self.aiOllamaModelEntry.set_sensitive(enabled)
|
||||
self.aiOllamaEndpointEntry.set_sensitive(enabled)
|
||||
self.aiConfirmationCheckButton.set_sensitive(enabled)
|
||||
self.aiScreenshotQualityCombo.set_sensitive(enabled)
|
||||
"""Refresh AI controls while keeping configuration fields editable."""
|
||||
_ = enabled # kept for signal/call compatibility
|
||||
# Keep settings editable even when AI assistant is disabled so users
|
||||
# can configure providers/keys before enabling it.
|
||||
self.aiProviderCombo.set_sensitive(True)
|
||||
self.aiConfirmationCheckButton.set_sensitive(True)
|
||||
self.aiScreenshotQualityCombo.set_sensitive(True)
|
||||
try:
|
||||
self.get_widget("aiGetClaudeKeyButton").set_sensitive(enabled)
|
||||
self.get_widget("aiGetClaudeKeyButton").set_sensitive(True)
|
||||
except:
|
||||
pass # Button might not exist in older UI files
|
||||
|
||||
# Update provider-specific controls if AI is enabled
|
||||
if enabled:
|
||||
current_provider = self.prefsDict.get("aiProvider", settings.aiProvider)
|
||||
self._updateProviderControls(current_provider)
|
||||
|
||||
current_provider = self.prefsDict.get("aiProvider", settings.aiProvider)
|
||||
self._updateProviderControls(current_provider)
|
||||
|
||||
def _initIndentationState(self):
|
||||
"""Initialize Indentation widgets with current settings."""
|
||||
@@ -2667,15 +3104,37 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self.ocrCopyToClipboardCheckButton.set_active(copyToClipboard)
|
||||
|
||||
def _initSoundThemeState(self):
|
||||
"""Initialize Sound Theme widgets with current settings."""
|
||||
"""Initialize sound widgets with current settings."""
|
||||
prefs = self.prefsDict
|
||||
|
||||
# Get widget references
|
||||
self.soundSinkCombo = self.get_widget("soundSinkCombo")
|
||||
self.soundThemeCombo = self.get_widget("soundThemeCombo")
|
||||
self.roleSoundPresentationCombo = self.get_widget("roleSoundPresentationCombo")
|
||||
self.soundSinkCombo.set_can_focus(False)
|
||||
self.soundThemeCombo.set_can_focus(False)
|
||||
self.roleSoundPresentationCombo.set_can_focus(False)
|
||||
|
||||
self._soundSinkChoices = [
|
||||
(settings.SOUND_SINK_AUTO, guilabels.SOUND_BACKEND_AUTO),
|
||||
(settings.SOUND_SINK_PIPEWIRE, guilabels.SOUND_BACKEND_PIPEWIRE),
|
||||
(settings.SOUND_SINK_PULSE, guilabels.SOUND_BACKEND_PULSE),
|
||||
(settings.SOUND_SINK_ALSA, guilabels.SOUND_BACKEND_ALSA),
|
||||
]
|
||||
self.soundSinkCombo.remove_all()
|
||||
for _, label in self._soundSinkChoices:
|
||||
self.soundSinkCombo.append_text(label)
|
||||
runtimeSoundSink = cthulhu.cthulhuApp.settingsManager.getSetting("soundSink")
|
||||
currentSink = sound_sink.normalize_sound_sink_choice(
|
||||
prefs.get("soundSink", runtimeSoundSink if runtimeSoundSink is not None else settings.soundSink)
|
||||
)
|
||||
sinkIndex = 0
|
||||
for index, (value, _) in enumerate(self._soundSinkChoices):
|
||||
if value == currentSink:
|
||||
sinkIndex = index
|
||||
break
|
||||
self.soundSinkCombo.set_active(sinkIndex)
|
||||
|
||||
# Populate sound theme combo box
|
||||
themeManager = sound_theme_manager.getManager()
|
||||
availableThemes = themeManager.getAvailableThemes()
|
||||
@@ -2724,6 +3183,14 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
if activeText:
|
||||
self.prefsDict["soundTheme"] = activeText
|
||||
|
||||
def soundSinkComboChanged(self, widget):
|
||||
"""Signal handler for the sound backend combo box."""
|
||||
activeIndex = widget.get_active()
|
||||
if activeIndex < 0:
|
||||
return
|
||||
value = self._soundSinkChoices[activeIndex][0]
|
||||
self.prefsDict["soundSink"] = value
|
||||
|
||||
def roleSoundPresentationComboChanged(self, widget):
|
||||
"""Signal handler for the role sound presentation combo box."""
|
||||
activeIndex = widget.get_active()
|
||||
@@ -2854,6 +3321,68 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
cthulhuSetupWindow.set_title(title)
|
||||
|
||||
cthulhuSetupWindow.show()
|
||||
self._initialFocusSyncAttempts = 0
|
||||
GLib.idle_add(self._set_initial_window_state)
|
||||
GLib.idle_add(self._set_initial_gtk_focus)
|
||||
GLib.timeout_add(50, self._set_initial_window_state)
|
||||
|
||||
def _set_initial_gtk_focus(self):
|
||||
"""Give GTK focus to a real preferences control as soon as the dialog appears."""
|
||||
|
||||
candidateIds = [
|
||||
"generalDesktopButton",
|
||||
"generalLaptopButton",
|
||||
"availableProfilesComboBox1",
|
||||
"speechSupportCheckButton",
|
||||
"notebook",
|
||||
]
|
||||
cthulhuSetupWindow = self.get_widget("cthulhuSetupWindow")
|
||||
for widgetId in candidateIds:
|
||||
widget = self.get_widget(widgetId)
|
||||
if not widget.get_visible() or not widget.get_sensitive():
|
||||
continue
|
||||
if not widget.get_can_focus():
|
||||
continue
|
||||
|
||||
debug.printMessage(
|
||||
debug.LEVEL_INFO,
|
||||
f"PREFERENCES DIALOG: Setting initial GTK focus to {widgetId}",
|
||||
True,
|
||||
)
|
||||
cthulhuSetupWindow.set_focus(widget)
|
||||
widget.grab_focus()
|
||||
return False
|
||||
|
||||
debug.printMessage(
|
||||
debug.LEVEL_INFO,
|
||||
"PREFERENCES DIALOG: No focusable initial GTK widget found",
|
||||
True,
|
||||
)
|
||||
return False
|
||||
|
||||
def _set_initial_window_state(self):
|
||||
"""Sync Cthulhu's active window to preferences without forcing dialog focus."""
|
||||
|
||||
self._initialFocusSyncAttempts += 1
|
||||
activeWindow = AXUtilities.find_active_window()
|
||||
if activeWindow is None:
|
||||
return self._initialFocusSyncAttempts < 5
|
||||
|
||||
app = AXObject.get_application(activeWindow)
|
||||
appName = (AXObject.get_name(app) or "").lower()
|
||||
if appName != "cthulhu":
|
||||
return self._initialFocusSyncAttempts < 5
|
||||
|
||||
debug.printTokens(
|
||||
debug.LEVEL_INFO,
|
||||
["PREFERENCES DIALOG: Syncing active window to", activeWindow],
|
||||
True,
|
||||
)
|
||||
cthulhu.setActiveWindow(activeWindow, notifyScript=False)
|
||||
|
||||
tokens = ["PREFERENCES DIALOG: Synced initial window state to", activeWindow]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
return False
|
||||
|
||||
def _initComboBox(self, combobox):
|
||||
"""Initialize the given combo box to take a list of int/str pairs.
|
||||
@@ -2898,22 +3427,91 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self.get_widget("enableNavigationKeysCheckButton").set_sensitive(enable)
|
||||
self.get_widget("enableDiacriticalKeysCheckButton").set_sensitive( \
|
||||
enable)
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
def _presentMessage(self, text, interrupt=False):
|
||||
def _setEchoVoiceItems(self):
|
||||
"""[In]sensitize echo voice controls based on current state."""
|
||||
|
||||
useCustomVoice = self.get_widget("useCustomEchoVoiceCheckButton").get_active()
|
||||
useCustomModule = self.get_widget("useCustomEchoSpeechServerCheckButton").get_active()
|
||||
keyEchoEnabled = self.get_widget("keyEchoCheckButton").get_active()
|
||||
charEchoEnabled = self.get_widget("enableEchoByCharacterCheckButton").get_active()
|
||||
wordEchoEnabled = self.get_widget("enableEchoByWordCheckButton").get_active()
|
||||
sentenceEchoEnabled = self.get_widget("enableEchoBySentenceCheckButton").get_active()
|
||||
speechEnabled = self.get_widget("speechSupportCheckButton").get_active()
|
||||
|
||||
speechSystemIsDispatcher = False
|
||||
if self.speechSystemsChoice:
|
||||
try:
|
||||
speechSystemIsDispatcher = \
|
||||
self.speechSystemsChoice.SpeechServer.getFactoryName() == guilabels.SPEECH_DISPATCHER
|
||||
except Exception:
|
||||
speechSystemIsDispatcher = False
|
||||
|
||||
voiceControlsEnabled = useCustomVoice and speechEnabled
|
||||
moduleOverrideAvailable = voiceControlsEnabled and speechSystemIsDispatcher
|
||||
moduleControlsEnabled = moduleOverrideAvailable and useCustomModule
|
||||
|
||||
self.get_widget("useCustomEchoSpeechServerCheckButton").set_sensitive(moduleOverrideAvailable)
|
||||
self.get_widget("echoSpeechServers").set_sensitive(moduleControlsEnabled)
|
||||
self.get_widget("echoSpeechFamilies").set_sensitive(voiceControlsEnabled)
|
||||
|
||||
self.get_widget("echoRateScale").set_sensitive(useCustomVoice)
|
||||
self.get_widget("echoPitchScale").set_sensitive(useCustomVoice)
|
||||
self.get_widget("echoVolumeScale").set_sensitive(useCustomVoice)
|
||||
|
||||
self.get_widget("useCustomEchoForKeyCheckButton").set_sensitive(useCustomVoice and keyEchoEnabled)
|
||||
self.get_widget("useCustomEchoForCharacterCheckButton").set_sensitive(
|
||||
useCustomVoice and charEchoEnabled)
|
||||
self.get_widget("useCustomEchoForWordCheckButton").set_sensitive(useCustomVoice and wordEchoEnabled)
|
||||
self.get_widget("useCustomEchoForSentenceCheckButton").set_sensitive(
|
||||
useCustomVoice and sentenceEchoEnabled)
|
||||
|
||||
def _presentMessage(self, text, interrupt=False, voice=None):
|
||||
"""If the text field is not None, presents the given text, optionally
|
||||
interrupting anything currently being spoken.
|
||||
|
||||
Arguments:
|
||||
- text: the text to present
|
||||
- interrupt: if True, interrupt any speech currently being spoken
|
||||
- voice: the voice to use; if None, use the default system voice
|
||||
"""
|
||||
|
||||
self.script.speakMessage(text, interrupt=interrupt)
|
||||
self.script.speakMessage(text, voice=voice, interrupt=interrupt)
|
||||
try:
|
||||
self.script.displayBrailleMessage(text, flashTime=-1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _previewVoiceSelection(self, voiceType, name):
|
||||
"""Present a short preview in the currently selected voice."""
|
||||
|
||||
if not name:
|
||||
return
|
||||
|
||||
voice = self._getACSSForVoiceType(voiceType)
|
||||
if not voice:
|
||||
return
|
||||
|
||||
previewVoice = acss.ACSS(voice)
|
||||
if self.speechSystemsChoice \
|
||||
and self.speechSystemsChoice.SpeechServer.getFactoryName() == guilabels.SPEECH_DISPATCHER:
|
||||
previewMessage = messages.SPEECH_VOICE_VALUE % name
|
||||
else:
|
||||
previewMessage = messages.SPEECH_VOICE_VALUE_GENERIC % name
|
||||
|
||||
# Speak through the currently-selected preferences speech server so
|
||||
# preview uses the chosen module/voice, not the globally-active one.
|
||||
server = self.speechServersChoice
|
||||
if server and hasattr(server, "speak"):
|
||||
try:
|
||||
server.speak(previewMessage, previewVoice, interrupt=True)
|
||||
return
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_WARNING)
|
||||
|
||||
self._presentMessage(previewMessage, interrupt=True, voice=previewVoice)
|
||||
|
||||
def _createNode(self, appName):
|
||||
"""Create a new root node in the TreeStore model with the name of the
|
||||
application.
|
||||
@@ -3194,6 +3792,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
enable = widget.get_active()
|
||||
self.prefsDict["enableSpeech"] = enable
|
||||
self.get_widget("speechOptionsGrid").set_sensitive(enable)
|
||||
self._setupEchoSpeechServers()
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
def onlySpeakDisplayedTextToggled(self, widget):
|
||||
"""Signal handler for the "toggled" signal for the GtkCheckButton
|
||||
@@ -3225,6 +3825,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
selectedIndex = widget.get_active()
|
||||
self.speechSystemsChoice = self.speechSystemsChoices[selectedIndex]
|
||||
self._setupSpeechServers()
|
||||
self._setupEchoSpeechServers()
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
def speechServersChanged(self, widget):
|
||||
"""Signal handler for the "changed" signal for the speechServers
|
||||
@@ -3257,6 +3859,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
pass
|
||||
|
||||
self._setupVoices()
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
def speechLanguagesChanged(self, widget):
|
||||
"""Signal handler for the "value_changed" signal for the languages
|
||||
@@ -3316,6 +3919,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
variant = family[speechserver.VoiceFamily.VARIANT]
|
||||
voiceType = self.get_widget("voiceTypesCombo").get_active()
|
||||
self._setFamilyNameForVoiceType(voiceType, name, language, dialect, variant)
|
||||
if not self._updatingSpeechFamilies:
|
||||
self._previewVoiceSelection(voiceType, name)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
|
||||
@@ -3393,6 +3998,75 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
voices.get(settings.DEFAULT_VOICE, {})[acss.ACSS.GAIN] = volume
|
||||
cthulhu.cthulhuApp.settingsManager.setSetting('voices', voices)
|
||||
|
||||
def useCustomEchoVoiceToggled(self, widget):
|
||||
"""Signal handler for enabling/disabling custom echo voice settings."""
|
||||
|
||||
self.prefsDict["useCustomEchoVoice"] = widget.get_active()
|
||||
self._setEchoVoiceItems()
|
||||
self._setupEchoSpeechFamilies()
|
||||
|
||||
def useCustomEchoSpeechServerToggled(self, widget):
|
||||
"""Signal handler for enabling/disabling custom echo module override."""
|
||||
|
||||
self.prefsDict["useCustomEchoSpeechServer"] = widget.get_active()
|
||||
self._setEchoVoiceItems()
|
||||
self._setupEchoSpeechFamilies()
|
||||
|
||||
def echoSpeechServersChanged(self, widget):
|
||||
"""Signal handler for selecting the echo speech-dispatcher module."""
|
||||
|
||||
if self.initializingEchoSpeech:
|
||||
return
|
||||
|
||||
selectedIndex = widget.get_active()
|
||||
if selectedIndex < 0:
|
||||
self.echoSpeechServersChoice = None
|
||||
self.prefsDict["echoSpeechServerInfo"] = None
|
||||
self._setupEchoSpeechFamilies()
|
||||
return
|
||||
|
||||
self.echoSpeechServersChoice = self.echoSpeechServersChoices[selectedIndex]
|
||||
self.prefsDict["echoSpeechServerInfo"] = self.echoSpeechServersChoice.getInfo()
|
||||
self._setupEchoSpeechFamilies()
|
||||
|
||||
def echoSpeechFamiliesChanged(self, widget):
|
||||
"""Signal handler for selecting the echo voice family."""
|
||||
|
||||
if self.initializingEchoSpeech or self._updatingEchoSpeechFamilies:
|
||||
return
|
||||
|
||||
selectedIndex = widget.get_active()
|
||||
if selectedIndex < 0:
|
||||
self.echoSpeechFamiliesChoice = None
|
||||
return
|
||||
|
||||
self.echoSpeechFamiliesChoice = self.echoSpeechFamiliesChoices[selectedIndex]
|
||||
self._setEchoVoiceFamily(self.echoSpeechFamiliesChoice)
|
||||
|
||||
def echoRateValueChanged(self, widget):
|
||||
"""Signal handler for changing custom echo rate."""
|
||||
|
||||
if self.echoVoice is None:
|
||||
self.echoVoice = acss.ACSS({})
|
||||
self.echoVoice[acss.ACSS.RATE] = widget.get_value()
|
||||
self.echoVoice['established'] = True
|
||||
|
||||
def echoPitchValueChanged(self, widget):
|
||||
"""Signal handler for changing custom echo pitch."""
|
||||
|
||||
if self.echoVoice is None:
|
||||
self.echoVoice = acss.ACSS({})
|
||||
self.echoVoice[acss.ACSS.AVERAGE_PITCH] = widget.get_value()
|
||||
self.echoVoice['established'] = True
|
||||
|
||||
def echoVolumeValueChanged(self, widget):
|
||||
"""Signal handler for changing custom echo volume."""
|
||||
|
||||
if self.echoVoice is None:
|
||||
self.echoVoice = acss.ACSS({})
|
||||
self.echoVoice[acss.ACSS.GAIN] = widget.get_value()
|
||||
self.echoVoice['established'] = True
|
||||
|
||||
def checkButtonToggled(self, widget):
|
||||
"""Signal handler for "toggled" signal for basic GtkCheckButton
|
||||
widgets. The user has altered the state of the checkbox.
|
||||
@@ -3410,6 +4084,15 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
# strip "CheckButton" from the end.
|
||||
settingName = settingName[:-11]
|
||||
self.prefsDict[settingName] = widget.get_active()
|
||||
if settingName in [
|
||||
"enableEchoByCharacter",
|
||||
"enableEchoByWord",
|
||||
"enableEchoBySentence",
|
||||
"useCustomEchoForKey",
|
||||
"useCustomEchoForCharacter",
|
||||
"useCustomEchoForWord",
|
||||
"useCustomEchoForSentence"]:
|
||||
self._setEchoVoiceItems()
|
||||
|
||||
def keyEchoChecked(self, widget):
|
||||
"""Signal handler for the "toggled" signal for the
|
||||
@@ -4193,6 +4876,11 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
if not self._isInitialSetup:
|
||||
self.restoreSettings()
|
||||
|
||||
if self.soundSinkCombo is not None:
|
||||
activeIndex = self.soundSinkCombo.get_active()
|
||||
if activeIndex >= 0:
|
||||
self.prefsDict["soundSink"] = self._soundSinkChoices[activeIndex][0]
|
||||
|
||||
enable = self.get_widget("speechSupportCheckButton").get_active()
|
||||
self.prefsDict["enableSpeech"] = enable
|
||||
|
||||
@@ -4200,9 +4888,24 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self.prefsDict["speechServerFactory"] = \
|
||||
self.speechSystemsChoice.__name__
|
||||
|
||||
if self.speechServersChoice:
|
||||
speechServerChoice = self._getSpeechServerChoiceForSave()
|
||||
if speechServerChoice:
|
||||
self.prefsDict["speechServerInfo"] = \
|
||||
self.speechServersChoice.getInfo()
|
||||
speechServerChoice.getInfo()
|
||||
else:
|
||||
activeSpeechInfo = speech.getInfo()
|
||||
if activeSpeechInfo:
|
||||
self.prefsDict["speechServerInfo"] = activeSpeechInfo
|
||||
|
||||
runtimeSpeechServerInfo = cthulhu.cthulhuApp.settingsManager.getSetting(
|
||||
"speechServerInfo"
|
||||
)
|
||||
if runtimeSpeechServerInfo and not self.prefsDict.get("speechServerInfo"):
|
||||
self.prefsDict["speechServerInfo"] = runtimeSpeechServerInfo
|
||||
|
||||
existingSpeechServerInfo = self.prefsDict.get("speechServerInfo")
|
||||
if existingSpeechServerInfo:
|
||||
self.prefsDict["speechServerInfo"] = existingSpeechServerInfo
|
||||
|
||||
if self.defaultVoice is not None:
|
||||
self.prefsDict["voices"] = {
|
||||
@@ -4212,6 +4915,36 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
settings.SYSTEM_VOICE: acss.ACSS(self.systemVoice),
|
||||
}
|
||||
|
||||
self.prefsDict["useCustomEchoVoice"] = \
|
||||
self.get_widget("useCustomEchoVoiceCheckButton").get_active()
|
||||
self.prefsDict["useCustomEchoSpeechServer"] = \
|
||||
self.get_widget("useCustomEchoSpeechServerCheckButton").get_active()
|
||||
self.prefsDict["useCustomEchoForKey"] = \
|
||||
self.get_widget("useCustomEchoForKeyCheckButton").get_active()
|
||||
self.prefsDict["useCustomEchoForCharacter"] = \
|
||||
self.get_widget("useCustomEchoForCharacterCheckButton").get_active()
|
||||
self.prefsDict["useCustomEchoForWord"] = \
|
||||
self.get_widget("useCustomEchoForWordCheckButton").get_active()
|
||||
self.prefsDict["useCustomEchoForSentence"] = \
|
||||
self.get_widget("useCustomEchoForSentenceCheckButton").get_active()
|
||||
|
||||
if self.echoVoice is None:
|
||||
self.echoVoice = acss.ACSS({})
|
||||
|
||||
# Persist slider values directly so saving does not depend on
|
||||
# value-changed signal timing.
|
||||
self.echoVoice[acss.ACSS.RATE] = self.get_widget("echoRateScale").get_value()
|
||||
self.echoVoice[acss.ACSS.AVERAGE_PITCH] = self.get_widget("echoPitchScale").get_value()
|
||||
self.echoVoice[acss.ACSS.GAIN] = self.get_widget("echoVolumeScale").get_value()
|
||||
self.echoVoice['established'] = True
|
||||
self.prefsDict["echoVoice"] = acss.ACSS(self.echoVoice)
|
||||
|
||||
echoSpeechServerChoice = self._getEchoSpeechServerChoiceForSave()
|
||||
if echoSpeechServerChoice:
|
||||
self.prefsDict["echoSpeechServerInfo"] = echoSpeechServerChoice.getInfo()
|
||||
else:
|
||||
self.prefsDict["echoSpeechServerInfo"] = None
|
||||
|
||||
def applyButtonClicked(self, widget):
|
||||
"""Signal handler for the "clicked" signal for the applyButton
|
||||
GtkButton widget. The user has clicked the Apply button.
|
||||
@@ -4244,6 +4977,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
self._refresh_dynamic_plugin_tabs()
|
||||
braille.checkBrailleSetting()
|
||||
self._initSpeechState()
|
||||
self._initEchoSpeechState()
|
||||
self._populateKeyBindings()
|
||||
self.__initProfileCombo()
|
||||
|
||||
@@ -4506,6 +5240,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
||||
braille.checkBrailleSetting()
|
||||
|
||||
self._initSpeechState()
|
||||
self._initEchoSpeechState()
|
||||
|
||||
self._populateKeyBindings()
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ __license__ = "LGPL"
|
||||
#
|
||||
locusOfFocus: Optional[Any] = None # Actually: Optional[Atspi.Accessible]
|
||||
|
||||
# A pending focused object from a Cthulhu-owned window that should be used
|
||||
# for keyboard-event context until the queued focus event is processed.
|
||||
#
|
||||
pendingSelfHostedFocus: Optional[Any] = None # Actually: Optional[Atspi.Accessible]
|
||||
|
||||
# The currently active window.
|
||||
#
|
||||
activeWindow: Optional[Any] = None # Actually: Optional[Atspi.Accessible]
|
||||
|
||||
@@ -70,7 +70,9 @@ class EventManager:
|
||||
self._dequeueCount: int = 0
|
||||
self._cmdlineCache: Dict[int, str] = {}
|
||||
self._eventQueue: queue.Queue[Any] = queue.Queue(0)
|
||||
self._prioritizedEvent: Optional[Atspi.Event] = None
|
||||
self._gidleId: int = 0
|
||||
self._prioritizedIdleId: int = 0
|
||||
self._gidleLock: threading.Lock = threading.Lock()
|
||||
self._gilSleepTime: float = 0.00001
|
||||
self._synchronousToolkits: List[str] = ['VCL']
|
||||
@@ -284,6 +286,12 @@ class EventManager:
|
||||
if self._isDuplicateEvent(event):
|
||||
return _ignore_with_reason("duplicate", "duplicate event")
|
||||
|
||||
if self._isSelfHostedFocusClearedEvent(event):
|
||||
return _ignore_with_reason("self-hosted-focus-cleared", "self-hosted focused=false event")
|
||||
|
||||
if self._isRedundantSelfHostedPropertyEvent(event):
|
||||
return _ignore_with_reason("self-hosted-redundant-property", "self-hosted redundant property event")
|
||||
|
||||
# Thunderbird spams us with these when a message list thread is expanded or collapsed.
|
||||
if event.type.endswith('system') \
|
||||
and AXObject.get_name(app).lower().startswith('thunderbird'):
|
||||
@@ -596,6 +604,11 @@ class EventManager:
|
||||
self._enqueueCount -= 1
|
||||
return
|
||||
|
||||
if isObjectEvent and self._prioritizeSelfHostedFocusedEvent(e):
|
||||
if debug.debugEventQueue:
|
||||
self._enqueueCount -= 1
|
||||
return
|
||||
|
||||
self._queuePrintln(e)
|
||||
|
||||
if self._inFlood() and self._prioritizeDuringFlood(e):
|
||||
@@ -628,6 +641,73 @@ class EventManager:
|
||||
if debug.debugEventQueue:
|
||||
self._enqueueCount -= 1
|
||||
|
||||
def _isSelfHostedFocusedEvent(self, event: Atspi.Event) -> bool:
|
||||
"""Returns True if the event is a newly-focused event from Cthulhu."""
|
||||
|
||||
if not event.type.startswith("object:state-changed:focused") or not event.detail1:
|
||||
return False
|
||||
|
||||
app = AXObject.get_application(event.source)
|
||||
appName = (AXObject.get_name(app) or "").lower()
|
||||
return appName == "cthulhu"
|
||||
|
||||
def _isSelfHostedFocusClearedEvent(self, event: Atspi.Event) -> bool:
|
||||
"""Returns True if the event is a focus-lost event from Cthulhu."""
|
||||
|
||||
if not event.type.startswith("object:state-changed:focused") or event.detail1:
|
||||
return False
|
||||
|
||||
app = AXObject.get_application(event.source)
|
||||
appName = (AXObject.get_name(app) or "").lower()
|
||||
return appName == "cthulhu"
|
||||
|
||||
def _isRedundantSelfHostedPropertyEvent(self, event: Atspi.Event) -> bool:
|
||||
"""Returns True if the event is redundant prefs startup noise from Cthulhu."""
|
||||
|
||||
app = AXObject.get_application(event.source)
|
||||
appName = (AXObject.get_name(app) or "").lower()
|
||||
if appName != "cthulhu":
|
||||
return False
|
||||
|
||||
if event.type.startswith("object:property-change:accessible-name"):
|
||||
return AXUtilities.is_combo_box(event.source) or AXUtilities.is_table_cell(event.source)
|
||||
|
||||
if event.type.startswith("object:property-change:accessible-value"):
|
||||
return AXObject.get_role(event.source) == Atspi.Role.SLIDER \
|
||||
and event.source != cthulhu_state.locusOfFocus
|
||||
|
||||
return False
|
||||
|
||||
def _prioritizeSelfHostedFocusedEvent(self, event: Atspi.Event) -> bool:
|
||||
"""Schedules the latest focused-child event from Cthulhu ahead of the normal queue."""
|
||||
|
||||
if not self._isSelfHostedFocusedEvent(event):
|
||||
return False
|
||||
|
||||
tokens = ["EVENT MANAGER: Prioritizing self-hosted focused event for", event.source]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
script = self._get_scriptForEvent(event)
|
||||
if script is not None:
|
||||
script.eventCache[event.type] = (event, time.time())
|
||||
|
||||
self._gidleLock.acquire()
|
||||
try:
|
||||
replaced = self._prioritizedEvent is not None
|
||||
self._prioritizedEvent = event
|
||||
cthulhu_state.pendingSelfHostedFocus = event.source
|
||||
if not self._prioritizedIdleId:
|
||||
self._prioritizedIdleId = GLib.idle_add(
|
||||
self._dequeuePrioritizedEvent,
|
||||
priority=GLib.PRIORITY_HIGH_IDLE,
|
||||
)
|
||||
finally:
|
||||
self._gidleLock.release()
|
||||
|
||||
msg = f"EVENT MANAGER: Prioritized self-hosted focused event. Replaced pending event: {replaced}"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
return True
|
||||
def _isNoFocus(self) -> bool:
|
||||
if cthulhu_state.locusOfFocus or cthulhu_state.activeWindow or cthulhu_state.activeScript:
|
||||
return False
|
||||
@@ -645,6 +725,60 @@ class EventManager:
|
||||
defaultScript.idleMessage()
|
||||
return False
|
||||
|
||||
def _dequeuePrioritizedEvent(self) -> bool:
|
||||
"""Handles prioritized focused events from Cthulhu-owned windows."""
|
||||
|
||||
self._gidleLock.acquire()
|
||||
try:
|
||||
event = self._prioritizedEvent
|
||||
self._prioritizedEvent = None
|
||||
self._prioritizedIdleId = 0
|
||||
if event is not None and cthulhu_state.pendingSelfHostedFocus == event.source:
|
||||
cthulhu_state.pendingSelfHostedFocus = None
|
||||
finally:
|
||||
self._gidleLock.release()
|
||||
|
||||
if event is None:
|
||||
return False
|
||||
|
||||
tokens = ["EVENT MANAGER: Dequeued prioritized event", event]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
debugging = not debug.eventDebugFilter or debug.eventDebugFilter.match(event.type)
|
||||
if debugging:
|
||||
startTime = time.time()
|
||||
msg = (
|
||||
f"\nvvvvv PROCESS OBJECT EVENT {event.type} "
|
||||
f"(prioritized) vvvvv"
|
||||
)
|
||||
debug.printMessage(debug.eventDebugLevel, msg, False)
|
||||
|
||||
try:
|
||||
self._processObjectEvent(event)
|
||||
if self._didSuspendEventsFor(event):
|
||||
self._unsuspendEvents(event)
|
||||
elif self._eventsSuspended and self._shouldUnsuspendEventsFor(event):
|
||||
self._unsuspendEvents(event, force=True)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
|
||||
if debugging:
|
||||
msg = (
|
||||
f"TOTAL PROCESSING TIME: {time.time() - startTime:.4f}"
|
||||
f"\n^^^^^ PROCESS OBJECT EVENT {event.type} ^^^^^\n"
|
||||
)
|
||||
debug.printMessage(debug.eventDebugLevel, msg, False)
|
||||
|
||||
self._gidleLock.acquire()
|
||||
try:
|
||||
hasMore = self._prioritizedEvent is not None
|
||||
if not hasMore and self._eventQueue.qsize() and not self._gidleId:
|
||||
self._gidleId = GLib.idle_add(self._dequeue)
|
||||
finally:
|
||||
self._gidleLock.release()
|
||||
|
||||
return hasMore
|
||||
|
||||
def _dequeue(self) -> bool:
|
||||
"""Handles all events destined for scripts. Called by the GTK
|
||||
idle thread."""
|
||||
@@ -657,7 +791,13 @@ class EventManager:
|
||||
self._dequeueCount += 1
|
||||
|
||||
try:
|
||||
event = self._eventQueue.get_nowait()
|
||||
fromPriority = False
|
||||
self._gidleLock.acquire()
|
||||
try:
|
||||
event = self._eventQueue.get_nowait()
|
||||
finally:
|
||||
self._gidleLock.release()
|
||||
|
||||
self._queuePrintln(event, isEnqueue=False)
|
||||
inputEvents = (input_event.KeyboardEvent, input_event.BrailleEvent)
|
||||
if isinstance(event, inputEvents):
|
||||
|
||||
@@ -116,6 +116,13 @@ class FocusManager:
|
||||
_log_tokens(["Focused object in", self._window, "is", result])
|
||||
return result
|
||||
|
||||
def active_window_is_cthulhu(self) -> bool:
|
||||
"""Returns True if the active window belongs to Cthulhu itself."""
|
||||
|
||||
app = AXObject.get_application(self._window)
|
||||
appName = (AXObject.get_name(app) or "").lower()
|
||||
return appName == "cthulhu"
|
||||
|
||||
def focus_and_window_are_unknown(self) -> bool:
|
||||
"""Returns True if we have no knowledge about what is focused."""
|
||||
|
||||
@@ -350,18 +357,39 @@ class FocusManager:
|
||||
self._window = frame
|
||||
cthulhu_state.activeWindow = frame
|
||||
|
||||
contextObject = self._focus
|
||||
if set_window_as_focus:
|
||||
self.set_locus_of_focus(None, self._window, notify_script)
|
||||
elif not (self.focus_is_active_window() or self.focus_is_in_active_window()):
|
||||
_log_tokens(["Focus", self._focus, "is not in", self._window], stack=True)
|
||||
|
||||
# Don't update the focus to the active window if we can't get to the active window
|
||||
# from the focused object. https://bugreports.qt.io/browse/QTBUG-130116
|
||||
if not AXObject.has_broken_ancestry(self._focus):
|
||||
self.set_locus_of_focus(None, self._window, notify_script=True)
|
||||
if self.active_window_is_cthulhu():
|
||||
_log_tokens(
|
||||
["Skipping focused-object lookup and script activation for self-hosted window", self._window],
|
||||
"self-window",
|
||||
)
|
||||
return
|
||||
else:
|
||||
focusedObject = self.find_focused_object()
|
||||
if focusedObject is not None and focusedObject != self._window:
|
||||
_log_tokens(["Using focused object", focusedObject, "from active window", self._window])
|
||||
self.set_locus_of_focus(None, focusedObject, notify_script=True)
|
||||
contextObject = focusedObject
|
||||
elif self._focus is None:
|
||||
_log_tokens(["No previous focus. Falling back to active window", self._window])
|
||||
self.set_locus_of_focus(None, self._window, notify_script=True)
|
||||
contextObject = self._window
|
||||
# Don't update the focus to the active window if we can't get to the active window
|
||||
# from the focused object. https://bugreports.qt.io/browse/QTBUG-130116
|
||||
elif not AXObject.has_broken_ancestry(self._focus):
|
||||
self.set_locus_of_focus(None, self._window, notify_script=True)
|
||||
contextObject = self._window
|
||||
|
||||
app = _get_ax_utilities().get_application(self._focus)
|
||||
self.app.scriptManager.activate_script_for_context(app, self._focus, "focus: active-window")
|
||||
if contextObject is None:
|
||||
contextObject = self._window
|
||||
|
||||
app = _get_ax_utilities().get_application(contextObject)
|
||||
self.app.scriptManager.activate_script_for_context(app, contextObject, "focus: active-window")
|
||||
|
||||
@dbus_service.command
|
||||
def toggle_presentation_mode(
|
||||
|
||||
@@ -938,9 +938,26 @@ USE_STRUCTURAL_NAVIGATION = _("Enable _structural navigation")
|
||||
# audio files that Cthulhu plays for various events.
|
||||
SOUND_THEME = _("Sound _theme:")
|
||||
|
||||
# Translators: This is the label for a combo box in the preferences dialog
|
||||
# where users can choose which audio backend Cthulhu should use.
|
||||
SOUND_BACKEND = _("Audio _backend:")
|
||||
|
||||
# Translators: This is a sound backend option which lets Cthulhu choose a
|
||||
# backend automatically.
|
||||
SOUND_BACKEND_AUTO = _("Automatic")
|
||||
|
||||
# Translators: This is a sound backend option which forces PipeWire.
|
||||
SOUND_BACKEND_PIPEWIRE = _("PipeWire")
|
||||
|
||||
# Translators: This is a sound backend option which forces PulseAudio.
|
||||
SOUND_BACKEND_PULSE = _("PulseAudio")
|
||||
|
||||
# Translators: This is a sound backend option which forces ALSA.
|
||||
SOUND_BACKEND_ALSA = _("ALSA")
|
||||
|
||||
# Translators: This is the title of a frame in the preferences dialog
|
||||
# containing sound theme options.
|
||||
SOUND_THEME_TITLE = _("Sound Theme")
|
||||
# containing sound options.
|
||||
SOUND_THEME_TITLE = _("Sound")
|
||||
|
||||
# Translators: This refers to the amount of information Cthulhu provides about a
|
||||
# particular object that receives focus.
|
||||
|
||||
@@ -870,6 +870,8 @@ class KeyboardEvent(InputEvent):
|
||||
return True, 'Cthulhu modifier'
|
||||
|
||||
if not self._handler:
|
||||
if scriptConsumes:
|
||||
return True, 'Script consumed without handler'
|
||||
return False, 'No handler'
|
||||
|
||||
return scriptConsumes, 'Script indication'
|
||||
@@ -1044,7 +1046,7 @@ class KeyboardEvent(InputEvent):
|
||||
return False, 'Should not consume'
|
||||
|
||||
if not (self._consumer or self._handler):
|
||||
return False, 'No consumer or handler'
|
||||
return True, 'Consumed during shouldConsume'
|
||||
|
||||
if self._consumer or self._handler.function:
|
||||
GLib.timeout_add(1, self._consume)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user