Merge lp:~jamesodhunt/upstart/upstart-monitor into lp:upstart

Proposed by James Hunt
Status: Merged
Merged at revision: 1457
Proposed branch: lp:~jamesodhunt/upstart/upstart-monitor
Merge into: lp:upstart
Diff against target: 898 lines (+827/-4)
8 files modified
ChangeLog (+20/-2)
configure.ac (+1/-1)
po/POTFILES.in (+2/-0)
scripts/Makefile.am (+4/-1)
scripts/data/Makefile.am (+24/-0)
scripts/data/upstart-monitor.desktop (+8/-0)
scripts/man/upstart-monitor.8 (+81/-0)
scripts/upstart-monitor.py (+687/-0)
To merge this branch: bzr merge lp:~jamesodhunt/upstart/upstart-monitor
Reviewer Review Type Date Requested Status
Dimitri John Ledkov Approve
Review via email: mp+153839@code.launchpad.net

Description of the change

Initial implementation of upstart-monitor event gui+cli tool.

See: http://ifdeflinux.blogspot.co.uk/2013/03/a-basic-upstart-events-gui-and-cli-tool.html

To post a comment you must log in.
Revision history for this message
Steve Langasek (vorlon) wrote :

I notice that the UI is hand-coded in python. Are you concerned about the maintainability of this, vs. using glade and gtkbuilder?

I see that the script is gettext-enabled, but I don't see any updates to po/ that will cause the script to be included in the translations.

Revision history for this message
Dimitri John Ledkov (xnox) wrote :

On 20 March 2013 21:52, Steve Langasek <email address hidden> wrote:
> I notice that the UI is hand-coded in python. Are you concerned about the maintainability of this, vs. using glade and gtkbuilder?
>

Well, If one uses glade, one should be prepared to keep local builds
and send patches to upstream when it crashes on loading ubiquity.ui
for example =)

I know Colin, mostly edits gtkbuilder xml with vim. Which is good a well.

I guess long term, it might be more appropriate to do it in Qt5, once
that's on the default desktop and the iso. At least Qt UI definitions
& GUI editor do not crash.

Anyway the UI code here is quite short and easy to understand /
extend. And mostly deal with list-store, which is a pain to code up
right in Glade. One nitpick would be to use GtkGrid instead of GtkBox.
As the Box is deprecated and you will notice weird sizing problems
(just look for the tonne of bugs mpt filed about dialogs/windows being
too tall for their width).

Regards,

Dmitrijs.

1452. By James Hunt

* po/POTFILES.in: Added upstart-monitor.py.
* scripts/upstart-monitor.py: UpstartEventsGui(): Removed class
  attributes and added explicit instance ones in __init__().

Revision history for this message
James Hunt (jamesodhunt) wrote :

@vorlon - I have been in two minds about even putting a gui tool into the upstream package; we already have a few scripts which set some sort of precedent, but they are all cli-based.

Adding gui tools is naturally going to increase the dependencies. To try and minimise these, I've coded the monitor to not need any GUI libraries to keep the spec for actually using this utility (in some fashion) to a minimum. However, I'd personally rather not also require additional tooling like Glade as it increases the requirements and might be an extra barrier to entry (well for some - for others not using Glade might be one I suppose).

Thanks - I've updated po/.

@xnox - I'm not sure about Qt: it might be appropriate for Ubuntu specifically. Since this monitor app is so simple, I do wonder if it could be implemented in a few lines of QML anyway? Maybe we should just opt for a good old curses interface ;-)

Revision history for this message
Dimitri John Ledkov (xnox) wrote :

On 21 March 2013 12:21, James Hunt <email address hidden> wrote:
> @vorlon - I have been in two minds about even putting a gui tool into the upstream package; we already have a few scripts which set some sort of precedent, but they are all cli-based.
>
> Adding gui tools is naturally going to increase the dependencies. To try and minimise these, I've coded the monitor to not need any GUI libraries to keep the spec for actually using this utility (in some fashion) to a minimum. However, I'd personally rather not also require additional tooling like Glade as it increases the requirements and might be an extra barrier to entry (well for some - for others not using Glade might be one I suppose).

Glade is a point&click interface to load/edit/save GtkBuilder xml,
which is loaded directly by GtkBuilder. So there is no extra
dependencies. One can write GtkBuilder xml by hand, which you already
do for menus in your code.

Regards,

Dmitrijs.

Revision history for this message
Steve Langasek (vorlon) wrote :

$ ./scripts/upstart-monitor.py
Traceback (most recent call last):
  File "./scripts/upstart-monitor.py", line 687, in <module>
    main()
  File "./scripts/upstart-monitor.py", line 677, in main
    win = UpstartEventsGui()
  File "./scripts/upstart-monitor.py", line 515, in __init__
    self.icon_pixbuf = theme.load_icon(NAME, 128, Gtk.IconLookupFlags.GENERIC_FALLBACK)
  File "/usr/lib/python3/dist-packages/gi/types.py", line 113, in function
    return info.invoke(*args, **kwargs)
gi._glib.GError: Icon 'upstart-monitor' not present in theme

Is there supposed to be an icon as part of this branch?

Revision history for this message
James Hunt (jamesodhunt) wrote :

Hi Steve - you need to "make install" to get the icon in the correct location or you can do:

dir=$HOME/.local/share/applications
mkdir -p $dir
cp upstart-monitor.desktop $dir

dir=$HOME/.local/share/icons/hicolor/scalable/apps
mkdir -p $dir
cp upstart-monitor.svg $dir
update-icon-caches $dir

Then, ./scripts/upstart-monitor.py should work.

Revision history for this message
Steve Langasek (vorlon) wrote :

My point is, this svg file doesn't appear to be committed to the branch.

Revision history for this message
James Hunt (jamesodhunt) wrote :

It's in the doc/ directory as upstart-logo.svg and gets copied to upstart-monitor.svg as part of the build.

Revision history for this message
Steve Langasek (vorlon) wrote :

On Thu, Mar 21, 2013 at 09:48:22PM -0000, James Hunt wrote:

> It's in the doc/ directory as upstart-logo.svg and gets copied to
> upstart-monitor.svg as part of the build.

Ahhh ok, I missed that it was using an existing file already in the tree.
Sorry. :)

Revision history for this message
Dimitri John Ledkov (xnox) wrote :

mpt would cringe at seeing that UI =) we will tweak it later ;-)

review: Approve
1453. By James Hunt

* Sync with lp:upstart.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2013-03-21 11:58:30 +0000
3+++ ChangeLog 2013-03-22 10:57:19 +0000
4@@ -1,7 +1,18 @@
5 2013-03-21 James Hunt <james.hunt@ubuntu.com>
6
7- * po/POTFILES.in: Added missing entries for init/quiesce.c
8- and init/state.c.
9+ * po/POTFILES.in:
10+ - Added missing entries for init/quiesce.c and init/state.c.
11+ - Added upstart-monitor.py.
12+ * scripts/upstart-monitor.py: UpstartEventsGui(): Removed class
13+ attributes and added explicit instance ones in __init__().
14+
15+2013-03-18 James Hunt <james.hunt@ubuntu.com>
16+
17+ * configure.ac: Added scripts/data/Makefile.
18+ * scripts/Makefile.am: Added SUBDIRS=data.
19+ * scripts/data/Makefile.am: New Makefile.
20+ * scripts/data/upstart-monitor.desktop: Desktop file for
21+ upstart-monitor.
22
23 2013-03-15 James Hunt <james.hunt@ubuntu.com>
24
25@@ -28,6 +39,13 @@
26 * extra/conf/upstart-file-bridge.conf: Change start on condition
27 to ensure all filesystems are mounted before it starts.
28
29+2013-03-13 James Hunt <james.hunt@ubuntu.com>
30+
31+ * scripts/man/upstart-monitor.8: New manpage.
32+ * scripts/upstart-monitor.py: New cli+gui tool to monitor
33+ Upstart events.
34+ * scripts/Makefile.am: Updated for upstart-monitor.
35+
36 2013-03-11 James Hunt <james.hunt@ubuntu.com>
37
38 * extra/Makefile.am: Add file bridge and conf file.
39
40=== modified file 'configure.ac'
41--- configure.ac 2013-03-04 11:57:18 +0000
42+++ configure.ac 2013-03-22 10:57:19 +0000
43@@ -101,6 +101,6 @@
44 AC_CONFIG_FILES([ Makefile intl/Makefile
45 dbus/Makefile init/Makefile util/Makefile conf/Makefile
46 extra/Makefile doc/Makefile contrib/Makefile po/Makefile.in
47- scripts/Makefile ])
48+ scripts/Makefile scripts/data/Makefile ])
49 AC_CONFIG_HEADERS([config.h])
50 AC_OUTPUT
51
52=== modified file 'po/POTFILES.in'
53--- po/POTFILES.in 2013-03-21 11:58:30 +0000
54+++ po/POTFILES.in 2013-03-22 10:57:19 +0000
55@@ -27,3 +27,5 @@
56
57 extra/upstart-udev-bridge.c
58 extra/upstart-socket-bridge.c
59+
60+scripts/upstart-monitor.py
61
62=== modified file 'scripts/Makefile.am'
63--- scripts/Makefile.am 2011-06-01 14:57:41 +0000
64+++ scripts/Makefile.am 2013-03-22 10:57:19 +0000
65@@ -1,8 +1,11 @@
66 ## Process this file with automake to produce Makefile.in
67
68+SUBDIRS = data
69+
70 EXTRA_DIST = \
71 initctl2dot.py \
72- init-checkconf.sh
73+ init-checkconf.sh \
74+ upstart-monitor.py
75
76 dist_man_MANS = \
77 man/initctl2dot.8 \
78
79=== added directory 'scripts/data'
80=== added file 'scripts/data/Makefile.am'
81--- scripts/data/Makefile.am 1970-01-01 00:00:00 +0000
82+++ scripts/data/Makefile.am 2013-03-22 10:57:19 +0000
83@@ -0,0 +1,24 @@
84+desktopdir = $(datadir)/applications
85+desktop_DATA = upstart-monitor.desktop
86+
87+icondir = $(datadir)/icons/hicolor/scalable/apps
88+icon = upstart-monitor.svg
89+icon_DATA = $(icon)
90+
91+$(icon): $(top_srcdir)/doc/upstart-logo.svg
92+ cp $< $@
93+
94+EXTRA_DIST = $(desktop_DATA)
95+
96+install-icon:
97+ mkdir -p $(DESTDIR)$(pkgdatadir)/icons/
98+ $(INSTALL_DATA) $(icon) $(DESTDIR)$(pkgdatadir)/icons/
99+
100+uninstall-icon:
101+ rm -f $(DESTDIR)$(pkgdatadir)/icons/$(icon)
102+
103+install-data-local: install-icon
104+uninstall-local: uninstall-icon
105+
106+clean-local:
107+ rm -f $(icon)
108
109=== added file 'scripts/data/upstart-monitor.desktop'
110--- scripts/data/upstart-monitor.desktop 1970-01-01 00:00:00 +0000
111+++ scripts/data/upstart-monitor.desktop 2013-03-22 10:57:19 +0000
112@@ -0,0 +1,8 @@
113+[Desktop Entry]
114+Encoding=UTF-8
115+Comment=Upstart Event Monitor
116+Name=upstart-monitor
117+Exec=upstart-monitor
118+Icon=upstart-monitor
119+Type=Application
120+Categories=GNOME;GTK;Utility;
121
122=== added file 'scripts/man/upstart-monitor.8'
123--- scripts/man/upstart-monitor.8 1970-01-01 00:00:00 +0000
124+++ scripts/man/upstart-monitor.8 2013-03-22 10:57:19 +0000
125@@ -0,0 +1,81 @@
126+.TH upstart\-monitor 8 2013-03-13 upstart
127+.\"
128+.SH NAME
129+upstart\-monitor \- Display system and session Upstart events
130+.\"
131+.SH SYNOPSIS
132+.B upstart\-monitor
133+.RI [ OPTIONS ]...
134+.\"
135+.SH DESCRIPTION
136+.B upstart\-monitor
137+displays Upstart system events or Upstart session events using either a
138+command-line or graphical interface.
139+.\"
140+.SH OPTIONS
141+.\"
142+.TP
143+.BR \-d " \fIdestination\fP" " , " \-\-destination=\fIdestination\fP
144+Specify the endpoint to connect to. Valid destinations are
145+.IR session\-socket ", "
146+.IR session\-bus ", "
147+.IR system\-socket " and "
148+.IR system-bus "."
149+.\"
150+.TP
151+.B \-\-help
152+Show brief usage summary.
153+.\"
154+.TP
155+.BR \-n " , " \-\-no-gui
156+Run in command-line mode.
157+.\"
158+.TP
159+.BR \-s " , " \-\-separator
160+Specify alternate field separator to use for command-line output
161+(default is a single tab character).
162+.\"
163+.SH NOTES
164+.\"
165+.IP \(bu 4
166+By default,
167+.B upstart\-monitor
168+will attempt to start in a graphical mode unless
169+.B \-\-no\-gui
170+is specified. However, if it is unable to display a graphical interface,
171+it will display a warning message and automatically revert to the
172+command-line interface.
173+.\"
174+.IP \(bu 4
175+When no command-line option (that affects the destination) is specified,
176+.B upstart\-monitor
177+will attempt to connect to the Session Init (destination
178+\fIsession\-socket\fR) and display session events. In this mode, it
179+will display both session events
180+.I and
181+system events, assuming the
182+.BR upstart-event-bridge (8)
183+is running.
184+
185+If invoked with no arguments from within a non-Upstart session
186+environment it will attempt to connect to Upstart running as process ID
187+1 (destination \fIsystem\-bus\fR) and will only display system events.
188+.\"
189+.SH AUTHOR
190+Written by James Hunt
191+.RB < james.hunt@ubuntu.com >
192+.\"
193+.SH BUGS
194+Report bugs at
195+.RB < https://launchpad.net/ubuntu/+source/upstart/+bugs >
196+.\"
197+.SH COPYRIGHT
198+Copyright \(co 2013 Canonical Ltd.
199+.PP
200+This is free software; see the source for copying conditions. There is NO
201+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
202+.SH SEE ALSO
203+.BR init (5)
204+.BR init (8)
205+.BR upstart-events (7)
206+.BR upstart-event-bridge (8)
207
208=== added file 'scripts/upstart-monitor.py'
209--- scripts/upstart-monitor.py 1970-01-01 00:00:00 +0000
210+++ scripts/upstart-monitor.py 2013-03-22 10:57:19 +0000
211@@ -0,0 +1,687 @@
212+#!/usr/bin/python3
213+# -*- coding: utf-8 -*-
214+# vim: set fileencoding=utf-8
215+#---------------------------------------------------------------------
216+# Copyright © 2013 Canonical Ltd.
217+#
218+# Author: James Hunt <james.hunt@canonical.com>
219+#
220+# This program is free software; you can redistribute it and/or modify
221+# it under the terms of the GNU General Public License version 2, as
222+# published by the Free Software Foundation.
223+#
224+# This program is distributed in the hope that it will be useful,
225+# but WITHOUT ANY WARRANTY; without even the implied warranty of
226+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
227+# GNU General Public License for more details.
228+#
229+# You should have received a copy of the GNU General Public License along
230+# with this program; if not, write to the Free Software Foundation, Inc.,
231+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
232+#---------------------------------------------------------------------
233+# TODO:
234+#
235+# - misc
236+# - handle_clear_data() default message should use gettext
237+# - gui
238+# - Gtk.STOCK_MEDIA_PLAY toggle
239+# - menus
240+# - Edit->Copy
241+# - Edit->Select
242+# - Edit->Select All
243+# - Edit->Copy All
244+# - Connection
245+# - Choose from 4 connection types.
246+# - View
247+# - Show/Hide index+time columns
248+# - Pause of auto-scroll
249+#---------------------------------------------------------------------
250+
251+import os
252+import sys
253+import gettext
254+import argparse
255+import signal
256+import dbus
257+import dbus.mainloop.glib
258+from datetime import datetime
259+
260+VERSION = 0.1
261+COPYRIGHT = 'Copyright © 2013 Canonical Ltd.'
262+DESCRIPTION = 'Simple Upstart Event Monitor'
263+NAME = 'upstart-monitor'
264+AUTHORS = [
265+ 'James Hunt <james.hunt@ubuntu.com>'
266+]
267+WEBSITE = 'http://upstart.ubuntu.com'
268+
269+cli = False
270+
271+try:
272+ from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
273+except ImportError:
274+ gettext.install(NAME)
275+ print("%s: %s" % (_('WARNING'), _('GUI modules not available - falling back to CLI')), file=sys.stderr)
276+ cli = True
277+
278+"""
279+Simple command-line and GUI event monitor for Upstart.
280+"""
281+
282+# If this file does not exist or is not readable...
283+LICENSE_FILE = 'aa/usr/share/common-licenses/GPL-2'
284+
285+# ... display this text instead.
286+LICENSE = """
287+http://www.gnu.org/
288+This program is free software; you can redistribute it and/or modify
289+it under the terms of the GNU General Public License version 2, as
290+published by the Free Software Foundation.
291+
292+This program is distributed in the hope that it will be useful,
293+but WITHOUT ANY WARRANTY; without even the implied warranty of
294+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
295+GNU General Public License for more details.
296+
297+You should have received a copy of the GNU General Public License along
298+with this program; if not, write to the Free Software Foundation, Inc.,
299+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
300+"""
301+
302+#------------------------------
303+
304+DEFAULT_WIN_SIZE_WIDTH = 600
305+DEFAULT_WIN_SIZE_HEIGHT = 200
306+
307+DEFAULT_LOGO_SIZE_WIDTH = 150
308+DEFAULT_LOGO_SIZE_HEIGHT = 150
309+
310+DEFAULT_OUTPUT_FILE = 'upstart-events.txt'
311+
312+# key=type : value=description
313+destinations = \
314+{
315+ 'system-bus' : 'D-Bus system bus',
316+ 'session-bus' : 'D-Bus session bus',
317+ 'system-socket' : 'D-Bus system socket',
318+ 'session-socket' : 'D-Bus session socket'
319+}
320+
321+#------------------------------
322+
323+# well-known address for Upstart running as PID 1
324+INIT_SOCKET = 'unix:abstract=/com/ubuntu/upstart'
325+
326+# Address of Session Inits private socket
327+SESSION_SOCKET = os.environ.get('UPSTART_SESSION')
328+
329+UI_INFO = """
330+<ui>
331+ <menubar name='MenuBar'>
332+
333+ <menu action='FileMenu'>
334+ <menuitem action='FileNew'/>
335+ <menuitem action='FileSave'/>
336+ <menuitem action='FileSaveAs'/>
337+ <separator/>
338+ <menuitem action='FileQuit'/>
339+ </menu>
340+
341+<!--
342+ <menu action='EditMenu'>
343+ <menuitem action='EditCopy'/>
344+ </menu>
345+
346+ <menu action='OptionsMenu'>
347+ <menuitem action='OptionsAutoScroll'/>
348+ <menu action='ConnectionMenu'>
349+ <menuitem action='System Bus'/>
350+ <menuitem action='System Socket'/>
351+ <menuitem action='Session Bus'/>
352+ <menuitem action='Session Socket'/>
353+ </menu>
354+ </menu>
355+
356+-->
357+ <menu action='HelpMenu'>
358+ <menuitem action='HelpAbout'/>
359+ </menu>
360+
361+ </menubar>
362+
363+ <popup name='ContextMenu'>
364+ <menuitem action='ContextCopy'/>
365+ </popup>
366+</ui>
367+"""
368+
369+
370+def format_event(*args):
371+ """
372+ Format raw D-Bus event details.
373+
374+ Returns: event and quoted event environment tuple.
375+ """
376+ quoted_env = []
377+
378+ event = args[0]
379+
380+ # unfortunately, quotes are stripped so they need to be re-added
381+ # to preserve whitespace in the environment variable values.
382+ raw_env = args[1]
383+
384+ for e in raw_env:
385+ f = e.split('=')
386+ quoted_env.append("%s='%s'" % (f[0], ''.join(f[1:])))
387+
388+ env = ' '.join(str(e) for e in quoted_env)
389+
390+ return event, env
391+
392+
393+def cmdline_event_handler(*args):
394+ """
395+ format event data for command-line display
396+ """
397+ global cmdline_args
398+
399+ event, env = format_event(*args)
400+ now = datetime.now().strftime("%F %T.%f")
401+ event_str = "%s %s" % (event, env) if env else event
402+ sep = cmdline_args.separator if cmdline_args.separator else "\t"
403+ print("%s%s%s" % (now, sep, event_str))
404+
405+
406+class UpstartEventsGui(Gtk.Window):
407+ """
408+ GUI Upstart Event monitor.
409+ """
410+ global bus
411+ global cmdline_args
412+
413+ def add_row(self, event, event_env):
414+ """
415+ Add new row to view.
416+ """
417+ self.data_index += 1
418+ now = datetime.now().strftime("%F %T.%f")
419+ event_str = "%s %s" % (event, event_env) if event_env else event
420+
421+ row = [self.data_index, now, event_str]
422+ self.liststore.append(row)
423+
424+
425+ def event_handler(self, *args):
426+ """
427+ Upstart has emitted an event so create a new row in the view.
428+ """
429+ event, env = format_event(*args)
430+ self.add_row(event, env)
431+
432+ # New data arrived since last save (if any).
433+ self.need_save = True
434+
435+
436+ def register_cb(self):
437+ """
438+ Callback to register a D-Bus callback whenever Upstart emits an
439+ event.
440+ """
441+ # register a D-Bus handler
442+ bus.add_signal_receiver(self.event_handler,
443+ dbus_interface='com.ubuntu.Upstart0_6',
444+ signal_name="EventEmitted")
445+
446+ # deregister this callback
447+ return False
448+
449+
450+ def treeview_changed(self, widget, event, data=None):
451+ """
452+ Allow GUI to auto-scroll.
453+ """
454+ adj = self.scrolled_window.get_vadjustment()
455+ adj.set_value(adj.get_upper() - adj.get_page_size())
456+
457+
458+ def ask_question(self, msg):
459+ """
460+ Ask a question.
461+
462+ @msg: message to display.
463+
464+ Returns: True if user answered positively, else False.
465+ """
466+ dialog = Gtk.MessageDialog(self,
467+ Gtk.DialogFlags.DESTROY_WITH_PARENT,
468+ Gtk.MessageType.QUESTION,
469+ Gtk.ButtonsType.YES_NO,
470+ msg)
471+ response = dialog.run()
472+ dialog.destroy()
473+ return True if response == Gtk.ResponseType.YES else False
474+
475+
476+ def show_dialog(self, msg):
477+ """
478+ Display an "ok" dialog which the user must click.
479+
480+ @msg: message to display.
481+ """
482+ dialog = Gtk.MessageDialog(self,
483+ Gtk.DialogFlags.DESTROY_WITH_PARENT,
484+ Gtk.MessageType.ERROR,
485+ Gtk.ButtonsType.OK,
486+ msg)
487+ dialog.run()
488+ dialog.destroy()
489+
490+
491+ def show_about(self):
492+ """
493+ Show an About dialog.
494+ """
495+ about = Gtk.AboutDialog()
496+ about.set_program_name(NAME)
497+ about.set_version("%s %s" % (_('Version'), VERSION))
498+ about.set_copyright(COPYRIGHT)
499+ about.set_comments(DESCRIPTION)
500+ about.set_authors(AUTHORS)
501+ about.set_website(WEBSITE)
502+
503+ try:
504+ with open(LICENSE_FILE, 'r') as f:
505+ license = f.read()
506+ except IOError:
507+ license = LICENSE
508+
509+ about.set_license(license)
510+ about.set_logo(self.icon_pixbuf)
511+ about.run()
512+ about.destroy()
513+
514+
515+ def on_button_clear_clicked(self, widget):
516+ """
517+ Clear cached event data (also clears GUI window).
518+ """
519+ if self.handle_clear_data():
520+ self.liststore.clear()
521+ self.data_index = 1
522+ self.need_save = False
523+
524+ def on_button_pause_clicked(self, widget):
525+ """
526+ Toggle auto-scroll.
527+ """
528+ if self.auto_scroll:
529+ self.treeview.disconnect(self.auto_scroll_handler)
530+ self.auto_scroll = False
531+ else:
532+ self.auto_scroll_handler = \
533+ self.treeview.connect('size-allocate', self.treeview_changed)
534+ self.auto_scroll = True
535+
536+
537+ def save_data(self, path):
538+ """
539+ Write received event data to specified path.
540+ """
541+ try:
542+ fh = open(path, 'w')
543+ except IOError:
544+ self.show_dialog('%s: %s' % (_('Error saving file'), path))
545+
546+ for row in self.liststore:
547+ fh.write("%d\t%s\t%s\n" % (row[0], row[1], row[2]))
548+ fh.close()
549+
550+ # All data was written
551+ self.need_save = False
552+
553+
554+ def handle_save(self, filename=None):
555+ """
556+ Display a file chooser dialog to allow user to select a filename to
557+ hold the cached event data.
558+ """
559+ # no data yet
560+ if not len(self.liststore):
561+ self.show_dialog(_('No events to save'))
562+ return
563+
564+ if filename != None:
565+ path = filename
566+ self.save_data(path)
567+ return
568+
569+ file_chooser = Gtk.FileChooserDialog(_('Save File'), self.get_toplevel(),
570+ Gtk.FileChooserAction.SAVE,
571+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
572+ Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT))
573+
574+ file_chooser.set_current_name(DEFAULT_OUTPUT_FILE)
575+
576+ response = file_chooser.run()
577+
578+ if response != Gtk.ResponseType.ACCEPT:
579+ file_chooser.destroy()
580+ return
581+
582+ path = file_chooser.get_filename()
583+
584+ self.save_data(path)
585+ file_chooser.destroy()
586+
587+ def handle_quit(self):
588+ """
589+ Quit application; if unsaved data exists, prompt user.
590+ """
591+
592+ if self.handle_clear_data('Quit without saving?'):
593+ Gtk.main_quit()
594+
595+ def on_button_quit_clicked(self, widget):
596+ """
597+ Handle quit request.
598+ """
599+ self.handle_quit()
600+
601+
602+ def on_button_save_clicked(self, widget):
603+ """
604+ Handle save request.
605+ """
606+ self.handle_save()
607+
608+ # FIXME: default message should use gettext
609+ def handle_clear_data(self, message='Clear data without saving?'):
610+ """
611+ Handle potentially clearing of cached events.
612+
613+ Returns: True if user is happy to clear the data, else False.
614+ """
615+ if len(self.liststore) and self.need_save:
616+ answer = self.ask_question(message)
617+ return answer
618+ else:
619+ # no data would be lost so allow action
620+ return True
621+
622+ def activate_action(self, action, user_data=None):
623+ """
624+ Main menu-callback-handling method.
625+ """
626+ name = action.get_name()
627+
628+ if name == 'FileNew':
629+ if self.handle_clear_data():
630+ self.liststore.clear()
631+ self.data_index = 1
632+ self.need_save = False
633+ elif name == 'FileSave':
634+ self.handle_save(DEFAULT_OUTPUT_FILE)
635+ elif name == 'FileSaveAs':
636+ self.handle_save()
637+ elif name == 'FileQuit':
638+ self.handle_quit()
639+ elif name == 'HelpAbout':
640+ self.show_about()
641+ elif name == 'ContextCopy':
642+ # Save event details from cell text to clipboard
643+ clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
644+ clipboard.set_text(self.context_value, -1)
645+
646+ def create_ui_manager(self):
647+ """
648+ Errrm... create the UI Manager.
649+ """
650+ ui_manager = Gtk.UIManager()
651+
652+ # Throws exception if something went wrong
653+ ui_manager.add_ui_from_string(UI_INFO)
654+
655+ # Add the accelerator group to the toplevel window
656+ accelgroup = ui_manager.get_accel_group()
657+ self.add_accel_group(accelgroup)
658+ return ui_manager
659+
660+ def on_button_press_event(self, treeview, event):
661+ """
662+ Handle mouse button press events.
663+ """
664+ if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
665+ x = int(event.x)
666+ y = int(event.y)
667+ path_info = treeview.get_path_at_pos(x, y)
668+ if path_info:
669+ path, col, cell_x, cell_y = path_info
670+
671+ model = treeview.get_model()
672+ model_iter = model.get_iter(path)
673+
674+ # Save the event details from the selected cell
675+ # (event is 3rd column (zero-indexed)).
676+ self.context_value = model.get_value (model_iter, 2)
677+
678+ treeview.grab_focus()
679+ treeview.set_cursor(path, col, 0)
680+
681+ # Display context menu
682+ self.popup.popup(None, None, None, None, event.button, event.time)
683+ return True
684+
685+
686+ def __init__(self):
687+ """
688+ Setup.
689+ """
690+ Gtk.Window.__init__(self, title=DESCRIPTION)
691+
692+ main_menu_action_entries = (
693+ ('FileMenu', None, 'File'),
694+ ('FileNew', Gtk.STOCK_NEW, 'New', '<control>N',
695+ _('Clear events'), self.activate_action),
696+ ('FileSave', Gtk.STOCK_SAVE, 'Save', '<control>S',
697+ _('Save events to default file'), self.activate_action),
698+ ('FileSaveAs', Gtk.STOCK_SAVE_AS, 'Save As', None,
699+ _('Save events to specified file'), self.activate_action),
700+ ('FileQuit', Gtk.STOCK_QUIT, 'Quit', '<control>Q',
701+ _('Exit application'), self.activate_action),
702+
703+ ('HelpMenu', None, 'Help'),
704+ ('HelpAbout', Gtk.STOCK_HELP, 'About', '<control>A',
705+ _('Application details'), self.activate_action),
706+ )
707+
708+ context_menu_action_entries = (
709+ ('ContextCopy', Gtk.STOCK_COPY, 'Copy', '<control>C',
710+ _('Copy'), self.activate_action),
711+ )
712+
713+ self.auto_scroll = True
714+ self.need_save = False
715+
716+ # value of cell containing event details that user has
717+ # right-clicked over
718+ self.context_value = None
719+
720+ self.data_index = 0
721+
722+ self.set_default_size(DEFAULT_WIN_SIZE_WIDTH, DEFAULT_WIN_SIZE_HEIGHT)
723+ self.set_resizable(True)
724+
725+ theme = Gtk.IconTheme.get_default()
726+ self.icon_pixbuf = theme.load_icon(NAME, 128, Gtk.IconLookupFlags.GENERIC_FALLBACK)
727+ self.set_icon(self.icon_pixbuf)
728+
729+ action_group = Gtk.ActionGroup('UpstartMonitorActions')
730+ action_group.add_actions(main_menu_action_entries)
731+ action_group.add_actions(context_menu_action_entries)
732+
733+ ui_manager = self.create_ui_manager()
734+ ui_manager.insert_action_group(action_group)
735+
736+ menubar = ui_manager.get_widget("/MenuBar")
737+
738+ # data types we'll be using in each column
739+ self.liststore = Gtk.ListStore(int, str, str)
740+
741+ GLib.idle_add(self.register_cb)
742+
743+ self.box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
744+
745+ self.scrolled_window = Gtk.ScrolledWindow()
746+ self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
747+
748+ self.treeview = Gtk.TreeView(model=self.liststore)
749+
750+ # columns are zero-based
751+ self.treeview.set_search_column(2)
752+
753+ self.treeview.set_headers_clickable(True)
754+
755+ if self.auto_scroll:
756+ self.auto_scroll_handler = \
757+ self.treeview.connect('size-allocate', self.treeview_changed)
758+
759+ renderer_text = Gtk.CellRendererText()
760+
761+ # XXX: mark column as editable, but do NOT connect the 'edited'
762+ # signal. This allows the user to select the text and copy it,
763+ # but they cannot modify it.
764+ renderer_editabletext = Gtk.CellRendererText()
765+ renderer_editabletext.set_property('editable', True)
766+
767+ column_index = Gtk.TreeViewColumn(_('Index'), renderer_text, text=0)
768+ column_index.set_resizable(True)
769+ self.treeview.append_column(column_index)
770+
771+ column_time = Gtk.TreeViewColumn(_('Time'), renderer_text, text=1)
772+ column_time.set_resizable(True)
773+ self.treeview.append_column(column_time)
774+
775+ column_event = Gtk.TreeViewColumn(_('Event and environment'),
776+ renderer_editabletext, text=2)
777+ column_event.set_resizable(True)
778+
779+ self.popup = ui_manager.get_widget("/ContextMenu")
780+ self.treeview.connect('button-press-event', self.on_button_press_event)
781+
782+ self.treeview.append_column(column_event)
783+
784+ # stops column titles from scrolling along with the data
785+ self.scrolled_window.add(self.treeview)
786+
787+ self.button_clear = Gtk.Button(stock=Gtk.STOCK_CLEAR)
788+ self.button_clear.connect('clicked', self.on_button_clear_clicked)
789+
790+ # FIXME: toggle to Gtk.STOCK_MEDIA_PLAY
791+ self.button_pause = Gtk.Button(stock=Gtk.STOCK_MEDIA_PAUSE)
792+ self.button_pause.connect('clicked', self.on_button_pause_clicked)
793+
794+ self.button_save = Gtk.Button(stock=Gtk.STOCK_SAVE)
795+ self.button_save.connect('clicked', self.on_button_save_clicked)
796+
797+ self.button_quit = Gtk.Button(stock=Gtk.STOCK_QUIT)
798+ self.button_quit.connect('clicked', self.on_button_quit_clicked)
799+
800+ self.label_connected = Gtk.Label('%s %s' % (_('Connected to'),
801+ destinations[cmdline_args.destination]))
802+ self.label_connected.set_justify(Gtk.Justification.RIGHT)
803+
804+ self.buttons_box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.HORIZONTAL)
805+ self.buttons_box.pack_start(self.button_clear, False, True, 0)
806+ self.buttons_box.pack_start(self.button_pause, False, True, 0)
807+ self.buttons_box.pack_start(self.button_save, False, True, 0)
808+ self.buttons_box.pack_start(self.button_quit, False, True, 0)
809+ self.buttons_box.pack_start(self.label_connected, True, True, 0)
810+
811+ self.box.pack_start(menubar, False, False, 0)
812+ self.box.pack_start(self.scrolled_window, True, True, 0)
813+ self.box.pack_end(self.buttons_box, False, False, 0)
814+ self.add(self.box)
815+
816+
817+ def text_edited(self, widget, path, text):
818+ self.liststore[path][1] = text
819+
820+
821+def main():
822+ """
823+ Parse arguments and run either the command-line or the GUI monitor.
824+ """
825+ global bus
826+ global cmdline_args
827+ global cli
828+
829+ gettext.install(NAME)
830+
831+ parser = argparse.ArgumentParser(description=_('Upstart Event Monitor'))
832+
833+ parser.add_argument('-n', '--no-gui',
834+ action='store_true',
835+ help=_('run in command-line mode'))
836+
837+ parser.add_argument('-s', '--separator',
838+ help=_('field separator to use for command-line output'))
839+
840+ parser.add_argument('-d', '--destination',
841+ choices=destinations.keys(),
842+ help=_('connect to Upstart via specified D-Bus route'))
843+
844+ cmdline_args = parser.parse_args()
845+
846+ # allow interrupt
847+ signal.signal(signal.SIGINT, signal.SIG_DFL)
848+
849+ dbus.set_default_main_loop(dbus.mainloop.glib.DBusGMainLoop())
850+
851+ if not cmdline_args.destination:
852+ # If no destination specified, attempt to connect to session
853+ # if running under a Session Init, else connect to the system
854+ # bus (since this allows even non-priv users to see events).
855+ if SESSION_SOCKET:
856+ cmdline_args.destination = 'session-socket'
857+ else:
858+ cmdline_args.destination = 'system-bus'
859+
860+ if cmdline_args.destination == 'system-bus':
861+ bus = dbus.SystemBus()
862+ elif cmdline_args.destination == 'session-bus':
863+ bus = dbus.SessionBus()
864+ elif cmdline_args.destination == 'system-socket':
865+ socket = INIT_SOCKET
866+ bus = dbus.connection.Connection(socket)
867+ elif cmdline_args.destination == 'session-socket':
868+ socket = SESSION_SOCKET
869+ bus = dbus.connection.Connection(socket)
870+ if cmdline_args.no_gui or not os.environ.get('DISPLAY'):
871+ cli = True
872+
873+ # dynamically load GUI elements so we can fall back to the
874+ # command-line version if we cannot display a GUI
875+ if cli == True:
876+ # register a D-Bus handler
877+ bus.add_signal_receiver(cmdline_event_handler, dbus_interface='com.ubuntu.Upstart0_6',
878+ signal_name='EventEmitted')
879+ loop = GLib.MainLoop()
880+ print('# Upstart Event Monitor (%s)' % _('console mode'))
881+ print('#')
882+ print('# %s %s' % (_('Connected to'), destinations[cmdline_args.destination]))
883+ print('#')
884+ print('# %s' % _('Columns: time, event and environment'))
885+ print('')
886+ loop.run()
887+ else:
888+ win = UpstartEventsGui()
889+ win.connect('delete-event', Gtk.main_quit)
890+ win.show_all()
891+ Gtk.main()
892+
893+
894+if __name__ == '__main__':
895+ # Allow _() to be called from this point onwards
896+ gettext.install(NAME)
897+
898+ main()

Subscribers

People subscribed via source and target branches