KGRKJGETMRETU895U-589TY5MIGM5JGB5SDFESFREWTGR54TY
Server : Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8e-fips-rhel5 DAV/2 PHP/5.2.17
System : Linux localhost 2.6.18-419.el5 #1 SMP Fri Feb 24 22:47:42 UTC 2017 x86_64
User : nobody ( 99)
PHP Version : 5.2.17
Disable Function : NONE
Directory :  /proc/21573/root/usr/bin/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/21573/root/usr/bin/dogtail-recorder
#!/usr/bin/python
__author__ = 'David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>'
appName = 'Script Recorder'
import os
os.environ['GTK_MODULES']=''

from dogtail.utils import checkForA11yInteractively
checkForA11yInteractively()

import atspi
import dogtail.tree
import gtk.gdk
import dogtail.rawinput
import re

# Begin GUI code
import threading
class PlaybackThread(threading.Thread):
    def __init__(self, script):
        threading.Thread.__init__(self)
        self.script = script

    def run(self):
        exec self.script

import gobject
import gnome
import gtk.glade

try:
    import gtksourceview
    useGtkSourceView = True
except:
    useGtkSourceView = False

def createSourceView():
    langManager = gtksourceview.SourceLanguagesManager()
    lang = langManager.get_language_from_mime_type("text/x-python")
    buffer = gtksourceview.SourceBuffer()
    sourceView = gtksourceview.SourceView(buffer)
    buffer.set_language(lang)
    buffer.set_highlight(True)
    return sourceView

class RecorderGUI(gnome.Program):
    def __init__(self):
        gnome.Program.__init__(self)
        appAuthors = ['Zack Cerza <zcerza@redhat.com>']
        program = gnome.program_init(appName, '0.1')

        if os.path.exists('recorder.glade'):
            x = gtk.glade.XML('recorder.glade')
        else:
            import sys
            exec_root = sys.argv[0].split("/bin/")[0]
            if exec_root[0] is not '/':
                exec_root = "/usr"
            x = gtk.glade.XML(exec_root + '/share/dogtail/glade/recorder.glade')

        self.window = x.get_widget('window')

        try:
            self.window.set_icon_from_file('../icons/dogtail-head.svg')
        except:
            self.window.set_icon_from_file('/usr/share/icons/hicolor/scalable/apps/dogtail-head.svg')

        self.recordButton = x.get_widget('recordButton')
        self.playButton = x.get_widget('playButton')
        self.playButton.set_sensitive(False)
        self.clearButton = x.get_widget('clearButton')
        self.clearButton.set_sensitive(False)
        self.saveButton = x.get_widget('saveButton')
        self.saveButton.set_sensitive(False)
        if useGtkSourceView:
            oldTextView = x.get_widget('scriptTextView')
            parent = oldTextView.get_parent()
            parent.remove(oldTextView)
            sourceView = createSourceView()
            parent.add(sourceView)
            sourceView.show()
            self.scriptTextView = sourceView
        else:
            self.scriptTextView = x.get_widget('scriptTextView')
        self.scriptTextView.set_editable(False)
        #self.writerComboBox = x.get_widget('writerComboBox')
        #self.writerComboBox.set_active(0)
        #self.writerComboBox.set_sensitive(True)

        # The following line added because self.writerComboBox is gone:
        recorder.writerClass = ProceduralScriptWriter

        self.connectSignals()

        self.window.show_all()
        gtk.main()

    def connectSignals(self):
        #self.writerComboBox.connect('changed', self.setWriterClass)
        self.recordButton.connect('clicked', self.toggleRecording, self.scriptTextView)
        self.playButton.connect('clicked', self.playScript, self.scriptTextView)
        self.clearButton.connect('clicked', self.clearScript, self.scriptTextView)
        self.saveButton.connect('clicked', self.saveScript)
        self.window.connect('delete_event', self.quit)

    def setWriterClass (self, comboBox):
        selected = comboBox.get_active_text()
        if selected == "Procedural":
            recorder.writerClass = ProceduralScriptWriter
        elif selected == "Object-Oriented":
            recorder.writerClass = OOScriptWriter
        else:
            print selected, "isn't a ScriptWriter, but it is selected. How?"

    def toggleRecording(self, recordButton = None, scriptTextView = None):
        label = self.recordButton.get_label()
        recordID = 'gtk-media-record'
        stopID = 'gtk-media-stop'
        
        if label == recordID:
            #self.writerComboBox.set_sensitive(False)
            self.playButton.set_sensitive(False)
            self.clearButton.set_sensitive(False)
            self.saveButton.set_sensitive(False)
            self.scriptTextView.set_editable(False)
            self.recordButton.set_label(stopID)
            recorder.startRecording(self.recordButton, self.scriptTextView)
        
        elif label == stopID:
            #self.writerComboBox.set_sensitive(True)
            self.playButton.set_sensitive(True)
            self.clearButton.set_sensitive(True)
            self.saveButton.set_sensitive(True)
            self.scriptTextView.set_editable(True)
            self.recordButton.set_label(recordID)
            recorder.stopRecording(self.recordButton, self.scriptTextView)

    def stopRecording(self):
        self.recordButton.set_label('gtk-media-stop')
        self.toggleRecording()

    def playScript(self, button = None, scriptTextView = None):
        self.recordButton.set_sensitive(False)
        self.playButton.set_sensitive(False)
        self.scriptTextView.set_editable(False)

        buffer = self.scriptTextView.get_buffer()
        startIter = buffer.get_start_iter()
        endIter = buffer.get_end_iter()
        scriptText = buffer.get_text(startIter, endIter)

        self.playbackThread = PlaybackThread(scriptText)
        self.playbackThread.start()
        self.playbackThread.join()

        self.playButton.set_sensitive(True)
        self.recordButton.set_sensitive(True)
        self.scriptTextView.set_editable(True)

    def clearScript(self, button = None, scriptTextView = None):
        self.scriptTextView.get_buffer().set_text('')
        self.clearButton.set_sensitive(False)
        self.saveButton.set_sensitive(False)

    def saveScript(self, button):
        """
        Brings up a file chooser dialog asking where to save the script.
        """
        self.saveFileChooser = gtk.FileChooserDialog("Save Script...", None, \
            gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, \
            gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        self.saveFileChooser.set_default_response(gtk.RESPONSE_OK)

        # Why this isn't default, I do not understand.
        self.saveFileChooser.set_do_overwrite_confirmation(True)

        filter = gtk.FileFilter()
        filter.set_name('Python files')
        filter.add_pattern('*.py')
        self.saveFileChooser.add_filter(filter)
        filter = gtk.FileFilter()
        filter.set_name('All Files')
        filter.add_pattern('*')
        self.saveFileChooser.add_filter(filter)

        response = self.saveFileChooser.run()
        if response == gtk.RESPONSE_OK:
            fileName = self.saveFileChooser.get_filename()
            # Append a .py to the file name if necessary
            if fileName[-3:] != '.py':
                fileName += '.py'
            file = open(fileName, 'w')
            buffer = self.scriptTextView.get_buffer()
            startIter = buffer.get_start_iter()
            endIter = buffer.get_end_iter()
            scriptText = buffer.get_text(startIter, endIter)
            file.write(scriptText)
            file.close()
        self.saveFileChooser.destroy()

    def quit(self, *args):
        self.stopRecording()
        gtk.main_quit()

# End GUI code

def logEvent(event):
    source = event.source
    if isinstance(source, atspi.Accessible):
        sourceStr = " source:%s"%(str(dogtail.tree.Node(source)))
    else:
        sourceStr = ""
    print "Got event: %s%s"%(event.type, sourceStr)

class ScriptWriter:
    """
    Abstract Writer subclass which writes out Python scripts
    """
    def __init__(self, scriptTextView = None):
        self.scriptBuffer = ""
        self.scriptTextView = scriptTextView
        self.debug = False

    def recordLine(self, string):
        print string
        if self.scriptTextView:
            buffer = self.scriptTextView.get_buffer()
            iter = buffer.get_end_iter()
            if buffer.get_line_count() > 1:
                string = '\n' + string
            buffer.insert(iter, string)
            
            # Scroll to the end
            iter = buffer.get_end_iter()
            mark = buffer.create_mark('end', iter, True)
            self.scriptTextView.scroll_mark_onscreen(mark)
            buffer.delete_mark(mark)
        else:
            self.scriptBuffer += '\n' + string

    def recordClick(self, node):
        raise NotImplementedError

    def recordTyping(self, string, type, node):
        raise NotImplementedError

    def recordKeyCombo(self, string, type, node):
        raise NotImplementedError

class OOScriptWriter(ScriptWriter):
    """
    Concrete Writer subclass which writes out Python scripts in an object-oriented
    style
    """
    def __init__(self, scriptTextView = None):
        ScriptWriter.__init__(self, scriptTextView)

        self.debugVariables = False

        self.recordLine("#!/usr/bin/python\nfrom dogtail.tree import *\n")

        # maintain a dict from variable names to search paths
        self.variables = {}

    def generateVariableName(self, predicate):
        # Ensure uniqueness
        result = predicate.makeScriptVariableName()
        if result in self.variables:
            # This variable name is already in use; need to append a number:
            index = 1
            while result+str(index) in self.variables:
                index+=1
            return result+str(index)
        else:
            return result

    def printVariables(self):
        # debug hook
        print "variables:"
        for (varName, varAbsPath) in self.variables.iteritems():
            print "varName:%s -> absPath:%s"%(varName, varAbsPath)

    def generateAbsSearchPathMethodCall(self, absSearchPath):
        """
        Generates a method call that identifies the absolute search path,
        optimizing away prefixes where possible with variable names.
        """
        # We optimize away the longest common absolute path prefix, i.e. the
        # shortest relative path suffix:
        if self.debug:
            print "*******************"
            print "generateAbsSearchPathMethodCall for %s"%absSearchPath
            self.printVariables()

        shortestRelativePath = None
        for (varName, varAbsPath) in self.variables.iteritems():
            relPath = varAbsPath.getRelativePath(absSearchPath)
            if relPath:
                if shortestRelativePath:
                    if relPath.length() < shortestRelativePath[2].length():
                        shortestRelativePath = (varName, varAbsPath, relPath)
                else:
                    shortestRelativePath = (varName, varAbsPath, relPath)

        if self.debug:
            if shortestRelativePath:
                (varName, varAbsPath, relPath) = shortestRelativePath
                print "shortestRelativePath: (%s, %s, %s)"%(varName, varAbsPath, relPath)
            else:
                print "shortestRelativePath: None"
            print "*******************"

        if shortestRelativePath:
            (varName, varAbsPath, relPath) = shortestRelativePath
            return varName+relPath.makeScriptMethodCall()
        else:
            # Have to specify it as an absolute path:
            return "root"+absSearchPath.makeScriptMethodCall()

    def recordClick(self, node):
        """
        Record a mouse click
        """
        if node == None: return
        searchPath = node.getAbsoluteSearchPath()

        if self.debug:
            print "----------------------------------"
            print "click on %s"%searchPath
            print "Full script would be: root%s"%searchPath.makeScriptMethodCall()

        # Generate variables for nodes likely to be referred to often (application, window)
        # FIXME: make this smarter?
        for i in [1,2,3]:
            if i<searchPath.length():

                prefixPath = searchPath.getPrefix(i)

                if self.debugVariables:
                    print "Considering: %s"%prefixPath

                if not prefixPath in self.variables.values():
                    if self.debugVariables:
                        print "It is not yet a variable"
                        self.printVariables()

                    predicate = prefixPath.getPredicate(i-1)
                    varName = predicate.makeScriptVariableName()
                    self.recordLine(varName+" = "+self.generateAbsSearchPathMethodCall(prefixPath))
                    self.variables[varName]=prefixPath
                else:
                    if self.debugVariables:
                        print "It is already a variable"

        result = self.generateAbsSearchPathMethodCall(searchPath)
        result +=".click()"

        if self.debug:
            print "----------------------------------"

        self.recordLine(result)

class ProceduralScriptWriter(ScriptWriter):
    """
    Concrete Writer subclass which writes out Python scripts in a procedural
    style
    """
    
    currentWidget = None
    currentDialog = None
    currentFrame = None
    currentApplication = None

    def __init__(self, scriptTextView = None):
        ScriptWriter.__init__(self, scriptTextView)

        self.recordLine("#!/usr/bin/python\nfrom dogtail.procedural import *\n")
    
    def setUpFocus(self, node):
        """
        Writes out the necessary focus.application() and focus.dialog() lines 
        to the script.
        """
        application = FakeNode.findAncestor(node, roleName = 'application')
        if application:
            needApp = True
            if self.currentApplication:
                if application == self.currentApplication: needApp = False
            if needApp:
                self.recordLine("focus.application('%s')" % application.name)
                self.currentApplication = application
        elif application == None:
            print "Warning: could not determine which application you are clicking or typing on."
            print "    It is most likely not reporting its toplevel Accessible as having a"
            print "    role name of 'application'. Please file a bug against it!"

        dialog = FakeNode.findAncestor(node, roleName = 'dialog')
        if dialog:
            needDialog = True
            if dialog == self.currentDialog: needDialog = False
            if needDialog:
                self.recordLine("focus.dialog('%s')" % dialog.name)
                self.currentDialog = dialog
        else:
            frame = FakeNode.findAncestor(node, roleName='frame')
            if frame:
                needFrame = True
                if frame == self.currentFrame: needFrame = False
                if needFrame:
                    self.recordLine("focus.frame('%s')" % frame.name)
                    self.currentFrame = frame

        
    def recordClick(self, node, button):
        if node == None: return False

        self.setUpFocus(node)
        
        widget = node
        #possibleActions = ['click', 'activate', 'open', 'menu']
        possibleActions = ['click', 'activate', 'menu']
        foundAnAction = False
        for action in possibleActions:
            if action in widget.actions.keys():
                if button == 1 and action == 'menu': break
                foundAnAction = True
                self.recordLine("%s('%s', roleName='%s')" % \
                        (action, widget.name.replace('\n','\\n'), widget.roleName))
                break
        if not foundAnAction: 
            if hasattr(widget, 'select') and button != 1:
                self.recordLine("select('%s', roleName='%s')" % \
                        (widget.name.replace('\n','\\n'), widget.roleName))
            else:
                if button != 1: btn = ', button = ' + str(button)
                else: btn = ''
                s = "click('%s', roleName='%s', raw=True%s)" % \
                        (widget.name.replace('\n','\\n'), widget.roleName, btn)
                self.recordLine(s)
        self.currentWidget = widget
        
    def recordTyping(self, string, type, node):
        if not string: return
        self.setUpFocus(node)
        self.recordLine("type(\"" + string.replace('"','\\"') + "\")")

    def recordKeyCombo(self, string, type, node):
        if not string: return
        self.setUpFocus(node)
        self.recordLine("keyCombo(\"" + string + "\")")

class FakeNode(dogtail.tree.Node):
    """A "cached pseudo-copy" of a Node

    This class exists for cases where we know we're going to need information
    about a Node instance at a point in time where it's no longer safe to
    assume that the Accessible it wraps is still valid. It is designed to
    cache enough information to allow all of the necessary Node methods to
    execute properly and return something meaningful.

    As it is often necessary to know the Node instance's parent, it creates
    FakeNode instances of each and every one of its ancestors.
    """
    def __init__(self, node):
        if node == None: raise TypeError, node

        self.__node = node
        self.name = self.__node.name
        self.roleName = self.__node.roleName
        self.description = self.__node.description
        self.actions = self.__node.actions
        self.debugName = self.__node.debugName

        self.text = self.__node.text

        self.position = self.__node.position

        self.size = self.__node.size

        if node.parent: self.parent = FakeNode(self.__node.parent)
        else: self.parent = None

        if node.labellee: self.labellee = FakeNode(self.__node.labellee)
        else: self.labellee = None

    def __getattr__(self, name):
        raise AttributeError, name

    def __setattr__(self, name, value):
        self.__dict__[name] = value

    def __cmp__(self, otherNode):
        if not otherNode: return True

        nameMatch = False
        roleNameMatch = False
        descMatch = False
        nameMatch = otherNode.name == self.name
        roleNameMatch = otherNode.roleName == self.roleName
        descMatch = otherNode.description == self.description
        match = nameMatch and roleNameMatch and descMatch
        return not match

    def findAncestor(node, name = None, roleName = None, description = None):
        while node.parent:
            nameMatch = False
            roleNameMatch = False
            descMatch = False
            if name != None: nameMatch = node.parent.name == name
            else: nameMatch = True
            if roleName != None: roleNameMatch = node.parent.roleName == roleName
            else: roleNameMatch = True
            if description != None: 
                descMatch = node.parent.description == description
            else: descMatch = True
            match = nameMatch and roleNameMatch and descMatch
            if match: return node.parent
            node = node.parent
        return None
    findAncestor = staticmethod(findAncestor)



# Singleton EventRecorder
global recorder

class EventRecorder:
    """
    Event Recorder
    """
    modifiers = {
            atspi.Accessibility_MODIFIER_NUMLOCK: 'NumLock',
            atspi.Accessibility_MODIFIER_META3: 'Meta3',
            atspi.Accessibility_MODIFIER_META2: 'Meta2',
            atspi.Accessibility_MODIFIER_META: 'Meta',
            atspi.Accessibility_MODIFIER_ALT: 'Alt',
            atspi.Accessibility_MODIFIER_CONTROL: 'Control',
            atspi.Accessibility_MODIFIER_SHIFTLOCK: 'ShiftLock',
            atspi.Accessibility_MODIFIER_SHIFT: 'Shift' }

    def __init__(self, writerClass = ProceduralScriptWriter):
        self.writer = None
        self.writerClass = writerClass
        self.lastFocusedNode = None
        self.lastSelectedNode = None
        self.lastPressedNode = None
        self.lastReleasedNode = None
        self.typedTextBuffer = ""
        self.lastTypedNode = None
        self.absoluteNodePaths = True
        self.listeners = []

        import gtk.keysyms

    def __registerEvents(self):
        # Only specific events are recorded:
        listeners = []

        # Focus events:
        listeners.append(atspi.EventListener(marshalOnFocus, ["focus:"]))

        # State Changed events:
        listeners.append(atspi.EventListener(marshalOnSelect, ["object:state-changed:selected"]))

        # Mouse button events:
        listeners.append(atspi.EventListener(marshalOnMouseButton, ["mouse:button"]))

        # Keystroke events:
        listeners.append(atspi.DeviceListener(marshalOnKeyPress))

        # Window creation:
        #listeners.append(atspi.EventListener(marshalOnWindowCreate, ["window:create"]))

        return listeners

    def startRecording(self, unused = None, scriptTextView = None):
        self.writer = self.writerClass(scriptTextView)
        self.listeners = self.__registerEvents()
        # set lastKeyPressTimeStamp to 1, which is an invalid value.
        self.lastKeyPressTimeStamp = 1
        atspi.event_main()

    def stopRecording(self, unused = None, scriptTextView = None):
        for listener in self.listeners:
            listener.deregister()
        atspi.event_quit()
        self.writer = None

    def onFocus(self, event):
        sourceNode = dogtail.tree.Node(event.source)
        sourceNode = FakeNode(sourceNode)
        #if sourceNode == self.lastPressedNode or \
        #        sourceNode == self.lastReleasedNode:
        #    sourceNode._FakeNode__node.blink()
        self.lastFocusedNode = sourceNode

    def onSelect(self, event):
        sourceNode = dogtail.tree.Node(event.source)
        sourceNode = FakeNode(sourceNode)
        self.lastSelectedNode = sourceNode

    def onMouseButton(self, event):
        #logEvent(event)
        self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastFocusedNode)
        self.typedTextBuffer = ""

        isPress = isRelease = False
        g=re.match('mouse:button:(\d)(p|r)', event.type).groups()
        button = int(g[0])
        if g[1] == 'p': isPress = True
        elif g[1] == 'r': isRelease = True

        # The source node is always "main" - which sucks. We have to detect
        # the real source ourselves.

        def detectByCoordinates(nodes, x, y):
            # From a list of nodes, find the smallest one that (x, y) is in
            def isCandidate(node, x, y):
                # If (x, y) is inside the node, return True
                if node and node.position:
                    #print "x,y: %s, %s" % (x, y)
                    #print "position: %s, size: %s" % (node.position, node.size)
                    if node.position[0] <= x <= (node.position[0] + node.size[0]) and \
                            node.position[1] <= y <= (node.position[1] + node.size[1]):
                        return True
            def getAllDescendants(node):
                result = []
                for child in node.children:
                    result.append(child)
                    result.extend(getAllDescendants(child))
                    #for grandChild in child.children:
                    #    result.append(grandChild)
                return result
            def smallestNode(nodes):
                # Return the node with the smallest area
                areas = {}
                for node in nodes:
                    area = node.size[0] * node.size[1]
                    if areas.get(area, None):
                        print "Two nodes are the same size?!"
                        print str(areas[area]) + "|" + str(node)
                    areas[area] = node
                if areas:
                    return areas[min(areas.keys())]

            detectedNode = None
            for node in nodes:
                if isCandidate(node, x, y):
                    detectedNode = node
                    # table children don't send focus signals, so we have to
                    # find the child itself.
                    if node.roleName == 'table':
                        detectedNode = None
                        if not hasattr(node, 'children'):
                            node = node._FakeNode__node
                        # getAllDescendants() is very expensive :(
                        possibleNodes = getAllDescendants(node)
                        probableNodes = [n for n in possibleNodes if \
                                isCandidate(n, x, y)]
                        detectedNode = smallestNode(probableNodes)
            return detectedNode

        x = event.detail1
        y = event.detail2
        if self.lastSelectedNode != self.lastFocusedNode:
            possibleNodes = (self.lastSelectedNode, self.lastFocusedNode)
        else:
            # If self.lastSelectedNode isn't meaningful, don't waste time on 
            # it with detectByCoordinates().
            possibleNodes = [self.lastFocusedNode]
        detectedNode = detectByCoordinates(possibleNodes, x, y)
        if detectedNode and ((detectedNode.name == appName and \
                detectedNode.roleName == 'frame') or \
                FakeNode.findAncestor(detectedNode, \
                roleName = 'frame', name = appName)):
            self.lastPressedNode = None
            self.lastReleasedNode = None
            return
        if detectedNode and not isinstance(detectedNode, FakeNode):
            detectedNode = FakeNode(detectedNode)
        if isPress: 
            self.lastPressedNode = detectedNode
        elif isRelease:
            self.lastReleasedNode = detectedNode
        
        if isRelease and detectedNode:
            self.writer.recordClick(detectedNode, button)

    def __checkModMask(fullMask, partMask, toCheck):
        result = False
        if fullMask == 0 or partMask == 0:
            return (partMask, result)
        if partMask - toCheck >= 0:
            partMask = partMask - toCheck
            result = True
        return (partMask, result)
    __checkModMask = staticmethod(__checkModMask)

    def __getModifiers(self, modMask, keyChar):
        partMask = modMask
        modsDict = {}
        keys = self.modifiers.keys()
        keys.sort()
        for i in keys[::-1]:
            (partMask, pressed) = self.__checkModMask(modMask, partMask, i)
            if pressed: modsDict[self.modifiers[i]] = i
        # Screw capslock.
        if modsDict.has_key('ShiftLock'):
            del modsDict['ShiftLock']
        # Shift+foo is not considered a key combo if foo is printable
        if modsDict.has_key('Shift') and not keyChar == '':
            del modsDict['Shift']
        return modsDict

    def onKeyPress(self, event):
        # The Fn key on my Thinkpad has a keyID of 0. Ignore it.
        if not event.keyID: return
        if event.type == atspi.SPI_KEY_PRESSED:
            type = "pressed"
        elif event.type == atspi.SPI_KEY_RELEASED:
            type = "released"
            return

        self.lastKeyPressTimeStamp = event.timeStamp
        if self.lastKeyPressTimeStamp < 0:
            elapsedTime = event.timeStamp - self.lastKeyPressTimeStamp
        else:
            elapsedTime = 0

        if self.lastFocusedNode:
            self.lastFocusedNode.text = self.lastFocusedNode._FakeNode__node.text

        keyString = dogtail.rawinput.keyStrings[event.keyID]
        keyChar = dogtail.rawinput.keySymToUniChar(event.keyID)

        # If the only key being pressed is a modifier, don't do anything.
        if keyString.startswith("Alt_") or keyString.startswith("Control_") \
                or keyString.startswith("Meta_") or \
                keyString.startswith("Shift") or keyString == 'Caps_Lock':
            isJustAMod = True
            return
        else:
            isJustAMod = False

        modsDict = self.__getModifiers(event.modifiers, keyChar)

        def buildModifierString(modsDict):
            s = ''
            if modsDict:
                for mod in modsDict.keys():
                    s = s + '<' + mod + '>'
            return s

        modString = buildModifierString(modsDict)

        # If modifiers are present, we're dealing with a key combo
        if modString: combo = True
        # Otherwise, we assume for a second that we're not.
        else: combo = False
        
        # If the key represents a printable character, use that.
        if keyChar != '': key = keyChar
        else:
            # Otherwise, use the keyString, e.g. 'Return'.
            key = keyString
            # We treat nonprintable characters like key combos.
            combo = True

        if not combo and self.lastTypedNode is not None and \
                (self.lastTypedNode != self.lastFocusedNode):
            #print "changed node, flushing"
            flush = True
        else: flush = False

        #print "%s ! %s ! %s" % (str(self.lastTypedNode), str(self.lastFocusedNode), self.typedTextBuffer)

        if combo:
            self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastFocusedNode)
            self.typedTextBuffer = ""
            self.writer.recordKeyCombo(modString + key, type, self.lastFocusedNode)
            self.lastTypedNode = self.lastFocusedNode
        elif flush:
            self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastTypedNode)
            self.typedTextBuffer = key
            self.lastTypedNode = self.lastFocusedNode
        else:
            if self.typedTextBuffer == "":
                self.lastTypedNode = self.lastFocusedNode
            self.typedTextBuffer = self.typedTextBuffer + key
        return

        # If we're using the keyString or have modifiers, flush 
        # self.typedTextBuffer by recording a line and also record the key 
        # combo.
        if modString or flush:
            if self.typedTextBuffer:
                self.writer.recordTyping(self.typedTextBuffer, "pressed", self.lastFocusedNode)
                if not combo:
                    self.typedTextBuffer = key
                else:
                    self.typedTextBuffer = ""
                self.lastTypedNode = self.lastFocusedNode
            if modString or combo:
                self.writer.recordKeyCombo(modString + key, type, self.lastFocusedNode)
        # Otherwise add to the buffer, and wait to flush it.
        else:
            if self.typedTextBuffer == "":
                self.lastTypedNode = self.lastFocusedNode
            self.typedTextBuffer = self.typedTextBuffer + key

    def onWindowCreate(self, event):
        # logEvent(event)
        sourceNode = dogtail.tree.Node(event.source)
        # print "Window creation: %s"%str(sourceNode)

    def getLogStringForNode(self, node):
        if self.absoluteNodePaths:
            return node.getAbsoluteSearchPath()
        else:
            return node

# Under construction.  These ought to be methods, but am having Python assertion
# failures in refcounting when trying to hook them up using lambda expressions; grrr...
import traceback
def marshalOnFocus(event):
    try: recorder.onFocus(event)
    except: traceback.print_exc()

def marshalOnSelect(event):
    try: recorder.onSelect(event)
    except: traceback.print_exc()

def marshalOnMouseButton(event):
    try: recorder.onMouseButton(event)
    except: traceback.print_exc()

def marshalOnKeyPress(event):
    try: recorder.onKeyPress(event)
    except: traceback.print_exc()

def marshalOnWindowCreate(event):
    try: recorder.onWindowCreate(event)
    except: traceback.print_exc()

recorder = EventRecorder()
recorderGUI = RecorderGUI()
#recorder.writer.debug = True
#recorder.writer.debugVariables = True


# vim: sw=4 ts=4 sts=4 et ai

Anon7 - 2021