25 Commits
Author SHA1 Message Date
Storm Dragon c4890f31af Fix 2026.03.02 release versioning 2026-03-02 17:56:45 -05:00
Storm Dragon c44245189a Merge branch 'testing' 2026-03-02 15:42:15 -05:00
Storm Dragon c7b8e4a30d Clean stale autostart source references from translations 2026-03-02 15:42:04 -05:00
Storm Dragon 20fe1a7e30 Remove autogenerated autostart desktop template 2026-03-02 15:38:29 -05:00
Storm Dragon 25931758f7 Merge branch 'testing'
# Conflicts:
#	distro-packages/Arch-Linux/PKGBUILD
#	meson.build
#	src/cthulhu/cthulhuVersion.py
2026-03-02 15:37:50 -05:00
Storm Dragon daf57ef22c Removed the autogenerated autostart desktop file. Moved the desktop file to contrib so it's still available if people want it. 2026-02-25 23:20:05 -05:00
Storm Dragon 1d2d727fa2 Requested feature added. It is now possible to customize speech used for echo settings including speech module, voice, rate, volume, pitch. 2026-02-22 01:44:19 -05:00
Storm Dragon 11bd7107d2 Attempt to stop plugins from spontaneously re-enabling themselves. 2026-02-19 02:40:46 -05:00
Storm Dragon 95d33047fa Fix prefs state churn and sound-only role speech
Some settings were being reset when tab changed and a key was pressed, e.g. control.

Radio buttons were still announced even with sound only set.

Controls for AI assistant tab were vanishing after being set.
2026-02-18 07:03:12 -05:00
Storm Dragon 523b896053 Couple minor bug fixes, new speech code added in prefs. 2026-02-18 06:10:57 -05:00
Storm Dragon 9152455227 Speech in settings now uses the voice when switched to it. 2026-02-18 06:07:55 -05:00
Storm Dragon c0fdaca4d0 Merge remote-tracking branch 'origin/web-and-punctuation-fixes' into testing 2026-02-17 14:50:01 -05:00
destructatron 8312a842c1 Fix duplicate role name when aria-roledescription is present
When an element has aria-roledescription (e.g. Discord messages use
'Message'), _generateRoleName was adding both the roledescription AND
the standard localized role name (e.g. 'article'). This resulted in
the role being announced twice.

The aria-roledescription attribute is intended to replace the standard
role name, not supplement it. Return early after adding the
roledescription to match Orca's behavior.
2026-02-17 19:21:36 +00:00
destructatron 07138197cb Fix web content editable label, line boundary, and punctuation bugs
Bug 1: Content editable entry label not announced on caret-nav focus
- When navigating into a content editable via caret navigation (e.g. down
  arrow from message list to message entry in Discord), the label and role
  were suppressed because the code treated entering the field the same as
  navigating within it.
- Fixed locus_of_focus_changed in web/script.py to detect when focus enters
  a content editable from outside and generate full object speech.
- Fixed _generateLabelOrName in web/speech_generator.py to preserve the
  label when the prior object was outside the content editable.

Bug 2: Adjacent button included in line contents on first line
- When reading line contents inside a content editable, the layout-mode
  expansion could walk outside the content editable boundary and include
  adjacent UI elements (e.g. a 'More options' button) that happened to
  share the same visual line.
- Fixed _getLineContentsAtOffset in web/script_utilities.py to stop
  expanding at content editable boundaries in both directions.

Bug 3: Punctuation stripped from live regions and AT-SPI announcements
- presentMessage uses resetStyles=True by default, which sets punctuation
  to NONE for system voice messages. This is correct for generated text
  but wrong for user content in live regions and AT-SPI announcements.
- Fixed liveregions.py pumpMessages and default.py onAnnouncement to pass
  resetStyles=False, preserving the user's punctuation settings.
2026-02-17 19:07:41 +00:00
Storm Dragon ed78ffc248 Visual speech monitor added to speech history plugin. Toggle with cthulhu+shift+D 2026-02-17 08:40:51 -05:00
Storm Dragon 4add36f5ca latest changed merged, seems to be reasonably stable. 2026-02-17 08:01:47 -05:00
Storm Dragon 40e63150a6 Read a list of applications that should always be started in sleep mode from sleep.toml. 2026-02-17 07:57:14 -05:00
Storm Dragon 4dba0ec0cd Fixed a bug with sleep mode, was not suspending review keys for sleeping applications. 2026-02-16 14:55:56 -05:00
Storm Dragon e6f780c38b New code tested and seems pretty stable so merged and bumped version. 2026-02-15 12:10:36 -05:00
Storm Dragon 0f7f73a6a0 Fixed flat review bug affecting some sites in chrome. 2026-02-13 13:23:14 -05:00
Storm Dragon aa71d02036 Merge remote-tracking branch 'origin/keyboard-monitoring-api' into testing 2026-02-13 11:19:40 -05:00
destructatron 51ef3de672 Port keyboard monitoring API from Orca, and fix flat review bug in GTK apps 2026-02-13 10:14:04 +00:00
Storm Dragon c6049ef5f3 Preparing for release. 2026-01-26 13:07:33 -05:00
Storm Dragon 78ef51d01f Latest code, hopefully stable. 2026-01-19 05:24:48 -05:00
Storm Dragon 30a40f6974 Merged dbus implementation for plugins. 2026-01-12 11:54:42 -05:00
113 changed files with 1860 additions and 611 deletions
-8
View File
@@ -8,13 +8,11 @@ set -e # Exit on error
# Colors for output (only if stdout is a terminal) # Colors for output (only if stdout is a terminal)
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
else else
RED=''
GREEN='' GREEN=''
YELLOW='' YELLOW=''
BLUE='' BLUE=''
@@ -56,12 +54,6 @@ if [[ $REPLY =~ ^[Yy]$ ]]; then
# Remove data files # 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 # Remove icons
for size in 16x16 22x22 24x24 32x32 48x48 256x256 scalable symbolic; do for size in 16x16 22x22 24x24 32x32 48x48 256x256 scalable symbolic; do
icon_path="$HOME/.local/share/icons/hicolor/$size/apps" icon_path="$HOME/.local/share/icons/hicolor/$size/apps"
-11
View File
@@ -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;
+2 -7
View File
@@ -1,7 +1,7 @@
# Maintainer: Storm Dragon <storm_dragon@stormux.org> # Maintainer: Storm Dragon <storm_dragon@stormux.org>
pkgname=cthulhu pkgname=cthulhu
pkgver=2026.01.19 pkgver=2026.03.02
pkgrel=1 pkgrel=1
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca" pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
url="https://git.stormux.org/storm/cthulhu" url="https://git.stormux.org/storm/cthulhu"
@@ -84,7 +84,7 @@ makedepends=(
) )
install=cthulhu.install install=cthulhu.install
source=( source=(
"git+https://git.stormux.org/storm/cthulhu.git" "git+https://git.stormux.org/storm/cthulhu.git#tag=${pkgver}"
"cthulhu.install" "cthulhu.install"
) )
b2sums=( b2sums=(
@@ -92,11 +92,6 @@ b2sums=(
'SKIP' 'SKIP'
) )
pkgver() {
cd cthulhu
grep "^version = " src/cthulhu/cthulhuVersion.py | sed 's/version = "\(.*\)"/\1/'
}
build() { build() {
cd cthulhu cd cthulhu
arch-meson _build arch-meson _build
+1 -11
View File
@@ -1,5 +1,5 @@
project('cthulhu', project('cthulhu',
version: '2026.01.19-testing', version: '2026.03.02-master',
meson_version: '>= 1.0.0', meson_version: '>= 1.0.0',
) )
@@ -93,16 +93,6 @@ else
summary += {'sound support': 'no (missing gstreamer)'} summary += {'sound support': 'no (missing gstreamer)'}
endif 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 # Update icon cache manually (desktop-neutral) - optional, ignore failures
gtk_update_icon_cache = find_program('gtk4-update-icon-cache', required: false) gtk_update_icon_cache = find_program('gtk4-update-icon-cache', required: false)
if gtk_update_icon_cache.found() if gtk_update_icon_cache.found()
-1
View File
@@ -1,6 +1,5 @@
# List of source files containing translatable strings. # List of source files containing translatable strings.
# Please keep this file sorted alphabetically. # Please keep this file sorted alphabetically.
cthulhu-autostart.desktop.in
src/cthulhu/braille_rolenames.py src/cthulhu/braille_rolenames.py
src/cthulhu/brltablenames.py src/cthulhu/brltablenames.py
src/cthulhu/chnames.py src/cthulhu/chnames.py
-2
View File
@@ -13,11 +13,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-DamnedLies-Scope: partial\n" "X-DamnedLies-Scope: partial\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "" msgstr ""
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.6.10\n" "X-Generator: Poedit 1.6.10\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lector de pantalla Cthulhu" msgstr "Lector de pantalla Cthulhu"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "lector de pantalla; fabla; braille;" msgstr "lector de pantalla; fabla; braille;"
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" "&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
"X-Poedit-Language: Arabic\n" "X-Poedit-Language: Arabic\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "قارئ الشاشة Cthulhu" msgstr "قارئ الشاشة Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "شاشة;قارئ;إمكانية الوصول;كلام;برايل;" msgstr "شاشة;قارئ;إمكانية الوصول;كلام;برايل;"
-2
View File
@@ -19,11 +19,9 @@ msgstr ""
"X-Poedit-Language: Asturian\n" "X-Poedit-Language: Asturian\n"
"X-Poedit-SourceCharset: utf-8\n" "X-Poedit-SourceCharset: utf-8\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Llector de pantalla d'Cthulhu" msgstr "Llector de pantalla d'Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -17,11 +17,9 @@ msgstr ""
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Чытач з экрана Cthulhu" msgstr "Чытач з экрана Cthulhu"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "чытач з экрана;экран;голас;маўленне;Брайль;" msgstr "чытач з экрана;экран;голас;маўленне;Брайль;"
-2
View File
@@ -23,11 +23,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu — екранен четец" msgstr "Cthulhu — екранен четец"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "екран;четец;достъпност;говор;брайл;" msgstr "екран;четец;достъпност;говор;брайл;"
-2
View File
@@ -21,12 +21,10 @@ msgstr ""
"X-Generator: KBabel 1.9.1\n" "X-Generator: KBabel 1.9.1\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "অর্কা স্ক্রীন রিডার / বিবর্ধন" msgstr "অর্কা স্ক্রীন রিডার / বিবর্ধন"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,12 +18,10 @@ msgstr ""
"X-Generator: KBabel 1.9.1\n" "X-Generator: KBabel 1.9.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu'র বৈশিষ্ট্য" msgstr "Cthulhu'র বৈশিষ্ট্য"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -15,11 +15,9 @@ msgstr ""
"X-Launchpad-Export-Date: 2015-02-05 06:46+0000\n" "X-Launchpad-Export-Date: 2015-02-05 06:46+0000\n"
"X-Generator: Poedit 1.7.4\n" "X-Generator: Poedit 1.7.4\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu čitač ekrana" msgstr "Cthulhu čitač ekrana"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "čitač ekrana;govor;brailleovo pismo;" msgstr "čitač ekrana;govor;brailleovo pismo;"
-2
View File
@@ -27,11 +27,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lector de pantalla Cthulhu" msgstr "Lector de pantalla Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "pantalla;lector;accessibilitat;veu;braille;" msgstr "pantalla;lector;accessibilitat;veu;braille;"
-2
View File
@@ -27,11 +27,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 1.8.11\n" "X-Generator: Poedit 1.8.11\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lector de pantalla Cthulhu" msgstr "Lector de pantalla Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "lector de pantalla;pronunciació;braille;" msgstr "lector de pantalla;pronunciació;braille;"
-2
View File
@@ -19,12 +19,10 @@ msgstr ""
"X-Launchpad-Export-Date: 2020-05-04 21:32+0000\n" "X-Launchpad-Export-Date: 2020-05-04 21:32+0000\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "خوێنەری پەردەی ئۆرکا" msgstr "خوێنەری پەردەی ئۆرکا"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Čtečka obrazovky Cthulhu" msgstr "Čtečka obrazovky Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "obrazovka;čtečka;přístupnost;řeč;braille;" msgstr "obrazovka;čtečka;přístupnost;řeč;braille;"
-2
View File
@@ -17,12 +17,10 @@ msgstr ""
"Plural-Forms: nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != " "Plural-Forms: nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != "
"11) ? 2 : 3;\n" "11) ? 2 : 3;\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Hoffterau Cthulhu" msgstr "Hoffterau Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -32,11 +32,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n" "X-Generator: Poedit 3.0.1\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Skærmlæseren Cthulhu" msgstr "Skærmlæseren Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "skærm;læser;tilgængelighed;tale;braille;" msgstr "skærm;læser;tilgængelighed;tale;braille;"
-2
View File
@@ -37,11 +37,9 @@ msgstr ""
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu-Bildschirmleser" msgstr "Cthulhu-Bildschirmleser"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "bildschirm;leser;barrierefreiheit;sprache;braille;" msgstr "bildschirm;leser;barrierefreiheit;sprache;braille;"
-2
View File
@@ -20,12 +20,10 @@ msgstr ""
"X-Poedit-SourceCharset: utf-8\n" "X-Poedit-SourceCharset: utf-8\n"
"Plural-Forms: nplurals=2; plural=(n!=1)\n" "Plural-Forms: nplurals=2; plural=(n!=1)\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "ཨོར་ཀ་གསལ་གཞི་ལྷག་བྱེད་/ཆེ་ཤེལ་" msgstr "ཨོར་ཀ་གསལ་གཞི་ལྷག་བྱེད་/ཆེ་ཤེལ་"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -28,11 +28,9 @@ msgstr ""
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Αναγνώστης οθόνης Cthulhu" msgstr "Αναγνώστης οθόνης Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "οθόνη;αναγνώστης;προσβασιμότητα;ομιλία;μπράιγ;" msgstr "οθόνη;αναγνώστης;προσβασιμότητα;ομιλία;μπράιγ;"
-2
View File
@@ -17,11 +17,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "" msgstr ""
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -21,11 +21,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Gtranslator 45.3\n" "X-Generator: Gtranslator 45.3\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu Screen Reader" msgstr "Cthulhu Screen Reader"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "screen reader;speech;braille;" msgstr "screen reader;speech;braille;"
-2
View File
@@ -21,11 +21,9 @@ msgstr ""
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 3.2.2\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Ekranlegilo Orko" msgstr "Ekranlegilo Orko"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "ekranlegilo;parolo;brajlo;" msgstr "ekranlegilo;parolo;brajlo;"
-2
View File
@@ -28,11 +28,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Gtranslator 45.alpha0\n" "X-Generator: Gtranslator 45.alpha0\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lector de pantalla Cthulhu" msgstr "Lector de pantalla Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "lector;pantalla;accesibilidad;voz;braille;" msgstr "lector;pantalla;accesibilidad;voz;braille;"
-2
View File
@@ -20,12 +20,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu ekraanilugeja ja luup" msgstr "Cthulhu ekraanilugeja ja luup"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu pantaila-irakurlea" msgstr "Cthulhu pantaila-irakurlea"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "pantaila;irakurlea;irisgarritasuna;mintzamena;braillea;" msgstr "pantaila;irakurlea;irisgarritasuna;mintzamena;braillea;"
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.4\n" "X-Generator: Poedit 3.4\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "صفحه‌خوان اورکا" msgstr "صفحه‌خوان اورکا"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -27,11 +27,9 @@ msgstr ""
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
"X-Poedit-Bookmarks: 814,-1,-1,-1,-1,-1,-1,-1,-1,-1\n" "X-Poedit-Bookmarks: 814,-1,-1,-1,-1,-1,-1,-1,-1,-1\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu-näytönlukija" msgstr "Cthulhu-näytönlukija"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "näyttö;lukija;saavutettavuus;puhe;pistekirjoitus;" msgstr "näyttö;lukija;saavutettavuus;puhe;pistekirjoitus;"
-2
View File
@@ -31,11 +31,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lecteur d’écran Cthulhu" msgstr "Lecteur d’écran Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "lecteur;écran;accessibilité;parole;braille;" msgstr "lecteur;écran;accessibilité;parole;braille;"
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.3\n" "X-Generator: Poedit 2.0.3\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "" msgstr ""
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,12 +18,10 @@ msgstr ""
"Plural-Forms: nplurals=5; plural=n==1 ? 0 : (n%10==1 || n%10==2) ? 1 : " "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" "(n%10>=3 && n%10<= 6) ? 2 : ((n%10>=7 && n%10<=9) || n==10) ? 3 : 4;\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Léitheoir Scáileáin agus Formhéadaitheoir Cthulhu" msgstr "Léitheoir Scáileáin agus Formhéadaitheoir Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -34,11 +34,9 @@ msgstr ""
"X-DL-Domain: po\n" "X-DL-Domain: po\n"
"X-DL-State: Translating\n" "X-DL-State: Translating\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lector da pantalla Cthulhu" msgstr "Lector da pantalla Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "pantalla;lector;accesibilidade;fala;braille;" msgstr "pantalla;lector;accesibilidade;fala;braille;"
-2
View File
@@ -33,12 +33,10 @@ msgstr ""
"\n" "\n"
"\n" "\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu સ્ક્રીન વાંચક અને વિસ્તૃતકારક" msgstr "Cthulhu સ્ક્રીન વાંચક અને વિસ્તૃતકારક"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -14,11 +14,9 @@ msgstr ""
"2 : 3);\n" "2 : 3);\n"
"X-Generator: Poedit 3.4.1\n" "X-Generator: Poedit 3.4.1\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "מקריא המסך Cthulhu" msgstr "מקריא המסך Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "מסך;קורא;נגישות;דיבור;ברייל;" msgstr "מסך;קורא;נגישות;דיבור;ברייל;"
-2
View File
@@ -22,11 +22,9 @@ msgstr ""
"\n" "\n"
"X-Generator: Lokalize 1.5\n" "X-Generator: Lokalize 1.5\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu स्क्रीन वाचक" msgstr "Cthulhu स्क्रीन वाचक"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "स्क्रीन रीडर;भाषण;ब्रेल;" msgstr "स्क्रीन रीडर;भाषण;ब्रेल;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"X-Launchpad-Export-Date: 2016-09-15 12:40+0000\n" "X-Launchpad-Export-Date: 2016-09-15 12:40+0000\n"
"X-Generator: Poedit 1.6.10\n" "X-Generator: Poedit 1.6.10\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu čitač zaslona" msgstr "Cthulhu čitač zaslona"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "čitač zaslona;govor;brajica;" msgstr "čitač zaslona;govor;brajica;"
-2
View File
@@ -22,11 +22,9 @@ msgstr ""
"X-Generator: Poedit 2.3\n" "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. # 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" msgid "Cthulhu Screen Reader"
msgstr "Orka képernyőolvasó" msgstr "Orka képernyőolvasó"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "képernyő;olvasó;akadálymentesítés;beszéd;braille;" msgstr "képernyő;olvasó;akadálymentesítés;beszéd;braille;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Pembaca Layar Cthulhu" msgstr "Pembaca Layar Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "layar;pembaca;aksesibilitas;ucapan;braille;" msgstr "layar;pembaca;aksesibilitas;ucapan;braille;"
-2
View File
@@ -17,11 +17,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 1.5\n" "X-Generator: Lokalize 1.5\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu skjálestur" msgstr "Cthulhu skjálestur"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -39,11 +39,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Gtranslator 42.0\n" "X-Generator: Gtranslator 42.0\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lettore schermo Cthulhu" msgstr "Lettore schermo Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "lettore;schermo;accessibilità;voce;braille;" msgstr "lettore;schermo;accessibilità;voce;braille;"
-2
View File
@@ -25,11 +25,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu スクリーンリーダー" msgstr "Cthulhu スクリーンリーダー"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "スクリーン;リーダー;アクセシビリティ;音声;点字;" msgstr "スクリーン;リーダー;アクセシビリティ;音声;点字;"
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu - ეკრანის მკითხველი" msgstr "Cthulhu - ეკრანის მკითხველი"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "ეკრანი;მკითხველი;ხელმისაწვდომობა;მეტყველება;ბრაილი;" msgstr "ეკრანი;მკითხველი;ხელმისაწვდომობა;მეტყველება;ბრაილი;"
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.0\n" "X-Generator: Poedit 3.0\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "" msgstr ""
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu экраннан оқитын қолданбасы" msgstr "Cthulhu экраннан оқитын қолданбасы"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,12 +18,10 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: KBabel 1.11.4\n" "X-Generator: KBabel 1.11.4\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "ಆದ್ಯತೆಗಳು" msgstr "ಆದ್ಯತೆಗಳು"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -15,11 +15,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu 화면 낭독기" msgstr "Cthulhu 화면 낭독기"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "화면;리더;접근성;음성;점자;" msgstr "화면;리더;접근성;음성;점자;"
-2
View File
@@ -25,11 +25,9 @@ msgstr ""
"(n%100<10 || n%100>=20) ? 1 : 2);\n" "(n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu ekrano skaityklė" msgstr "Cthulhu ekrano skaityklė"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "ekrano skaityklė;kalba;brailio raštas;" msgstr "ekrano skaityklė;kalba;brailio raštas;"
-2
View File
@@ -23,11 +23,9 @@ msgstr ""
"2);\n" "2);\n"
"X-Generator: Lokalize 21.12.3\n" "X-Generator: Lokalize 21.12.3\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu ekrāna lasītājs" msgstr "Cthulhu ekrāna lasītājs"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "ekrāna lasītājs;runa;Brails;" msgstr "ekrāna lasītājs;runa;Brails;"
-2
View File
@@ -19,12 +19,10 @@ msgstr ""
"\n" "\n"
"\n" "\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu स्क्रीन पाठक / आवर्द्धक" msgstr "Cthulhu स्क्रीन पाठक / आवर्द्धक"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural= n==1 || n%10==1 ? 0 : 1\n" "Plural-Forms: nplurals=2; plural= n==1 || n%10==1 ? 0 : 1\n"
"X-Generator: KBabel 1.11.4\n" "X-Generator: KBabel 1.11.4\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu читач на екранот" msgstr "Cthulhu читач на екранот"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -22,12 +22,10 @@ msgstr ""
"X-Generator: Poedit 2.0.1\n" "X-Generator: Poedit 2.0.1\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "സ്ക്രീന്‍ വായന അവസാനിപ്പിക്കാം." msgstr "സ്ക്രീന്‍ വായന അവസാനിപ്പിക്കാം."
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,12 +18,10 @@ msgstr ""
"X-Generator: KBabel 1.9.1\n" "X-Generator: KBabel 1.9.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "वर्धक भिंग" msgstr "वर्धक भिंग"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.0.6\n" "X-Generator: Poedit 2.0.6\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Pembaca skrin Cthulhu" msgstr "Pembaca skrin Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "skrin;pembaca;kebolehcapaian;pertuturan;braille;" msgstr "skrin;pembaca;kebolehcapaian;pertuturan;braille;"
-2
View File
@@ -19,11 +19,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu skjermleser" msgstr "Cthulhu skjermleser"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "skjerm;leser;tilgjengelighet;tale;blindeskrift;" msgstr "skjerm;leser;tilgjengelighet;tale;blindeskrift;"
-2
View File
@@ -13,11 +13,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n" "X-Generator: Poedit 3.0.1\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "ओर्का दृष्टि वाचक" msgstr "ओर्का दृष्टि वाचक"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "दृष्टि वाचक; वाचन;ब्रेल;" msgstr "दृष्टि वाचक; वाचन;ब्रेल;"
-2
View File
@@ -31,11 +31,9 @@ msgstr ""
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 3.2.2\n"
# vergrootglas/loep # vergrootglas/loep
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu schermlezer" msgstr "Cthulhu schermlezer"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "scherm;lezer;toegankelijkheid;spraak;braille;" msgstr "scherm;lezer;toegankelijkheid;spraak;braille;"
-2
View File
@@ -18,12 +18,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu skjermlesar og forstørringsglas" msgstr "Cthulhu skjermlesar og forstørringsglas"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -23,11 +23,9 @@ msgstr ""
"X-Launchpad-Export-Date: 2015-05-21 18:05+0000\n" "X-Launchpad-Export-Date: 2015-05-21 18:05+0000\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Lector d'ecran Cthulhu" msgstr "Lector d'ecran Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
"lector d'ecran;oral;braille;votz;sintèsi vocala;sintesi vocala;lector " "lector d'ecran;oral;braille;votz;sintèsi vocala;sintesi vocala;lector "
-2
View File
@@ -22,12 +22,10 @@ msgstr ""
"\n" "\n"
"\n" "\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "ଓର୍କା ସ୍କ୍ରିନ ପାଠକ /ଛୋଟକୁ ବଡ ଦେଖଉଥିବା ଉପକରଣ " msgstr "ଓର୍କା ସ୍କ୍ରିନ ପାଠକ /ଛୋଟକୁ ବଡ ଦେଖଉଥିବା ଉପକରଣ "
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-Bookmarks: 589,-1,-1,-1,-1,-1,-1,-1,-1,-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" msgid "Cthulhu Screen Reader"
msgstr "ਓਰਕਾ ਸਕਰੀਨ ਰੀਡਰ" msgstr "ਓਰਕਾ ਸਕਰੀਨ ਰੀਡਰ"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n" "|| n%100>=20) ? 1 : 2);\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Czytnik ekranowy Cthulhu" msgstr "Czytnik ekranowy Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "czytnik;ekran;dostępność;mowa;braille;" msgstr "czytnik;ekran;dostępność;mowa;braille;"
-2
View File
@@ -28,11 +28,9 @@ msgstr ""
"X-Source-Language: C\n" "X-Source-Language: C\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Leitor de ecrã Cthulhu" msgstr "Leitor de ecrã Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "leitor;ecrã;acessibilidade;voz;braille;" msgstr "leitor;ecrã;acessibilidade;voz;braille;"
-2
View File
@@ -49,11 +49,9 @@ msgstr ""
"X-DL-Domain: po\n" "X-DL-Domain: po\n"
"X-DL-State: Translating\n" "X-DL-State: Translating\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Leitor de tela Cthulhu" msgstr "Leitor de tela Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "leitor;tela;acessibilidade;voz;braille;" msgstr "leitor;tela;acessibilidade;voz;braille;"
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cititorul de ecran Cthulhu" msgstr "Cititorul de ecran Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "ecran;cititor;accesibilitate;vorbire;braille;" msgstr "ecran;cititor;accesibilitate;vorbire;braille;"
-2
View File
@@ -22,11 +22,9 @@ msgstr ""
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Экранный диктор Cthulhu" msgstr "Экранный диктор Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "экран;чтец;доступность;речь;брайль;" msgstr "экран;чтец;доступность;речь;брайль;"
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "" msgstr ""
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: KBabel 1.11.4\n" "X-Generator: KBabel 1.11.4\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "" msgstr ""
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -19,11 +19,9 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n"
"X-Generator: Gtranslator 2.91.7\n" "X-Generator: Gtranslator 2.91.7\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Ukončí čítačku obrazovky" msgstr "Ukončí čítačku obrazovky"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -22,11 +22,9 @@ msgstr ""
"X-Poedit-SourceCharset: utf-8\n" "X-Poedit-SourceCharset: utf-8\n"
"X-Generator: Poedit 3.0.1\n" "X-Generator: Poedit 3.0.1\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Zaslonski bralnik Orka" msgstr "Zaslonski bralnik Orka"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "govor;zaslonski bralnik;Braillova pisava;brajica;povečevalnik" msgstr "govor;zaslonski bralnik;Braillova pisava;brajica;povečevalnik"
-2
View File
@@ -17,12 +17,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cthulhu-autostart.desktop.in:3
#, fuzzy #, fuzzy
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Ekrani Zmadhues (Lupë)" msgstr "Ekrani Zmadhues (Lupë)"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -21,11 +21,9 @@ msgstr ""
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
"X-Generator: Poedit 3.1.1\n" "X-Generator: Poedit 3.1.1\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Читач екрана Орка" msgstr "Читач екрана Орка"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "читач екрана;говор;брајева азбука;" msgstr "читач екрана;говор;брајева азбука;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
"X-Generator: Poedit 2.0.3\n" "X-Generator: Poedit 2.0.3\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Čitač ekrana Orka" msgstr "Čitač ekrana Orka"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "čitač ekrana;govor;brajeva azbuka;" msgstr "čitač ekrana;govor;brajeva azbuka;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu skärmläsare" msgstr "Cthulhu skärmläsare"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "skärm;läsare;tillgänglighet;tal;punktskrift;" msgstr "skärm;läsare;tillgänglighet;tal;punktskrift;"
-2
View File
@@ -24,11 +24,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n!=1);\\n\n" "Plural-Forms: nplurals=2; plural=(n!=1);\\n\n"
"\n" "\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "ஆர்கா திரை படிப்பி " msgstr "ஆர்கா திரை படிப்பி "
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "திரைபடிப்பான்;பேசுதல்;ப்ரெய்ல்;" msgstr "திரைபடிப்பான்;பேசுதல்;ப்ரெய்ல்;"
-2
View File
@@ -23,11 +23,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n!=1);\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n"
"X-Generator: KBabel 1.11.4\n" "X-Generator: KBabel 1.11.4\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "ఓర్కా తెరచదువరి" msgstr "ఓర్కా తెరచదువరి"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "" msgstr ""
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.6.3\n" "X-Generator: Poedit 1.6.3\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Хонандаи экрани Cthulhu" msgstr "Хонандаи экрани Cthulhu"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "хонандаи экран;нутқ;брайл;" msgstr "хонандаи экран;нутқ;брайл;"
-2
View File
@@ -19,11 +19,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "โปรแกรมอ่านหน้าจอ Cthulhu" msgstr "โปรแกรมอ่านหน้าจอ Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "หน้าจอ;อ่าน;การเข้าถึง;เสียงพูด;อักษรเบรลล์;" msgstr "หน้าจอ;อ่าน;การเข้าถึง;เสียงพูด;อักษรเบรลล์;"
-2
View File
@@ -27,11 +27,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.4\n" "X-Generator: Poedit 3.4\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu Ekran Okuyucu" msgstr "Cthulhu Ekran Okuyucu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "ekran;okuyucu;erişilebilirlik;konuşma;braille;" msgstr "ekran;okuyucu;erişilebilirlik;konuşma;braille;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu ئېكران ئوقۇغۇ" msgstr "Cthulhu ئېكران ئوقۇغۇ"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "screen reader;speech;braille;ئېكران ئوقۇغۇچ;تاۋۇش;ئەمالار ئېلىپبەسى;" msgstr "screen reader;speech;braille;ئېكران ئوقۇغۇچ;تاۋۇش;ئەمالار ئېلىپبەسى;"
-2
View File
@@ -26,11 +26,9 @@ msgstr ""
"n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Lokalize 20.12.0\n" "X-Generator: Lokalize 20.12.0\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Інструмент читання з екрана «Cthulhu»" msgstr "Інструмент читання з екрана «Cthulhu»"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "екран;читач;доступність;мовлення;брайль;" msgstr "екран;читач;доступність;мовлення;брайль;"
-2
View File
@@ -18,11 +18,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: LocFactoryEditor 1.8\n" "X-Generator: LocFactoryEditor 1.8\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Trình đọc màn hình Cthulhu" msgstr "Trình đọc màn hình Cthulhu"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "màn hình;đọc;trợ năng;giọng nói;chữ nổi;" msgstr "màn hình;đọc;trợ năng;giọng nói;chữ nổi;"
-2
View File
@@ -29,11 +29,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.3.2\n" "X-Generator: Poedit 3.3.2\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu 屏幕阅读器" msgstr "Cthulhu 屏幕阅读器"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "屏幕;阅读器;无障碍;语音;盲文;" msgstr "屏幕;阅读器;无障碍;语音;盲文;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 1.6.5\n" "X-Generator: Poedit 1.6.5\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu 螢幕閱讀器" msgstr "Cthulhu 螢幕閱讀器"
#: cthulhu-autostart.desktop.in:11
#, fuzzy #, fuzzy
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "screen reader;speech;braille;螢幕閱讀器;語音;點字;" msgstr "screen reader;speech;braille;螢幕閱讀器;語音;點字;"
-2
View File
@@ -20,11 +20,9 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 1.8.12\n" "X-Generator: Poedit 1.8.12\n"
#: cthulhu-autostart.desktop.in:3
msgid "Cthulhu Screen Reader" msgid "Cthulhu Screen Reader"
msgstr "Cthulhu 螢幕閱讀器" msgstr "Cthulhu 螢幕閱讀器"
#: cthulhu-autostart.desktop.in:11
msgid "screen;reader;accessibility;speech;braille;" msgid "screen;reader;accessibility;speech;braille;"
msgstr "螢幕;閱讀器;無障礙;語音;點字;" msgstr "螢幕;閱讀器;無障礙;語音;點字;"
+48 -2
View File
@@ -85,12 +85,16 @@ class Backend:
with open(fileName, 'w', encoding='utf-8') as settingsFile: with open(fileName, 'w', encoding='utf-8') as settingsFile:
settingsFile.write(dumps(prefsDoc)) settingsFile.write(dumps(prefsDoc))
def _updateTable(self, targetTable, newValues): def _updateTable(self, targetTable, newValues, preserveMissingKeys=None):
if not isinstance(newValues, dict): if not isinstance(newValues, dict):
return return
preserveMissingKeys = set(preserveMissingKeys or [])
for key in list(targetTable.keys()): for key in list(targetTable.keys()):
if key not in newValues: if key not in newValues:
if key in preserveMissingKeys:
continue
del targetTable[key] del targetTable[key]
continue continue
newValue = newValues[key] newValue = newValues[key]
@@ -173,7 +177,12 @@ class Backend:
profiles[profile] = {} profiles[profile] = {}
profileTable = 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) self._writeDocument(self.settingsFile, prefsDoc)
def _getSettings(self): def _getSettings(self):
@@ -213,12 +222,33 @@ class Backend:
return settingsDict 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): def getGeneral(self, profile=None):
""" Get general settings from default settings and """ Get general settings from default settings and
override with profile values. """ override with profile values. """
self._getSettings() self._getSettings()
generalSettings = self.general.copy() generalSettings = self.general.copy()
generalSettings = self._migrateSettings(generalSettings) 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', defaultProfile = generalSettings.get('startingProfile',
['Default', 'default']) ['Default', 'default'])
if profile is None: if profile is None:
@@ -229,8 +259,24 @@ class Backend:
if key == 'voices': if key == 'voices':
for voiceType, voiceDef in value.items(): for voiceType, voiceDef in value.items():
value[voiceType] = acss.ACSS(voiceDef) value[voiceType] = acss.ACSS(voiceDef)
if key == 'echoVoice' and isinstance(value, dict):
value = acss.ACSS(value)
if key not in ['startingProfile', 'activeProfile']: if key not in ['startingProfile', 'activeProfile']:
generalSettings[key] = value 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: try:
generalSettings['activeProfile'] = profileSettings['profile'] generalSettings['activeProfile'] = profileSettings['profile']
except KeyError: except KeyError:
+310 -3
View File
@@ -136,6 +136,24 @@
<property name="step_increment">0.10000000149</property> <property name="step_increment">0.10000000149</property>
<property name="page_increment">1</property> <property name="page_increment">1</property>
</object> </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"> <object class="GtkDialog" id="cthulhuSetupWindow">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="title" translatable="yes">Cthulhu Preferences</property> <property name="title" translatable="yes">Cthulhu Preferences</property>
@@ -3037,6 +3055,295 @@
<property name="top_attach">1</property> <property name="top_attach">1</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkCheckButton" id="enableEchoByCharacterCheckButton"> <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> <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 +3356,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">2</property> <property name="top_attach">3</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -3064,7 +3371,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">3</property> <property name="top_attach">4</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -3079,7 +3386,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">4</property> <property name="top_attach">5</property>
</packing> </packing>
</child> </child>
</object> </object>
+2 -2
View File
@@ -23,5 +23,5 @@
# Forked from Orca screen reader. # Forked from Orca screen reader.
# Cthulhu project: https://git.stormux.org/storm/cthulhu # Cthulhu project: https://git.stormux.org/storm/cthulhu
version = "2026.01.19" version = "2026.03.02"
codeName = "testing" codeName = "master"
+494 -15
View File
@@ -38,6 +38,9 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Atspi from gi.repository import Atspi
import os import os
import json
import subprocess
import sys
import threading import threading
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
@@ -173,6 +176,16 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self.speechSystemsChoice = None self.speechSystemsChoice = None
self.speechSystemsChoices = None self.speechSystemsChoices = None
self.speechSystemsModel = 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.systemVoice = None
self.uppercaseVoice = None self.uppercaseVoice = None
self.window = None self.window = None
@@ -183,6 +196,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self.soundThemeCombo = None self.soundThemeCombo = None
self.roleSoundPresentationCombo = None self.roleSoundPresentationCombo = None
self._isInitialSetup = False self._isInitialSetup = False
self._updatingSpeechFamilies = False
self.selectedFamilyChoices = {} self.selectedFamilyChoices = {}
self.selectedLanguageChoices = {} self.selectedLanguageChoices = {}
self.profilesCombo = None self.profilesCombo = None
@@ -385,7 +399,12 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self._initComboBox(self.get_widget("speechLanguages")) self._initComboBox(self.get_widget("speechLanguages"))
self.speechFamiliesModel = \ self.speechFamiliesModel = \
self._initComboBox(self.get_widget("speechFamilies")) 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._initSpeechState()
self._initEchoSpeechState()
# TODO - JD: Will this ever be the case?? # TODO - JD: Will this ever be the case??
self._isInitialSetup = \ self._isInitialSetup = \
@@ -1271,7 +1290,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
for familyChoice in self.speechFamiliesChoices: for familyChoice in self.speechFamiliesChoices:
name = familyChoice[speechserver.VoiceFamily.NAME] name = familyChoice[speechserver.VoiceFamily.NAME]
if name == familyName: if name == familyName:
self.get_widget("speechFamilies").set_active(i) self._setSpeechFamiliesActive(i)
self.speechFamiliesChoice = self.speechFamiliesChoices[i] self.speechFamiliesChoice = self.speechFamiliesChoices[i]
familySet = True familySet = True
break break
@@ -1295,13 +1314,22 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
if not familySet: if not familySet:
tokens = ["PREFERENCES DIALOG: Could not find speech family match for", familyName] tokens = ["PREFERENCES DIALOG: Could not find speech family match for", familyName]
debug.printTokens(debug.LEVEL_INFO, tokens, True) debug.printTokens(debug.LEVEL_INFO, tokens, True)
self.get_widget("speechFamilies").set_active(0) self._setSpeechFamiliesActive(0)
self.speechFamiliesChoice = self.speechFamiliesChoices[0] self.speechFamiliesChoice = self.speechFamiliesChoices[0]
if familySet: if familySet:
self.selectedFamilyChoices[self.speechServersChoice, self.selectedFamilyChoices[self.speechServersChoice,
self.speechLanguagesChoice] = i 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): def _setupFamilies(self):
"""Gets the list of voice variants for the current speech server and """Gets the list of voice variants for the current speech server and
current language. current language.
@@ -1353,7 +1381,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
selectedIndex = self.selectedFamilyChoices[self.speechServersChoice, selectedIndex = self.selectedFamilyChoices[self.speechServersChoice,
self.speechLanguagesChoice] self.speechLanguagesChoice]
self.get_widget("speechFamilies").set_active(selectedIndex) self._setSpeechFamiliesActive(selectedIndex)
def _setSpeechLanguagesChoice(self, languageName): def _setSpeechLanguagesChoice(self, languageName):
"""Sets the active item in the languages ("Language:") combo box """Sets the active item in the languages ("Language:") combo box
@@ -1661,6 +1689,261 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self._setupSpeechSystems(factories) self._setupSpeechSystems(factories)
self.initializingSpeech = False 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 _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
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, def _setSpokenTextAttributes(self, view, setAttributes,
state, moveToTop=False): state, moveToTop=False):
"""Given a set of spoken text attributes, update the model used by the """Given a set of spoken text attributes, update the model used by the
@@ -2394,6 +2677,19 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
prefs["enableEchoByWord"]) prefs["enableEchoByWord"])
self.get_widget("enableEchoBySentenceCheckButton").set_active( \ self.get_widget("enableEchoBySentenceCheckButton").set_active( \
prefs["enableEchoBySentence"]) 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. # Text attributes pane.
# #
@@ -2553,20 +2849,18 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self._updateAIControlsState(enabled) self._updateAIControlsState(enabled)
def _updateAIControlsState(self, enabled): def _updateAIControlsState(self, enabled):
"""Enable or disable AI controls based on AI enabled state.""" """Refresh AI controls while keeping configuration fields editable."""
self.aiProviderCombo.set_sensitive(enabled) _ = enabled # kept for signal/call compatibility
self.aiApiKeyEntry.set_sensitive(enabled) # Keep settings editable even when AI assistant is disabled so users
self.aiOllamaModelEntry.set_sensitive(enabled) # can configure providers/keys before enabling it.
self.aiOllamaEndpointEntry.set_sensitive(enabled) self.aiProviderCombo.set_sensitive(True)
self.aiConfirmationCheckButton.set_sensitive(enabled) self.aiConfirmationCheckButton.set_sensitive(True)
self.aiScreenshotQualityCombo.set_sensitive(enabled) self.aiScreenshotQualityCombo.set_sensitive(True)
try: try:
self.get_widget("aiGetClaudeKeyButton").set_sensitive(enabled) self.get_widget("aiGetClaudeKeyButton").set_sensitive(True)
except: except:
pass # Button might not exist in older UI files 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) current_provider = self.prefsDict.get("aiProvider", settings.aiProvider)
self._updateProviderControls(current_provider) self._updateProviderControls(current_provider)
@@ -2898,22 +3192,91 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self.get_widget("enableNavigationKeysCheckButton").set_sensitive(enable) self.get_widget("enableNavigationKeysCheckButton").set_sensitive(enable)
self.get_widget("enableDiacriticalKeysCheckButton").set_sensitive( \ self.get_widget("enableDiacriticalKeysCheckButton").set_sensitive( \
enable) 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 """If the text field is not None, presents the given text, optionally
interrupting anything currently being spoken. interrupting anything currently being spoken.
Arguments: Arguments:
- text: the text to present - text: the text to present
- interrupt: if True, interrupt any speech currently being spoken - 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: try:
self.script.displayBrailleMessage(text, flashTime=-1) self.script.displayBrailleMessage(text, flashTime=-1)
except Exception: except Exception:
pass 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): def _createNode(self, appName):
"""Create a new root node in the TreeStore model with the name of the """Create a new root node in the TreeStore model with the name of the
application. application.
@@ -3194,6 +3557,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
enable = widget.get_active() enable = widget.get_active()
self.prefsDict["enableSpeech"] = enable self.prefsDict["enableSpeech"] = enable
self.get_widget("speechOptionsGrid").set_sensitive(enable) self.get_widget("speechOptionsGrid").set_sensitive(enable)
self._setupEchoSpeechServers()
self._setEchoVoiceItems()
def onlySpeakDisplayedTextToggled(self, widget): def onlySpeakDisplayedTextToggled(self, widget):
"""Signal handler for the "toggled" signal for the GtkCheckButton """Signal handler for the "toggled" signal for the GtkCheckButton
@@ -3225,6 +3590,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
selectedIndex = widget.get_active() selectedIndex = widget.get_active()
self.speechSystemsChoice = self.speechSystemsChoices[selectedIndex] self.speechSystemsChoice = self.speechSystemsChoices[selectedIndex]
self._setupSpeechServers() self._setupSpeechServers()
self._setupEchoSpeechServers()
self._setEchoVoiceItems()
def speechServersChanged(self, widget): def speechServersChanged(self, widget):
"""Signal handler for the "changed" signal for the speechServers """Signal handler for the "changed" signal for the speechServers
@@ -3257,6 +3624,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
pass pass
self._setupVoices() self._setupVoices()
self._setEchoVoiceItems()
def speechLanguagesChanged(self, widget): def speechLanguagesChanged(self, widget):
"""Signal handler for the "value_changed" signal for the languages """Signal handler for the "value_changed" signal for the languages
@@ -3316,6 +3684,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
variant = family[speechserver.VoiceFamily.VARIANT] variant = family[speechserver.VoiceFamily.VARIANT]
voiceType = self.get_widget("voiceTypesCombo").get_active() voiceType = self.get_widget("voiceTypesCombo").get_active()
self._setFamilyNameForVoiceType(voiceType, name, language, dialect, variant) self._setFamilyNameForVoiceType(voiceType, name, language, dialect, variant)
if not self._updatingSpeechFamilies:
self._previewVoiceSelection(voiceType, name)
except Exception: except Exception:
debug.printException(debug.LEVEL_SEVERE) debug.printException(debug.LEVEL_SEVERE)
@@ -3393,6 +3763,75 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
voices.get(settings.DEFAULT_VOICE, {})[acss.ACSS.GAIN] = volume voices.get(settings.DEFAULT_VOICE, {})[acss.ACSS.GAIN] = volume
cthulhu.cthulhuApp.settingsManager.setSetting('voices', voices) 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): def checkButtonToggled(self, widget):
"""Signal handler for "toggled" signal for basic GtkCheckButton """Signal handler for "toggled" signal for basic GtkCheckButton
widgets. The user has altered the state of the checkbox. widgets. The user has altered the state of the checkbox.
@@ -3410,6 +3849,15 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
# strip "CheckButton" from the end. # strip "CheckButton" from the end.
settingName = settingName[:-11] settingName = settingName[:-11]
self.prefsDict[settingName] = widget.get_active() self.prefsDict[settingName] = widget.get_active()
if settingName in [
"enableEchoByCharacter",
"enableEchoByWord",
"enableEchoBySentence",
"useCustomEchoForKey",
"useCustomEchoForCharacter",
"useCustomEchoForWord",
"useCustomEchoForSentence"]:
self._setEchoVoiceItems()
def keyEchoChecked(self, widget): def keyEchoChecked(self, widget):
"""Signal handler for the "toggled" signal for the """Signal handler for the "toggled" signal for the
@@ -4212,6 +4660,35 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
settings.SYSTEM_VOICE: acss.ACSS(self.systemVoice), 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)
if self.echoSpeechServersChoice:
self.prefsDict["echoSpeechServerInfo"] = self.echoSpeechServersChoice.getInfo()
else:
self.prefsDict["echoSpeechServerInfo"] = None
def applyButtonClicked(self, widget): def applyButtonClicked(self, widget):
"""Signal handler for the "clicked" signal for the applyButton """Signal handler for the "clicked" signal for the applyButton
GtkButton widget. The user has clicked the Apply button. GtkButton widget. The user has clicked the Apply button.
@@ -4244,6 +4721,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self._refresh_dynamic_plugin_tabs() self._refresh_dynamic_plugin_tabs()
braille.checkBrailleSetting() braille.checkBrailleSetting()
self._initSpeechState() self._initSpeechState()
self._initEchoSpeechState()
self._populateKeyBindings() self._populateKeyBindings()
self.__initProfileCombo() self.__initProfileCombo()
@@ -4506,6 +4984,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
braille.checkBrailleSetting() braille.checkBrailleSetting()
self._initSpeechState() self._initSpeechState()
self._initEchoSpeechState()
self._populateKeyBindings() self._populateKeyBindings()
+44 -1
View File
@@ -844,6 +844,11 @@ class KeyboardEvent(InputEvent):
"shouldConsume: No handler found", "shouldConsume: No handler found",
reason="no-handler", timestamp=True) reason="no-handler", timestamp=True)
if self._isSleepModeActive():
if self._isSleepModeToggleHandler():
return True, 'Sleep mode toggle command'
return False, 'Sleep mode active'
self._script.updateKeyboardEventState(self, self._handler) self._script.updateKeyboardEventState(self, self._handler)
scriptConsumes = self._script.shouldConsumeKeyboardEvent(self, self._handler) scriptConsumes = self._script.shouldConsumeKeyboardEvent(self, self._handler)
if globalHandlerUsed: if globalHandlerUsed:
@@ -881,6 +886,35 @@ class KeyboardEvent(InputEvent):
return None return None
return global_bindings.getInputHandler(self) return global_bindings.getInputHandler(self)
def _isSleepModeActive(self):
"""Returns True if the script for this event is in sleep mode."""
if not self._script:
return False
if "scripts.sleepmode" in self._script.__module__:
return True
app = getattr(self._script, "app", None)
if app is None:
return False
try:
from . import sleep_mode_manager
manager = sleep_mode_manager.getManager()
return bool(manager and manager.isActiveForApp(app))
except Exception:
return False
def _isSleepModeToggleHandler(self):
"""Returns True if the resolved handler toggles sleep mode."""
if not self._handler or not self._handler.function:
return False
functionName = getattr(self._handler.function, "__name__", "")
return "toggleSleepMode" in functionName
def didConsume(self): def didConsume(self):
"""Returns True if this event was consumed.""" """Returns True if this event was consumed."""
@@ -1139,7 +1173,7 @@ class RemoteControllerEvent(InputEvent):
class InputEventHandler: class InputEventHandler:
def __init__(self, function, description, learnModeEnabled=True): def __init__(self, function, description, learnModeEnabled=True, enabled=True):
"""Creates a new InputEventHandler instance. All bindings """Creates a new InputEventHandler instance. All bindings
(e.g., key bindings and braille bindings) will be handled (e.g., key bindings and braille bindings) will be handled
by an instance of an InputEventHandler. by an instance of an InputEventHandler.
@@ -1159,6 +1193,15 @@ class InputEventHandler:
self.function = function self.function = function
self.description = description self.description = description
self.learnModeEnabled = learnModeEnabled self.learnModeEnabled = learnModeEnabled
self._enabled = enabled
def is_enabled(self):
"""Returns True if this handler is enabled."""
return self._enabled
def set_enabled(self, enabled):
"""Sets enabled state of this handler."""
self._enabled = enabled
def __eq__(self, other): def __eq__(self, other):
"""Compares one input handler to another.""" """Compares one input handler to another."""
+13 -37
View File
@@ -74,7 +74,10 @@ class InputEventManager:
msg = "INPUT EVENT MANAGER: Starting key watcher." msg = "INPUT EVENT MANAGER: Starting key watcher."
debug.print_message(debug.LEVEL_INFO, msg, True) debug.print_message(debug.LEVEL_INFO, msg, True)
if Atspi.get_version() >= (2, 55, 90):
self._device = Atspi.Device.new_full("org.stormux.Cthulhu") self._device = Atspi.Device.new_full("org.stormux.Cthulhu")
else:
self._device = Atspi.Device.new()
self._device.add_key_watcher(self.process_keyboard_event) self._device.add_key_watcher(self.process_keyboard_event)
def stop_key_watcher(self) -> None: def stop_key_watcher(self) -> None:
@@ -100,43 +103,30 @@ class InputEventManager:
msg = f"INPUT EVENT MANAGER: {grab_id} for: {binding}" msg = f"INPUT EVENT MANAGER: {grab_id} for: {binding}"
debug.print_message(debug.LEVEL_INFO, msg, True) debug.print_message(debug.LEVEL_INFO, msg, True)
def _get_key_definitions(self, binding: keybindings.KeyBinding) -> List[Atspi.KeyDefinition]:
if hasattr(binding, "key_definitions"):
return list(binding.key_definitions())
if hasattr(binding, "keyDefs"):
return list(binding.keyDefs())
return []
def add_grabs_for_keybinding(self, binding: keybindings.KeyBinding) -> List[int]: def add_grabs_for_keybinding(self, binding: keybindings.KeyBinding) -> List[int]:
"""Adds grabs for binding if it is enabled, returns grab IDs.""" """Adds grabs for binding if it is enabled, returns grab IDs."""
if not (binding.is_enabled() and binding.is_bound()):
return []
if binding.has_grabs():
tokens = ["INPUT EVENT MANAGER:", binding, "already has grabs."]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return []
if self._device is None: if self._device is None:
tokens = ["INPUT EVENT MANAGER: No device to add grab for", binding] tokens = ["INPUT EVENT MANAGER: No device to add grab for", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True) debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return [] return []
grab_ids = [] grab_ids = []
key_definitions = self._get_key_definitions(binding) for kd in binding.key_definitions():
if not key_definitions:
tokens = ["INPUT EVENT MANAGER: No key definitions for", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return []
for kd in key_definitions:
grab_id = self._device.add_key_grab(kd, None) grab_id = self._device.add_key_grab(kd, None)
# When we have double/triple-click bindings, the single-click binding will be
# registered first, and subsequent attempts to register what is externally the
# same grab will fail. If we only have a double/triple-click, it succeeds.
# A grab id of 0 indicates failure.
if grab_id == 0: if grab_id == 0:
continue continue
grab_ids.append(grab_id) grab_ids.append(grab_id)
self._grabbed_bindings[grab_id] = binding self._grabbed_bindings[grab_id] = binding
if grab_ids:
tokens = ["INPUT EVENT MANAGER: Added grabs", grab_ids, "for", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return grab_ids return grab_ids
def remove_grabs_for_keybinding(self, binding: keybindings.KeyBinding) -> None: def remove_grabs_for_keybinding(self, binding: keybindings.KeyBinding) -> None:
@@ -147,30 +137,18 @@ class InputEventManager:
debug.print_tokens(debug.LEVEL_INFO, tokens, True) debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return return
grab_ids = None
if hasattr(binding, "get_grab_ids"):
grab_ids = binding.get_grab_ids() grab_ids = binding.get_grab_ids()
elif hasattr(binding, "_grab_ids"):
grab_ids = list(binding._grab_ids)
if not grab_ids: if not grab_ids:
tokens = ["INPUT EVENT MANAGER:", binding, "doesn't have grabs to remove."] tokens = ["INPUT EVENT MANAGER:", binding, "doesn't have grabs to remove."]
debug.print_tokens(debug.LEVEL_INFO, tokens, True) debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return return
tokens = ["INPUT EVENT MANAGER: Removing grabs", grab_ids, "for", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
for grab_id in grab_ids: for grab_id in grab_ids:
self._device.remove_key_grab(grab_id) self._device.remove_key_grab(grab_id)
removed = self._grabbed_bindings.pop(grab_id, None) removed = self._grabbed_bindings.pop(grab_id, None)
if removed is None: if removed is None:
msg = f"INPUT EVENT MANAGER: No key binding for grab id {grab_id}" msg = f"INPUT EVENT MANAGER: No key binding for grab id {grab_id}"
debug.print_message(debug.LEVEL_INFO, msg, True) debug.print_message(debug.LEVEL_INFO, msg, True)
if hasattr(binding, "_grab_ids") and grab_id in binding._grab_ids:
binding._grab_ids.remove(grab_id)
if hasattr(binding, "_grab_ids") and not binding._grab_ids:
delattr(binding, "_grab_ids")
def remove_grab_by_id(self, grab_id: int) -> None: def remove_grab_by_id(self, grab_id: int) -> None:
"""Removes a grab by id.""" """Removes a grab by id."""
@@ -182,10 +160,8 @@ class InputEventManager:
self._device.remove_key_grab(grab_id) self._device.remove_key_grab(grab_id)
binding = self._grabbed_bindings.pop(grab_id, None) binding = self._grabbed_bindings.pop(grab_id, None)
if binding and hasattr(binding, "_grab_ids") and grab_id in binding._grab_ids: if binding and grab_id in binding._grab_ids:
binding._grab_ids.remove(grab_id) binding._grab_ids.remove(grab_id)
if not binding._grab_ids:
delattr(binding, "_grab_ids")
if binding: if binding:
tokens = ["INPUT EVENT MANAGER: Removed grab", grab_id, "for", binding] tokens = ["INPUT EVENT MANAGER: Removed grab", grab_id, "for", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True) debug.print_tokens(debug.LEVEL_INFO, tokens, True)
+161 -152
View File
@@ -26,6 +26,8 @@
"""Provides support for defining keybindings and matching them to input """Provides support for defining keybindings and matching them to input
events.""" events."""
from __future__ import annotations
__id__ = "$Id$" __id__ = "$Id$"
__version__ = "$Revision$" __version__ = "$Revision$"
__date__ = "$Date$" __date__ = "$Date$"
@@ -39,6 +41,7 @@ from gi.repository import Gdk
from gi.repository import Atspi from gi.repository import Atspi
import functools import functools
from typing import TYPE_CHECKING
from . import debug from . import debug
from . import settings from . import settings
@@ -46,6 +49,9 @@ from . import cthulhu_state
from .cthulhu_i18n import _ from .cthulhu_i18n import _
if TYPE_CHECKING:
from .input_event import KeyboardEvent, InputEventHandler
_keysymsCache = {} _keysymsCache = {}
_keycodeCache = {} _keycodeCache = {}
@@ -80,63 +86,35 @@ NON_LOCKING_MODIFIER_MASK = (1 << Atspi.ModifierType.SHIFT |
1 << MODIFIER_CTHULHU) 1 << MODIFIER_CTHULHU)
defaultModifierMask = NON_LOCKING_MODIFIER_MASK defaultModifierMask = NON_LOCKING_MODIFIER_MASK
def getKeycode(keysym): CAN_USE_KEYSYMS = Atspi.get_version() >= (2, 55, 0)
"""Converts an XKeysym string (e.g., 'KP_Enter') to a keycode that
should match the event.hw_code for key events.
This whole situation is caused by the fact that Solaris chooses
to give us different keycodes for the same key, and the keypad
is the primary place where this happens: if NumLock is not on,
there is no telling the difference between keypad keys and the
other navigation keys (e.g., arrows, page up/down, etc.). One,
for example, would expect to get KP_End for the '1' key on the
keypad if NumLock were not on. Instead, we get 'End' and the
keycode for it matches the keycode for the other 'End' key. Odd.
If NumLock is on, we at least get KP_* keys.
So...when setting up keybindings, we say we're interested in
KeySyms, but those keysyms are carefully chosen so as to result
in a keycode that matches the actual key on the keyboard. This
is why we use KP_1 instead of KP_End and so on in our keybindings.
Arguments:
- keysym: a string that is a valid representation of an XKeysym.
Returns an integer representing a key code that should match the
event.hw_code for key events.
"""
def get_keycodes(keysym):
"""Converts an XKeysym string to a (keyval, keycode) tuple."""
if not keysym: if not keysym:
return 0 return (0, 0)
if keysym not in _keycodeCache: if keysym not in _keycodeCache:
keymap = Gdk.Keymap.get_default() keymap = Gdk.Keymap.get_default()
# Find the numerical value of the keysym
#
keyval = Gdk.keyval_from_name(keysym) keyval = Gdk.keyval_from_name(keysym)
if keyval == 0: if keyval == 0:
return 0 return (0, 0)
# Now find the keycodes for the keysym. Since a keysym can _keycodeCache[keysym] = (keyval, 0)
# be associated with more than one key, we'll shoot for the _success, entries = keymap.get_entries_for_keyval(keyval)
# keysym that's in group 0, regardless of shift level (each
# entry is of the form [keycode, group, level]).
#
_keycodeCache[keysym] = 0
success, entries = keymap.get_entries_for_keyval(keyval)
for entry in entries: for entry in entries:
if entry.group == 0: if entry.group == 0:
_keycodeCache[keysym] = entry.keycode _keycodeCache[keysym] = (keyval, entry.keycode)
break break
if _keycodeCache[keysym] == 0: if _keycodeCache[keysym] == (keyval, 0):
_keycodeCache[keysym] = entries[0].keycode _keycodeCache[keysym] = (keyval, entries[0].keycode)
#print keysym, keyval, entries, _keycodeCache[keysym]
return _keycodeCache[keysym] return _keycodeCache[keysym]
def getKeycode(keysym):
"""Converts an XKeysym string to a keycode. Legacy wrapper."""
return get_keycodes(keysym)[1]
def getModifierNames(mods): def getModifierNames(mods):
"""Gets the modifier names of a numeric modifier mask as a human """Gets the modifier names of a numeric modifier mask as a human
consumable string. consumable string.
@@ -211,13 +189,43 @@ def get_click_countString(count):
return _("triple click") return _("triple click")
return "" return ""
def create_key_definitions(keycode, keyval, modifiers):
"""Returns a list of Atspi key definitions for the given keycode, keyval, and modifiers."""
ret = []
if modifiers & CTHULHU_MODIFIER_MASK:
modifier_list = []
other_modifiers = modifiers & ~CTHULHU_MODIFIER_MASK
from . import input_event_manager
manager = input_event_manager.get_manager()
for key in settings.cthulhuModifierKeys:
mod_keyval, mod_keycode = get_keycodes(key)
if mod_keycode == 0 and key == "Shift_Lock":
mod_keyval, mod_keycode = get_keycodes("Caps_Lock")
if CAN_USE_KEYSYMS:
mod = manager.map_keysym_to_modifier(mod_keyval)
else:
mod = manager.map_keycode_to_modifier(mod_keycode)
if mod:
modifier_list.append(mod | other_modifiers)
else:
modifier_list = [modifiers]
for mod in modifier_list:
kd = Atspi.KeyDefinition()
if CAN_USE_KEYSYMS:
kd.keysym = keyval
else:
kd.keycode = keycode
kd.modifiers = mod
ret.append(kd)
return ret
class KeyBinding: class KeyBinding:
"""A single key binding, consisting of a keycode, a modifier mask, """A single key binding, consisting of a keycode, a modifier mask,
and the InputEventHandler. and the InputEventHandler.
""" """
def __init__(self, keysymstring, modifier_mask, modifiers, handler, def __init__(self, keysymstring, modifier_mask, modifiers, handler,
click_count = 1): click_count = 1, enabled=True):
"""Creates a new key binding. """Creates a new key binding.
Arguments: Arguments:
@@ -237,41 +245,21 @@ class KeyBinding:
self.modifiers = modifiers self.modifiers = modifiers
self.handler = handler self.handler = handler
self.click_count = click_count self.click_count = click_count
self.keycode = None self.keycode = 0
self.keyval = 0
self._enabled = enabled
self._grab_ids = []
def matches(self, keycode, modifiers): def matches(self, keyval, keycode, modifiers):
"""Returns true if this key binding matches the given keycode and """Returns true if this key binding matches the given keyval/keycode and modifier state."""
modifier state.
"""
# We lazily bind the keycode. The primary reason for doing this
# is so that atspi does not have to be initialized before setting
# keybindings in the user's preferences file.
#
if not self.keycode: if not self.keycode:
self.keycode = getKeycode(self.keysymstring) self.keyval, self.keycode = get_keycodes(self.keysymstring)
# Debug logging for DisplayVersion plugin specifically if self.keycode == keycode or self.keyval == keyval:
if self.keysymstring == 'v' and self.modifiers == 257:
with open('/tmp/displayversion_matches.log', 'a') as f:
f.write(f"=== DisplayVersion matches() debug ===\n")
f.write(f"Self keycode: {self.keycode}\n")
f.write(f"Self keysymstring: {self.keysymstring}\n")
f.write(f"Self modifiers: {self.modifiers}\n")
f.write(f"Self modifier_mask: {self.modifier_mask}\n")
f.write(f"Input keycode: {keycode}\n")
f.write(f"Input modifiers: {modifiers}\n")
f.write(f"Keycode match: {self.keycode == keycode}\n")
if self.keycode == keycode:
result = modifiers & self.modifier_mask
f.write(f"Modifier calculation: {modifiers} & {self.modifier_mask} = {result}\n")
f.write(f"Modifier match: {result == self.modifiers}\n")
f.write(f"Overall match: {self.keycode == keycode and (modifiers & self.modifier_mask) == self.modifiers}\n")
if self.keycode == keycode:
result = modifiers & self.modifier_mask result = modifiers & self.modifier_mask
return result == self.modifiers return result == self.modifiers
else:
return False return False
def description(self): def description(self):
@@ -292,41 +280,52 @@ class KeyBinding:
return string.strip() return string.strip()
def keyDefs(self): def is_bound(self):
""" return a list of Atspi key definitions for the given binding. """Returns True if this KeyBinding is bound to a key."""
This may return more than one binding if the Cthulhu modifier is bound return bool(self.keysymstring)
to more than one key.
If AT-SPI is older than 2.40, then this function will not work and def is_enabled(self):
will return an empty set. """Returns True if this KeyBinding is enabled."""
""" return self._enabled
def set_enabled(self, enabled):
"""Set this KeyBinding's enabled state."""
self._enabled = enabled
def get_grab_ids(self):
"""Returns the grab IDs for this KeyBinding."""
return self._grab_ids
def has_grabs(self):
"""Returns True if there are existing grabs associated with this KeyBinding."""
return bool(self._grab_ids)
def add_grabs(self):
"""Adds key grabs for this KeyBinding."""
from . import input_event_manager
self._grab_ids = input_event_manager.get_manager().add_grabs_for_keybinding(self)
def remove_grabs(self):
"""Removes key grabs for this KeyBinding."""
from . import input_event_manager
input_event_manager.get_manager().remove_grabs_for_keybinding(self)
self._grab_ids = []
def key_definitions(self):
"""Return a list of Atspi key definitions for the given binding."""
ret = [] ret = []
if not self.keycode: if not self.keycode:
self.keycode = getKeycode(self.keysymstring) self.keyval, self.keycode = get_keycodes(self.keysymstring)
ret.extend(create_key_definitions(self.keycode, self.keyval, self.modifiers))
if CAN_USE_KEYSYMS and self.modifiers & SHIFT_MODIFIER_MASK:
upper_keyval = Gdk.keyval_to_upper(self.keyval)
if upper_keyval != self.keyval:
ret.extend(create_key_definitions(self.keycode, upper_keyval, self.modifiers))
return ret
if self.modifiers & CTHULHU_MODIFIER_MASK: def keyDefs(self):
device = cthulhu_state.device """Legacy wrapper. Use key_definitions() instead."""
if device is None: return self.key_definitions()
return ret
modList = []
otherMods = self.modifiers & ~CTHULHU_MODIFIER_MASK
numLockMod = device.get_modifier(getKeycode("Num_Lock"))
lockedMods = device.get_locked_modifiers()
numLockOn = lockedMods & numLockMod
for key in settings.cthulhuModifierKeys:
keycode = getKeycode(key)
if keycode == 0 and key == "Shift_Lock":
keycode = getKeycode("Caps_Lock")
mod = device.map_modifier(keycode)
if key != "KP_Insert" or not numLockOn:
modList.append(mod | otherMods)
else:
modList = [self.modifiers]
for mod in modList:
kd = Atspi.KeyDefinition()
kd.keycode = self.keycode
kd.modifiers = mod
ret.append(kd)
return ret
class KeyBindings: class KeyBindings:
"""Structure that maintains a set of KeyBinding instances. """Structure that maintains a set of KeyBinding instances.
@@ -347,7 +346,7 @@ class KeyBindings:
result += "]" result += "]"
return result return result
def add(self, keyBinding): def add(self, keyBinding, include_grabs=False):
"""Adds the given KeyBinding instance to this set of keybindings. """Adds the given KeyBinding instance to this set of keybindings.
""" """
@@ -359,17 +358,29 @@ class KeyBindings:
debug.printMessage(debug.LEVEL_INFO, msg, True) debug.printMessage(debug.LEVEL_INFO, msg, True)
self.keyBindings.append(keyBinding) self.keyBindings.append(keyBinding)
if include_grabs:
keyBinding.add_grabs()
def remove(self, keyBinding): def remove(self, keyBinding, include_grabs=False):
"""Removes the given KeyBinding instance from this set of keybindings. """Removes the given KeyBinding instance from this set of keybindings.
""" """
if keyBinding not in self.keyBindings:
candidates = self.getBindingsForHandler(keyBinding.handler)
if not candidates:
return
for candidate in self.getBindingsForHandler(keyBinding.handler):
self.remove(candidate, include_grabs)
return
if keyBinding.has_grabs():
if include_grabs:
keyBinding.remove_grabs()
try: try:
i = self.keyBindings.index(keyBinding) self.keyBindings.remove(keyBinding)
except Exception: except ValueError:
pass pass
else:
del self.keyBindings[i]
def removeByHandler(self, handler): def removeByHandler(self, handler):
"""Removes the given KeyBinding instance from this set of keybindings. """Removes the given KeyBinding instance from this set of keybindings.
@@ -380,6 +391,38 @@ class KeyBindings:
del self.keyBindings[i - 1] del self.keyBindings[i - 1]
i = i - 1 i = i - 1
def add_key_grabs(self, reason=""):
"""Adds grabs for all enabled bindings in this set of keybindings."""
msg = "KEYBINDINGS: Adding key grabs"
if reason:
msg += f": {reason}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
count = 0
for binding in self.keyBindings:
if binding.is_enabled() and not binding.has_grabs():
count += 1
binding.add_grabs()
msg = f"KEYBINDINGS: {count} key grabs added (total bindings: {len(self.keyBindings)})."
debug.printMessage(debug.LEVEL_INFO, msg, True)
def remove_key_grabs(self, reason=""):
"""Removes all grabs for this set of keybindings."""
msg = "KEYBINDINGS: Removing key grabs"
if reason:
msg += f": {reason}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
count = 0
for binding in self.keyBindings:
if binding.has_grabs():
count += 1
binding.remove_grabs()
msg = f"KEYBINDINGS: {count} key grabs removed (total bindings: {len(self.keyBindings)})."
debug.printMessage(debug.LEVEL_INFO, msg, True)
def hasKeyBinding (self, newKeyBinding, typeOfSearch="strict"): def hasKeyBinding (self, newKeyBinding, typeOfSearch="strict"):
"""Return True if keyBinding is already in self.keyBindings. """Return True if keyBinding is already in self.keyBindings.
@@ -461,6 +504,13 @@ class KeyBindings:
return [kb for kb in self.keyBindings if kb.handler == handler] return [kb for kb in self.keyBindings if kb.handler == handler]
def has_enabled_handler(self, handler):
"""Returns True if the handler is found in this set of keybindings and is enabled."""
for binding in self.keyBindings:
if binding.handler == handler and binding.is_enabled():
return True
return False
def _checkMatchingBindings(self, keyboardEvent, result): def _checkMatchingBindings(self, keyboardEvent, result):
if debug.debugLevel > debug.LEVEL_INFO: if debug.debugLevel > debug.LEVEL_INFO:
return return
@@ -487,39 +537,11 @@ class KeyBindings:
given keycode and modifiers, or None if no match exists. given keycode and modifiers, or None if no match exists.
""" """
import logging
logger = logging.getLogger(__name__)
# Check if this might be the DisplayVersion key combination
event_str = keyboardEvent.event_string if hasattr(keyboardEvent, 'event_string') else 'unknown'
if event_str.lower() == 'v':
logger.info(f"=== KeyBindings.getInputHandler: Looking for handler ===")
logger.info(f"Event string: {event_str}")
logger.info(f"Hardware code: {keyboardEvent.hw_code}")
logger.info(f"Modifiers: {keyboardEvent.modifiers}")
logger.info(f"Total keybindings to check: {len(self.keyBindings)}")
with open('/tmp/keybinding_lookup.log', 'a') as f:
f.write(f"=== Looking for 'v' key handler ===\n")
f.write(f"Event string: {event_str}\n")
f.write(f"Hardware code: {keyboardEvent.hw_code}\n")
f.write(f"Modifiers: {keyboardEvent.modifiers}\n")
f.write(f"Total keybindings: {len(self.keyBindings)}\n")
# Log all keybindings for comparison
for i, kb in enumerate(self.keyBindings):
if 'v' in kb.keysymstring.lower() or 'version' in kb.handler.description.lower():
logger.info(f"Binding {i}: keysym={kb.keysymstring}, modifiers={kb.modifiers}, mask={kb.modifier_mask}, desc={kb.handler.description}")
with open('/tmp/keybinding_lookup.log', 'a') as f:
f.write(f"Found V-related binding {i}: keysym={kb.keysymstring}, modifiers={kb.modifiers}, mask={kb.modifier_mask}, desc={kb.handler.description}\n")
matches = [] matches = []
candidates = [] candidates = []
clickCount = keyboardEvent.get_click_count() clickCount = keyboardEvent.get_click_count()
for keyBinding in self.keyBindings: for keyBinding in self.keyBindings:
if keyBinding.matches(keyboardEvent.hw_code, keyboardEvent.modifiers): if keyBinding.matches(keyboardEvent.id, keyboardEvent.hw_code, keyboardEvent.modifiers):
if event_str.lower() == 'v':
logger.info(f"MATCH found! keysym={keyBinding.keysymstring}, desc={keyBinding.handler.description}")
if (keyboardEvent.modifiers & keyBinding.modifier_mask) == keyBinding.modifiers and \ if (keyboardEvent.modifiers & keyBinding.modifier_mask) == keyBinding.modifiers and \
keyBinding.click_count == clickCount: keyBinding.click_count == clickCount:
matches.append(keyBinding) matches.append(keyBinding)
@@ -529,17 +551,8 @@ class KeyBindings:
if keyBinding.keysymstring: if keyBinding.keysymstring:
candidates.append(keyBinding) candidates.append(keyBinding)
if event_str.lower() == 'v':
logger.info(f"Exact matches: {len(matches)}")
logger.info(f"Candidates: {len(candidates)}")
with open('/tmp/keybinding_lookup.log', 'a') as f:
f.write(f"Exact matches: {len(matches)}\n")
f.write(f"Candidates: {len(candidates)}\n")
self._checkMatchingBindings(keyboardEvent, matches) self._checkMatchingBindings(keyboardEvent, matches)
if matches: if matches:
if event_str.lower() == 'v':
logger.info(f"Returning exact match handler: {matches[0].handler.description}")
return matches[0].handler return matches[0].handler
if keyboardEvent.isKeyPadKeyWithNumlockOn(): if keyboardEvent.isKeyPadKeyWithNumlockOn():
@@ -553,12 +566,8 @@ class KeyBindings:
self._checkMatchingBindings(keyboardEvent, candidates) self._checkMatchingBindings(keyboardEvent, candidates)
for candidate in candidates: for candidate in candidates:
if candidate.click_count <= clickCount: if candidate.click_count <= clickCount:
if event_str.lower() == 'v':
logger.info(f"Returning candidate handler: {candidate.handler.description}")
return candidate.handler return candidate.handler
if event_str.lower() == 'v':
logger.info("No handler found!")
return None return None
def load(self, keymap, handlers): def load(self, keymap, handlers):
+3 -1
View File
@@ -276,7 +276,9 @@ class LiveRegionManager:
utts = message['labels'] + message['content'] utts = message['labels'] + message['content']
if self.monitoring: if self.monitoring:
self._script.presentMessage(utts) # Live region content is user-generated text, not system messages.
# Use resetStyles=False to preserve the user's punctuation settings.
self._script.presentMessage(utts, resetStyles=False)
else: else:
msg = "INFO: Not presenting message because monitoring is off" msg = "INFO: Not presenting message because monitoring is off"
debug.printMessage(debug.LEVEL_INFO, msg, True) debug.printMessage(debug.LEVEL_INFO, msg, True)
+8 -7
View File
@@ -325,7 +325,6 @@ class AIAssistant(Plugin):
if not self._prefs_widgets: if not self._prefs_widgets:
return return
enabled = self._prefs_widgets["enable_check"].get_active()
provider_values = self._prefs_widgets.get("provider_values", []) provider_values = self._prefs_widgets.get("provider_values", [])
provider_index = self._prefs_widgets["provider_combo"].get_active() provider_index = self._prefs_widgets["provider_combo"].get_active()
provider = provider_values[provider_index] if 0 <= provider_index < len(provider_values) else settings.aiProvider provider = provider_values[provider_index] if 0 <= provider_index < len(provider_values) else settings.aiProvider
@@ -333,12 +332,14 @@ class AIAssistant(Plugin):
is_gemini = provider == settings.AI_PROVIDER_GEMINI is_gemini = provider == settings.AI_PROVIDER_GEMINI
is_ollama = provider == settings.AI_PROVIDER_OLLAMA is_ollama = provider == settings.AI_PROVIDER_OLLAMA
self._prefs_widgets["provider_combo"].set_sensitive(enabled) # Keep preferences editable even when the feature is disabled so users
self._prefs_widgets["api_key_entry"].set_sensitive(enabled and is_gemini) # can prepare configuration before turning AI assistant on.
self._prefs_widgets["ollama_model_entry"].set_sensitive(enabled and is_ollama) self._prefs_widgets["provider_combo"].set_sensitive(True)
self._prefs_widgets["ollama_endpoint_entry"].set_sensitive(enabled and is_ollama) self._prefs_widgets["api_key_entry"].set_sensitive(is_gemini)
self._prefs_widgets["confirmation_check"].set_sensitive(enabled) self._prefs_widgets["ollama_model_entry"].set_sensitive(is_ollama)
self._prefs_widgets["quality_combo"].set_sensitive(enabled) self._prefs_widgets["ollama_endpoint_entry"].set_sensitive(is_ollama)
self._prefs_widgets["confirmation_check"].set_sensitive(True)
self._prefs_widgets["quality_combo"].set_sensitive(True)
def refresh_settings(self): def refresh_settings(self):
"""Refresh plugin settings and reinitialize provider. Called when settings change.""" """Refresh plugin settings and reinitialize provider. Called when settings change."""
+3 -19
View File
@@ -21,10 +21,8 @@ from gi.repository import Gtk, Gdk, Pango
from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import cthulhu from cthulhu import cthulhu
from cthulhu import debug from cthulhu import debug
from cthulhu import settings_manager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
class PluginManager(Plugin): class PluginManager(Plugin):
@@ -473,25 +471,11 @@ class PluginManager(Plugin):
current_general['activePlugins'] = active_plugins current_general['activePlugins'] = active_plugins
cthulhu.cthulhuApp.settingsManager.profile = profile_name cthulhu.cthulhuApp.settingsManager.profile = profile_name
cthulhu.cthulhuApp.settingsManager._setProfileGeneral(current_general) cthulhu.cthulhuApp.settingsManager.saveProfileSettings(current_general)
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Settings saved via settings manager (profile {profile_name})", True)
pronunciations = cthulhu.cthulhuApp.settingsManager.getPronunciations(profile_name) or {}
keybindings = cthulhu.cthulhuApp.settingsManager.getKeybindings(profile_name) or {}
backend = cthulhu.cthulhuApp.settingsManager._backend
if backend:
backend.saveProfileSettings(
profile_name,
cthulhu.cthulhuApp.settingsManager.profileGeneral,
pronunciations,
keybindings
)
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Settings saved to backend (profile {profile_name})", True)
else:
debug.printMessage(debug.LEVEL_INFO, "PluginManager: No backend available for saving", True)
except Exception as save_error: except Exception as save_error:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error saving via backend: {save_error}", True) debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error saving plugin state: {save_error}", True)
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Updated active plugins: {active_plugins}", True) debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Updated active plugins: {active_plugins}", True)
@@ -1,9 +1,8 @@
name = Speech History name = Speech History
version = 1.0.0 version = 1.2.0
description = Shows a searchable history of the last 50 unique utterances spoken by Cthulhu description = Shows speech history and a live floating monitor of spoken text from Cthulhu
authors = Stormux authors = Stormux
website = https://git.stormux.org/storm/cthulhu website = https://git.stormux.org/storm/cthulhu
copyright = Copyright 2025 Stormux copyright = Copyright 2025 Stormux
builtin = false builtin = false
hidden = false hidden = false
+210 -82
View File
@@ -7,39 +7,47 @@
# License as published by the Free Software Foundation; either # License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version. # version 2.1 of the License, or (at your option) any later version.
"""Speech History plugin for Cthulhu.""" """Speech history and speech monitor plugin for Cthulhu."""
import logging import logging
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk, GLib
from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import debug from cthulhu import debug
from cthulhu import speech
from cthulhu import speech_history from cthulhu import speech_history
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SpeechHistory(Plugin): class SpeechHistory(Plugin):
"""Plugin that displays a window containing recent spoken output.""" """Plugin that provides speech history and a live speech monitor."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._activated = False self._activated = False
self._kbOpenWindow = None self._kbOpenWindow = None
self._kbToggleMonitor = None
# Speech history window state
self._window = None self._window = None
self._filterEntry = None self._filterEntry = None
self._filterText = "" self._filterText = ""
self._listStore = None self._listStore = None
self._filterModel = None self._filterModel = None
self._treeView = None self._treeView = None
self._capturePaused = False self._capturePaused = False
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Plugin initialized", True) # Live monitor window state
self._monitorWindow = None
self._monitorTextView = None
self._monitorTextBuffer = None
self._monitorEndMark = None
self._monitorLineCount = 0
self._monitorMaxLines = 500
@cthulhu_hookimpl @cthulhu_hookimpl
def activate(self, plugin=None): def activate(self, plugin=None):
@@ -47,17 +55,13 @@ class SpeechHistory(Plugin):
return return
if self._activated: if self._activated:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Already activated, skipping", True)
return True return True
try: try:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Activating plugin", True) self._register_keybindings()
self._register_keybinding()
self._activated = True self._activated = True
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Activated successfully", True)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR during activate: {e}", True)
logger.exception("Error activating SpeechHistory plugin") logger.exception("Error activating SpeechHistory plugin")
return False return False
@@ -67,46 +71,208 @@ class SpeechHistory(Plugin):
return return
try: try:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Deactivating plugin", True)
self._close_window() self._close_window()
self._disable_monitor()
self._kbOpenWindow = None self._kbOpenWindow = None
self._kbToggleMonitor = None
self._activated = False self._activated = False
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Deactivated successfully", True)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR during deactivate: {e}", True)
logger.exception("Error deactivating SpeechHistory plugin") logger.exception("Error deactivating SpeechHistory plugin")
return False return False
def _register_keybinding(self): def _register_keybindings(self):
try:
if not self.app: if not self.app:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: No app reference; cannot register keybinding", True) debug.printMessage(
debug.LEVEL_INFO,
"SpeechHistory: No app reference; cannot register keybindings",
True,
)
return return
gestureString = "kb:cthulhu+control+h" historyGesture = "kb:cthulhu+control+h"
description = "Open speech history" historyDescription = "Open speech history"
self._kbOpenWindow = self.registerGestureByString( self._kbOpenWindow = self.registerGestureByString(
self._open_window, self._open_window,
description, historyDescription,
gestureString, historyGesture,
) )
if self._kbOpenWindow: monitorGesture = "kb:cthulhu+shift+d"
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Registered keybinding {gestureString}", True) monitorDescription = "Toggle speech monitor"
else: self._kbToggleMonitor = self.registerGestureByString(
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Failed to register keybinding {gestureString}", True) self._toggle_monitor,
except Exception as e: monitorDescription,
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR registering keybinding: {e}", True) monitorGesture,
logger.exception("Error registering keybinding for SpeechHistory") )
if not self._kbOpenWindow:
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Failed to register keybinding {historyGesture}",
True,
)
if not self._kbToggleMonitor:
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Failed to register keybinding {monitorGesture}",
True,
)
# Live monitor methods
def _toggle_monitor(self, script=None, inputEvent=None):
try:
if self._monitorWindow is not None:
self._disable_monitor()
self._present_message("Speech monitor disabled.")
return True
self._enable_monitor()
self._present_message("Speech monitor enabled.")
return True
except Exception:
logger.exception("Error toggling speech monitor")
self._disable_monitor()
self._present_message("Error toggling speech monitor.")
return False
def _enable_monitor(self):
if self._monitorWindow is not None:
self._monitorWindow.show_all()
return
self._create_monitor_window()
speech.set_monitor_callbacks(writeText=self._on_spoken_text)
self._monitorWindow.show_all()
def _disable_monitor(self):
speech.set_monitor_callbacks(writeText=None)
if self._monitorWindow is not None:
self._monitorWindow.destroy()
def _create_monitor_window(self):
self._monitorWindow = Gtk.Window(title="Speech Monitor - Cthulhu")
self._monitorWindow.set_default_size(900, 320)
self._monitorWindow.set_modal(False)
self._monitorWindow.set_border_width(8)
self._monitorWindow.set_keep_above(True)
self._monitorWindow.set_accept_focus(False)
self._monitorWindow.set_focus_on_map(False)
self._monitorWindow.set_skip_taskbar_hint(True)
self._monitorWindow.set_skip_pager_hint(True)
self._monitorWindow.set_type_hint(Gdk.WindowTypeHint.UTILITY)
self._monitorWindow.connect("destroy", self._on_monitor_window_destroy)
self._monitorWindow.connect("key-press-event", self._on_monitor_window_key_press)
mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
scrolledWindow = Gtk.ScrolledWindow()
scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._monitorTextView = Gtk.TextView()
self._monitorTextView.set_editable(False)
self._monitorTextView.set_cursor_visible(False)
self._monitorTextView.set_can_focus(False)
self._monitorTextView.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
self._monitorTextView.set_accepts_tab(False)
self._monitorTextView.set_left_margin(6)
self._monitorTextView.set_right_margin(6)
self._monitorTextView.set_top_margin(6)
self._monitorTextView.set_bottom_margin(6)
textAccessible = self._monitorTextView.get_accessible()
if textAccessible:
textAccessible.set_name("Speech monitor output")
self._monitorTextBuffer = self._monitorTextView.get_buffer()
self._monitorEndMark = self._monitorTextBuffer.create_mark(
"monitorEnd",
self._monitorTextBuffer.get_end_iter(),
False,
)
self._monitorLineCount = 0
scrolledWindow.add(self._monitorTextView)
mainBox.pack_start(scrolledWindow, True, True, 0)
buttonRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
buttonRow.set_halign(Gtk.Align.END)
clearButton = Gtk.Button(label="Clear")
closeButton = Gtk.Button(label="Close")
clearButton.connect("clicked", self._on_monitor_clear_clicked)
closeButton.connect("clicked", self._on_monitor_close_clicked)
buttonRow.pack_start(clearButton, False, False, 0)
buttonRow.pack_start(closeButton, False, False, 0)
mainBox.pack_start(buttonRow, False, False, 0)
self._monitorWindow.add(mainBox)
def _on_monitor_close_clicked(self, button):
self._disable_monitor()
def _on_monitor_clear_clicked(self, button):
if self._monitorTextBuffer is None:
return
self._monitorTextBuffer.set_text("")
self._monitorLineCount = 0
def _on_monitor_window_destroy(self, widget):
speech.set_monitor_callbacks(writeText=None)
self._monitorWindow = None
self._monitorTextView = None
self._monitorTextBuffer = None
self._monitorEndMark = None
self._monitorLineCount = 0
def _on_monitor_window_key_press(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self._disable_monitor()
return True
return False
def _on_spoken_text(self, text):
if not text or not isinstance(text, str):
return
GLib.idle_add(self._append_text_on_main_thread, text)
def _append_text_on_main_thread(self, text):
if self._monitorTextBuffer is None:
return False
self._monitorTextBuffer.insert(self._monitorTextBuffer.get_end_iter(), f"{text}\n")
self._monitorLineCount += 1
if self._monitorLineCount > self._monitorMaxLines:
excess = self._monitorLineCount - self._monitorMaxLines
startIter = self._monitorTextBuffer.get_start_iter()
cutIter = self._monitorTextBuffer.get_iter_at_line(excess)
self._monitorTextBuffer.delete(startIter, cutIter)
self._monitorLineCount = self._monitorMaxLines
if self._monitorEndMark is not None and self._monitorTextView is not None:
self._monitorTextBuffer.move_mark(
self._monitorEndMark,
self._monitorTextBuffer.get_end_iter(),
)
self._monitorTextView.scroll_mark_onscreen(self._monitorEndMark)
return False
# Speech history methods
def _open_window(self, script=None, inputEvent=None): def _open_window(self, script=None, inputEvent=None):
try: try:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Open window requested", True)
if self._window: if self._window:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window already open; presenting", True)
self._window.present() self._window.present()
return True return True
@@ -120,10 +286,8 @@ class SpeechHistory(Plugin):
elif self._filterEntry: elif self._filterEntry:
self._filterEntry.grab_focus() self._filterEntry.grab_focus()
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window shown", True)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR opening window: {e}", True)
logger.exception("Error opening SpeechHistory window") logger.exception("Error opening SpeechHistory window")
self._resume_capture() self._resume_capture()
return False return False
@@ -141,7 +305,6 @@ class SpeechHistory(Plugin):
self._filterText = "" self._filterText = ""
# Filter row
filterRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) filterRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
filterLabel = Gtk.Label(label="_Filter:") filterLabel = Gtk.Label(label="_Filter:")
filterLabel.set_use_underline(True) filterLabel.set_use_underline(True)
@@ -156,7 +319,6 @@ class SpeechHistory(Plugin):
filterRow.pack_start(self._filterEntry, True, True, 0) filterRow.pack_start(self._filterEntry, True, True, 0)
mainBox.pack_start(filterRow, False, False, 0) mainBox.pack_start(filterRow, False, False, 0)
# List
self._listStore = Gtk.ListStore(str) self._listStore = Gtk.ListStore(str)
self._filterModel = self._listStore.filter_new() self._filterModel = self._listStore.filter_new()
self._filterModel.set_visible_func(self._filter_visible_func) self._filterModel.set_visible_func(self._filter_visible_func)
@@ -169,7 +331,7 @@ class SpeechHistory(Plugin):
textRenderer = Gtk.CellRendererText() textRenderer = Gtk.CellRendererText()
textRenderer.set_property("wrap-width", 640) textRenderer.set_property("wrap-width", 640)
textRenderer.set_property("wrap-mode", 2) # Pango.WrapMode.WORD_CHAR textRenderer.set_property("wrap-mode", 2)
textColumn = Gtk.TreeViewColumn("Spoken Text", textRenderer, text=0) textColumn = Gtk.TreeViewColumn("Spoken Text", textRenderer, text=0)
textColumn.set_resizable(True) textColumn.set_resizable(True)
textColumn.set_expand(True) textColumn.set_expand(True)
@@ -180,7 +342,6 @@ class SpeechHistory(Plugin):
scrolled.add(self._treeView) scrolled.add(self._treeView)
mainBox.pack_start(scrolled, True, True, 0) mainBox.pack_start(scrolled, True, True, 0)
# Buttons
buttonRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) buttonRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
buttonRow.set_halign(Gtk.Align.END) buttonRow.set_halign(Gtk.Align.END)
@@ -201,16 +362,12 @@ class SpeechHistory(Plugin):
self._refresh_list(selectFirst=True) self._refresh_list(selectFirst=True)
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window created", True)
def _on_filter_changed(self, entry): def _on_filter_changed(self, entry):
try: try:
self._filterText = entry.get_text() or "" self._filterText = entry.get_text() or ""
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Filter changed '{self._filterText}'", True)
if self._filterModel: if self._filterModel:
self._filterModel.refilter() self._filterModel.refilter()
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR filtering: {e}", True)
logger.exception("Error updating speech history filter") logger.exception("Error updating speech history filter")
def _filter_visible_func(self, model, treeIter, data=None): def _filter_visible_func(self, model, treeIter, data=None):
@@ -221,8 +378,7 @@ class SpeechHistory(Plugin):
spokenText = model[treeIter][0] or "" spokenText = model[treeIter][0] or ""
return spokenText.lower().startswith(filterText) return spokenText.lower().startswith(filterText)
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR in filter func: {e}", True)
return True return True
def _refresh_list(self, selectFirst=False): def _refresh_list(self, selectFirst=False):
@@ -230,31 +386,19 @@ class SpeechHistory(Plugin):
if not self._listStore: if not self._listStore:
return return
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Refreshing list", True)
self._listStore.clear() self._listStore.clear()
items = speech_history.get_items() items = speech_history.get_items()
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Retrieved {len(items)} items (paused={speech_history.is_capture_paused()})",
True,
)
for item in items: for item in items:
self._listStore.append([item]) self._listStore.append([item])
if self._filterModel: if self._filterModel:
self._filterModel.refilter() self._filterModel.refilter()
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Filtered items visible={len(self._filterModel)} filter='{self._filterText}'",
True,
)
if selectFirst and self._treeView and len(self._filterModel) > 0: if selectFirst and self._treeView and len(self._filterModel) > 0:
selection = self._treeView.get_selection() selection = self._treeView.get_selection()
selection.select_path(Gtk.TreePath.new_first()) selection.select_path(Gtk.TreePath.new_first())
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR refreshing list: {e}", True)
logger.exception("Error refreshing speech history list") logger.exception("Error refreshing speech history list")
def _get_selected_text(self): def _get_selected_text(self):
@@ -268,8 +412,7 @@ class SpeechHistory(Plugin):
return None return None
return model[treeIter][0] return model[treeIter][0]
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR getting selection: {e}", True)
logger.exception("Error getting selected speech history item") logger.exception("Error getting selected speech history item")
return None return None
@@ -284,11 +427,8 @@ class SpeechHistory(Plugin):
clipboard.set_text(selectedText, -1) clipboard.set_text(selectedText, -1)
clipboard.store() clipboard.store()
preview = selectedText[:60] + ("..." if len(selectedText) > 60 else "")
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Copied to clipboard '{preview}'", True)
self._present_message("Copied to clipboard.") self._present_message("Copied to clipboard.")
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR copying: {e}", True)
logger.exception("Error copying speech history item to clipboard") logger.exception("Error copying speech history item to clipboard")
self._present_message("Error copying to clipboard.") self._present_message("Error copying to clipboard.")
@@ -300,33 +440,27 @@ class SpeechHistory(Plugin):
return return
removed = speech_history.remove(selectedText) removed = speech_history.remove(selectedText)
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Remove requested removed={removed}", True)
if removed: if removed:
self._refresh_list(selectFirst=True) self._refresh_list(selectFirst=True)
self._present_message("Removed from history.") self._present_message("Removed from history.")
else: else:
self._present_message("Item not found in history.") self._present_message("Item not found in history.")
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR removing: {e}", True)
logger.exception("Error removing speech history item") logger.exception("Error removing speech history item")
self._present_message("Error removing item from history.") self._present_message("Error removing item from history.")
def _on_close_clicked(self, button): def _on_close_clicked(self, button):
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Close button clicked", True)
self._close_window() self._close_window()
def _close_window(self): def _close_window(self):
try: try:
if self._window: if self._window:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Closing window", True)
self._window.destroy() self._window.destroy()
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR closing window: {e}", True)
logger.exception("Error closing SpeechHistory window") logger.exception("Error closing SpeechHistory window")
self._resume_capture() self._resume_capture()
def _on_window_destroy(self, widget): def _on_window_destroy(self, widget):
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window destroyed", True)
self._window = None self._window = None
self._filterEntry = None self._filterEntry = None
self._listStore = None self._listStore = None
@@ -338,14 +472,12 @@ class SpeechHistory(Plugin):
def _on_window_key_press(self, widget, event): def _on_window_key_press(self, widget, event):
try: try:
if event.keyval == Gdk.KEY_Escape: if event.keyval == Gdk.KEY_Escape:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Escape pressed; closing window", True)
self._close_window() self._close_window()
return True return True
if not self._filterEntry or self._filterEntry.is_focus(): if not self._filterEntry or self._filterEntry.is_focus():
return False return False
# If user starts typing anywhere, move focus to filter and update it.
if event.keyval == Gdk.KEY_BackSpace: if event.keyval == Gdk.KEY_BackSpace:
currentText = self._filterEntry.get_text() or "" currentText = self._filterEntry.get_text() or ""
if currentText: if currentText:
@@ -376,8 +508,7 @@ class SpeechHistory(Plugin):
self._filterEntry.set_text(currentText + charTyped) self._filterEntry.set_text(currentText + charTyped)
self._filterEntry.set_position(-1) self._filterEntry.set_position(-1)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR in key handler: {e}", True)
logger.exception("Error handling key press in SpeechHistory window") logger.exception("Error handling key press in SpeechHistory window")
return False return False
@@ -385,7 +516,6 @@ class SpeechHistory(Plugin):
if self._capturePaused: if self._capturePaused:
return return
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Pausing capture while window is open", True)
speech_history.pause_capture(reason="SpeechHistory window open") speech_history.pause_capture(reason="SpeechHistory window open")
self._capturePaused = True self._capturePaused = True
@@ -393,7 +523,6 @@ class SpeechHistory(Plugin):
if not self._capturePaused: if not self._capturePaused:
return return
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Resuming capture (window closed)", True)
speech_history.resume_capture(reason="SpeechHistory window closed") speech_history.resume_capture(reason="SpeechHistory window closed")
self._capturePaused = False self._capturePaused = False
@@ -408,6 +537,5 @@ class SpeechHistory(Plugin):
state.activeScript.presentMessage(message, resetStyles=False) state.activeScript.presentMessage(message, resetStyles=False)
else: else:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: {message}", True) debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: {message}", True)
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR presenting message: {e}", True)
logger.exception("Error presenting message from SpeechHistory") logger.exception("Error presenting message from SpeechHistory")

Some files were not shown because too many files have changed in this diff Show More