miranda32.py
9.24 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
#appModules/miranda32.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2012 NVDA Contributors
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import ui
import config
from ctypes import *
from ctypes.wintypes import *
import winKernel
from NVDAObjects.IAccessible import IAccessible, ContentGenericClient
from NVDAObjects.behaviors import Dialog
import appModuleHandler
import speech
import braille
import controlTypes
from scriptHandler import isScriptWaiting
import api
import mouseHandler
import oleacc
from keyboardHandler import KeyboardInputGesture
import watchdog
#contact list window messages
CLM_FIRST=0x1000 #this is the same as LVM_FIRST
CLM_LAST=0x1100
#messages, compare with equivalent TVM_s in the MSDN
CLM_ENSUREVISIBLE=CLM_FIRST+6 #wParam=hItem, lParam=partialOk
CLE_TOGGLE=-1
CLE_COLLAPSE=0
CLE_EXPAND=1
CLE_INVALID=0xFFFF
CLM_EXPAND=CLM_FIRST+7 #wParam=hItem, lParam=CLE_
CLM_FINDCONTACT=CLM_FIRST+8 #wParam=hContact, returns an hItem
CLM_FINDGROUP=CLM_FIRST+9 #wParam=hGroup, returns an hItem
CLM_GETBKCOLOR=CLM_FIRST+10 #returns a COLORREF
CLM_GETCHECKMARK=CLM_FIRST+11 #wParam=hItem, returns 1 or 0
CLM_GETCOUNT=CLM_FIRST+12 #returns the total number of items
CLM_GETEXPAND=CLM_FIRST+14 #wParam=hItem, returns a CLE_, CLE_INVALID if not a group
CLM_GETEXTRACOLUMNS=CLM_FIRST+15 #returns number of extra columns
CLM_GETEXTRAIMAGE=CLM_FIRST+16 #wParam=hItem, lParam=MAKELPARAM(iColumn (0 based),0), returns iImage or 0xFF
CLM_GETEXTRAIMAGELIST=CLM_FIRST+17 #returns HIMAGELIST
CLM_GETFONT=CLM_FIRST+18 #wParam=fontId, see clm_setfont. returns hFont.
CLM_GETINDENT=CLM_FIRST+19 #wParam=new group indent
CLM_GETISEARCHSTRING=CLM_FIRST+20 #lParam=(char*)pszStr, max 120 bytes, returns number of chars in string
MAXITEMTEXTLEN=120
CLM_GETITEMTEXT=CLM_FIRST+21 #wParam=hItem, lParam=(char*)pszStr, max 120 bytes
CLM_GETSELECTION=CLM_FIRST+23 #returns hItem
CLM_SELECTITEM=CLM_FIRST+26 #wParam=hItem
CLM_GETHIDEOFFLINEROOT=CLM_FIRST+40 #returns TRUE/FALSE
CLM_GETEXSTYLE=CLM_FIRST+44 #returns CLS_EX_ flags
CLM_GETLEFTMARGIN=CLM_FIRST+46 #returns count of pixels
CLCIT_INVALID=-1
CLCIT_GROUP=0
CLCIT_CONTACT=1
CLCIT_DIVIDER=2
CLCIT_INFO=3
CLM_GETITEMTYPE=CLM_FIRST+49 #wParam=hItem, returns a CLCIT_
CLGN_ROOT=0
CLGN_CHILD=1
CLGN_PARENT=2
CLGN_NEXT=3
CLGN_PREVIOUS=4
CLGN_NEXTCONTACT=5
CLGN_PREVIOUSCONTACT=6
CLGN_NEXTGROUP=7
CLGN_PREVIOUSGROUP=8
CLM_GETNEXTITEM=CLM_FIRST+50 #wParam=flag, lParam=hItem, returns an hItem
CLM_GETTEXTCOLOR=CLM_FIRST+51 #wParam=FONTID_, returns COLORREF
MAXSTATUSMSGLEN=256
CLM_GETSTATUSMSG=CLM_FIRST+105
#other constants
ANSILOGS=(1001,1006)
MESSAGEVIEWERS=(1001,1005,5005)
class AppModule(appModuleHandler.AppModule):
lastTextLengths={}
lastMessages=[]
# Must not be > 9.
MessageHistoryLength=3
def chooseNVDAObjectOverlayClasses(self, obj, clsList):
if obj.role == controlTypes.ROLE_WINDOW:
return
windowClass = obj.windowClassName
if windowClass == "CListControl":
try:
clsList.remove(ContentGenericClient)
except ValueError:
pass
clsList.insert(0, mirandaIMContactList)
elif windowClass in ("MButtonClass", "TSButtonClass", "CLCButtonClass"):
clsList.insert(0, mirandaIMButton)
elif windowClass == "Hyperlink":
clsList.insert(0, mirandaIMHyperlink)
elif isinstance(obj, IAccessible) and obj.IAccessibleRole == oleacc.ROLE_SYSTEM_PROPERTYPAGE:
clsList.insert(0, MPropertyPage)
elif isinstance(obj, IAccessible) and obj.IAccessibleRole == oleacc.ROLE_SYSTEM_SCROLLBAR and obj.windowControlID in MESSAGEVIEWERS:
clsList.insert(0, MirandaMessageViewerScrollbar)
elif windowClass == "ListBox" and obj.windowControlID == 0:
clsList.insert(0, DuplicateFocusListBox)
def event_NVDAObject_init(self,obj):
if obj.windowClassName=="ColourPicker":
obj.role=controlTypes.ROLE_COLORCHOOSER
elif (obj.windowControlID in ANSILOGS) and (obj.windowClassName=="RichEdit20A"):
obj._isWindowUnicode=False
def script_readMessage(self,gesture):
num=int(gesture.mainKeyName[-1])
if len(self.lastMessages)>num-1:
ui.message(self.lastMessages[num-1])
else:
# Translators: This is presented to inform the user that no instant message has been received.
ui.message(_("No message yet"))
# Translators: The description of an NVDA command to view one of the recent messages.
script_readMessage.__doc__=_("Displays one of the recent messages")
def __init__(self, *args, **kwargs):
super(AppModule, self).__init__(*args, **kwargs)
for n in xrange(1, self.MessageHistoryLength + 1):
self.bindGesture("kb:NVDA+control+%s" % n, "readMessage")
class mirandaIMContactList(IAccessible):
def _get_name(self):
hItem=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSELECTION,0,0)
internalBuf=winKernel.virtualAllocEx(self.processHandle,None,MAXITEMTEXTLEN,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
try:
watchdog.cancellableSendMessage(self.windowHandle,CLM_GETITEMTEXT,hItem,internalBuf)
buf=create_unicode_buffer(MAXITEMTEXTLEN)
winKernel.readProcessMemory(self.processHandle,internalBuf,buf,MAXITEMTEXTLEN,None)
text=buf.value
statusMsgPtr=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSTATUSMSG,hItem,0)
if statusMsgPtr>0:
buf2=create_unicode_buffer(MAXSTATUSMSGLEN)
winKernel.readProcessMemory(self.processHandle,statusMsgPtr,buf2,MAXSTATUSMSGLEN,None)
text="%s %s"%(text,buf2.value)
finally:
winKernel.virtualFreeEx(self.processHandle,internalBuf,0,winKernel.MEM_RELEASE)
return text
def _get_role(self):
hItem=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSELECTION,0,0)
iType=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETITEMTYPE,hItem,0)
if iType==CLCIT_DIVIDER or iType==CLCIT_INVALID: #some clists treat invalid as divider
return controlTypes.ROLE_SEPARATOR
else:
return controlTypes.ROLE_TREEVIEWITEM
def _get_states(self):
newStates=super(mirandaIMContactList,self)._get_states()
hItem=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETSELECTION,0,0)
state=watchdog.cancellableSendMessage(self.windowHandle,CLM_GETEXPAND,hItem,0)
if state==CLE_EXPAND:
newStates.add(controlTypes.STATE_EXPANDED)
elif state==CLE_COLLAPSE:
newStates.add(controlTypes.STATE_COLLAPSED)
return newStates
def script_changeItem(self,gesture):
gesture.send()
if not isScriptWaiting():
api.processPendingEvents()
speech.speakObject(self,reason=controlTypes.REASON_FOCUS)
braille.handler.handleGainFocus(self)
__changeItemGestures = (
"kb:downArrow",
"kb:upArrow",
"kb:leftArrow",
"kb:rightArrow",
"kb:home",
"kb:end",
"kb:pageUp",
"kb:pageDown",
)
def initOverlayClass(self):
for gesture in self.__changeItemGestures:
self.bindGesture(gesture, "changeItem")
class mirandaIMButton(IAccessible):
def _get_name(self):
api.moveMouseToNVDAObject(self)
return super(mirandaIMButton,self)._get_name()
def _get_role(self):
return controlTypes.ROLE_BUTTON
def getActionName(self):
if controlTypes.STATE_FOCUSED not in self.states:
return
return "Click"
def doAction(self):
if controlTypes.STATE_FOCUSED not in self.states:
return
KeyboardInputGesture.fromName("space").send()
def script_doDefaultAction(self,gesture):
self.doAction()
def initOverlayClass(self):
self.bindGesture("kb:enter", "doDefaultAction")
class mirandaIMHyperlink(mirandaIMButton):
def _get_role(self):
return controlTypes.ROLE_LINK
class MPropertyPage(Dialog,IAccessible):
def _get_name(self):
name=super(MPropertyPage,self)._get_name()
if not name:
try:
tc=self.parent.next.firstChild
except AttributeError:
tc=None
if tc and tc.role==controlTypes.ROLE_TABCONTROL:
children=tc.children
for index in xrange(len(children)):
if (children[index].role==controlTypes.ROLE_TAB) and (controlTypes.STATE_SELECTED in children[index].states):
name=children[index].name
break
return name
class MirandaMessageViewerScrollbar(IAccessible):
def event_valueChange(self):
curTextLength=len(self.windowText)
if self.windowHandle not in self.appModule.lastTextLengths:
self.appModule.lastTextLengths[self.windowHandle]=curTextLength
elif self.appModule.lastTextLengths[self.windowHandle]<curTextLength:
message=self.windowText[self.appModule.lastTextLengths[self.windowHandle]:]
self.appModule.lastMessages.insert(0,message)
self.appModule.lastMessages=self.appModule.lastMessages[:self.appModule.MessageHistoryLength]
if config.conf["presentation"]["reportDynamicContentChanges"]:
ui.message(message)
self.appModule.lastTextLengths[self.windowHandle]=curTextLength
super(MirandaMessageViewerScrollbar,self).event_valueChange()
class DuplicateFocusListBox(IAccessible):
"""A list box which annoyingly fires focus events every second, even when a menu is open.
"""
def _get_shouldAllowIAccessibleFocusEvent(self):
# Stop annoying duplicate focus events, which are fired even if a menu is open.
focus = api.getFocusObject()
focusRole = focus.role
focusStates = focus.states
if (self == focus or
(focusRole == controlTypes.ROLE_MENUITEM and controlTypes.STATE_FOCUSED in focusStates) or
(focusRole == controlTypes.ROLE_POPUPMENU and controlTypes.STATE_INVISIBLE not in focusStates)
):
return False
return super(DuplicateFocusListBox, self).shouldAllowIAccessibleFocusEvent