forked from nvaccess/nvda
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmouseHandler.py
226 lines (201 loc) · 8 KB
/
mouseHandler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2016 NVDA Contributors <http://www.nvda-project.org/>
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import time
import wx
import tones
import ctypes
import winUser
import queueHandler
import api
import screenBitmap
import speech
import globalVars
import eventHandler
from logHandler import log
import config
import winInputHook
import core
import ui
from math import floor
WM_MOUSEMOVE=0x0200
WM_LBUTTONDOWN=0x0201
WM_LBUTTONUP=0x0202
WM_LBUTTONDBLCLK=0x0203
WM_RBUTTONDOWN=0x0204
WM_RBUTTONUP=0x0205
WM_RBUTTONDBLCLK=0x0206
curMousePos=(0,0)
mouseMoved=False
curMouseShape=""
_shapeTimer=None
scrBmpObj=None
#: The time (in seconds) at which the last mouse event occurred.
#: @type: float
lastMouseEventTime=0
SHAPE_REPORT_DELAY = 100
def updateMouseShape(name):
global curMouseShape
if not name or name==curMouseShape:
return
curMouseShape=name
if config.conf["mouse"]["reportMouseShapeChanges"]:
# Delay reporting to avoid unnecessary/excessive verbosity.
_shapeTimer.Stop()
_shapeTimer.Start(SHAPE_REPORT_DELAY, True)
def playAudioCoordinates(x, y, screenWidth, screenHeight, screenMinPos, detectBrightness=True,blurFactor=0):
""" play audio coordinates:
- left to right adjusting the volume between left and right speakers
- top to bottom adjusts the pitch of the sound
- brightness adjusts the volume of the sound
Coordinates (x, y) are absolute, and can be negative.
"""
# make relative to (0,0) and positive
x = x - screenMinPos.x
y = y - screenMinPos.y
minPitch=config.conf['mouse']['audioCoordinates_minPitch']
maxPitch=config.conf['mouse']['audioCoordinates_maxPitch']
curPitch=minPitch+((maxPitch-minPitch)*((screenHeight-y)/float(screenHeight)))
if detectBrightness:
startX=min(max(x-blurFactor,0),screenWidth)+screenMinPos.x
startY=min(max(y-blurFactor,0),screenHeight)+screenMinPos.y
width=min(blurFactor+1,screenWidth)
height=min(blurFactor+1,screenHeight)
grey=screenBitmap.rgbPixelBrightness(scrBmpObj.captureImage( startX, startY, width, height)[0][0])
brightness=grey/255.0
minBrightness=config.conf['mouse']['audioCoordinates_minVolume']
maxBrightness=config.conf['mouse']['audioCoordinates_maxVolume']
brightness=(brightness*(maxBrightness-minBrightness))+minBrightness
else:
brightness=config.conf['mouse']['audioCoordinates_maxVolume']
leftVolume=int((85*((screenWidth-float(x))/screenWidth))*brightness)
rightVolume=int((85*(float(x)/screenWidth))*brightness)
tones.beep(curPitch,40,left=leftVolume,right=rightVolume)
#Internal mouse event
def internal_mouseEvent(msg,x,y,injected):
global mouseMoved, curMousePos, lastMouseEventTime
lastMouseEventTime=time.time()
if injected:
return True
if not config.conf['mouse']['enableMouseTracking']:
return True
try:
curMousePos=(x,y)
if msg==WM_MOUSEMOVE:
mouseMoved=True
core.requestPump()
elif msg in (WM_LBUTTONDOWN,WM_RBUTTONDOWN):
queueHandler.queueFunction(queueHandler.eventQueue,speech.cancelSpeech)
except:
log.error("", exc_info=True)
return True
def getMouseRestrictedToScreens(x, y, displays):
""" Ensures that the mouse position is within the area of one of the displays, relative to (0,0)
but not necessarily positive (which is as expected for mouse coordinates)
We need to first get the closest point on the edge of each display rectangle (if the mouse
is outside the rectangle). This is done by clamping the mouse position to the extents of each
screen. The distance from this point to the actual mouse position can then be calculated. The
smallest adjustment to get the mouse within the screen bounds is desired.
"""
mpos =wx.RealPoint(x,y)
closestDistValue = None
newXY = None
for screenRect in displays:
halfWidth = wx.RealPoint(0.5*screenRect.GetWidth(),0.5*screenRect.GetHeight())
tl = screenRect.GetTopLeft()
# tl is an integer based wx.Point, so convert to float based wx.RealPoint
screenMin = wx.RealPoint(tl.x, tl.y)
screenCenter = screenMin + halfWidth
scrCenterToMouse = mpos - screenCenter
mouseLimitedToScreen = screenCenter + wx.RealPoint( # relative to origin
max(min(scrCenterToMouse.x, halfWidth.x), -halfWidth.x),
max(min(scrCenterToMouse.y, halfWidth.y), -halfWidth.y))
edgeToMouse = mpos - mouseLimitedToScreen
distFromRectToMouseSqd = abs(edgeToMouse.x) + abs(edgeToMouse.y)
if closestDistValue == None or closestDistValue > distFromRectToMouseSqd:
closestDistValue = distFromRectToMouseSqd
newXY = mouseLimitedToScreen
# drop any partial position information. Even the 99% of the way to the edge of a
# pixel is still in the pixel.
return (int(floor(newXY.x)), int(floor(newXY.y)))
def getMinMaxPoints(screenRect):
screenMin = screenRect.GetTopLeft()
screenDim = wx.Point(screenRect.GetWidth(),screenRect.GetHeight())
screenMax = screenMin+screenDim
return (screenMin, screenMax)
def getTotalWidthAndHeightAndMinimumPosition(displays):
""" Calculate the total screen width and height.
Depending on screen layouts the rectangles may overlap on the vertical or
horizontal axis. Screens may also have a gap between them. In the case where
there is a gap in between we count that as contributing to the full virtual
space """
smallestX, smallestY, largestX, largestY = (None, None, None, None)
for screenRect in displays:
(screenMin, screenMax) = getMinMaxPoints(screenRect)
if smallestX == None or screenMin.x < smallestX: smallestX = screenMin.x
if smallestY == None or screenMin.y < smallestY: smallestY = screenMin.y
if largestX == None or screenMax.x > largestX: largestX = screenMax.x
if largestY == None or screenMax.y > largestY: largestY = screenMax.y
# get full range, including any "blank space" between monitors
totalWidth = largestX - smallestX
totalHeight = largestY - smallestY
return (totalWidth, totalHeight, wx.Point(smallestX, smallestY))
def executeMouseMoveEvent(x,y):
global currentMouseWindow
desktopObject=api.getDesktopObject()
displays = [ wx.Display(i).GetGeometry() for i in xrange(wx.Display.GetCount()) ]
x, y = getMouseRestrictedToScreens(x, y, displays)
screenWidth, screenHeight, minPos = getTotalWidthAndHeightAndMinimumPosition(displays)
if config.conf["mouse"]["audioCoordinatesOnMouseMove"]:
playAudioCoordinates(x, y, screenWidth, screenHeight, minPos,
config.conf['mouse']['audioCoordinates_detectBrightness'],
config.conf['mouse']['audioCoordinates_blurFactor'])
oldMouseObject=api.getMouseObject()
mouseObject=desktopObject.objectFromPoint(x, y)
while mouseObject and mouseObject.beTransparentToMouse:
mouseObject=mouseObject.parent
if not mouseObject:
return
if oldMouseObject==mouseObject:
mouseObject=oldMouseObject
else:
api.setMouseObject(mouseObject)
try:
eventHandler.executeEvent("mouseMove",mouseObject,x=x,y=y)
oldMouseObject=mouseObject
except:
log.error("api.notifyMouseMoved", exc_info=True)
#Register internal mouse event
def initialize():
global curMousePos, scrBmpObj, _shapeTimer
scrBmpObj=screenBitmap.ScreenBitmap(1,1)
(x,y)=winUser.getCursorPos()
desktopObject=api.getDesktopObject()
try:
mouseObject=desktopObject.objectFromPoint(x,y)
except:
log.exception("Error retrieving initial mouse object")
mouseObject=None
if not mouseObject:
mouseObject=api.getDesktopObject()
api.setMouseObject(mouseObject)
curMousePos=(x,y)
winInputHook.initialize()
winInputHook.setCallbacks(mouse=internal_mouseEvent)
_shapeTimer = wx.PyTimer(_reportShape)
def _reportShape():
# Translators: Reported when mouse cursor shape changes (example output: edit cursor).
ui.message(_("%s cursor")%curMouseShape)
def pumpAll():
global mouseMoved, curMousePos
if mouseMoved:
mouseMoved=False
(x,y)=curMousePos
executeMouseMoveEvent(x,y)
def terminate():
global scrBmpObj, _shapeTimer
scrBmpObj=None
winInputHook.terminate()
_shapeTimer.Stop()
_shapeTimer = None