#winConsoleHandler.py #A part of NonVisual Desktop Access (NVDA) #This file is covered by the GNU General Public License. #See the file COPYING for more details. #Copyright (C) 2009-2010 Michael Curran , James Teh import wx import winUser import winKernel import wincon from colors import RGB import eventHandler from logHandler import log import speech import textInfos import api import config #: How often to check whether the console is dead (in ms). CHECK_DEAD_INTERVAL = 100 consoleObject=None #:The console window that is currently in the foreground. consoleWinEventHookHandles=[] #:a list of currently registered console win events. consoleOutputHandle=None checkDeadTimer=None CONSOLE_COLORS_TO_RGB=( #http://en.wikipedia.org/wiki/Color_Graphics_Adapter RGB(0x00,0x00,0x00), #black RGB(0x00,0x00,0xAA), #blue RGB(0x00,0xAA,0x00), #green RGB(0x00,0xAA,0xAA), #cyan RGB(0xAA,0x00,0x00), #red RGB(0xAA,0x00,0xAA), #magenta RGB(0xAA,0x55, 0x00), #brown RGB(0xAA,0xAA,0xAA), #white RGB(0x55,0x55,0x55), #gray RGB(0x55,0x55,0xFF), #light blue RGB(0x55,0xFF,0x55), #light green RGB(0x55,0xFF,0xFF), #light cyan RGB(0xFF,0x55,0x55), #light red RGB(0xFF,0x55,0xFF), #light magenta RGB(0xFF,0xFF,0x55), #yellow RGB(0xFF,0xFF,0xFF), #white (high intensity) ) COMMON_LVB_UNDERSCORE=0x8000 @wincon.PHANDLER_ROUTINE def _consoleCtrlHandler(event): if event in (wincon.CTRL_C_EVENT,wincon.CTRL_BREAK_EVENT): return True return False def connectConsole(obj): global consoleObject, consoleOutputHandle, checkDeadTimer #Get the process ID of the console this NVDAObject is fore processID,threadID=winUser.getWindowThreadProcessID(obj.windowHandle) #Attach NVDA to this console so we can access its text etc try: wincon.AttachConsole(processID) except WindowsError as e: log.debugWarning("Could not attach console: %s"%e) return False wincon.SetConsoleCtrlHandler(_consoleCtrlHandler,True) consoleOutputHandle=winKernel.CreateFile(u"CONOUT$",winKernel.GENERIC_READ|winKernel.GENERIC_WRITE,winKernel.FILE_SHARE_READ|winKernel.FILE_SHARE_WRITE,None,winKernel.OPEN_EXISTING,0,None) #Register this callback with all the win events we need, storing the given handles for removal later for eventID in (winUser.EVENT_CONSOLE_CARET,winUser.EVENT_CONSOLE_UPDATE_REGION,winUser.EVENT_CONSOLE_UPDATE_SIMPLE,winUser.EVENT_CONSOLE_UPDATE_SCROLL,winUser.EVENT_CONSOLE_LAYOUT): handle=winUser.setWinEventHook(eventID,eventID,0,consoleWinEventHook,0,0,0) if not handle: raise OSError("could not register eventID %s"%eventID) consoleWinEventHookHandles.append(handle) consoleObject=obj checkDeadTimer=wx.PyTimer(_checkDead) checkDeadTimer.Start(CHECK_DEAD_INTERVAL) return True def disconnectConsole(): global consoleObject, consoleOutputHandle, consoleWinEventHookHandles, checkDeadTimer if not consoleObject: log.debugWarning("console was not connected") return False checkDeadTimer.Stop() checkDeadTimer=None #Unregister any win events we are using for handle in consoleWinEventHookHandles: winUser.unhookWinEvent(handle) consoleEventHookHandles=[] consoleObject.stopMonitoring() winKernel.closeHandle(consoleOutputHandle) consoleOutputHandle=None consoleObject=None try: wincon.SetConsoleCtrlHandler(_consoleCtrlHandler,False) except WindowsError: pass #Try freeing NVDA from this console try: wincon.FreeConsole() except WindowsError: pass return True def isConsoleDead(): # Every console should have at least one process associated with it. # This console should have two if NVDA is also connected. # If there is only one, it must be NVDA alone, so it is dead. processList=wincon.GetConsoleProcessList(2) return len(processList) < 2 def _checkDead(): try: if isConsoleDead(): # We must disconnect NVDA from this console so it can close. disconnectConsole() except: log.exception() def getConsoleVisibleLines(): consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle) topLine=consoleScreenBufferInfo.srWindow.Top lineCount=(consoleScreenBufferInfo.srWindow.Bottom-topLine)+1 lineLength=consoleScreenBufferInfo.dwSize.x text=wincon.ReadConsoleOutputCharacter(consoleOutputHandle,lineCount*lineLength,0,topLine) newLines=[text[x:x+lineLength] for x in xrange(0,len(text),lineLength)] return newLines @winUser.WINEVENTPROC def consoleWinEventHook(handle,eventID,window,objectID,childID,threadID,timestamp): #We don't want to do anything with the event if the event is not for the window this console is in if window!=consoleObject.windowHandle: return if eventID==winUser.EVENT_CONSOLE_CARET and not eventHandler.isPendingEvents("caret",consoleObject): eventHandler.queueEvent("caret",consoleObject) # It is safe to call this event from this callback. # This avoids an extra core cycle. consoleObject.event_textChange() if eventID==winUser.EVENT_CONSOLE_UPDATE_SIMPLE: x=winUser.LOWORD(objectID) y=winUser.HIWORD(objectID) consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle) if x0: #offsets span multiple lines rect.Left=0 rect.Right=self.consoleScreenBufferInfo.dwSize.x-1 length=self.consoleScreenBufferInfo.dwSize.x*(bottom-top+1) else: length=self._endOffset-self._startOffset buf=wincon.ReadConsoleOutput(consoleOutputHandle, length, rect) if bottom-top>0: buf=buf[left:len(buf)-(self.consoleScreenBufferInfo.dwSize.x-right)+1] lastAttr=None lastText=[] boundEnd=self._startOffset for i,c in enumerate(buf): if self._startOffset+i==boundEnd: field,(boundStart,boundEnd)=self._getFormatFieldAndOffsets(boundEnd,formatConfig) if lastText: commands.append("".join(lastText)) lastText=[] commands.append(textInfos.FieldCommand("formatChange",field)) if not c.Attributes==lastAttr: formatField=textInfos.FormatField() if formatConfig['reportColor']: formatField["color"]=CONSOLE_COLORS_TO_RGB[c.Attributes&0x0f] formatField["background-color"]=CONSOLE_COLORS_TO_RGB[(c.Attributes>>4)&0x0f] if formatConfig['reportFontAttributes'] and c.Attributes&COMMON_LVB_UNDERSCORE: formatField['underline']=True if formatField: if lastText: commands.append("".join(lastText)) lastText=[] command=textInfos.FieldCommand("formatChange", formatField) commands.append(command) lastAttr=c.Attributes lastText.append(c.Char) commands.append("".join(lastText)) return commands def _getTextRange(self,start,end): startX,startY=self._consoleCoordFromOffset(start) return wincon.ReadConsoleOutputCharacter(consoleOutputHandle,end-start,startX,startY) def _getLineOffsets(self,offset): consoleScreenBufferInfo=self.consoleScreenBufferInfo x,y=self._consoleCoordFromOffset(offset) x=0 start=self._offsetFromConsoleCoord(x,y) end=start+consoleScreenBufferInfo.dwSize.x return start,end def _getLineNumFromOffset(self,offset): consoleScreenBufferInfo=self.consoleScreenBufferInfo x,y=self._consoleCoordFromOffset(offset) return y-consoleScreenBufferInfo.srWindow.Top def _getStoryLength(self): consoleScreenBufferInfo=self.consoleScreenBufferInfo return consoleScreenBufferInfo.dwSize.x*((consoleScreenBufferInfo.srWindow.Bottom+1)-consoleScreenBufferInfo.srWindow.Top) def _get_clipboardText(self): return "\r\n".join(block.rstrip() for block in self.getTextInChunks(textInfos.UNIT_LINE))