1
1
#winConsoleHandler.py
2
2
#A part of NonVisual Desktop Access (NVDA)
3
#Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/>
4
3
#This file is covered by the GNU General Public License.
5
4
#See the file COPYING for more details.
5
#Copyright (C) 2009-2010 Michael Curran <mick@kulgan.net>, James Teh <jamie@jantrid.net>
13
11
import eventHandler
14
12
from logHandler import log
19
from NVDAObjects import NVDAObjectTextInfo
17
#: How often to check whether the console is dead (in ms).
18
CHECK_DEAD_INTERVAL = 100
22
20
consoleObject=None #:The console window that is currently in the foreground.
23
21
consoleWinEventHookHandles=[] #:a list of currently registered console win events.
24
keepAliveMonitorThread=False #:While true, the monitor thread should continue to run
26
22
consoleOutputHandle=None
27
lastConsoleWinEvent=None
28
lastConsoleVisibleLines=[] #:The most recent lines in the console (to work out a diff for announcing updates)
30
25
@wincon.PHANDLER_ROUTINE
31
26
def _consoleCtrlHandler(event):
36
31
def connectConsole(obj):
37
global consoleObject, consoleOutputHandle, lastConsoleWinEvent, keepAliveMonitorThread, monitorThread, lastConsoleVisibleLines
32
global consoleObject, consoleOutputHandle, checkDeadTimer
38
33
#Get the process ID of the console this NVDAObject is fore
39
34
processID,threadID=winUser.getWindowThreadProcessID(obj.windowHandle)
40
35
#Attach NVDA to this console so we can access its text etc
46
41
wincon.SetConsoleCtrlHandler(_consoleCtrlHandler,True)
47
42
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)
48
lastConsoleVisibleLines=getConsoleVisibleLines()
49
43
#Register this callback with all the win events we need, storing the given handles for removal later
50
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]:
44
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):
51
45
handle=winUser.setWinEventHook(eventID,eventID,0,consoleWinEventHook,0,0,0)
53
47
raise OSError("could not register eventID %s"%eventID)
54
48
consoleWinEventHookHandles.append(handle)
55
#Setup the monitoring thread which will watch a variable, and speak new text at the appropriate time
56
#Each event doesn't individually speak its own text since speaking text is quite intensive due to the diff algorithms
57
keepAliveMonitorThread=True
58
lastConsoleWinEvent=None
60
monitorThread=threading.Thread(target=monitorThreadFunc)
50
checkDeadTimer=wx.PyTimer(_checkDead)
51
checkDeadTimer.Start(CHECK_DEAD_INTERVAL)
64
54
def disconnectConsole():
65
global consoleObject, consoleOutputHandle, consoleWinEventHookHandles, keepAliveMonitorThread
55
global consoleObject, consoleOutputHandle, consoleWinEventHookHandles, checkDeadTimer
66
56
if not consoleObject:
67
57
log.debugWarning("console was not connected")
69
61
#Unregister any win events we are using
70
62
for handle in consoleWinEventHookHandles:
71
63
winUser.unhookWinEvent(handle)
72
64
consoleEventHookHandles=[]
73
#Get ready to stop monitoring - give it a little time to finish
74
keepAliveMonitorThread=False
65
consoleObject.stopMonitoring()
76
66
winKernel.closeHandle(consoleOutputHandle)
77
67
consoleOutputHandle=None
90
80
def isConsoleDead():
91
#Every console should have at least one process associated with it
92
#This console should have two if NVDA is also connected
93
#if there is only one (it must be NVDA) so we free NVDA from it so it can close
81
# Every console should have at least one process associated with it.
82
# This console should have two if NVDA is also connected.
83
# If there is only one, it must be NVDA alone, so it is dead.
94
84
processList=wincon.GetConsoleProcessList(2)
95
if len(processList)<2:
85
return len(processList) < 2
90
# We must disconnect NVDA from this console so it can close.
100
95
def getConsoleVisibleLines():
101
96
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
109
104
@winUser.WINEVENTPROC
110
105
def consoleWinEventHook(handle,eventID,window,objectID,childID,threadID,timestamp):
111
global lastConsoleWinEvent
112
106
#We don't want to do anything with the event if the event is not for the window this console is in
113
107
if window!=consoleObject.windowHandle:
115
if eventID==winUser.EVENT_CONSOLE_CARET:
109
if eventID==winUser.EVENT_CONSOLE_CARET and not eventHandler.isPendingEvents("caret",consoleObject):
116
110
eventHandler.queueEvent("caret",consoleObject)
117
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
118
#Notify the monitor thread that an event has occurred
119
lastConsoleWinEvent=eventID
111
# It is safe to call this event from this callback.
112
# This avoids an extra core cycle.
113
consoleObject.event_textChange()
120
114
if eventID==winUser.EVENT_CONSOLE_UPDATE_SIMPLE:
121
115
x=winUser.LOWORD(objectID)
122
116
y=winUser.HIWORD(objectID)
117
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
123
118
if x<consoleScreenBufferInfo.dwCursorPosition.x and (y==consoleScreenBufferInfo.dwCursorPosition.y or y==consoleScreenBufferInfo.dwCursorPosition.y+1):
124
queueHandler.queueFunction(queueHandler.eventQueue,speech.speakTypedCharacters,unichr(winUser.LOWORD(childID)))
126
def monitorThreadFunc():
127
global lastConsoleWinEvent, lastConsoleVisibleLines, keepAliveMonitorThread
130
# We want the first event to be handled immediately.
133
#Keep the thread alive while keepMonitoring is true - disconnectConsole will make it false if the focus moves away
134
while keepAliveMonitorThread:
135
#If there has been a console event lately, remember it and reset the notification to None
136
if lastConsoleWinEvent:
137
consoleEvent=lastConsoleWinEvent
138
lastConsoleWinEvent=None
141
if consoleEvent and timeSinceLast==5:
142
# There is a new event and there has been enough time since the last one was handled, so handle this.
144
if globalVars.reportDynamicContentChanges:
145
newLines=getConsoleVisibleLines()
146
outLines=calculateNewText(newLines,lastConsoleVisibleLines)
147
if not (len(outLines) == 1 and len(outLines[0]) <= 1):
148
for line in outLines:
149
queueHandler.queueFunction(queueHandler.eventQueue, speech.speakText, line)
150
lastConsoleVisibleLines=newLines
152
#Every 10 times we also make sure the console isn't dead, if so we need to stop the thread ourselves
153
if checkDead_timer>=10:
156
keepAliveMonitorThread=False
157
queueHandler.queueFunction(queueHandler.eventQueue,disconnectConsole)
159
#Each round of the while loop we wait 10 milliseconds
162
log.error("console monitorThread", exc_info=True)
119
eventHandler.queueEvent("typedCharacter",consoleObject,ch=unichr(winUser.LOWORD(childID)))
164
121
def initialize():
168
125
if consoleObject:
169
126
disconnectConsole()
171
def calculateNewText(newLines,oldLines):
174
diffLines=[x for x in difflib.ndiff(oldLines,newLines) if (x[0] in ['+','-'])]
175
for lineNum in xrange(len(diffLines)):
176
if diffLines[lineNum][0]=="+":
177
text=diffLines[lineNum][2:]
178
if not text.isspace() and (lineNum>0) and (diffLines[lineNum-1][0]=="-"):
181
for pos in xrange(len(text)):
182
if text[pos]!=diffLines[lineNum-1][2:][pos]:
185
for pos in xrange(len(text)-1,0,-1):
186
if text[pos]!=diffLines[lineNum-1][2:][pos]:
190
# Less than 15 characters have changed, so only speak the changed chunk.
193
if len(text)>0 and not text.isspace():
194
outLines.append(text)
197
class WinConsoleTextInfo(NVDAObjectTextInfo):
128
class WinConsoleTextInfo(textInfos.offsets.OffsetsTextInfo):
199
130
def _offsetFromConsoleCoord(self,x,y):
200
131
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
267
198
def _getLineNumFromOffset(self,offset):
268
199
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
269
200
x,y=self._consoleCoordFromOffset(offset)
270
return y-consoleScreenBufferInfo.srWindow.top
201
return y-consoleScreenBufferInfo.srWindow.Top
272
203
def _getStoryLength(self):
273
204
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
274
205
return consoleScreenBufferInfo.dwSize.x*((consoleScreenBufferInfo.srWindow.Bottom+1)-consoleScreenBufferInfo.srWindow.Top)
276
def copyToClipboard(self):
277
blocks = (block.rstrip() for block in self.getTextInChunks(textInfos.UNIT_LINE))
278
return api.copyToClip("\r\n".join(blocks))
207
def _get_clipboardText(self):
208
return "\r\n".join(block.rstrip() for block in self.getTextInChunks(textInfos.UNIT_LINE))