def getVolumeObject(): CLSID_MMDeviceEnumerator = GUID('{BCDE0395-E52F-467C-8E3D-C4579291692E}') deviceEnumerator = CoCreateInstance(CLSID_MMDeviceEnumerator, IMMDeviceEnumerator, 1) volObj = cast( deviceEnumerator.GetDefaultAudioEndpoint(0, 1).Activate(IAudioEndpointVolume._iid_, 7, None), POINTER(IAudioEndpointVolume)) return volObj
def __init__(self, DEVICE_STATE=DEVICE_STATE_ACTIVE, PKEY_Device=PKEY_Device_FriendlyName): self.DEVICE_STATE = DEVICE_STATE self.PKEY_Device = PKEY_Device self.pDevEnum = CoCreateInstance(_CLSID_MMDeviceEnumerator, _IMMDeviceEnumerator, CLSCTX_INPROC_SERVER) self.pPolicyConfig = None
def SetDefault(self, endpoint, role=eConsole): OldDefault = self.GetDefault(role) if not self.pPolicyConfig: self.pPolicyConfig = CoCreateInstance( CLSID_CPolicyConfigVistaClient, IPolicyConfigVista, CLSCTX_ALL) hr = self.pPolicyConfig.SetDefaultEndpoint(endpoint.getId(), role) if hr: import win32api print('SetDefaultEndpoint', win32api.FormatMessage(hr)) return OldDefault
def __init__(self): self._finalIndex = None self._bufSink = SynthDriverBufSink(weakref.ref(self)) self._bufSinkPtr = self._bufSink.QueryInterface(ITTSBufNotifySink) # HACK: Some buggy engines call Release() too many times on our buf sink. # Therefore, don't let the buf sink be deleted before we release it ourselves. self._bufSink._allowDelete = False self._ttsEngines = CoCreateInstance(CLSID_TTSEnumerator, ITTSEnumW) self._enginesList = self._fetchEnginesList() if len(self._enginesList) == 0: raise RuntimeError("No Sapi4 engines available") self.voice = str(self._enginesList[0].gModeID)
def GetSpeaker(id: Optional[str] = None) -> pycaw.IMMDevice: """Get speakers by its ID (render + multimedia) device. @param id: audio device ID @type id: str @return: pointer to the detected audio device @rtype: pycaw.IMMDevice """ device_enumerator = CoCreateInstance(pycaw.CLSID_MMDeviceEnumerator, pycaw.IMMDeviceEnumerator, CLSCTX_INPROC_SERVER) if id is not None: speakers = device_enumerator.GetDevice(id) else: speakers = device_enumerator.GetDefaultAudioEndpoint( pycaw.EDataFlow.eRender.value, pycaw.ERole.eMultimedia.value) return speakers
class DesktopWallpaper(ModelNode): '''The wallpaper node on Windows.''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__idw = CoCreateInstance( desktopwallpaper.CLSID_DesktopWallpaper, interface=desktopwallpaper.IDesktopWallpaper ) @WaveSynScriptAPI def set(self, path:datatypes.ArgOpenFile, monitor_id=None): '''Set wallpaper. path: a given image which will be set as the wallpaper. monitor_id: the id of the monitor of which the wallpaper will be set. None for default monitor setting.''' self.__idw.SetWallpaper(monitor_id, path) @WaveSynScriptAPI def get(self, monitor_id=None): '''Get the path of the current wallpaper. monitor_id: the id of the monitor. Default: None.''' return Path(self.__idw.GetWallpaper(monitor_id)) @WaveSynScriptAPI def get_background_color(self): return self.__idw.GetBackgroundColor() @property def position(self): return self.__idw.GetPosition() @position.setter def position(self, val): self.__idw.SetPosition(val)
class TaskbarIcon(object): def __init__(self, root): self.__tbm = CoCreateInstance(GUID_CTaskbarList, interface=ITaskbarList4) self.__root = root @property def progress(self): '''Not implemented''' pass @progress.setter def progress(self, value): self.__tbm.SetProgressValue(GetParent(self.__root.winfo_id()), int(value), 100) @property def state(self): '''Not Implemented''' pass @state.setter def state(self, state): self.__tbm.SetProgressState(GetParent(self.__root.winfo_id()), state)
def __init(): global __uia, __root_element __uia = CoCreateInstance(CUIAutomation._reg_clsid_, interface=IUIAutomation, clsctx=comtypes.CLSCTX_INPROC_SERVER) __root_element = __uia.GetRootElement()
except hy.errors.HyCompileError: # After the bytecode file generated, we can import the module written by hy. interfaces_path = Path(__file__).parent / 'interfaces.hy' os.system(f'hyc {interfaces_path}') from wavesynlib.interfaces.os.windows.shell.taskbarmanager.interfaces\ import ( ITaskbarList, ITaskbarList2, ITaskbarList3, ITaskbarList4) GUID_CTaskbarList = GUID('{56FDF344-FD6D-11d0-958A-006097C9A090}') if __name__ == '__main__': from tkinter import Tk, Scale from comtypes import CoCreateInstance from win32gui import GetParent tbm = CoCreateInstance(GUID_CTaskbarList, interface=ITaskbarList3) def onMove_gen(root): def onMove(value): tbm.SetProgressValue(GetParent(root.winfo_id()), int(value), 100) return onMove root = Tk() tbar = Scale(root, from_=0, to=100, orient='horizontal', command=onMove_gen(root)) tbar.pack() root.mainloop()
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__idw = CoCreateInstance( desktopwallpaper.CLSID_DesktopWallpaper, interface=desktopwallpaper.IDesktopWallpaper )
def GetInterfaceFromGlobal(self, cookie, interface=IUnknown): ptr = POINTER(interface)() self.__com_GetInterfaceFromGlobal(cookie, interface._iid_, ptr) return ptr def RevokeInterfaceFromGlobal(self, cookie): self.__com_RevokeInterfaceFromGlobal(cookie) # It was a pain to get this CLSID: it's neither in the registry, nor # in any header files. I had to compile a C program, and find it out # with the debugger. Apparently it is in uuid.lib. CLSID_StdGlobalInterfaceTable = GUID("{00000323-0000-0000-C000-000000000046}") git = CoCreateInstance(CLSID_StdGlobalInterfaceTable, interface=IGlobalInterfaceTable, clsctx=CLSCTX_INPROC_SERVER) RevokeInterfaceFromGlobal = git.RevokeInterfaceFromGlobal RegisterInterfaceInGlobal = git.RegisterInterfaceInGlobal GetInterfaceFromGlobal = git.GetInterfaceFromGlobal __all__ = ["RegisterInterfaceInGlobal", "RevokeInterfaceFromGlobal", "GetInterfaceFromGlobal"] if __name__ == "__main__": from comtypes.typeinfo import CreateTypeLib, ICreateTypeLib tlib = CreateTypeLib("foo.bar") # we don not save it later assert (tlib.AddRef(), tlib.Release()) == (2, 1) cookie = RegisterInterfaceInGlobal(tlib)
class AudioEndpoints(object): def __init__(self, DEVICE_STATE=DEVICE_STATE_ACTIVE, PKEY_Device=PKEY_Device_FriendlyName): self.DEVICE_STATE = DEVICE_STATE self.PKEY_Device = PKEY_Device self.pDevEnum = CoCreateInstance(_CLSID_MMDeviceEnumerator, _IMMDeviceEnumerator, CLSCTX_INPROC_SERVER) self.pPolicyConfig = None def GetDefault(self, role=eConsole, dataFlow=eRender): return AudioEndpoint( self.pDevEnum.GetDefaultAudioEndpoint(dataFlow, role), self, self.PKEY_Device) def SetDefault(self, endpoint, role=eConsole): OldDefault = self.GetDefault(role) if not self.pPolicyConfig: self.pPolicyConfig = CoCreateInstance( CLSID_CPolicyConfigVistaClient, IPolicyConfigVista, CLSCTX_ALL) hr = self.pPolicyConfig.SetDefaultEndpoint(endpoint.getId(), role) if hr: import win32api print('SetDefaultEndpoint', win32api.FormatMessage(hr)) return OldDefault def RegisterCallback(self, Callback): self.Callback = Callback hr = self.pDevEnum.RegisterEndpointNotificationCallback(self.Callback) if hr: import win32api print('RegisterEndpointNotificationCallback', hr) print('SetDefaultEndpoint', win32api.FormatMessage(hr)) def UnregisterCallback(self): try: hr = self.pDevEnum.UnregisterEndpointNotificationCallback( self.Callback) self.Callback = None if hr: import win32api print('UnregisterEndpointNotificationCallback', hr) print('SetDefaultEndpoint', win32api.FormatMessage(hr)) except AttributeError: pass def __call__(self, ID): try: return AudioEndpoint(self.pDevEnum.GetDevice(ID), self, self.PKEY_Device) except COMError: for endpoint in self: if endpoint.getName() == ID: return endpoint def __str__(self): return str([str(endpoint) for endpoint in self]) def ChangeFilter(self, DEVICE_STATE=None, PKEY_Device=None): if DEVICE_STATE != None: self.DEVICE_STATE = DEVICE_STATE if PKEY_Device != None: self.PKEY_Device = PKEY_Device def __iter__(self, dataFlow=eRender): pEndpoints = self.pDevEnum.EnumAudioEndpoints(dataFlow, self.DEVICE_STATE) for i in range(pEndpoints.GetCount()): yield AudioEndpoint(pEndpoints.Item(i), self, self.PKEY_Device) def __len__(self): return int( self.pDevEnum.EnumAudioEndpoints(eRender, self.DEVICE_STATE).GetCount())
def MTAThreadFunc(self): try: oledll.ole32.CoInitializeEx(None, comtypes.COINIT_MULTITHREADED) isUIA8 = False try: self.clientObject = CoCreateInstance( CUIAutomation8._reg_clsid_, interface=IUIAutomation, clsctx=CLSCTX_INPROC_SERVER) isUIA8 = True except (COMError, WindowsError, NameError): self.clientObject = CoCreateInstance( CUIAutomation._reg_clsid_, interface=IUIAutomation, clsctx=CLSCTX_INPROC_SERVER) # #7345: Instruct UIA to never map MSAA winEvents to UIA propertyChange events. # These events are not needed by NVDA, and they can cause the UI Automation client library to become unresponsive if an application firing winEvents has a slow message pump. pfm = self.clientObject.proxyFactoryMapping for index in range(pfm.count): e = pfm.getEntry(index) entryChanged = False for eventId, propertyIds in ignoreWinEventsMap.items(): for propertyId in propertyIds: # Check if this proxy has mapped any winEvents to the UIA propertyChange event for this property ID try: oldWinEvents = e.getWinEventsForAutomationEvent( eventId, propertyId) except IndexError: # comtypes does not seem to correctly handle a returned empty SAFEARRAY, raising IndexError oldWinEvents = None if oldWinEvents: # As winEvents were mapped, replace them with an empty list e.setWinEventsForAutomationEvent( eventId, propertyId, []) entryChanged = True if entryChanged: # Changes to an entry are not automatically picked up. # Therefore remove the entry and re-insert it. pfm.removeEntry(index) pfm.insertEntry(index, e) if isUIA8: # #8009: use appropriate interface based on highest supported interface. # #8338: made easier by traversing interfaces supported on Windows 8 and later in reverse. for interface in reversed(CUIAutomation8._com_interfaces_): try: self.clientObject = self.clientObject.QueryInterface( interface) break except COMError: pass # Windows 10 RS5 provides new performance features for UI Automation including event coalescing and connection recovery. # Enable all of these where available. if isinstance(self.clientObject, IUIAutomation6): self.clientObject.CoalesceEvents = CoalesceEventsOptions_Enabled self.clientObject.ConnectionRecoveryBehavior = ConnectionRecoveryBehaviorOptions_Enabled log.info("UIAutomation: %s" % self.clientObject.__class__.__mro__[1].__name__) self.windowTreeWalker = self.clientObject.createTreeWalker( self.clientObject.CreateNotCondition( self.clientObject.CreatePropertyCondition( UIA_NativeWindowHandlePropertyId, 0))) self.windowCacheRequest = self.clientObject.CreateCacheRequest() self.windowCacheRequest.AddProperty( UIA_NativeWindowHandlePropertyId) self.UIAWindowHandleCache = {} self.baseTreeWalker = self.clientObject.RawViewWalker self.baseCacheRequest = self.windowCacheRequest.Clone() for propertyId in (UIA_FrameworkIdPropertyId, UIA_AutomationIdPropertyId, UIA_ClassNamePropertyId, UIA_ControlTypePropertyId, UIA_ProviderDescriptionPropertyId, UIA_ProcessIdPropertyId, UIA_IsTextPatternAvailablePropertyId, UIA_IsContentElementPropertyId, UIA_IsControlElementPropertyId): self.baseCacheRequest.addProperty(propertyId) self.baseCacheRequest.addPattern(UIA_TextPatternId) self.rootElement = self.clientObject.getRootElementBuildCache( self.baseCacheRequest) self.reservedNotSupportedValue = self.clientObject.ReservedNotSupportedValue self.ReservedMixedAttributeValue = self.clientObject.ReservedMixedAttributeValue if config.conf['UIA']['selectiveEventRegistration']: self._createLocalEventHandlerGroup() self._registerGlobalEventHandlers() if winVersion.getWinVer() >= winVersion.WIN11: UIARemote.initialize(True, self.clientObject) except Exception as e: self.MTAThreadInitException = e finally: self.MTAThreadInitEvent.set() while True: func = self.MTAThreadQueue.get() if func: try: func() except Exception: log.error("Exception in function queued to UIA MTA thread", exc_info=True) else: break self.clientObject.RemoveAllEventHandlers()
class UIAHandler(COMObject): _com_interfaces_ = [ UIA.IUIAutomationEventHandler, UIA.IUIAutomationFocusChangedEventHandler, UIA.IUIAutomationPropertyChangedEventHandler, UIA.IUIAutomationNotificationEventHandler, UIA.IUIAutomationActiveTextPositionChangedEventHandler, ] def __init__(self): super(UIAHandler, self).__init__() self.globalEventHandlerGroup = None self.localEventHandlerGroup = None self._localEventHandlerGroupElements = set() self.MTAThreadInitEvent = threading.Event() self.MTAThreadQueue = Queue() self.MTAThreadInitException = None self.MTAThread = threading.Thread( name= f"{self.__class__.__module__}.{self.__class__.__qualname__}.MTAThread", target=self.MTAThreadFunc) self.MTAThread.daemon = True self.MTAThread.start() self.MTAThreadInitEvent.wait(2) if self.MTAThreadInitException: raise self.MTAThreadInitException def terminate(self): MTAThreadHandle = ctypes.wintypes.HANDLE( windll.kernel32.OpenThread(winKernel.SYNCHRONIZE, False, self.MTAThread.ident)) self.MTAThreadQueue.put_nowait(None) #Wait for the MTA thread to die (while still message pumping) if windll.user32.MsgWaitForMultipleObjects(1, byref(MTAThreadHandle), False, 200, 0) != 0: log.debugWarning( "Timeout or error while waiting for UIAHandler MTA thread") windll.kernel32.CloseHandle(MTAThreadHandle) del self.MTAThread def MTAThreadFunc(self): try: oledll.ole32.CoInitializeEx(None, comtypes.COINIT_MULTITHREADED) isUIA8 = False try: self.clientObject = CoCreateInstance( CUIAutomation8._reg_clsid_, interface=IUIAutomation, clsctx=CLSCTX_INPROC_SERVER) isUIA8 = True except (COMError, WindowsError, NameError): self.clientObject = CoCreateInstance( CUIAutomation._reg_clsid_, interface=IUIAutomation, clsctx=CLSCTX_INPROC_SERVER) # #7345: Instruct UIA to never map MSAA winEvents to UIA propertyChange events. # These events are not needed by NVDA, and they can cause the UI Automation client library to become unresponsive if an application firing winEvents has a slow message pump. pfm = self.clientObject.proxyFactoryMapping for index in range(pfm.count): e = pfm.getEntry(index) entryChanged = False for eventId, propertyIds in ignoreWinEventsMap.items(): for propertyId in propertyIds: # Check if this proxy has mapped any winEvents to the UIA propertyChange event for this property ID try: oldWinEvents = e.getWinEventsForAutomationEvent( eventId, propertyId) except IndexError: # comtypes does not seem to correctly handle a returned empty SAFEARRAY, raising IndexError oldWinEvents = None if oldWinEvents: # As winEvents were mapped, replace them with an empty list e.setWinEventsForAutomationEvent( eventId, propertyId, []) entryChanged = True if entryChanged: # Changes to an entry are not automatically picked up. # Therefore remove the entry and re-insert it. pfm.removeEntry(index) pfm.insertEntry(index, e) if isUIA8: # #8009: use appropriate interface based on highest supported interface. # #8338: made easier by traversing interfaces supported on Windows 8 and later in reverse. for interface in reversed(CUIAutomation8._com_interfaces_): try: self.clientObject = self.clientObject.QueryInterface( interface) break except COMError: pass # Windows 10 RS5 provides new performance features for UI Automation including event coalescing and connection recovery. # Enable all of these where available. if isinstance(self.clientObject, IUIAutomation6): self.clientObject.CoalesceEvents = CoalesceEventsOptions_Enabled self.clientObject.ConnectionRecoveryBehavior = ConnectionRecoveryBehaviorOptions_Enabled log.info("UIAutomation: %s" % self.clientObject.__class__.__mro__[1].__name__) self.windowTreeWalker = self.clientObject.createTreeWalker( self.clientObject.CreateNotCondition( self.clientObject.CreatePropertyCondition( UIA_NativeWindowHandlePropertyId, 0))) self.windowCacheRequest = self.clientObject.CreateCacheRequest() self.windowCacheRequest.AddProperty( UIA_NativeWindowHandlePropertyId) self.UIAWindowHandleCache = {} self.baseTreeWalker = self.clientObject.RawViewWalker self.baseCacheRequest = self.windowCacheRequest.Clone() for propertyId in (UIA_FrameworkIdPropertyId, UIA_AutomationIdPropertyId, UIA_ClassNamePropertyId, UIA_ControlTypePropertyId, UIA_ProviderDescriptionPropertyId, UIA_ProcessIdPropertyId, UIA_IsTextPatternAvailablePropertyId, UIA_IsContentElementPropertyId, UIA_IsControlElementPropertyId): self.baseCacheRequest.addProperty(propertyId) self.baseCacheRequest.addPattern(UIA_TextPatternId) self.rootElement = self.clientObject.getRootElementBuildCache( self.baseCacheRequest) self.reservedNotSupportedValue = self.clientObject.ReservedNotSupportedValue self.ReservedMixedAttributeValue = self.clientObject.ReservedMixedAttributeValue if config.conf['UIA']['selectiveEventRegistration']: self._createLocalEventHandlerGroup() self._registerGlobalEventHandlers() if winVersion.getWinVer() >= winVersion.WIN11: UIARemote.initialize(True, self.clientObject) except Exception as e: self.MTAThreadInitException = e finally: self.MTAThreadInitEvent.set() while True: func = self.MTAThreadQueue.get() if func: try: func() except Exception: log.error("Exception in function queued to UIA MTA thread", exc_info=True) else: break self.clientObject.RemoveAllEventHandlers() def _registerGlobalEventHandlers(self): self.clientObject.AddFocusChangedEventHandler(self.baseCacheRequest, self) if isinstance(self.clientObject, UIA.IUIAutomation6): self.globalEventHandlerGroup = self.clientObject.CreateEventHandlerGroup( ) else: self.globalEventHandlerGroup = utils.FakeEventHandlerGroup( self.clientObject) self.globalEventHandlerGroup.AddPropertyChangedEventHandler( UIA.TreeScope_Subtree, self.baseCacheRequest, self, *self.clientObject.IntSafeArrayToNativeArray( globalEventHandlerGroupUIAPropertyIds if config. conf['UIA']['selectiveEventRegistration'] else UIAPropertyIdsToNVDAEventNames)) for eventId in (globalEventHandlerGroupUIAEventIds if config.conf['UIA']['selectiveEventRegistration'] else UIAEventIdsToNVDAEventNames): self.globalEventHandlerGroup.AddAutomationEventHandler( eventId, UIA.TreeScope_Subtree, self.baseCacheRequest, self) # #7984: add support for notification event (IUIAutomation5, part of Windows 10 build 16299 and later). if isinstance(self.clientObject, UIA.IUIAutomation5): self.globalEventHandlerGroup.AddNotificationEventHandler( UIA.TreeScope_Subtree, self.baseCacheRequest, self) if isinstance(self.clientObject, UIA.IUIAutomation6): self.globalEventHandlerGroup.AddActiveTextPositionChangedEventHandler( UIA.TreeScope_Subtree, self.baseCacheRequest, self) self.addEventHandlerGroup(self.rootElement, self.globalEventHandlerGroup) def _createLocalEventHandlerGroup(self): if isinstance(self.clientObject, UIA.IUIAutomation6): self.localEventHandlerGroup = self.clientObject.CreateEventHandlerGroup( ) else: self.localEventHandlerGroup = utils.FakeEventHandlerGroup( self.clientObject) self.localEventHandlerGroup.AddPropertyChangedEventHandler( UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, self, *self.clientObject.IntSafeArrayToNativeArray( localEventHandlerGroupUIAPropertyIds)) for eventId in localEventHandlerGroupUIAEventIds: self.localEventHandlerGroup.AddAutomationEventHandler( eventId, UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, self) def addEventHandlerGroup(self, element, eventHandlerGroup): if isinstance(eventHandlerGroup, UIA.IUIAutomationEventHandlerGroup): self.clientObject.AddEventHandlerGroup(element, eventHandlerGroup) elif isinstance(eventHandlerGroup, utils.FakeEventHandlerGroup): eventHandlerGroup.registerToClientObject(element) else: raise NotImplementedError def removeEventHandlerGroup(self, element, eventHandlerGroup): if isinstance(eventHandlerGroup, UIA.IUIAutomationEventHandlerGroup): self.clientObject.RemoveEventHandlerGroup(element, eventHandlerGroup) elif isinstance(eventHandlerGroup, utils.FakeEventHandlerGroup): eventHandlerGroup.unregisterFromClientObject(element) else: raise NotImplementedError def addLocalEventHandlerGroupToElement(self, element, isFocus=False): if not self.localEventHandlerGroup or element in self._localEventHandlerGroupElements: return def func(): if isFocus: try: isStillFocus = self.clientObject.CompareElements( self.clientObject.GetFocusedElement(), element) except COMError: isStillFocus = False if not isStillFocus: return try: self.addEventHandlerGroup(element, self.localEventHandlerGroup) except COMError: log.error("Could not register for UIA events for element", exc_info=True) else: self._localEventHandlerGroupElements.add(element) self.MTAThreadQueue.put_nowait(func) def removeLocalEventHandlerGroupFromElement(self, element): if not self.localEventHandlerGroup or element not in self._localEventHandlerGroupElements: return def func(): try: self.removeEventHandlerGroup(element, self.localEventHandlerGroup) except COMError: # The old UIAElement has probably died as the window was closed. # The system should forget the old event registration itself. # Yet, as we don't expect this to happen very often, log a debug warning. log.debugWarning( "Could not unregister for UIA events for element", exc_info=True) self._localEventHandlerGroupElements.remove(element) self.MTAThreadQueue.put_nowait(func) def IUIAutomationEventHandler_HandleAutomationEvent(self, sender, eventID): if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. if _isDebug(): log.debug( "HandleAutomationEvent: event received while not fully initialized" ) return if eventID == UIA_MenuOpenedEventId and eventHandler.isPendingEvents( "gainFocus"): # We don't need the menuOpened event if focus has been fired, # as focus should be more correct. if _isDebug(): log.debug( "HandleAutomationEvent: Ignored MenuOpenedEvent while focus event pending" ) return NVDAEventName = UIAEventIdsToNVDAEventNames.get(eventID, None) if not NVDAEventName: if _isDebug(): log.debugWarning( f"HandleAutomationEvent: Don't know how to handle event {eventID}" ) return focus = api.getFocusObject() import NVDAObjects.UIA if (isinstance(focus, NVDAObjects.UIA.UIA) and self.clientObject.compareElements(focus.UIAElement, sender)): pass elif not self.isNativeUIAElement(sender): if _isDebug(): log.debug( f"HandleAutomationEvent: Ignoring event {NVDAEventName} for non native element" ) return window = self.getNearestWindowHandle(sender) if window and not eventHandler.shouldAcceptEvent(NVDAEventName, windowHandle=window): if _isDebug(): log.debug( f"HandleAutomationEvent: Ignoring event {NVDAEventName} for shouldAcceptEvent=False" ) return try: obj = NVDAObjects.UIA.UIA(UIAElement=sender) except Exception: if _isDebug(): log.debugWarning( f"HandleAutomationEvent: Exception while creating object for event {NVDAEventName}", exc_info=True) return if (not obj or (NVDAEventName == "gainFocus" and not obj.shouldAllowUIAFocusEvent) or (NVDAEventName == "liveRegionChange" and not obj._shouldAllowUIALiveRegionChangeEvent)): if _isDebug(): log.debug( "HandleAutomationEvent: " f"Ignoring event {NVDAEventName} because no object or ignored by object itself" ) return if obj == focus: obj = focus eventHandler.queueEvent(NVDAEventName, obj) # The last UIAElement that received a UIA focus event # This is updated no matter if this is a native element, the window is UIA blacklisted by NVDA, or the element is proxied from MSAA lastFocusedUIAElement = None def IUIAutomationFocusChangedEventHandler_HandleFocusChangedEvent( self, sender): if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. if _isDebug(): log.debug( "HandleFocusChangedEvent: event received while not fully initialized" ) return self.lastFocusedUIAElement = sender if not self.isNativeUIAElement(sender): # #12982: This element may be the root of an MS Word document # for which we may be refusing to use UIA as its implementation may be incomplete. # However, there are some controls embedded in the MS Word document window # such as the Modern comments side track pane # for which we do have to use UIA. # But, if focus jumps from one of these controls back to the document (E.g. the user presses escape), # we receive no MSAA focus event, only a UIA focus event. # As we are not treating the Word doc as UIA, we need to manually fire an MSAA focus event on the document. self._emitMSAAFocusForWordDocIfNecessary(sender) if _isDebug(): log.debug( "HandleFocusChangedEvent: Ignoring for non native element") return import NVDAObjects.UIA if isinstance(eventHandler.lastQueuedFocusObject, NVDAObjects.UIA.UIA): lastFocusObj = eventHandler.lastQueuedFocusObject # Ignore duplicate focus events. # It seems that it is possible for compareElements to return True, even though the objects are different. # Therefore, don't ignore the event if the last focus object has lost its hasKeyboardFocus state. try: if (not lastFocusObj.shouldAllowDuplicateUIAFocusEvent and self.clientObject.compareElements( sender, lastFocusObj.UIAElement) and lastFocusObj.UIAElement.currentHasKeyboardFocus): if _isDebug(): log.debugWarning( "HandleFocusChangedEvent: Ignoring duplicate focus event" ) return except COMError: if _isDebug(): log.debugWarning( "HandleFocusChangedEvent: Couldn't check for duplicate focus event", exc_info=True) window = self.getNearestWindowHandle(sender) if window and not eventHandler.shouldAcceptEvent("gainFocus", windowHandle=window): if _isDebug(): log.debug( "HandleFocusChangedEvent: Ignoring for shouldAcceptEvent=False" ) return try: obj = NVDAObjects.UIA.UIA(UIAElement=sender) except Exception: if _isDebug(): log.debugWarning( "HandleFocusChangedEvent: Exception while creating object", exc_info=True) return if not obj or not obj.shouldAllowUIAFocusEvent: if _isDebug(): log.debug( "HandleFocusChangedEvent: Ignoring because no object or ignored by object itself" ) return eventHandler.queueEvent("gainFocus", obj) def IUIAutomationPropertyChangedEventHandler_HandlePropertyChangedEvent( self, sender, propertyId, newValue): # #3867: For now manually force this VARIANT type to empty to get around a nasty double free in comtypes/ctypes. # We also don't use the value in this callback. newValue.vt = VT_EMPTY if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. if _isDebug(): log.debug( "HandlePropertyChangedEvent: event received while not fully initialized" ) return try: processId = sender.CachedProcessID except COMError: pass else: appMod = appModuleHandler.getAppModuleFromProcessID(processId) if not appMod.shouldProcessUIAPropertyChangedEvent( sender, propertyId): return NVDAEventName = UIAPropertyIdsToNVDAEventNames.get(propertyId, None) if not NVDAEventName: if _isDebug(): log.debugWarning( f"HandlePropertyChangedEvent: Don't know how to handle property {propertyId}" ) return focus = api.getFocusObject() import NVDAObjects.UIA if (isinstance(focus, NVDAObjects.UIA.UIA) and self.clientObject.compareElements(focus.UIAElement, sender)): pass elif not self.isNativeUIAElement(sender): if _isDebug(): log.debug( f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} for non native element" ) return window = self.getNearestWindowHandle(sender) if window and not eventHandler.shouldAcceptEvent(NVDAEventName, windowHandle=window): if _isDebug(): log.debug( f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} for shouldAcceptEvent=False" ) return try: obj = NVDAObjects.UIA.UIA(UIAElement=sender) except Exception: if _isDebug(): log.debugWarning( f"HandlePropertyChangedEvent: Exception while creating object for event {NVDAEventName}", exc_info=True) return if not obj: if _isDebug(): log.debug( f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} because no object" ) return if obj == focus: obj = focus eventHandler.queueEvent(NVDAEventName, obj) def IUIAutomationNotificationEventHandler_HandleNotificationEvent( self, sender, NotificationKind, NotificationProcessing, displayString, activityId): if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. if _isDebug(): log.debug( "HandleNotificationEvent: event received while not fully initialized" ) return import NVDAObjects.UIA try: obj = NVDAObjects.UIA.UIA(UIAElement=sender) except Exception: if _isDebug(): log.debugWarning( "HandleNotificationEvent: Exception while creating object: " f"NotificationProcessing={NotificationProcessing} " f"displayString={displayString} " f"activityId={activityId}", exc_info=True) return if not obj: # Sometimes notification events can be fired on a UIAElement that has no windowHandle and does not connect through parents back to the desktop. # There is nothing we can do with these. if _isDebug(): log.debug( "HandleNotificationEvent: Ignoring because no object: " f"NotificationProcessing={NotificationProcessing} " f"displayString={displayString} " f"activityId={activityId}") return eventHandler.queueEvent("UIA_notification", obj, notificationKind=NotificationKind, notificationProcessing=NotificationProcessing, displayString=displayString, activityId=activityId) def IUIAutomationActiveTextPositionChangedEventHandler_HandleActiveTextPositionChangedEvent( self, sender, textRange): if not self.MTAThreadInitEvent.isSet(): # UIAHandler hasn't finished initialising yet, so just ignore this event. if _isDebug(): log.debug( "HandleActiveTextPositionchangedEvent: event received while not fully initialized" ) return import NVDAObjects.UIA try: obj = NVDAObjects.UIA.UIA(UIAElement=sender) except Exception: if _isDebug(): log.debugWarning( "HandleActiveTextPositionChangedEvent: Exception while creating object: ", exc_info=True) return if not obj: if _isDebug(): log.debug( "HandleActiveTextPositionchangedEvent: Ignoring because no object: " ) return eventHandler.queueEvent("UIA_activeTextPositionChanged", obj, textRange=textRange) def _isUIAWindowHelper(self, hwnd): # UIA in NVDA's process freezes in Windows 7 and below processID = winUser.getWindowThreadProcessID(hwnd)[0] if windll.kernel32.GetCurrentProcessId() == processID: return False import NVDAObjects.window windowClass = NVDAObjects.window.Window.normalizeWindowClassName( winUser.getClassName(hwnd)) # For certain window classes, we always want to use UIA. if windowClass in goodUIAWindowClassNames: return True # allow the appModule for the window to also choose if this window is good # An appModule should be able to override bad UIA class names as prescribed by core appModule = appModuleHandler.getAppModuleFromProcessID(processID) if appModule and appModule.isGoodUIAWindow(hwnd): return True # There are certain window classes that just had bad UIA implementations if windowClass in badUIAWindowClassNames: return False # allow the appModule for the window to also choose if this window is bad if appModule and appModule.isBadUIAWindow(hwnd): return False if windowClass == "NetUIHWND" and appModule: # NetUIHWND is used for various controls in MS Office. # IAccessible should be used for NetUIHWND in versions older than 2016 # Fixes: lack of focus reporting (#4207), # Fixes: strange reporting of context menu items(#9252), # fixes: not being able to report ribbon sections when they starts with an edit field (#7067) # Note that #7067 is not fixed for Office 2016 and never. # Using IAccessible for NetUIHWND controls causes focus changes not to be reported # when the ribbon is collapsed. # Testing shows that these controls emits proper events but they are ignored by NVDA. isOfficeApp = appModule.productName.startswith( ("Microsoft Office", "Microsoft Outlook")) isOffice2013OrOlder = int( appModule.productVersion.split(".")[0]) < 16 if isOfficeApp and isOffice2013OrOlder: parentHwnd = winUser.getAncestor(hwnd, winUser.GA_PARENT) while parentHwnd: if winUser.getClassName(parentHwnd) in ( "Net UI Tool Window", "MsoCommandBar", ): return False parentHwnd = winUser.getAncestor(parentHwnd, winUser.GA_PARENT) # Ask the window if it supports UIA natively res = windll.UIAutomationCore.UiaHasServerSideProvider(hwnd) if res: canUseOlderInProcessApproach = bool( appModule.helperLocalBindingHandle) if windowClass == MS_WORD_DOCUMENT_WINDOW_CLASS: # The window does support UIA natively, but MS Word documents now # have a fairly usable UI Automation implementation. # However, builds of MS Office 2016 before build 15000 or so had bugs which # we cannot work around. # Therefore, if we can inject in-process, refuse to use UIA and instead # fall back to the MS Word object model. if not shouldUseUIAInMSWord(appModule): return False # MS Excel spreadsheets now have a fairly usable UI Automation implementation. # However, builds of MS Office 2016 before build 9000 or so had bugs which we # cannot work around. # And even current builds of Office 2016 are still missing enough info from UIA # that it is still impossible to switch to UIA completely. # Therefore, if we can inject in-process, refuse to use UIA and instead fall # back to the MS Excel object model. elif ( # An MS Excel spreadsheet window windowClass == "EXCEL7" # Disabling is only useful if we can inject in-process (and use our older code) and appModule.helperLocalBindingHandle # Allow the user to explicitly force UIA support for MS Excel spreadsheets # no matter the Office version and not config.conf['UIA']['useInMSExcelWhenAvailable']): return False # Unless explicitly allowed, all Chromium implementations (including Edge) should not be UIA, # As their IA2 implementation is still better at the moment. elif ( windowClass == "Chrome_RenderWidgetHostHWND" and (AllowUiaInChromium.getConfig() == AllowUiaInChromium.NO # Disabling is only useful if we can inject in-process (and use our older code) or (canUseOlderInProcessApproach and AllowUiaInChromium.getConfig() != AllowUiaInChromium.YES # Users can prefer to use UIA ))): return False if windowClass == "ConsoleWindowClass": return utils._shouldUseUIAConsole(hwnd) return bool(res) def isUIAWindow(self, hwnd): now = time.time() v = self.UIAWindowHandleCache.get(hwnd, None) if not v or (now - v[1]) > 0.5: v = self._isUIAWindowHelper(hwnd), now self.UIAWindowHandleCache[hwnd] = v return v[0] def getNearestWindowHandle(self, UIAElement): if hasattr(UIAElement, "_nearestWindowHandle"): # Called previously. Use cached result. return UIAElement._nearestWindowHandle try: processID = UIAElement.cachedProcessID except COMError: return None appModule = appModuleHandler.getAppModuleFromProcessID(processID) # WDAG (Windows Defender application Guard) UIA elements should be treated as being from a remote machine, and therefore their window handles are completely invalid on this machine. # Therefore, jump all the way up to the root of the WDAG process and use that window handle as it is local to this machine. if appModule.appName == WDAG_PROCESS_NAME: condition = utils.createUIAMultiPropertyCondition({ UIA.UIA_ClassNamePropertyId: ['ApplicationFrameWindow', 'CabinetWClass'] }) walker = self.clientObject.createTreeWalker(condition) else: # Not WDAG, just walk up to the nearest valid windowHandle walker = self.windowTreeWalker try: new = walker.NormalizeElementBuildCache(UIAElement, self.windowCacheRequest) except COMError: return None try: window = new.cachedNativeWindowHandle except COMError: window = None # Cache for future use to improve performance. UIAElement._nearestWindowHandle = window return window def _isNetUIEmbeddedInWordDoc(self, element: UIA.IUIAutomationElement) -> bool: """ Detects if the given UIA element represents a control in a NetUI container embedded within a MS Word document window. E.g. the Modern Comments side track pane. This method also caches the answer on the element itself to both speed up checking later and to allow checking on an already dead element E.g. a previous focus. """ if getattr(element, '_isNetUIEmbeddedInWordDoc', False): return True windowHandle = self.getNearestWindowHandle(element) if winUser.getClassName(windowHandle) != MS_WORD_DOCUMENT_WINDOW_CLASS: return False condition = utils.createUIAMultiPropertyCondition( {UIA.UIA_ClassNamePropertyId: 'NetUIHWNDElement'}, {UIA.UIA_NativeWindowHandlePropertyId: windowHandle}) walker = self.clientObject.createTreeWalker(condition) cacheRequest = self.clientObject.createCacheRequest() cacheRequest.AddProperty(UIA.UIA_ClassNamePropertyId) cacheRequest.AddProperty(UIA.UIA_NativeWindowHandlePropertyId) ancestor = walker.NormalizeElementBuildCache(element, cacheRequest) # ancestor will either be the embedded NetUIElement, or just hit the root of the MS Word document window if ancestor.CachedClassName != 'NetUIHWNDElement': return False element._isNetUIEmbeddedInWordDoc = True return True def _emitMSAAFocusForWordDocIfNecessary( self, element: UIA.IUIAutomationElement) -> None: """ Fires an MSAA focus event on the given UIA element if the element is the root of a Word document, and the focus was previously in a NetUI container embedded in this Word document. """ import NVDAObjects.UIA oldFocus = eventHandler.lastQueuedFocusObject if (isinstance(oldFocus, NVDAObjects.UIA.UIA) and getattr( oldFocus.UIAElement, '_isNetUIEmbeddedInWordDoc', False) and element.CachedClassName == MS_WORD_DOCUMENT_WINDOW_CLASS and element.CachedControlType == UIA.UIA_DocumentControlTypeId and self.getNearestWindowHandle(element) == oldFocus.windowHandle and not self.isUIAWindow(oldFocus.windowHandle)): IAccessibleHandler.internalWinEventHandler.winEventLimiter.addEvent( winUser.EVENT_OBJECT_FOCUS, oldFocus.windowHandle, winUser.OBJID_CLIENT, 0, oldFocus.windowThreadID) def isNativeUIAElement(self, UIAElement): #Due to issues dealing with UIA elements coming from the same process, we do not class these UIA elements as usable. #It seems to be safe enough to retreave the cached processID, but using tree walkers or fetching other properties causes a freeze. try: processID = UIAElement.cachedProcessId except COMError: return False if processID == windll.kernel32.GetCurrentProcessId(): return False # Whether this is a native element depends on whether its window natively supports UIA. windowHandle = self.getNearestWindowHandle(UIAElement) if windowHandle: if self.isUIAWindow(windowHandle): return True # #12982: although NVDA by default may not treat this element's window as native UIA, # E.g. it is proxied from MSAA, or NVDA has specifically black listed it, # It may be an element from a NetUIcontainer embedded in a Word document, # such as the MS Word Modern Comments side track pane. # These elements are only exposed via UIA, and not MSAA, # thus we must treat these elements as native UIA. if self._isNetUIEmbeddedInWordDoc(UIAElement): return True if winUser.getClassName( windowHandle ) == "DirectUIHWND" and "IEFRAME.dll" in UIAElement.cachedProviderDescription and UIAElement.currentClassName in ( "DownloadBox", "accessiblebutton", "DUIToolbarButton", "PushButton"): # This is the IE 9 downloads list. # #3354: UiaHasServerSideProvider returns false for the IE 9 downloads list window, # so we'd normally use MSAA for this control. # However, its MSAA implementation is broken (fires invalid events) if UIA is initialised, # whereas its UIA implementation works correctly. # Therefore, we must use UIA here. return True return False
def __init__(self, root): self.__tbm = CoCreateInstance(GUID_CTaskbarList, interface=ITaskbarList4) self.__root = root
def _set_voice(self, val): try: val = GUID(val) except: val = self._enginesList[0].gModeID mode = None for mode in self._enginesList: if mode.gModeID == val: break if mode is None: raise ValueError("no such mode: %s" % val) self._currentMode = mode self._ttsAudio = CoCreateInstance(CLSID_MMAudioDest, IAudioMultiMediaDevice) self._ttsAudio.DeviceNumSet( nvwave.outputDeviceNameToID(config.conf["speech"]["outputDevice"], True)) self._ttsCentral = POINTER(ITTSCentralW)() self._ttsEngines.Select(self._currentMode.gModeID, byref(self._ttsCentral), self._ttsAudio) self._ttsAttrs = self._ttsCentral.QueryInterface(ITTSAttributes) #Find out rate limits hasRate = bool(mode.dwFeatures & TTSFEATURE_SPEED) if hasRate: try: oldVal = DWORD() self._ttsAttrs.SpeedGet(byref(oldVal)) self._ttsAttrs.SpeedSet(TTSATTR_MINSPEED) newVal = DWORD() self._ttsAttrs.SpeedGet(byref(newVal)) self._minRate = newVal.value self._ttsAttrs.SpeedSet(TTSATTR_MAXSPEED) self._ttsAttrs.SpeedGet(byref(newVal)) # ViaVoice (and perhaps other synths) doesn't seem to like the speed being set to maximum. self._maxRate = newVal.value - 1 self._ttsAttrs.SpeedSet(oldVal.value) if self._maxRate <= self._minRate: hasRate = False except COMError: hasRate = False if hasRate: if not self.isSupported('rate'): self.supportedSettings.insert(1, SynthDriver.RateSetting()) else: if self.isSupported("rate"): self.removeSetting("rate") #Find out pitch limits hasPitch = bool(mode.dwFeatures & TTSFEATURE_PITCH) if hasPitch: try: oldVal = WORD() self._ttsAttrs.PitchGet(byref(oldVal)) self._ttsAttrs.PitchSet(TTSATTR_MINPITCH) newVal = WORD() self._ttsAttrs.PitchGet(byref(newVal)) self._minPitch = newVal.value self._ttsAttrs.PitchSet(TTSATTR_MAXPITCH) self._ttsAttrs.PitchGet(byref(newVal)) self._maxPitch = newVal.value self._ttsAttrs.PitchSet(oldVal.value) if self._maxPitch <= self._minPitch: hasPitch = False except COMError: hasPitch = False if hasPitch: if not self.isSupported('pitch'): self.supportedSettings.insert(2, SynthDriver.PitchSetting()) else: if self.isSupported('pitch'): self.removeSetting('pitch') #Find volume limits hasVolume = bool(mode.dwFeatures & TTSFEATURE_VOLUME) if hasVolume: try: oldVal = DWORD() self._ttsAttrs.VolumeGet(byref(oldVal)) self._ttsAttrs.VolumeSet(TTSATTR_MINVOLUME) newVal = DWORD() self._ttsAttrs.VolumeGet(byref(newVal)) self._minVolume = newVal.value self._ttsAttrs.VolumeSet(TTSATTR_MAXVOLUME) self._ttsAttrs.VolumeGet(byref(newVal)) self._maxVolume = newVal.value self._ttsAttrs.VolumeSet(oldVal.value) if self._maxVolume <= self._minVolume: hasVolume = False except COMError: hasVolume = False if hasVolume: if not self.isSupported('volume'): self.supportedSettings.insert(3, SynthDriver.VolumeSetting()) else: if self.isSupported('volume'): self.removeSetting('volume')
class SynthDriver(SynthDriver): name = "sapi4" description = "Microsoft Speech API version 4" supportedSettings = [SynthDriver.VoiceSetting()] supportedNotifications = {synthIndexReached, synthDoneSpeaking} @classmethod def check(cls): try: winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r"CLSID\%s" % CLSID_TTSEnumerator).Close() return True except WindowsError: return False def _fetchEnginesList(self): enginesList = [] self._ttsEngines.Reset() while True: mode = TTSMODEINFO() fetched = c_ulong() try: self._ttsEngines.Next(1, byref(mode), byref(fetched)) except: log.error("can't get next engine", exc_info=True) break if fetched.value == 0: break enginesList.append(mode) return enginesList def __init__(self): self._finalIndex = None self._bufSink = SynthDriverBufSink(weakref.ref(self)) self._bufSinkPtr = self._bufSink.QueryInterface(ITTSBufNotifySink) # HACK: Some buggy engines call Release() too many times on our buf sink. # Therefore, don't let the buf sink be deleted before we release it ourselves. self._bufSink._allowDelete = False self._ttsEngines = CoCreateInstance(CLSID_TTSEnumerator, ITTSEnumW) self._enginesList = self._fetchEnginesList() if len(self._enginesList) == 0: raise RuntimeError("No Sapi4 engines available") self.voice = str(self._enginesList[0].gModeID) def terminate(self): self._bufSink._allowDelete = True def speak(self, speechSequence): textList = [] charMode = False item = None isPitchCommand = False pitch = WORD() self._ttsAttrs.PitchGet(byref(pitch)) oldPitch = pitch.value for item in speechSequence: if isinstance(item, str): textList.append(item.replace('\\', '\\\\')) elif isinstance(item, IndexCommand): textList.append("\\mrk=%d\\" % item.index) elif isinstance(item, CharacterModeCommand): textList.append("\\RmS=1\\" if item.state else "\\RmS=0\\") charMode = item.state elif isinstance(item, BreakCommand): textList.append(f"\\Pau={item.time}\\") elif isinstance(item, PitchCommand): offset = int(config.conf["speech"]['sapi4']["capPitchChange"]) offset = int((self._maxPitch - self._minPitch) * offset / 100) val = oldPitch + offset if val > self._maxPitch: val = self._maxPitch if val < self._minPitch: val = self._minPitch self._ttsAttrs.PitchSet(val) isPitchCommand = True elif isinstance(item, SpeechCommand): log.debugWarning("Unsupported speech command: %s" % item) else: log.error("Unknown speech: %s" % item) if isinstance(item, IndexCommand): # This is the index denoting the end of the speech sequence. self._finalIndex = item.index if charMode: # Some synths stay in character mode if we don't explicitly disable it. textList.append("\\RmS=0\\") # Some SAPI4 synthesizers complete speech sequence just after the last text # and ignore any indexes passed after it # Therefore we add the pause of 1ms at the end textList.append("\\PAU=1\\") text = "".join(textList) flags = TTSDATAFLAG_TAGGED if isPitchCommand: self._ttsCentral.TextData(VOICECHARSET.CHARSET_TEXT, flags, TextSDATA(text), self._bufSinkPtr, ITTSBufNotifySink._iid_) self._ttsAttrs.PitchSet(oldPitch) isPitchCommand = False else: self._ttsCentral.TextData(VOICECHARSET.CHARSET_TEXT, flags, TextSDATA(text), self._bufSinkPtr, ITTSBufNotifySink._iid_) def cancel(self): self._ttsCentral.AudioReset() self.lastIndex = None def pause(self, switch): if switch: try: self._ttsCentral.AudioPause() except COMError: pass else: self._ttsCentral.AudioResume() def removeSetting(self, name): #Putting it here because currently no other synths make use of it. OrderedDict, where you are? for i, s in enumerate(self.supportedSettings): if s.name == name: del self.supportedSettings[i] return def _set_voice(self, val): try: val = GUID(val) except: val = self._enginesList[0].gModeID mode = None for mode in self._enginesList: if mode.gModeID == val: break if mode is None: raise ValueError("no such mode: %s" % val) self._currentMode = mode self._ttsAudio = CoCreateInstance(CLSID_MMAudioDest, IAudioMultiMediaDevice) self._ttsAudio.DeviceNumSet( nvwave.outputDeviceNameToID(config.conf["speech"]["outputDevice"], True)) self._ttsCentral = POINTER(ITTSCentralW)() self._ttsEngines.Select(self._currentMode.gModeID, byref(self._ttsCentral), self._ttsAudio) self._ttsAttrs = self._ttsCentral.QueryInterface(ITTSAttributes) #Find out rate limits hasRate = bool(mode.dwFeatures & TTSFEATURE_SPEED) if hasRate: try: oldVal = DWORD() self._ttsAttrs.SpeedGet(byref(oldVal)) self._ttsAttrs.SpeedSet(TTSATTR_MINSPEED) newVal = DWORD() self._ttsAttrs.SpeedGet(byref(newVal)) self._minRate = newVal.value self._ttsAttrs.SpeedSet(TTSATTR_MAXSPEED) self._ttsAttrs.SpeedGet(byref(newVal)) # ViaVoice (and perhaps other synths) doesn't seem to like the speed being set to maximum. self._maxRate = newVal.value - 1 self._ttsAttrs.SpeedSet(oldVal.value) if self._maxRate <= self._minRate: hasRate = False except COMError: hasRate = False if hasRate: if not self.isSupported('rate'): self.supportedSettings.insert(1, SynthDriver.RateSetting()) else: if self.isSupported("rate"): self.removeSetting("rate") #Find out pitch limits hasPitch = bool(mode.dwFeatures & TTSFEATURE_PITCH) if hasPitch: try: oldVal = WORD() self._ttsAttrs.PitchGet(byref(oldVal)) self._ttsAttrs.PitchSet(TTSATTR_MINPITCH) newVal = WORD() self._ttsAttrs.PitchGet(byref(newVal)) self._minPitch = newVal.value self._ttsAttrs.PitchSet(TTSATTR_MAXPITCH) self._ttsAttrs.PitchGet(byref(newVal)) self._maxPitch = newVal.value self._ttsAttrs.PitchSet(oldVal.value) if self._maxPitch <= self._minPitch: hasPitch = False except COMError: hasPitch = False if hasPitch: if not self.isSupported('pitch'): self.supportedSettings.insert(2, SynthDriver.PitchSetting()) else: if self.isSupported('pitch'): self.removeSetting('pitch') #Find volume limits hasVolume = bool(mode.dwFeatures & TTSFEATURE_VOLUME) if hasVolume: try: oldVal = DWORD() self._ttsAttrs.VolumeGet(byref(oldVal)) self._ttsAttrs.VolumeSet(TTSATTR_MINVOLUME) newVal = DWORD() self._ttsAttrs.VolumeGet(byref(newVal)) self._minVolume = newVal.value self._ttsAttrs.VolumeSet(TTSATTR_MAXVOLUME) self._ttsAttrs.VolumeGet(byref(newVal)) self._maxVolume = newVal.value self._ttsAttrs.VolumeSet(oldVal.value) if self._maxVolume <= self._minVolume: hasVolume = False except COMError: hasVolume = False if hasVolume: if not self.isSupported('volume'): self.supportedSettings.insert(3, SynthDriver.VolumeSetting()) else: if self.isSupported('volume'): self.removeSetting('volume') def _get_voice(self): return str(self._currentMode.gModeID) def _getAvailableVoices(self): voices = OrderedDict() for mode in self._enginesList: ID = str(mode.gModeID) name = "%s - %s" % (mode.szModeName, mode.szProductName) try: language = locale.windows_locale[mode.language.LanguageID] except KeyError: language = None voices[ID] = VoiceInfo(ID, name, language) return voices def _get_rate(self): val = DWORD() self._ttsAttrs.SpeedGet(byref(val)) return self._paramToPercent(val.value, self._minRate, self._maxRate) def _set_rate(self, val): val = self._percentToParam(val, self._minRate, self._maxRate) self._ttsAttrs.SpeedSet(val) def _get_pitch(self): val = WORD() self._ttsAttrs.PitchGet(byref(val)) return self._paramToPercent(val.value, self._minPitch, self._maxPitch) def _set_pitch(self, val): val = self._percentToParam(val, self._minPitch, self._maxPitch) self._ttsAttrs.PitchSet(val) def _get_volume(self): val = DWORD() self._ttsAttrs.VolumeGet(byref(val)) return self._paramToPercent(val.value & 0xffff, self._minVolume & 0xffff, self._maxVolume & 0xffff) def _set_volume(self, val): val = self._percentToParam(val, self._minVolume & 0xffff, self._maxVolume & 0xffff) val += val << 16 self._ttsAttrs.VolumeSet(val)