Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Input Help Mode to Report Characters Typed by Key Combinations in Normal Mode #17629

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion source/inputCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ def _get_displayName(self):
"""
return self.getDisplayTextForIdentifier(self.normalizedIdentifiers[0])[1]

def _get__nameForInputHelp(self):
"""The name of this gesture as presented to the user in input help mode.
The base implementation returns self.displayName.
Subclasses can override this to provide a more specific implementation.
@return: The name to be displayed in input help mode.
@rtype: str
"""
return self.displayName

#: Whether this gesture should be reported when reporting of command gestures is enabled.
#: @type: bool
shouldReportAsCommand = True
Expand Down Expand Up @@ -643,7 +652,7 @@ def _inputHelpCaptor(self, gesture):
return bypass

def _handleInputHelp(self, gesture, onlyLog=False):
textList = [gesture.displayName]
textList = [gesture._nameForInputHelp]
script = gesture.script
runScript = False
logMsg = "Input help: gesture %s" % gesture.identifiers[0]
Expand Down
84 changes: 84 additions & 0 deletions source/keyboardHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,66 @@ def __init__(self, modifiers, vkCode, scanCode, isExtended):
self.isExtended = isExtended
super(KeyboardInputGesture, self).__init__()

def _get_character(self):
threadID = api.getFocusObject().windowThreadID
keyboardLayout = ctypes.windll.user32.GetKeyboardLayout(threadID)
buffer = ctypes.create_unicode_buffer(5)
states = (ctypes.c_byte * 256)()
modifierList = []
valid = True
for i in self.modifiers:
if i[0] not in self.NORMAL_MODIFIER_KEYS.values():
modifier = self.NORMAL_MODIFIER_KEYS.get(i[0])
else:
modifier = i[0]
if modifier:
modifierList.append(modifier)
for i in range(256):
if i in modifierList:
states[
i
] = (
-128
) # We tell ToUnicodeEx that the modifier is down, even tho it isn't according to Windows
else:
states[i] = ctypes.windll.user32.GetKeyState(i)
res = ctypes.windll.user32.ToUnicodeEx(
self.vkCode,
self.scanCode,
states,
buffer,
ctypes.sizeof(buffer),
0x04,
keyboardLayout,
)
if res < 0:
return ""
charList = []
if res > 0:
if winUser.VK_MENU in modifierList:
# When alt is the only modifier that is down,
# ToUnicodeEx still writes the character to the provided buffer as tho alt wasn't down
# So remove alt and try again
newBuffer = ctypes.create_unicode_buffer(5)
states[winUser.VK_MENU] = 0
ctypes.windll.user32.ToUnicodeEx(
self.vkCode,
self.scanCode,
states,
newBuffer,
ctypes.sizeof(newBuffer),
0x04,
keyboardLayout,
)
# Check if newBuffer.value == buffer.value.
# If they do, treat the key as invalid as it wouldn't have bin written to the focused window
valid = False if buffer.value == newBuffer.value else True
for i in buffer[:res]:
charList.append(i)
if not valid or "windows" in modifierList:
charList.clear()
return "".join(charList)

def _get_bypassInputHelp(self):
# #4226: Numlock must always be handled normally otherwise the Keyboard controller and Windows can get out of synk wih each other in regard to this key state.
return self.vkCode == winUser.VK_NUMLOCK
Expand Down Expand Up @@ -553,6 +613,30 @@ def _get_displayName(self):
for key in self._keyNamesInDisplayOrder
)

def _get__nameForInputHelp(self):
"""Returns the name of this gesture for input help mode.
For keyboard gestures that produce printable characters,
the character will be prepended to the display name,
unless it contains NVDA modifier.
@return: The name to be displayed in input help mode
@rtype: str
"""
displayName = super()._get__nameForInputHelp()
# NVDA commands keep original behavior
if any(isNVDAModifierKey(mod[0], mod[1]) for mod in self.modifiers):
return displayName
# Get character if available
if not self.character:
return displayName
char = "".join(self.character)
if not char.isprintable():
return displayName
# Avoid duplicating if displayName is the same as character (case insensitive)
if displayName.lower() == char.lower():
return displayName
# Add character before display name
return f"{char} {displayName}"

def _get_identifiers(self):
keyName = "+".join(self._keyNamesInDisplayOrder)
return (
Expand Down
3 changes: 3 additions & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ Prefix matching on command line flags, e.g. using `--di` for `--disable-addons`
* Microsoft Speech API version 5 and Microsoft Speech Platform voices now use WASAPI for audio output, which may improve the responsiveness of those voices. (#13284, @gexgd0419)
* The keyboard settings for "Speak typed characters" and "Speak typed words" now have three options: Off, Only in edit controls, and Always. (#17505, @Cary-rowen)
* By default, "Speak typed characters" is now set to "Only in edit controls".
*Input help mode has been improved: (#17629, @Cary-rowen, @Emil-18)
* When a key combination would produce a character in normal input mode, the character is reported first, followed by the key combination.
* If the key combination corresponds to an NVDA command, the behavior remains the same as before, i.e. the description of the command is reported.

### Bug Fixes

Expand Down