Skip to content

Improve handling of keyboard emulation gestures and testing of braille display gesture map integrity#8240

Closed
LeonarddeR wants to merge 12 commits into
nvaccess:masterfrom
BabbageCom:i8149
Closed

Improve handling of keyboard emulation gestures and testing of braille display gesture map integrity#8240
LeonarddeR wants to merge 12 commits into
nvaccess:masterfrom
BabbageCom:i8149

Conversation

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Link to issue number:

Fixes #8149
Fixes #8201

This pr also contains code from #7784. I consider this pr more important than #7784 and will revert duplicate code from #7784

Summary of the issue:

  1. When using combined emulated system keyboard keys (i.e. including modifiers), the user gesture map is ignored. See Combining emulated keys via a braille display ignores "gestures.ini" #8149 for some examples.
  2. When creating an input gesture from a key name (e.g. when emulating gestures), NVDA treats the last key in the sequence as the main key and the other keys as modifiers. This way of creating a keyboard input gesture (e.g. for emulated keys) is unreliable.

Description of how this pull request fixes the issue:

  1. The user gesture map is now properly handled when looking up braille display modifiers and combined emulated system keyboard keys. I wrote unit tests to make sure the new code behaves as expected.
  2. keyboardHandler.KeyboardInputGesture.fromName has been improved to support keys in indeterminate error. It will raise a ValueError when a key is malformed (i.e. kb:nothing or kb:a+s+x+control.

Other enhancements and improvements

  1. The test module for brailleDisplayDrivers has been removed. Its tests are covered in the new test_inputCore module.
  2. test_inputCore is now devoted to test gesture map integrity. It not only tests the validity of braille display gesture identifiers, but also tests whether all GlobalCommands scripts in every driver's gesture map are bound to the expected gesture. For example, the tests will fail if one would assign a gesture to a non-existent script. It will also test the emulated system keyboard keys framework, even though most of the logic behind that resides in the braille module. Of course, I'm open for any suggestion that might improve the structure of the inputCore tests.
  3. Braille display gesture instances can now be created using a new fromIdentifier class method. this new class method is also available to create keyboard input gestures. It is used within the tests that are outlined above.

Testing performed:

Unit tests.

Known issues with pull request:

Using fromIdentifier to create braille display gestures doesn't support the braille input framework. For example, when pressing dot1 on a hims display, this would map to the identifiers bk:dots, bk:dot1 and br(hims):dot1. However, when creating an input gesture from br(hims):dot1, the braille input identifiers won't be available on that instance. This would require manual implementations of fromModifier for every specific driver, which is way beyond the scope of this pr.

Change log entry:

@LeonarddeR LeonarddeR requested a review from feerrenrut May 7, 2018 09:44

@feerrenrut feerrenrut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that I fully understand this change. It bothers me somewhat that the code for processing gestures (including string manipulations etc) is spread around as it is. I wouldn't suggest it as part of this change, but I think it might be worthwhile considering the abstraction level for gestures. I would like to go over this again.

Comment thread source/braille.py Outdated
for scriptCls, gesture, scriptName in globalMap.getScriptsForAllGestures()
if any(gesture.startswith(prefix.lower()) for prefix in prefixes)
and scriptCls is globalCommands.GlobalCommands
and (scriptName is None or scriptName.startswith("kb"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditions in this block are fairly dense, and not very well explained. I would be all for trying to break this up a little bit. Something I am concerned about here (if I am reading this right), in the old version we took gesture / scriptName if only if scriptName started with "kb". Now we also take it if it is None?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also take scriptNames when they are None, because the following can happen:

  1. A display driver has br(alva):t3 assigned to control
  2. A user removes this binding from the input gestures dialog
  3. br(alva):t3 is now assigned to None

If we would ignore the None occurrences here, _getModifierGestures would still yield t3 as being assigned to control.

Comment thread source/braille.py
and (scriptName is None or scriptName.startswith("kb"))
}
for gesture, scriptName in emuGestureToScriptMap.iteritems():
if scriptName:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we accept scriptName when it's none, but then check to ensure it isn't? See my other comment.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We accept the None to filter out the cases where a user has manually overridden an assignment as noted above. However, we only yield the assignments that are currently in effect. As in the example above, the function wouldn't yield anything for the br(alva):t3 key, as it has been re-assigned to None and therefore can be ignored within the scope of modifier gestures.

Comment thread source/braille.py Outdated
# Find a script for L{gestureKeys}.
id = "+".join(gestureKeys)
fakeGestureIds = [u"br({source}):{id}".format(source=self.source, id=id),]
fakeGestureIds = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a "fake gesture"?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very happy with this name either. I renamed fakeGestureId to gestureIdWithoutModifiers and added an explanation in comments, all in non committed code as of yet.

Comment thread source/braille.py Outdated
for globalMap in globalMaps:
for fakeGestureId in fakeGestureIds:
scriptNames.extend(scriptName for cls, scriptName in globalMap.getScriptsForGesture(fakeGestureId.lower()) if scriptName.startswith("kb"))
scriptNames.extend(scriptName for cls, scriptName in globalMap.getScriptsForGesture(fakeGestureId) if scriptName is None or scriptName.startswith("kb"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you split this onto multiple lines?

Comment thread source/braille.py
inputCore.normalizeGestureIdentifier(u"br({source}.{model}):{id}".format(source=self.source, model=self.model, id=id))
)
scriptNames = []
# We can't bother about multiple scripts for a gesture, we will just use the first one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment could be clarified. I think this means
"Gestures may be duplicated across different maps (EG the same gesture is present in both the userGestureMap and the gestureMap. User gestures should override display gestures, so we intentionally order the maps with the most important first when finding matches. Later we just take the first"

Comment thread source/braille.py
keys=scriptName.split(":")[1]
)
else:
scriptName = scriptNames[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we are only interested in the first, could we just break out of the loop at line 2264 when we find the first match?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, instead of extending the scriptNames list, I could imagine the creation of a nested loop, i.e.

for globalMap in globalMaps:
    for gestureId in gestureIds:
        for cls, scriptName in globalMap.getScriptsForGesture(gestureId):
            if scriptName is something:
                break

However as you know, this only breaks out of the inner most loop. I guess the only way to do this, would be by means of a function. I can declare a local function inside _get_script, but don't know whether that is very ideal.

Comment thread source/braille.py Outdated
gestureKeys = set(self.keyNames)
# Input gestures are normalized in gesture maps.
# Therefore, if we look for possible modifiers in a gesture, we ought to convert the key names to lower case.
keyNames = [name.lower() for name in self.keyNames]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we run this through normalizedGesture?

Comment thread source/keyboardHandler.py
@@ -540,15 +547,41 @@ def fromName(cls, name):
keys.append((winUser.VK_MENU, False))
# Not sure whether we need to support the Hankaku modifier (& 8).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this comment for

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea honestly.

@LeonarddeR

Copy link
Copy Markdown
Collaborator Author

It is likely that this needs a significant rewrite/refactor after the python 3 transition. Therefore I'm closing this for now. It is probably best to reopen smaller, more manageable pull requests in the future.

@LeonarddeR LeonarddeR closed this Apr 24, 2019
@Adriani90 Adriani90 added the Abandoned requested reports or updates are missing since more than 1 year, author or users are not available. label Sep 7, 2023
@Adriani90

Copy link
Copy Markdown
Collaborator

@LeonarddeR some work has been done on this during last years, including a fix for #8149.
What needs to be done still to fix #8201?

cc: @burmancomp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Abandoned requested reports or updates are missing since more than 1 year, author or users are not available.

Projects

None yet

4 participants