From d0d81fc1527c0f2a44c43cab35a52b2b136692fd Mon Sep 17 00:00:00 2001 From: Thomas Hurst Date: Thu, 7 Aug 2008 05:32:28 +0100 Subject: [PATCH] Introduce a debug service; spawn a Python REPL on a random local TCP port. Supports basic telnet commands, ^C and ^D work, etc. --- terminator | 13 ++- terminatorlib/debugserver.py | 153 +++++++++++++++++++++++++++++++++++ terminatorlib/terminator.py | 13 ++- 3 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 terminatorlib/debugserver.py diff --git a/terminator b/terminator index 01fa5d94..c53346ef 100755 --- a/terminator +++ b/terminator @@ -143,9 +143,20 @@ See the following bug report for more details: except IOError: pass + import terminatorlib.debugserver as debugserver + (serverthread, server) = debugserver.spawn() + import threading + + gtk.gdk.threads_init() + dbg ('profile_cb: settled on profile: "%s"' % options.profile) term = Terminator (options.profile, command, options.fullscreen, options.maximise, options.borderless, options.no_gconf) - gtk.main () + gtk.main() + +# guithread = threading.Thread(target=gtk.main, name="Main GUI thread") +# guithread.start() +# guithread.join() + server.socket.close() diff --git a/terminatorlib/debugserver.py b/terminatorlib/debugserver.py new file mode 100644 index 00000000..17ac9ef5 --- /dev/null +++ b/terminatorlib/debugserver.py @@ -0,0 +1,153 @@ +#!/usr/local/bin/python +# +# Copyright (c) 2008, Thomas Hurst +# +# Use of this file is unrestricted provided this notice is retained. +# If you use it, it'd be nice if you dropped me a note. Also beer. + +from terminatorlib.config import dbg, err +from terminatorlib.version import APP_NAME, APP_VERSION + +import socket +import threading +import SocketServer +import code +import sys +import readline +import rlcompleter +import re + +class PythonConsoleServer(SocketServer.BaseRequestHandler): + def setup(self): + dbg('debugserver: connect from %s' % str(self.client_address)) + self.console = TerminatorConsole() + + def handle(self): + dbg("debugserver: handling") + try: + self.socketio = self.request.makefile() + sys.stdout = self.socketio + sys.stdin = self.socketio +# sys.stderr = self.socketio + self.console.run(self) + finally: + sys.stdout = sys.__stdout__ + sys.stdin = sys.__stdin__ +# sys.stderr = sys.__stderr__ + self.socketio.close() + dbg("debugserver: done handling") + + def verify_request(self, request, client_address): + return True + + def finish(self): + dbg('debugserver: disconnect from %s' % str(self.client_address)) + +BareLF = re.compile('([^\015])\015') +DoDont = re.compile('(^|[^\377])\377[\375\376](.)') +WillWont = re.compile('(^|[^\377])\377[\373\374](.)') +AreYouThere = re.compile('(^|[^\377])\377\366') +IpTelnet = re.compile('(^|[^\377])\377\364') +OtherTelnet = re.compile('(^|[^\377])\377[^\377]') + +# See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/205335 for telnet bits +# Python doesn't make this an especially neat conversion :( +class TerminatorConsole(code.InteractiveConsole): + def parse_telnet(self, data): + odata = data + data = re.sub(BareLF, '\\1', data) + data = data.replace('\015\000', '') + data = data.replace('\000', '') + + bits = re.findall(DoDont, data) + dbg("bits = %s" % repr(bits)) + if bits: + data = re.sub(DoDont, '\\1', data) + dbg("telnet: DO/DON'T answer") + # answer DO and DON'T with WON'T + for bit in bits: + self.write("\377\374" + bit[1]) + + bits = re.findall(WillWont, data) + if bits: + data = re.sub(WillWont, '\\1', data) + dbg("telnet: WILL/WON'T answer") + for bit in bits: + # answer WILLs and WON'T with DON'Ts + self.write("\377\376" + bit[1]) + + bits = re.findall(AreYouThere, data) + if bits: + dbg("telnet: am I there answer") + data = re.sub(AreYouThere, '\\1', data) + for bit in bits: + self.write("Yes, I'm still here, I think.\n") + + bits = re.findall(IpTelnet, data) # IP (Interrupt Process) + for bit in bits: + dbg("debugserver: Ctrl-C detected") + raise KeyboardInterrupt + + data = re.sub(IpTelnet, '\\1', data) # ignore IP Telnet codes + data = re.sub(OtherTelnet, '\\1', data) # and any other Telnet codes + data = data.replace('\377\377', '\377') # and handle escapes + + if data != odata: + dbg("debugserver: Replaced %s with %s" % (repr(odata), repr(data))) + + return data + + + def raw_input(self, prompt = None): + dbg("debugserver: raw_input prompt = %s" % repr(prompt)) + if prompt: + self.write(prompt) + + buf = '' + compstate = 0 + while True: + data = self.server.socketio.read(1) # should get the client sending unbuffered for tab complete? + dbg('raw_input: char=%s' % repr(data)) + if data == '\n' or data == '\006': + buf = self.parse_telnet(buf + data).lstrip() + if buf != '': + return buf + elif data == '\004' or data == '': # ^D + raise EOFError + else: + buf += data + + def write(self, data): + dbg("debugserver: write %s" % repr(data)) + self.server.socketio.write(data) + self.server.socketio.flush() + + def run(self, server): + self.server = server + + self.write("Welcome to the %s-%s debug server, have a nice stay\n" % (APP_NAME, APP_VERSION)) + self.interact() + try: + self.write("Time to go. Bye!\n") + except: + pass + + +def server(): + tcpserver = SocketServer.TCPServer(('127.0.0.1', 0), PythonConsoleServer) + print "Serving on %s" % str(tcpserver.server_address) + tcpserver.serve_forever() + +def spawn(): +# server() + # tcpserver = SocketServer.ThreadingTCPServer(('', 0), PythonConsoleServer) + tcpserver = SocketServer.TCPServer(('', 0), PythonConsoleServer) + print("debugserver: listening on %s" % str(tcpserver.server_address)) + dbg("debugserver: about to spawn thread") + debugserver = threading.Thread(target=tcpserver.serve_forever, name="DebugServer") + debugserver.setDaemon(True) + dbg("debugserver: about to start thread") + debugserver.start() + dbg("debugserver: started thread") + return(debugserver, tcpserver) + diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index 90ab68be..549f4433 100755 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -200,9 +200,9 @@ class Terminator: self.window.fullscreen () def on_window_state_changed (self, window, event): - state = event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN - self._fullscreen = bool (state) - + self._fullscreen = bool (event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN) + self._maximised = bool (event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED) + dbg("window state changed: fullscreen: %s, maximised: %s" % (self._fullscreen, self._maximised)) return (False) def on_delete_event (self, window, event, data=None): @@ -493,7 +493,12 @@ class Terminator: notebooktablabel = TerminatorNotebookTabLabel(notebooklabel, notebook, self) notebook.set_tab_label(child, notebooktablabel) notebook.set_tab_label_packing(child, True, True, gtk.PACK_START) - self.window.resize(self.window.allocation.width, min(self.window.allocation.height + notebooktablabel.height_request(), gtk.gdk.screen_height())) + + wal = self.window.allocation + if not (self._maximised or self._fullscreen): + self.window.resize(wal.width, + min(wal.height + notebooktablabel.height_request(), gtk.gdk.screen_height())) + notebook.show() elif isinstance(parent, gtk.Notebook): notebook = parent