Repository: nvaccess/nvda

Issue:
Commands with modifier keys don't work with Focus Blue braille displays
### Steps to reproduce:
> 1. Install [driver](https://www.freedomscientific.com/Downloads/Focus/FocusBlueBrailleDisplayDriver) for Focus Blue braille displays
> 2. Start NVDA and configure the braille settings for the Freedom Scientific Focus (see my configs attached in screenshot)
> 3. Open Firefox or Chrome
> 4. Press the equivalent of ALT+D on the Braille display  (press SPACE + DOTS 6-8, then type the letter D in Braille)
> 5. Press the equivalent of INSERT+T on the Braille display (press SPACE + DOTS 2-8, then type the letter T in Braille)
> 6. Press the equivalent of CTRL+N on the Braille display to open a new window (press SPACE + DOTS 3-8, then type the letter N in Braille)

### Expected behavior:
> ALT+D command on the Braille display should go to the address bar
> INSERT+T command on the Braille display should read the title of the current window
> CTRL+N command on the Braille display should open a new window

### Actual behavior:
> ALT+D command on the Braille display just types the letter D
> INSERT+T command on the Braille display just types the letter T
> CTRL+N command on the Braille display just types the letter N

### System configuration:
NVDA version: 2017.3 (Brazilian Portuguese)
NVDA Installed or portable: installed
Windows version: 10
Braille display: Focus 80 Blue and Focus 40 Blue
Focus Blue driver: version 1.0.2030

Does the issue still occur after restarting your PC? Yes
Have you tried any other versions of NVDA? Yes, 2018.rc1 (same result)

![nvda_2017-10-27_14-03-27](https://user-images.githubusercontent.com/24814666/32114438-4fb0e544-bb22-11e7-8cb5-717caf4a081b.png)



Discussion:
I don't know if we have support for such prefix keys to "hold" a modifier such as ctrl. We could implement this as part of #2945.
@xtstoll the situation might have changed since the additional Freedom Scientific driver is not needed anymore. The braille display should work natively with NVDA now.
Could you please give an update? Are you still having this issue?
@Adriani90 - Sorry for the delay. I don't have easy access to the Focus 40 Blue anymore, so I needed to go to the public braille library here in São Paulo to retest this issue. 

I retested with NVDA 2019.3.1 with Windows 10. I did not use the Freedom Scientific driver, as it was stated that it's not necessary anymore with the latest NVDA. 

Unfortunately, the 2-step keystrokes don't worry. For example, to reproduce CTRL + N on the braille display to open a new window, I pressed SPACE + DOT 3 + DOT 8, then type the letter N in braille (DOTS 1-3-4-5). Nothing happened.

Simple keystrokes, like Tab, do work. For example, I pressed SPACE + DOT 4 + DOT 5 and it worked like the key Tab perfectly.

It seems the problem is still with support for the modifier keys. I am referring to the list on pages 24 and 25 of the Freedom Scientific manual, link below.

https://support.freedomscientific.com/Content/Documents/Manuals/Focus/Focus40Blue/Focus40-Blue-Users-Guide.pdf 

Resolving patch:
diff --git a/source/brailleDisplayDrivers/freedomScientific.py b/source/brailleDisplayDrivers/freedomScientific.py
--- a/source/brailleDisplayDrivers/freedomScientific.py
+++ b/source/brailleDisplayDrivers/freedomScientific.py
@@ -566,6 +566,18 @@ def script_toggleRightWizWheelAction(self, _gesture):
 			"reportCurrentLine": ("br(freedomScientific):dot1+dot4+brailleSpaceBar",),
 			"showGui": ("br(freedomScientific):dot1+dot3+dot4+dot5+brailleSpaceBar",),
 			"braille_toggleTether": ("br(freedomScientific):leftGDFButton+rightGDFButton",),
+			# Based on corresponding assignments in JAWS, modifing where Shift goes
+			"braille_toggleControl": ("br(freedomscientific):dot3+dot8+brailleSpaceBar",),
+			"braille_toggleAlt": ("br(freedomscientific):dot6+dot8+brailleSpaceBar",),
+			"braille_toggleWindows": ("br(freedomscientific):dot4+dot8+brailleSpaceBar",),
+			"braille_toggleNVDAKey": ("br(freedomscientific):dot5+dot8+brailleSpaceBar",),
+			"braille_toggleShift": ("br(freedomscientific):dot7+dot8+brailleSpaceBar",),
+			"braille_toggleControlShift": ("br(freedomscientific):dot3+dot7+dot8+brailleSpaceBar",),
+			"braille_toggleAltShift": ("br(freedomscientific):dot6+dot7+dot8+brailleSpaceBar",),
+			"braille_toggleWindowsShift": ("br(freedomscientific):dot4+dot7+dot8+brailleSpaceBar",),
+			"braille_toggleNVDAKeyShift": ("br(freedomscientific):dot5+dot7+dot8+brailleSpaceBar",),
+			"braille_toggleControlAlt": ("br(freedomscientific):dot3+dot6+dot8+brailleSpaceBar",),
+			"braille_toggleControlAltShift": ("br(freedomscientific):dot3+dot6+dot7+dot8+brailleSpaceBar",),
 		}
 	})
 
diff --git a/source/brailleInput.py b/source/brailleInput.py
--- a/source/brailleInput.py
+++ b/source/brailleInput.py
@@ -133,7 +133,7 @@ def _translate(self, endWord: bool) -> bool:
 		data = u"".join([chr(cell | LOUIS_DOTS_IO_START) for cell in self.bufferBraille[:pos]])
 		mode = louis.dotsIO | louis.noUndefinedDots
 		if (not self.currentFocusIsTextObj or self.currentModifiers) and self._table.contracted:
-			mode |=  louis.partialTrans
+			mode |= louis.partialTrans
 		self.bufferText = louis.backTranslate(
 			[os.path.join(brailleTables.TABLES_DIR, self._table.fileName),
 			"braille-patterns.cti"],
@@ -262,22 +262,32 @@ def input(self, dots: int):
 			self._reportUntranslated(pos)
 
 	def toggleModifier(self, modifier: str):
+		self.toggleModifiers([modifier])
+
+	def toggleModifiers(self, modifiers: List[str]):
 		# Check modifier validity
-		isModifier: bool = keyboardHandler.KeyboardInputGesture.fromName(modifier).isModifier
-		if not isModifier:
-			raise ValueError("%r is not a valid modifier"%modifier)
-		if modifier in self.currentModifiers:
-			self.currentModifiers.discard(modifier)
+		validModifiers: bool = all(
+			keyboardHandler.KeyboardInputGesture.fromName(m).isModifier
+			for m in modifiers)
+		if not validModifiers:
+			raise ValueError("%r contains unknown modifiers" % modifiers)
+
+		# Ensure input buffer is clear for the modified key
+		if self.bufferText:
+			self._translate(True)
+
+		toToggle: frozenset[str] = frozenset(modifiers)
+		added = toToggle - self.currentModifiers
+		removed = toToggle & self.currentModifiers
+		self.currentModifiers.difference_update(toToggle)
+		self.currentModifiers.update(added)
+		for modifier in added:
+			speech.speakMessage(keyLabels.getKeyCombinationLabel(modifier))
+		for modifier in removed:
 			# Translators: Reported when a braille input modifier is released.
 			speech.speakMessage(_("{modifier} released").format(
 				modifier=keyLabels.getKeyCombinationLabel(modifier)
 			))
-		else: # modifier not in self.currentModifiers
-			self.currentModifiers.add(modifier)
-			# Translators: Reported when a braille input modifier is pressed.
-			speech.speakMessage(_("{modifier} pressed").format(
-				modifier=keyLabels.getKeyCombinationLabel(modifier)
-			))
 
 	def enter(self):
 		"""Translates any braille input and presses the enter key.
diff --git a/source/globalCommands.py b/source/globalCommands.py
--- a/source/globalCommands.py
+++ b/source/globalCommands.py
@@ -3125,6 +3125,72 @@ def script_braille_toggleWindows(self, gesture):
 	def script_braille_toggleNVDAKey(self, gesture):
 		brailleInput.handler.toggleModifier("NVDA")
 
+	@script(
+		description=_(
+			# Translators: Input help mode message for a braille command.
+			"Virtually toggles the control and shift keys to emulate a "
+			"keyboard shortcut with braille input"),
+		category=inputCore.SCRCAT_KBEMU,
+		bypassInputHelp=True
+	)
+	def script_braille_toggleControlShift(self, gesture):
+		brailleInput.handler.toggleModifiers(["control", "shift"])
+
+	@script(
+		description=_(
+			# Translators: Input help mode message for a braille command.
+			"Virtually toggles the alt and shift keys to emulate a "
+			"keyboard shortcut with braille input"),
+		category=inputCore.SCRCAT_KBEMU,
+		bypassInputHelp=True
+	)
+	def script_braille_toggleAltShift(self, gesture):
+		brailleInput.handler.toggleModifiers(["alt", "shift"])
+
+	@script(
+		description=_(
+			# Translators: Input help mode message for a braille command.
+			"Virtually toggles the left windows and shift keys to emulate a "
+			"keyboard shortcut with braille input"),
+		category=inputCore.SCRCAT_KBEMU,
+		bypassInputHelp=True
+	)
+	def script_braille_toggleWindowsShift(self, gesture):
+		brailleInput.handler.toggleModifiers(["leftWindows", "shift"])
+
+	@script(
+		description=_(
+			# Translators: Input help mode message for a braille command.
+			"Virtually toggles the NVDA and shift keys to emulate a "
+			"keyboard shortcut with braille input"),
+		category=inputCore.SCRCAT_KBEMU,
+		bypassInputHelp=True
+	)
+	def script_braille_toggleNVDAKeyShift(self, gesture):
+		brailleInput.handler.toggleModifiers(["NVDA", "shift"])
+
+	@script(
+		description=_(
+			# Translators: Input help mode message for a braille command.
+			"Virtually toggles the control and alt keys to emulate a "
+			"keyboard shortcut with braille input"),
+		category=inputCore.SCRCAT_KBEMU,
+		bypassInputHelp=True
+	)
+	def script_braille_toggleControlAlt(self, gesture):
+		brailleInput.handler.toggleModifiers(["control", "alt"])
+
+	@script(
+		description=_(
+			# Translators: Input help mode message for a braille command.
+			"Virtually toggles the control, alt, and shift keys to emulate a "
+			"keyboard shortcut with braille input"),
+		category=inputCore.SCRCAT_KBEMU,
+		bypassInputHelp=True
+	)
+	def script_braille_toggleControlAltShift(self, gesture):
+		brailleInput.handler.toggleModifiers(["control", "alt", "shift"])
+
 	@script(
 		description=_(
 			# Translators: Input help mode message for reload plugins command.


Test patch: