Skip to content

Commit

Permalink
Merge pull request #5 from chaosparrot/issue-4-none-mutator
Browse files Browse the repository at this point in the history
Fix windows and start up crashes
  • Loading branch information
chaosparrot authored Dec 21, 2024
2 parents c15e9be + 6eb4564 commit e3e8c95
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 48 deletions.
131 changes: 103 additions & 28 deletions accessibility/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,79 @@ def index_element_text(self, element = None) -> AccessibilityText:
element = self.get_focused_element()
if element is None:
return None


# Takes the precedence order of Text2, Text, LegacyIAccessble, Value
# NOTE - TextEdit is not a read value but a way to change the text
accessibility_text = None
value = None
if "LegacyIAccessible" in element.patterns:
try:
value = element.legacyiaccessible_pattern.value
# Windows sometimes just throws operation successful errors...
except OSError:
value = value
accessibility_text = AccessibilityText(value)

elif "Text2" in element.patterns:
if "Text2" in element.patterns:
try:
value = element.text_pattern2.document_range.text
# Windows sometimes just throws operation successful errors...
except OSError:
value = value
accessibility_text = AccessibilityText(value)
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
value = None
# Talon pre 0.4 beta
except NameError as e:
value = None

elif "Value" in element.patterns:
if value is not None:
accessibility_text = AccessibilityText(value)

if accessibility_text and "Text" in element.patterns:
try:
value = element.text_pattern.document_range.text
# Windows sometimes just throws operation successful errors...
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
value = None
# Talon pre 0.4 beta
except NameError as e:
value = None

if value is not None:
accessibility_text = AccessibilityText(value)

if not accessibility_text and "LegacyIAccessible" in element.patterns:
# IAccessible Enum values
# https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.accessiblerole?view=windowsdesktop-8.0
try:
if element.legacyiaccessible_pattern.role == 42:
value = element.legacyiaccessible_pattern.value
# Windows sometimes just throws operation successful errors...
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
value = None
# Talon pre 0.4 beta
except NameError as e:
value = None
# Talon pre 0.4 beta - no selection supported
except AttributeError as e:
return []

if value is not None:
accessibility_text = AccessibilityText(value)

# Finally fallback to the Value pattern
if (not accessibility_text or accessibility_text.text == "") and "Value" in element.patterns:
try:
value = element.value_pattern.value
# Windows sometimes just throws operation successful errors...
except OSError:
value = value
accessibility_text = AccessibilityText(value)
# NotImplemented error
if str(e) == "0x80004001":
value = None
# Talon pre 0.4 beta
except NameError as e:
value = None

if value is not None:
accessibility_text = AccessibilityText(value)

if accessibility_text:
if accessibility_text and accessibility_text.text != "":
caret_position = self.determine_caret_positions(element)
if caret_position is not None and len(caret_position) == 2:
accessibility_text.active_caret = caret_position[0]
Expand All @@ -54,37 +99,61 @@ def determine_caret_positions(self, element = None) -> List[AccessibilityCaret]:
return None

# Code adapted from AndreasArvidsson's talon files
# Currently only Text2 is supported
if "Text2" in element.patterns:
text_pattern = element.text_pattern2
# Currently only Text and Text2 are supported
has_text_pattern = False if "Text2" not in element.patterns and "Text" not in element.patterns else True
if has_text_pattern:
text_pattern = element.text_pattern2 if "Text2" in element.patterns else element.text_pattern

# Make copy of the document range to avoid modifying the original
range_before_selection = text_pattern.document_range.clone()
selection_ranges = text_pattern.selection
if len(selection_ranges) != 1:
print("ACCESSIBILITY INDEXATION ERROR - Found multiple selections")
try:
selection_ranges = text_pattern.selection
except OSError as e:
# NotImplemented error - Cannot check selection so early return
if str(e) == "0x80004001":
return []

if len(selection_ranges) > 1:
print("ACCESSIBILITY INDEXATION ERROR - Found multiple or no selections")
return []
selection_range = selection_ranges[0].clone()

# Move the end of the copy to the start of the selection
# range_before_selection.end = selection_range.start
range_before_selection.move_endpoint_by_range("End", "Start", target=selection_range)
try:
range_before_selection.move_endpoint_by_range("End", "Start", target=selection_range)
# Talon pre 0.4 beta - no selection supported
except AttributeError as e:
return []

range_before_selection_text = None
try:
range_before_selection_text = range_before_selection.text
# Windows sometimes just throws operation successful errors...
except OSError:
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
return []
pass

selection_range_text = None
try:
selection_range_text = selection_range.text
# Windows sometimes just throws operation successful errors...
except OSError:
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
return []
pass

document_range_text = None
try:
document_range_text = text_pattern.document_range.text
# Windows sometimes just throws operation successful errors...
except OSError:
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
return []
pass

# Find the index by using the before selection text and the indexed text
Expand All @@ -99,7 +168,10 @@ def determine_caret_positions(self, element = None) -> List[AccessibilityCaret]:
try:
is_reversed = text_pattern.caret_range.compare_endpoints("Start", "Start", target=selection_ranges[0]) == 0
# Windows sometimes just throws operation successful errors...
except OSError:
except OSError as e:
# NotImplemented error
if str(e) == "0x80004001":
return []
pass

start_caret = AccessibilityCaret(start, start_position[0], start_position[1])
Expand All @@ -109,4 +181,7 @@ def determine_caret_positions(self, element = None) -> List[AccessibilityCaret]:
else:
return []

return []


windows_api = WindowsAccessibilityApi()
4 changes: 2 additions & 2 deletions virtual_buffer/input_context_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def poll_accessible_changes(self, update_caret = False, update_content = False):
second_caret = (accessible_content.selection_caret.line_index, accessible_content.selection_caret.characters_from_end)
results = [accessible_content.text, first_caret, second_caret]

if not accessible_content or ( accessible_content.active_caret.line_index == -1 and accessible_content.active_caret.characters_from_end == -1 ):
if not accessible_content or not accessible_content.active_caret or ( accessible_content.active_caret.line_index == -1 and accessible_content.active_caret.characters_from_end == -1 ):
results = self.find_caret_position(value, -1)

# Only if the caret position is not the same as the known position do we need to reindex
Expand Down Expand Up @@ -298,7 +298,7 @@ def index_accessible_content(self):

# Update the visual state to accessible if a value was found
if accessible_text:
caret_confidence = 2 if accessible_text.active_caret.line_index > -1 else 0
caret_confidence = 2 if accessible_text.active_caret and accessible_text.active_caret.line_index > -1 else 0
text_confidence = 2 if accessible_text.text is not None else -1
self.update_visual_state("accessibility", caret_confidence, text_confidence)

Expand Down
42 changes: 24 additions & 18 deletions virtual_buffer/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ def window_closed(self, event):
self.context.close_context(event)

def update_language(language: str):
global mutator
if not language:
language = settings.get("speech.language", "en")
if language is None:
Expand All @@ -310,6 +311,11 @@ def init_mutator():
settings.register("speech.language", lambda language: update_language(language))
settings.register("speech.engine", lambda _: update_language(""))
update_language("")
return mutator

def get_mutator() -> VirtualBufferManager:
global mutator
return mutator if mutator is not None else init_mutator()

app.register("ready", init_mutator)

Expand All @@ -318,27 +324,27 @@ class Actions:

def marithime_enable_input_tracking():
"""Enable tracking of input values so that we can make contextual decisions and keep the caret position"""
global mutator
mutator = get_mutator()
mutator.enable_tracking()

def marithime_disable_input_tracking():
"""Disable tracking of input values"""
global mutator
mutator = get_mutator()
mutator.disable_tracking()

def marithime_set_formatter(formatter: str):
"""Sets the current formatter to be used in text editing"""
global mutator
mutator = get_mutator()
mutator.set_formatter(formatter)

def marithime_transform_insert(insert: str) -> str:
"""Transform an insert automatically depending on previous context"""
global mutator
mutator = get_mutator()
return mutator.transform_insert(insert)[0]

def marithime_self_repair_insert(prose: str):
"""Input words based on context surrounding the words to input, allowing for self repair within speech as well"""
global mutator
mutator = get_mutator()

text_to_insert, keys = mutator.transform_insert(prose, True)
if len(keys) > 0:
Expand All @@ -351,7 +357,7 @@ def marithime_self_repair_insert(prose: str):

def marithime_insert(prose: str):
"""Input words based on context surrounding the words to input"""
global mutator
mutator = get_mutator()

text_to_insert, keys = mutator.transform_insert(prose)
if len(keys) > 0:
Expand All @@ -364,25 +370,25 @@ def marithime_insert(prose: str):

def marithime_track_key(key_string: str) -> str:
"""Track one or more key presses according to the key string"""
global mutator
mutator = get_mutator()
mutator.track_key(key_string)

def marithime_track_insert(insert: str, phrase: str = "") -> str:
"""Track a full insert"""
global mutator
mutator = get_mutator()
mutator.track_insert(insert, phrase)

def marithime_backspace(backward: bool = True):
"""Apply a clear based on the current virtual buffer"""
global mutator
mutator = get_mutator()
keys = mutator.clear_keys(backward)
for key in keys:
actions.key(key)
mutator.index_textarea()

def marithime_move_caret(phrase: str, caret_position: int = -1):
"""Move the caret to the given phrase"""
global mutator
mutator = get_mutator()
if mutator.has_phrase(phrase):
keys = mutator.move_to_phrase(phrase, caret_position)
if keys:
Expand All @@ -395,7 +401,7 @@ def marithime_move_caret(phrase: str, caret_position: int = -1):

def marithime_select(phrase: Union[str, List[str]]):
"""Move the caret to the given phrase and select it"""
global mutator
mutator = get_mutator()

phrases = phrase if isinstance(phrase, List) else [phrase]
keys = mutator.select_phrases(phrases)
Expand All @@ -407,7 +413,7 @@ def marithime_select(phrase: Union[str, List[str]]):

def marithime_correction(selection_and_correction: List[str]):
"""Select a fuzzy match of the words and apply the given words"""
global mutator
mutator = get_mutator()
keys = mutator.select_phrases(selection_and_correction, for_correction=True)
if len(keys) > 0:
mutator.disable_tracking()
Expand All @@ -422,7 +428,7 @@ def marithime_correction(selection_and_correction: List[str]):

def marithime_clear_phrase(phrase: str):
"""Move the caret behind the given phrase and remove it"""
global mutator
mutator = get_mutator()
before_keys = mutator.move_to_phrase(phrase, -1, False, False)
mutator.disable_tracking()
if before_keys:
Expand All @@ -436,7 +442,7 @@ def marithime_clear_phrase(phrase: str):

def marithime_continue():
"""Move the caret to the end of the current virtual buffer"""
global mutator
mutator = get_mutator()
keys = mutator.move_caret_back()

mutator.disable_tracking()
Expand All @@ -446,12 +452,12 @@ def marithime_continue():

def marithime_forget_context():
"""Forget the current context of the virtual buffer completely"""
global mutator
mutator = get_mutator()
mutator.clear_context()

def marithime_best_match(phrases: List[str], correct_previous: bool = False, starting_phrase: str = '') -> str:
"""Improve accuracy by picking the best matches out of the words used"""
global mutator
mutator = get_mutator()
match_dictionary = {}
if starting_phrase:
phrases.append( starting_phrase )
Expand All @@ -465,7 +471,7 @@ def marithime_best_match(phrases: List[str], correct_previous: bool = False, sta

def marithime_index_textarea():
"""Select the index area and update the internal state completely"""
global mutator
mutator = get_mutator()
mutator.index_textarea()

def marithime_update_sensory_state(scanning: bool, level: str, caret_confidence: int, content_confidence: int):
Expand All @@ -474,7 +480,7 @@ def marithime_update_sensory_state(scanning: bool, level: str, caret_confidence:

def marithime_dump_context():
"""Dump the current state of the virtual buffer for debugging purposes"""
global mutator
mutator = get_mutator()
mutator.disable_tracking("DUMP")
actions.insert("Available contexts:" )
for context in mutator.context.contexts:
Expand Down

0 comments on commit e3e8c95

Please sign in to comment.