Skip to content

Instantly share code, notes, and snippets.

@jcsteh
Created October 21, 2024 02:59
Show Gist options
  • Save jcsteh/a7c94e4c52219ebe93d71882021ac916 to your computer and use it in GitHub Desktop.
Save jcsteh/a7c94e4c52219ebe93d71882021ac916 to your computer and use it in GitHub Desktop.
NVDA app module for Microsoft Phone Link
# Copyright 2024 James Teh
# License: GNU General Public License version 2.0
"""App module for Phone Link.
This does the following:
1. Works around the Phone Link crash when tabbing into the messages list.
2. Makes alt+1 focus the Conversations list when the Conversations view is
already active. Without this, alt+1 does nothing in this case. This makes it
much easier to switch conversations.
3. Adds alt+d to focus the message input text box. This is useful if you've
moved somewhere else in the Messages view and want to get back to the input
text box quickly.
"""
import api
import appModuleHandler
import controlTypes
import UIAHandler
from NVDAObjects.UIA import UIA
from scriptHandler import script
class AppModule(appModuleHandler.AppModule):
def _focusMessageList(self, container):
cond = UIAHandler.handler.clientObject.CreatePropertyCondition(
UIAHandler.UIA_ClassNamePropertyId,
"ListView"
)
el = container.UIAElement.FindFirst(UIAHandler.TreeScope_Children, cond)
if (
not el
or not UIAHandler.handler.clientObject.RawViewWalker.GetFirstChildElement(el)
):
# The list isn't visible or it contains no items, so it can't get focus.
# Note that IsKeyboardFocusable returns false even when there are items.
return False
el.setFocus()
return True
def _isMessageLink(self, obj):
if obj.role != controlTypes.Role.LINK:
return False
try:
return obj.parent.parent.parent.role == controlTypes.Role.LISTITEM
except AttributeError:
return False
@script(gesture="kb:shift+tab")
def script_shiftTab(self, gesture):
# Shift+tabbing into the message list often crashes the app. However,
# programmatically focusing it seems to work fine.
focus = api.getFocusObject()
if (
isinstance(focus, UIA)
and (
focus.UIAElement.CurrentAutomationId == "InputTextBox"
or self._isMessageLink(focus)
)
and self._focusMessageList(focus.parent)
):
return
gesture.send()
@script(gesture="kb:tab")
def script_tab(self, gesture):
# Tabbing into the message list often crashes the app. However,
# programmatically focusing it seems to work fine.
focus = api.getFocusObject()
if (
isinstance(focus, UIA)
and focus.UIAElement.CurrentAutomationId == "CallingFromMessagesButton"
and self._focusMessageList(focus.parent.parent)
):
return
gesture.send()
def _getRootUia(self):
return api.getFocusAncestors()[5].UIAElement
def _findConversationsList(self):
cond = UIAHandler.handler.clientObject.CreatePropertyCondition(
UIAHandler.UIA_AutomationIdPropertyId,
"CVSListView"
)
el = self._getRootUia().FindFirst(UIAHandler.TreeScope_Descendants, cond)
return el or None
@script(gesture="kb:alt+1")
def script_messagesView(self, gesture):
convList = self._findConversationsList()
if convList:
# The Conversations list is visible, which means the Messages view is
# active. Alt+1 won't do anything in this case. Focus the Conversations
# list to make it easier to switch conversations.
convList.setFocus()
return
# The Messages view isn't active, so let the app handle this.
gesture.send()
@script(gesture="kb:alt+d")
def script_focusInputTextBox(self, gesture):
cond = UIAHandler.handler.clientObject.CreatePropertyCondition(
UIAHandler.UIA_AutomationIdPropertyId,
"InputTextBox"
)
el = self._getRootUia().FindFirst(UIAHandler.TreeScope_Descendants, cond)
if el:
el.SetFocus()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment