Module nvda_service
[hide private]
[frames] | no frames]

Source Code for Module nvda_service

  1  #nvda_service.py 
  2  #A part of NonVisual Desktop Access (NVDA) 
  3  #Copyright (C) 2009-2011 NV Access Inc 
  4  #This file is covered by the GNU General Public License. 
  5  #See the file COPYING for more details. 
  6   
  7  from ctypes import * 
  8  from ctypes.wintypes import * 
  9  import threading 
 10  import win32serviceutil 
 11  import win32service 
 12  import sys 
 13  import os 
 14  import time 
 15  import subprocess 
 16  import _winreg 
 17   
 18  CREATE_UNICODE_ENVIRONMENT=1024 
 19  INFINITE = 0xffffffff 
 20  UOI_NAME = 2 
 21  SYNCHRONIZE = 0x100000 
 22  WAIT_OBJECT_0 = 0 
 23  MAXIMUM_ALLOWED = 0x2000000 
 24  SecurityIdentification = 2 
 25  TokenPrimary = 1 
 26  PROCESS_QUERY_INFORMATION = 0x0400 
 27  TokenSessionId = 12 
 28  TokenUIAccess = 26 
 29  WTS_CONSOLE_CONNECT = 0x1 
 30  WTS_CONSOLE_DISCONNECT = 0x2 
 31  WTS_SESSION_LOGON = 0x5 
 32  WTS_SESSION_LOGOFF = 0x6 
 33  WTS_SESSION_LOCK = 0x7 
 34  WTS_SESSION_UNLOCK = 0x8 
 35  WTS_CURRENT_SERVER_HANDLE = 0 
 36  WTSUserName = 5 
 37   
 38  nvdaExec = os.path.join(sys.prefix,"nvda.exe") 
 39  slaveExec = os.path.join(sys.prefix,"nvda_slave.exe") 
 40  nvdaSystemConfigDir=os.path.join(sys.prefix,'systemConfig') 
 41   
42 -class AutoHANDLE(HANDLE):
43 """A HANDLE which is automatically closed when no longer in use. 44 """ 45
46 - def __del__(self):
47 if self: 48 windll.kernel32.CloseHandle(self)
49 50 isDebug = False 51
52 -def debug(msg):
53 if not isDebug: 54 return 55 try: 56 file(os.path.join(os.getenv("windir"), "temp", "nvda_service.log"), "a").write(msg + "\n") 57 except (OSError, IOError): 58 pass
59
60 -def getInputDesktopName():
61 desktop = windll.user32.OpenInputDesktop(0, False, 0) 62 name = create_unicode_buffer(256) 63 windll.user32.GetUserObjectInformationW(desktop, UOI_NAME, byref(name), sizeof(name), None) 64 windll.user32.CloseDesktop(desktop) 65 return ur"WinSta0\%s" % name.value
66
67 -class STARTUPINFO(Structure):
68 _fields_=[ 69 ('cb',DWORD), 70 ('lpReserved',LPWSTR), 71 ('lpDesktop',LPWSTR), 72 ('lpTitle',LPWSTR), 73 ('dwX',DWORD), 74 ('dwY',DWORD), 75 ('dwXSize',DWORD), 76 ('dwYSize',DWORD), 77 ('dwXCountChars',DWORD), 78 ('dwYCountChars',DWORD), 79 ('dwFillAttribute',DWORD), 80 ('dwFlags',DWORD), 81 ('wShowWindow',WORD), 82 ('cbReserved2',WORD), 83 ('lpReserved2',POINTER(c_byte)), 84 ('hSTDInput',HANDLE), 85 ('hSTDOutput',HANDLE), 86 ('hSTDError',HANDLE), 87 ]
88
89 -class PROCESS_INFORMATION(Structure):
90 _fields_=[ 91 ('hProcess',HANDLE), 92 ('hThread',HANDLE), 93 ('dwProcessID',DWORD), 94 ('dwThreadID',DWORD), 95 ]
96
97 -def getLoggedOnUserToken(session):
98 # Only works in Windows XP and above. 99 token = AutoHANDLE() 100 windll.wtsapi32.WTSQueryUserToken(session, byref(token)) 101 return token
102
103 -def duplicateTokenPrimary(token):
104 newToken = AutoHANDLE() 105 windll.advapi32.DuplicateTokenEx(token, MAXIMUM_ALLOWED, None, SecurityIdentification, TokenPrimary, byref(newToken)) 106 return newToken
107
108 -def getOwnToken():
109 process = AutoHANDLE(windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, os.getpid())) 110 token = AutoHANDLE() 111 windll.advapi32.OpenProcessToken(process, MAXIMUM_ALLOWED, byref(token)) 112 return token
113
114 -def getSessionSystemToken(session):
115 token = duplicateTokenPrimary(getOwnToken()) 116 session = DWORD(session) 117 windll.advapi32.SetTokenInformation(token, TokenSessionId, byref(session), sizeof(DWORD)) 118 return token
119
120 -def executeProcess(desktop, token, executable, *argStrings):
121 argsString=subprocess.list2cmdline(list(argStrings)) 122 startupInfo=STARTUPINFO(cb=sizeof(STARTUPINFO),lpDesktop=desktop) 123 processInformation=PROCESS_INFORMATION() 124 cmdBuf=create_unicode_buffer(u'"%s" %s'%(executable,argsString)) 125 if token: 126 env=c_void_p() 127 windll.userenv.CreateEnvironmentBlock(byref(env),token,False) 128 try: 129 if windll.advapi32.CreateProcessAsUserW(token, None, cmdBuf,None,None,False,CREATE_UNICODE_ENVIRONMENT,env,None,byref(startupInfo),byref(processInformation)) == 0: 130 raise WinError() 131 finally: 132 windll.userenv.DestroyEnvironmentBlock(env) 133 else: 134 if windll.kernel32.CreateProcessW(None, cmdBuf,None,None,False,0,None,None,byref(startupInfo),byref(processInformation)) == 0: 135 raise WinError() 136 windll.kernel32.CloseHandle(processInformation.hThread) 137 return AutoHANDLE(processInformation.hProcess)
138
139 -def nvdaLauncher():
140 initDebug() 141 desktop = getInputDesktopName() 142 debug("launcher: starting with desktop %s" % desktop) 143 desktopBn = os.path.basename(desktop) 144 if desktopBn != u"Winlogon" and not desktopBn.startswith(u"InfoCard{"): 145 debug("launcher: user or screen-saver desktop, exiting") 146 return 147 148 debug("launcher: starting NVDA") 149 process = startNVDA(desktop) 150 desktopSwitchEvt = AutoHANDLE(windll.kernel32.OpenEventW(SYNCHRONIZE, False, u"WinSta0_DesktopSwitch")) 151 windll.kernel32.WaitForSingleObject(desktopSwitchEvt, INFINITE) 152 debug("launcher: desktop switch, exiting NVDA on desktop %s" % desktop) 153 exitNVDA(desktop) 154 # NVDA should never ever be left running on other desktops, so make certain it is dead. 155 # It may still be running if it hasn't quite finished initialising yet, in which case -q won't work. 156 windll.kernel32.TerminateProcess(process, 1)
157
158 -def startNVDA(desktop):
159 token=duplicateTokenPrimary(getOwnToken()) 160 windll.advapi32.SetTokenInformation(token,TokenUIAccess,byref(c_ulong(1)),sizeof(c_ulong)) 161 args = [desktop, token, nvdaExec, "-m", "-c", nvdaSystemConfigDir, "--no-sr-flag"] 162 if not isDebug: 163 args.append("--secure") 164 return executeProcess(*args)
165
166 -def exitNVDA(desktop):
167 token=duplicateTokenPrimary(getOwnToken()) 168 windll.advapi32.SetTokenInformation(token,TokenUIAccess,byref(c_ulong(1)),sizeof(c_ulong)) 169 process = executeProcess(desktop, token, nvdaExec, "-q") 170 windll.kernel32.WaitForSingleObject(process, 10000)
171
172 -def isUserRunningNVDA(session):
173 token = duplicateTokenPrimary(getSessionSystemToken(session)) 174 windll.advapi32.SetTokenInformation(token,TokenUIAccess,byref(c_ulong(1)),sizeof(c_ulong)) 175 process = executeProcess(ur"WinSta0\Default", token, nvdaExec, u"--check-running") 176 windll.kernel32.WaitForSingleObject(process, INFINITE) 177 exitCode = DWORD() 178 windll.kernel32.GetExitCodeProcess(process, byref(exitCode)) 179 return exitCode.value == 0
180
181 -def isSessionLoggedOn(session):
182 username = c_wchar_p() 183 size = DWORD() 184 windll.wtsapi32.WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session, WTSUserName, byref(username), byref(size)) 185 ret = bool(username.value) 186 windll.wtsapi32.WTSFreeMemory(username) 187 return ret
188
189 -def execBg(func):
190 t = threading.Thread(target=func) 191 t.setDaemon(True) 192 t.start()
193
194 -def shouldStartOnLogonScreen():
195 try: 196 k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, ur"SOFTWARE\NVDA") 197 return bool(_winreg.QueryValueEx(k, u"startOnLogonScreen")[0]) 198 except WindowsError: 199 return False
200
201 -def initDebug():
202 global isDebug 203 try: 204 k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, ur"SOFTWARE\NVDA") 205 isDebug = bool(_winreg.QueryValueEx(k, u"serviceDebug")[0]) 206 except WindowsError: 207 isDebug = False
208
209 -class NVDAService(win32serviceutil.ServiceFramework):
210 211 _svc_name_="nvda" 212 _svc_display_name_="NVDA" 213
214 - def GetAcceptedControls(self):
215 return win32serviceutil.ServiceFramework.GetAcceptedControls(self) | win32service.SERVICE_ACCEPT_SESSIONCHANGE
216
217 - def initSession(self, session):
218 debug("init session %d" % session) 219 self.session = session 220 self.launcherLock = threading.RLock() 221 self.launcherStarted = False 222 self.desktopSwitchSupervisorStarted = False 223 self.isSessionLoggedOn = isSessionLoggedOn(session) 224 debug("session logged on: %r" % self.isSessionLoggedOn) 225 226 if self.isWindowsXP and session != 0 and not self.isSessionLoggedOn: 227 # In Windows XP, sessions other than 0 are broken before logon, so we can't do anything more here. 228 debug("Windows XP, returning before action") 229 return 230 231 if self.isSessionLoggedOn: 232 # The session is logged on, so treat this as a normal desktop switch. 233 self.handleDesktopSwitch() 234 else: 235 # We're at the logon screen. 236 if shouldStartOnLogonScreen(): 237 execBg(self.startLauncher) 238 execBg(self.desktopSwitchSupervisor)
239
240 - def desktopSwitchSupervisor(self):
241 if self.desktopSwitchSupervisorStarted: 242 return 243 self.desktopSwitchSupervisorStarted = True 244 origSession = self.session 245 debug("starting desktop switch supervisor, session %d" % origSession) 246 desktopSwitchEvt = AutoHANDLE(windll.kernel32.OpenEventW(SYNCHRONIZE, False, u"Session\%d\WinSta0_DesktopSwitch" % self.session)) 247 if not desktopSwitchEvt: 248 try: 249 raise WinError() 250 except Exception, e: 251 debug("error opening event: %s" % e) 252 raise 253 254 while True: 255 windll.kernel32.WaitForSingleObject(desktopSwitchEvt, INFINITE) 256 if self.session != origSession: 257 break 258 debug("desktop switch, session %r" % self.session) 259 self.handleDesktopSwitch() 260 261 debug("desktop switch supervisor terminated, session %d" % origSession)
262
263 - def handleDesktopSwitch(self):
264 with self.launcherLock: 265 self.launcherStarted = False 266 267 if (not self.isSessionLoggedOn and shouldStartOnLogonScreen()) or isUserRunningNVDA(self.session): 268 self.startLauncher() 269 else: 270 debug("not starting launcher")
271
272 - def SvcOtherEx(self, control, eventType, data):
273 if control == win32service.SERVICE_CONTROL_SESSIONCHANGE: 274 self.handleSessionChange(eventType, data[0])
275
276 - def handleSessionChange(self, event, session):
277 if event == WTS_CONSOLE_CONNECT: 278 debug("connect %d" % session) 279 if session != self.session: 280 self.initSession(session) 281 elif event == WTS_SESSION_LOGON: 282 debug("logon %d" % session) 283 self.isSessionLoggedOn = True 284 execBg(self.desktopSwitchSupervisor) 285 elif event == WTS_SESSION_LOGOFF: 286 debug("logoff %d" % session) 287 self.isSessionLoggedOn = False 288 if session == 0 and shouldStartOnLogonScreen(): 289 # In XP, a logoff in session 0 does not cause a new session to be created. 290 # Instead, we're probably heading back to the logon screen. 291 execBg(self.startLauncher) 292 elif event == WTS_SESSION_LOCK: 293 debug("lock %d" % session) 294 # If the user was running NVDA, the desktop switch will have started NVDA on the secure desktop. 295 # This only needs to cover the case where the user was not running NVDA and the session is locked. 296 # In this case, we should treat the lock screen like the logon screen. 297 if session == self.session and shouldStartOnLogonScreen(): 298 self.startLauncher()
299
300 - def startLauncher(self):
301 with self.launcherLock: 302 if self.launcherStarted: 303 return 304 305 debug("attempt launcher start on session %d" % self.session) 306 token = getSessionSystemToken(self.session) 307 try: 308 process = executeProcess(ur"WinSta0\Winlogon", token, slaveExec, u"service_NVDALauncher") 309 self.launcherStarted = True 310 debug("launcher started on session %d" % self.session) 311 except Exception, e: 312 debug("error starting launcher: %s" % e)
313
314 - def SvcDoRun(self):
315 initDebug() 316 debug("service starting") 317 self.isWindowsXP = sys.getwindowsversion()[0:2] == (5, 1) 318 self.exitEvent = threading.Event() 319 self.initSession(windll.kernel32.WTSGetActiveConsoleSessionId()) 320 self.exitEvent.wait() 321 debug("service exiting")
322
323 - def SvcStop(self):
324 self.exitEvent.set()
325
326 -def installService(nvdaDir):
327 servicePath = os.path.join(nvdaDir, __name__ + ".exe") 328 if not os.path.isfile(servicePath): 329 raise RuntimeError("Could not find service executable") 330 win32serviceutil.InstallService(None, NVDAService._svc_name_, NVDAService._svc_display_name_, startType=win32service.SERVICE_AUTO_START, exeName=servicePath)
331
332 -def removeService():
333 win32serviceutil.RemoveService(NVDAService._svc_name_)
334
335 -def startService():
336 win32serviceutil.StartService(NVDAService._svc_name_)
337
338 -def stopService():
339 """Stop the running service and wait for its process to die. 340 """ 341 scm = win32service.OpenSCManager(None,None,win32service.SC_MANAGER_ALL_ACCESS) 342 try: 343 serv = win32service.OpenService(scm, NVDAService._svc_name_, win32service.SERVICE_ALL_ACCESS) 344 try: 345 pid = win32service.QueryServiceStatusEx(serv)["ProcessId"] 346 347 # Stop the service. 348 win32service.ControlService(serv, win32service.SERVICE_CONTROL_STOP) 349 350 # Wait for the process to exit. 351 proc = AutoHANDLE(windll.kernel32.OpenProcess(SYNCHRONIZE, False, pid)) 352 if not proc: 353 return 354 windll.kernel32.WaitForSingleObject(proc, INFINITE) 355 356 finally: 357 win32service.CloseServiceHandle(serv) 358 finally: 359 win32service.CloseServiceHandle(scm)
360 361 if __name__=='__main__': 362 if not getattr(sys, "frozen", None): 363 raise RuntimeError("Can only be run compiled with py2exe") 364 win32serviceutil.HandleCommandLine(NVDAService) 365