24 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
Storm Dragon f873fcee11 Use toml instead of json for settings. 2026-02-13 11:13:38 -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 13976b7235 Look for missing sound files if not found in 1 directory e.g. if not found in ~/.local look in system themes. 2026-02-07 22:40:21 -05:00
119 changed files with 2086 additions and 698 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;
+3 -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.26 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"
@@ -27,6 +27,7 @@ depends=(
# Plugin system and D-Bus remote control # Plugin system and D-Bus remote control
python-pluggy python-pluggy
python-tomlkit
python-dasbus python-dasbus
# AI Assistant dependencies (for screenshots, HTTP requests, and actions) # AI Assistant dependencies (for screenshots, HTTP requests, and actions)
@@ -83,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=(
@@ -91,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
+46
View File
@@ -0,0 +1,46 @@
# Cthulhu Logging Guidelines
This document defines the preferred format for debug logging in Cthulhu.
The goal is to keep logs consistent, searchable, and easy to scan.
## Helpers
Use the helpers in `cthulhu.debug` for new logs:
- `debug.print_log(level, prefix, message, reason=None, timestamp=False, stack=False)`
- `debug.print_log_tokens(level, prefix, tokens, reason=None, timestamp=False, stack=False)`
These helpers ensure the prefix and optional reason tag are formatted consistently.
Timestamps are appended at the end of the message when enabled.
## Prefixes
Use short, uppercase prefixes that identify the subsystem:
- `EVENT MANAGER`
- `FOCUS MANAGER`
- `INPUT EVENT`
- `SCRIPT MANAGER`
- `WEB`
## Messages
- Keep messages short and action-focused.
- Do not include the prefix in the message.
- Prefer consistent verbs (e.g., "Not using …", "Setting …", "Ignoring …").
## Reason Tags
Use reason tags to explain decisions or early exits.
- Lowercase with hyphens (e.g., `focus-mode`, `no-active-script`).
- Use a short phrase rather than a full sentence.
- If a human-readable reason is already available, it can be used directly.
## Examples
```text
WEB: Not using caret navigation (reason=disabled)
FOCUS MANAGER: Setting locus of focus to existing locus of focus (reason=no-change)
SCRIPT MANAGER: Setting active script to [script] (reason=focus: active-window)
```
+1 -1
View File
@@ -290,7 +290,7 @@ toggle the reading of tables, either by single cell or whole row.
.B Cthulhu .B Cthulhu
user preferences directory user preferences directory
.TP .TP
.BI ~/.local/share/cthulhu/user-settings.conf .BI ~/.local/share/cthulhu/user-settings.toml
.B Cthulhu .B Cthulhu
user preferences configuration file. user preferences configuration file.
.TP .TP
+1 -11
View File
@@ -1,5 +1,5 @@
project('cthulhu', project('cthulhu',
version: '2026.01.26-master', 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 "螢幕;閱讀器;無障礙;語音;點字;"
+1
View File
@@ -12,6 +12,7 @@ license = { text = "LGPL-2.1-or-later" }
dependencies = [ dependencies = [
"pygobject>=3.18", "pygobject>=3.18",
"pluggy", "pluggy",
"tomlkit",
"brlapi; extra == 'braille'", "brlapi; extra == 'braille'",
"python-speechd; extra == 'speech'", "python-speechd; extra == 'speech'",
"piper-tts; extra == 'piper'", "piper-tts; extra == 'piper'",
+1 -1
View File
@@ -1,6 +1,6 @@
backends_python_sources = files([ backends_python_sources = files([
'__init__.py', '__init__.py',
'json_backend.py', 'toml_backend.py',
]) ])
python3.install_sources( python3.install_sources(
@@ -23,7 +23,7 @@
# 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
"""JSON backend for Cthulhu settings""" """TOML backend for Cthulhu settings"""
__id__ = "$Id$" __id__ = "$Id$"
__version__ = "$Revision$" __version__ = "$Revision$"
@@ -31,20 +31,22 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2010-2011 Consorcio Fernando de los Rios." __copyright__ = "Copyright (c) 2010-2011 Consorcio Fernando de los Rios."
__license__ = "LGPL" __license__ = "LGPL"
from json import load, dump
import os import os
from tomlkit import parse, dumps, document
from cthulhu import settings, acss from cthulhu import settings, acss
class Backend: class Backend:
def __init__(self, prefsDir): def __init__(self, prefsDir):
""" Initialize the JSON Backend. """ Initialize the TOML Backend.
""" """
self.general = {} self.general = {}
self.pronunciations = {} self.pronunciations = {}
self.keybindings = {} self.keybindings = {}
self.profiles = {} self.profiles = {}
self.settingsFile = os.path.join(prefsDir, "user-settings.conf") self.settingsFile = os.path.join(prefsDir, "user-settings.toml")
self.appPrefsDir = os.path.join(prefsDir, "app-settings") self.appPrefsDir = os.path.join(prefsDir, "app-settings")
self._defaultProfiles = {'default': { 'profile': settings.profile, self._defaultProfiles = {'default': { 'profile': settings.profile,
@@ -53,46 +55,105 @@ class Backend:
} }
} }
def _stripNone(self, value):
if isinstance(value, dict):
cleaned = {}
for key, item in value.items():
if item is None:
continue
cleanedItem = self._stripNone(item)
if cleanedItem is None:
continue
cleaned[key] = cleanedItem
return cleaned
if isinstance(value, list):
cleanedList = []
for item in value:
if item is None:
continue
cleanedList.append(self._stripNone(item))
return cleanedList
return value
def _readDocument(self, fileName):
if os.path.exists(fileName):
with open(fileName, 'r', encoding='utf-8') as settingsFile:
return parse(settingsFile.read())
return document()
def _writeDocument(self, fileName, prefsDoc):
with open(fileName, 'w', encoding='utf-8') as settingsFile:
settingsFile.write(dumps(prefsDoc))
def _updateTable(self, targetTable, newValues, preserveMissingKeys=None):
if not isinstance(newValues, dict):
return
preserveMissingKeys = set(preserveMissingKeys or [])
for key in list(targetTable.keys()):
if key not in newValues:
if key in preserveMissingKeys:
continue
del targetTable[key]
continue
newValue = newValues[key]
existingValue = targetTable.get(key)
if isinstance(newValue, dict) and isinstance(existingValue, dict):
self._updateTable(existingValue, newValue)
continue
if existingValue != newValue:
targetTable[key] = newValue
for key, newValue in newValues.items():
if key not in targetTable:
targetTable[key] = newValue
def saveDefaultSettings(self, general, pronunciations, keybindings): def saveDefaultSettings(self, general, pronunciations, keybindings):
""" Save default settings for all the properties from """ Save default settings for all the properties from
cthulhu.settings. """ cthulhu.settings. """
prefs = {'general': general, prefs = {'general': self._stripNone(general),
'profiles': self._defaultProfiles, 'profiles': self._stripNone(self._defaultProfiles),
'pronunciations': pronunciations, 'pronunciations': self._stripNone(pronunciations),
'keybindings': keybindings} 'keybindings': self._stripNone(keybindings)}
self.general = general self.general = general
self.profiles = self._defaultProfiles self.profiles = self._defaultProfiles
self.pronunciations = pronunciations self.pronunciations = pronunciations
self.keybindings = keybindings self.keybindings = keybindings
settingsFile = open(self.settingsFile, 'w') prefsDoc = document()
dump(prefs, settingsFile, indent=4) prefsDoc['general'] = prefs['general']
settingsFile.close() prefsDoc['profiles'] = prefs['profiles']
prefsDoc['pronunciations'] = prefs['pronunciations']
prefsDoc['keybindings'] = prefs['keybindings']
self._writeDocument(self.settingsFile, prefsDoc)
def getAppSettings(self, appName): def getAppSettings(self, appName):
fileName = os.path.join(self.appPrefsDir, f"{appName}.conf") fileName = os.path.join(self.appPrefsDir, f"{appName}.toml")
if os.path.exists(fileName): return self._readDocument(fileName)
settingsFile = open(fileName, 'r')
prefs = load(settingsFile)
settingsFile.close()
else:
prefs = {}
return prefs
def saveAppSettings(self, appName, profile, general, pronunciations, keybindings): def saveAppSettings(self, appName, profile, general, pronunciations, keybindings):
prefs = self.getAppSettings(appName) prefsDoc = self.getAppSettings(appName)
profiles = prefs.get('profiles', {}) profiles = prefsDoc.get('profiles')
profiles[profile] = {'general': general, if profiles is None or not isinstance(profiles, dict):
'pronunciations': pronunciations, prefsDoc['profiles'] = {}
'keybindings': keybindings} profiles = prefsDoc['profiles']
prefs['profiles'] = profiles
fileName = os.path.join(self.appPrefsDir, f"{appName}.conf") profileTable = profiles.get(profile)
settingsFile = open(fileName, 'w') if profileTable is None or not isinstance(profileTable, dict):
dump(prefs, settingsFile, indent=4) profiles[profile] = {}
settingsFile.close() profileTable = profiles[profile]
self._updateTable(profileTable, {
'general': self._stripNone(general),
'pronunciations': self._stripNone(pronunciations),
'keybindings': self._stripNone(keybindings),
})
fileName = os.path.join(self.appPrefsDir, f"{appName}.toml")
self._writeDocument(fileName, prefsDoc)
def saveProfileSettings(self, profile, general, def saveProfileSettings(self, profile, general,
pronunciations, keybindings): pronunciations, keybindings):
@@ -103,25 +164,37 @@ class Backend:
general['pronunciations'] = pronunciations general['pronunciations'] = pronunciations
general['keybindings'] = keybindings general['keybindings'] = keybindings
general = self._stripNone(general)
with open(self.settingsFile, 'r+') as settingsFile: prefsDoc = self._readDocument(self.settingsFile)
prefs = load(settingsFile) profiles = prefsDoc.get('profiles')
prefs['profiles'][profile] = general if profiles is None or not isinstance(profiles, dict):
settingsFile.seek(0) prefsDoc['profiles'] = {}
settingsFile.truncate() profiles = prefsDoc['profiles']
dump(prefs, settingsFile, indent=4)
profileTable = profiles.get(profile)
if profileTable is None or not isinstance(profileTable, dict):
profiles[profile] = {}
profileTable = profiles[profile]
# Keep plugin persistence keys when callers provide partial updates.
self._updateTable(
profileTable,
general,
preserveMissingKeys={"activePlugins", "pluginSources"},
)
self._writeDocument(self.settingsFile, prefsDoc)
def _getSettings(self): def _getSettings(self):
""" Load from config file all settings """ """ Load from config file all settings """
settingsFile = open(self.settingsFile) prefsDoc = self._readDocument(self.settingsFile)
try: try:
prefs = load(settingsFile) self.general = dict(prefsDoc.get('general', {}))
except ValueError: self.pronunciations = dict(prefsDoc.get('pronunciations', {}))
self.keybindings = dict(prefsDoc.get('keybindings', {}))
self.profiles = dict(prefsDoc.get('profiles', {}))
except Exception:
return return
self.general = prefs['general'].copy()
self.pronunciations = prefs['pronunciations']
self.keybindings = prefs['keybindings']
self.profiles = prefs['profiles'].copy()
def _migrateSettings(self, settingsDict): def _migrateSettings(self, settingsDict):
"""Migrate old setting names to new ones.""" """Migrate old setting names to new ones."""
@@ -149,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:
@@ -165,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:
@@ -201,12 +311,18 @@ class Backend:
def _setProfileKey(self, key, value): def _setProfileKey(self, key, value):
self.general[key] = value self.general[key] = value
with open(self.settingsFile, 'r+') as settingsFile: prefsDoc = self._readDocument(self.settingsFile)
prefs = load(settingsFile) general = prefsDoc.get('general')
prefs['general'][key] = value if general is None or not isinstance(general, dict):
settingsFile.seek(0) prefsDoc['general'] = {}
settingsFile.truncate() general = prefsDoc['general']
dump(prefs, settingsFile, indent=4)
if value is None:
if key in general:
del general[key]
else:
general[key] = value
self._writeDocument(self.settingsFile, prefsDoc)
def setFirstStart(self, value=False): def setFirstStart(self, value=False):
"""Set firstStart. This user-configurable setting is primarily """Set firstStart. This user-configurable setting is primarily
@@ -238,10 +354,8 @@ class Backend:
if profile in self.profiles: if profile in self.profiles:
removeProfileFrom(self.profiles) removeProfileFrom(self.profiles)
with open(self.settingsFile, 'r+') as settingsFile: prefsDoc = self._readDocument(self.settingsFile)
prefs = load(settingsFile) profiles = prefsDoc.get('profiles')
if profile in prefs['profiles']: if isinstance(profiles, dict) and profile in profiles:
removeProfileFrom(prefs['profiles']) removeProfileFrom(profiles)
settingsFile.seek(0) self._writeDocument(self.settingsFile, prefsDoc)
settingsFile.truncate()
dump(prefs, settingsFile, indent=4)
+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>
+1 -1
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.26" version = "2026.03.02"
codeName = "master" codeName = "master"
+499 -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
@@ -180,7 +193,10 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
self.savedGain = None self.savedGain = None
self.savedPitch = None self.savedPitch = None
self.savedRate = None self.savedRate = None
self.soundThemeCombo = 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
@@ -383,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 = \
@@ -1269,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
@@ -1293,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.
@@ -1351,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
@@ -1659,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
@@ -2392,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.
# #
@@ -2551,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)
@@ -2671,6 +2967,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
# Get widget references # Get widget references
self.soundThemeCombo = self.get_widget("soundThemeCombo") self.soundThemeCombo = self.get_widget("soundThemeCombo")
self.roleSoundPresentationCombo = self.get_widget("roleSoundPresentationCombo") self.roleSoundPresentationCombo = self.get_widget("roleSoundPresentationCombo")
self.soundThemeCombo.set_can_focus(False)
self.roleSoundPresentationCombo.set_can_focus(False)
# Populate sound theme combo box # Populate sound theme combo box
themeManager = sound_theme_manager.getManager() themeManager = sound_theme_manager.getManager()
@@ -2728,6 +3026,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
value = self._roleSoundPresentationChoices[activeIndex][0] value = self._roleSoundPresentationChoices[activeIndex][0]
self.prefsDict["roleSoundPresentation"] = value self.prefsDict["roleSoundPresentation"] = value
def _updateCthulhuModifier(self): def _updateCthulhuModifier(self):
combobox = self.get_widget("cthulhuModifierComboBox") combobox = self.get_widget("cthulhuModifierComboBox")
keystring = ", ".join(self.prefsDict["cthulhuModifierKeys"]) keystring = ", ".join(self.prefsDict["cthulhuModifierKeys"])
@@ -2893,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.
@@ -3189,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
@@ -3220,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
@@ -3252,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
@@ -3311,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)
@@ -3388,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.
@@ -3405,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
@@ -4207,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.
@@ -4239,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()
@@ -4501,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)

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