Compare commits
4 Commits
49f0d3c612
...
testing
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e97c1ce33 | |||
| 394d814ad0 | |||
| 006612c1da | |||
| 0576b6f79f |
@@ -1,23 +1,24 @@
|
||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||
# shellcheck shell=bash disable=SC2034,SC2154,SC2164
|
||||
|
||||
pkgname=cthulhu-git
|
||||
_pkgname=cthulhu
|
||||
pkgver=2026.05.25.r407.gc39f231
|
||||
pkgrel=1
|
||||
pkgrel=2
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
arch=(any)
|
||||
license=(LGPL)
|
||||
provides=("${_pkgname}")
|
||||
license=(LGPL-2.1-or-later)
|
||||
provides=("${_pkgname}=${pkgver}")
|
||||
conflicts=("${_pkgname}")
|
||||
depends=(
|
||||
# Core AT-SPI accessibility
|
||||
at-spi2-core
|
||||
glib2
|
||||
gobject-introspection-runtime
|
||||
python-gobject
|
||||
python-cairo
|
||||
gtk3
|
||||
python-pywayland
|
||||
# Audio and speech
|
||||
speech-dispatcher
|
||||
gstreamer
|
||||
@@ -25,75 +26,51 @@ depends=(
|
||||
gst-plugins-good # pulsesink, more decoders
|
||||
|
||||
# Braille support
|
||||
brltty
|
||||
python-brltty
|
||||
liblouis
|
||||
|
||||
# Plugin system and D-Bus remote control
|
||||
python-pluggy
|
||||
python-tomlkit
|
||||
python-dasbus
|
||||
|
||||
# AI Assistant dependencies (for screenshots, HTTP requests, and actions)
|
||||
python-requests
|
||||
python-pyautogui
|
||||
|
||||
python-msgpack
|
||||
python-tornado
|
||||
python-xlib
|
||||
|
||||
# Desktop integration
|
||||
hicolor-icon-theme
|
||||
libwnck3
|
||||
pango
|
||||
|
||||
|
||||
# System utilities
|
||||
python
|
||||
python-setproctitle
|
||||
socat # for self-voicing feature
|
||||
xorg-xkbcomp
|
||||
xorg-xmodmap
|
||||
)
|
||||
optdepends=(
|
||||
'espeak-ng: Alternative TTS engine'
|
||||
'festival: Alternative TTS engine'
|
||||
'flite: Lightweight TTS engine'
|
||||
'espeak: Legacy TTS engine'
|
||||
'git: Install and update third-party plugin sources'
|
||||
'python-pywayland: Wayland shared workspace backend'
|
||||
'piper-tts: Piper speech backend'
|
||||
'socat: Send text to the Self Voice socket from the command line'
|
||||
|
||||
# AI Assistant providers (optional)
|
||||
'python-requests: AI Assistant plugin and HTTP provider support'
|
||||
'python-pyautogui: AI Assistant mouse and keyboard actions'
|
||||
'claude-code: Claude AI provider support'
|
||||
'openai-codex: ChatGPT AI provider support'
|
||||
'openai-codex: OpenAI Codex provider support'
|
||||
'gemini-cli: Gemini AI provider support'
|
||||
'ollama: Local AI model support'
|
||||
|
||||
# OCR plugin dependencies (optional)
|
||||
'python-pillow: Image processing for OCR and AI Assistant'
|
||||
'python-pillow: Image processing for OCR'
|
||||
'python-pytesseract: Python wrapper for Tesseract OCR engine'
|
||||
'python-pdf2image: PDF to image conversion for OCR'
|
||||
'python-scipy: Scientific computing for OCR color analysis'
|
||||
'python-webcolors: Color name lookup for OCR text decoration'
|
||||
'tesseract: OCR engine for text recognition'
|
||||
'tesseract-data-eng: English language data for Tesseract'
|
||||
|
||||
# nvda2cthulhu plugin (optional)
|
||||
'python-msgpack: Msgpack decoding for nvda2cthulhu'
|
||||
'python-tornado: WebSocket server for nvda2cthulhu'
|
||||
|
||||
# Window Title Reader plugin (optional)
|
||||
'python-xlib: X11 access for Wine window title plugin'
|
||||
)
|
||||
makedepends=(
|
||||
git
|
||||
meson
|
||||
ninja
|
||||
python-build
|
||||
python-installer
|
||||
python-wheel
|
||||
)
|
||||
install=cthulhu.install
|
||||
source=(
|
||||
"${_pkgname}::git+https://git.stormux.org/storm/${_pkgname}.git#branch=master"
|
||||
"cthulhu.install"
|
||||
)
|
||||
b2sums=(
|
||||
'SKIP'
|
||||
'SKIP'
|
||||
)
|
||||
source=("${_pkgname}::git+https://git.stormux.org/storm/${_pkgname}.git#branch=master")
|
||||
sha512sums=('SKIP')
|
||||
|
||||
pkgver() {
|
||||
cd "${_pkgname}"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
post_install() {
|
||||
gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
|
||||
}
|
||||
|
||||
post_upgrade() {
|
||||
post_install
|
||||
}
|
||||
|
||||
post_remove() {
|
||||
gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
|
||||
}
|
||||
@@ -1,96 +1,73 @@
|
||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||
# shellcheck shell=bash disable=SC2034,SC2154,SC2164
|
||||
|
||||
pkgname=cthulhu
|
||||
pkgver=2026.05.25
|
||||
pkgrel=1
|
||||
pkgrel=2
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
arch=(any)
|
||||
license=(LGPL)
|
||||
license=(LGPL-2.1-or-later)
|
||||
depends=(
|
||||
# Core AT-SPI accessibility
|
||||
at-spi2-core
|
||||
glib2
|
||||
gobject-introspection-runtime
|
||||
python-gobject
|
||||
python-cairo
|
||||
gtk3
|
||||
python-pywayland
|
||||
# Audio and speech
|
||||
speech-dispatcher
|
||||
gstreamer
|
||||
gst-plugins-base # playbin, audiotestsrc, basic decoders
|
||||
gst-plugins-good # pulsesink, more decoders
|
||||
|
||||
|
||||
# Braille support
|
||||
brltty
|
||||
python-brltty
|
||||
liblouis
|
||||
|
||||
|
||||
# Plugin system and D-Bus remote control
|
||||
python-pluggy
|
||||
python-tomlkit
|
||||
python-dasbus
|
||||
|
||||
# AI Assistant dependencies (for screenshots, HTTP requests, and actions)
|
||||
python-requests
|
||||
python-pyautogui
|
||||
|
||||
python-msgpack
|
||||
python-tornado
|
||||
python-xlib
|
||||
|
||||
# Desktop integration
|
||||
hicolor-icon-theme
|
||||
libwnck3
|
||||
pango
|
||||
|
||||
|
||||
# System utilities
|
||||
python
|
||||
python-setproctitle
|
||||
socat # for self-voicing feature
|
||||
xorg-xkbcomp
|
||||
xorg-xmodmap
|
||||
)
|
||||
optdepends=(
|
||||
'espeak-ng: Alternative TTS engine'
|
||||
'festival: Alternative TTS engine'
|
||||
'flite: Lightweight TTS engine'
|
||||
'espeak: Legacy TTS engine'
|
||||
|
||||
'git: Install and update third-party plugin sources'
|
||||
'python-pywayland: Wayland shared workspace backend'
|
||||
'piper-tts: Piper speech backend'
|
||||
'socat: Send text to the Self Voice socket from the command line'
|
||||
|
||||
# AI Assistant providers (optional)
|
||||
'python-requests: AI Assistant plugin and HTTP provider support'
|
||||
'python-pyautogui: AI Assistant mouse and keyboard actions'
|
||||
'claude-code: Claude AI provider support'
|
||||
'openai-codex: ChatGPT AI provider support'
|
||||
'openai-codex: OpenAI Codex provider support'
|
||||
'gemini-cli: Gemini AI provider support'
|
||||
'ollama: Local AI model support'
|
||||
|
||||
# OCR plugin dependencies (optional)
|
||||
'python-pillow: Image processing for OCR and AI Assistant'
|
||||
'python-pytesseract: Python wrapper for Tesseract OCR engine'
|
||||
'python-pdf2image: PDF to image conversion for OCR'
|
||||
'python-scipy: Scientific computing for OCR color analysis'
|
||||
'python-webcolors: Color name lookup for OCR text decoration'
|
||||
'tesseract: OCR engine for text recognition'
|
||||
'tesseract-data-eng: English language data for Tesseract'
|
||||
|
||||
# nvda2cthulhu plugin (optional)
|
||||
'python-msgpack: Msgpack decoding for nvda2cthulhu'
|
||||
'python-tornado: WebSocket server for nvda2cthulhu'
|
||||
|
||||
# Window Title Reader plugin (optional)
|
||||
'python-xlib: X11 access for Wine window title plugin'
|
||||
# OCR plugin dependencies (optional)
|
||||
'python-pillow: Image processing for OCR'
|
||||
'python-pytesseract: Python wrapper for Tesseract OCR engine'
|
||||
'tesseract: OCR engine for text recognition'
|
||||
)
|
||||
makedepends=(
|
||||
git
|
||||
meson
|
||||
ninja
|
||||
python-build
|
||||
python-installer
|
||||
python-wheel
|
||||
)
|
||||
install=cthulhu.install
|
||||
source=(
|
||||
"git+https://git.stormux.org/storm/cthulhu.git#tag=${pkgver}"
|
||||
"cthulhu.install"
|
||||
)
|
||||
b2sums=(
|
||||
'SKIP'
|
||||
'SKIP'
|
||||
)
|
||||
source=("git+https://git.stormux.org/storm/cthulhu.git#tag=${pkgver}")
|
||||
sha512sums=('SKIP')
|
||||
|
||||
build() {
|
||||
cd cthulhu
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
post_install() {
|
||||
gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
|
||||
}
|
||||
|
||||
post_upgrade() {
|
||||
post_install
|
||||
}
|
||||
|
||||
post_remove() {
|
||||
gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
|
||||
}
|
||||
@@ -56,12 +56,8 @@ optional_modules = {
|
||||
'louis': 'contracted braille',
|
||||
'speechd': 'speech output',
|
||||
'dasbus': 'D-Bus remote controller',
|
||||
'psutil': 'system information commands',
|
||||
'pywayland': 'Wayland shared workspace backend',
|
||||
'gi.repository.Wnck': 'mouse review',
|
||||
'pdf2image': 'PDF processing for OCR',
|
||||
'scipy': 'Scientific computing for OCR analysis',
|
||||
'webcolors': 'Color name resolution for OCR',
|
||||
}
|
||||
|
||||
summary = {}
|
||||
|
||||
@@ -254,6 +254,8 @@ class AXUtilitiesEvent:
|
||||
reason = TextEventReason.SELECT_ALL
|
||||
elif mgr.last_event_was_primary_click_or_release():
|
||||
reason = TextEventReason.MOUSE_PRIMARY_BUTTON
|
||||
elif mgr.last_event_was_tab_navigation():
|
||||
reason = TextEventReason.FOCUS_CHANGE
|
||||
elif AXUtilitiesState.is_editable(obj) or AXUtilitiesRole.is_terminal(obj):
|
||||
if mgr.last_event_was_backspace():
|
||||
reason = TextEventReason.BACKSPACE
|
||||
@@ -273,8 +275,6 @@ class AXUtilitiesEvent:
|
||||
reason = TextEventReason.UNSPECIFIED_COMMAND
|
||||
elif mgr.last_event_was_printable_key():
|
||||
reason = TextEventReason.TYPING
|
||||
elif mgr.last_event_was_tab_navigation():
|
||||
reason = TextEventReason.FOCUS_CHANGE
|
||||
elif AXObject.find_ancestor(obj, AXUtilitiesRole.children_are_presentational):
|
||||
reason = TextEventReason.UI_UPDATE
|
||||
return reason
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
# Forked from Orca screen reader.
|
||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||
|
||||
version = "2026.05.25"
|
||||
codeName = "master"
|
||||
version = "2026.07.18"
|
||||
codeName = "testing"
|
||||
|
||||
@@ -47,28 +47,6 @@ try:
|
||||
except ImportError:
|
||||
PYTESSERACT_AVAILABLE = False
|
||||
|
||||
# pdf2image
|
||||
try:
|
||||
from pdf2image import convert_from_path
|
||||
PDF2IMAGE_AVAILABLE = True
|
||||
except ImportError:
|
||||
PDF2IMAGE_AVAILABLE = False
|
||||
|
||||
# scipy
|
||||
try:
|
||||
from scipy.spatial import KDTree
|
||||
SCIPY_AVAILABLE = True
|
||||
except ImportError:
|
||||
SCIPY_AVAILABLE = False
|
||||
|
||||
# webcolors
|
||||
try:
|
||||
from webcolors import CSS3_HEX_TO_NAMES
|
||||
from webcolors import hex_to_rgb
|
||||
WEBCOLORS_AVAILABLE = True
|
||||
except ImportError:
|
||||
WEBCOLORS_AVAILABLE = False
|
||||
|
||||
# GTK/GDK
|
||||
try:
|
||||
gi.require_version("Gtk", "3.0")
|
||||
@@ -594,9 +572,7 @@ class OCRDesktop(Plugin):
|
||||
"""Get color information for OCR text (simplified version)."""
|
||||
if not self._colorCalculation:
|
||||
return 'unknown'
|
||||
if not SCIPY_AVAILABLE or not WEBCOLORS_AVAILABLE:
|
||||
return 'unknown'
|
||||
|
||||
|
||||
# Simplified color calculation - just return "unknown" for now
|
||||
# Full implementation would require the color analysis from ocrdesktop
|
||||
return 'unknown'
|
||||
|
||||
@@ -60,6 +60,7 @@ from cthulhu.scripts import default
|
||||
from cthulhu.ax_object import AXObject
|
||||
from cthulhu.ax_text import AXText
|
||||
from cthulhu.ax_utilities import AXUtilities
|
||||
from cthulhu.ax_utilities_event import AXUtilitiesEvent, TextEventReason
|
||||
|
||||
from .bookmarks import Bookmarks
|
||||
from .braille_generator import BrailleGenerator
|
||||
@@ -2104,6 +2105,7 @@ class Script(default.Script):
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return False
|
||||
|
||||
reason = AXUtilitiesEvent.get_text_event_reason(event)
|
||||
obj, offset = self.utilities.getCaretContext(document, False, False)
|
||||
tokens = ["WEB: Context: ", obj, ", ", offset, "(focus: ", cthulhu_state.locusOfFocus, ")"]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
@@ -2156,14 +2158,10 @@ class Script(default.Script):
|
||||
self.updateBraille(event.source)
|
||||
return True
|
||||
|
||||
if self.utilities.lastInputEventWasTab():
|
||||
msg = "WEB: Last input event was Tab."
|
||||
if reason == TextEventReason.FOCUS_CHANGE:
|
||||
msg = "WEB: Event ignored: Caret moved due to focus change."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
if self.utilities.isDocument(event.source):
|
||||
msg = "WEB: Event ignored: Caret moved in document due to Tab."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return True
|
||||
return True
|
||||
|
||||
if self.utilities.inFindContainer():
|
||||
msg = "WEB: Event handled: Presenting find results"
|
||||
|
||||
@@ -24,6 +24,7 @@ if soundGeneratorModule is not None and not hasattr(soundGeneratorModule, "Sound
|
||||
soundGeneratorModule.SoundGenerator = _StubSoundGenerator
|
||||
|
||||
from cthulhu import messages
|
||||
from cthulhu.ax_utilities_event import AXUtilitiesEvent, TextEventReason
|
||||
from cthulhu.scripts.web import script as web_script
|
||||
from cthulhu.scripts.web import script_utilities as web_script_utilities
|
||||
from cthulhu.scripts.toolkits.Gecko import script_utilities as gecko_script_utilities
|
||||
@@ -336,6 +337,7 @@ class WebFocusSpeechInterruptionRegressionTests(unittest.TestCase):
|
||||
class WebCaretMovedRegressionTests(unittest.TestCase):
|
||||
def _make_script(self):
|
||||
testScript = web_script.Script.__new__(web_script.Script)
|
||||
testScript._inFocusMode = False
|
||||
testScript._lastCommandWasCaretNav = False
|
||||
testScript._lastCommandWasStructNav = False
|
||||
testScript._lastCommandWasMouseButton = False
|
||||
@@ -351,15 +353,87 @@ class WebCaretMovedRegressionTests(unittest.TestCase):
|
||||
def test_matching_char_nav_caret_event_is_consumed_without_duplicate_presentation(self):
|
||||
testScript = self._make_script()
|
||||
source = "source"
|
||||
event = mock.Mock(source=source, detail1=4)
|
||||
event = mock.Mock(type="object:text-caret-moved", source=source, detail1=4)
|
||||
|
||||
with mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus:
|
||||
with (
|
||||
mock.patch.object(
|
||||
AXUtilitiesEvent,
|
||||
"get_text_event_reason",
|
||||
return_value=TextEventReason.NAVIGATION_BY_CHARACTER,
|
||||
),
|
||||
mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus,
|
||||
):
|
||||
result = web_script.Script.onCaretMoved(testScript, event)
|
||||
|
||||
self.assertTrue(result)
|
||||
testScript.utilities.setCaretContext.assert_not_called()
|
||||
setLocusOfFocus.assert_not_called()
|
||||
|
||||
def test_focus_change_caret_event_does_not_replace_real_focus_event(self):
|
||||
testScript = self._make_script()
|
||||
source = object()
|
||||
event = mock.Mock(
|
||||
type="object:text-caret-moved",
|
||||
source=source,
|
||||
detail1=0,
|
||||
)
|
||||
testScript.utilities.getCaretContext.return_value = ("old-focus", 0)
|
||||
testScript.utilities.lastInputEventWasCharNav.return_value = False
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
AXUtilitiesEvent,
|
||||
"get_text_event_reason",
|
||||
return_value=TextEventReason.FOCUS_CHANGE,
|
||||
) as getReason,
|
||||
mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus,
|
||||
):
|
||||
result = web_script.Script.onCaretMoved(testScript, event)
|
||||
|
||||
self.assertTrue(result)
|
||||
getReason.assert_called_once_with(event)
|
||||
testScript.utilities.setCaretContext.assert_not_called()
|
||||
setLocusOfFocus.assert_not_called()
|
||||
|
||||
|
||||
class TextEventReasonRegressionTests(unittest.TestCase):
|
||||
def test_tab_is_classified_as_focus_change_before_editable_state(self):
|
||||
oldFocus = object()
|
||||
source = object()
|
||||
event = mock.Mock(type="object:text-caret-moved", source=source)
|
||||
inputManager = mock.Mock()
|
||||
inputManager.last_event_was_caret_selection.return_value = False
|
||||
inputManager.last_event_was_caret_navigation.return_value = False
|
||||
inputManager.last_event_was_select_all.return_value = False
|
||||
inputManager.last_event_was_primary_click_or_release.return_value = False
|
||||
inputManager.last_event_was_tab_navigation.return_value = True
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"cthulhu.input_event_manager.get_manager",
|
||||
return_value=inputManager,
|
||||
),
|
||||
mock.patch(
|
||||
"cthulhu.ax_utilities_event.focus_manager.get_manager"
|
||||
) as getFocusManager,
|
||||
mock.patch(
|
||||
"cthulhu.ax_utilities_event.AXUtilitiesRole.is_text_input_search",
|
||||
return_value=False,
|
||||
),
|
||||
mock.patch(
|
||||
"cthulhu.ax_utilities_event.AXUtilitiesState.is_editable",
|
||||
return_value=True,
|
||||
) as isEditable,
|
||||
):
|
||||
getFocusManager.return_value.get_active_mode_and_object_of_interest.return_value = (
|
||||
None,
|
||||
oldFocus,
|
||||
)
|
||||
reason = AXUtilitiesEvent._get_caret_moved_event_reason(event)
|
||||
|
||||
self.assertEqual(reason, TextEventReason.FOCUS_CHANGE)
|
||||
isEditable.assert_not_called()
|
||||
|
||||
|
||||
class WebDescriptionChangeRegressionTests(unittest.TestCase):
|
||||
def _make_script(self):
|
||||
@@ -610,7 +684,11 @@ class WebSelectionRegressionTests(unittest.TestCase):
|
||||
testScript = self._make_script()
|
||||
document = object()
|
||||
section = object()
|
||||
event = mock.Mock(source=section, detail1=-1)
|
||||
event = mock.Mock(
|
||||
type="object:text-caret-moved",
|
||||
source=section,
|
||||
detail1=-1,
|
||||
)
|
||||
manager = mock.Mock()
|
||||
manager.last_event_was_forward_caret_selection.return_value = True
|
||||
manager.last_event_was_backward_caret_selection.return_value = False
|
||||
|
||||
Reference in New Issue
Block a user