Added support for Orca's new self-voiced capabilities.

This commit is contained in:
Storm Dragon 2025-06-15 21:32:07 -04:00
parent 2efd0de68f
commit c9f9e66c7c

View File

@ -49,6 +49,62 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QHBoxLayout,
from PySide6.QtCore import Qt, QTimer
import webbrowser
class OrcaRemoteController:
"""D-Bus interface for Orca screen reader remote control"""
def __init__(self):
self.service_name = "org.gnome.Orca.Service"
self.main_path = "/org/gnome/Orca/Service"
self.available = self._test_availability()
def _test_availability(self):
"""Test if Orca remote controller is available"""
try:
result = subprocess.run([
"gdbus", "call", "--session",
"--dest", self.service_name,
"--object-path", self.main_path,
"--method", "org.gnome.Orca.Service.ListCommands"
], check=True, capture_output=True, text=True, timeout=2)
return True
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
return False
def present_message(self, message):
"""Present a message via Orca speech/braille output"""
if not self.available:
return False
try:
result = subprocess.run([
"gdbus", "call", "--session",
"--dest", self.service_name,
"--object-path", self.main_path,
"--method", "org.gnome.Orca.Service.PresentMessage",
message
], check=True, capture_output=True, text=True, timeout=2)
return "true" in result.stdout.lower()
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
return False
def interrupt_speech(self):
"""Interrupt current speech via SpeechAndVerbosityManager"""
if not self.available:
return False
try:
result = subprocess.run([
"gdbus", "call", "--session",
"--dest", self.service_name,
"--object-path", f"{self.main_path}/SpeechAndVerbosityManager",
"--method", "org.gnome.Orca.Module.ExecuteCommand",
"InterruptSpeech", "false"
], check=True, capture_output=True, text=True, timeout=2)
return "true" in result.stdout.lower()
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
return False
# Initialize speech provider based on platform
if platform.system() == "Windows":
# Set up DLL paths for Windows
@ -70,23 +126,28 @@ if platform.system() == "Windows":
print(f"Failed to initialize accessible_output2: {e}")
sys.exit()
else:
# Linux/Mac path
try:
output = subprocess.check_output(["pgrep", "cthulhu"])
speechProvider = "cthulhu"
except (subprocess.CalledProcessError, FileNotFoundError):
# Linux/Mac path - prioritize Orca remote controller
orca_remote = OrcaRemoteController()
if orca_remote.available:
speechProvider = "orca_remote"
orca = orca_remote
else:
try:
import accessible_output2.outputs.auto
s = accessible_output2.outputs.auto.Auto()
speechProvider = "accessible_output2"
except ImportError as e:
output = subprocess.check_output(["pgrep", "cthulhu"])
speechProvider = "cthulhu"
except (subprocess.CalledProcessError, FileNotFoundError):
try:
import speechd
spd = speechd.Client()
speechProvider = "speechd"
except ImportError:
print("No speech providers found.")
sys.exit()
import accessible_output2.outputs.auto
s = accessible_output2.outputs.auto.Auto()
speechProvider = "accessible_output2"
except ImportError as e:
try:
import speechd
spd = speechd.Client()
speechProvider = "speechd"
except ImportError:
print("No speech providers found.")
sys.exit()
class AccessibleComboBox(QComboBox):
@ -205,7 +266,12 @@ class SpeechHandler:
if not text or not self.use_tts:
return
if speechProvider == "speechd":
if speechProvider == "orca_remote":
# Try Orca first, interrupt current speech then present message
orca.interrupt_speech()
if not orca.present_message(text):
print(f"Orca remote error - message not delivered", file=sys.stderr)
elif speechProvider == "speechd":
spd.cancel()
spd.speak(text)
elif speechProvider == "accessible_output2":