2
__author__ = 'David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>'
3
appName = 'Script Recorder'
5
os.environ['GTK_MODULES']=''
7
from dogtail.utils import checkForA11yInteractively
8
checkForA11yInteractively()
13
import dogtail.rawinput
18
class PlaybackThread(threading.Thread):
19
def __init__(self, script):
20
threading.Thread.__init__(self)
32
useGtkSourceView = True
34
useGtkSourceView = False
36
def createSourceView():
37
langManager = gtksourceview.SourceLanguagesManager()
38
lang = langManager.get_language_from_mime_type("text/x-python")
39
buffer = gtksourceview.SourceBuffer()
40
sourceView = gtksourceview.SourceView(buffer)
41
buffer.set_language(lang)
42
buffer.set_highlight(True)
45
class RecorderGUI(gnome.Program):
47
gnome.Program.__init__(self)
48
appAuthors = ['Zack Cerza <zcerza@redhat.com>']
49
program = gnome.program_init(appName, '0.1')
51
if os.path.exists('recorder.glade'):
52
x = gtk.glade.XML('recorder.glade')
55
exec_root = sys.argv[0].split("/bin/")[0]
56
if exec_root[0] is not '/':
58
x = gtk.glade.XML(exec_root + '/share/dogtail/glade/recorder.glade')
60
self.window = x.get_widget('window')
63
self.window.set_icon_from_file('../icons/dogtail-head.svg')
65
self.window.set_icon_from_file('/usr/share/icons/hicolor/scalable/apps/dogtail-head.svg')
67
self.recordButton = x.get_widget('recordButton')
68
self.playButton = x.get_widget('playButton')
69
self.playButton.set_sensitive(False)
70
self.clearButton = x.get_widget('clearButton')
71
self.clearButton.set_sensitive(False)
72
self.saveButton = x.get_widget('saveButton')
73
self.saveButton.set_sensitive(False)
75
oldTextView = x.get_widget('scriptTextView')
76
parent = oldTextView.get_parent()
77
parent.remove(oldTextView)
78
sourceView = createSourceView()
79
parent.add(sourceView)
81
self.scriptTextView = sourceView
83
self.scriptTextView = x.get_widget('scriptTextView')
84
self.scriptTextView.set_editable(False)
85
#self.writerComboBox = x.get_widget('writerComboBox')
86
#self.writerComboBox.set_active(0)
87
#self.writerComboBox.set_sensitive(True)
89
# The following line added because self.writerComboBox is gone:
90
recorder.writerClass = ProceduralScriptWriter
94
self.window.show_all()
97
def connectSignals(self):
98
#self.writerComboBox.connect('changed', self.setWriterClass)
99
self.recordButton.connect('clicked', self.toggleRecording, self.scriptTextView)
100
self.playButton.connect('clicked', self.playScript, self.scriptTextView)
101
self.clearButton.connect('clicked', self.clearScript, self.scriptTextView)
102
self.saveButton.connect('clicked', self.saveScript)
103
self.window.connect('delete_event', self.quit)
105
def setWriterClass (self, comboBox):
106
selected = comboBox.get_active_text()
107
if selected == "Procedural":
108
recorder.writerClass = ProceduralScriptWriter
109
elif selected == "Object-Oriented":
110
recorder.writerClass = OOScriptWriter
112
print selected, "isn't a ScriptWriter, but it is selected. How?"
114
def toggleRecording(self, recordButton = None, scriptTextView = None):
115
label = self.recordButton.get_label()
116
recordID = 'gtk-media-record'
117
stopID = 'gtk-media-stop'
119
if label == recordID:
120
#self.writerComboBox.set_sensitive(False)
121
self.playButton.set_sensitive(False)
122
self.clearButton.set_sensitive(False)
123
self.saveButton.set_sensitive(False)
124
self.scriptTextView.set_editable(False)
125
self.recordButton.set_label(stopID)
126
recorder.startRecording(self.recordButton, self.scriptTextView)
128
elif label == stopID:
129
#self.writerComboBox.set_sensitive(True)
130
self.playButton.set_sensitive(True)
131
self.clearButton.set_sensitive(True)
132
self.saveButton.set_sensitive(True)
133
self.scriptTextView.set_editable(True)
134
self.recordButton.set_label(recordID)
135
recorder.stopRecording(self.recordButton, self.scriptTextView)
137
def stopRecording(self):
138
self.recordButton.set_label('gtk-media-stop')
139
self.toggleRecording()
141
def playScript(self, button = None, scriptTextView = None):
142
self.recordButton.set_sensitive(False)
143
self.playButton.set_sensitive(False)
144
self.scriptTextView.set_editable(False)
146
buffer = self.scriptTextView.get_buffer()
147
startIter = buffer.get_start_iter()
148
endIter = buffer.get_end_iter()
149
scriptText = buffer.get_text(startIter, endIter)
151
self.playbackThread = PlaybackThread(scriptText)
152
self.playbackThread.start()
153
self.playbackThread.join()
155
self.playButton.set_sensitive(True)
156
self.recordButton.set_sensitive(True)
157
self.scriptTextView.set_editable(True)
159
def clearScript(self, button = None, scriptTextView = None):
160
self.scriptTextView.get_buffer().set_text('')
161
self.clearButton.set_sensitive(False)
162
self.saveButton.set_sensitive(False)
164
def saveScript(self, button):
166
Brings up a file chooser dialog asking where to save the script.
168
self.saveFileChooser = gtk.FileChooserDialog("Save Script...", None, \
169
gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, \
170
gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
171
self.saveFileChooser.set_default_response(gtk.RESPONSE_OK)
173
# Why this isn't default, I do not understand.
174
self.saveFileChooser.set_do_overwrite_confirmation(True)
176
filter = gtk.FileFilter()
177
filter.set_name('Python files')
178
filter.add_pattern('*.py')
179
self.saveFileChooser.add_filter(filter)
180
filter = gtk.FileFilter()
181
filter.set_name('All Files')
182
filter.add_pattern('*')
183
self.saveFileChooser.add_filter(filter)
185
response = self.saveFileChooser.run()
186
if response == gtk.RESPONSE_OK:
187
fileName = self.saveFileChooser.get_filename()
188
# Append a .py to the file name if necessary
189
if fileName[-3:] != '.py':
191
file = open(fileName, 'w')
192
buffer = self.scriptTextView.get_buffer()
193
startIter = buffer.get_start_iter()
194
endIter = buffer.get_end_iter()
195
scriptText = buffer.get_text(startIter, endIter)
196
file.write(scriptText)
198
self.saveFileChooser.destroy()
200
def quit(self, *args):
207
source = event.source
208
if isinstance(source, atspi.Accessible):
209
sourceStr = " source:%s"%(str(dogtail.tree.Node(source)))
212
print "Got event: %s%s"%(event.type, sourceStr)
216
Abstract Writer subclass which writes out Python scripts
218
def __init__(self, scriptTextView = None):
219
self.scriptBuffer = ""
220
self.scriptTextView = scriptTextView
223
def recordLine(self, string):
225
if self.scriptTextView:
226
buffer = self.scriptTextView.get_buffer()
227
iter = buffer.get_end_iter()
228
if buffer.get_line_count() > 1:
229
string = '\n' + string
230
buffer.insert(iter, string)
233
iter = buffer.get_end_iter()
234
mark = buffer.create_mark('end', iter, True)
235
self.scriptTextView.scroll_mark_onscreen(mark)
236
buffer.delete_mark(mark)
238
self.scriptBuffer += '\n' + string
240
def recordClick(self, node):
241
raise NotImplementedError
243
def recordTyping(self, string, type, node):
244
raise NotImplementedError
246
def recordKeyCombo(self, string, type, node):
247
raise NotImplementedError
249
class OOScriptWriter(ScriptWriter):
251
Concrete Writer subclass which writes out Python scripts in an object-oriented
254
def __init__(self, scriptTextView = None):
255
ScriptWriter.__init__(self, scriptTextView)
257
self.debugVariables = False
259
self.recordLine("#!/usr/bin/python\nfrom dogtail.tree import *\n")
261
# maintain a dict from variable names to search paths
264
def generateVariableName(self, predicate):
266
result = predicate.makeScriptVariableName()
267
if result in self.variables:
268
# This variable name is already in use; need to append a number:
270
while result+str(index) in self.variables:
272
return result+str(index)
276
def printVariables(self):
279
for (varName, varAbsPath) in self.variables.iteritems():
280
print "varName:%s -> absPath:%s"%(varName, varAbsPath)
282
def generateAbsSearchPathMethodCall(self, absSearchPath):
284
Generates a method call that identifies the absolute search path,
285
optimizing away prefixes where possible with variable names.
287
# We optimize away the longest common absolute path prefix, i.e. the
288
# shortest relative path suffix:
290
print "*******************"
291
print "generateAbsSearchPathMethodCall for %s"%absSearchPath
292
self.printVariables()
294
shortestRelativePath = None
295
for (varName, varAbsPath) in self.variables.iteritems():
296
relPath = varAbsPath.getRelativePath(absSearchPath)
298
if shortestRelativePath:
299
if relPath.length() < shortestRelativePath[2].length():
300
shortestRelativePath = (varName, varAbsPath, relPath)
302
shortestRelativePath = (varName, varAbsPath, relPath)
305
if shortestRelativePath:
306
(varName, varAbsPath, relPath) = shortestRelativePath
307
print "shortestRelativePath: (%s, %s, %s)"%(varName, varAbsPath, relPath)
309
print "shortestRelativePath: None"
310
print "*******************"
312
if shortestRelativePath:
313
(varName, varAbsPath, relPath) = shortestRelativePath
314
return varName+relPath.makeScriptMethodCall()
316
# Have to specify it as an absolute path:
317
return "root"+absSearchPath.makeScriptMethodCall()
319
def recordClick(self, node):
323
if node == None: return
324
searchPath = node.getAbsoluteSearchPath()
327
print "----------------------------------"
328
print "click on %s"%searchPath
329
print "Full script would be: root%s"%searchPath.makeScriptMethodCall()
331
# Generate variables for nodes likely to be referred to often (application, window)
332
# FIXME: make this smarter?
334
if i<searchPath.length():
336
prefixPath = searchPath.getPrefix(i)
338
if self.debugVariables:
339
print "Considering: %s"%prefixPath
341
if not prefixPath in self.variables.values():
342
if self.debugVariables:
343
print "It is not yet a variable"
344
self.printVariables()
346
predicate = prefixPath.getPredicate(i-1)
347
varName = predicate.makeScriptVariableName()
348
self.recordLine(varName+" = "+self.generateAbsSearchPathMethodCall(prefixPath))
349
self.variables[varName]=prefixPath
351
if self.debugVariables:
352
print "It is already a variable"
354
result = self.generateAbsSearchPathMethodCall(searchPath)
358
print "----------------------------------"
360
self.recordLine(result)
362
class ProceduralScriptWriter(ScriptWriter):
364
Concrete Writer subclass which writes out Python scripts in a procedural
371
currentApplication = None
373
def __init__(self, scriptTextView = None):
374
ScriptWriter.__init__(self, scriptTextView)
376
self.recordLine("#!/usr/bin/python\nfrom dogtail.procedural import *\n")
378
def setUpFocus(self, node):
380
Writes out the necessary focus.application() and focus.dialog() lines
383
application = FakeNode.findAncestor(node, roleName = 'application')
386
if self.currentApplication:
387
if application == self.currentApplication: needApp = False
389
self.recordLine("focus.application('%s')" % application.name)
390
self.currentApplication = application
391
elif application == None:
392
print "Warning: could not determine which application you are clicking or typing on."
393
print " It is most likely not reporting its toplevel Accessible as having a"
394
print " role name of 'application'. Please file a bug against it!"
396
dialog = FakeNode.findAncestor(node, roleName = 'dialog')
399
if dialog == self.currentDialog: needDialog = False
401
self.recordLine("focus.dialog('%s')" % dialog.name)
402
self.currentDialog = dialog
404
frame = FakeNode.findAncestor(node, roleName='frame')
407
if frame == self.currentFrame: needFrame = False
409
self.recordLine("focus.frame('%s')" % frame.name)
410
self.currentFrame = frame
413
def recordClick(self, node, button):
414
if node == None: return False
416
self.setUpFocus(node)
419
#possibleActions = ['click', 'activate', 'open', 'menu']
420
possibleActions = ['click', 'activate', 'menu']
421
foundAnAction = False
422
for action in possibleActions:
423
if action in widget.actions.keys():
424
if button == 1 and action == 'menu': break
426
self.recordLine("%s('%s', roleName='%s')" % \
427
(action, widget.name.replace('\n','\\n'), widget.roleName))
429
if not foundAnAction:
430
if hasattr(widget, 'select') and button != 1:
431
self.recordLine("select('%s', roleName='%s')" % \
432
(widget.name.replace('\n','\\n'), widget.roleName))
434
if button != 1: btn = ', button = ' + str(button)
436
s = "click('%s', roleName='%s', raw=True%s)" % \
437
(widget.name.replace('\n','\\n'), widget.roleName, btn)
439
self.currentWidget = widget
441
def recordTyping(self, string, type, node):
442
if not string: return
443
self.setUpFocus(node)
444
self.recordLine("type(\"" + string.replace('"','\\"') + "\")")
446
def recordKeyCombo(self, string, type, node):
447
if not string: return
448
self.setUpFocus(node)
449
self.recordLine("keyCombo(\"" + string + "\")")
451
class FakeNode(dogtail.tree.Node):
452
"""A "cached pseudo-copy" of a Node
454
This class exists for cases where we know we're going to need information
455
about a Node instance at a point in time where it's no longer safe to
456
assume that the Accessible it wraps is still valid. It is designed to
457
cache enough information to allow all of the necessary Node methods to
458
execute properly and return something meaningful.
460
As it is often necessary to know the Node instance's parent, it creates
461
FakeNode instances of each and every one of its ancestors.
463
def __init__(self, node):
464
if node == None: raise TypeError, node
467
self.name = self.__node.name
468
self.roleName = self.__node.roleName
469
self.description = self.__node.description
470
self.actions = self.__node.actions
471
self.debugName = self.__node.debugName
473
self.text = self.__node.text
475
self.position = self.__node.position
477
self.size = self.__node.size
479
if node.parent: self.parent = FakeNode(self.__node.parent)
480
else: self.parent = None
482
if node.labellee: self.labellee = FakeNode(self.__node.labellee)
483
else: self.labellee = None
485
def __getattr__(self, name):
486
raise AttributeError, name
488
def __setattr__(self, name, value):
489
self.__dict__[name] = value
491
def __cmp__(self, otherNode):
492
if not otherNode: return True
495
roleNameMatch = False
497
nameMatch = otherNode.name == self.name
498
roleNameMatch = otherNode.roleName == self.roleName
499
descMatch = otherNode.description == self.description
500
match = nameMatch and roleNameMatch and descMatch
503
def findAncestor(node, name = None, roleName = None, description = None):
506
roleNameMatch = False
508
if name != None: nameMatch = node.parent.name == name
509
else: nameMatch = True
510
if roleName != None: roleNameMatch = node.parent.roleName == roleName
511
else: roleNameMatch = True
512
if description != None:
513
descMatch = node.parent.description == description
514
else: descMatch = True
515
match = nameMatch and roleNameMatch and descMatch
516
if match: return node.parent
519
findAncestor = staticmethod(findAncestor)
523
# Singleton EventRecorder
531
atspi.Accessibility_MODIFIER_NUMLOCK: 'NumLock',
532
atspi.Accessibility_MODIFIER_META3: 'Meta3',
533
atspi.Accessibility_MODIFIER_META2: 'Meta2',
534
atspi.Accessibility_MODIFIER_META: 'Meta',
535
atspi.Accessibility_MODIFIER_ALT: 'Alt',
536
atspi.Accessibility_MODIFIER_CONTROL: 'Control',
537
atspi.Accessibility_MODIFIER_SHIFTLOCK: 'ShiftLock',
538
atspi.Accessibility_MODIFIER_SHIFT: 'Shift' }
540
def __init__(self, writerClass = ProceduralScriptWriter):
542
self.writerClass = writerClass
543
self.lastFocusedNode = None
544
self.lastSelectedNode = None
545
self.lastPressedNode = None
546
self.lastReleasedNode = None
547
self.typedTextBuffer = ""
548
self.lastTypedNode = None
549
self.absoluteNodePaths = True
554
def __registerEvents(self):
555
# Only specific events are recorded:
559
listeners.append(atspi.EventListener(marshalOnFocus, ["focus:"]))
561
# State Changed events:
562
listeners.append(atspi.EventListener(marshalOnSelect, ["object:state-changed:selected"]))
564
# Mouse button events:
565
listeners.append(atspi.EventListener(marshalOnMouseButton, ["mouse:button"]))
568
listeners.append(atspi.DeviceListener(marshalOnKeyPress))
571
#listeners.append(atspi.EventListener(marshalOnWindowCreate, ["window:create"]))
575
def startRecording(self, unused = None, scriptTextView = None):
576
self.writer = self.writerClass(scriptTextView)
577
self.listeners = self.__registerEvents()
578
# set lastKeyPressTimeStamp to 1, which is an invalid value.
579
self.lastKeyPressTimeStamp = 1
582
def stopRecording(self, unused = None, scriptTextView = None):
583
for listener in self.listeners:
584
listener.deregister()
588
def onFocus(self, event):
589
sourceNode = dogtail.tree.Node(event.source)
590
sourceNode = FakeNode(sourceNode)
591
#if sourceNode == self.lastPressedNode or \
592
# sourceNode == self.lastReleasedNode:
593
# sourceNode._FakeNode__node.blink()
594
self.lastFocusedNode = sourceNode
596
def onSelect(self, event):
597
sourceNode = dogtail.tree.Node(event.source)
598
sourceNode = FakeNode(sourceNode)
599
self.lastSelectedNode = sourceNode
601
def onMouseButton(self, event):
603
self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastFocusedNode)
604
self.typedTextBuffer = ""
606
isPress = isRelease = False
607
g=re.match('mouse:button:(\d)(p|r)', event.type).groups()
609
if g[1] == 'p': isPress = True
610
elif g[1] == 'r': isRelease = True
612
# The source node is always "main" - which sucks. We have to detect
613
# the real source ourselves.
615
def detectByCoordinates(nodes, x, y):
616
# From a list of nodes, find the smallest one that (x, y) is in
617
def isCandidate(node, x, y):
618
# If (x, y) is inside the node, return True
619
if node and node.position:
620
#print "x,y: %s, %s" % (x, y)
621
#print "position: %s, size: %s" % (node.position, node.size)
622
if node.position[0] <= x <= (node.position[0] + node.size[0]) and \
623
node.position[1] <= y <= (node.position[1] + node.size[1]):
625
def getAllDescendants(node):
627
for child in node.children:
629
result.extend(getAllDescendants(child))
630
#for grandChild in child.children:
631
# result.append(grandChild)
633
def smallestNode(nodes):
634
# Return the node with the smallest area
637
area = node.size[0] * node.size[1]
638
if areas.get(area, None):
639
print "Two nodes are the same size?!"
640
print str(areas[area]) + "|" + str(node)
643
return areas[min(areas.keys())]
647
if isCandidate(node, x, y):
649
# table children don't send focus signals, so we have to
650
# find the child itself.
651
if node.roleName == 'table':
653
if not hasattr(node, 'children'):
654
node = node._FakeNode__node
655
# getAllDescendants() is very expensive :(
656
possibleNodes = getAllDescendants(node)
657
probableNodes = [n for n in possibleNodes if \
658
isCandidate(n, x, y)]
659
detectedNode = smallestNode(probableNodes)
664
if self.lastSelectedNode != self.lastFocusedNode:
665
possibleNodes = (self.lastSelectedNode, self.lastFocusedNode)
667
# If self.lastSelectedNode isn't meaningful, don't waste time on
668
# it with detectByCoordinates().
669
possibleNodes = [self.lastFocusedNode]
670
detectedNode = detectByCoordinates(possibleNodes, x, y)
671
if detectedNode and ((detectedNode.name == appName and \
672
detectedNode.roleName == 'frame') or \
673
FakeNode.findAncestor(detectedNode, \
674
roleName = 'frame', name = appName)):
675
self.lastPressedNode = None
676
self.lastReleasedNode = None
678
if detectedNode and not isinstance(detectedNode, FakeNode):
679
detectedNode = FakeNode(detectedNode)
681
self.lastPressedNode = detectedNode
683
self.lastReleasedNode = detectedNode
685
if isRelease and detectedNode:
686
self.writer.recordClick(detectedNode, button)
688
def __checkModMask(fullMask, partMask, toCheck):
690
if fullMask == 0 or partMask == 0:
691
return (partMask, result)
692
if partMask - toCheck >= 0:
693
partMask = partMask - toCheck
695
return (partMask, result)
696
__checkModMask = staticmethod(__checkModMask)
698
def __getModifiers(self, modMask, keyChar):
701
keys = self.modifiers.keys()
704
(partMask, pressed) = self.__checkModMask(modMask, partMask, i)
705
if pressed: modsDict[self.modifiers[i]] = i
707
if modsDict.has_key('ShiftLock'):
708
del modsDict['ShiftLock']
709
# Shift+foo is not considered a key combo if foo is printable
710
if modsDict.has_key('Shift') and not keyChar == '':
711
del modsDict['Shift']
714
def onKeyPress(self, event):
715
# The Fn key on my Thinkpad has a keyID of 0. Ignore it.
716
if not event.keyID: return
717
if event.type == atspi.SPI_KEY_PRESSED:
719
elif event.type == atspi.SPI_KEY_RELEASED:
723
self.lastKeyPressTimeStamp = event.timeStamp
724
if self.lastKeyPressTimeStamp < 0:
725
elapsedTime = event.timeStamp - self.lastKeyPressTimeStamp
729
if self.lastFocusedNode:
730
self.lastFocusedNode.text = self.lastFocusedNode._FakeNode__node.text
732
keyString = dogtail.rawinput.keyStrings[event.keyID]
733
keyChar = dogtail.rawinput.keySymToUniChar(event.keyID)
735
# If the only key being pressed is a modifier, don't do anything.
736
if keyString.startswith("Alt_") or keyString.startswith("Control_") \
737
or keyString.startswith("Meta_") or \
738
keyString.startswith("Shift") or keyString == 'Caps_Lock':
744
modsDict = self.__getModifiers(event.modifiers, keyChar)
746
def buildModifierString(modsDict):
749
for mod in modsDict.keys():
750
s = s + '<' + mod + '>'
753
modString = buildModifierString(modsDict)
755
# If modifiers are present, we're dealing with a key combo
756
if modString: combo = True
757
# Otherwise, we assume for a second that we're not.
760
# If the key represents a printable character, use that.
761
if keyChar != '': key = keyChar
763
# Otherwise, use the keyString, e.g. 'Return'.
765
# We treat nonprintable characters like key combos.
768
if not combo and self.lastTypedNode is not None and \
769
(self.lastTypedNode != self.lastFocusedNode):
770
#print "changed node, flushing"
774
#print "%s ! %s ! %s" % (str(self.lastTypedNode), str(self.lastFocusedNode), self.typedTextBuffer)
777
self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastFocusedNode)
778
self.typedTextBuffer = ""
779
self.writer.recordKeyCombo(modString + key, type, self.lastFocusedNode)
780
self.lastTypedNode = self.lastFocusedNode
782
self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastTypedNode)
783
self.typedTextBuffer = key
784
self.lastTypedNode = self.lastFocusedNode
786
if self.typedTextBuffer == "":
787
self.lastTypedNode = self.lastFocusedNode
788
self.typedTextBuffer = self.typedTextBuffer + key
791
# If we're using the keyString or have modifiers, flush
792
# self.typedTextBuffer by recording a line and also record the key
794
if modString or flush:
795
if self.typedTextBuffer:
796
self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastFocusedNode)
798
self.typedTextBuffer = key
800
self.typedTextBuffer = ""
801
self.lastTypedNode = self.lastFocusedNode
802
if modString or combo:
803
self.writer.recordKeyCombo(modString + key, type, self.lastFocusedNode)
804
# Otherwise add to the buffer, and wait to flush it.
806
if self.typedTextBuffer == "":
807
self.lastTypedNode = self.lastFocusedNode
808
self.typedTextBuffer = self.typedTextBuffer + key
810
def onWindowCreate(self, event):
812
sourceNode = dogtail.tree.Node(event.source)
813
# print "Window creation: %s"%str(sourceNode)
815
def getLogStringForNode(self, node):
816
if self.absoluteNodePaths:
817
return node.getAbsoluteSearchPath()
821
# Under construction. These ought to be methods, but am having Python assertion
822
# failures in refcounting when trying to hook them up using lambda expressions; grrr...
824
def marshalOnFocus(event):
825
try: recorder.onFocus(event)
826
except: traceback.print_exc()
828
def marshalOnSelect(event):
829
try: recorder.onSelect(event)
830
except: traceback.print_exc()
832
def marshalOnMouseButton(event):
833
try: recorder.onMouseButton(event)
834
except: traceback.print_exc()
836
def marshalOnKeyPress(event):
837
try: recorder.onKeyPress(event)
838
except: traceback.print_exc()
840
def marshalOnWindowCreate(event):
841
try: recorder.onWindowCreate(event)
842
except: traceback.print_exc()
844
recorder = EventRecorder()
845
recorderGUI = RecorderGUI()
846
#recorder.writer.debug = True
847
#recorder.writer.debugVariables = True
850
# vim: sw=4 ts=4 sts=4 et ai