Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
 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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
 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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
def __init():
    global __uia, __root_element
    __uia = CoCreateInstance(CUIAutomation._reg_clsid_,
                             interface=IUIAutomation,
                             clsctx=comtypes.CLSCTX_INPROC_SERVER)
    __root_element = __uia.GetRootElement()
Ejemplo n.º 9
0
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()
Ejemplo n.º 10
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.__idw = CoCreateInstance(
         desktopwallpaper.CLSID_DesktopWallpaper,
         interface=desktopwallpaper.IDesktopWallpaper
     )
Ejemplo n.º 11
0
Archivo: git.py Proyecto: 5Nap/Geometry
    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)
Ejemplo n.º 12
0
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())
Ejemplo n.º 13
0
 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()
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
 def __init__(self, root):
     self.__tbm  = CoCreateInstance(GUID_CTaskbarList, interface=ITaskbarList4)
     self.__root = root
Ejemplo n.º 16
0
 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')
Ejemplo n.º 17
0
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)