From af60207c652d4c3f00cc218e499cd86d62a0ab34 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sat, 6 May 2023 21:11:19 -0700 Subject: [PATCH 01/13] Event handler: update copyright header. Re #5641 --- source/eventHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index f322c45097f..8f1c000805c 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -1,7 +1,7 @@ # A part of NonVisual Desktop Access (NVDA) # This file is covered by the GNU General Public License. # See the file COPYING for more details. -# Copyright (C) 2007-2022 NV Access Limited, Babbage B.V. +# Copyright (C) 2007-2023 NV Access Limited, Babbage B.V., Joseph Lee import threading import typing From e10139f8e8cb865d5ad336e00b194fe0602cd182 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sat, 6 May 2023 22:28:11 -0700 Subject: [PATCH 02/13] Event handler/virtual desktops announcement: use a dedicated function to handle possible virtual desktop switches. Re #5641. Original work by Jamie Teh (Mozilla): define a dedicated function to handle posible (and real) Windows 10/11 virtual desktop switche announcements. This function checks to make sure this is Widows 10 or later, and if yes, announces desktop name as defined in the event handler module, and clears this string once announcements are done. The actual event responsible for placing virtual desktop name in event handler is name change event from CSRSS (client/server runtime subsystem/Windows subsystem) process, specificlaly desktop object. Handling name change event will be done as part of handling gain focus event, specifically when doing pre-gain focus routine. --- source/eventHandler.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/eventHandler.py b/source/eventHandler.py index 8f1c000805c..78840be0a19 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -302,6 +302,24 @@ def executeEvent( log.exception("error executing event: %s on %s with extra args of %s"%(eventName,obj,kwargs)) +virtualDesktopName = None + + +def handlePossibleDesktopNameChange(): + """ + Reports the new virtual desktop name if changed. + On Windows versions lower than Windows 10, this function does nothing. + """ + global virtualDesktopName + import winVersion + if winVersion.getWinVer() < winVersion.WIN10: + return + if virtualDesktopName: + import ui + ui.message(virtualDesktopName) + virtualDesktopName = None + + def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bool: from IAccessibleHandler import SecureDesktopNVDAObject From 743c6751c769c6f58e898e511a079686e0cb3956 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sat, 6 May 2023 22:32:14 -0700 Subject: [PATCH 03/13] Event handler/virtual desktop switch announcements: detect name change event coming from CSRSS/desktop object and treat as a possible virtual desktop switch. Re #5641. Original idea from Jamie Teh (Mozilla): in 'execute event' function, detect namechange coming from CSRSS/desktop object and treat it as a possible virtual desktop name change before handling focus events. If it turns out this is indeed a virtual desktop switch, set virtual desktop name, then queue handle possible virtual desktop switch routine (250 milliseconds) so desktop switches can be announced when opening/closing/switching virtual desktops while focused on the desktop. --- source/eventHandler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/eventHandler.py b/source/eventHandler.py index 78840be0a19..9ee3a842825 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -287,11 +287,17 @@ def executeEvent( ): return try: + global virtualDesktopName isGainFocus = eventName == "gainFocus" # Allow NVDAObjects to redirect focus events to another object of their choosing. if isGainFocus and obj.focusRedirect: obj=obj.focusRedirect sleepMode=obj.sleepMode + # Handle possible virtual desktop name change event. + if eventName == "nameChange" and obj.windowClassName == "#32769": + import core + virtualDesktopName = obj.name + core.callLater(250, handlePossibleDesktopNameChange) if isGainFocus and not doPreGainFocus(obj, sleepMode=sleepMode): return elif not sleepMode and eventName=="documentLoadComplete" and not doPreDocumentLoadComplete(obj): From b1d726b050f75febec1a42f7e4aed24d52649c65 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sat, 6 May 2023 22:40:52 -0700 Subject: [PATCH 04/13] Event handler/virtual desktop switch announcements: handle possible virtual desktop switches after handling new foreground event. Re #5641. Credit: Jamie Teh (Mozilla): handle possible virtual desktop switch announcements after handling new foreground announcement in 'do pre gain focus' routine. This allows virtual desktop names to be announced between actual desktop switch and handling gain focus event, more noticeable when creating new virtual desktops. --- source/eventHandler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/eventHandler.py b/source/eventHandler.py index 9ee3a842825..910856acc75 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -373,6 +373,7 @@ def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bo if not api.setForegroundObject(newForeground): return False executeEvent('foreground', newForeground) + handlePossibleDesktopNameChange() if sleepMode: return True #Fire focus entered events for all new ancestors of the focus if this is a gainFocus event for parent in api.getFocusAncestors()[api.getFocusDifferenceLevel():]: From 942f6ec54fdced71bb9d6e8849d3eab5a28cc23e Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sat, 6 May 2023 22:47:45 -0700 Subject: [PATCH 05/13] Event handler/virtual desktop switch announcement: limit announcement to Windows 10 Version 1903 and later. Re #5641. A note from Jamie Teh (Mozila): virtual desktop switch announcement works more effectively in Windows 10 Version 1903 (May 2019 Update) and later, therefore restrict virtual desktop announcement to that feature update and later. --- source/eventHandler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 910856acc75..6c5370289de 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -296,6 +296,10 @@ def executeEvent( # Handle possible virtual desktop name change event. if eventName == "nameChange" and obj.windowClassName == "#32769": import core + import winVersion + # More effective in Windows 10 Version 1903 and later. + if winVersion.getWinVer() < winVersion.WIN10_1903: + return virtualDesktopName = obj.name core.callLater(250, handlePossibleDesktopNameChange) if isGainFocus and not doPreGainFocus(obj, sleepMode=sleepMode): @@ -318,7 +322,8 @@ def handlePossibleDesktopNameChange(): """ global virtualDesktopName import winVersion - if winVersion.getWinVer() < winVersion.WIN10: + # Virtual desktop switch announcement works more effectively in Version 1903 and later. + if winVersion.getWinVer() < winVersion.WIN10_1903: return if virtualDesktopName: import ui From 804ffc7461c0096e5f903c5680f80727247e274a Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sat, 6 May 2023 23:40:35 -0700 Subject: [PATCH 06/13] Event handler/virtual desktop switch announcement and surrounding code: lint --- source/eventHandler.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 6c5370289de..d24cdaeec03 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -291,8 +291,8 @@ def executeEvent( isGainFocus = eventName == "gainFocus" # Allow NVDAObjects to redirect focus events to another object of their choosing. if isGainFocus and obj.focusRedirect: - obj=obj.focusRedirect - sleepMode=obj.sleepMode + obj = obj.focusRedirect + sleepMode = obj.sleepMode # Handle possible virtual desktop name change event. if eventName == "nameChange" and obj.windowClassName == "#32769": import core @@ -304,12 +304,12 @@ def executeEvent( core.callLater(250, handlePossibleDesktopNameChange) if isGainFocus and not doPreGainFocus(obj, sleepMode=sleepMode): return - elif not sleepMode and eventName=="documentLoadComplete" and not doPreDocumentLoadComplete(obj): + elif not sleepMode and eventName == "documentLoadComplete" and not doPreDocumentLoadComplete(obj): return elif not sleepMode: - _EventExecuter(eventName,obj,kwargs) + _EventExecuter(eventName, obj, kwargs) except: - log.exception("error executing event: %s on %s with extra args of %s"%(eventName,obj,kwargs)) + log.exception("error executing event: %s on %s with extra args of %s" % (eventName, obj, kwargs)) virtualDesktopName = None @@ -339,8 +339,8 @@ def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bo shouldLog=config.conf["debugLog"]["events"], ): return False - oldFocus=api.getFocusObject() - oldTreeInterceptor=oldFocus.treeInterceptor if oldFocus else None + oldFocus = api.getFocusObject() + oldTreeInterceptor = oldFocus.treeInterceptor if oldFocus else None if not api.setFocusObject(obj): return False if speech.manager._shouldCancelExpiredFocusEvents(): @@ -367,26 +367,31 @@ def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bo # not the secure desktop. and not isinstance(obj, SecureDesktopNVDAObject) ): - newForeground=api.getDesktopObject().objectInForeground() + newForeground = api.getDesktopObject().objectInForeground() if not newForeground: log.debugWarning("Can not get real foreground, resorting to focus ancestors") - ancestors=api.getFocusAncestors() - if len(ancestors)>1: - newForeground=ancestors[1] + ancestors = api.getFocusAncestors() + if len(ancestors) > 1: + newForeground = ancestors[1] else: - newForeground=obj + newForeground = obj if not api.setForegroundObject(newForeground): return False executeEvent('foreground', newForeground) handlePossibleDesktopNameChange() - if sleepMode: return True - #Fire focus entered events for all new ancestors of the focus if this is a gainFocus event + if sleepMode: + return True + # Fire focus entered events for all new ancestors of the focus if this is a gainFocus event for parent in api.getFocusAncestors()[api.getFocusDifferenceLevel():]: - executeEvent("focusEntered",parent) + executeEvent("focusEntered", parent) if obj.treeInterceptor is not oldTreeInterceptor: - if hasattr(oldTreeInterceptor,"event_treeInterceptor_loseFocus"): + if hasattr(oldTreeInterceptor, "event_treeInterceptor_loseFocus"): oldTreeInterceptor.event_treeInterceptor_loseFocus() - if obj.treeInterceptor and obj.treeInterceptor.isReady and hasattr(obj.treeInterceptor,"event_treeInterceptor_gainFocus"): + if ( + obj.treeInterceptor + and obj.treeInterceptor.isReady + and hasattr(obj.treeInterceptor, "event_treeInterceptor_gainFocus") + ): obj.treeInterceptor.event_treeInterceptor_gainFocus() return True From 5e11bf26f426dbcf00aae9689d05a25dc370f583 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sun, 7 May 2023 00:00:32 -0700 Subject: [PATCH 07/13] Event handler/virtual desktop switch announcement: use formatted string literals when logging exceptions with execute event routine. --- source/eventHandler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index d24cdaeec03..48c5fc57712 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -308,8 +308,8 @@ def executeEvent( return elif not sleepMode: _EventExecuter(eventName, obj, kwargs) - except: - log.exception("error executing event: %s on %s with extra args of %s" % (eventName, obj, kwargs)) + except Exception: + log.exception(f"error executing event: {eventName} on {obj} with extra args of {kwargs}") virtualDesktopName = None From 10528b9d505c1c5a99bbc56b913bab3ab284a926 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sun, 7 May 2023 00:02:36 -0700 Subject: [PATCH 08/13] Event handler/virtual desktop switch announcement: add typing info for virtual desktop name field and handle possible desktop switch function. Re #5641 --- source/eventHandler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 48c5fc57712..146490916cb 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -312,10 +312,10 @@ def executeEvent( log.exception(f"error executing event: {eventName} on {obj} with extra args of {kwargs}") -virtualDesktopName = None +virtualDesktopName: Optional[str] = None -def handlePossibleDesktopNameChange(): +def handlePossibleDesktopNameChange() -> None: """ Reports the new virtual desktop name if changed. On Windows versions lower than Windows 10, this function does nothing. From 7d598c893640601f345a0a724ee02be0c80b8dca Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Sun, 7 May 2023 00:13:26 -0700 Subject: [PATCH 09/13] Event handler/virtual desktop switch announcement: use a dedicated variable to detect Windows 10 1903 and later. Re #5641. In addition to placing winVersion module import at the top of the file, use a dedicated flag to indicate support for virtual desktop switch announcements in Windows 10 Version 1903 (May 2019 Update) and later. --- source/eventHandler.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 146490916cb..9e79ce64395 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -22,6 +22,7 @@ import extensionPoints import oleacc from utils.security import objectBelowLockScreenAndWindowsIsLocked +import winVersion if typing.TYPE_CHECKING: import NVDAObjects @@ -294,12 +295,13 @@ def executeEvent( obj = obj.focusRedirect sleepMode = obj.sleepMode # Handle possible virtual desktop name change event. - if eventName == "nameChange" and obj.windowClassName == "#32769": + # More effective in Windows 10 Version 1903 and later. + if ( + eventName == "nameChange" + and obj.windowClassName == "#32769" + and canAnnounceVirtualDesktopNames + ): import core - import winVersion - # More effective in Windows 10 Version 1903 and later. - if winVersion.getWinVer() < winVersion.WIN10_1903: - return virtualDesktopName = obj.name core.callLater(250, handlePossibleDesktopNameChange) if isGainFocus and not doPreGainFocus(obj, sleepMode=sleepMode): @@ -313,6 +315,7 @@ def executeEvent( virtualDesktopName: Optional[str] = None +canAnnounceVirtualDesktopNames: bool = winVersion.getWinVer() >= winVersion.WIN10_1903 def handlePossibleDesktopNameChange() -> None: @@ -321,9 +324,8 @@ def handlePossibleDesktopNameChange() -> None: On Windows versions lower than Windows 10, this function does nothing. """ global virtualDesktopName - import winVersion # Virtual desktop switch announcement works more effectively in Version 1903 and later. - if winVersion.getWinVer() < winVersion.WIN10_1903: + if not canAnnounceVirtualDesktopNames: return if virtualDesktopName: import ui From 9611eba43beac3756806ce293fc72dc9fb262e9c Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Mon, 15 May 2023 23:20:02 -0700 Subject: [PATCH 10/13] Event handler: move virtual desktop constants to the top of the file. Reported by several testers: in some cases, NVDA fails to start due to type hint scope issue if a constant is placed between functions. Therefore, move virtual desktops handling to top fo the file next to other constants and data structures. --- source/eventHandler.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 9e79ce64395..85005864347 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -38,6 +38,12 @@ #: the last object queued for a gainFocus event. Useful for code running outside NVDA's core queue lastQueuedFocusObject=None + +# Handle virtual desktop switch announcements in Windows 10 and later +virtualDesktopName: Optional[str] = None +canAnnounceVirtualDesktopNames: bool = winVersion.getWinVer() >= winVersion.WIN10_1903 + + def queueEvent(eventName,obj,**kwargs): """Queues an NVDA event to be executed. @param eventName: the name of the event type (e.g. 'gainFocus', 'nameChange') @@ -314,10 +320,6 @@ def executeEvent( log.exception(f"error executing event: {eventName} on {obj} with extra args of {kwargs}") -virtualDesktopName: Optional[str] = None -canAnnounceVirtualDesktopNames: bool = winVersion.getWinVer() >= winVersion.WIN10_1903 - - def handlePossibleDesktopNameChange() -> None: """ Reports the new virtual desktop name if changed. From f5293e05cb47d89df26a084299a9d183d3a7b080 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Wed, 17 May 2023 01:24:56 -0700 Subject: [PATCH 11/13] Event handler: lint (Flake8 ET126) --- source/eventHandler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 85005864347..1d919ac18b0 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -303,9 +303,9 @@ def executeEvent( # Handle possible virtual desktop name change event. # More effective in Windows 10 Version 1903 and later. if ( - eventName == "nameChange" - and obj.windowClassName == "#32769" - and canAnnounceVirtualDesktopNames + eventName == "nameChange" + and obj.windowClassName == "#32769" + and canAnnounceVirtualDesktopNames ): import core virtualDesktopName = obj.name From c7399423f31c817a40af0abb93ba9c70924b6995 Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Thu, 1 Jun 2023 05:46:59 +0900 Subject: [PATCH 12/13] Event handler/virtual desktop announcements: make virtual desktop name and announcement check variables private. Re #5641. Reviewed by Sean Budd (NV Access): make virtual desktop name and can nanouncement virtual desktop name variables private - at the moment no add-on appears to need this functionality but this can change if requested by add-on authors. --- source/eventHandler.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/eventHandler.py b/source/eventHandler.py index 1d919ac18b0..d148e1d6cac 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.py @@ -40,8 +40,8 @@ # Handle virtual desktop switch announcements in Windows 10 and later -virtualDesktopName: Optional[str] = None -canAnnounceVirtualDesktopNames: bool = winVersion.getWinVer() >= winVersion.WIN10_1903 +_virtualDesktopName: Optional[str] = None +_canAnnounceVirtualDesktopNames: bool = winVersion.getWinVer() >= winVersion.WIN10_1903 def queueEvent(eventName,obj,**kwargs): @@ -294,7 +294,7 @@ def executeEvent( ): return try: - global virtualDesktopName + global _virtualDesktopName isGainFocus = eventName == "gainFocus" # Allow NVDAObjects to redirect focus events to another object of their choosing. if isGainFocus and obj.focusRedirect: @@ -305,10 +305,10 @@ def executeEvent( if ( eventName == "nameChange" and obj.windowClassName == "#32769" - and canAnnounceVirtualDesktopNames + and _canAnnounceVirtualDesktopNames ): import core - virtualDesktopName = obj.name + _virtualDesktopName = obj.name core.callLater(250, handlePossibleDesktopNameChange) if isGainFocus and not doPreGainFocus(obj, sleepMode=sleepMode): return @@ -325,14 +325,14 @@ def handlePossibleDesktopNameChange() -> None: Reports the new virtual desktop name if changed. On Windows versions lower than Windows 10, this function does nothing. """ - global virtualDesktopName + global _virtualDesktopName # Virtual desktop switch announcement works more effectively in Version 1903 and later. - if not canAnnounceVirtualDesktopNames: + if not _canAnnounceVirtualDesktopNames: return - if virtualDesktopName: + if _virtualDesktopName: import ui - ui.message(virtualDesktopName) - virtualDesktopName = None + ui.message(_virtualDesktopName) + _virtualDesktopName = None def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bool: From 293bad6cca335d5561230a15becc3176089a3335 Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Mon, 5 Jun 2023 14:04:15 +1000 Subject: [PATCH 13/13] update changes --- user_docs/en/changes.t2t | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 20bea4e1494..fe0a915f22d 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -26,6 +26,7 @@ There are now gestures for showing the braille settings dialog, accessing the st - NVDA now outputs audio via the Windows Audio Session API (WASAPI), which may improve the responsiveness, performance and stability of NVDA speech and sounds. This can be disabled in Advanced settings if audio problems are encountered. (#14697) - When using Excel shortcuts to toggle format such as bold, italic, underline and strike through of a cell in Excel, the result is now reported. (#14923) - Added support for the Help Tech Activator Braille display. (#14917) +- In Windows 10 May 2019 Update and later, NVDA can announe virtual desktop names when opening, changing, and closing them. (#5641) - @@ -39,7 +40,8 @@ There are now gestures for showing the braille settings dialog, accessing the st - - Distance reported in Microsoft Word will now honour the unit defined in Word's advanced options even when using UIA to access Word documents. (#14542) - NVDA responds faster when moving the cursor in edit controls. (#14708) -- Baum Braille driver: addes several Braille chord gestures for performing common keyboard commands such as windows+d, alt+tab etc. Please refer to the NVDA user guide for a full list. (#14714) +- Baum Braille driver: addes several Braille chord gestures for performing common keyboard commands such as ``windows+d``, ``alt+tab`` etc. +Please refer to the NVDA user guide for a full list. (#14714) - When using a Braille Display via the Standard HID braille driver, the dpad can be used to emulate the arrow keys and enter. Also space+dot1 and space+dot4 now map to up and down arrow respectively. (#14713) - Script for reporting the destination of a link now reports from the caret / focus position rather than the navigator object. (#14659) - Portable copy creation no longer requires that a drive letter be entered as part of the absolute path. (#14681)