#!/usr/bin/python
+#shuber, 2008-06-04
+__author__ = "shuber"
-import select
+
+import gobject
+import gtk
+import os
+import pango
+import pty
+import re
import string
import sys
-import thread
-import threading
import time
-import os
-import pty
-import Queue
-
-
-class DbgTerminal:
+import threading
+import vte
- #Reply cache from debugger
- dbgreplyqueue = Queue.Queue()
+import ClientIOTerminal
- def __init__ (self, binary, gdbReadCallback, childReadCallback):
- #Set some members
- self.gdbReadCallback = gdbReadCallback
- self.childReadCallback = childReadCallback
- self.binary = binary
- self.stopped = False
+class DbgTerminal (vte.Terminal):
- #Connect to sub-process
- self.__connect()
+ def __init__(self, clientCmd):
- def __connect( self ):
+ vte.Terminal.__init__(self)
- #This function handles readings from the debugger
- def gdbCb(str):
- self.gdbReadCallback(str)
- self.dbgreplyqueue.put(str)
+ #Set members
+ self.childpid = None
+ self.history = [""]
+ self.isactive = True
+ self.lastc, self.lastr = 0,0
+ self.gotActiveCallback = []
+ self.gotInactiveCallback = []
+ self.activityChanged = None
+ #Start debugger
+ self.clientCmd = clientCmd
#Open pseudo-terminal where to-be-debugged process reads/writes to
- self.ptymaster, self.ptyslave = pty.openpty()
- self.childout = os.fdopen(self.ptymaster, "r", 0)
- self.childin = os.fdopen(self.ptymaster, "w", 0)
+ self.client_ptymaster, self.client_ptyslave = pty.openpty()
- #Call gdb and get in- and out-streams to/from gdb
- cmd = self.getCommand(self.binary)
- self.gdbin, self.gdbout = os.popen4( cmd, bufsize=0)
+ #Set up terminal window and initialize debugger
+ self.connect("cursor-moved", self.contents_changed)
+ self.connect("child-exited", quitHandler)
+ gobject.timeout_add(50, self.checkActivityChanged)
- #Set up a reading thread to gdb output
- self.gdbReadThread = self.ReadThread(self.gdbout, gdbCb)
- self.gdbReadThread.start()
+ #font description
+ fontdesc = pango.FontDescription("monospace 9")
+ self.set_font(fontdesc)
- #Set up a reading thread to childs output
- self.childReadThread = self.ReadThread(self.childout, self.childReadCallback)
- self.childReadThread.start()
- #Set up tty gdb-childs process
- self.sendSetTTY(os.ttyname(self.ptyslave))
+ def initialize(self):
+ self.childpid = self.fork_command( self.getCommand(), self.getArgv())
+ self.waitForPrompt(0)
+ self.setPty(self.client_ptyslave)
-
+ def stopDbg(self):
- def getDbgReply(self):
- raise NotImplementedError()
+ if self.childpid != None:
+ #9=KILL, 15=TERM
+ os.kill(self.childpid, 15);
+ self.childpid = None
+ def getClientExecuteable(self):
+ return string.split(self.clientCmd)[0]
- def iseof( self ):
- """Check if terminal is closed already"""
- return self.gdbReadThread.eventFin.isSet()
- def stop( self ):
+ def toAbsPath(self, path):
+ """convert path to an absolute path relative to the client
+ executable we debug."""
+
+ #Current working dir
+ pwd = os.getcwd() + "/"
- if not self.stopped:
+ #executeable path
+ client = self.getClientExecuteable()
+ client = relToAbsPath(pwd, client)
- self.stopped = True
+ return relToAbsPath(client, path)
- #Finish the reading-thread
- self.gdbReadThread.fin = True
- self.childReadThread.fin = True
- self.gdbReadThread.eventFin.wait(1)
- self.childReadThread.eventFin.wait(1)
+ def checkActivityChanged(self):
- def getCommand( self, binary ):
- """Get the command to execute"""
- raise NotImplementedError()
+ try:
- def sendBreak(self, file, lineno):
- raise NotImplementedError()
+ #There was activity
+ if self.activityChanged != None:
- def sendContinue(self):
- raise NotImplementedError()
-
- def sendRun(self):
- raise NotImplementedError()
+ res = self.activityChanged
+ self.activityChanged = None
- def sendInspectVar(self, var):
- raise NotImplementedError()
+ status, param = res
+ if self.isActive():
+ print "got active: ", res
+ for cb in self.gotActiveCallback:
+ cb(status, param)
+ else:
+ print "got inactive: ", res
+ for cb in self.gotInactiveCallback:
+ cb(status, param)
+ except Exception, e:
+ print e
- def sendInspectExpr(self, expr):
- raise NotImplementedError()
+ return True
- def sendSetTTY(self, ttyname):
- raise NotImplementedError()
-
- def sendQuit(self):
- raise NotImplementedError()
- class ReadThread (threading.Thread):
- """Thread which reads from sub-process output"""
+ def contents_changed(self, term):
+ assert( self.getHistoryLen()>0 )
- def __init__( self, stream, callback, sleep=0.1):
- self.stream = stream
- self.fin = False
- self.callback = callback
- self.sleep = sleep
- self.eventFin = threading.Event()
- threading.Thread.__init__(self)
+ c,r = term.get_cursor_position()
+ text = self.get_text_range(self.lastr,0,r,c,lambda *w:True)
+ self.lastc, self.lastr = c,r
- def run(self):
+ #Remove annoying \n at the end
+ assert(text[-1] == "\n")
+ text = text[:-1]
- try:
- while True:
- #Wait until data is available
- rlist, wlist, xlist = select.select([self.stream], [], [], self.sleep)
+ #Get the lines and remove empty lines
+ lines = string.split(text, "\n")
- #If we should finish, finish
- if self.fin:
- break
+ #Remove the incomplete line
+ len = max(0,self.getHistoryLen())
+ self.history[-1] = lines[0]
+ self.history += lines[1:]
- #Got new data
- if len(rlist) > 0:
- fd = rlist[0]
- str = fd.read(1)
- #Call callbacks
- self.callback(str)
- except:
- pass
- #Set the finished event
- self.eventFin.set()
+ #Check if activity status has been changed
+ for i in range(len, self.getHistoryLen()):
+ line = self.history[i]
+
+ res = self.testForInactivity(i)
+ if res != None:
+ while self.activityChanged != None:
+ print "wait for pending activity"
+ gtk.main_iteration()
+ self.setActive(False)
+ self.activityChanged = res
+ res = self.testForActivity(i)
+ if res != None:
+ while self.activityChanged != None:
+ print "wait for pending activity"
+ gtk.main_iteration()
+ self.setActive(True)
+ self.activityChanged = res
-class GdbTerminal (DbgTerminal):
- gdbreply = ""
- def getCommand( self, binary ):
- return "gdb --fullname %s" % (binary,)
+ def waitForNewline(self):
+ l = self.getHistoryLen()
+ while not self.getHistoryLen() > l:
+ gtk.main_iteration()
- def sendBreak(self, file, lineno):
- self.gdbin.write("break %s:%d\n" % (file, lineno))
+ def getHistoryLen(self):
+ return len(self.history)
- def sendContinue(self):
- self.gdbin.write("cont\n")
+ def waitForRx(self, pat, start):
- def sendRun(self):
- self.gdbin.write("run\n")
+ rx = re.compile(pat)
+ curr = start
+ while True:
+ assert( curr>=start )
+ for no in range(curr, self.getHistoryLen()):
+ line = self.history[no]
+ if rx.search(line):
+ return no, line
- def sendInspectVar(self, var):
- self.sendInspectExpr(var)
+ #Do not forget the last line
+ curr = max(start,self.getHistoryLen()-1)
+ lr, lc = self.lastr, self.lastc
- def sendInspectExpr(self, expr):
- self.gdbin.write("print %s\n" % (expr,))
+ while (self.lastr, self.lastc) == (lr,lc):
+ gtk.main_iteration()
- def sendSetTTY(self, ttyname):
- self.gdbin.write("set inferior-tty %s\n" % (ttyname,))
- def sendQuit(self):
- self.gdbin.write("quit\n")
- DbgTerminal.stop(self)
+ def getCommand(self):
+ return self.getArgv()[0];
- def getDbgReply(self, timeout=None):
+ def getArgv(self):
+ raise NotImplementedError()
- while True:
- splits = self.gdbreply.split("\n")
-
- #Need more data: If there is a single (gdb) entry, then
- #there are at least two splits
- if len(splits) <= 1:
- try:
- self.gdbreply += self.dbgreplyqueue.get(True, timeout)
- except Queue.Empty:
- return None
- #Yeah there is data!
- else:
- self.gdbreply = string.join(splits[1:], "(gdb)")
- return string.strip(splits[0])
-
- def flushDbgReply(self):
+ def setPty(self, pty):
+ raise NotImplementedError()
- try:
- self.gdbreply = ""
- #Remove all elements from queue
- while True:
- self.dbgreplyqueue.get(False)
- except Queue.Empty:
- pass
+ def setRun(self):
+ raise NotImplementedError()
-if __name__ == "__main__":
+ def setContinue(self):
+ raise NotImplementedError()
- def tostdout(str):
- sys.stdout.write(str)
- sys.stdout.flush()
+ def setStepover(self):
+ raise NotImplementedError()
- try:
+ def setStepin(self):
+ raise NotImplementedError()
- term = GdbTerminal( "./main", tostdout, tostdout)
- term.sendBreak("main.cpp", 13)
- term.sendBreak("main.cpp", 14)
- term.sendRun()
- term.childin.write("1\n");
- term.childin.write("2\n");
+ def setQuit(self):
+ raise NotImplementedError()
- time.sleep(0.2)
- term.flushDbgReply()
- term.sendInspectVar("a+b")
+ def setBreakpoint(self, file, lineno, condition=False):
+ raise NotImplementedError()
- term.sendContinue()
- term.sendInspectVar("a+b")
- term.sendContinue()
+ def delBreakpoint(self, breakpoint):
+ raise NotImplementedError()
+ def getExpression(self, expr):
+ raise NotImplementedError()
- time.sleep(1)
+ def listCodeSnippet(self):
+ raise NotImplementedError()
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
- print "reply >>>", term.getDbgReply(1)
+ def waitForPrompt(self, his):
+ raise NotImplementedError()
+ def testForActivity(self, his):
+ raise NotImplementedError()
- while not term.iseof():
+ def testForInactivity(self, his):
+ raise NotImplementedError()
- cmd = sys.stdin.readline()
+ def setActive(self, isactive):
+ self.isactive = isactive
- if term.iseof():
- break
+ def isActive(self):
+ return self.isactive
- if cmd == "quit\n":
- term.sendQuit()
- else:
- term.gdbin.write(cmd)
+
- except Exception, e:
- print e
+def quitHandler(*w):
+ try:
+ gtk.main_quit()
except:
pass
- print "stopping..."
- term.stop()
+def updateVim():
+ os.system('gvim --servername pygdb --remote-send ":GDBLoadConfig<CR>"')
+
+
+def relToAbsPath(absfile, relfile):
+ """When an absfile is given and a relfile is given by
+ relative paths relative to absfile, determine the abs
+ path of relfile"""
+
+ #Get directories except for "." parts
+ relsplit = filter(lambda x: x!=".", string.split(relfile, os.sep))
+ #Get the directories of absfile withouth the trailing filename
+ abssplit = string.split(absfile, os.sep)[:-1]
+
+ #Determine number of ".." and remove them
+ up=0
+ while relsplit[0] == "..":
+ up += 1
+ del relsplit[0]
+ del abssplit[-1]
+
+ return string.join(abssplit + relsplit, os.sep)
+
+
+class DbgWindow (gtk.Window):
+
+ clientIOWnd = None
+
+
+ def __init__(self, terminal):
+
+ #Set up some members
+ self.terminal = terminal
+
+ #Set up GTK stuff
+ gtk.Window.__init__(self)
+ self.connect("destroy", quitHandler)
+
+ #Set title and add terminal
+ self.set_title("Debugger I/O")
+ self.terminal.history = []
+ self.terminal.history_length = 5
+ self.add(self.terminal)
+
+ #Show the window
+ self.show_all()
+
+ def toggleClientIOWindow(self):
+ if not self.clientIOWnd:
+ self.clientIOWnd = ClientIOTerminal.ClientIOWindow(self, \
+ self.terminal.client_ptymaster)
+ else:
+ self.clientIOWnd.destroy()
+ self.clientIOWnd = None
+
+ def isClientIOWindowExisting(self):
+ return self.clientIOWnd != None
+
-