Skip to content

Commit eb06de5

Browse files
authored
Merge 92d2fd2 into 2d0ba99
2 parents 2d0ba99 + 92d2fd2 commit eb06de5

7 files changed

Lines changed: 147 additions & 65 deletions

File tree

source/NVDAObjects/IAccessible/MSHTML.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .. import InvalidNVDAObject
2828
from ..window import Window
2929
from NVDAObjects.UIA import UIA, UIATextInfo
30-
from locationHelper import RectLTRB
30+
from locationHelper import RectLTRB, Point
3131

3232
IID_IHTMLElement=comtypes.GUID('{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}')
3333

@@ -478,14 +478,20 @@ def kwargsFromSuper(cls,kwargs,relation=None):
478478

479479
elif isinstance(relation,tuple):
480480
windowHandle=kwargs.get('windowHandle')
481-
p=ctypes.wintypes.POINT(x=relation[0],y=relation[1])
482-
ctypes.windll.user32.ScreenToClient(windowHandle,ctypes.byref(p))
481+
if not windowHandle:
482+
log.debugWarning("Error converting point to client coordinates, no window handle")
483+
return False
484+
try:
485+
point = Point(*relation).toClient(windowHandle)
486+
except WindowsError:
487+
log.debugWarning("Error converting point to client coordinates", exc_info=True)
488+
return False
483489
# #3494: MSHTML's internal coordinates are always at a hardcoded DPI (usually 96) no matter the system DPI or zoom level.
484490
xFactor,yFactor=getZoomFactorsFromHTMLDocument(HTMLNode.document)
485491
try:
486-
HTMLNode=HTMLNode.document.elementFromPoint(p.x // xFactor, p.y // yFactor)
492+
HTMLNode = HTMLNode.document.elementFromPoint(point.x // xFactor, point.y // yFactor)
487493
except:
488-
HTMLNode=None
494+
HTMLNode = None
489495
if not HTMLNode:
490496
log.debugWarning("Error getting HTMLNode with elementFromPoint")
491497
return False

source/NVDAObjects/window/__init__.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,18 @@ def _get_displayText(self):
202202

203203
def redraw(self):
204204
"""Redraw the display for this object.
205+
@raise WindowsError: If redrawing fails.
205206
"""
206-
left, top, width, height = self.location
207-
left, top = winUser.ScreenToClient(self.windowHandle, left, top)
208-
winUser.RedrawWindow(self.windowHandle,
209-
winUser.RECT(left, top, left + width, top + height), None,
210-
winUser.RDW_INVALIDATE | winUser.RDW_UPDATENOW)
207+
# Conversion to client coordinates may fail if the window handle of this object is incorrect.
208+
# This will most likely be caused by a died window.
209+
location = self.location.toClient(self.windowHandle)
210+
if not winUser.RedrawWindow(
211+
self.windowHandle,
212+
location.toRECT(),
213+
None,
214+
winUser.RDW_INVALIDATE | winUser.RDW_UPDATENOW
215+
):
216+
raise ctypes.WinError()
211217

212218
def _get_windowText(self):
213219
textLength=watchdog.cancellableSendMessage(self.windowHandle,winUser.WM_GETTEXTLENGTH,0,0)
@@ -404,6 +410,8 @@ class DisplayModelLiveText(LiveText, Window):
404410

405411
def startMonitoring(self):
406412
# Force the window to be redrawn, as our display model might be out of date.
413+
# Do not catch exceptions caused by redraw, as when redrawing fails,
414+
# it is most likely that the window died, and we don't want to monitor in that case.
407415
self.redraw()
408416
displayModel.requestTextChangeNotifications(self, True)
409417
super(DisplayModelLiveText, self).startMonitoring()

source/NVDAObjects/window/edit.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,19 @@ def _getPointFromOffset(self,offset):
180180
if point.x <0 or point.y <0:
181181
raise LookupError("Point with client coordinates x=%d, y=%d not within client area of object" %
182182
(point.x, point.y))
183-
return point.toScreen(self.obj.windowHandle)
184-
183+
try:
184+
return point.toScreen(self.obj.windowHandle)
185+
except WindowsError as e:
186+
raise LookupError(
187+
"Couldn't convert point at offset %d to screen coordinates: %s"
188+
% (offset, e.strerror)
189+
)
185190

186191
def _getOffsetFromPoint(self,x,y):
187-
x, y = winUser.ScreenToClient(self.obj.windowHandle, x, y)
192+
try:
193+
x, y = winUser.ScreenToClient(self.obj.windowHandle, x, y)
194+
except WindowsError as e:
195+
raise LookupError(f"Couldn't convert point ({x},{y}) to client coordinates: {e.strerror}")
188196
if self.obj.editAPIVersion>=1:
189197
processHandle=self.obj.processHandle
190198
internalP=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(PointLStruct),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)

source/displayModel.py

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import watchdog
2222
from logHandler import log
2323
import windowUtils
24-
from locationHelper import RectLTRB, RectLTWH
24+
from locationHelper import RectLTRB, RectLTWH, Point
2525
import textUtils
2626
from typing import Union, List, Tuple
2727

@@ -200,7 +200,15 @@ def getWindowTextInRect(bindingHandle, windowHandle, left, top, right, bottom,mi
200200
characterLocations = []
201201
cpBufIt = iter(cpBuf)
202202
for cp in cpBufIt:
203-
characterLocations.append(RectLTRB(wcharToInt(cp), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt)), wcharToInt(next(cpBufIt))))
203+
left, top, right, bottom = (
204+
wcharToInt(cp),
205+
wcharToInt(next(cpBufIt)),
206+
wcharToInt(next(cpBufIt)),
207+
wcharToInt(next(cpBufIt))
208+
)
209+
if right < left:
210+
left, right = right, left
211+
characterLocations.append(RectLTRB(left, top, right, bottom))
204212
return text, characterLocations
205213

206214
def getFocusRect(obj):
@@ -292,23 +300,31 @@ def _get__storyFieldsAndRects(self) -> Tuple[
292300
List[int]
293301
]:
294302
# All returned coordinates are logical coordinates.
295-
if self._location:
296-
left, top, right, bottom = self._location
297-
else:
298-
try:
299-
left, top, width, height = self.obj.location
300-
except TypeError:
301-
# No location; nothing we can do.
302-
return [], [], [], []
303-
right = left + width
304-
bottom = top + height
303+
location = self._location if self._location else self.obj.location
304+
if location is None or not any(location):
305+
# No location; nothing we can do.
306+
return [], [], [], []
305307
bindingHandle=self.obj.appModule.helperLocalBindingHandle
306308
if not bindingHandle:
307309
log.debugWarning("AppModule does not have a binding handle")
308310
return [], [], [], []
309-
left,top=windowUtils.physicalToLogicalPoint(self.obj.windowHandle,left,top)
310-
right,bottom=windowUtils.physicalToLogicalPoint(self.obj.windowHandle,right,bottom)
311-
text,rects=getWindowTextInRect(bindingHandle, self.obj.windowHandle, left, top, right, bottom, self.minHorizontalWhitespace, self.minVerticalWhitespace,self.stripOuterWhitespace,self.includeDescendantWindows)
311+
try:
312+
location = location.toLogical(self.obj.windowHandle)
313+
except RuntimeError:
314+
log.exception()
315+
return [], [], [], []
316+
text, rects = getWindowTextInRect(
317+
bindingHandle,
318+
self.obj.windowHandle,
319+
location.left,
320+
location.top,
321+
location.right,
322+
location.bottom,
323+
self.minHorizontalWhitespace,
324+
self.minVerticalWhitespace,
325+
self.stripOuterWhitespace,
326+
self.includeDescendantWindows
327+
)
312328
if not text:
313329
return [], [], [], []
314330
text="<control>%s</control>"%text
@@ -436,15 +452,29 @@ def _normalizeFormatField(self,field):
436452

437453
def _getOffsetFromPoint(self, x, y):
438454
# Accepts physical coordinates.
439-
x,y=windowUtils.physicalToLogicalPoint(self.obj.windowHandle,x,y)
455+
try:
456+
x, y = windowUtils.physicalToLogicalPoint(
457+
self.obj.windowHandle,
458+
x,
459+
y
460+
)
461+
except RuntimeError:
462+
raise LookupError("physicalToLogicalPoint failed")
440463
for charOffset, (charLeft, charTop, charRight, charBottom) in enumerate(self._storyFieldsAndRects[1]):
441464
if charLeft<=x<charRight and charTop<=y<charBottom:
442465
return charOffset
443466
raise LookupError
444467

445468
def _getClosestOffsetFromPoint(self,x,y):
446469
# Accepts physical coordinates.
447-
x,y=windowUtils.physicalToLogicalPoint(self.obj.windowHandle,x,y)
470+
try:
471+
x, y = windowUtils.physicalToLogicalPoint(
472+
self.obj.windowHandle,
473+
x,
474+
y
475+
)
476+
except RuntimeError:
477+
raise LookupError("physicalToLogicalPoint failed")
448478
#Enumerate the character rectangles
449479
a=enumerate(self._storyFieldsAndRects[1])
450480
#Convert calculate center points for all the rectangles
@@ -462,7 +492,15 @@ def _getBoundingRectFromOffset(self, offset):
462492
rects=self._storyFieldsAndRects[1]
463493
if not rects or offset>=len(rects):
464494
raise LookupError
465-
return rects[offset].toPhysical(self.obj.windowHandle).toLTWH()
495+
rect = rects[offset].toLTWH()
496+
try:
497+
rect = rect.toPhysical(self.obj.windowHandle)
498+
except RuntimeError:
499+
raise LookupError(
500+
"Couldn't convert character rectangle at offset %d to physical coordinates"
501+
% offset
502+
)
503+
return rect
466504

467505
def _getNVDAObjectFromOffset(self,offset):
468506
try:
@@ -554,7 +592,15 @@ def _get_boundingRects(self):
554592
for lineEndOffset in lineEndOffsets:
555593
startOffset=endOffset
556594
endOffset=lineEndOffset
557-
rects.append(RectLTWH.fromCollection(*self._storyFieldsAndRects[1][startOffset:endOffset]).toPhysical(self.obj.windowHandle))
595+
lineRect = RectLTWH.fromCollection(*self._storyFieldsAndRects[1][startOffset:endOffset])
596+
try:
597+
lineRect = lineRect.toPhysical(self.obj.windowHandle)
598+
except RuntimeError:
599+
raise LookupError(
600+
f"Couldn't convert line rectangle at offsets {startOffset} to {endOffset} "
601+
"to physical coordinates"
602+
)
603+
rects.append(lineRect)
558604
return rects
559605

560606
def _getFirstVisibleOffset(self):
@@ -598,11 +644,15 @@ def _findCaretOffsetFromLocation(
598644
def _getCaretOffset(self):
599645
caretRect = getCaretRect(self.obj)
600646
objLocation = self.obj.location
601-
objRect = objLocation.toLTRB().toLogical(self.obj.windowHandle)
647+
try:
648+
objRect = objLocation.toLTRB().toLogical(self.obj.windowHandle)
649+
except RuntimeError:
650+
raise RuntimeError(
651+
"Couldn't convert object location to logical coordinates when getting caret offset"
652+
)
602653
caretRect = caretRect.intersection(objRect)
603654
if not any(caretRect):
604-
raise RuntimeError("The caret rectangle does not overlap with the window")
605-
# Find a character offset where the caret overlaps vertically, overlaps horizontally, overlaps the baseline and is totally within or on the correct side for the reading order
655+
raise RuntimeError("The caret rectangle does not overlap with the window") # Find a character offset where the caret overlaps vertically, overlaps horizontally, overlaps the baseline and is totally within or on the correct side for the reading order
606656
try:
607657
return self._findCaretOffsetFromLocation(caretRect,validateBaseline=True,validateDirection=True)
608658
except LookupError:
@@ -623,11 +673,14 @@ def _setCaretOffset(self,offset):
623673
if offset>=len(rects):
624674
raise RuntimeError("offset %d out of range")
625675
rect = rects[offset]
626-
x = rect.left
627-
y= rect.center.y
628-
x,y=windowUtils.logicalToPhysicalPoint(self.obj.windowHandle,x,y)
629-
oldX,oldY=winUser.getCursorPos()
630-
winUser.setCursorPos(x,y)
676+
# Place the cursor at the left coordinate of the character, vertically centered.
677+
point = Point(rect.left, rect.center.y)
678+
try:
679+
point = point.toPhysical(self.obj.windowHandle)
680+
except RuntimeError:
681+
raise RuntimeError("Conversion to physical coordinates failed when setting caret offset")
682+
oldX, oldY = winUser.getCursorPos()
683+
winUser.setCursorPos(*point)
631684
mouseHandler.executeMouseEvent(winUser.MOUSEEVENTF_LEFTDOWN,0,0)
632685
mouseHandler.executeMouseEvent(winUser.MOUSEEVENTF_LEFTUP,0,0)
633686
winUser.setCursorPos(oldX,oldY)

source/locationHelper.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,24 @@ def toRECT(self):
254254
return RECT(self.left,self.top,self.right,self.bottom)
255255

256256
def toLogical(self, hwnd):
257-
left,top=self.topLeft.toLogical(hwnd)
258-
right,bottom=self.bottomRight.toLogical(hwnd)
257+
try:
258+
left, top = self.topLeft.toLogical(hwnd)
259+
right, bottom = self.bottomRight.toLogical(hwnd)
260+
except RuntimeError:
261+
raise RuntimeError("Couldn't convert %s to logical coordinates" % str(self))
259262
if isinstance(self, RectLTWH):
260-
return RectLTWH(left,top,right-left,bottom-top)
261-
return RectLTRB(left,top,right,bottom)
263+
return RectLTWH(left, top, right - left, bottom - top)
264+
return RectLTRB(left, top, right, bottom)
262265

263266
def toPhysical(self, hwnd):
264-
left,top=self.topLeft.toPhysical(hwnd)
265-
right,bottom=self.bottomRight.toPhysical(hwnd)
267+
try:
268+
left, top = self.topLeft.toPhysical(hwnd)
269+
right, bottom = self.bottomRight.toPhysical(hwnd)
270+
except RuntimeError:
271+
raise RuntimeError("Couldn't convert %s to physical coordinates" % str(self))
266272
if isinstance(self, RectLTWH):
267-
return RectLTWH(left,top,right-left,bottom-top)
268-
return RectLTRB(left,top,right,bottom)
273+
return RectLTWH(left, top, right - left, bottom - top)
274+
return RectLTRB(left, top, right, bottom)
269275

270276
def toClient(self, hwnd):
271277
left, top =self.topLeft.toClient(hwnd)
@@ -274,7 +280,7 @@ def toClient(self, hwnd):
274280
return RectLTRB(left, top, left+self.width, top+self.height)
275281

276282
def toScreen(self, hwnd):
277-
left,top=self.topLeft.toScreen(hwnd)
283+
left, top = self.topLeft.toScreen(hwnd)
278284
if isinstance(self, RectLTWH):
279285
return RectLTWH(left, top, self.width, self.height)
280286
return RectLTRB(left, top, left+self.width, top+self.height)

source/winUser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,12 +608,14 @@ def VkKeyScanEx(ch, hkl):
608608

609609
def ScreenToClient(hwnd, x, y):
610610
point = POINT(x, y)
611-
user32.ScreenToClient(hwnd, byref(point))
611+
if not user32.ScreenToClient(hwnd, byref(point)):
612+
raise WinError()
612613
return point.x, point.y
613614

614615
def ClientToScreen(hwnd, x, y):
615616
point = POINT(x, y)
616-
user32.ClientToScreen(hwnd, byref(point))
617+
if not user32.ClientToScreen(hwnd, byref(point)):
618+
raise WinError()
617619
return point.x, point.y
618620

619621
def NotifyWinEvent(event, hwnd, idObject, idChild):

source/windowUtils.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,9 @@ def callback(window, data):
5454
_logicalToPhysicalPoint = ctypes.windll.user32.LogicalToPhysicalPointForPerMonitorDPI
5555
_physicalToLogicalPoint = ctypes.windll.user32.PhysicalToLogicalPointForPerMonitorDPI
5656
except AttributeError:
57-
try:
58-
# Windows Vista..Windows 8
59-
_logicalToPhysicalPoint = ctypes.windll.user32.LogicalToPhysicalPoint
60-
_physicalToLogicalPoint = ctypes.windll.user32.PhysicalToLogicalPoint
61-
except AttributeError:
62-
# Windows <= XP
63-
_logicalToPhysicalPoint = None
64-
_physicalToLogicalPoint = None
57+
# Windows Vista..Windows 8
58+
_logicalToPhysicalPoint = ctypes.windll.user32.LogicalToPhysicalPoint
59+
_physicalToLogicalPoint = ctypes.windll.user32.PhysicalToLogicalPoint
6560

6661
def logicalToPhysicalPoint(window, x, y):
6762
"""Converts the logical coordinates of a point in a window to physical coordinates.
@@ -74,10 +69,13 @@ def logicalToPhysicalPoint(window, x, y):
7469
@return: The physical x and y coordinates.
7570
@rtype: tuple of (int, int)
7671
"""
77-
if not _logicalToPhysicalPoint:
78-
return x, y
7972
point = ctypes.wintypes.POINT(x, y)
80-
_logicalToPhysicalPoint(window, ctypes.byref(point))
73+
if not _logicalToPhysicalPoint(window, ctypes.byref(point)):
74+
raise RuntimeError(
75+
"Couldn't convert point(x=%d,y=%d) from logical "
76+
"to physical coordinates for window %d"
77+
% (x, y, window)
78+
)
8179
return point.x, point.y
8280

8381
def physicalToLogicalPoint(window, x, y):
@@ -91,10 +89,11 @@ def physicalToLogicalPoint(window, x, y):
9189
@return: The logical x and y coordinates.
9290
@rtype: tuple of (int, int)
9391
"""
94-
if not _physicalToLogicalPoint:
95-
return x, y
9692
point = ctypes.wintypes.POINT(x, y)
97-
_physicalToLogicalPoint(window, ctypes.byref(point))
93+
if not _physicalToLogicalPoint(window, ctypes.byref(point)):
94+
raise RuntimeError(
95+
f"Couldn't convert point(x={x}, y={y}) from physical to logical coordinates for window {window}"
96+
)
9897
return point.x, point.y
9998

10099
DEFAULT_DPI_LEVEL = 96.0

0 commit comments

Comments
 (0)