~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/winConsoleHandler.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
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>
6
6
 
7
 
import time
8
 
import threading
9
 
import difflib
 
7
import wx
10
8
import winUser
11
9
import winKernel
12
10
import wincon
13
11
import eventHandler
14
12
from logHandler import log
15
 
import globalVars
16
13
import speech
17
 
import queueHandler
18
14
import textInfos
19
 
from NVDAObjects import NVDAObjectTextInfo
20
15
import api
21
16
 
 
17
#: How often to check whether the console is dead (in ms).
 
18
CHECK_DEAD_INTERVAL = 100
 
19
 
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
25
 
monitorThread=None
26
22
consoleOutputHandle=None
27
 
lastConsoleWinEvent=None
28
 
lastConsoleVisibleLines=[] #:The most recent lines in the console (to work out a diff for announcing updates)
 
23
checkDeadTimer=None
29
24
 
30
25
@wincon.PHANDLER_ROUTINE
31
26
def _consoleCtrlHandler(event):
34
29
        return False
35
30
 
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
45
40
                return False
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)
52
46
                if not handle:
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
59
49
        consoleObject=obj
60
 
        monitorThread=threading.Thread(target=monitorThreadFunc)
61
 
        monitorThread.start()
 
50
        checkDeadTimer=wx.PyTimer(_checkDead)
 
51
        checkDeadTimer.Start(CHECK_DEAD_INTERVAL)
62
52
        return True
63
53
 
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")
68
58
                return False
 
59
        checkDeadTimer.Stop()
 
60
        checkDeadTimer=None
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
75
 
        monitorThread.join()
 
65
        consoleObject.stopMonitoring()
76
66
        winKernel.closeHandle(consoleOutputHandle)
77
67
        consoleOutputHandle=None
78
68
        consoleObject=None
88
78
        return True
89
79
 
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:
96
 
                return True
97
 
        else:
98
 
                return False
 
85
        return len(processList) < 2
 
86
 
 
87
def _checkDead():
 
88
        try:
 
89
                if isConsoleDead():
 
90
                        # We must disconnect NVDA from this console so it can close.
 
91
                        disconnectConsole()
 
92
        except:
 
93
                log.exception()
99
94
 
100
95
def getConsoleVisibleLines():
101
96
        consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
108
103
 
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:
114
108
                return
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)))
125
 
 
126
 
def monitorThreadFunc():
127
 
        global lastConsoleWinEvent, lastConsoleVisibleLines, keepAliveMonitorThread
128
 
        try:
129
 
                consoleEvent=None
130
 
                # We want the first event to be handled immediately.
131
 
                timeSinceLast=5
132
 
                checkDead_timer=0
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
139
 
                        if timeSinceLast<5:
140
 
                                timeSinceLast+=1
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.
143
 
                                timeSinceLast=0
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
151
 
                                consoleEvent=None
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:
154
 
                                checkDead_timer=0
155
 
                                if isConsoleDead():
156
 
                                        keepAliveMonitorThread=False
157
 
                                        queueHandler.queueFunction(queueHandler.eventQueue,disconnectConsole)
158
 
                        checkDead_timer+=1
159
 
                        #Each round of the while loop we wait 10 milliseconds
160
 
                        time.sleep(0.01)
161
 
        except:
162
 
                log.error("console monitorThread", exc_info=True)
 
119
                        eventHandler.queueEvent("typedCharacter",consoleObject,ch=unichr(winUser.LOWORD(childID)))
163
120
 
164
121
def initialize():
165
122
        pass
168
125
        if consoleObject:
169
126
                disconnectConsole()
170
127
 
171
 
def calculateNewText(newLines,oldLines):
172
 
        foundChange=False
173
 
        outLines=[]
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]=="-"):
179
 
                                start=0
180
 
                                end=len(text)
181
 
                                for pos in xrange(len(text)):
182
 
                                        if text[pos]!=diffLines[lineNum-1][2:][pos]:
183
 
                                                start=pos
184
 
                                                break
185
 
                                for pos in xrange(len(text)-1,0,-1):
186
 
                                        if text[pos]!=diffLines[lineNum-1][2:][pos]:
187
 
                                                end=pos+1
188
 
                                                break
189
 
                                if end - start < 15:
190
 
                                        # Less than 15 characters have changed, so only speak the changed chunk.
191
 
                                        text=text[start:end]
192
 
                                        foundChange=True
193
 
                        if len(text)>0 and not text.isspace():
194
 
                                outLines.append(text)
195
 
        return outLines
196
 
 
197
 
class WinConsoleTextInfo(NVDAObjectTextInfo):
 
128
class WinConsoleTextInfo(textInfos.offsets.OffsetsTextInfo):
198
129
 
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
271
202
 
272
203
        def _getStoryLength(self):
273
204
                consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
274
205
                return consoleScreenBufferInfo.dwSize.x*((consoleScreenBufferInfo.srWindow.Bottom+1)-consoleScreenBufferInfo.srWindow.Top)
275
206
 
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))