Skip to content

Commit 7f96082

Browse files
authored
Merge a271d6f into f47eb66
2 parents f47eb66 + a271d6f commit 7f96082

10 files changed

Lines changed: 433 additions & 188 deletions

File tree

source/NVDAObjects/behaviors.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ def event_valueChange(self):
6363
self.progressValueCache["beep,%d,%d"%(x,y)]=percentage
6464
lastSpeechProgressValue=self.progressValueCache.get("speech,%d,%d"%(x,y),None)
6565
if pbConf["progressBarOutputMode"] in ("speak","both") and (lastSpeechProgressValue is None or abs(percentage-lastSpeechProgressValue)>=pbConf["speechPercentageInterval"]):
66-
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakMessage,_("%d percent")%percentage)
66+
queueHandler.queueFunction(
67+
queueHandler.eventQueue,
68+
speech.speakMessage,
69+
# Translators: This is presented to inform the user of a progress bar percentage.
70+
_("%d percent") % percentage,
71+
)
6772
self.progressValueCache["speech,%d,%d"%(x,y)]=percentage
6873

6974
class Dialog(NVDAObject):

source/bdDetect.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
#bdDetect.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#This file is covered by the GNU General Public License.
4-
#See the file COPYING for more details.
5-
#Copyright (C) 2013-2017 NV Access Limited
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# This file is covered by the GNU General Public License.
3+
# See the file COPYING for more details.
4+
# Copyright (C) 2013-2022 NV Access Limited
65

76
"""Support for braille display detection.
87
This allows devices to be automatically detected and used when they become available,
@@ -18,19 +17,16 @@
1817
import threading
1918

2019
import typing
21-
import wx
2220
import hwPortUtils
2321
import braille
2422
import winKernel
2523
import winUser
26-
import core
27-
import ctypes
2824
from logHandler import log
2925
import config
30-
import time
3126
import appModuleHandler
3227
from baseObject import AutoPropertyObject
3328
import re
29+
from winAPI import messageWindow
3430

3531

3632
HID_USAGE_PAGE_BRAILLE = 0x41
@@ -256,7 +252,7 @@ def __init__(self, usb=True, bluetooth=True, limitToDevices=None):
256252
self._BgScanApc = winKernel.PAPCFUNC(self._bgScan)
257253
self._btDevsLock = threading.Lock()
258254
self._btDevs = None
259-
core.post_windowMessageReceipt.register(self.handleWindowMessage)
255+
messageWindow.post_windowMessageReceipt.register(self.handleWindowMessage)
260256
appModuleHandler.post_appSwitch.register(self.pollBluetoothDevices)
261257
self._stopEvent = threading.Event()
262258
self._queuedScanLock = threading.Lock()
@@ -390,7 +386,7 @@ def pollBluetoothDevices(self):
390386

391387
def terminate(self):
392388
appModuleHandler.post_appSwitch.unregister(self.pollBluetoothDevices)
393-
core.post_windowMessageReceipt.unregister(self.handleWindowMessage)
389+
messageWindow.post_windowMessageReceipt.unregister(self.handleWindowMessage)
394390
self._stopBgScan()
395391

396392

source/core.py

Lines changed: 17 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from dataclasses import dataclass
1111
from typing import (
12+
Any,
1213
List,
1314
Optional,
1415
)
@@ -29,6 +30,20 @@
2930
import garbageHandler
3031

3132

33+
def __getattr__(attrName: str) -> Any:
34+
"""Module level `__getattr__` used to preserve backward compatibility.
35+
"""
36+
if attrName == "post_windowMessageReceipt" and globalVars._allowDeprecatedAPI:
37+
from winAPI.messageWindow import post_windowMessageReceipt
38+
log.warning(
39+
"core.post_windowMessageReceipt is deprecated, "
40+
"use winAPI.messageWindow.post_windowMessageReceipt instead."
41+
)
42+
return post_windowMessageReceipt
43+
raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}")
44+
45+
46+
3247
# inform those who want to know that NVDA has finished starting up.
3348
postNvdaStartup = extensionPoints.Action()
3449

@@ -37,19 +52,6 @@
3752
#: The thread identifier of the main thread.
3853
mainThreadId = threading.get_ident()
3954

40-
#: Notifies when a window message has been received by NVDA.
41-
#: This allows components to perform an action when several system events occur,
42-
#: such as power, screen orientation and hardware changes.
43-
#: Handlers are called with three arguments.
44-
#: @param msg: The window message.
45-
#: @type msg: int
46-
#: @param wParam: Additional message information.
47-
#: @type wParam: int
48-
#: @param lParam: Additional message information.
49-
#: @type lParam: int
50-
# TODO: move to winAPI.messageWindow
51-
post_windowMessageReceipt = extensionPoints.Action()
52-
5355
_pump = None
5456
_isPumpPending = False
5557

@@ -549,131 +551,9 @@ def onEndSession(evt):
549551
# the GUI mainloop must be running for this to work so delay it
550552
wx.CallAfter(audioDucking.initialize)
551553

552-
from winAPI.messageWindow import WindowMessage
553-
from winAPI.sessionTracking import (
554-
handleSessionChange,
555-
registerSessionNotification,
556-
unregisterSessionNotification,
557-
WindowsTrackedSession,
558-
)
559-
import winUser
560-
# #3763: In wxPython 3, the class name of frame windows changed from wxWindowClassNR to wxWindowNR.
561-
# NVDA uses the main frame to check for and quit another instance of NVDA.
562-
# To remain compatible with older versions of NVDA, create our own wxWindowClassNR.
563-
# We don't need to do anything else because wx handles WM_QUIT for all windows.
564-
import windowUtils
565-
# TODO: move to winAPI.messageWindow
566-
class MessageWindow(windowUtils.CustomWindow):
567-
className = u"wxWindowClassNR"
568-
# Windows constants for power / display changes
569-
# TODO: move to winAPI
570-
PBT_APMPOWERSTATUSCHANGE = 0xA
571-
UNKNOWN_BATTERY_STATUS = 0xFF
572-
AC_ONLINE = 0X1
573-
NO_SYSTEM_BATTERY = 0X80
574-
#States for screen orientation
575-
# TODO: move to winAPI, turn to Enum
576-
ORIENTATION_NOT_INITIALIZED = 0
577-
ORIENTATION_PORTRAIT = 1
578-
ORIENTATION_LANDSCAPE = 2
579-
580-
def __init__(self, windowName=None):
581-
super(MessageWindow, self).__init__(windowName)
582-
self.oldBatteryStatus = None
583-
self.orientationStateCache = self.ORIENTATION_NOT_INITIALIZED
584-
self.orientationCoordsCache = (0,0)
585-
self.handlePowerStatusChange()
586-
587-
# Call must be paired with a call to unregisterSessionNotification
588-
self._isSessionTrackingRegistered = registerSessionNotification(self.handle)
589-
590-
def warnIfSessionTrackingNotRegistered(self) -> None:
591-
if self._isSessionTrackingRegistered:
592-
return
593-
failedToRegisterMsg = _(
594-
# Translators: This is a warning to users, shown if NVDA cannot determine if
595-
# Windows is locked.
596-
"NVDA failed to register session tracking. "
597-
"While this instance of NVDA is running, "
598-
"your desktop will not be secure when Windows is locked. "
599-
"Restart NVDA? "
600-
)
601-
if wx.YES == gui.messageBox(
602-
failedToRegisterMsg,
603-
# Translators: This is a warning to users, shown if NVDA cannot determine if
604-
# Windows is locked.
605-
caption=_("NVDA could not start securely."),
606-
style=wx.ICON_ERROR | wx.YES_NO,
607-
):
608-
restart()
609-
610-
def destroy(self):
611-
"""
612-
NVDA must unregister session tracking before destroying the message window.
613-
"""
614-
if self._isSessionTrackingRegistered:
615-
# Requires an active message window and a handle to unregister.
616-
unregisterSessionNotification(self.handle)
617-
super().destroy()
618-
619-
def windowProc(self, hwnd, msg, wParam, lParam):
620-
post_windowMessageReceipt.notify(msg=msg, wParam=wParam, lParam=lParam)
621-
if msg == WindowMessage.POWERBROADCAST and wParam == self.PBT_APMPOWERSTATUSCHANGE:
622-
self.handlePowerStatusChange()
623-
elif msg == winUser.WM_DISPLAYCHANGE:
624-
self.handleScreenOrientationChange(lParam)
625-
elif msg == WindowMessage.WTSSESSION_CHANGE:
626-
# If we are receiving WTSSESSION_CHANGE events, _isSessionTrackingRegistered should be True
627-
handleSessionChange(WindowsTrackedSession(wParam), lParam)
628-
629-
def handleScreenOrientationChange(self, lParam):
630-
# TODO: move to winAPI
631-
import ui
632-
# Resolution detection comes from an article found at https://msdn.microsoft.com/en-us/library/ms812142.aspx.
633-
#The low word is the width and hiword is height.
634-
width = winUser.LOWORD(lParam)
635-
height = winUser.HIWORD(lParam)
636-
self.orientationCoordsCache = (width,height)
637-
if width > height:
638-
# If the height and width are the same, it's actually a screen flip, and we do want to alert of those!
639-
if self.orientationStateCache == self.ORIENTATION_LANDSCAPE and self.orientationCoordsCache != (width,height):
640-
return
641-
#Translators: The screen is oriented so that it is wider than it is tall.
642-
ui.message(_("Landscape" ))
643-
self.orientationStateCache = self.ORIENTATION_LANDSCAPE
644-
else:
645-
if self.orientationStateCache == self.ORIENTATION_PORTRAIT and self.orientationCoordsCache != (width,height):
646-
return
647-
#Translators: The screen is oriented in such a way that the height is taller than it is wide.
648-
ui.message(_("Portrait"))
649-
self.orientationStateCache = self.ORIENTATION_PORTRAIT
650-
651-
def handlePowerStatusChange(self):
652-
# TODO: move to winAPI
653-
#Mostly taken from script_say_battery_status, but modified.
654-
import ui
655-
import winKernel
656-
sps = winKernel.SYSTEM_POWER_STATUS()
657-
if not winKernel.GetSystemPowerStatus(sps) or sps.BatteryFlag is self.UNKNOWN_BATTERY_STATUS:
658-
return
659-
if sps.BatteryFlag & self.NO_SYSTEM_BATTERY:
660-
return
661-
if self.oldBatteryStatus is None:
662-
#Just initializing the cache, do not report anything.
663-
self.oldBatteryStatus = sps.ACLineStatus
664-
return
665-
if sps.ACLineStatus == self.oldBatteryStatus:
666-
#Sometimes, this double fires. This also fires when the battery level decreases by 3%.
667-
return
668-
self.oldBatteryStatus = sps.ACLineStatus
669-
if sps.ACLineStatus & self.AC_ONLINE:
670-
#Translators: Reported when the battery is plugged in, and now is charging.
671-
ui.message(_("Charging battery. %d percent") % sps.BatteryLifePercent)
672-
else:
673-
#Translators: Reported when the battery is no longer plugged in, and now is not charging.
674-
ui.message(_("Not charging battery. %d percent") %sps.BatteryLifePercent)
554+
from winAPI.messageWindow import _MessageWindow
675555
import versionInfo
676-
messageWindow = MessageWindow(versionInfo.name)
556+
messageWindow = _MessageWindow(versionInfo.name)
677557

678558
# initialize wxpython localization support
679559
wxLocaleObj = wx.Locale()

source/globalCommands.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import characterProcessing
4747
from baseObject import ScriptableObject
4848
import core
49+
from winAPI.powerTracking import reportCurrentBatteryStatus
4950
import winVersion
5051
from base64 import b16encode
5152
import vision
@@ -2515,26 +2516,8 @@ def script_toggleAutoFocusFocusableElements(self,gesture):
25152516
category=SCRCAT_SYSTEM,
25162517
gesture="kb:NVDA+shift+b"
25172518
)
2518-
def script_say_battery_status(self,gesture):
2519-
UNKNOWN_BATTERY_STATUS = 0xFF
2520-
AC_ONLINE = 0X1
2521-
NO_SYSTEM_BATTERY = 0X80
2522-
sps = winKernel.SYSTEM_POWER_STATUS()
2523-
if not winKernel.GetSystemPowerStatus(sps) or sps.BatteryFlag is UNKNOWN_BATTERY_STATUS:
2524-
log.error("error accessing system power status")
2525-
return
2526-
if sps.BatteryFlag & NO_SYSTEM_BATTERY:
2527-
# Translators: This is presented when there is no battery such as desktop computers and laptops with battery pack removed.
2528-
ui.message(_("No system battery"))
2529-
return
2530-
# Translators: This is presented to inform the user of the current battery percentage.
2531-
text = _("%d percent") % sps.BatteryLifePercent + " "
2532-
# Translators: This is presented when AC power is connected such as when recharging a laptop battery.
2533-
if sps.ACLineStatus & AC_ONLINE: text += _("AC power on")
2534-
elif sps.BatteryLifeTime!=0xffffffff:
2535-
# Translators: This is the estimated remaining runtime of the laptop battery.
2536-
text += _("{hours:d} hours and {minutes:d} minutes remaining") .format(hours=sps.BatteryLifeTime // 3600, minutes=(sps.BatteryLifeTime % 3600) // 60)
2537-
ui.message(text)
2519+
def script_say_battery_status(self, gesture: inputCore.InputGesture) -> None:
2520+
reportCurrentBatteryStatus()
25382521

25392522
@script(
25402523
description=_(

source/visionEnhancementProviders/NVDAHighlighter.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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) 2018-2019 NV Access Limited, Babbage B.V., Takuya Nishimoto
4+
# Copyright (C) 2018-2022 NV Access Limited, Babbage B.V., Takuya Nishimoto
55

66
"""Default highlighter based on GDI Plus."""
77
from typing import Optional, Tuple
@@ -25,6 +25,7 @@
2525
from locationHelper import RectLTWH
2626
from collections import namedtuple
2727
import threading
28+
from winAPI.messageWindow import WindowMessage
2829
import winGDI
2930
import weakref
3031
from colors import RGB
@@ -143,7 +144,7 @@ def windowProc(self, hwnd, msg, wParam, lParam):
143144
winUser.user32.PostQuitMessage(0)
144145
elif msg == winUser.WM_TIMER:
145146
self.refresh()
146-
elif msg == winUser.WM_DISPLAYCHANGE:
147+
elif msg == WindowMessage.DISPLAY_CHANGE:
147148
# wx might not be aware of the display change at this point
148149
core.callLater(100, self.updateLocationForDisplays)
149150

source/winAPI/displayTracking.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2022 NV Access Limited
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
6+
"""
7+
Tracking was introduced so that NVDA has a mechanism to announce changes to the device orientation.
8+
9+
When the display resolution changes, the new height and width is sent to NVDA,
10+
and we notify the user of changes to the orientation.
11+
"""
12+
13+
from dataclasses import dataclass
14+
import enum
15+
16+
import ui
17+
import winUser
18+
19+
20+
class Orientation(enum.Enum):
21+
NOT_INITIALIZED = enum.auto()
22+
PORTRAIT = enum.auto()
23+
LANDSCAPE = enum.auto()
24+
25+
26+
@dataclass
27+
class OrientationState:
28+
width: int = 0
29+
height: int = 0
30+
style: Orientation = Orientation.NOT_INITIALIZED
31+
32+
33+
_orientationState = OrientationState()
34+
35+
36+
def reportScreenOrientationChange(heightWidth: int) -> None:
37+
"""
38+
Reports the screen orientation only if the screen orientation has changed.
39+
"""
40+
# Resolution detection comes from an article found at https://msdn.microsoft.com/en-us/library/ms812142.aspx.
41+
width = winUser.LOWORD(heightWidth)
42+
height = winUser.HIWORD(heightWidth)
43+
if width > height:
44+
# The new orientation is landscape
45+
if (
46+
# Orientation has changed
47+
_orientationState.style != Orientation.LANDSCAPE
48+
# If the height and width are the same, it's a screen flip
49+
or (_orientationState.height == height and _orientationState.width == width)
50+
):
51+
# Translators: The screen is oriented so that it is wider than it is tall.
52+
ui.message(_("Landscape"))
53+
_orientationState.style = Orientation.LANDSCAPE
54+
else:
55+
# The new orientation is portrait
56+
if (
57+
# Orientation has changed
58+
_orientationState.style != Orientation.PORTRAIT
59+
# If the height and width are the same, it's a screen flip
60+
or (_orientationState.height == height and _orientationState.width == width)
61+
):
62+
# Translators: The screen is oriented in such a way that the height is taller than it is wide.
63+
ui.message(_("Portrait"))
64+
_orientationState.style = Orientation.PORTRAIT
65+
66+
_orientationState.height = height
67+
_orientationState.width = width

0 commit comments

Comments
 (0)