winConsoleHandler.py
10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#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 <mick@kulgan.net>, James Teh <jamie@jantrid.net>
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 x<consoleScreenBufferInfo.dwCursorPosition.x and (y==consoleScreenBufferInfo.dwCursorPosition.y or y==consoleScreenBufferInfo.dwCursorPosition.y+1):
eventHandler.queueEvent("typedCharacter",consoleObject,ch=unichr(winUser.LOWORD(childID)))
def initialize():
pass
def terminate():
if consoleObject:
disconnectConsole()
class WinConsoleTextInfo(textInfos.offsets.OffsetsTextInfo):
_cache_consoleScreenBufferInfo=True
def _get_consoleScreenBufferInfo(self):
return wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
def _offsetFromConsoleCoord(self,x,y):
consoleScreenBufferInfo=self.consoleScreenBufferInfo
val=y-consoleScreenBufferInfo.srWindow.Top
val*=consoleScreenBufferInfo.dwSize.x
val+=x
return val
def _consoleCoordFromOffset(self,offset):
consoleScreenBufferInfo=self.consoleScreenBufferInfo
x=offset%consoleScreenBufferInfo.dwSize.x
y=offset-x
y/=consoleScreenBufferInfo.dwSize.x
y+=consoleScreenBufferInfo.srWindow.Top
return x,y
def _getOffsetFromPoint(self,x,y):
consoleScreenBufferInfo=self.consoleScreenBufferInfo
screenLeft,screenTop,screenWidth,screenHeight=self.obj.location
relativeX=x-screenLeft
relativeY=y-screenTop
lineLength=(consoleScreenBufferInfo.srWindow.Right+1)-consoleScreenBufferInfo.srWindow.Left
numLines=(consoleScreenBufferInfo.srWindow.Bottom+1)-consoleScreenBufferInfo.srWindow.Top
characterWidth=screenWidth/lineLength
characterHeight=screenHeight/numLines
characterX=(relativeX/characterWidth)+consoleScreenBufferInfo.srWindow.Left
characterY=(relativeY/characterHeight)+consoleScreenBufferInfo.srWindow.Top
return self._offsetFromConsoleCoord(characterX,characterY)
def _getPointFromOffset(self,offset):
consoleScreenBufferInfo=self.consoleScreenBufferInfo
characterX,characterY=self._consoleCoordFromOffset(offset)
screenLeft,screenTop,screenWidth,screenHeight=self.obj.location
lineLength=(consoleScreenBufferInfo.srWindow.Right+1)-consoleScreenBufferInfo.srWindow.Left
numLines=(consoleScreenBufferInfo.srWindow.Bottom+1)-consoleScreenBufferInfo.srWindow.Top
characterWidth=screenWidth/lineLength
characterHeight=screenHeight/numLines
relativeX=(characterX-consoleScreenBufferInfo.srWindow.Left)*characterWidth
relativeY=(characterY-consoleScreenBufferInfo.srWindow.Top)*characterHeight
x=relativeX+screenLeft
y=relativeY+screenTop
return textInfos.Point(x,y)
def _getCaretOffset(self):
consoleScreenBufferInfo=self.consoleScreenBufferInfo
return self._offsetFromConsoleCoord(consoleScreenBufferInfo.dwCursorPosition.x,consoleScreenBufferInfo.dwCursorPosition.y)
def _getSelectionOffsets(self):
selInfo=wincon.GetConsoleSelectionInfo()
if selInfo.dwFlags&wincon.CONSOLE_SELECTION_NOT_EMPTY:
start=self._offsetFromConsoleCoord(selInfo.srSelection.Left,selInfo.srSelection.Top)
end=self._offsetFromConsoleCoord(selInfo.srSelection.Right,selInfo.srSelection.Bottom)
else:
start=end=self._getCaretOffset()
return start,end
def getTextWithFields(self,formatConfig=None):
commands=[]
if self.isCollapsed:
return commands
if not formatConfig:
formatConfig=config.conf["documentFormatting"]
left,top=self._consoleCoordFromOffset(self._startOffset)
right,bottom=self._consoleCoordFromOffset(self._endOffset-1)
rect=wincon.SMALL_RECT(left,top,right,bottom)
if bottom-top>0: #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))