5151post_appSwitch = extensionPoints .Action ()
5252
5353
54+ _executableNamesToAppMods : Dict [str , str ] = {
55+ # Azure Data Studio (both stable and Insiders versions) should use module for Visual Studio Code
56+ "azuredatastudio" : "code" ,
57+ "azuredatastudio-insiders" : "code" ,
58+ # Windows 11 calculator should use module for the Windows 10 one.
59+ "calculatorapp" : "calculator" ,
60+ # The Insider version of Visual Studio Code should use the module for the stable version.
61+
62+ "code - insiders" : "code" ,
63+ # commsapps is an alias for the Windows 10 mail and calendar.
64+ "commsapps" : "hxmail" ,
65+ # DBeaver is based on Eclipse and should use its appModule.
66+ "dbeaver" : "eclipse" ,
67+ # Preview version of the Adobe Digital Editions should use the module for the stable version.
68+ "digitaleditionspreview" : "digitaleditions" ,
69+ # Esybraille should use module for esysuite.
70+ "esybraille" : "esysuite" ,
71+ # hxoutlook is an alias for Windows 10 mail in Creators update.
72+ "hxoutlook" : "hxmail" ,
73+ # 64-bit versions of Miranda IM should use module for the 32-bit executable.
74+ "miranda64" : "miranda32" ,
75+ # Various incarnations of Media Player Classic.
76+ "mpc-hc" : "mplayerc" ,
77+ "mpc-hc64" : "mplayerc" ,
78+ # searchapp is an alias for searchui in Windows 10 build 18965 and later.
79+ "searchapp" : "searchui" ,
80+ # Windows search in Windows 11.
81+ "searchhost" : "searchui" ,
82+ # Spring Tool Suite is based on Eclipse and should use its appModule.
83+ "springtoolsuite4" : "eclipse" ,
84+ "sts" : "eclipse" ,
85+ # Various versions of Teamtalk.
86+ "teamtalk3" : "teamtalk4classic" ,
87+ # App module for Windows 10/11 Modern Keyboard aka new touch keyboard panel
88+ # should use Composable Shell modern keyboard app module
89+ "textinputhost" : "windowsinternal_composableshell_experiences_textinput_inputapp" ,
90+ # Total Commander X64 should use the module for the 32-bit version.
91+ "totalcmd64" : "totalcmd" ,
92+ # The calculator on Windows Server and LTSB versions of Windows 10
93+ # should use the module for the desktop calculator from the earlier Windows releases.
94+ "win32calc" : "calc" ,
95+ # Windows Mail should use module for Outlook Express.
96+ "winmail" : "msimn" ,
97+ # Zend Eclipse PHP Developer Tools is based on Eclipse and should use its appModule.
98+ "zend-eclipse-php" : "eclipse" ,
99+ # Zend Studio is based on Eclipse and should use its appModule.
100+ "zendstudio" : "eclipse" ,
101+ }
102+
103+ """Maps names of the executables to the names of the appModule which should be loaded for the given program.
104+ This mapping is needed since:
105+ - Names of some programs are incompatible with the Python's import system (they contain a dot or a plus)
106+ - Sometimes it is necessary to map one module to multiple executables - this map saves us from adding multiple
107+ appModules in such cases.
108+ """
109+
110+
54111class processEntry32W (ctypes .Structure ):
55112 _fields_ = [
56113 ("dwSize" ,ctypes .wintypes .DWORD ),
@@ -65,6 +122,29 @@ class processEntry32W(ctypes.Structure):
65122 ("szExeFile" , ctypes .c_wchar * 260 )
66123 ]
67124
125+
126+ def registerExecutableWithAppModule (executableName : str , appModName : str ) -> None :
127+ """Registers appModule to be used for a given executable.
128+ """
129+ _executableNamesToAppMods [executableName ] = appModName
130+
131+
132+ def unregisterExecutable (executableName : str ) -> None :
133+ """Removes the executable of a given name from the mapping of applications to appModules.
134+ """
135+ try :
136+ del _executableNamesToAppMods [executableName ]
137+ except KeyError :
138+ log .error (f"Executable { executableName } was not previously registered." )
139+
140+
141+ def _getAppModuleNameFromExecutable (executableName : str ) -> str :
142+ """Returns name of the appModule which should be used for a given executable.
143+ If there is no mapping for the given program just returns the executable name.
144+ """
145+ return _executableNamesToAppMods .get (executableName , executableName )
146+
147+
68148def getAppNameFromProcessID (processID ,includeExt = False ):
69149 """Finds out the application name of the given process.
70150 @param processID: the ID of the process handle of the application you wish to get the name of.
@@ -95,7 +175,10 @@ def getAppNameFromProcessID(processID,includeExt=False):
95175 # This might be an executable which hosts multiple apps.
96176 # Try querying the app module for the name of the app being hosted.
97177 try :
98- mod = importlib .import_module ("appModules.%s" % appName , package = "appModules" )
178+ mod = importlib .import_module (
179+ f"appModules.{ _getAppModuleNameFromExecutable (appName )} " ,
180+ package = "appModules"
181+ )
99182 return mod .getAppNameFromHost (processID )
100183 except (ImportError , AttributeError , LookupError ):
101184 pass
@@ -153,25 +236,30 @@ def cleanup():
153236 except :
154237 log .exception ("Error terminating app module %r" % deadMod )
155238
156- def doesAppModuleExist (name ):
157- return any (importer .find_module ("appModules.%s" % name ) for importer in _importers )
158239
159- def fetchAppModule (processID ,appName ):
240+ def doesAppModuleExist (name : str ) -> bool :
241+ return any (
242+ importer .find_module (
243+ f"appModules.{ _getAppModuleNameFromExecutable (name )} "
244+ ) for importer in _importers
245+ )
246+
247+
248+ def fetchAppModule (processID : int , appName : str ) -> AppModule :
160249 """Returns an appModule found in the appModules directory, for the given application name.
161250 @param processID: process ID for it to be associated with
162- @type processID: integer
163251 @param appName: the application name for which an appModule should be found.
164- @type appName: str
165252 @returns: the appModule, or None if not found
166- @rtype: AppModule
167253 """
168254 # First, check whether the module exists.
169255 # We need to do this separately because even though an ImportError is raised when a module can't be found, it might also be raised for other reasons.
170256 modName = appName
171257
172258 if doesAppModuleExist (modName ):
173259 try :
174- return importlib .import_module ("appModules.%s" % modName , package = "appModules" ).AppModule (processID , appName )
260+ return importlib .import_module (
261+ f"appModules.{ _getAppModuleNameFromExecutable (modName )} " , package = "appModules"
262+ ).AppModule (processID , appName )
175263 except :
176264 log .exception (f"error in appModule { modName !r} " )
177265 import ui
@@ -311,6 +399,10 @@ class AppModule(baseObject.ScriptableObject):
311399 Each app module should be a Python module or a package in the appModules package
312400 named according to the executable it supports;
313401 e.g. explorer.py for the explorer.exe application or firefox/__init__.py for firefox.exe.
402+ If the name of the executable is not compatible with the Python's import system
403+ i.e. contains some special characters such as "." or "+" you can name the module however you like
404+ and then map the executable name to the module name
405+ by adding an entry to `_executableNamesToAppMods` dictionary.
314406 It should containa C{AppModule} class which inherits from this base class.
315407 App modules can implement and bind gestures to scripts.
316408 These bindings will only take effect while an object in the associated application has focus.
0 commit comments