# -*- Python -*-
# $Id: gtk2manhole.py,v 1.9 2003/09/07 19:58:09 acapnotic Exp $
# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Manhole client with a GTK v2.x front-end.
"""
# Note: Because GTK 2.x Python bindings are only available for Python 2.2,
# this code may use Python 2.2-isms.
__version__ = '$Revision: 1.9 $'[11:-2]
from twisted import copyright
from twisted.internet import reactor
from twisted.python import components, failure, log, util
from twisted.spread import pb
from twisted.spread.ui import gtk2util
from twisted.manhole.service import IManholeClient
# The pygtk.require for version 2.0 has already been done by the reactor.
import gtk
import code, types, inspect
# TODO:
# Make wrap-mode a run-time option.
# Command history.
# Explorer.
# Code doesn't cleanly handle opening a second connection. Fix that.
# Make some acknowledgement of when a command has completed, even if
# it has no return value so it doesn't print anything to the console.
class OfflineError(Exception):
pass
class ManholeWindow(components.Componentized, gtk2util.GladeKeeper):
gladefile = util.sibpath(__file__, "gtk2manhole.glade")
_widgets = ('input','output','manholeWindow')
def __init__(self):
self.defaults = {}
gtk2util.GladeKeeper.__init__(self)
components.Componentized.__init__(self)
self.input = ConsoleInput(self._input)
self.input.toplevel = self
self.output = ConsoleOutput(self._output)
# Ugh. GladeKeeper actually isn't so good for composite objects.
# I want this connected to the ConsoleInput's handler, not something
# on this class.
self._input.connect("key_press_event", self.input._on_key_press_event)
def setDefaults(self, defaults):
self.defaults = defaults
def login(self):
client = self.getComponent(IManholeClient)
d = gtk2util.login(client, **self.defaults)
d.addCallback(self._cbLogin)
d.addCallback(client._cbLogin)
d.addErrback(self._ebLogin)
def _cbDisconnected(self, perspective):
self.output.append("%s went away. :(\n" % (perspective,), "local")
self._manholeWindow.set_title("Manhole")
def _cbLogin(self, perspective):
peer = perspective.broker.transport.getPeer()
self.output.append("Connected to %s\n" % (peer,), "local")
perspective.notifyOnDisconnect(self._cbDisconnected)
self._manholeWindow.set_title("Manhole - %s:%s" % (peer[1], peer[2]))
return perspective
def _ebLogin(self, reason):
self.output.append("Login FAILED %s\n" % (reason.value,), "exception")
def _on_aboutMenuItem_activate(self, widget, *unused):
import sys
from os import path
self.output.append("""\
a Twisted Manhole client
Versions:
%(twistedVer)s
Python %(pythonVer)s on %(platform)s
GTK %(gtkVer)s / PyGTK %(pygtkVer)s
%(module)s %(modVer)s
http://twistedmatrix.com/
""" % {'twistedVer': copyright.longversion,
'pythonVer': sys.version.replace('\n', '\n '),
'platform': sys.platform,
'gtkVer': ".".join(map(str, gtk.gtk_version)),
'pygtkVer': ".".join(map(str, gtk.pygtk_version)),
'module': path.basename(__file__),
'modVer': __version__,
}, "local")
def _on_openMenuItem_activate(self, widget, userdata=None):
self.login()
def _on_manholeWindow_delete_event(self, widget, *unused):
reactor.stop()
def _on_quitMenuItem_activate(self, widget, *unused):
reactor.stop()
def on_reload_self_activate(self, *unused):
from twisted.python import rebuild
rebuild.rebuild(inspect.getmodule(self.__class__))
tagdefs = {
'default': {"family": "monospace"},
# These are message types we get from the server.
'stdout': {"foreground": "black"},
'stderr': {"foreground": "#AA8000"},
'result': {"foreground": "blue"},
'exception': {"foreground": "red"},
# Messages generate locally.
'local': {"foreground": "#008000"},
'log': {"foreground": "#000080"},
'command': {"foreground": "#666666"},
}
# TODO: Factor Python console stuff back out to pywidgets.
class ConsoleOutput:
_willScroll = None
def __init__(self, textView):
self.textView = textView
self.buffer = textView.get_buffer()
# TODO: Make this a singleton tag table.
for name, props in tagdefs.iteritems():
tag = self.buffer.create_tag(name)
# This can be done in the constructor in newer pygtk (post 1.99.14)
for k, v in props.iteritems():
tag.set_property(k, v)
self.buffer.tag_table.lookup("default").set_priority(0)
self._captureLocalLog()
def _captureLocalLog(self):
return log.startLogging(_Notafile(self, "log"), setStdout=False)
def append(self, text, kind=None):
# XXX: It seems weird to have to do this thing with always applying
# a 'default' tag. Can't we change the fundamental look instead?
tags = ["default"]
if kind is not None:
tags.append(kind)
self.buffer.insert_with_tags_by_name(self.buffer.get_end_iter(),
text, *tags)
# Silly things, the TextView needs to update itself before it knows
# where the bottom is.
if self._willScroll is None:
self._willScroll = gtk.idle_add(self._scrollDown)
def _scrollDown(self, *unused):
self.textView.scroll_to_iter(self.buffer.get_end_iter(), 0,
True, 1.0, 1.0)
self._willScroll = None
return False
class ConsoleInput:
toplevel = None
def __init__(self, textView):
self.textView=textView
def _on_key_press_event(self, entry, event):
stopSignal = False
if event.keyval == gtk.keysyms.Return:
buffer = self.textView.get_buffer()
iter1, iter2 = buffer.get_bounds()
text = buffer.get_text(iter1, iter2, False)
# Figure out if that Return meant "next line" or "execute."
try:
c = code.compile_command(text)
except SyntaxError, e:
# This could conceivably piss you off if the client's python
# doesn't accept keywords that are known to the manhole's
# python.
point = buffer.get_iter_at_line_offset(e.lineno, e.offset)
buffer.place(point)
# TODO: Componentize!
self.toplevel.output.append(str(e), "exception")
except (OverflowError, ValueError), e:
self.toplevel.output.append(str(e), "exception")
else:
if c is not None:
self.sendMessage()
# Don't insert Return as a newline in the buffer.
entry.emit_stop_by_name("key_press_event")
self.clear()
else:
# not a complete code block
pass
return False
def clear(self):
buffer = self.textView.get_buffer()
buffer.delete(*buffer.get_bounds())
def sendMessage(self):
buffer = self.textView.get_buffer()
iter1, iter2 = buffer.get_bounds()
text = buffer.get_text(iter1, iter2, False)
# TODO: Componentize better!
try:
return self.toplevel.getComponent(IManholeClient).do(text)
except OfflineError:
self.toplevel.output.append("Not connected, command not sent.\n",
"exception")
class _Notafile:
"""Curry to make failure.printTraceback work with the output widget."""
def __init__(self, output, kind):
self.output = output
self.kind = kind
def write(self, txt):
self.output.append(txt, self.kind)
def flush(self):
pass
class ManholeClient(components.Adapter, pb.Referenceable):
__implements__ = (IManholeClient,)
capabilities = {
# "Explorer": 'Set',
"Failure": 'Set'
}
def _cbLogin(self, perspective):
self.perspective = perspective
perspective.notifyOnDisconnect(self._cbDisconnected)
return perspective
def remote_console(self, messages):
for kind, content in messages:
if isinstance(content, types.StringTypes):
self.original.output.append(content, kind)
elif (kind == "exception") and isinstance(content, failure.Failure):
content.printTraceback(_Notafile(self.original.output,
"exception"))
else:
self.original.output.append(str(content), kind)
def remote_receiveExplorer(self, xplorer):
pass
def remote_listCapabilities(self):
return self.capabilities
def _cbDisconnected(self, perspective):
self.perspective = None
def do(self, text):
if self.perspective is None:
raise OfflineError
return self.perspective.callRemote("do", text)
components.registerAdapter(ManholeClient, ManholeWindow, IManholeClient)
syntax highlighted by Code2HTML, v. 0.9.1