This is exciting!!! -------- Original Message --------Subject: [Nvda-dev] commit r2928 - in trunk: . installer source source/NVDAHelper source/appModules source/config source/gui user_docs/en
Date: Fri, 15 May 2009 07:13:21 +0000 From: NVDA Subversion <svn@xxxxxxxxxxxxxxxx>Reply-To: News and discussion for NVDA (NonVisual Desktop Access), a free and open source screen reader for Microsoft Windows <nvda-dev@xxxxxxxxxxxxxxxxxx>
To: nvda-dev@xxxxxxxxxxxxxxxxxx Author: nvda Date: Fri May 15 07:13:17 2009 New Revision: 2928 Log: * new: NVDA can be configured to start automatically after you log on to Windows. The option is in the General Settings dialog. * new: NVDA can read secure Windows screens such as the Windows logon, control+alt+delete and UAC screens in Windows XP and above. Reading of the Windows logon screen can be configured from the General Settings dialog. (#97) * fix: On Windows Vista, the NVDA installer now starts NVDA with normal user privileges when requested to run NVDA on the finish screen. New misc-deps package, version 2009-05-15-01. Includes new NVDAHelper with bug fixes, new nvVBufLib with bug fixes and optimisations, and NSIS components for secure desktop and UAC support. Added: trunk/source/NVDAHelper/IA2Support.cpp - copied, changed from r2927, /trunk/source/NVDAHelper/IA2Support.c trunk/source/NVDAHelper/hookManager.cpp - copied, changed from r2927, /trunk/source/NVDAHelper/hookManager.c trunk/source/NVDAHelper/inputLangChange.cpp - copied, changed from r2927, /trunk/source/NVDAHelper/inputLangChange.c trunk/source/NVDAHelper/typedCharacter.cpp - copied, changed from r2927, /trunk/source/NVDAHelper/typedCharacter.c trunk/source/appModules/logonui.py trunk/source/nvda_service.py trunk/source/nvda_slave.pyw trunk/source/shellapi.py Removed: trunk/source/NVDAHelper/IA2Support.c trunk/source/NVDAHelper/hookManager.c trunk/source/NVDAHelper/inputLangChange.c trunk/source/NVDAHelper/typedCharacter.c Modified: trunk/dependencies.txt trunk/installer/nvda.nsi trunk/source/IAccessibleHandler.py trunk/source/NVDAHelper/hookManager.h trunk/source/config/__init__.py trunk/source/gui/settingsDialogs.py trunk/source/nvda.pyw trunk/source/setup.py trunk/source/winKernel.py trunk/source/winUser.py trunk/user_docs/en/whats new.txt Modified: trunk/dependencies.txt ============================================================================== --- trunk/dependencies.txt (original) +++ trunk/dependencies.txt Fri May 15 07:13:17 2009 @@ -7,7 +7,7 @@ http://www.wxpython.org/ Python Windows Extensions (for Python 2.6), build 212 or later: http://www.sourceforge.net/projects/pywin32/ -Several other packages, made available for convenience in the NVDA miscellaneous dependencies package, version 2009-04-16-01 or later: +Several other packages, made available for convenience in the NVDA miscellaneous dependencies package, version 2009-05-15-01 or later: http://www.nvda-project.org/wiki/MiscellaneousDependencies To build a binary version of NVDA, you will also need: Modified: trunk/installer/nvda.nsi ============================================================================== --- trunk/installer/nvda.nsi (original) +++ trunk/installer/nvda.nsi Fri May 15 07:13:17 2009 @@ -11,6 +11,7 @@ !include "Library.nsh" !include "FileFunc.nsh" + ;-------- ;Settings @@ -36,6 +37,7 @@ SetDateSave on XPStyle on InstProgressFlags Smooth +RequestExecutionLevel user /* RequestExecutionLevel REQUIRED! */ !define MUI_ABORTWARNING ;Should ask to exit !define MUI_UNINSTALLER ;We want an uninstaller to be generated @@ -54,6 +56,8 @@ !define MUI_CUSTOMFUNCTION_GUIINIT NVDA_GUIInit !define MUI_CUSTOMFUNCTION_ABORT userAbort +!include "serviceLib.nsh" + ;-------------------------------- ;Pages @@ -71,6 +75,9 @@ ;Directory selection page !insertmacro MUI_PAGE_DIRECTORY +;Components selection page +!insertmacro MUI_PAGE_COMPONENTS + ;Start menu page Var StartMenuFolder !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM" @@ -153,6 +160,8 @@ ReserveFile "${NSISDIR}\Plugins\system.dll" ReserveFile "${NSISDIR}\Plugins\banner.dll" ReserveFile "waves\${SNDLogo}" +ReserveFile "UAC.dll" +!addplugindir "." ;----- ;Include install logger code (depends on some above settings) @@ -170,8 +179,7 @@ ;----- ;Sections -;The only installable section -Section "install" +Section "-NVDA" SetShellVarContext all SetOutPath "$INSTDIR" ; open and close uninstallation log after ennumerating all the files being copied @@ -214,10 +222,19 @@ WriteRegStr ${INSTDIR_REG_ROOT} "Software\${PRODUCT}" "" $INSTDIR SectionEnd +section "nvda service (Windows logon / Security dialog support)" +!insertmacro SERVICE create "nvda" "path=$INSTDIR\nvda_service.exe;autostart=1;display=NonVisual Desktop Access;description=Runs NVDA at Windows logon and in Windows security dialogs;" +!insertmacro SERVICE "start" "nvda" "" +SectionEnd + ;The uninstall section Section "Uninstall" SetShellVarContext all - +;Stop and uninstall the service +!undef UN +!define UN "un." +!insertmacro SERVICE stop "nvda" "" +!insertmacro SERVICE delete "nvda" "" ; Uninstall libraries !insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$INSTDIR\lib\NVDAHelper.dll" !insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$INSTDIR\lib\VBufBase.dll" @@ -246,6 +263,18 @@ ;Functions Function .onInit +UAC::RunElevated +;If couldn't change user then fail +strcmp 0 $0 +1 elevationFail +;If we are the outer user process, then silently quit +strcmp 1 $1 +1 +2 +quit +;If we are now an admin, success +strcmp 1 $3 elevationSuccess +elevationFail: +MessageBox mb_iconstop "Unable to elevate, error $0" +quit +elevationSuccess: strcpy $runAppOnInstSuccess "0" ; Fix an error from previous installers where the "nvda" file would be left behind after uninstall IfFileExists "$PROGRAMFILES\NVDA" 0 @@ -356,7 +385,7 @@ !insertmacro UNINSTALL.LOG_UPDATE_INSTALL Execwait "$PLUGINSDIR\${NVDATempDir}\${NVDAApp} -q" strcmp $runAppOnInstSuccess "1" +1 end -Exec "$INSTDIR\${NVDAApp}" +uac::exec "" "$INSTDIR\${NVDAApp}" "" "" end: FunctionEnd @@ -367,6 +396,7 @@ Function .onGUIEnd ; Clean up the temporary folder rmdir /R /REBOOTOK "$PLUGINSDIR\${NVDATempDir}" +UAC::Unload ;Must call unload! FunctionEnd function makeRunAppOnInstSuccess Modified: trunk/source/IAccessibleHandler.py ============================================================================== --- trunk/source/IAccessibleHandler.py (original) +++ trunk/source/IAccessibleHandler.py Fri May 15 07:13:17 2009 @@ -898,9 +898,6 @@ def _get_role(self): return controlTypes.ROLE_PANE - def _get_description(self): - return _("Inaccessible to NVDA") - def processDesktopSwitchWinEvent(window,objectID,childID): hDesk=ctypes.windll.user32.OpenInputDesktop(0, False, 0) #name = ctypes.create_string_buffer(256) Modified: trunk/source/NVDAHelper/hookManager.h ============================================================================== --- trunk/source/NVDAHelper/hookManager.h (original) +++ trunk/source/NVDAHelper/hookManager.h Fri May 15 07:13:17 2009 @@ -7,7 +7,7 @@ #include<windows.h> #include<wchar.h> -#define DLLEXPORT __declspec(dllexport) +#define DLLEXPORT extern "C" __declspec(dllexport) //Private variables extern HINSTANCE moduleHandle; Added: trunk/source/appModules/logonui.py ============================================================================== --- (empty file) +++ trunk/source/appModules/logonui.py Fri May 15 07:13:17 2009 @@ -0,0 +1,44 @@ +import keyUtils +from NVDAObjects.IAccessible import IAccessible +import _default + +class PasswordField(IAccessible): + + def bindKeys(self): + for key, script in ( + ("extendedUp", "changeUser"), + ("extendedDown", "changeUser"), + ): + self.bindKey_runtime(key, script) + + def _get_name(self): + # Focus automatically jumps to the password field when a user is selected. This field has no name. + # This means that the new selected user is not reported. + # Therefore, override the name of the password field to be the selected user name. + try: + # The accessibility hierarchy is totally screwed here, so NVDA gets confused. + # Therefore, we'll have to do it the ugly way... + return self.IAccessibleObject.accParent.accName(0) + except: + return super(PasswordField, self).name + + def script_changeUser(self, key): + # The up and down arrow keys change the selected user, but there's no reliable NVDA event for detecting this. + oldName = self.name + keyUtils.sendKey(key) + if oldName == self.name: + return + self.event_gainFocus() + +class AppModule(_default.AppModule): + + def event_NVDAObject_init(self, obj): + if obj.windowClassName == "NativeHWNDHost" and obj.parent and not obj.parent.parent: + # Make sure the top level pane is always presented. + obj.isPresentableFocusAncestor = True + return + + if obj.windowClassName == "Edit" and not obj.name and not obj.parent: + self.overlayCustomNVDAObjectClass(obj, PasswordField, outerMost=True) + obj.bindKeys() + return Modified: trunk/source/config/__init__.py ============================================================================== --- trunk/source/config/__init__.py (original) +++ trunk/source/config/__init__.py Fri May 15 07:13:17 2009 @@ -4,6 +4,7 @@ import globalVars import _winreg import os +import sys from cStringIO import StringIO from configobj import ConfigObj from validate import Validator @@ -205,3 +206,73 @@ if ctypes.windll.shell32.SHGetSpecialFolderPathW(0,buf,CSIDL_APPDATA,0): return u'%s\\nvda'%buf.value return u'.\\' + +RUN_REGKEY = ur"SOFTWARE\Microsoft\Windows\CurrentVersion\Run" + +def getStartAfterLogon(): + try: + k = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, RUN_REGKEY) + val = _winreg.QueryValueEx(k, u"nvda")[0] + return os.stat(val) == os.stat(sys.argv[0]) + except (WindowsError, OSError): + return False + +def setStartAfterLogon(enable): + k = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, RUN_REGKEY, 0, _winreg.KEY_WRITE) + if enable: + _winreg.SetValueEx(k, u"nvda", None, _winreg.REG_SZ, sys.argv[0]) + else: + _winreg.DeleteValue(k, u"nvda") + +SERVICE_FILENAME = u"nvda_service.exe" + +def isServiceInstalled(): + if not os.path.isfile(SERVICE_FILENAME): + return False + try: + k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, ur"SYSTEM\CurrentControlSet\Services\nvda") + val = _winreg.QueryValueEx(k, u"ImagePath")[0].replace(u'"', u'') + return os.stat(val) == os.stat(SERVICE_FILENAME) + except (WindowsError, OSError): + return False + +def execElevated(path, params=None, wait=False): + import shellapi + import winKernel + import winUser + sei = shellapi.SHELLEXECUTEINFO(lpVerb=u"runas", lpFile=os.path.abspath(path), lpParameters=params, nShow=winUser.SW_HIDE) + if wait: + sei.fMask = shellapi.SEE_MASK_NOCLOSEPROCESS + shellapi.ShellExecuteEx(sei) + if wait: + try: + winKernel.waitForSingleObject(sei.hProcess, winKernel.INFINITE) + return winKernel.GetExitCodeProcess(sei.hProcess) + finally: + winKernel.closeHandle(sei.hProcess) + +SLAVE_FILENAME = u"nvda_slave.exe" + +NVDA_REGKEY = ur"SOFTWARE\NVDA" + +def getStartOnLogonScreen(): + try: + k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY) + return bool(_winreg.QueryValueEx(k, u"startOnLogonScreen")[0]) + except WindowsError: + return False + +def _setStartOnLogonScreen(enable): + k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY, 0, _winreg.KEY_WRITE) + _winreg.SetValueEx(k, u"startOnLogonScreen", None, _winreg.REG_DWORD, int(enable)) + +def setStartOnLogonScreen(enable): + if getStartOnLogonScreen() == enable: + return + try: + # Try setting it directly. + _setStartOnLogonScreen(enable) + except WindowsError: + # We probably don't have admin privs, so we need to elevate to do this using the slave. + if execElevated(SLAVE_FILENAME, "config_setStartOnLogonScreen %d" % enable, wait=True) != 0: + raise RuntimeError("Slave failed to set startOnLogonScreen") Modified: trunk/source/gui/settingsDialogs.py ============================================================================== --- trunk/source/gui/settingsDialogs.py (original) +++ trunk/source/gui/settingsDialogs.py Fri May 15 07:13:17 2009 @@ -128,6 +128,16 @@ log.debugWarning("Could not set log level list to current log level") logLevelSizer.Add(self.logLevelList) settingsSizer.Add(logLevelSizer,border=10,flag=wx.BOTTOM) + self.startAfterLogonCheckBox = wx.CheckBox(self, wx.ID_ANY, label=_("&Automatically start NVDA after I log on to Windows")) + self.startAfterLogonCheckBox.SetValue(config.getStartAfterLogon()) + if not config.isInstalledCopy(): + self.startAfterLogonCheckBox.Disable() + settingsSizer.Add(self.startAfterLogonCheckBox) + self.startOnLogonScreenCheckBox = wx.CheckBox(self, wx.ID_ANY, label=_("Use NVDA on the Windows logon screen (requires administrator privileges)")) + self.startOnLogonScreenCheckBox.SetValue(config.getStartOnLogonScreen()) + if not config.isServiceInstalled(): + self.startOnLogonScreenCheckBox.Disable() + settingsSizer.Add(self.startOnLogonScreenCheckBox) def postInit(self): self.languageList.SetFocus() @@ -147,6 +157,13 @@ logLevel=self.LOG_LEVELS[self.logLevelList.GetSelection()][0] config.conf["general"]["loggingLevel"]=logHandler.levelNames[logLevel] logHandler.setLogLevelFromConfig() + if self.startAfterLogonCheckBox.IsEnabled(): + config.setStartAfterLogon(self.startAfterLogonCheckBox.GetValue()) + if self.startOnLogonScreenCheckBox.IsEnabled(): + try: + config.setStartOnLogonScreen(self.startOnLogonScreenCheckBox.GetValue()) + except (WindowsError, RuntimeError): + wx.MessageBox(_("This change requires administrator privileges."), _("Insufficient Privileges"), style=wx.OK | wx.ICON_ERROR) if self.oldLanguage!=newLanguage: if wx.MessageDialog(self,_("For the new language to take effect, the configuration must be saved and NVDA must be restarted. Press enter to save and restart NVDA, or cancel to manually save and exit at a later time."),_("Language Configuration Change"),wx.OK|wx.CANCEL|wx.ICON_WARNING).ShowModal()==wx.ID_OK: config.save() Modified: trunk/source/nvda.pyw ============================================================================== --- trunk/source/nvda.pyw (original) +++ trunk/source/nvda.pyw Fri May 15 07:13:17 2009 @@ -62,6 +62,7 @@ parser=NoConsoleOptionParser() parser.add_option('-q','--quit',action="store_true",dest='quit',default=False,help="Quit already running copy of NVDA") parser.add_option('-r','--replace',action="store_true",dest='replace',default=False,help="Quit already running copy of NVDA and start this one") +parser.add_option('-k','--check-running',action="store_true",dest='check_running',default=False,help="Report whether NVDA is running via the exit code; 0 if running, 1 if not running") parser.add_option('-f','--log-file',dest='logFileName',default=logFileName,help="The file where log messages should be written to") parser.add_option('-l','--log-level',type="int",dest='logLevel',default=0,help="The lowest level of message logged (debug 10, info 20, warning 30, error 40, critical 50), default is warning") parser.add_option('-c','--config-path',dest='configPath',default=config.getUserDefaultConfigPath(),help="The path where all settings for NVDA are stored") @@ -87,6 +88,9 @@ sys.exit(1) if globalVars.appArgs.quit or (oldAppWindowHandle and not globalVars.appArgs.replace): sys.exit(0) +elif globalVars.appArgs.check_running: + # NVDA is not running. + sys.exit(1) #os.environ['PYCHECKER']="--limit 10000 -q --changetypes" #import pychecker.checker Added: trunk/source/nvda_service.py ============================================================================== --- (empty file) +++ trunk/source/nvda_service.py Fri May 15 07:13:17 2009 @@ -0,0 +1,288 @@ +from ctypes import * +from ctypes.wintypes import * +import threading +import win32serviceutil +import win32service +import sys +import os +import time +import _winreg + +CREATE_UNICODE_ENVIRONMENT=1024 +INFINITE = 0xffffffff +UOI_NAME = 2 +SYNCHRONIZE = 0x100000 +WAIT_OBJECT_0 = 0 +MAXIMUM_ALLOWED = 0x2000000 +SecurityIdentification = 2 +TokenPrimary = 1 +PROCESS_QUERY_INFORMATION = 0x0400 +TokenSessionId = 12 +TokenUIAccess = 26 +WTS_CONSOLE_CONNECT = 0x1 +WTS_CONSOLE_DISCONNECT = 0x2 +WTS_SESSION_LOGON = 0x5 +WTS_SESSION_LOGOFF = 0x6 +WTS_SESSION_LOCK = 0x7 +WTS_SESSION_UNLOCK = 0x8 +WTS_CURRENT_SERVER_HANDLE = 0 +WTSUserName = 5 + +nvdaExec = os.path.join(sys.prefix,"nvda.exe") +slaveExec = os.path.join(sys.prefix,"nvda_slave.exe") + +def debug(msg): + try: + file(os.path.join(os.getenv("windir"), "temp", "nvda_service.log"), "a").write(msg + "\n") + except (OSError, IOError): + pass + +def getInputDesktopName(): + desktop = windll.user32.OpenInputDesktop(0, False, 0) + name = create_unicode_buffer(256) + windll.user32.GetUserObjectInformationW(desktop, UOI_NAME, byref(name), sizeof(name), None) + windll.user32.CloseDesktop(desktop) + return ur"WinSta0\%s" % name.value + +class STARTUPINFO(Structure): + _fields_=[ + ('cb',DWORD), + ('lpReserved',LPWSTR), + ('lpDesktop',LPWSTR), + ('lpTitle',LPWSTR), + ('dwX',DWORD), + ('dwY',DWORD), + ('dwXSize',DWORD), + ('dwYSize',DWORD), + ('dwXCountChars',DWORD), + ('dwYCountChars',DWORD), + ('dwFillAttribute',DWORD), + ('dwFlags',DWORD), + ('wShowWindow',WORD), + ('cbReserved2',WORD), + ('lpReserved2',POINTER(c_byte)), + ('hSTDInput',HANDLE), + ('hSTDOutput',HANDLE), + ('hSTDError',HANDLE), + ] + +class PROCESS_INFORMATION(Structure): + _fields_=[ + ('hProcess',HANDLE), + ('hThread',HANDLE), + ('dwProcessID',DWORD), + ('dwThreadID',DWORD), + ] + +def getLoggedOnUserToken(session): + # Only works in Windows XP and above. + token = HANDLE() + windll.wtsapi32.WTSQueryUserToken(session, byref(token)) + return token.value + +def duplicateTokenPrimary(token): + newToken = HANDLE() + windll.advapi32.DuplicateTokenEx(token, MAXIMUM_ALLOWED, None, SecurityIdentification, TokenPrimary, byref(newToken)) + windll.kernel32.CloseHandle(token) + return newToken.value + +def getOwnToken(): + process = windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, os.getpid()) + token = HANDLE() + windll.advapi32.OpenProcessToken(process, MAXIMUM_ALLOWED, byref(token)) + windll.kernel32.CloseHandle(process) + return token + +def getSessionSystemToken(session): + token = duplicateTokenPrimary(getOwnToken()) + session = DWORD(session) + windll.advapi32.SetTokenInformation(token, TokenSessionId, byref(session), sizeof(DWORD)) + return token + +def executeProcess(desktop, token, executable, *argStrings): + argsString=" ".join(list(argStrings)) + startupInfo=STARTUPINFO(cb=sizeof(STARTUPINFO),lpDesktop=desktop) + processInformation=PROCESS_INFORMATION() + cmdBuf=create_unicode_buffer(u'"%s" %s'%(executable,argsString)) + if token: + env=c_void_p() + windll.userenv.CreateEnvironmentBlock(byref(env),token,False) + try: + if windll.advapi32.CreateProcessAsUserW(token, None, cmdBuf,None,None,False,CREATE_UNICODE_ENVIRONMENT,env,None,byref(startupInfo),byref(processInformation)) == 0: + raise WinError() + finally: + windll.kernel32.CloseHandle(token) + windll.userenv.DestroyEnvironmentBlock(env) + else: + if windll.kernel32.CreateProcessW(None, cmdBuf,None,None,False,0,None,None,byref(startupInfo),byref(processInformation)) == 0: + raise WinError() + return processInformation.hProcess + +def nvdaLauncher(): + desktop = getInputDesktopName() + if desktop == ur"WinSta0\Default": + return + + startNVDA(desktop) + desktopSwitchEvt = windll.kernel32.OpenEventW(SYNCHRONIZE, False, u"WinSta0_DesktopSwitch") + windll.kernel32.WaitForSingleObject(desktopSwitchEvt, INFINITE) + windll.kernel32.CloseHandle(desktopSwitchEvt) + exitNVDA(desktop) + +def startNVDA(desktop): + process = executeProcess(desktop, None, nvdaExec, "-m") + windll.kernel32.CloseHandle(process) + +def startNVDAUIAccess(session, desktop): + token = duplicateTokenPrimary(getLoggedOnUserToken(session)) + uiAccess = ULONG(1) + windll.advapi32.SetTokenInformation(token, TokenUIAccess, byref(uiAccess), sizeof(ULONG)) + process = executeProcess(desktop, token, nvdaExec, "-m") + windll.kernel32.CloseHandle(process) + +def exitNVDA(desktop): + process = executeProcess(desktop, None, nvdaExec, "-q") + windll.kernel32.WaitForSingleObject(process, 10000) + windll.kernel32.CloseHandle(process) + +def isUserRunningNVDA(session): + token = getSessionSystemToken(session) + process = executeProcess(ur"WinSta0\Default", token, nvdaExec, u"--check-running") + windll.kernel32.WaitForSingleObject(process, INFINITE) + exitCode = DWORD() + windll.kernel32.GetExitCodeProcess(process, byref(exitCode)) + windll.kernel32.CloseHandle(process) + return exitCode.value == 0 + +def isSessionLoggedOn(session): + username = c_wchar_p() + size = DWORD() + windll.wtsapi32.WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session, WTSUserName, byref(username), byref(size)) + ret = bool(username.value) + windll.wtsapi32.WTSFreeMemory(username) + return ret + +def execBg(func): + t = threading.Thread(target=func) + t.setDaemon(True) + t.start() + +def shouldStartOnLogonScreen(): + try: + k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, ur"SOFTWARE\NVDA") + return bool(_winreg.QueryValueEx(k, u"startOnLogonScreen")[0]) + except WindowsError: + return False + +class NVDAService(win32serviceutil.ServiceFramework): + + _svc_name_="nvda" + _svc_display_name_="nonVisual Desktop Access" + + def GetAcceptedControls(self): + return win32serviceutil.ServiceFramework.GetAcceptedControls(self) | win32service.SERVICE_ACCEPT_SESSIONCHANGE + + def initSession(self, session): + debug("init session %d" % session) + self.session = session + self.launcherLock = threading.RLock() + self.launcherStarted = False + self.desktopSwitchSupervisorStarted = False + self.isSessionLoggedOn = isSessionLoggedOn(session) + debug("session logged on: %r" % self.isSessionLoggedOn) + + if self.isSessionLoggedOn: + # The session is logged on, so treat this as a normal desktop switch. + self.handleDesktopSwitch() + execBg(self.desktopSwitchSupervisor) + else: + # We're at the logon screen. + if shouldStartOnLogonScreen(): + execBg(self.startLauncher) + # The desktop switch supervisor will be started by the logon event. + + def desktopSwitchSupervisor(self): + if self.desktopSwitchSupervisorStarted: + return + self.desktopSwitchSupervisorStarted = True + origSession = self.session + debug("starting desktop switch supervisor, session %d" % origSession) + desktopSwitchEvt = windll.kernel32.OpenEventW(SYNCHRONIZE, False, u"Session\%d\WinSta0_DesktopSwitch" % self.session) + if not desktopSwitchEvt: + try: + raise WinError() + except Exception, e: + debug("error opening event: %s" % e) + raise + + while self.session == origSession: + windll.kernel32.WaitForSingleObject(desktopSwitchEvt, INFINITE) + debug("desktop switch, session %r" % self.session) + self.handleDesktopSwitch() + + windll.kernel32.CloseHandle(desktopSwitchEvt) + debug("desktop switch supervisor terminated, session %d" % origSession) + + def handleDesktopSwitch(self): + with self.launcherLock: + self.launcherStarted = False + + if (not self.isSessionLoggedOn and shouldStartOnLogonScreen()) or isUserRunningNVDA(self.session): + self.startLauncher() + else: + debug("not starting launcher") + + def SvcOtherEx(self, control, eventType, data): + if control == win32service.SERVICE_CONTROL_SESSIONCHANGE: + self.handleSessionChange(eventType, data[0]) + + def handleSessionChange(self, event, session): + if event == WTS_CONSOLE_CONNECT: + debug("connect %d" % session) + if session != self.session: + self.initSession(session) + elif event == WTS_SESSION_LOGON: + debug("logon %d" % session) + self.isSessionLoggedOn = True + execBg(self.desktopSwitchSupervisor) + elif event == WTS_SESSION_LOGOFF: + debug("logoff %d" % session) + self.isSessionLoggedOn = False + # We may be heading back to the logon screen. + if shouldStartOnLogonScreen(): + execBg(self.startLauncher) + elif event == WTS_SESSION_LOCK: + debug("lock %d" % session) + if session == self.session: + # We always start NVDA for the lock/switch user screen. + self.startLauncher() + + def startLauncher(self): + with self.launcherLock: + if self.launcherStarted: + return + + debug("attempt launcher start") + token = getSessionSystemToken(self.session) + try: + process = executeProcess(ur"WinSta0\Winlogon", token, slaveExec, u"service_NVDALauncher") + self.launcherStarted = True + debug("launcher started") + windll.kernel32.CloseHandle(process) + except Exception, e: + debug("error starting launcher: %s" % e) + + def SvcDoRun(self): + debug("service starting") + self.exitEvent = threading.Event() + self.initSession(windll.kernel32.WTSGetActiveConsoleSessionId()) + self.exitEvent.wait() + debug("service exiting") + + def SvcStop(self): + self.exitEvent.set() + +if __name__=='__main__': + if not getattr(sys, "frozen", None): + raise RuntimeError("Can only be run compiled with py2exe") + win32serviceutil.HandleCommandLine(NVDAService) Added: trunk/source/nvda_slave.pyw ============================================================================== --- (empty file) +++ trunk/source/nvda_slave.pyw Fri May 15 07:13:17 2009 @@ -0,0 +1,32 @@ +"""NVDA slave process +Performs miscellaneous tasks which need to be performed in a separate process. +""" + +import sys +if hasattr(sys, "frozen"): + # Error messages (which are only for debugging) should not cause the py2exe log message box to appear. + sys.stderr = sys.stdout + +def main(): + try: + action = sys.argv[1] + except IndexError: + sys.exit("No action") + args = sys.argv[2:] + + try: + if action == "service_NVDALauncher": + import nvda_service + nvda_service.nvdaLauncher() + elif action == "config_setStartOnLogonScreen": + enable = bool(int(args[0])) + import config + config._setStartOnLogonScreen(enable) + else: + raise ValueError("No such action") + + except Exception, e: + sys.exit(e) + +if __name__ == "__main__": + main() Modified: trunk/source/setup.py ============================================================================== --- trunk/source/setup.py (original) +++ trunk/source/setup.py Fri May 15 07:13:17 2009 @@ -98,13 +98,31 @@ 'Operating System :: Microsoft :: Windows', ], cmdclass={"py2exe": py2exe}, - windows = [{ - "script":"nvda.pyw", - "uac_info": ("asInvoker", False), - "icon_resources":[(1,"images/nvda.ico")], - "version":"0.0.0.0", - "product_version":version, - "copyright":copyright, + windows=[ + { + "script":"nvda.pyw", + "uac_info": ("asInvoker", False), + "icon_resources":[(1,"images/nvda.ico")], + "version":"0.0.0.0", + "product_version":version, + "copyright":copyright, + }, + { + "script": "nvda_slave.pyw", + "icon_resources": [(1,"images/nvda.ico")], + "version": "0.0.0.0", + "product_version": version, + "copyright": copyright, + }, + ], + service=[{ + "modules": ["nvda_service"], + "icon_resources": [(1, "images/nvda.ico")], + "version": "0.0.0.0", + "product_version": version, + "copyright": copyright, + "uac_info": ("requireAdministrator", False), + "cmdline_style": "pywin32", }], options = {"py2exe": { "bundle_files": 3, @@ -112,7 +130,6 @@ "packages": ["NVDAObjects","virtualBuffers"], "includes": getOptionalIncludes(), }}, - zipfile = None, data_files=[ (".",glob("*.dll")+glob("*.manifest")+["builtin.dic"]), ("documentation", ['../copying.txt', '../contributors.txt']), Added: trunk/source/shellapi.py ============================================================================== --- (empty file) +++ trunk/source/shellapi.py Fri May 15 07:13:17 2009 @@ -0,0 +1,38 @@ +#shellapi.py +#A part of NonVisual Desktop Access (NVDA) +#Copyright (C) 2006-2009 NVDA Contributors<http://www.nvda-project.org/> +#This file is covered by the GNU General Public License. +#See the file COPYING for more details. + +from ctypes import * +from ctypes.wintypes import * + +shell32 = windll.shell32 + +class SHELLEXECUTEINFOW(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("fMask", ULONG), + ("hwnd", HWND), + ("lpVerb", LPCWSTR), + ("lpFile", LPCWSTR), + ("lpParameters", LPCWSTR), + ("lpDirectory", LPCWSTR), + ("nShow", c_int), + ("hInstApp", HINSTANCE), + ("lpIDList", LPVOID), + ("lpClass", LPCWSTR), + ("hkeyClass", HKEY), + ("dwHotKey", DWORD), + ("hIconOrMonitor", HANDLE), + ("hProcess", HANDLE), + ) + def __init__(self, **kwargs): + super(SHELLEXECUTEINFOW, self).__init__(cbSize=sizeof(self), **kwargs) +SHELLEXECUTEINFO = SHELLEXECUTEINFOW + +SEE_MASK_NOCLOSEPROCESS = 0x00000040 + +def ShellExecuteEx(execInfo): + if not shell32.ShellExecuteExW(byref(execInfo)): + raise WinError() Modified: trunk/source/winKernel.py ============================================================================== --- trunk/source/winKernel.py (original) +++ trunk/source/winKernel.py Fri May 15 07:13:17 2009 @@ -101,3 +101,9 @@ res = kernel32.SetProcessShutdownParameters(level, flags) if res == 0: raise ctypes.WinError() + +def GetExitCodeProcess(process): + exitCode = ctypes.wintypes.DWORD() + if not kernel32.GetExitCodeProcess(process, ctypes.byref(exitCode)): + raise ctypes.WinError() + return exitCode.value Modified: trunk/source/winUser.py ============================================================================== --- trunk/source/winUser.py (original) +++ trunk/source/winUser.py Fri May 15 07:13:17 2009 @@ -237,6 +237,10 @@ EVENT_CONSOLE_START_APPLICATION=0x4006 EVENT_CONSOLE_END_APPLICATION=0x4007 +# ShowWindow() commands +SW_HIDE = 0 +SW_SHOWNORMAL = 1 + def setSystemScreenReaderFlag(val): user32.SystemParametersInfoW(SPI_SETSCREENREADER,val,0,SPIF_SENDCHANGE) Modified: trunk/user_docs/en/whats new.txt ============================================================================== --- trunk/user_docs/en/whats new.txt (original) +++ trunk/user_docs/en/whats new.txt Fri May 15 07:13:17 2009 @@ -24,8 +24,11 @@ *New: In Windows Vista, if the user moves to the secure desktop (either because a UAC control dialog appeared, or because control+alt+delete was pressed), NVDA will announce the fact that the user is now on the secure desktop. This desktop is still not accessible to NVDA, though the user at least is notified as to what happened. *Fix: better support for Dos consoles. specifically: NVDA can now read the content of particular consoles it always used to think were blank. Pressing control+break no longer terminates NVDA. *New: NVDA can announce text under the mouse within dos console windows. +* new: NVDA can be configured to start automatically after you log on to Windows. The option is in the General Settings dialog. +* new: NVDA can read secure Windows screens such as the Windows logon, control+alt+delete and UAC screens in Windows XP and above. Reading of the Windows logon screen can be configured from the General Settings dialog. (#97) +* fix: On Windows Vista, the NVDA installer now starts NVDA with normal user privileges when requested to run NVDA on the finish screen. - Changes since 0.6 P2: +Changes since 0.6 P2: * new: As Microsoft Excel's formula bar is inaccessible to NVDA, provide an NVDA specific dialog box for editing when the user presses f2 on a cell. * change: If the audio output device is set to use the Windows default device (Microsoft Sound Mapper), NVDA will now switch to the new default device for eSpeak and tones when the default device changes. For example, NVDA will switch to a USB audio device if it automatically becomes the default device when it is connected. * fix: The last chunk of audio is no longer cut off when using NVDA with eSpeak on a remote desktop server. _______________________________________________ Nvda-dev mailing list Nvda-dev@xxxxxxxxxxxxxxxxxx http://lists.nvaccess.org/listinfo/nvda-dev blind_html To unsubscribe, please send a blank email to blind_html-request@xxxxxxxxxxxxx with unsubscribe in the subject line. To access the archives, please visit: //www.freelists.org/archive/blind_html Thanks