source: trunk/debathena/debathena/console/debathena-console @ 25377

Revision 25377, 7.9 KB checked in by jdreed, 13 years ago (diff)
In console: * Actually support passing filenames on the command line * Add a timestamp to each line (c.f. /etc/athena/console)
  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2"""Debathena Console viewer
3Written by quentin@mit.edu
4
5Based on pygtk textview example code
6"""
7
8import os
9import sys
10import time
11# We don't get argparse until 2.7 which is natty-only
12from optparse import OptionParser 
13
14import gobject
15import gtk
16import gconf
17import dbus, dbus.service
18import _dbus_bindings as dbus_bindings
19import dbus.mainloop.glib
20import wnck
21
22NAME = 'Console'
23
24# Supported gconf options:
25# /apps/debathena-console/blink
26# /apps/debathena-console/auto_hide
27# /apps/debathena-console/start_visible
28
29DBUS_IFACE="edu.mit.debathena.console"
30DBUS_BUS="edu.mit.debathena.console"
31DBUS_OBJECT="/edu/mit/debathena/console"
32
33class ConsoleDBus(dbus.service.Object):
34    def __init__(self, show_me, hide_me):
35        self.show_me = show_me
36        self.hide_me = hide_me
37        session_bus = dbus.SessionBus()
38        bus_name = dbus.service.BusName(DBUS_BUS, bus=session_bus)
39        object_path = DBUS_OBJECT
40        dbus.service.Object.__init__(self, bus_name, object_path)
41
42    @dbus.service.method(DBUS_IFACE,
43                         in_signature="b")
44    def set_visibility(self, visible):
45        if visible:
46            self.show_me(True)
47        else:
48            self.hide_me()
49
50class ConsoleViewer(gtk.Window):
51    def __init__(self, fds=[]):
52        # Create the toplevel window
53        gtk.Window.__init__(self)
54
55        self.connect('focus-in-event', self.on_focus)
56
57        self.set_focus_on_map(False)
58
59        client = gconf.client_get_default()
60        client.add_dir('/apps/debathena-console',
61                gconf.CLIENT_PRELOAD_ONELEVEL)
62        client.notify_add('/apps/debathena-console/blink',
63                self.new_blink)
64        client.notify_add('/apps/debathena-console/auto_hide',
65                self.new_auto_hide)
66        self.auto_hide_id = False
67        self.new_blink(client)
68        self.new_auto_hide(client)
69
70        self.systray = gtk.StatusIcon()
71        self.systray.set_from_stock("gtk-info")
72        self.systray.connect("activate", self.on_tray_activate, "activate")
73#        self.systray.connect("popup-menu", self.on_tray_popupmenu, self.popupmenu)
74        self.systray.set_tooltip(NAME)
75
76
77        self.set_title(NAME)
78        self.set_default_size(640, 320)
79        self.set_border_width(0)
80
81        vbox = gtk.VBox(False, 0)
82        self.add(vbox)
83
84        self.view = gtk.TextView();
85        buffer = self.view.get_buffer()
86
87        self.view.set_editable(False)
88        self.view.set_cursor_visible(False)
89        # See http://www.pygtk.org/docs/pygtk/gtk-constants.html#gtk-wrap-mode-constants
90        self.view.set_wrap_mode(gtk.WRAP_CHAR)
91
92        sw = gtk.ScrolledWindow()
93        sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
94        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
95
96        vbox.pack_start(sw)
97
98        sw.add(self.view)
99
100        bbox = gtk.HButtonBox()
101        bbox.set_border_width(5)
102        bbox.set_layout(gtk.BUTTONBOX_END)
103        bbox.set_spacing(40)
104
105        vbox.pack_start(bbox, expand=False)
106
107        def hide_window(*args):
108            gobject.idle_add(self.hide_me)
109            return True
110
111        button = gtk.Button("Hide")
112        button.connect("clicked", hide_window)
113        bbox.add(button)
114        self.connect('delete-event', hide_window)
115
116        self.create_tags(buffer)
117        self.insert_text(buffer)
118
119        # NB: we do show_all on the vbox so the widgets are ready
120        # when we present_with_time() inside show_me()
121        vbox.show_all()
122
123        if (client.get_bool("/apps/debathena-console/start_visible")):
124            self.show_me()
125
126        self.start_listening(buffer, self.view, fds)
127
128        self.dbus_service = ConsoleDBus(self.show_me, self.hide_me)
129        dbus.SessionBus().request_name(DBUS_BUS, dbus_bindings.NAME_FLAG_DO_NOT_QUEUE)
130
131    def new_blink(self, client, *a):
132        self.blink = client.get_bool("/apps/debathena-console/blink")
133
134    def new_auto_hide(self, client, *a):
135        self.auto_hide = client.get_int("/apps/debathena-console/auto_hide")
136        if self.auto_hide == 0 and self.auto_hide_id:
137            gobject.source_remove(self.auto_hide_id)
138            self.auto_hide_id = False
139
140    def on_tray_activate(self, widget, data=None):
141        if self.is_active():
142            self.hide_me()
143        else:
144            self.show_me(True)
145
146    def show_me(self, focus=False):
147        screen = wnck.screen_get_default()
148
149        active = screen.get_active_window()
150        if focus:
151            self.show_all()
152            self.window.focus()
153        elif not self.has_toplevel_focus():
154            # we need to be shown, but we're in the back
155            self.present_with_time(int(time.time()))
156            # restore the active window
157            while gtk.events_pending():
158                gtk.main_iteration()
159            if active:
160                active.activate(int(time.time()))
161
162        if self.auto_hide > 0:
163            if self.auto_hide_id:
164                gobject.source_remove(self.auto_hide_id)
165            self.auto_hide_id = gobject.timeout_add(self.auto_hide * 1000, self.hide_me)
166
167    def hide_me(self):
168        self.window.lower()
169        self.hide()
170        if self.auto_hide_id:
171            gobject.source_remove(self.auto_hide_id)
172            self.auto_hide_id = False
173        return False
174
175    def on_focus(self, widget, event):
176        self.systray.set_blinking(False)
177        return False
178
179    def create_tags(self, text_buffer):
180        '''
181        Create the tags we use for text (stdout and stderr)
182        '''
183
184        import pango
185
186        # See http://www.pygtk.org/docs/pygtk/class-gtktexttag.html
187        default_args = {'family': 'monospace',
188                        'size_points': 12}
189
190        text_buffer.create_tag("stdout", **default_args)
191        text_buffer.create_tag("stderr", foreground="red", **default_args)
192        text_buffer.create_tag("xconsole", weight=pango.WEIGHT_BOLD, **default_args)
193
194    def insert_text(self, text_buffer):
195        '''
196        Insert some sample text demonstrating the tags
197        '''
198        # get start of buffer; each insertion will revalidate the
199        # iterator to point to just after the inserted text.
200        iter = text_buffer.get_end_iter()
201
202    def start_listening(self, text_buffer, text_view, fds):
203        '''
204        Sets up a gobject event listener that adds text to the textview whenever
205        stdin has something to read
206        '''
207        def got_data(source, condition, tag):
208            data = source.read()
209            # act like tee, so the logs still end up in .xsession-errors
210            print data,
211            text_buffer.insert_with_tags_by_name(text_buffer.get_end_iter(),
212                                                 time.strftime("%H:%M ", time.localtime()) + data, tag)
213
214            text_view.scroll_to_mark(text_buffer.create_mark(None, text_buffer.get_end_iter(), False), 0, False)
215
216            if self.blink and not self.has_toplevel_focus():
217                self.systray.set_blinking(True)
218            elif not self.has_toplevel_focus():
219                self.show_me()
220
221            if source.closed:
222                return False # we got an eof
223            else:
224                return True # causes callback to remain in existence
225        import fcntl, os
226        for i in range(len(fds)):
227            f = os.fdopen(fds[i][1])
228            fcntl.fcntl(fds[i][1], fcntl.F_SETFL, os.O_NONBLOCK)
229            gobject.io_add_watch(f, gobject.IO_IN, got_data, fds[i][0])
230
231def main():
232    parser = OptionParser()
233    parser.add_option("-n","--names", action="store_true", dest="names",
234                      default=False, help="Use named files instead of FDs")
235    (opts, args) = parser.parse_args()
236    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
237    if len(args) >= 1:
238        ConsoleViewer(fds=[(type, opts.names and os.open(fname, os.O_RDONLY | os.O_NONBLOCK) or int(fname)) for x in args
239                           for type, fname in [x.split(':')]])
240    else:
241        ConsoleViewer(fds=[("stdout", 0)])
242    gtk.main()
243
244if __name__ == '__main__':
245    main()
Note: See TracBrowser for help on using the repository browser.