2
2
#A part of NonVisual Desktop Access (NVDA)
3
#Copyright (C) 2006-2009 NVDA Contributors <http://www.nvda-project.org/>
4
3
#This file is covered by the GNU General Public License.
5
4
#See the file COPYING for more details.
5
#Copyright (C) 2006-2010 Michael Curran <mick@kulgan.net>, James Teh <jamie@jantrid.net>, Peter Vágner <peter.v@datagate.sk>, Aleksey Sadovoy <lex@onm.su>
7
7
"""Keyboard support"""
15
from keyUtils import key, keyName, sendKey, localizedKeyLabels
15
from keyLabels import localizedKeyLabels
18
16
from logHandler import log
19
17
import queueHandler
22
20
import winInputHook
24
import nvdajp_keyEvents # Masataka.Shinke
27
passKeyThroughCount=-1 #If 0 or higher then key downs and key ups will be passed straight through
28
lastPassThroughKeyDown=None
30
usedNVDAModifierKey=False
31
lastNVDAModifierKey=None
32
lastNVDAModifierKeyTime=None
33
unpauseByShiftUp=False
24
# These constants should be assigned to the name that NVDA will use for the key.
27
#: Keys which have been trapped by NVDA and should not be passed to the OS.
29
#: Tracks the number of keys passed through by request of the user.
30
#: If -1, pass through is disabled.
31
#: If 0 or higher then key downs and key ups will be passed straight through.
32
passKeyThroughCount=-1
33
#: The last key down passed through by request of the user.
34
lastPassThroughKeyDown = None
35
#: The last NVDA modifier key that was pressed with no subsequent key presses.
36
lastNVDAModifier = None
37
#: When the last NVDA modifier key was released.
38
lastNVDAModifierReleaseTime = None
39
#: Indicates that the NVDA modifier's special functionality should be bypassed until a key is next released.
40
bypassNVDAModifier = False
41
#: The modifiers currently being pressed.
42
currentModifiers = set()
43
#: A counter which is incremented each time a key is pressed.
44
#: Note that this may be removed in future, so reliance on it should generally be avoided.
35
48
def passNextKeyThrough():
36
global passKeyThroughCount, lastPassThroughKeyDown
49
global passKeyThroughCount
37
50
if passKeyThroughCount==-1:
38
51
passKeyThroughCount=0
39
lastPassThroughKeyDown=None
41
53
def isNVDAModifierKey(vkCode,extended):
42
54
if config.conf["keyboard"]["useNumpadInsertAsNVDAModifierKey"] and vkCode==winUser.VK_INSERT and not extended:
53
def speakToggleKey(vkCode):
54
toggleState=bool(not winUser.getKeyState(vkCode)&1)
55
if vkCode==winUser.VK_CAPITAL:
56
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,_("caps lock %s")%(_("on") if toggleState else _("off")))
57
elif vkCode==winUser.VK_NUMLOCK:
58
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,_("num lock %s")%(_("on") if toggleState else _("off")))
59
elif vkCode==winUser.VK_SCROLL:
60
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,_("scroll lock %s")%(_("on") if toggleState else _("off")))
62
65
def internal_keyDownEvent(vkCode,scanCode,extended,injected):
63
"""Event called by keyHook when it receives a keyDown. It sees if there is a script tied to this key and if so executes it. It also handles the speaking of characters, words and command keys.
66
"""Event called by winInputHook when it receives a keyDown.
66
global NVDAModifierKey, usedNVDAModifierKey, lastNVDAModifierKey, lastNVDAModifierKeyTime, passKeyThroughCount, lastPassThroughKeyDown, unpauseByShiftUp
69
global lastNVDAModifier, lastNVDAModifierReleaseTime, bypassNVDAModifier, passKeyThroughCount, lastPassThroughKeyDown, currentModifiers, keyCounter
67
70
#Injected keys should be ignored
70
# IF we're passing keys through, increment the pass key through count,
71
# but only if this isn't a repeat of the previous key down, as we don't receive key ups for repeated key downs.
72
if passKeyThroughCount>=0 and lastPassThroughKeyDown!=(vkCode,extended):
73
passKeyThroughCount+=1
74
lastPassThroughKeyDown=(vkCode,extended)
76
if watchdog.isAttemptingRecovery:
77
# The core is dead, so let keys pass through unhindered.
79
focusObject=api.getFocusObject()
80
focusAppModule=focusObject.appModule
81
if focusAppModule and focusAppModule.selfVoicing:
83
#pass the volume controlling keys
84
if extended and vkCode >= winUser.VK_VOLUME_MUTE and vkCode <= winUser.VK_VOLUME_UP: return True
85
vkName=vkCodes.byCode.get(vkCode,"").lower()
86
vkChar=ctypes.windll.user32.MapVirtualKeyW(vkCode,winUser.MAPVK_VK_TO_CHAR)
87
if vkName.startswith('oem') or not vkName:
89
vkName=unichr(vkCode).lower()
91
vkName=unichr(vkChar).lower()
92
if vkCode in (winUser.VK_SHIFT,winUser.VK_LSHIFT,winUser.VK_RSHIFT):
96
queueHandler.queueFunction(queueHandler.eventQueue,speech.pauseSpeech,True)
74
keyCode = (vkCode, extended)
76
if passKeyThroughCount >= 0:
77
# We're passing keys through.
78
if lastPassThroughKeyDown != keyCode:
79
# Increment the pass key through count.
80
# We only do this if this isn't a repeat of the previous key down, as we don't receive key ups for repeated key downs.
81
passKeyThroughCount += 1
82
lastPassThroughKeyDown = keyCode
86
gesture = KeyboardInputGesture(currentModifiers, vkCode, scanCode, extended)
87
if bypassNVDAModifier or (keyCode == lastNVDAModifier and lastNVDAModifierReleaseTime and time.time() - lastNVDAModifierReleaseTime < 0.5):
88
# The user wants the key to serve its normal function instead of acting as an NVDA modifier key.
89
# There may be key repeats, so ensure we do this until they stop.
90
bypassNVDAModifier = True
91
gesture.isNVDAModifierKey = False
92
lastNVDAModifierReleaseTime = None
93
if gesture.isNVDAModifierKey:
94
lastNVDAModifier = keyCode
98
unpauseByShiftUp=False
99
globalVars.keyCounter+=1
100
queueHandler.queueFunction(queueHandler.eventQueue,speech.cancelSpeech)
101
if lastNVDAModifierKey and (vkCode,extended)==lastNVDAModifierKey:
102
lastNVDAModifierKey=None
103
if (time.time()-lastNVDAModifierKeyTime)<0.5:
104
speakToggleKey(vkCode)
96
# Another key was pressed after the last NVDA modifier key, so it should not be passed through on the next press.
97
lastNVDAModifier = None
98
if gesture.isModifier:
99
if gesture.speechEffectWhenExecuted in (gesture.SPEECHEFFECT_PAUSE, gesture.SPEECHEFFECT_RESUME) and keyCode in currentModifiers:
100
# Ignore key repeats for the pause speech key to avoid speech stuttering as it continually pauses and resumes.
106
lastNVDAModifierKey=None
107
if isNVDAModifierKey(vkCode,extended):
108
NVDAModifierKey=(vkCode,extended)
109
if not globalVars.keyboardHelp:
102
currentModifiers.add(keyCode)
105
inputCore.manager.executeGesture(gesture)
106
trappedKeys.add(keyCode)
108
except inputCore.NoInputGestureAction:
109
if gesture.isNVDAModifierKey:
110
# Never pass the NVDA modifier key to the OS.
111
trappedKeys.add(keyCode)
111
if not globalVars.keyboardHelp and vkCode in [winUser.VK_CONTROL,winUser.VK_LCONTROL,winUser.VK_RCONTROL,winUser.VK_SHIFT,winUser.VK_LSHIFT,winUser.VK_RSHIFT,winUser.VK_MENU,winUser.VK_LMENU,winUser.VK_RMENU,winUser.VK_LWIN,winUser.VK_RWIN]:
115
modifierList.append("nvda")
116
if not vkCode in [winUser.VK_CONTROL,winUser.VK_LCONTROL,winUser.VK_RCONTROL] and winUser.getKeyState(winUser.VK_CONTROL)&32768:
117
modifierList.append("control")
118
if not vkCode in [winUser.VK_SHIFT,winUser.VK_LSHIFT,winUser.VK_RSHIFT] and winUser.getKeyState(winUser.VK_SHIFT)&32768:
119
modifierList.append("shift")
120
if not vkCode in [winUser.VK_MENU,winUser.VK_LMENU,winUser.VK_RMENU] and winUser.getKeyState(winUser.VK_MENU)&32768:
121
modifierList.append("alt")
122
if not vkCode in [winUser.VK_LWIN,winUser.VK_RWIN] and winUser.getKeyState(winUser.VK_LWIN)&32768:
123
modifierList.append("win")
124
if not vkCode in [winUser.VK_LWIN,winUser.VK_RWIN] and winUser.getKeyState(winUser.VK_RWIN)&32768:
125
modifierList.append("win")
126
if len(modifierList) > 0:
127
modifiers=frozenset(modifierList)
132
mainKey=winUser.getKeyNameText(scanCode,extended)
134
mainKey="extended%s"%mainKey
135
keyPress=(modifiers,mainKey)
136
if log.isEnabledFor(log.IO): log.io("key press: %s"%keyName(keyPress))
137
speakCommandKeys=config.conf["keyboard"]["speakCommandKeys"]
138
if globalVars.keyboardHelp or speakCommandKeys:
141
for mod in modifiers:
142
if localizedKeyLabels.has_key(mod):
143
labelList.append(localizedKeyLabels[mod])
145
labelList.append(mod)
146
if not isNVDAModifierKey(vkCode,extended):
147
ch=ctypes.windll.user32.MapVirtualKeyW(vkCode,winUser.MAPVK_VK_TO_CHAR)
148
if localizedKeyLabels.has_key(keyPress[1]):
149
labelList.append(localizedKeyLabels[keyPress[1]])
150
elif ch>=32 and not mainKey.startswith('numpad') and not mainKey in ('extendeddivide', 'multiply', 'subtract', 'add', 'extendedreturn', 'decimal'):
151
labelList.append(unichr(ch))
153
labelList.append(keyPress[1])
154
if not speakCommandKeys or (speakCommandKeys and (
155
# An alphanumeric key has a label of only 1 character.
156
# Therefore, a command key either has a label longer than 1 character (except space)...
157
(labelList[-1]!="space" and len(labelList[-1])>1)
158
# or it has modifiers other than shift; e.g. control+f is a command key, but shift+f is not.
159
or (modifiers and modifiers!=frozenset(("shift",)))
161
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,"+".join(labelList))
162
if not globalVars.keyboardHelp and (mainKey in ('extendeddivide', 'multiply', 'subtract', 'add', 'extendedreturn')) and (bool(winUser.getKeyState(winUser.VK_NUMLOCK)&1)):
164
script=scriptHandler.findScript(keyPress)
166
scriptName=scriptHandler.getScriptName(script)
167
if globalVars.keyboardHelp and scriptName!="keyboardHelp":
169
brailleTextList.append("+".join(labelList))
170
scriptDescription = scriptHandler.getScriptDescription(script)
171
if scriptDescription:
172
brailleTextList.append(scriptDescription)
173
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,_("Description: %s")%scriptDescription)
174
scriptLocation=scriptHandler.getScriptLocation(script)
175
brailleTextList.append(scriptLocation)
176
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,_("Location: %s")%scriptLocation)
178
braille.handler.message("\t\t".join(brailleTextList))
180
scriptHandler.queueScript(script,keyPress)
181
if script or globalVars.keyboardHelp:
182
keyUpIgnoreSet.add((vkCode,extended))
184
usedNVDAModifierKey=True
187
speakToggleKey(vkCode)
190
114
log.error("internal_keyDownEvent", exc_info=True)
191
speech.speakMessage(_("Error in keyboardHandler.internal_keyDownEvent"))
194
117
def internal_keyUpEvent(vkCode,scanCode,extended,injected):
195
"""Event that pyHook calls when it receives keyUps"""
118
"""Event called by winInputHook when it receives a keyUp.
197
global NVDAModifierKey, usedNVDAModifierKey, lastNVDAModifierKey, lastNVDAModifierKeyTime, passKeyThroughCount, unpauseByShiftUp
121
global lastNVDAModifier, lastNVDAModifierReleaseTime, bypassNVDAModifier, passKeyThroughCount, lastPassThroughKeyDown, currentModifiers
200
if passKeyThroughCount>=1:
201
passKeyThroughCount-=1
202
if passKeyThroughCount==0:
203
passKeyThroughCount=-1
205
if watchdog.isAttemptingRecovery:
206
# The core is dead, so let keys pass through unhindered.
208
focusObject=api.getFocusObject()
209
focusAppModule=focusObject.appModule
210
if focusAppModule and focusAppModule.selfVoicing:
212
if unpauseByShiftUp and vkCode in (winUser.VK_SHIFT,winUser.VK_LSHIFT,winUser.VK_RSHIFT):
213
queueHandler.queueFunction(queueHandler.eventQueue,speech.pauseSpeech,False)
214
unpauseByShiftUp=False
215
if NVDAModifierKey and (vkCode,extended)==NVDAModifierKey:
216
if not usedNVDAModifierKey:
217
lastNVDAModifierKey=NVDAModifierKey
218
lastNVDAModifierKeyTime=time.time()
220
usedNVDAModifierKey=False
222
elif (vkCode,extended) in keyUpIgnoreSet:
223
keyUpIgnoreSet.remove((vkCode,extended))
225
elif vkCode in [winUser.VK_CONTROL,winUser.VK_LCONTROL,winUser.VK_RCONTROL,winUser.VK_SHIFT,winUser.VK_LSHIFT,winUser.VK_RSHIFT,winUser.VK_MENU,winUser.VK_LMENU,winUser.VK_RMENU,winUser.VK_LWIN,winUser.VK_RWIN]:
125
keyCode = (vkCode, extended)
127
if passKeyThroughCount >= 1:
128
if lastPassThroughKeyDown == keyCode:
129
# This key has been released.
130
lastPassThroughKeyDown = None
131
passKeyThroughCount -= 1
132
if passKeyThroughCount == 0:
133
passKeyThroughCount = -1
136
if lastNVDAModifier and keyCode == lastNVDAModifier:
137
# The last pressed NVDA modifier key is being released and there were no key presses in between.
138
# The user may want to press it again quickly to pass it through.
139
lastNVDAModifierReleaseTime = time.time()
140
# If we were bypassing the NVDA modifier, stop doing so now, as there will be no more repeats.
141
bypassNVDAModifier = False
143
currentModifiers.discard(keyCode)
145
if keyCode in trappedKeys:
146
trappedKeys.remove(keyCode)
228
log.error("", exc=True)
229
speech.speakMessage(_("Error in keyboardHandler.internal_keyUpEvent"))
149
log.error("", exc_info=True)
232
152
#Register internal key press event with operating system
235
155
"""Initialises keyboard support."""
236
156
winInputHook.initialize()
237
157
winInputHook.setCallbacks(keyDown=internal_keyDownEvent,keyUp=internal_keyUpEvent)
238
nvdajp_keyEvents.initialize() # Masataka.Shinke
241
nvdajp_keyEvents.terminate() # Masataka.Shinke
242
160
winInputHook.terminate()
162
class KeyboardInputGesture(inputCore.InputGesture):
163
"""A key pressed on the traditional system keyboard.
166
#: All normal modifier keys, where modifier vk codes are mapped to a more general modifier vk code or C{None} if not applicable.
168
NORMAL_MODIFIER_KEYS = {
169
winUser.VK_LCONTROL: winUser.VK_CONTROL,
170
winUser.VK_RCONTROL: winUser.VK_CONTROL,
171
winUser.VK_LSHIFT: winUser.VK_SHIFT,
172
winUser.VK_RSHIFT: winUser.VK_SHIFT,
173
winUser.VK_LMENU: winUser.VK_MENU,
174
winUser.VK_RMENU: winUser.VK_MENU,
175
winUser.VK_LWIN: VK_WIN,
176
winUser.VK_RWIN: VK_WIN,
179
#: All possible toggle key vk codes.
181
TOGGLE_KEYS = frozenset((winUser.VK_CAPITAL, winUser.VK_NUMLOCK, winUser.VK_SCROLL))
183
#: All possible keyboard layouts, where layout names are mapped to localised layout names.
186
"desktop": _("desktop"),
187
"laptop": _("laptop"),
191
def getVkName(cls, vkCode, isExtended):
192
if isinstance(vkCode, str):
194
name = vkCodes.byCode.get((vkCode, isExtended))
195
if not name and isExtended is not None:
196
# Whether the key is extended doesn't matter for many keys, so try None.
197
name = vkCodes.byCode.get((vkCode, None))
198
return name if name else ""
200
def __init__(self, modifiers, vkCode, scanCode, isExtended):
201
#: The keyboard layout in which this gesture was created.
203
self.layout = config.conf["keyboard"]["keyboardLayout"]
204
self.modifiers = modifiers = set(modifiers)
205
# Don't double up if this is a modifier key repeat.
206
modifiers.discard((vkCode, isExtended))
207
if vkCode in (winUser.VK_DIVIDE, winUser.VK_MULTIPLY, winUser.VK_SUBTRACT, winUser.VK_ADD) and winUser.getKeyState(winUser.VK_NUMLOCK) & 1:
208
# Some numpad keys have the same vkCode regardless of numlock.
209
# For these keys, treat numlock as a modifier.
210
modifiers.add((winUser.VK_NUMLOCK, False))
211
self.generalizedModifiers = set((self.NORMAL_MODIFIER_KEYS.get(mod) or mod, extended) for mod, extended in modifiers)
213
self.scanCode = scanCode
214
self.isExtended = isExtended
215
super(KeyboardInputGesture, self).__init__()
217
def _get_isNVDAModifierKey(self):
218
return isNVDAModifierKey(self.vkCode, self.isExtended)
220
def _get_isModifier(self):
221
return self.vkCode in self.NORMAL_MODIFIER_KEYS or self.isNVDAModifierKey
223
def _get_mainKeyName(self):
224
if self.isNVDAModifierKey:
227
name = self.getVkName(self.vkCode, self.isExtended)
231
if 32 < self.vkCode < 128:
232
return unichr(self.vkCode).lower()
233
vkChar = winUser.user32.MapVirtualKeyW(self.vkCode, winUser.MAPVK_VK_TO_CHAR)
235
return unichr(vkChar).lower()
237
return winUser.getKeyNameText(self.scanCode, self.isExtended)
239
def _get_modifierNames(self):
241
for modVk, modExt in self.generalizedModifiers:
242
if isNVDAModifierKey(modVk, modExt):
245
modTexts.add(self.getVkName(modVk, None))
249
def _get__keyNamesInDisplayOrder(self):
250
return tuple(self.modifierNames) + (self.mainKeyName,)
252
def _get_logIdentifier(self):
253
return u"kb({layout}):{key}".format(layout=self.layout,
254
key="+".join(self._keyNamesInDisplayOrder))
256
def _get_displayName(self):
257
return "+".join(localizedKeyLabels.get(key, key) for key in self._keyNamesInDisplayOrder)
259
def _get_identifiers(self):
260
keyNames = set(self.modifierNames)
261
keyNames.add(self.mainKeyName)
262
keyName = "+".join(keyNames).lower()
264
u"kb({layout}):{key}".format(layout=self.layout, key=keyName),
265
u"kb:{key}".format(key=keyName)
268
def _get_shouldReportAsCommand(self):
269
if self.isExtended and winUser.VK_VOLUME_MUTE <= self.vkCode <= winUser.VK_VOLUME_UP:
270
# Don't report volume controlling keys.
272
# Aside from space, a key name of more than 1 character is a command.
273
if self.vkCode != winUser.VK_SPACE and len(self.mainKeyName) > 1:
275
# If this key has modifiers other than shift, it is a command; e.g. shift+f is text, but control+f is a command.
276
modifiers = self.generalizedModifiers
277
if modifiers and (len(modifiers) > 1 or tuple(modifiers)[0][0] != winUser.VK_SHIFT):
281
def _get_speechEffectWhenExecuted(self):
282
if inputCore.manager.isInputHelpActive:
283
return self.SPEECHEFFECT_CANCEL
284
if self.isExtended and winUser.VK_VOLUME_MUTE <= self.vkCode <= winUser.VK_VOLUME_UP:
286
if self.vkCode in (winUser.VK_SHIFT, winUser.VK_LSHIFT, winUser.VK_RSHIFT):
287
return self.SPEECHEFFECT_RESUME if speech.isPaused else self.SPEECHEFFECT_PAUSE
288
return self.SPEECHEFFECT_CANCEL
290
def reportExtra(self):
291
if self.vkCode in self.TOGGLE_KEYS:
292
wx.CallLater(30, self._reportToggleKey)
294
def _reportToggleKey(self):
295
toggleState = winUser.getKeyState(self.vkCode) & 1
296
key = self.mainKeyName
297
ui.message(u"{key} {state}".format(
298
key=localizedKeyLabels.get(key, key),
299
state=_("on") if toggleState else _("off")))
303
for vk, ext in self.generalizedModifiers:
305
if winUser.getKeyState(winUser.VK_LWIN) & 32768 or winUser.getKeyState(winUser.VK_RWIN) & 32768:
309
elif winUser.getKeyState(vk) & 32768:
312
keys.append((vk, 0, ext))
313
keys.append((self.vkCode, self.scanCode, self.isExtended))
315
if winUser.getKeyState(self.vkCode) & 32768:
316
# This key is already down, so send a key up for it first.
317
winUser.keybd_event(self.vkCode, self.scanCode, self.isExtended + 2, 0)
319
# Send key down events for these keys.
320
for vk, scan, ext in keys:
321
winUser.keybd_event(vk, scan, ext, 0)
322
# Send key up events for the keys in reverse order.
323
for vk, scan, ext in reversed(keys):
324
winUser.keybd_event(vk, scan, ext + 2, 0)
326
if not queueHandler.isPendingItems(queueHandler.eventQueue):
331
def fromName(cls, name):
332
"""Create an instance given a key name.
333
@param name: The key name.
335
@return: A gesture for the specified key.
336
@rtype: L{KeyboardInputGesture}
338
keyNames = name.split("+")
340
for keyName in keyNames:
341
if keyName == VK_WIN:
344
elif len(keyName) == 1:
346
requiredMods, vk = winUser.VkKeyScan(keyName)
348
keys.append((winUser.VK_SHIFT, False))
350
keys.append((winUser.VK_CONTROL, False))
352
keys.append((winUser.VK_MENU, False))
353
# Not sure whether we need to support the Hankaku modifier (& 8).
355
vk, ext = vkCodes.byName[keyName.lower()]
358
keys.append((vk, ext))
363
return cls(keys[:-1], vk, 0, ext)