Compare commits
2
Commits
23abeca651
...
1707dca020
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1707dca020 | ||
|
|
60d3fc613b |
@@ -24,6 +24,13 @@ This repository is a screen reader. Prioritize accessibility, correctness, and s
|
|||||||
- If repo and installed behavior differ, prefer rebuilding with `./build-local.sh` over patching the installed package directly.
|
- If repo and installed behavior differ, prefer rebuilding with `./build-local.sh` over patching the installed package directly.
|
||||||
- Treat direct edits under `~/.local/.../cthulhu/` as an exception path that requires explicit user approval.
|
- Treat direct edits under `~/.local/.../cthulhu/` as an exception path that requires explicit user approval.
|
||||||
|
|
||||||
|
## Platform support stance
|
||||||
|
- **critical** Robust Xorg support is required and is a merge gate for Cthulhu.
|
||||||
|
- Wayland support is desirable, but it is secondary to keeping Xorg stable and usable.
|
||||||
|
- If a change that improves Wayland would break, weaken, or regress Xorg support, the answer is a hard no by default.
|
||||||
|
- If the user explicitly insists on such a change anyway, warn them plainly that the change is expected to be rejected without further consideration as long as Xorg is adversely affected.
|
||||||
|
- Prefer desktop-agnostic fixes first. If a tradeoff is unavoidable, choose the path that preserves Xorg correctness and defer the Wayland-specific improvement.
|
||||||
|
|
||||||
## Coding guidelines
|
## Coding guidelines
|
||||||
- **When modifying existing code:** follow the surrounding code’s conventions.
|
- **When modifying existing code:** follow the surrounding code’s conventions.
|
||||||
- **When writing new code from scratch:** prefer
|
- **When writing new code from scratch:** prefer
|
||||||
|
|||||||
@@ -3370,7 +3370,7 @@ class Script(script.Script):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def presentMessage(self, fullMessage, briefMessage=None, voice=None, resetStyles=True,
|
def presentMessage(self, fullMessage, briefMessage=None, voice=None, resetStyles=True,
|
||||||
force=False):
|
force=False, interrupt=True):
|
||||||
"""Convenience method to speak a message and 'flash' it in braille.
|
"""Convenience method to speak a message and 'flash' it in braille.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -3398,7 +3398,13 @@ class Script(script.Script):
|
|||||||
else:
|
else:
|
||||||
message = fullMessage
|
message = fullMessage
|
||||||
if message:
|
if message:
|
||||||
self.speakMessage(message, voice=voice, resetStyles=resetStyles, force=force)
|
self.speakMessage(
|
||||||
|
message,
|
||||||
|
voice=voice,
|
||||||
|
interrupt=interrupt,
|
||||||
|
resetStyles=resetStyles,
|
||||||
|
force=force,
|
||||||
|
)
|
||||||
|
|
||||||
if (cthulhu.cthulhuApp.settingsManager.getSetting('enableBraille') \
|
if (cthulhu.cthulhuApp.settingsManager.getSetting('enableBraille') \
|
||||||
or cthulhu.cthulhuApp.settingsManager.getSetting('enableBrailleMonitor')) \
|
or cthulhu.cthulhuApp.settingsManager.getSetting('enableBrailleMonitor')) \
|
||||||
|
|||||||
@@ -2217,13 +2217,15 @@ class StructuralNavigation:
|
|||||||
|
|
||||||
if settings.speakCellCoordinates:
|
if settings.speakCellCoordinates:
|
||||||
[row, col] = self.getCellCoordinates(cell)
|
[row, col] = self.getCellCoordinates(cell)
|
||||||
self._script.presentMessage(messages.TABLE_CELL_COORDINATES \
|
self._script.presentMessage(
|
||||||
% {"row" : row + 1, "column" : col + 1})
|
messages.TABLE_CELL_COORDINATES % {"row": row + 1, "column": col + 1},
|
||||||
|
interrupt=False,
|
||||||
|
)
|
||||||
|
|
||||||
rowspan, colspan = self._script.utilities.rowAndColumnSpan(cell)
|
rowspan, colspan = self._script.utilities.rowAndColumnSpan(cell)
|
||||||
spanString = messages.cellSpan(rowspan, colspan)
|
spanString = messages.cellSpan(rowspan, colspan)
|
||||||
if spanString and settings.speakCellSpan:
|
if spanString and settings.speakCellSpan:
|
||||||
self._script.presentMessage(spanString)
|
self._script.presentMessage(spanString, interrupt=False)
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# #
|
# #
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
os.environ.setdefault("GSETTINGS_BACKEND", "memory")
|
||||||
|
|
||||||
|
gi.require_version("Atspi", "2.0")
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||||
|
|
||||||
|
from cthulhu import messages
|
||||||
|
from cthulhu import structural_navigation
|
||||||
|
|
||||||
|
|
||||||
|
class StructuralNavigationTableRegressionTests(unittest.TestCase):
|
||||||
|
def test_table_cell_coordinates_do_not_interrupt_cell_contents(self):
|
||||||
|
navigator = structural_navigation.StructuralNavigation.__new__(
|
||||||
|
structural_navigation.StructuralNavigation
|
||||||
|
)
|
||||||
|
navigator._script = mock.Mock()
|
||||||
|
navigator._script.utilities.rowAndColumnSpan.return_value = (1, 1)
|
||||||
|
navigator._getCaretPosition = mock.Mock(return_value=("cell", 0))
|
||||||
|
navigator._setCaretPosition = mock.Mock(return_value=("cell", 0))
|
||||||
|
navigator._isBlankCell = mock.Mock(return_value=False)
|
||||||
|
navigator._presentObject = mock.Mock()
|
||||||
|
navigator.getCellCoordinates = mock.Mock(return_value=(1, 2))
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock.patch.object(structural_navigation.settings, "speakCellHeaders", False),
|
||||||
|
mock.patch.object(structural_navigation.settings, "speakCellCoordinates", True),
|
||||||
|
mock.patch.object(structural_navigation.settings, "speakCellSpan", False),
|
||||||
|
):
|
||||||
|
navigator._tableCellPresentation("cell", None)
|
||||||
|
|
||||||
|
navigator._presentObject.assert_called_once_with("cell", 0)
|
||||||
|
navigator._script.presentMessage.assert_called_once_with(
|
||||||
|
messages.TABLE_CELL_COORDINATES % {"row": 2, "column": 3},
|
||||||
|
interrupt=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from cthulhu import settings
|
||||||
|
from cthulhu.backends.toml_backend import Backend
|
||||||
|
|
||||||
|
|
||||||
|
LEGACY_SETTINGS = """format-version = 2
|
||||||
|
|
||||||
|
[profiles.default.metadata]
|
||||||
|
display-name = "Default"
|
||||||
|
internal-name = "default"
|
||||||
|
|
||||||
|
[profiles.default.keybindings]
|
||||||
|
keyboard-layout = "desktop"
|
||||||
|
desktop-modifier-keys = ["Insert", "KP_Insert"]
|
||||||
|
|
||||||
|
[profiles.default.ai-assistant]
|
||||||
|
enabled = false
|
||||||
|
provider = "ollama"
|
||||||
|
api-key-file = ""
|
||||||
|
ollama-model = "llama3.2-vision"
|
||||||
|
ollama-endpoint = "http://localhost:11434"
|
||||||
|
confirmation-required = true
|
||||||
|
action-timeout = 30
|
||||||
|
screenshot-quality = "medium"
|
||||||
|
max-context-length = 4000
|
||||||
|
|
||||||
|
[profiles.default.ocr]
|
||||||
|
language-code = "eng"
|
||||||
|
scale-factor = 3
|
||||||
|
grayscale-image = false
|
||||||
|
invert-image = false
|
||||||
|
black-white-image = false
|
||||||
|
black-white-threshold = 200
|
||||||
|
color-calculation = false
|
||||||
|
color-calculation-max = 3
|
||||||
|
copy-to-clipboard = false
|
||||||
|
|
||||||
|
[profiles.default.plugins]
|
||||||
|
active-plugins = ["PluginManager", "OCR"]
|
||||||
|
plugin-sources = []
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyTomlSchemaMigrationTests(unittest.TestCase):
|
||||||
|
def test_backend_migrates_legacy_nested_profile_schema_on_read(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tempDir:
|
||||||
|
Path(tempDir, "user-settings.toml").write_text(
|
||||||
|
LEGACY_SETTINGS,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
backend = Backend(tempDir)
|
||||||
|
|
||||||
|
self.assertEqual(backend.availableProfiles(), [["Default", "default"]])
|
||||||
|
|
||||||
|
general = backend.getGeneral("default")
|
||||||
|
|
||||||
|
self.assertEqual(general["profile"], ["Default", "default"])
|
||||||
|
self.assertEqual(
|
||||||
|
general["keyboardLayout"],
|
||||||
|
settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP,
|
||||||
|
)
|
||||||
|
self.assertEqual(general["cthulhuModifierKeys"], settings.DESKTOP_MODIFIER_KEYS)
|
||||||
|
self.assertEqual(general["activePlugins"], ["PluginManager", "OCR"])
|
||||||
|
self.assertEqual(general["aiProvider"], settings.AI_PROVIDER_OLLAMA)
|
||||||
|
self.assertFalse(general["aiAssistantEnabled"])
|
||||||
|
self.assertEqual(general["ocrLanguageCode"], "eng")
|
||||||
|
self.assertEqual(backend.getKeybindings("default"), {})
|
||||||
|
|
||||||
|
def test_saving_after_legacy_read_rewrites_current_schema(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tempDir:
|
||||||
|
settingsPath = Path(tempDir, "user-settings.toml")
|
||||||
|
settingsPath.write_text(LEGACY_SETTINGS, encoding="utf-8")
|
||||||
|
|
||||||
|
backend = Backend(tempDir)
|
||||||
|
general = backend.getGeneral("default")
|
||||||
|
backend.saveProfileSettings("default", dict(general), {}, {})
|
||||||
|
|
||||||
|
savedSettings = settingsPath.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn('profile = ["Default", "default"]', savedSettings)
|
||||||
|
self.assertIn('activePlugins = ["PluginManager", "OCR"]', savedSettings)
|
||||||
|
self.assertNotIn("format-version = 2", savedSettings)
|
||||||
|
self.assertNotIn("[profiles.default.metadata]", savedSettings)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user