11# A part of NonVisual Desktop Access (NVDA)
22# This file is covered by the GNU General Public License.
33# See the file COPYING for more details.
4- # Copyright (C) 2007-2022 NV Access Limited, Babbage B.V.
4+ # Copyright (C) 2007-2023 NV Access Limited, Babbage B.V., Joseph Lee
55
66import threading
77import typing
2222import extensionPoints
2323import oleacc
2424from utils .security import objectBelowLockScreenAndWindowsIsLocked
25+ import winVersion
2526
2627if typing .TYPE_CHECKING :
2728 import NVDAObjects
3738#: the last object queued for a gainFocus event. Useful for code running outside NVDA's core queue
3839lastQueuedFocusObject = None
3940
41+
42+ # Handle virtual desktop switch announcements in Windows 10 and later
43+ virtualDesktopName : Optional [str ] = None
44+ canAnnounceVirtualDesktopNames : bool = winVersion .getWinVer () >= winVersion .WIN10_1903
45+
46+
4047def queueEvent (eventName ,obj ,** kwargs ):
4148 """Queues an NVDA event to be executed.
4249 @param eventName: the name of the event type (e.g. 'gainFocus', 'nameChange')
@@ -287,19 +294,45 @@ def executeEvent(
287294 ):
288295 return
289296 try :
297+ global virtualDesktopName
290298 isGainFocus = eventName == "gainFocus"
291299 # Allow NVDAObjects to redirect focus events to another object of their choosing.
292300 if isGainFocus and obj .focusRedirect :
293- obj = obj .focusRedirect
294- sleepMode = obj .sleepMode
301+ obj = obj .focusRedirect
302+ sleepMode = obj .sleepMode
303+ # Handle possible virtual desktop name change event.
304+ # More effective in Windows 10 Version 1903 and later.
305+ if (
306+ eventName == "nameChange"
307+ and obj .windowClassName == "#32769"
308+ and canAnnounceVirtualDesktopNames
309+ ):
310+ import core
311+ virtualDesktopName = obj .name
312+ core .callLater (250 , handlePossibleDesktopNameChange )
295313 if isGainFocus and not doPreGainFocus (obj , sleepMode = sleepMode ):
296314 return
297- elif not sleepMode and eventName == "documentLoadComplete" and not doPreDocumentLoadComplete (obj ):
315+ elif not sleepMode and eventName == "documentLoadComplete" and not doPreDocumentLoadComplete (obj ):
298316 return
299317 elif not sleepMode :
300- _EventExecuter (eventName ,obj ,kwargs )
301- except :
302- log .exception ("error executing event: %s on %s with extra args of %s" % (eventName ,obj ,kwargs ))
318+ _EventExecuter (eventName , obj , kwargs )
319+ except Exception :
320+ log .exception (f"error executing event: { eventName } on { obj } with extra args of { kwargs } " )
321+
322+
323+ def handlePossibleDesktopNameChange () -> None :
324+ """
325+ Reports the new virtual desktop name if changed.
326+ On Windows versions lower than Windows 10, this function does nothing.
327+ """
328+ global virtualDesktopName
329+ # Virtual desktop switch announcement works more effectively in Version 1903 and later.
330+ if not canAnnounceVirtualDesktopNames :
331+ return
332+ if virtualDesktopName :
333+ import ui
334+ ui .message (virtualDesktopName )
335+ virtualDesktopName = None
303336
304337
305338def doPreGainFocus (obj : "NVDAObjects.NVDAObject" , sleepMode : bool = False ) -> bool :
@@ -310,8 +343,8 @@ def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bo
310343 shouldLog = config .conf ["debugLog" ]["events" ],
311344 ):
312345 return False
313- oldFocus = api .getFocusObject ()
314- oldTreeInterceptor = oldFocus .treeInterceptor if oldFocus else None
346+ oldFocus = api .getFocusObject ()
347+ oldTreeInterceptor = oldFocus .treeInterceptor if oldFocus else None
315348 if not api .setFocusObject (obj ):
316349 return False
317350 if speech .manager ._shouldCancelExpiredFocusEvents ():
@@ -338,25 +371,31 @@ def doPreGainFocus(obj: "NVDAObjects.NVDAObject", sleepMode: bool = False) -> bo
338371 # not the secure desktop.
339372 and not isinstance (obj , SecureDesktopNVDAObject )
340373 ):
341- newForeground = api .getDesktopObject ().objectInForeground ()
374+ newForeground = api .getDesktopObject ().objectInForeground ()
342375 if not newForeground :
343376 log .debugWarning ("Can not get real foreground, resorting to focus ancestors" )
344- ancestors = api .getFocusAncestors ()
345- if len (ancestors )> 1 :
346- newForeground = ancestors [1 ]
377+ ancestors = api .getFocusAncestors ()
378+ if len (ancestors ) > 1 :
379+ newForeground = ancestors [1 ]
347380 else :
348- newForeground = obj
381+ newForeground = obj
349382 if not api .setForegroundObject (newForeground ):
350383 return False
351384 executeEvent ('foreground' , newForeground )
352- if sleepMode : return True
353- #Fire focus entered events for all new ancestors of the focus if this is a gainFocus event
385+ handlePossibleDesktopNameChange ()
386+ if sleepMode :
387+ return True
388+ # Fire focus entered events for all new ancestors of the focus if this is a gainFocus event
354389 for parent in api .getFocusAncestors ()[api .getFocusDifferenceLevel ():]:
355- executeEvent ("focusEntered" ,parent )
390+ executeEvent ("focusEntered" , parent )
356391 if obj .treeInterceptor is not oldTreeInterceptor :
357- if hasattr (oldTreeInterceptor ,"event_treeInterceptor_loseFocus" ):
392+ if hasattr (oldTreeInterceptor , "event_treeInterceptor_loseFocus" ):
358393 oldTreeInterceptor .event_treeInterceptor_loseFocus ()
359- if obj .treeInterceptor and obj .treeInterceptor .isReady and hasattr (obj .treeInterceptor ,"event_treeInterceptor_gainFocus" ):
394+ if (
395+ obj .treeInterceptor
396+ and obj .treeInterceptor .isReady
397+ and hasattr (obj .treeInterceptor , "event_treeInterceptor_gainFocus" )
398+ ):
360399 obj .treeInterceptor .event_treeInterceptor_gainFocus ()
361400 return True
362401
0 commit comments