mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2025-07-18 06:06:06 -04:00
Putting all the code from the current master branch of TWBlue
This commit is contained in:
29
src/accessible_output2/__init__.py
Normal file
29
src/accessible_output2/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import ctypes
|
||||
import os
|
||||
import types
|
||||
from platform_utils import paths
|
||||
|
||||
def load_library(libname):
|
||||
if paths.is_frozen():
|
||||
libfile = os.path.join(paths.embedded_data_path(), 'accessible_output2', 'lib', libname)
|
||||
else:
|
||||
libfile = os.path.join(paths.module_path(), 'lib', libname)
|
||||
return ctypes.windll[libfile]
|
||||
|
||||
def get_output_classes():
|
||||
import outputs
|
||||
module_type = types.ModuleType
|
||||
classes = [m.output_class for m in outputs.__dict__.itervalues() if type(m) == module_type and hasattr(m, 'output_class')]
|
||||
return sorted(classes, key=lambda c: c.priority)
|
||||
|
||||
def find_datafiles():
|
||||
import os
|
||||
import platform
|
||||
from glob import glob
|
||||
import accessible_output2
|
||||
if platform.system() != 'Windows':
|
||||
return []
|
||||
path = os.path.join(accessible_output2.__path__[0], 'lib', '*.dll')
|
||||
results = glob(path)
|
||||
dest_dir = os.path.join('accessible_output2', 'lib')
|
||||
return [(dest_dir, results)]
|
BIN
src/accessible_output2/lib/PCTKUSR.dll
Normal file
BIN
src/accessible_output2/lib/PCTKUSR.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/PCTKUSR64.dll
Normal file
BIN
src/accessible_output2/lib/PCTKUSR64.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/SAAPI32.dll
Normal file
BIN
src/accessible_output2/lib/SAAPI32.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/dolapi.dll
Normal file
BIN
src/accessible_output2/lib/dolapi.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/jfwapi.dll
Normal file
BIN
src/accessible_output2/lib/jfwapi.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/nvdaControllerClient32.dll
Normal file
BIN
src/accessible_output2/lib/nvdaControllerClient32.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/nvdaControllerClient64.dll
Normal file
BIN
src/accessible_output2/lib/nvdaControllerClient64.dll
Normal file
Binary file not shown.
16
src/accessible_output2/outputs/__init__.py
Normal file
16
src/accessible_output2/outputs/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import platform
|
||||
if platform.system() == 'Windows':
|
||||
import nvda
|
||||
import jaws
|
||||
import sapi5
|
||||
import window_eyes
|
||||
import system_access
|
||||
import dolphin
|
||||
import pc_talker
|
||||
#import sapi4
|
||||
elif platform.system() == "Darwin":
|
||||
import voiceover
|
||||
elif platform.system() == "Linux":
|
||||
import speechDispatcher
|
||||
|
||||
import auto
|
42
src/accessible_output2/outputs/auto.py
Normal file
42
src/accessible_output2/outputs/auto.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import platform
|
||||
import accessible_output2
|
||||
from base import Output, OutputError
|
||||
|
||||
class Auto(Output):
|
||||
|
||||
def __init__(self):
|
||||
if platform.system() == "Darwin":
|
||||
import voiceover
|
||||
self.outputs = [voiceover.VoiceOver()]
|
||||
elif platform.system() == "Linux":
|
||||
import speechDispatcher
|
||||
self.outputs = [speechDispatcher.SpeechDispatcher()]
|
||||
elif platform.system() == "Windows":
|
||||
output_classes = accessible_output2.get_output_classes()
|
||||
self.outputs = []
|
||||
for output in output_classes:
|
||||
try:
|
||||
self.outputs.append(output())
|
||||
except OutputError:
|
||||
pass
|
||||
|
||||
def get_first_available_output(self):
|
||||
for output in self.outputs:
|
||||
if output.is_active():
|
||||
return output
|
||||
return None
|
||||
|
||||
def speak(self, *args, **kwargs):
|
||||
output = self.get_first_available_output()
|
||||
if output:
|
||||
output.speak(*args, **kwargs)
|
||||
|
||||
def braille(self, *args, **kwargs):
|
||||
output = self.get_first_available_output()
|
||||
if output:
|
||||
output.braille(*args, **kwargs)
|
||||
|
||||
def output(self, *args, **kwargs):
|
||||
output = self.get_first_available_output()
|
||||
if output:
|
||||
output.speak(*args, **kwargs)
|
31
src/accessible_output2/outputs/base.py
Normal file
31
src/accessible_output2/outputs/base.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from accessible_output2 import load_library
|
||||
import platform
|
||||
|
||||
class OutputError(Exception):
|
||||
pass
|
||||
|
||||
class Output(object):
|
||||
name = "Unnamed Output" #The name of this output
|
||||
lib32 = None #name of 32-bit lib
|
||||
lib64 = None #name of 64-bit lib
|
||||
priority = 100 #Where to sort in the list of available outputs for automaticly speaking
|
||||
|
||||
def __init__(self):
|
||||
is_32bit = platform.architecture()[0] == "32bit"
|
||||
if self.lib32 and is_32bit:
|
||||
self.lib = load_library(self.lib32)
|
||||
elif self.lib64:
|
||||
self.lib = load_library(self.lib64)
|
||||
|
||||
def output(self, text, **options):
|
||||
output = False
|
||||
if hasattr(self, 'speak') and callable(self.speak):
|
||||
self.speak(text, **options)
|
||||
output = True
|
||||
if hasattr(self, 'braille') and callable(self.braille):
|
||||
self.braille(text, **options)
|
||||
output = True
|
||||
if not output:
|
||||
raise RuntimeError("Output %r does not have any method defined to output" % self)
|
||||
|
||||
|
27
src/accessible_output2/outputs/dolphin.py
Normal file
27
src/accessible_output2/outputs/dolphin.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
|
||||
from base import Output
|
||||
|
||||
class Dolphin (Output):
|
||||
"""Supports dolphin products."""
|
||||
|
||||
name = 'Dolphin'
|
||||
lib32 = 'dolapi.dll'
|
||||
|
||||
def speak(self, text, interrupt=0):
|
||||
if interrupt:
|
||||
self.silence()
|
||||
#If we don't call this, the API won't let us speak.
|
||||
if self.is_active():
|
||||
self.lib.DolAccess_Command(unicode(text), (len(text)*2)+2, 1)
|
||||
|
||||
def silence(self):
|
||||
self.lib.DolAccess_Action(141)
|
||||
|
||||
def is_active(self):
|
||||
try:
|
||||
return self.lib.DolAccess_GetSystem() in (1, 4, 8)
|
||||
except:
|
||||
return False
|
||||
|
||||
output_class = Dolphin
|
33
src/accessible_output2/outputs/jaws.py
Normal file
33
src/accessible_output2/outputs/jaws.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import win32gui
|
||||
from libloader.com import load_com
|
||||
import pywintypes
|
||||
|
||||
from base import Output, OutputError
|
||||
|
||||
class Jaws (Output):
|
||||
"""Output supporting the Jaws for Windows screen reader."""
|
||||
|
||||
name = 'jaws'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super (Jaws, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
self.object = load_com("FreedomSci.JawsApi", "jfwapi")
|
||||
except pywintypes.com_error:
|
||||
raise OutputError
|
||||
|
||||
def braille(self, text, **options):
|
||||
# HACK: replace " with ', Jaws doesn't seem to understand escaping them with \
|
||||
text = text.replace('"', "'")
|
||||
self.object.RunFunction("BrailleString(\"%s\")" % text)
|
||||
|
||||
def speak(self, text, interrupt=False):
|
||||
self.object.SayString(' %s' % text, interrupt)
|
||||
|
||||
def is_active(self):
|
||||
try:
|
||||
return self.object.SayString('',0) == True or win32gui.FindWindow("JFWUI2", "JAWS") != 0
|
||||
except:
|
||||
return False
|
||||
|
||||
output_class = Jaws
|
31
src/accessible_output2/outputs/nvda.py
Normal file
31
src/accessible_output2/outputs/nvda.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import platform
|
||||
|
||||
from platform_utils import paths
|
||||
from libloader import load_library
|
||||
from base import Output
|
||||
|
||||
class NVDA(Output):
|
||||
"""Supports The NVDA screen reader"""
|
||||
name = "NVDA"
|
||||
lib32 = 'nvdaControllerClient32.dll'
|
||||
lib64 = 'nvdaControllerClient64.dll'
|
||||
|
||||
def is_active(self):
|
||||
try:
|
||||
return self.lib.nvdaController_testIfRunning() == 0
|
||||
except:
|
||||
return False
|
||||
|
||||
def braille(self, text, **options):
|
||||
self.lib.nvdaController_brailleMessage(unicode(text))
|
||||
|
||||
def speak(self, text, interrupt=False):
|
||||
if interrupt:
|
||||
self.silence()
|
||||
self.lib.nvdaController_speakText(unicode(text))
|
||||
|
||||
def silence(self):
|
||||
self.lib.nvdaController_cancelSpeech()
|
||||
|
||||
output_class = NVDA
|
19
src/accessible_output2/outputs/pc_talker.py
Normal file
19
src/accessible_output2/outputs/pc_talker.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import ctypes
|
||||
from base import Output
|
||||
|
||||
class PCTalker(Output):
|
||||
lib32 = 'pctkusr.dll'
|
||||
lib64 = 'pctkusr64.dll'
|
||||
|
||||
def speak(self, text, interrupt=False):
|
||||
if interrupt:
|
||||
self.silence()
|
||||
self.lib.PCTKPRead(text.encode('cp932', 'replace'))
|
||||
|
||||
def silence(self):
|
||||
self.lib.PCTKVReset()
|
||||
|
||||
def is_active(self):
|
||||
return self.lib.PCTKStatus() != 0
|
||||
|
||||
output_class = PCTalker
|
141
src/accessible_output2/outputs/sapi4.py
Normal file
141
src/accessible_output2/outputs/sapi4.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from libloader.com import load_com
|
||||
from base import Output
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Sapi4(Output):
|
||||
|
||||
name = 'sapi4'
|
||||
priority = 102
|
||||
|
||||
def __init__(self):
|
||||
sapi4 = load_com("{EEE78591-FE22-11D0-8BEF-0060081841DE}")
|
||||
self._voiceNo = sapi4.Find(0)
|
||||
sapi4.Select(self._voiceNo)
|
||||
sapi4.Speak(" ")
|
||||
self.__object = sapi4
|
||||
self._voice_list = self._available_voices()
|
||||
|
||||
def _set_capabilities(self):
|
||||
sapi4 = self.__object
|
||||
try:
|
||||
sapi4.Pitch = sapi4.Pitch
|
||||
self._min_pitch = sapi4.MinPitch
|
||||
self._max_pitch = sapi4.MaxPitch
|
||||
self._has_pitch = True
|
||||
except:
|
||||
self._min_pitch = 0
|
||||
self._max_pitch = 0
|
||||
self._has_pitch = False
|
||||
try:
|
||||
sapi4.Speed = sapi4.Speed
|
||||
self._min_rate = sapi4.MinSpeed
|
||||
self._max_rate = sapi4.MaxSpeed
|
||||
self._has_rate = True
|
||||
except:
|
||||
self._min_rate = 0
|
||||
self._max_rate = 0
|
||||
self._has_rate = False
|
||||
try:
|
||||
sapi4.VolumeLeft = sapi4.VolumeLeft
|
||||
self._min_volume = sapi4.MinVolumeLeft
|
||||
self._max_volume = sapi4.MaxVolumeLeft
|
||||
self._has_volume = True
|
||||
except:
|
||||
self._min_volume = 0
|
||||
self._max_volume = 0
|
||||
self._has_volume = False
|
||||
|
||||
def _available_voices(self):
|
||||
voice_list = []
|
||||
for voice_no in range(1, self.__object.CountEngines):
|
||||
voice_list.append(self.__object.ModeName(voice_no))
|
||||
return voice_list
|
||||
|
||||
@property
|
||||
def available_voices(self):
|
||||
return self._voice_list
|
||||
|
||||
def list_voices(self):
|
||||
return self.available_voices
|
||||
|
||||
def get_voice(self):
|
||||
return self.__object.ModeName(self._voice_no)
|
||||
|
||||
def set_voice(self, value):
|
||||
self._voice_no = self.list_voices().index(value) + 1
|
||||
self.__object.Select(self._voice_no)
|
||||
self.silence()
|
||||
self.__object.Speak(" ")
|
||||
self._set_capabilities()
|
||||
|
||||
def get_pitch(self):
|
||||
if self.has_pitch:
|
||||
return self.__object.Pitch
|
||||
|
||||
def set_pitch(self, value):
|
||||
if self.has_pitch:
|
||||
self.__object.Pitch = value
|
||||
|
||||
def get_rate(self):
|
||||
if self.has_rate:
|
||||
return self.__object.Speed
|
||||
|
||||
def set_rate(self, value):
|
||||
if self.has_rate:
|
||||
self.__object.Speed = value
|
||||
|
||||
def get_volume(self):
|
||||
if self.has_volume:
|
||||
return self.__object.VolumeLeft
|
||||
|
||||
def set_volume(self, value):
|
||||
if self.has_volume:
|
||||
self.__object.VolumeLeft = value
|
||||
|
||||
@property
|
||||
def has_pitch(self):
|
||||
return self._has_pitch
|
||||
|
||||
@property
|
||||
def has_rate(self):
|
||||
return self._has_rate
|
||||
|
||||
@property
|
||||
def has_volume(self):
|
||||
return self._has_volume
|
||||
|
||||
@property
|
||||
def min_pitch(self):
|
||||
return self._min_pitch
|
||||
|
||||
@property
|
||||
def max_pitch(self):
|
||||
return self._max_pitch
|
||||
|
||||
@property
|
||||
def min_rate(self):
|
||||
return self._min_rate
|
||||
|
||||
@property
|
||||
def max_rate(self):
|
||||
return self._max_rate
|
||||
|
||||
@property
|
||||
def min_volume(self):
|
||||
return self._min_volume
|
||||
|
||||
@property
|
||||
def max_volume(self):
|
||||
return self._max_volume
|
||||
|
||||
def speak(self, text, interrupt=False):
|
||||
if interrupt:
|
||||
self.silence()
|
||||
self.__object.Speak(text)
|
||||
|
||||
def silence(self):
|
||||
self.__object.AudioReset()
|
||||
|
||||
output_class = Sapi4
|
88
src/accessible_output2/outputs/sapi5.py
Normal file
88
src/accessible_output2/outputs/sapi5.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import config
|
||||
from collections import OrderedDict
|
||||
from libloader.com import load_com
|
||||
from base import Output, OutputError
|
||||
import pywintypes
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class SAPI5(Output):
|
||||
has_volume = True
|
||||
has_rate = True
|
||||
has_pitch = True
|
||||
min_pitch = -10
|
||||
max_pitch = 10
|
||||
min_rate = -10
|
||||
max_rate = 10
|
||||
min_volume = 0
|
||||
max_volume = 100
|
||||
name = "sapi5"
|
||||
priority = 101
|
||||
|
||||
def __init__(self):
|
||||
if config.main["general"]["voice_enabled"] == False: raise OutputError
|
||||
try:
|
||||
self.object = load_com("SAPI.SPVoice")
|
||||
self._voices = self._available_voices()
|
||||
except pywintypes.com_error:
|
||||
raise OutputError
|
||||
self._pitch = 0
|
||||
|
||||
def _available_voices(self):
|
||||
_voices = OrderedDict()
|
||||
for v in self.object.GetVoices():
|
||||
_voices[v.GetDescription()] = v
|
||||
return _voices
|
||||
|
||||
def list_voices(self):
|
||||
return self.available_voices.keys()
|
||||
|
||||
def get_voice(self):
|
||||
return self.object.Voice.GetDescription()
|
||||
|
||||
def set_voice(self, value):
|
||||
log.debug("Setting SAPI5 voice to \"%s\"" % value)
|
||||
self.object.Voice = self.available_voices[value]
|
||||
# For some reason SAPI5 does not reset audio after changing the voice
|
||||
# By setting the audio device after changing voices seems to fix this
|
||||
# This was noted from information at:
|
||||
# http://lists.nvaccess.org/pipermail/nvda-dev/2011-November/022464.html
|
||||
self.object.AudioOutput = self.object.AudioOutput
|
||||
|
||||
def get_pitch(self):
|
||||
return self._pitch
|
||||
|
||||
def set_pitch(self, value):
|
||||
log.debug("Setting pitch to %d" % value)
|
||||
self._pitch = value
|
||||
|
||||
def get_rate(self):
|
||||
return self.object.Rate
|
||||
|
||||
def set_rate(self, value):
|
||||
log.debug("Setting rate to %d" % value)
|
||||
self.object.Rate = value
|
||||
|
||||
def get_volume(self):
|
||||
return self.object.Volume
|
||||
|
||||
def set_volume(self, value):
|
||||
self.object.Volume = value
|
||||
|
||||
def speak(self, text, interrupt=False):
|
||||
if interrupt:
|
||||
self.silence()
|
||||
# We need to do the pitch in XML here
|
||||
textOutput = "<pitch absmiddle=\"%d\">%s</pitch>" % (round(self._pitch), text.replace("<", "<"))
|
||||
self.object.Speak(textOutput, 1|8)
|
||||
|
||||
def silence(self):
|
||||
self.object.Speak("", 3)
|
||||
|
||||
def is_active(self):
|
||||
if self.object:
|
||||
return True
|
||||
return False
|
||||
|
||||
output_class = SAPI5
|
29
src/accessible_output2/outputs/speechDispatcher.py
Normal file
29
src/accessible_output2/outputs/speechDispatcher.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from base import Output, OutputError
|
||||
import atexit
|
||||
|
||||
class SpeechDispatcher(Output):
|
||||
"""Supports speech dispatcher on Linux.
|
||||
Note that it will take the configuration from the speech dispatcher, the user will need configure voice, language, punctuation and rate before use this module.
|
||||
"""
|
||||
name = 'SpeechDispatcher'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SpeechDispatcher, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
import speechd
|
||||
self.spd = speechd.SSIPClient("TWBlue")
|
||||
except ImportError:
|
||||
raise OutputError
|
||||
atexit.register(self.on_exit_event)
|
||||
|
||||
def speak(self, text, interupt=False):
|
||||
if interupt == True:
|
||||
self.spd.cancel()
|
||||
self.spd.speak(text)
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
def on_exit_event(self):
|
||||
self.spd.close()
|
||||
del self.spd
|
18
src/accessible_output2/outputs/speechd/__init__.py
Normal file
18
src/accessible_output2/outputs/speechd/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2001, 2002 Brailcom, o.p.s.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
from .client import *
|
||||
|
1125
src/accessible_output2/outputs/speechd/client.py
Normal file
1125
src/accessible_output2/outputs/speechd/client.py
Normal file
File diff suppressed because it is too large
Load Diff
1
src/accessible_output2/outputs/speechd/paths.py
Normal file
1
src/accessible_output2/outputs/speechd/paths.py
Normal file
@@ -0,0 +1 @@
|
||||
SPD_SPAWN_CMD = "/usr/bin/speech-dispatcher"
|
23
src/accessible_output2/outputs/system_access.py
Normal file
23
src/accessible_output2/outputs/system_access.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from base import Output
|
||||
|
||||
class SystemAccess (Output):
|
||||
"""Supports System Access and System Access Mobile"""
|
||||
|
||||
name = "System Access"
|
||||
lib32 = 'saapi32.dll'
|
||||
priority = 99
|
||||
|
||||
def braille(self, text, **options):
|
||||
self.lib.SA_BrlShowTextW(unicode(text))
|
||||
|
||||
def speak(self, text, interrupt=False):
|
||||
if self.is_active():
|
||||
self.dll.SA_SayW(unicode(text))
|
||||
|
||||
def is_active(self):
|
||||
try:
|
||||
return self.dll.SA_IsRunning()
|
||||
except:
|
||||
return False
|
||||
|
||||
output_class = SystemAccess
|
23
src/accessible_output2/outputs/voiceover.py
Normal file
23
src/accessible_output2/outputs/voiceover.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from base import Output, OutputError
|
||||
|
||||
class VoiceOver (Output):
|
||||
"""Supports the VoiceOver screenreader on the Mac.
|
||||
|
||||
Note that this will also output as a message to the braille display if VoiceOver is used with braille.
|
||||
Calling this module could cause VoiceOver to be started.
|
||||
"""
|
||||
name = 'VoiceOver'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VoiceOver, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
from appscript import app
|
||||
self.app = app('VoiceOver')
|
||||
except ImportError:
|
||||
raise OutputError
|
||||
|
||||
def speak(self, text, interupt=False):
|
||||
self.app.output(text)
|
||||
|
||||
def is_active(self):
|
||||
return True
|
32
src/accessible_output2/outputs/window_eyes.py
Normal file
32
src/accessible_output2/outputs/window_eyes.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import win32gui
|
||||
from libloader.com import load_com
|
||||
from base import Output, OutputError
|
||||
import pywintypes
|
||||
|
||||
class WindowEyes (Output):
|
||||
"""Speech output supporting the WindowEyes screen reader"""
|
||||
|
||||
name = 'Window-Eyes'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WindowEyes, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
self.object = load_com("gwspeak.speak")
|
||||
except pywintypes.com_error:
|
||||
raise OutputError
|
||||
|
||||
def speak(self, text, interrupt=0):
|
||||
if interrupt:
|
||||
self.silence()
|
||||
self.object.SpeakString(text)
|
||||
|
||||
def silence (self):
|
||||
self.object.Silence()
|
||||
|
||||
def is_active(self):
|
||||
try:
|
||||
return win32gui.FindWindow("GWMExternalControl", "External Control") != 0
|
||||
except:
|
||||
return False
|
||||
|
||||
output_class = WindowEyes
|
Reference in New Issue
Block a user