diff --git a/debug-viewer/GstDebugViewer/Common/Data.py b/debug-viewer/GstDebugViewer/Common/Data.py new file mode 100644 index 0000000000..a1d12330ed --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/Data.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer development utilities common Data module""" + +import pygtk +pygtk.require ("2.0") + +import gobject + +class Dispatcher (object): + + def __call__ (self, iterator): + + raise NotImplementedError ("derived classes must override this method") + +class DefaultDispatcher (Dispatcher): + + def __call__ (self, iterator): + + for x in iterator: + pass + +class GSourceDispatcher (Dispatcher): + + def __init__ (self): + + Dispatcher.__init__ (self) + + self.source_id = None + + def __call__ (self, iterator): + + if self.source_id is not None: + gobject.source_remove (self.source_id) + + self.source_id = gobject.idle_add (iterator.next) diff --git a/debug-viewer/GstDebugViewer/Common/GUI.py b/debug-viewer/GstDebugViewer/Common/GUI.py new file mode 100644 index 0000000000..bf668ca6a6 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/GUI.py @@ -0,0 +1,467 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer development utilities common GUI module""" + +import logging + +import pygtk +pygtk.require ("2.0") +del pygtk + +import gobject +import gtk + +from GstDebugViewer.Common import utils + +class Actions (dict): + + def __init__ (self): + + dict.__init__ (self) + + self.groups = () + + def __getattr__ (self, name): + + try: + return self[name] + except KeyError: + if "_" in name: + try: + return self[name.replace ("_", "-")] + except KeyError: + pass + + raise AttributeError ("no action with name %r" % (name,)) + + def add_group (self, group): + + self.groups += (group,) + for action in group.list_actions (): + self[action.props.name] = action + +class Widgets (dict): + + def __init__ (self, glade_tree): + + widgets = glade_tree.get_widget_prefix ("") + dict.__init__ (self, ((w.name, w,) for w in widgets)) + + def __getattr__ (self, name): + + try: + return self[name] + except KeyError: + if "_" in name: + try: + return self[name.replace ("_", "-")] + except KeyError: + pass + + raise AttributeError ("no widget with name %r" % (name,)) + +class WidgetFactory (object): + + def __init__ (self, glade_filename): + + self.filename = glade_filename + + def make (self, widget_name, autoconnect = None): + + glade_tree = gtk.glade.XML (self.filename, widget_name) + + if autoconnect is not None: + glade_tree.signal_autoconnect (autoconnect) + + return Widgets (glade_tree) + + def make_one (self, widget_name): + + glade_tree = gtk.glade.XML (self.filename, widget_name) + + return glade_tree.get_widget (widget_name) + +class UIFactory (object): + + def __init__ (self, ui_filename, actions = None): + + self.filename = ui_filename + if actions: + self.action_groups = actions.groups + else: + self.action_groups = () + + def make (self, extra_actions = None): + + ui_manager = gtk.UIManager () + for action_group in self.action_groups: + ui_manager.insert_action_group (action_group, 0) + if extra_actions: + for action_group in extra_actions.groups: + ui_manager.insert_action_group (action_group, 0) + ui_manager.add_ui_from_file (self.filename) + ui_manager.ensure_update () + + return ui_manager + +class MetaModel (gobject.GObjectMeta): + + """Meta class for easy setup of gtk tree models. + + Looks for a class attribute named `columns' which must be set to a + sequence of the form name1, type1, name2, type2, ..., where the + names are strings. This metaclass adds the following attributes + to created classes: + + cls.column_types = (type1, type2, ...) + cls.column_ids = (0, 1, ...) + cls.name1 = 0 + cls.name2 = 1 + ... + + Example: A gtk.ListStore derived model can use + + columns = ("COL_NAME", str, "COL_VALUE", str) + + and use this in __init__: + + gtk.ListStore.__init__ (self, *self.column_types) + + Then insert data like this: + + self.set (self.append (), + self.COL_NAME, "spam", + self.COL_VALUE, "ham") + """ + + def __init__ (cls, name, bases, dict): + + super (MetaModel, cls).__init__ (name, bases, dict) + + spec = tuple (cls.columns) + + column_names = spec[::2] + column_types = spec[1::2] + column_indices = range (len (column_names)) + + for col_index, col_name, in zip (column_indices, column_names): + setattr (cls, col_name, col_index) + + cls.column_types = column_types + cls.column_ids = tuple (column_indices) + +class Manager (object): + + """GUI Manager base class.""" + + @classmethod + def iter_item_classes (cls): + + msg = "%s class does not support manager item class access" + raise NotImplementedError (msg % (cls.__name__,)) + + @classmethod + def find_item_class (self, **kw): + + return self.__find_by_attrs (self.iter_item_classes (), kw) + + def iter_items (self): + + msg = "%s object does not support manager item access" + raise NotImplementedError (msg % (type (self).__name__,)) + + def find_item (self, **kw): + + return self.__find_by_attrs (self.iter_items (), kw) + + @staticmethod + def __find_by_attrs (i, kw): + + from operator import attrgetter + + if len (kw) != 1: + raise ValueError ("need exactly one keyword argument") + + attr, value = kw.items ()[0] + getter = attrgetter (attr) + + for item in i: + if getter (item) == value: + return item + else: + raise KeyError ("no item such that item.%s == %r" % (attr, value,)) + +class StateString (object): + + """Descriptor for binding to AppState classes.""" + + def __init__ (self, option, section = None): + + self.option = option + self.section = section + + def get_section (self, state): + + if self.section is None: + return state._default_section + else: + return self.section + + def get_getter (self, state): + + return state._parser.get + + def get_default (self, state): + + return None + + def __get__ (self, state, state_class = None): + + import ConfigParser + + if state is None: + return self + + getter = self.get_getter (state) + section = self.get_section (state) + + try: + return getter (section, self.option) + except (ConfigParser.NoSectionError, + ConfigParser.NoOptionError,): + return self.get_default (state) + + def __set__ (self, state, value): + + import ConfigParser + + if value is None: + value = "" + + section = self.get_section (state) + option = self.option + option_value = str (value) + + try: + state._parser.set (section, option, option_value) + except ConfigParser.NoSectionError: + state._parser.add_section (section) + state._parser.set (section, option, option_value) + +class StateBool (StateString): + + """Descriptor for binding to AppState classes.""" + + def get_getter (self, state): + + return state._parser.getboolean + +class StateInt (StateString): + + """Descriptor for binding to AppState classes.""" + + def get_getter (self, state): + + return state._parser.getint + +class StateInt4 (StateString): + + """Descriptor for binding to AppState classes. This implements storing a + tuple of 4 integers.""" + + def __get__ (self, state, state_class = None): + + if state is None: + return self + + value = StateString.__get__ (self, state) + + try: + l = value.split (",") + if len (l) != 4: + return None + else: + return tuple ((int (v) for v in l)) + except (AttributeError, TypeError, ValueError,): + return None + + def __set__ (self, state, value): + + if value is None: + svalue = "" + elif len (value) != 4: + raise ValueError ("value needs to be a 4-sequence, or None") + else: + svalue = ", ".join ((str (v) for v in value)) + + return StateString.__set__ (self, state, svalue) + +class StateItem (StateString): + + """Descriptor for binding to AppState classes. This implements storing a + class controlled by a Manager class.""" + + def __init__ (self, option, manager_class, section = None): + + StateString.__init__ (self, option, section = section) + + self.manager = manager_class + + def __get__ (self, state, state_class = None): + + if state is None: + return self + + value = StateString.__get__ (self, state) + + if not value: + return None + + return self.parse_item (value) + + def __set__ (self, state, value): + + if value is None: + svalue = "" + else: + svalue = value.name + + StateString.__set__ (self, state, svalue) + + def parse_item (self, value): + + name = value.strip () + + try: + return self.manager.find_item_class (name = name) + except KeyError: + return None + +class StateItemList (StateItem): + + """Descriptor for binding to AppState classes. This implements storing an + ordered set of Manager items.""" + + def __get__ (self, state, state_class = None): + + if state is None: + return self + + value = StateString.__get__ (self, state) + + if not value: + return [] + + classes = [] + for name in value.split (","): + item_class = self.parse_item (name) + if item_class is None: + continue + if not item_class in classes: + classes.append (item_class) + + return classes + + def __set__ (self, state, value): + + if value is None: + svalue = "" + else: + svalue = ", ".join ((v.name for v in value)) + + StateString.__set__ (self, state, svalue) + +class AppState (object): + + _default_section = "state" + + def __init__ (self, filename, old_filenames = ()): + + import ConfigParser + + self._filename = filename + self._parser = ConfigParser.RawConfigParser () + success = self._parser.read ([filename]) + if not success: + for old_filename in old_filenames: + success = self._parser.read ([old_filename]) + if success: + break + + def save (self): + + # TODO Py2.5: Use 'with' statement. + fp = utils.SaveWriteFile (self._filename, "wt") + try: + self._parser.write (fp) + except: + fp.discard () + else: + fp.close () + +class WindowState (object): + + def __init__ (self): + + self.logger = logging.getLogger ("ui.window-state") + + self.is_maximized = False + + def attach (self, window, state): + + self.window = window + self.state = state + + self.window.connect ("window-state-event", + self.handle_window_state_event) + + geometry = self.state.geometry + if geometry: + self.window.move (*geometry[:2]) + self.window.set_default_size (*geometry[2:]) + + if self.state.maximized: + self.logger.debug ("initially maximized") + self.window.maximize () + + def detach (self): + + window = self.window + + self.state.maximized = self.is_maximized + if not self.is_maximized: + position = tuple (window.get_position ()) + size = tuple (window.get_size ()) + self.state.geometry = position + size + + self.window.disconnect_by_func (self.handle_window_state_event) + self.window = None + + def handle_window_state_event (self, window, event): + + if not event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED: + return + + if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED: + self.logger.debug ("maximized") + self.is_maximized = True + else: + self.logger.debug ("unmaximized") + self.is_maximized = False diff --git a/debug-viewer/GstDebugViewer/Common/Main.py b/debug-viewer/GstDebugViewer/Common/Main.py new file mode 100644 index 0000000000..934276a304 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/Main.py @@ -0,0 +1,479 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Development Utilities +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, see . + +"""GStreamer development utilities common main module.""" + +import sys +import os +import traceback +from operator import attrgetter +import logging +import locale +import gettext +from gettext import gettext as _, ngettext + +import pygtk +pygtk.require ("2.0") +del pygtk + +import gobject + +class ExceptionHandler (object): + + exc_types = (Exception,) + priority = 50 + inherit_fork = True + + _handling_exception = False + + def __call__ (self, exc_type, exc_value, exc_traceback): + + raise NotImplementedError ("derived classes need to override this method") + +class DefaultExceptionHandler (ExceptionHandler): + + # TODO Py2.5: In Python 2.5, this succeeds. Remove the try...except block + # once we depend on 2.5. + try: + exc_types = (BaseException,) + except NameError: + # Python < 2.5. + exc_types = (Exception,) + priority = 0 + inherit_fork = True + + def __init__ (self, excepthook): + + ExceptionHandler.__init__ (self) + + self.excepthook = excepthook + + def __call__ (self, *exc_info): + + return self.excepthook (*exc_info) + +class ExitOnInterruptExceptionHandler (ExceptionHandler): + + exc_types = (KeyboardInterrupt,) + priority = 100 + inherit_fork = False + + exit_status = 2 + + def __call__ (self, *args): + + print >> sys.stderr, "Interrupt caught, exiting." + + sys.exit (self.exit_status) + +class MainLoopWrapper (ExceptionHandler): + + priority = 95 + inherit_fork = False + + def __init__ (self, enter, exit): + + ExceptionHandler.__init__ (self) + + self.exc_info = (None,) * 3 + self.enter = enter + self.exit = exit + + def __call__ (self, *exc_info): + + self.exc_info = exc_info + self.exit () + + def run (self): + + ExceptHookManager.register_handler (self) + try: + self.enter () + finally: + ExceptHookManager.unregister_handler (self) + + if self.exc_info != (None,) * 3: + # Re-raise unhandled exception that occured while running the loop. + exc_type, exc_value, exc_tb = self.exc_info + raise exc_type, exc_value, exc_tb + +class ExceptHookManagerClass (object): + + def __init__ (self): + + self._in_forked_child = False + + self.handlers = [] + + def setup (self): + + if sys.excepthook == self.__excepthook: + raise ValueError ("already set up") + + hook = sys.excepthook + self.__instrument_excepthook () + self.__instrument_fork () + self.register_handler (DefaultExceptionHandler (hook)) + + def shutdown (self): + + if sys.excepthook != self.__excepthook: + raise ValueError ("not set up") + + self.__restore_excepthook () + self.__restore_fork () + + def __instrument_excepthook (self): + + hook = sys.excepthook + self._original_excepthook = hook + sys.excepthook = self.__excepthook + + def __restore_excepthook (self): + + sys.excepthook = self._original_excepthook + + def __instrument_fork (self): + + try: + fork = os.fork + except AttributeError: + # System has no fork() system call. + self._original_fork = None + else: + self._original_fork = fork + os.fork = self.__fork + + def __restore_fork (self): + + if not hasattr (os, "fork"): + return + + os.fork = self._original_fork + + def entered_forked_child (self): + + self._in_forked_child = True + + for handler in tuple (self.handlers): + if not handler.inherit_fork: + self.handlers.remove (handler) + + def register_handler (self, handler): + + if self._in_forked_child and not handler.inherit_fork: + return + + self.handlers.append (handler) + + def unregister_handler (self, handler): + + self.handlers.remove (handler) + + def __fork (self): + + pid = self._original_fork () + if pid == 0: + # Child process. + self.entered_forked_child () + return pid + + def __excepthook (self, exc_type, exc_value, exc_traceback): + + for handler in sorted (self.handlers, + key = attrgetter ("priority"), + reverse = True): + + if handler._handling_exception: + continue + + for type_ in handler.exc_types: + if issubclass (exc_type, type_): + break + else: + continue + + handler._handling_exception = True + handler (exc_type, exc_value, exc_traceback) + # Not using try...finally on purpose here. If the handler itself + # fails with an exception, this prevents recursing into it again. + handler._handling_exception = False + return + + else: + from warnings import warn + warn ("ExceptHookManager: unhandled %r" % (exc_value,), + RuntimeWarning, + stacklevel = 2) + +ExceptHookManager = ExceptHookManagerClass () + +class PathsBase (object): + + data_dir = None + icon_dir = None + locale_dir = None + + @classmethod + def setup_installed (cls, data_prefix): + + """Set up paths for running from a regular installation.""" + + pass + + @classmethod + def setup_uninstalled (cls, source_dir): + + """Set up paths for running 'uninstalled' (i.e. directly from the + source dist).""" + + pass + + @classmethod + def ensure_setup (cls): + + """If paths are still not set up, try to set from a fallback.""" + + if cls.data_dir is None: + source_dir = os.path.dirname (os.path.dirname (os.path.abspath (__file__))) + cls.setup_uninstalled (source_dir) + + def __new__ (cls): + + raise RuntimeError ("do not create instances of this class -- " + "use the class object directly") + +class PathsProgramBase (PathsBase): + + program_name = None + + @classmethod + def setup_installed (cls, data_prefix): + + if cls.program_name is None: + raise NotImplementedError ("derived classes need to set program_name attribute") + + cls.data_dir = os.path.join (data_prefix, "share", cls.program_name) + cls.icon_dir = os.path.join (data_prefix, "share", "icons") + cls.locale_dir = os.path.join (data_prefix, "share", "locale") + + @classmethod + def setup_uninstalled (cls, source_dir): + + """Set up paths for running 'uninstalled' (i.e. directly from the + source dist).""" + + # This is essential: The GUI module needs to find the .glade file. + cls.data_dir = os.path.join (source_dir, "data") + + # The locale data might be missing if "setup.py build" wasn't run. + cls.locale_dir = os.path.join (source_dir, "build", "mo") + + # Not setting icon_dir. It is not useful since we don't employ the + # needed directory structure in the source dist. + +class OptionError (Exception): + + pass + +class OptionParser (object): + + def __init__ (self, options): + + self.__entries = [] + self.__parsers = {} + + self.options = options + + def add_option (self, long_name, short_name = None, description = None, + arg_name = None, arg_parser = None, hidden = False): + + flags = 0 + + if not short_name: + # A deficiency of pygobject: + short_name = "\0" + + if not description: + description = "" + + if arg_name is None: + flags |= gobject.OPTION_FLAG_NO_ARG + elif arg_parser is not None: + self.__parsers[long_name] = arg_parser + + if hidden: + flags |= gobject.OPTION_FLAG_HIDDEN + + self.__entries.append ((long_name, short_name, flags, description, + arg_name,)) + + def __handle_option (self, option, arg, group): + + for entry in self.__entries: + long_name, short_name = entry[:2] + arg_name = entry[-1] + if (option != "--%s" % (long_name,) and + option != "-%s" % (short_name,)): + continue + attr = long_name.replace ("-", "_") + if arg_name is None: + value = True + elif long_name in self.__parsers: + value = self.__parsers[long_name](arg) + else: + value = arg + self.options[attr] = value + + def parse (self, argv): + + context = gobject.OptionContext (self.get_parameter_string ()) + group = gobject.OptionGroup (None, None, None, self.__handle_option) + context.set_main_group (group) + group.add_entries (self.__entries) + + try: + context.parse (argv) + except gobject.GError, exc: + raise OptionError (exc.message) + + self.handle_parse_complete () + + def get_parameter_string (self): + + raise NotImplementedError ("derived classes must override this method") + + def handle_parse_complete (self): + + pass + +class LogOptionParser (OptionParser): + + """Like OptionParser, but adds a --log-level option.""" + + def __init__ (self, *a, **kw): + + OptionParser.__init__ (self, *a, **kw) + + # TODO: Re-evaluate usage of log levels to use less of them. Like + # unifying warning, error and critical. + + self.add_option ("log-level", "l", + "%s (debug, info, warning, error, critical)" + % (_("Enable logging"),), + "LEVEL", self.parse_log_level) + + @staticmethod + def parse_log_level (arg): + + try: + level = int (arg) + except ValueError: + level = {"off" : logging.NOTSET, + "none" : logging.NOTSET, + "debug" : logging.DEBUG, + "info" : logging.INFO, + "warning" : logging.WARNING, + "error" : logging.ERROR, + "critical" : logging.CRITICAL}.get (arg.strip ().lower ()) + if level is None: + return logging.NOTSET + else: + return level + else: + if level < 0: + level = 0 + elif level > 5: + level = 5 + return {0 : logging.NOTSET, + 1 : logging.DEBUG, + 2 : logging.INFO, + 3 : logging.WARNING, + 4 : logging.ERROR, + 5 : logging.CRITICAL}[level] + +def _init_excepthooks (): + + ExceptHookManager.setup () + ExceptHookManager.register_handler (ExitOnInterruptExceptionHandler ()) + +def _init_paths (paths): + + paths.ensure_setup () + +def _init_locale (gettext_domain = None): + + if Paths.locale_dir and gettext_domain is not None: + try: + locale.setlocale (locale.LC_ALL, "") + except locale.Error, exc: + from warnings import warn + warn ("locale error: %s" % (exc,), + RuntimeWarning, + stacklevel = 2) + Paths.locale_dir = None + else: + gettext.bindtextdomain (gettext_domain, Paths.locale_dir) + gettext.textdomain (gettext_domain) + gettext.bind_textdomain_codeset (gettext_domain, "UTF-8") + +def _init_options (option_parser = None): + + if option_parser is None: + return {} + + try: + option_parser.parse (sys.argv) + except OptionError, exc: + print >> sys.stderr, exc.args[0] + sys.exit (1) + + return option_parser.options + +def _init_logging (level = logging.NOTSET): + + logging.basicConfig (level = level, + format = '%(asctime)s.%(msecs)03d %(levelname)8s %(name)20s: %(message)s', + datefmt = '%H:%M:%S') + + logger = logging.getLogger ("main") + logger.debug ("logging at level %s", logging.getLevelName (level)) + logger.info ("using Python %i.%i.%i %s %i", *sys.version_info) + +def main (option_parser = None, gettext_domain = None, paths = None): + + # FIXME: + global Paths + Paths = paths + + _init_excepthooks () + _init_paths (paths) + _init_locale (gettext_domain) + options = _init_options (option_parser) + try: + log_level = options["log_level"] + except KeyError: + _init_logging () + else: + _init_logging (log_level) + + try: + options["main"] () + finally: + logging.shutdown () diff --git a/debug-viewer/GstDebugViewer/Common/__init__.py b/debug-viewer/GstDebugViewer/Common/__init__.py new file mode 100644 index 0000000000..2dc1d5c89a --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8; mode: python; -*- +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer development utilities common module""" + diff --git a/debug-viewer/GstDebugViewer/Common/utils.py b/debug-viewer/GstDebugViewer/Common/utils.py new file mode 100644 index 0000000000..95f491e703 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Common/utils.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Inspector - Multimedia system plugin introspection +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, see . + +"""Misc utilities.""" + +import os +import logging +import subprocess as _subprocess + +class SingletonMeta (type): + + def __init__ (cls, name, bases, dict_): + + from weakref import WeakValueDictionary + + super (SingletonMeta, cls).__init__ (name, bases, dict_) + + cls._singleton_instances = WeakValueDictionary () + + def __call__ (cls, *a, **kw): + + kw_key = tuple (sorted (kw.iteritems ())) + + try: + obj = cls._singleton_instances[a + kw_key] + except KeyError: + obj = super (SingletonMeta, cls).__call__ (*a, **kw) + cls._singleton_instances[a + kw_key] = obj + return obj + +def gettext_cache (): + + """Return a callable object that operates like gettext.gettext, but is much + faster when a string is looked up more than once. This is very useful in + loops, where calling gettext.gettext can quickly become a major performance + bottleneck.""" + + from gettext import gettext + + d = {} + + def gettext_cache_access (s): + + if not s in d: + d[s] = gettext (s) + return d[s] + + return gettext_cache_access + +class ClassProperty (property): + + "Like the property class, but also invokes the getter for class access." + + def __init__ (self, fget = None, fset = None, fdel = None, doc = None): + + property.__init__ (self, fget, fset, fdel, doc) + + self.__fget = fget + + def __get__ (self, obj, obj_class = None): + + ret = property.__get__ (self, obj, obj_class) + if ret == self: + return self.__fget (None) + else: + return ret + +class _XDGClass (object): + + """Partial implementation of the XDG Base Directory specification v0.6. + + http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html""" + + def __init__ (self): + + self._add_base_dir ("DATA_HOME", "~/.local/share") + self._add_base_dir ("CONFIG_HOME", "~/.config") + self._add_base_dir ("CACHE_HOME", "~/.cache") + + def _add_base_dir (self, name, default): + + dir = os.environ.get ("XDG_%s" % (name,)) + if not dir: + dir = os.path.expanduser (os.path.join (*default.split ("/"))) + + setattr (self, name, dir) + +XDG = _XDGClass () + +class SaveWriteFile (object): + + def __init__ (self, filename, mode = "wt"): + + from tempfile import mkstemp + + self.logger = logging.getLogger ("tempfile") + + dir = os.path.dirname (filename) + base_name = os.path.basename (filename) + temp_prefix = "%s-tmp" % (base_name,) + + if dir: + # Destination dir differs from current directory, ensure that it + # exists: + try: + os.makedirs (dir) + except OSError: + pass + + self.clean_stale (dir, temp_prefix) + + fd, temp_name = mkstemp (dir = dir, prefix = temp_prefix) + + self.target_name = filename + self.temp_name = temp_name + self.real_file = os.fdopen (fd, mode) + + def __enter__ (self): + + return self + + def __exit__ (self, *exc_args): + + if exc_args == (None, None, None,): + self.close () + else: + self.discard () + + def __del__ (self): + + try: + self.discard () + except AttributeError: + # If __init__ failed, self has no real_file attribute. + pass + + def __close_real (self): + + if self.real_file: + self.real_file.close () + self.real_file = None + + def clean_stale (self, dir, temp_prefix): + + from time import time + from glob import glob + + now = time () + pattern = os.path.join (dir, "%s*" % (temp_prefix,)) + + for temp_filename in glob (pattern): + mtime = os.stat (temp_filename).st_mtime + if now - mtime > 3600: + self.logger.info ("deleting stale temporary file %s", + temp_filename) + try: + os.unlink (temp_filename) + except EnvironmentError, exc: + self.logger.warning ("deleting stale temporary file " + "failed: %s", exc) + + def tell (self, *a, **kw): + + return self.real_file.tell (*a, **kw) + + def write (self, *a, **kw): + + return self.real_file.write (*a, **kw) + + def close (self): + + self.__close_real () + + if self.temp_name: + try: + os.rename (self.temp_name, self.target_name) + except OSError, exc: + import errno + if exc.errno == errno.EEXIST: + # We are probably on windows. + os.unlink (self.target_name) + os.rename (self.temp_name, self.target_name) + self.temp_name = None + + def discard (self): + + self.__close_real () + + if self.temp_name: + + try: + os.unlink (self.temp_name) + except EnvironmentError, exc: + self.logger.warning ("deleting temporary file failed: %s", exc) + self.temp_name = None + +class TeeWriteFile (object): + + # TODO Py2.5: Add context manager methods. + + def __init__ (self, *file_objects): + + self.files = list (file_objects) + + def close (self): + + for file in self.files: + file.close () + + def flush (self): + + for file in self.files: + file.flush () + + def write (self, string): + + for file in self.files: + file.write (string) + + def writelines (self, lines): + + for file in self.files: + file.writelines (lines) + +class FixedPopen (_subprocess.Popen): + + def __init__ (self, args, **kw): + + # Unconditionally specify all descriptors as redirected, to + # work around Python bug #1358527 (which is triggered for + # console-less applications on Windows). + + close = [] + + for name in ("stdin", "stdout", "stderr",): + target = kw.get (name) + if not target: + kw[name] = _subprocess.PIPE + close.append (name) + + _subprocess.Popen.__init__ (self, args, **kw) + + for name in close: + fp = getattr (self, name) + fp.close () + setattr (self, name, None) + +class DevhelpError (EnvironmentError): + + pass + +class DevhelpUnavailableError (DevhelpError): + + pass + +class DevhelpClient (object): + + def available (self): + + try: + self.version () + except DevhelpUnavailableError: + return False + else: + return True + + def version (self): + + return self._invoke ("--version") + + def search (self, entry): + + self._invoke_no_interact ("-s", entry) + + def _check_os_error (self, exc): + + import errno + if exc.errno == errno.ENOENT: + raise DevhelpUnavailableError () + + def _invoke (self, *args): + + from subprocess import PIPE + + try: + proc = FixedPopen (("devhelp",) + args, + stdout = PIPE) + except OSError, exc: + self._check_os_error (exc) + raise + + out, err = proc.communicate () + + if proc.returncode is not None and proc.returncode != 0: + raise DevhelpError ("devhelp exited with status %i" + % (proc.returncode,)) + return out + + def _invoke_no_interact (self, *args): + + from subprocess import PIPE + + try: + proc = FixedPopen (("devhelp",) + args) + except OSError, exc: + self._check_os_error (exc) + raise + diff --git a/debug-viewer/GstDebugViewer/Data.py b/debug-viewer/GstDebugViewer/Data.py new file mode 100644 index 0000000000..c9c7113ddc --- /dev/null +++ b/debug-viewer/GstDebugViewer/Data.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer debug viewer data module""" + +import logging +import re + +# Nanosecond resolution (like gst.SECOND) +SECOND = 1000000000 + +def time_args (ts): + + secs = ts // SECOND + + return "%i:%02i:%02i.%09i" % (secs // 60**2, + secs // 60 % 60, + secs % 60, + ts % SECOND,) + +def time_args_no_hours (ts): + + secs = ts // SECOND + + return "%02i:%02i.%09i" % (secs // 60, + secs % 60, + ts % SECOND,) + +def parse_time (st): + + """Parse time strings that look like "0:00:00.0000000".""" + + h, m, s = st.split (":") + secs, subsecs = s.split (".") + + return (long ((int (h) * 60**2 + int (m) * 60) * SECOND) + + long (secs) * SECOND + long (subsecs)) + +class DebugLevel (int): + + __names = ["NONE", "ERROR", "WARNING", "INFO", "DEBUG", "LOG"] + __instances = {} + + def __new__ (cls, level): + + try: + level_int = int (level) + except (ValueError, TypeError,): + try: + level_int = cls.__names.index (level.upper ()) + except ValueError: + raise ValueError ("no debug level named %r" % (level,)) + if level_int in cls.__instances: + return cls.__instances[level_int] + else: + new_instance = int.__new__ (cls, level_int) + new_instance.name = cls.__names[level_int] + cls.__instances[level_int] = new_instance + return new_instance + + def __repr__ (self): + + return "<%s %s (%i)>" % (type (self).__name__, self.__names[self], self,) + + def higher_level (self): + + if self == len (self.__names) - 1: + raise ValueError ("already the highest debug level") + + return DebugLevel (self + 1) + + def lower_level (self): + + if self == 0: + raise ValueError ("already the lowest debug level") + + return DebugLevel (self - 1) + +DebugLevelNone = DebugLevel ("NONE") +DebugLevelError = DebugLevel ("ERROR") +DebugLevelWarning = DebugLevel ("WARNING") +DebugLevelInfo = DebugLevel ("INFO") +DebugLevelDebug = DebugLevel ("DEBUG") +DebugLevelLog = DebugLevel ("LOG") + +# For stripping color codes: +_escape = re.compile ("\x1b\\[[0-9;]*m") +def strip_escape (s): + + return _escape.sub ("", s) + +def default_log_line_regex_ (): + + # "DEBUG " + LEVEL = "([A-Z]+) +" + # "0x8165430 " + THREAD = r"(0x[0-9a-f]+) +" #r"\((0x[0-9a-f]+) - " + # "0:00:00.777913000 " + #TIME = r"([0-9]+:[0-9][0-9]:[0-9][0-9]\.[0-9]+) +" + TIME = " +" # Only eating whitespace before PID away, we parse timestamps + # without regex. + CATEGORY = "([A-Za-z_-]+) +" # "GST_REFCOUNTING ", "flacdec " + # " 3089 " + PID = r"([0-9]+) +" + FILENAME = r"([^:]+):" + LINE = r"([0-9]+):" + FUNCTION = "([A-Za-z0-9_]+):" + # FIXME: When non-g(st)object stuff is logged with *_OBJECT (like + # buffers!), the address is printed *without* <> brackets! + OBJECT = "(?:<([^>]+)>)?" + MESSAGE = " (.+)" + + expressions = [TIME, PID, THREAD, LEVEL, CATEGORY, FILENAME, LINE, FUNCTION, + OBJECT, MESSAGE] +## expressions = [LEVEL, THREAD, TIME, CATEGORY, PID, FILENAME, LINE, +## FUNCTION, OBJECT, MESSAGE] + + return expressions + +def default_log_line_regex (): + + expressions = default_log_line_regex_ () + return re.compile ("".join (expressions)) + +class Producer (object): + + def __init__ (self): + + self.consumers = [] + + def have_load_started (self): + + for consumer in self.consumers: + consumer.handle_load_started () + + def have_load_finished (self): + + for consumer in self.consumers: + consumer.handle_load_finished () + +class LineCache (Producer): + + _lines_per_iteration = 1000 + + def __init__ (self, fileobj, dispatcher): + + Producer.__init__ (self) + + self.logger = logging.getLogger ("linecache") + + self.offsets = [] + self.dispatcher = dispatcher + + import mmap + self.__fileobj = mmap.mmap (fileobj.fileno (), 0, prot = mmap.PROT_READ) + + self.__fileobj.seek (0, 2) + self.__file_size = self.__fileobj.tell () + self.__fileobj.seek (0) + + def start_loading (self): + + self.logger.debug ("dispatching load process") + self.have_load_started () + self.dispatcher (self.__process ()) + + def get_progress (self): + + return float (self.__fileobj.tell ()) / self.__file_size + + def __process (self): + + offsets = self.offsets + readline = self.__fileobj.readline + tell = self.__fileobj.tell + + self.__fileobj.seek (0) + limit = self._lines_per_iteration + i = 0 + while True: + offset = tell () + line = readline () + if not line: + break + if not line.strip (): + # Ignore empty lines, especially the one established by the + # final newline at the end: + continue + # FIXME: We need to handle foreign lines separately! + if line[1] != ":" or line[4] != ":" or line[7] != ".": + # No timestamp at start, ignore line: + continue + offsets.append (offset) + i += 1 + if i == limit: + yield True + + self.have_load_finished () + yield False + +class LogFile (Producer): + + def __init__ (self, filename, dispatcher): + + Producer.__init__ (self) + + self.logger = logging.getLogger ("logfile") + + self.fileobj = file (filename, "rb") + self.line_cache = LineCache (self.fileobj, dispatcher) + self.line_cache.consumers.append (self) + + def start_loading (self): + + self.logger.debug ("starting load") + self.line_cache.start_loading () + + def get_load_progress (self): + + return self.line_cache.get_progress () + + def handle_load_started (self): + + # Chain up to our consumers: + self.have_load_started () + + def handle_load_finished (self): + + # Chain up to our consumers: + self.have_load_finished () + diff --git a/debug-viewer/GstDebugViewer/GUI.py b/debug-viewer/GstDebugViewer/GUI.py new file mode 100755 index 0000000000..3acbc87376 --- /dev/null +++ b/debug-viewer/GstDebugViewer/GUI.py @@ -0,0 +1,1196 @@ +#!/usr/bin/python +# -*- coding: utf-8; mode: python; -*- +## +## gst-debug-viewer.py: GStreamer debug log viewer +## +## Copyright (C) 2006 Rene Stadler +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program 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 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., 51 Franklin Street, Fifth Floor, +## Boston, MA 02110-1301 USA +## + +__author__ = u"René Stadler " +__version__ = "0.1" + +def _ (s): + return s + +import sys +import os +import os.path +from operator import add +from sets import Set +import logging + +import pygtk +pygtk.require ("2.0") + +import gobject +import gtk +import gtk.glade + +## import gnome # FIXME + +import GstDebugViewer.Common.Data +import GstDebugViewer.Common.GUI +import GstDebugViewer.Common.Main +Common = GstDebugViewer.Common +from GstDebugViewer.Common import utils + +from GstDebugViewer import Data, Main + +## # Keep in sync with gst-inspector.py! +## class MetaModel (gobject.GObjectMeta): + +## def __init__ (cls, name, bases, dict): + +## super (MetaModel, cls).__init__ (name, bases, dict) + +## columns = cls.columns +## column_types = [] +## def gen (): +## i = 0 +## it = iter (columns) +## while True: +## yield (i, it.next (), it.next (),) +## i += 1 +## for col_index, col_name, col_type in gen (): +## setattr (cls, col_name, col_index) +## column_types.append (col_type) + +## cls.column_types = tuple (column_types) + +class LazyLogModel (gtk.GenericTreeModel): + + __metaclass__ = Common.GUI.MetaModel + + columns = ("COL_LEVEL", str, + "COL_PID", int, + "COL_THREAD", gobject.TYPE_UINT64, + "COL_TIME", gobject.TYPE_UINT64, + "COL_CATEGORY", str, + "COL_FILENAME", str, + "COL_LINE", int, + "COL_FUNCTION", str, + "COL_OBJECT", str, + "COL_MESSAGE", str,) + + def __init__ (self, log_obj = None): + + gtk.GenericTreeModel.__init__ (self) + + ##self.props.leak_references = False + + self.__log_obj = log_obj + + self.__line_regex = Data.default_log_line_regex () + self.__line_match_order = (self.COL_TIME, + self.COL_PID, + self.COL_THREAD, + self.COL_LEVEL, + self.COL_CATEGORY, + self.COL_FILENAME, + self.COL_LINE, + self.COL_FUNCTION, + self.COL_OBJECT, + self.COL_MESSAGE,) + self.__line_offsets = [] + self.__line_cache = {} + + if log_obj: + self.set_log (log_obj) + + def set_log (self, log_obj): + + self.__line_cache.clear () + self.__line_offsets = log_obj.line_cache.offsets + self.__fileobj = log_obj.fileobj + + def __ensure_cached (self, line_index): + + if line_index in self.__line_cache: + return + + line_offset = self.__line_offsets[line_index] + + if line_offset == 0: + self.__fileobj.seek (0) + line = self.__fileobj.readline () + else: + # Seek a bit further backwards to verify that offset (still) points + # to the beginning of a line: + self.__fileobj.seek (line_offset - len (os.linesep)) + line_start = (self.__fileobj.readline () == os.linesep) + if not line_start: + # FIXME: We should re-read the file instead! + raise ValueError ("file changed!") + line = self.__fileobj.readline () + + ts_len = 17 + ts = Data.parse_time (line[:ts_len]) + match = self.__line_regex.match (line[ts_len:-len (os.linesep)]) + if match is None: + # FIXME? + groups = [ts, 0, 0, "?", "", "", 0, "", "", line[ts_len:-len (os.linesep)]] + else: + groups = [ts] + list (match.groups ()) + + # TODO: Figure out how much string interning can save here and how + # much run time speed it costs! + groups[1] = int (groups[1]) # pid + groups[2] = int (groups[2], 16) # thread pointer + groups[6] = int (groups[6]) # line + groups[8] = groups[8] or "" # object (optional) + + groups = [x[1] for x in sorted (zip (self.__line_match_order, + groups))] + self.__line_cache[line_index] = groups + + def on_get_flags (self): + + flags = gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST + + return flags + + def on_get_n_columns (self): + + return len (self.column_types) + + def on_get_column_type (self, index): + + return self.column_types[index] + + def on_get_iter (self, path): + + if len (path) > 1: + raise ValueError ("flat model") + + line_index = path[0] + + return line_index + + def on_get_path (self, rowref): + + return (rowref,) + + def on_get_value (self, rowref, column): + + if rowref >= len (self.__line_offsets): + return None + + self.__ensure_cached (rowref) + + return self.__line_cache[rowref][column] + + def on_iter_next (self, rowref): + + if rowref >= len (self.__line_offsets): + return None + else: + return rowref + 1 + + def on_iter_children (self, parent): + + return self.on_iter_nth_child (parent, 0) + + def on_iter_has_child (self, rowref): + + return False + + def on_iter_n_children (self, rowref): + + return len (self.__line_offsets) + + def on_iter_nth_child (self, parent, n): + + if parent or n >= len (self.__line_offsets): + return None + else: + return n ## self.__line_offsets[n] + + def on_iter_parent (self, child): + + return None + + def on_ref_node (self, rowref): + + pass + + def on_unref_node (self, rowref): + + pass + +class LogModel (gtk.ListStore): + + __metaclass__ = Common.GUI.MetaModel + + __gsignals__ = {"loading-progress" : (0, None, (float,),), + "loading-finished" : (0, None, (),),} + + columns = ("COL_LEVEL", str, + "COL_PID", int, + "COL_THREAD", gobject.TYPE_UINT64, + "COL_TIME", gobject.TYPE_UINT64, + "COL_CATEGORY", str, + "COL_FILENAME", str, + "COL_LINE", int, + "COL_FUNCTION", str, + "COL_OBJECT", str, + #"COL_MESSAGE_OFFSET", object,) + "COL_MESSAGE", str,) + + def __init__ (self): + + gtk.ListStore.__init__ (self, *self.column_types) + + self.match_line = default_log_line_regex () + self.match_column_order = (self.COL_TIME, + self.COL_PID, + self.COL_THREAD, + self.COL_LEVEL, + self.COL_CATEGORY, + self.COL_FILENAME, + self.COL_LINE, + self.COL_FUNCTION, + self.COL_OBJECT, + #self.COL_MESSAGE_OFFSET,) + self.COL_MESSAGE,) + + self.categories = Set () + self.objects = {} + + @staticmethod + def _filter_func (model, tree_iter): + + return True + + def filtered (self): + + filtered_model = self.filter_new () + filtered_model.set_visible_func (self._filter_func) + return filtered_model + + def add_parsed (self, args): + + category = args[4] + self.categories.add (category) + + object_name = args[8] + if object_name: + self.objects[object_name] = None + + #args[-1] = args[-1][:60] # FIXME: Message truncated + + list_iter = self.insert (-1) + try: + self.set (list_iter, *(reduce (add, + zip (self.match_column_order, + args)))) + except TypeError, exc: + for column_id, arg in zip (self.match_column_order, args): + try: + self.set (list_iter, column_id, arg) + except TypeError, exc: + # FIXME + print >> sys.stderr, str (exc) + print >> sys.stderr, "column: %i, arg: %r" % (column_id, arg) + + def parse_line (self, line, offset): + + if not line.endswith (os.linesep): + raise ValueError, "line does not end in a line separator" + + match = self.match_line.match (line[:-1]) + if match is None: + raise ValueError ("line does not match format") + groups = list (match.groups ()) + + groups[0] = parse_time (groups[0]) # time + groups[1] = int (groups[1]) # pid + groups[2] = int (groups[2], 16) # thread pointer + groups[6] = int (groups[6]) # line + groups[8] = groups[8] or "" # object (optional) + ##groups[9] = offset + match.start (10) + return groups + + def load_file (self, filename): + + file_class = file + if filename.endswith (".gz") or filename.endswith (".gzip"): + import gzip + file_class = gzip.GzipFile + elif filename.endswith (".bz") or filename.endswith (".bz2"): + import bz2 + file_class = bz2.BZ2File + + self.fp = file_class (filename, "r") + self.size = os.path.getsize (filename) + gobject.idle_add (self.load_deferred ().next) + + def load_deferred (self): + + yield True + + UPDATE = 1000 + + i = 0 + while True: + offset = self.fp.tell () + line = self.fp.readline () + if not line: + break + + if not line.strip (): + continue + + line = strip_escape (line) + + try: + self.add_parsed (self.parse_line (line, offset)) + except ValueError, exc: + print >> sys.stderr, "Cannot parse %s (%s)" % (repr (line), exc) + + i += 1 + if i % UPDATE == 0: + self.emit ("loading-progress", min (1.0, float (self.fp.tell ()) / self.size)) + yield True + + if i % UPDATE != 0: + self.emit ("loading-progress", 1.0) + + self.emit ("loading-finished") + + yield False + +# Sync with gst-inspector! +class Column (object): + + """A single list view column, managed by a ColumnManager instance.""" + + name = None + id = None + label_header = None + get_modify_func = None + get_sort_func = None + + def __init__ (self): + + view_column = gtk.TreeViewColumn (self.label_header) + view_column.props.reorderable = True + + self.view_column = view_column + +# FIXME: Merge with gst-inspector? +class SizedColumn (Column): + + default_size = None + + def compute_default_size (self, view, model): + + return None + +# Sync with gst-inspector? +class TextColumn (SizedColumn): + + def __init__ (self): + + Column.__init__ (self) + + column = self.view_column + cell = gtk.CellRendererText () + column.pack_start (cell) + + if not self.get_modify_func: + column.add_attribute (cell, "text", self.id) + else: + modify_func = self.get_modify_func () + id_ = self.id + def cell_data_func (column, cell, model, tree_iter): + cell.props.text = modify_func (model.get (tree_iter, id_)[0]) + column.set_cell_data_func (cell, cell_data_func) + + column.props.resizable = True + column.set_sort_column_id (self.id) + + def compute_default_size (self, view, model): + + values = self.get_values_for_size () + if not values: + return SizedColumn.compute_default_size (self, view, model) + + cell = self.view_column.get_cells ()[0] + + if self.get_modify_func is not None: + format = self.get_modify_func () + else: + def identity (x): + return x + format = identity + max_width = 0 + for value in values: + cell.props.text = format (value) + max_width = max (max_width, cell.get_size (view, None)[2]) + + return max_width + + def get_values_for_size (self): + + return () + +class TimeColumn (TextColumn): + + name = "time" + label_header = _("Time") + id = LazyLogModel.COL_TIME + + @staticmethod + def get_modify_func (): + + time_args = Data.time_args + def format_time (value): + # TODO: This is hard coded to omit hours as well as the last 3 + # digits at the end, since current gst uses g_get_current_time, + # which has microsecond precision only. + return time_args (value)[2:-3] + + return format_time + + def get_values_for_size (self): + + # TODO: Use more than just 0:00:00.000000000 to account for funny fonts + # maybe? Well, or use monospaced... + values = [0] + + return values + +class LevelColumn (TextColumn): + + name = "level" + label_header = _("L") + id = LazyLogModel.COL_LEVEL + + @staticmethod + def get_modify_func (): + + def format_level (value): + if value is None: + # FIXME: Should never be None! + return "" + return value[0] + + return format_level + + def get_values_for_size (self): + + values = ["LOG", "DEBUG", "INFO", "WARN", "ERROR"] + + return values + +class ThreadColumn (TextColumn): + + name = "thread" + label_header = _("Thread") + id = LazyLogModel.COL_THREAD + + @staticmethod + def get_modify_func (): + + def format_thread (value): + return "0x%07x" % (value,) + + return format_thread + + def get_values_for_size (self): + + # TODO: Same as for TimeColumn. There is no guarantee that aaaaaaaa is + # the widest string; use fixed font or come up with something better. + + return [int ("aaaaaaaaa", 16)] + +class CategoryColumn (TextColumn): + + name = "category" + label_header = _("Category") + id = LazyLogModel.COL_CATEGORY + + def get_values_for_size (self): + + return ["GST_LONG_CATEGORY", "somelongelement"] + +class FunctionColumn (TextColumn): + + name = "function" + label_header = _("Function") + id = LazyLogModel.COL_FUNCTION + + def get_values_for_size (self): + + return ["gst_this_should_be_enough"] + +## class FullCodeLocation (TextColumn): + +## name = "code-location" +## label_header = _("Code Location") +## id = LazyLogModel.COL_FILENAME + +## def get_values_for_size (self): + +## return ["gstwhateverfile.c:1234"] + +class ObjectColumn (TextColumn): + + name = "object" + label_header = _("Object") + id = LazyLogModel.COL_OBJECT + + def get_values_for_size (self): + + return ["longobjectname00"] + +class MessageColumn (TextColumn): + + name = "message" + label_header = _("Message") + id = LazyLogModel.COL_MESSAGE + +# Sync with gst-inspector! +class ColumnManager (Common.GUI.Manager): + + column_classes = () + + @classmethod + def iter_item_classes (cls): + + return iter (cls.column_classes) + + def __init__ (self): + + self.view = None + self.actions = None + self.__columns_changed_id = None + self.columns = [] + self.column_order = list (self.column_classes) + + self.action_group = gtk.ActionGroup ("ColumnActions") + + def make_entry (col_class): + return ("show-%s-column" % (col_class.name,), + None, + col_class.label_header, + None, + None, + None, + True,) + + entries = [make_entry (cls) for cls in self.column_classes] + self.action_group.add_toggle_actions (entries) + + def iter_items (self): + + return iter (self.columns) + + def attach (self): + + for col_class in self.column_classes: + action = self.get_toggle_action (col_class) + if action.props.active: + self._add_column (col_class ()) + action.connect ("toggled", + self.__handle_show_column_action_toggled, + col_class.name) + + self.__columns_changed_id = self.view.connect ("columns-changed", + self.__handle_view_columns_changed) + + def detach (self): + + if self.__columns_changed_id is not None: + self.view.disconnect (self.__columns_changed_id) + self.__columns_changed_id = None + + def attach_sort (self): + + sort_model = self.view.props.model + + # Inform the sorted tree model of any custom sorting functions. + for col_class in self.column_classes: + if col_class.get_sort_func: + sort_func = col_class.get_sort_func () + sort_model.set_sort_func (col_class.id, sort_func) + + def enable_sort (self): + + sort_model = self.view.props.model + + if sort_model: + self.logger.debug ("activating sort") + sort_model.set_sort_column_id (*self.default_sort) + self.default_sort = None + else: + self.logger.debug ("not activating sort (no model set)") + + def disable_sort (self): + + self.logger.debug ("deactivating sort") + + sort_model = self.view.props.model + + self.default_sort = tree_sortable_get_sort_column_id (sort_model) + + sort_model.set_sort_column_id (TREE_SORTABLE_UNSORTED_COLUMN_ID, + gtk.SORT_ASCENDING) + + def get_toggle_action (self, column_class): + + action_name = "show-%s-column" % (column_class.name,) + return self.action_group.get_action (action_name) + + def get_initial_column_order (self): + + return tuple (self.column_classes) + + def _add_column (self, column): + + name = column.name + pos = self.__get_column_insert_position (column) + if self.view.props.fixed_height_mode: + column.view_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + self.columns.insert (pos, column) + self.view.insert_column (column.view_column, pos) + + def _remove_column (self, column): + + self.columns.remove (column) + self.view.remove_column (column.view_column) + + def __get_column_insert_position (self, column): + + col_class = self.find_item_class (name = column.name) + pos = self.column_order.index (col_class) + before = self.column_order[:pos] + shown_names = [col.name for col in self.columns] + for col_class in before: + if not col_class.name in shown_names: + pos -= 1 + return pos + + def __iter_next_hidden (self, column_class): + + pos = self.column_order.index (column_class) + rest = self.column_order[pos + 1:] + for next_class in rest: + try: + self.find_item (name = next_class.name) + except KeyError: + # No instance -- the column is hidden. + yield next_class + else: + break + + def __handle_show_column_action_toggled (self, toggle_action, name): + + if toggle_action.props.active: + try: + # This should fail. + column = self.find_item (name = name) + except KeyError: + col_class = self.find_item_class (name = name) + self._add_column (col_class ()) + else: + # Out of sync for some reason. + return + else: + try: + column = self.find_item (name = name) + except KeyError: + # Out of sync for some reason. + return + else: + self._remove_column (column) + + def __handle_view_columns_changed (self, element_view): + + view_columns = element_view.get_columns () + new_visible = [self.find_item (view_column = column) + for column in view_columns] + + # We only care about reordering here. + if len (new_visible) != len (self.columns): + return + + if new_visible != self.columns: + + new_order = [] + for column in new_visible: + col_class = self.find_item_class (name = column.name) + new_order.append (col_class) + new_order.extend (self.__iter_next_hidden (col_class)) + + names = (column.name for column in new_visible) + self.logger.debug ("visible columns reordered: %s", + ", ".join (names)) + + self.columns[:] = new_visible + self.column_order[:] = new_order + +class ViewColumnManager (ColumnManager): + + column_classes = (TimeColumn, LevelColumn, ThreadColumn, CategoryColumn, + FunctionColumn, ObjectColumn, MessageColumn,) + + def __init__ (self, *a, **kw): + + ColumnManager.__init__ (self, *a, **kw) + + self.logger = logging.getLogger ("ui.columns") + + def attach (self, view): + + self.view = view + view.connect ("notify::model", self.__handle_notify_model) + + ColumnManager.attach (self) + + def size_column (self, column, view, model): + + if column.default_size is None: + default_size = column.compute_default_size (view, model) + else: + default_size = column.default_size + # FIXME: Abstract away fixed size setting in Column class! + if default_size is None: + # Dummy fallback: + column.view_column.props.fixed_width = 50 + self.logger.warning ("%s column does not implement default size", column.name) + else: + column.view_column.props.fixed_width = default_size + + def _add_column (self, column): + + result = ColumnManager._add_column (self, column) + model = self.view.props.model + self.size_column (column, self.view, model) + return result + + def _remove_column (self, column): + + column.default_size = column.view_column.props.fixed_width + return ColumnManager._remove_column (self, column) + + def __handle_notify_model (self, view, gparam): + + model = self.view.props.model + for column in self.iter_items (): + self.size_column (column, view, model) + +class Window (object): + + def __init__ (self, app): + + self.logger = logging.getLogger ("ui.window") + self.app = app + + self.sentinels = [] + + self.progress_bar = None + self.update_progress_id = None + + self.window_state = Common.GUI.WindowState () + self.column_manager = ViewColumnManager () + + self.actions = Common.GUI.Actions () + + group = gtk.ActionGroup ("MenuActions") + group.add_actions ([("FileMenuAction", None, _("_File")), + ("ViewMenuAction", None, _("_View")), + ("ViewColumnsMenuAction", None, _("View columns")), + ("HelpMenuAction", None, _("_Help"))]) + self.actions.add_group (group) + + group = gtk.ActionGroup ("WindowActions") + group.add_actions ([("new-window", gtk.STOCK_NEW, _("_New Window"), "N"), + ("open-file", gtk.STOCK_OPEN, _("_Open File"), "O"), + ("close-window", gtk.STOCK_CLOSE, _("Close _Window"), "W"), + ("show-about", gtk.STOCK_ABOUT, None)]) + ## group.add_toggle_actions ([("show-line-density", None, _("Line _Density"), "D")]) + self.actions.add_group (group) + + group = gtk.ActionGroup ("RowActions") + group.add_actions ([("edit-copy-line", gtk.STOCK_COPY, _("Copy line"), "C"), + ("edit-copy-message", gtk.STOCK_COPY, _("Copy message"))]) + self.actions.add_group (group) + + self.actions.add_group (self.column_manager.action_group) + + self.file = None + self.log_model = LazyLogModel () +## self.log_model.connect ("loading-progress", self.handle_load_progress) +## self.log_model.connect ("loading-finished", self.handle_load_finished) + + glade_filename = os.path.join (Main.Paths.data_dir, "gst-debug-viewer.glade") + self.widget_factory = Common.GUI.WidgetFactory (glade_filename) + self.widgets = self.widget_factory.make ("main_window") + + ui_filename = os.path.join (Main.Paths.data_dir, + "gst-debug-viewer.ui") + self.ui_factory = Common.GUI.UIFactory (ui_filename, self.actions) + + self.ui_manager = ui = self.ui_factory.make () + menubar = ui.get_widget ("/ui/menubar") + self.widgets.vbox_main.pack_start (menubar, False, False, 0) + self.view_popup = ui.get_widget ("/ui/menubar/ViewMenu").get_submenu () + + self.gtk_window = self.widgets.main_window + self.gtk_window.add_accel_group (ui.get_accel_group ()) + self.log_view = self.widgets.log_view + self.log_view.drag_dest_unset () + self.log_view.props.fixed_height_mode = True + #self.log_view.props.model = self.log_model.filtered () + + self.log_view.connect ("button-press-event", self.handle_log_view_button_press_event) + + self.attach () + self.column_manager.attach (self.log_view) + +## cell = gtk.CellRendererText () +## column = gtk.TreeViewColumn ("Level", cell, +## text = self.log_model.COL_LEVEL) +## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED +## column.props.fixed_width = 80 # FIXME +## self.log_view.append_column (column) + +## cell = gtk.CellRendererText () +## cell.props.family = "monospace" +## cell.props.family_set = True +## column = gtk.TreeViewColumn ("Time", cell) +## #text = self.log_model.COL_TIME) +## column.set_cell_data_func (cell, self._timestamp_cell_data_func) +## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED +## column.props.fixed_width = 180 # FIXME +## self.log_view.append_column (column) + +## cell = gtk.CellRendererText () +## column = gtk.TreeViewColumn ("Category", cell, +## text = self.log_model.COL_CATEGORY) +## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED +## column.props.fixed_width = 150 # FIXME +## self.log_view.append_column (column) + +## cell = gtk.CellRendererText () +## column = gtk.TreeViewColumn ("Function", cell, +## text = self.log_model.COL_FUNCTION) +## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED +## column.props.fixed_width = 180 # FIXME +## self.log_view.append_column (column) + +## cell = gtk.CellRendererText () +## column = gtk.TreeViewColumn ("Object", cell, +## text = self.log_model.COL_OBJECT) +## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED +## column.props.fixed_width = 150 # FIXME +## self.log_view.append_column (column) + +## cell = gtk.CellRendererText () +## column = gtk.TreeViewColumn ("Message", cell, text = self.log_model.COL_MESSAGE) +## ##column.set_cell_data_func (cell, self._message_cell_data_func) +## column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED +## self.log_view.append_column (column) + + def get_top_attach_point (self): + + return self.widgets.vbox_main + + def attach (self): + + self.window_state.attach (window = self.gtk_window, state = self.app.state) + + self.clipboard = gtk.Clipboard (self.gtk_window.get_display (), + gtk.gdk.SELECTION_CLIPBOARD) + + for action_name in ("new-window", "open-file", "close-window", + "edit-copy-line", "edit-copy-message", + "show-about",): + name = action_name.replace ("-", "_") + action = getattr (self.actions, name) + handler = getattr (self, "handle_%s_action_activate" % (name,)) + action.connect ("activate", handler) + + self.gtk_window.connect ("delete-event", self.handle_window_delete_event) + + self.features = [] + for plugin_feature in self.app.iter_plugin_features (): + feature = plugin_feature () + feature.attach (self) + self.features.append (feature) + + def detach (self): + + self.window_state.detach () + + def get_active_line (self): + + selection = self.log_view.get_selection () + model, tree_iter = selection.get_selected () + if tree_iter is None: + raise ValueError ("no line selected") + return self.log_model.get (tree_iter, *LazyLogModel.column_ids) + + def close (self, *a, **kw): + + self.detach () + + self.gtk_window.hide () + + self.app.close_window (self) + + def handle_window_delete_event (self, window, event): + + self.actions.close_window.activate () + + def handle_new_window_action_activate (self, action): + + pass + + def handle_open_file_action_activate (self, action): + + dialog = gtk.FileChooserDialog (None, self.gtk_window, + gtk.FILE_CHOOSER_ACTION_OPEN, + (gtk.STOCK_CANCEL, 1, + gtk.STOCK_OPEN, 0,)) + response = dialog.run () + dialog.hide () + if response == 0: + self.set_log_file (dialog.get_filename ()) + dialog.destroy () + + def handle_close_window_action_activate (self, action): + + self.close () + + def handle_edit_copy_line_action_activate (self, action): + + self.logger.warning ("FIXME") + return + col_id = self.log_model.COL_ + self.clipboard.set_text (self.get_active_line ()[col_id]) + + def handle_edit_copy_message_action_activate (self, action): + + col_id = self.log_model.COL_MESSAGE + self.clipboard.set_text (self.get_active_line ()[col_id]) + + def handle_show_about_action_activate (self, action): + + from GstDebugViewer import version + + dialog = self.widget_factory.make_one ("about_dialog") + dialog.props.version = version + dialog.run () + dialog.destroy () + + @staticmethod + def _timestamp_cell_data_func (column, renderer, model, tree_iter): + + ts = model.get (tree_iter, LogModel.COL_TIME)[0] + renderer.props.text = Data.time_args (ts) + + def _message_cell_data_func (self, column, renderer, model, tree_iter): + + offset = model.get (tree_iter, LogModel.COL_MESSAGE_OFFSET)[0] + self.log_file.seek (offset) + renderer.props.text = strip_escape (self.log_file.readline ().strip ()) + + def set_log_file (self, filename): + + self.logger.debug ("setting log file %r", filename) + + dispatcher = Common.Data.GSourceDispatcher () + self.log_file = Data.LogFile (filename, dispatcher) + self.log_file.consumers.append (self) + self.log_file.start_loading () + + def handle_log_view_button_press_event (self, view, event): + + if event.button != 3: + return False + + self.view_popup.popup (None, None, None, event.button, event.get_time ()) + return True + + def handle_load_started (self): + + self.logger.debug ("load has started") + + widgets = self.widget_factory.make ("progress_dialog") + dialog = widgets.progress_dialog + self.progress_dialog = dialog + self.progress_bar = widgets.progress_bar + dialog.set_transient_for (self.gtk_window) + dialog.show () + + self.update_progress_id = gobject.timeout_add (50, self.update_load_progress) + + def update_load_progress (self): + + if not self.progress_bar: + self.logger.debug ("progress window is gone, removing progress update timeout") + self.update_progress_id = None + return False + + progress = self.log_file.get_load_progress () + self.logger.debug ("update progress to %i%%", progress * 100) + self.progress_bar.props.fraction = progress + + return True + + def handle_load_finished (self): + + self.logger.debug ("load has finshed") + + if self.update_progress_id is not None: + gobject.source_remove (self.update_progress_id) + self.update_progress_id = None + + self.progress_dialog.hide () + self.progress_dialog.destroy () + self.progress_dialog = None + self.progress_bar = None + + ## parent_menu = self.glade_tree.get_widget ("view_categories") + ## sub_menu = gtk.Menu () + ## sub_menu.show () + ## parent_menu.set_submenu (sub_menu) +## for category in sorted (model.categories): +## item = gtk.CheckMenuItem (category, use_underline = False) +## item.props.active = True +## item.connect ("toggled", self.handle_view_category_toggled) +## item.show () +## sub_menu.append (item) + + self.log_model.set_log (self.log_file) + + for sentinel in self.sentinels: + sentinel () + + def idle_set (): + self.log_view.props.model = self.log_model #model.filtered () + return False + + gobject.idle_add (idle_set) + +class AppState (Common.GUI.AppState): + + geometry = Common.GUI.StateInt4 ("window-geometry") + maximized = Common.GUI.StateBool ("window-maximized") + +class App (object): + + def __init__ (self): + + self.load_plugins () + + self.attach () + + def load_plugins (self): + + from GstDebugViewer import Plugins + + self.plugins = list (Plugins.load ([os.path.dirname (Plugins.__file__)])) + + def iter_plugin_features (self): + + for plugin in self.plugins: + for feature in plugin.features: + yield feature + + def attach (self): + + state_filename = os.path.join (utils.XDG.CONFIG_HOME, "gst-debug-viewer", "state") + + self.state = AppState (state_filename) + + self.windows = [Window (self)] + + def detach (self): + + # TODO: If we take over deferred saving from the inspector, specify now + # = True here! + self.state.save () + + def run (self): + + try: + Common.Main.MainLoopWrapper (gtk.main, gtk.main_quit).run () + except: + raise + else: + self.detach () + + def close_window (self, window): + + # For some reason, going down takes some time for large files. Let's + # block until the window is hidden: + gobject.idle_add (gtk.main_quit) + gtk.main () + + gtk.main_quit () + +import time + +class TestParsingPerformance (object): + + def __init__ (self, filename): + + self.main_loop = gobject.MainLoop () + self.log_file = Data.LogFile (filename, DefaultDispatcher ()) + self.log_file.consumers.append (self) + + def start (self): + + self.log_file.start_loading () + + def handle_load_started (self): + + self.start_time = time.time () + + def handle_load_finished (self): + + diff = time.time () - self.start_time + print "line cache built in %0.1f ms" % (diff * 1000.,) + + self.start_time = time.time () + model = LazyLogModel (self.log_file) + for row in model: + pass + diff = time.time () - self.start_time + print "data parsed in %0.1f ms" % (diff * 1000.,) + +def main (): + + if len (sys.argv) > 1 and sys.argv[1] == "--benchmark": + test = TestParsingPerformance (sys.argv[2]) + test.start () + return + + app = App () + + window = app.windows[0] + if len (sys.argv) > 1: + window.set_log_file (sys.argv[-1]) + + app.run () + +if __name__ == "__main__": + main () diff --git a/debug-viewer/GstDebugViewer/Main.py b/debug-viewer/GstDebugViewer/Main.py new file mode 100644 index 0000000000..65ad0a3a55 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Main.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer debug viewer main module""" + +import sys +from gettext import gettext as _, ngettext + +import GstDebugViewer.Common.Main +Common = GstDebugViewer.Common + +GETTEXT_DOMAIN = "gst-debug-viewer" + +def main_version (): + + from GstDebugViewer import version + + print "GStreamer Debug Viewer %s" % (version,) + +class Paths (Common.Main.PathsProgramBase): + + program_name = "gst-debug-viewer" + +class OptionParser (Common.Main.LogOptionParser): + + def __init__ (self, options): + + Common.Main.LogOptionParser.__init__ (self, options) + + options["main"] = None + + self.add_option ("version", None, _("Display version and exit")) + + def get_parameter_string (self): + + return _("- Display and analyze debug log files") + + def handle_parse_complete (self): + + try: + version = self.options["version"] + except KeyError: + pass + else: + main_version () + sys.exit (0) + + if self.options["main"] is None: + import GUI + self.options["main"] = GUI.main + +def main (): + + options = {} + parser = OptionParser (options) + + Common.Main.main (option_parser = parser, + gettext_domain = GETTEXT_DOMAIN, + paths = Paths) diff --git a/debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py b/debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py new file mode 100644 index 0000000000..e80130acff --- /dev/null +++ b/debug-viewer/GstDebugViewer/Plugins/ColorizeRows.py @@ -0,0 +1,42 @@ + +from GstDebugViewer.Plugins import FeatureBase, PluginBase + +class ColorizeLevels (FeatureBase): + + def attach (self, window): + + pass + + def detach (self, window): + + pass + +class LevelColorSentinel (object): + + def processor (self, proc): + + for row in proc: + + yield None + +class ColorizeCategories (FeatureBase): + + def attach (self, window): + + pass + + def detach (self, window): + + pass + +class CategoryColorSentinel (object): + + def processor (self): + + pass + +class Plugin (PluginBase): + + features = [ColorizeLevels, ColorizeCategories] + + diff --git a/debug-viewer/GstDebugViewer/Plugins/LineFrequency.py b/debug-viewer/GstDebugViewer/Plugins/LineFrequency.py new file mode 100644 index 0000000000..4b67dbcab1 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Plugins/LineFrequency.py @@ -0,0 +1,322 @@ + +import logging + +from GstDebugViewer.Plugins import * + +import cairo +import gtk + +def iter_model_reversed (model): + + count = model.iter_n_children (None) + for i in xrange (count - 1, 0, -1): + yield model[i] + +class LineFrequencySentinel (object): + + def __init__ (self, model): + + self.model = model + + def _search_ts (self, target_ts, first_index, last_index): + + model_get = self.model.get + model_iter_nth_child = self.model.iter_nth_child + col_id = self.model.COL_TIME + + while True: + middle = (last_index - first_index) // 2 + first_index + if middle == first_index: + return last_index + ts = model_get (model_iter_nth_child (None, middle), col_id)[0] + if ts < target_ts: + first_index = middle + 1 + elif ts > target_ts: + last_index = middle - 1 + else: + return middle + + def run_for (self, n): + + if n == 0: + raise ValueError ("illegal value for n") + + model = self.model + result = [] + + last_ts = None + for row in iter_model_reversed (self.model): + last_ts = row[model.COL_TIME] + if last_ts: + last_index = row.path[0] + break + + if last_ts is None: + return result + + step = int (float (last_ts) / float (n)) + + first_index = 0 + target_ts = step + old_found = 0 + while target_ts < last_ts: + found = self._search_ts (target_ts, first_index, last_index) + result.append (found - old_found) + old_found = found + first_index = found + target_ts += step + + ## count = 0 + ## limit = step + ## for row in self.model: + ## ts = row[model.COL_TIME] + ## if ts is None: + ## continue + ## if ts > limit: + ## limit += step + ## result.append (count) + ## count = 0 + ## count += 1 + + return (step, result,) + +class LineFrequencyWidget (gtk.DrawingArea): + + __gtype_name__ = "LineFrequencyWidget" + + def __init__ (self, sentinel = None): + + gtk.DrawingArea.__init__ (self) + + self.logger = logging.getLogger ("ui.density-widget") + + self.sentinel = sentinel + self.sentinel_step = None + self.sentinel_data = None + self.__configure_id = None + self.connect ("expose-event", self.__handle_expose_event) + self.connect ("configure-event", self.__handle_configure_event) + self.connect ("size-request", self.__handle_size_request) + + def set_sentinel (self, sentinel): + + self.sentinel = sentinel + self.__redraw () + + def __redraw (self): + + if not self.props.visible: + return + + x, y, w, h = self.get_allocation () + self.offscreen = gtk.gdk.Pixmap (self.window, w, h, -1) + + self.__draw (self.offscreen) + + self.__update () + + def __update (self): + + if not self.props.visible: + return + + gc = gtk.gdk.GC (self.window) + self.window.draw_drawable (gc, self.offscreen, 0, 0, 0, 0, -1, -1) + + def update_position (self, start_ts, end_ts): + + self.__update () + + position1 = int (float (start_ts) / self.sentinel_step) + position2 = int (float (end_ts) / self.sentinel_step) + + ctx = self.window.cairo_create () + x, y, w, h = self.get_allocation () + + line_width = position2 - position1 + ctx.set_source_rgba (1., 0., 0., .5) + if line_width <= 1: + ctx.set_line_width (1.) + ctx.move_to (position1 + .5, 0) + ctx.line_to (position1 + .5, h) + ctx.stroke () + else: + ctx.rectangle (position1, 0, line_width, h) + ctx.fill () + + def __draw (self, drawable): + + ctx = drawable.cairo_create () + x, y, w, h = self.get_allocation () + ctx.set_line_width (0.) + ctx.rectangle (0, 0, w, h) + ctx.set_source_rgb (1., 1., 1.) + ctx.fill () + ctx.new_path () + + if self.sentinel_data is None and self.sentinel: + if w > 15: + self.logger.debug ("running sentinel for width %i", w) + self.sentinel_step, self.sentinel_data = self.sentinel.run_for (w) + else: + return + + if self.sentinel_data is None: + self.logger.debug ("not redrawing: no sentinel set") + return + + from operator import add + maximum = max (self.sentinel_data) + heights = [h * float (d) / maximum for d in self.sentinel_data] + ctx.move_to (0, h) + ctx.set_source_rgb (0., 0., 0.) + for i in range (len (heights)): + ctx.line_to (i - .5, h - heights[i] + .5) + #ctx.rectangle (i - .5, h - heights[i] + .5, i + 1, h) + + ctx.line_to (i, h) + ctx.close_path () + + ctx.fill () + + def __handle_expose_event (self, self_, event): + + self.__redraw () + return True + + def __handle_configure_event (self, self_, event): + + if event.width < 16: + return + + if self.sentinel: + self.sentinel_step, self.sentinel_data = self.sentinel.run_for (event.width) + + # FIXME: Is this done automatically? + self.queue_draw () + return False + + def __handle_size_request (self, self_, req): + + # FIXME: + req.height = 64 + +class LineFrequencyFeature (FeatureBase): + + state_section_name = "line-frequency-display" + + def __init__ (self): + + self.action_group = gtk.ActionGroup ("LineFrequencyActions") + self.action_group.add_toggle_actions ([("show-line-frequency", + None, _("Line _Density"))]) + + def attach (self, window): + + self.log_model = window.log_model + self.log_view = window.log_view + + ui = window.ui_manager + + ui.insert_action_group (self.action_group, 0) + + self.merge_id = ui.new_merge_id () + ui.add_ui (self.merge_id, "/menubar/ViewMenu/ViewMenuAdditions", + "ViewLineFrequency", "show-line-frequency", + gtk.UI_MANAGER_MENUITEM, False) + + box = window.get_top_attach_point () + + self.density_display = LineFrequencyWidget () + self.density_display.add_events (gtk.gdk.ALL_EVENTS_MASK) # FIXME + self.density_display.connect ("button-press-event", self.handle_density_button_press_event) + self.density_display.connect ("motion-notify-event", self.handle_density_motion_notify_event) + box.pack_start (self.density_display, False, False, 0) + self.density_display.hide () + + window.widgets.log_view_scrolled_window.props.vadjustment.connect ("value-changed", + self.handle_log_view_adjustment_value_changed) + + handler = self.handle_show_action_toggled + self.action_group.get_action ("show-line-frequency").connect ("toggled", handler) + + window.sentinels.append (self.sentinel_process) + + def detach (self, window): + + window.sentinels.remove (self.sentinel_process) + + window.ui_manager.remove_ui (self.merge_id) + self.merge_id = None + + # FIXME: Remove action group from ui manager! + + self.density_display.destroy () + self.density_display = None + + def sentinel_process (self): + + if self.action_group.get_action ("show-line-frequency").props.active: + sentinel = LineDensitySentinel (self.log_model) + self.density_display.set_sentinel (sentinel) + + def handle_log_view_adjustment_value_changed (self, adj): + + # FIXME: If not visible, disconnect this handler! + if not self.density_display.props.visible: + return + + start_path, end_path = self.log_view.get_visible_range () + ts1 = self.log_model.get (self.log_model.get_iter (start_path), + self.log_model.COL_TIME)[0] + ts2 = self.log_model.get (self.log_model.get_iter (end_path), + self.log_model.COL_TIME)[0] + self.density_display.update_position (ts1, ts2) + + def handle_show_action_toggled (self, action): + + show = action.props.active + + if show: + self.density_display.show () + if self.density_display.sentinel is None: + sentinel = LineFrequencySentinel (self.log_model) + self.density_display.set_sentinel (sentinel) + else: + self.density_display.hide () + + def handle_density_button_press_event (self, widget, event): + + if event.button != 1: + return True + + pos = int (event.x) + self.goto_density (pos) + return False + + def handle_density_motion_notify_event (self, widget, event): + + if not event.state & gtk.gdk.BUTTON1_MASK: + return True + + pos = int (event.x) + self.goto_density (pos) + return False + + def goto_density (self, pos): + + data = self.density_display.sentinel_data + if not data: + return True + count = 0 + for i in range (pos): + count += data[i] + + row = self.log_model[count] + self.log_view.scroll_to_cell ((count,), use_align = True, row_align = .5) + + return False + +class Plugin (PluginBase): + + features = [LineFrequencyFeature] diff --git a/debug-viewer/GstDebugViewer/Plugins/__init__.py b/debug-viewer/GstDebugViewer/Plugins/__init__.py new file mode 100644 index 0000000000..1429e7b869 --- /dev/null +++ b/debug-viewer/GstDebugViewer/Plugins/__init__.py @@ -0,0 +1,42 @@ + +__all__ = ["_", "FeatureBase", "PluginBase"] + +import os.path +from gettext import gettext as _ + +def load (paths = ()): + + for path in paths: + for plugin_module in _load_plugins (path): + yield plugin_module.Plugin + +def _load_plugins (path): + + import imp, glob + + files = glob.glob (os.path.join (path, "*.py")) + + for filename in files: + + name = os.path.basename (os.path.splitext (filename)[0]) + if name == "__init__": + continue + fp, pathname, description = imp.find_module (name, [path]) + module = imp.load_module (name, fp, pathname, description) + yield module + +class FeatureBase (object): + + state_section_name = None + + def register_lazy_sentinel (self, sentinel): + + pass + +class PluginBase (object): + + features = () + + def __init__ (self): + + pass diff --git a/debug-viewer/GstDebugViewer/__init__.py b/debug-viewer/GstDebugViewer/__init__.py new file mode 100644 index 0000000000..b903d2daa3 --- /dev/null +++ b/debug-viewer/GstDebugViewer/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +version = "0.1" + +__version__ = version + +from GstDebugViewer.Main import Paths, main as run diff --git a/debug-viewer/data/gst-debug-viewer.glade b/debug-viewer/data/gst-debug-viewer.glade new file mode 100644 index 0000000000..cd915c3fd5 --- /dev/null +++ b/debug-viewer/data/gst-debug-viewer.glade @@ -0,0 +1,502 @@ + + + + + + + + True + GStreamer Debug Viewer + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 640 + 480 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + True + False + 0 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + True + True + False + False + False + + + + + 0 + True + True + GTK_PACK_END + + + + + + + + 5 + True + False + GStreamer Debug Viewer + Copyright © 2007 René Stadler + View and analyze GStreamer debug files + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + + False + René Stadler <mail@renestadler.de> + translator-credits + gst-debug-viewer.png + + + + True + Loading log... + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER_ON_PARENT + True + False + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + False + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + 0 + False + True + GTK_PACK_END + + + + + + 6 + True + False + 0 + + + + 250 + True + GTK_PROGRESS_LEFT_TO_RIGHT + 0 + 0.10000000149 + Loading file + PANGO_ELLIPSIZE_NONE + + + 12 + False + False + + + + + 0 + True + False + + + + + + + diff --git a/debug-viewer/data/gst-debug-viewer.glade.bak b/debug-viewer/data/gst-debug-viewer.glade.bak new file mode 100644 index 0000000000..66b57cf824 --- /dev/null +++ b/debug-viewer/data/gst-debug-viewer.glade.bak @@ -0,0 +1,502 @@ + + + + + + + + True + GStreamer Debug Viewer + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 640 + 480 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + True + False + 0 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + True + True + False + False + False + + + + + 0 + True + True + GTK_PACK_END + + + + + + + + 5 + True + False + GStreamer Debug Viewer + Copyright © 2007 René Stadler + View and analyze GStreamer debug files + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + + False + René Stadler <mail@renestadler.de> + translator-credits + gst-debug-viewer.png + + + + True + Loading log... + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER_ON_PARENT + True + False + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + False + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + 0 + False + True + GTK_PACK_END + + + + + + 6 + True + False + 0 + + + + 250 + True + GTK_PROGRESS_LEFT_TO_RIGHT + 0 + 0.10000000149 + Loading file + PANGO_ELLIPSIZE_NONE + + + 12 + False + False + + + + + 0 + True + False + + + + + + + diff --git a/debug-viewer/data/gst-debug-viewer.gladep b/debug-viewer/data/gst-debug-viewer.gladep new file mode 100644 index 0000000000..7012234d59 --- /dev/null +++ b/debug-viewer/data/gst-debug-viewer.gladep @@ -0,0 +1,7 @@ + + + + + + + diff --git a/debug-viewer/data/gst-debug-viewer.png b/debug-viewer/data/gst-debug-viewer.png new file mode 100644 index 0000000000..81099a6853 Binary files /dev/null and b/debug-viewer/data/gst-debug-viewer.png differ diff --git a/debug-viewer/data/gst-debug-viewer.ui b/debug-viewer/data/gst-debug-viewer.ui new file mode 100644 index 0000000000..2073334031 --- /dev/null +++ b/debug-viewer/data/gst-debug-viewer.ui @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/debug-viewer/gst-debug-viewer.desktop b/debug-viewer/gst-debug-viewer.desktop new file mode 100644 index 0000000000..fbe939d6d0 --- /dev/null +++ b/debug-viewer/gst-debug-viewer.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=GStreamer Debug Viewer +Comment=Examine GStreamer debug log information +StartupNotify=true +Exec=python /home/cymacs/src/gst-debug-viewer/gst-debug-viewer.py +Icon=/home/cymacs/src/gst-debug-viewer/gst-debug-viewer.png +Type=Application +Categories=GNOME;Development + diff --git a/debug-viewer/gst-debug-viewer.py b/debug-viewer/gst-debug-viewer.py new file mode 100755 index 0000000000..3343f7cd27 --- /dev/null +++ b/debug-viewer/gst-debug-viewer.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8; mode: python; -*- +# +# GStreamer Debug Viewer +# +# Copyright (C) 2007 René Stadler +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# This program 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""GStreamer Debug Viewer program invocation.""" + +def main (): + + import sys + import os.path + + def substituted (s): + if s.startswith ("$") and s.endswith ("$"): + return None + else: + return s + + # These "$"-enclosed strings are substituted at install time by a custom + # distutils extension (see setup.py). If you don't see any dollar signs at + # all, you are looking at an installed version of this file. + data_dir = substituted ("$DATADIR$") + lib_dir = substituted ("$LIBDIR$") + + if data_dir: + installed = True + else: + # Substitution has not been run, we are running uninstalled: + lib_dir = os.path.dirname (os.path.abspath (sys.argv[0])) + installed = False + + if lib_dir: + if not os.path.normpath (lib_dir) in [os.path.normpath (p) + for p in sys.path]: + sys.path.insert (0, lib_dir) + + try: + import GstDebugViewer + except ImportError, exc: + print >> sys.stderr, str (exc) + sys.exit (1) + else: + if installed: + GstDebugViewer.Paths.setup_installed (data_dir) + else: + # Assume that we reside inside the source dist. + source_dir = os.path.dirname (os.path.abspath (sys.argv[0])) + GstDebugViewer.Paths.setup_uninstalled (source_dir) + + GstDebugViewer.run () + +if __name__ == "__main__": + main () diff --git a/debug-viewer/pixmaps/gst-debug-viewer.png b/debug-viewer/pixmaps/gst-debug-viewer.png new file mode 100644 index 0000000000..81099a6853 Binary files /dev/null and b/debug-viewer/pixmaps/gst-debug-viewer.png differ