diff options
Diffstat (limited to 'gi/overrides/GLib.py')
-rw-r--r-- | gi/overrides/GLib.py | 557 |
1 files changed, 533 insertions, 24 deletions
diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py index 27fe017..f4b1ef5 100644 --- a/gi/overrides/GLib.py +++ b/gi/overrides/GLib.py @@ -2,6 +2,7 @@ # vim: tabstop=4 shiftwidth=4 expandtab # # Copyright (C) 2010 Tomeu Vizoso <tomeu.vizoso@collabora.co.uk> +# Copyright (C) 2011, 2012 Canonical Ltd. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -18,13 +19,44 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 # USA -from ..importer import modules -from .._gi import variant_new_tuple, variant_type_from_string +import signal +import warnings +import sys -GLib = modules['GLib']._introspection_module +from ..module import get_introspection_module +from .._gi import (variant_new_tuple, variant_type_from_string, source_new, + source_set_callback, io_channel_read) +from ..overrides import override, deprecated +from gi import PyGIDeprecationWarning, version_info + +GLib = get_introspection_module('GLib') __all__ = [] +from gi._glib import option +option # pyflakes +__all__.append('option') + + +# Types and functions still needed from static bindings +from gi._glib import _glib +GError = _glib.GError +OptionContext = _glib.OptionContext +OptionGroup = _glib.OptionGroup +Pid = _glib.Pid +spawn_async = _glib.spawn_async + + +def threads_init(): + warnings.warn('threads_init no longer needs to be called. ' + 'See: https://bugzilla.gnome.org/show_bug.cgi?id=686914', + PyGIDeprecationWarning) + + +__all__ += ['GError', 'OptionContext', 'OptionGroup', 'Pid', + 'spawn_async', 'threads_init'] + + class _VariantCreator(object): _LEAF_CONSTRUCTORS = { @@ -83,20 +115,36 @@ class _VariantCreator(object): def _create_tuple(self, format, args): '''Handle the case where the outermost type of format is a tuple.''' - format = format[1:] # eat the '(' - builder = GLib.VariantBuilder.new(variant_type_from_string('r')) - if args is not None: - if not args or type(args[0]) != type(()): - raise (TypeError, 'expected tuple argument') + format = format[1:] # eat the '(' + if args is None: + # empty value: we need to call _create() to parse the subtype + rest_format = format + while rest_format: + if rest_format.startswith(')'): + break + rest_format = self._create(rest_format, None)[1] + else: + raise TypeError('tuple type string not closed with )') + rest_format = rest_format[1:] # eat the ) + return (None, rest_format, None) + else: + if not args or not isinstance(args[0], tuple): + raise TypeError('expected tuple argument') + + builder = GLib.VariantBuilder.new(variant_type_from_string('r')) for i in range(len(args[0])): if format.startswith(')'): - raise (TypeError, 'too many arguments for tuple signature') + raise TypeError('too many arguments for tuple signature') (v, format, _) = self._create(format, args[0][i:]) builder.add_value(v) args = args[1:] - return (builder.end(), format[1:], args) + if not format.startswith(')'): + raise TypeError('tuple type string not closed with )') + + rest_format = format[1:] # eat the ) + return (builder.end(), rest_format, args) def _create_dict(self, format, args): '''Handle the case where the outermost type of format is a dict.''' @@ -108,8 +156,8 @@ class _VariantCreator(object): rest_format = self._create(format[2:], None)[1] rest_format = self._create(rest_format, None)[1] if not rest_format.startswith('}'): - raise ValueError('dictionary type string not closed with }') - rest_format = rest_format[1:] # eat the } + raise TypeError('dictionary type string not closed with }') + rest_format = rest_format[1:] # eat the } element_type = format[:len(format) - len(rest_format)] builder = GLib.VariantBuilder.new(variant_type_from_string(element_type)) else: @@ -119,8 +167,8 @@ class _VariantCreator(object): (val_v, rest_format, _) = self._create(rest_format, [v]) if not rest_format.startswith('}'): - raise ValueError('dictionary type string not closed with }') - rest_format = rest_format[1:] # eat the } + raise TypeError('dictionary type string not closed with }') + rest_format = rest_format[1:] # eat the } entry = GLib.VariantBuilder.new(variant_type_from_string('{?*}')) entry.add_value(key_v) @@ -150,27 +198,39 @@ class _VariantCreator(object): args = args[1:] return (builder.end(), rest_format, args) + class Variant(GLib.Variant): def __new__(cls, format_string, value): '''Create a GVariant from a native Python object. format_string is a standard GVariant type signature, value is a Python object whose structure has to match the signature. - + Examples: GLib.Variant('i', 1) GLib.Variant('(is)', (1, 'hello')) - GLib.Variant('(asa{sv})', ([], {'foo': GLib.Variant('b', True), + GLib.Variant('(asa{sv})', ([], {'foo': GLib.Variant('b', True), 'bar': GLib.Variant('i', 2)})) ''' creator = _VariantCreator() (v, rest_format, _) = creator._create(format_string, [value]) if rest_format: raise TypeError('invalid remaining format string: "%s"' % rest_format) + v.format_string = format_string return v + def __del__(self): + self.unref() + + def __str__(self): + return self.print_(True) + def __repr__(self): - return '<GLib.Variant(%s)>' % getattr(self, 'print')(True) + if hasattr(self, 'format_string'): + f = self.format_string + else: + f = self.get_type_string() + return "GLib.Variant('%s', %s)" % (f, self.print_(False)) def __eq__(self, other): try: @@ -205,8 +265,8 @@ class Variant(GLib.Variant): 'h': self.get_handle, 'd': self.get_double, 's': self.get_string, - 'o': self.get_string, # object path - 'g': self.get_string, # signature + 'o': self.get_string, # object path + 'g': self.get_string, # signature } # simple values @@ -216,8 +276,8 @@ class Variant(GLib.Variant): # tuple if self.get_type_string().startswith('('): - res = [self.get_child_value(i).unpack() - for i in range(self.n_children())] + res = [self.get_child_value(i).unpack() + for i in range(self.n_children())] return tuple(res) # dictionary @@ -230,13 +290,18 @@ class Variant(GLib.Variant): # array if self.get_type_string().startswith('a'): - return [self.get_child_value(i).unpack() + return [self.get_child_value(i).unpack() for i in range(self.n_children())] # variant (just unbox transparently) if self.get_type_string().startswith('v'): return self.get_variant().unpack() + # maybe + if self.get_type_string().startswith('m'): + m = self.get_maybe() + return m.unpack() if m else None + raise NotImplementedError('unsupported GVariant type ' + self.get_type_string()) @classmethod @@ -245,7 +310,7 @@ class Variant(GLib.Variant): If the signature is not a tuple, it returns one element with the entire signature. If the signature is an empty tuple, the result is []. - + This is useful for e. g. iterating over method parameters which are passed as a single Variant. ''' @@ -257,7 +322,7 @@ class Variant(GLib.Variant): result = [] head = '' - tail = signature[1:-1] # eat the surrounding ( ) + tail = signature[1:-1] # eat the surrounding () while tail: c = tail[0] head += c @@ -292,6 +357,7 @@ class Variant(GLib.Variant): # # Pythonic iterators # + def __len__(self): if self.get_type_string() in ['s', 'o', 'g']: return len(self.get_string()) @@ -336,6 +402,7 @@ class Variant(GLib.Variant): # # Pythonic bool operations # + def __nonzero__(self): return self.__bool__() @@ -365,10 +432,12 @@ class Variant(GLib.Variant): res.append(v.get_child_value(0).unpack()) return res + @classmethod def new_tuple(cls, *elements): return variant_new_tuple(elements) + def get_string(self): value, length = GLib.Variant.get_string(self) return value @@ -378,3 +447,443 @@ setattr(Variant, 'get_string', get_string) __all__.append('Variant') + +def markup_escape_text(text, length=-1): + if isinstance(text, bytes): + return GLib.markup_escape_text(text.decode('UTF-8'), length) + else: + return GLib.markup_escape_text(text, length) +__all__.append('markup_escape_text') + + +# backwards compatible names from old static bindings +for n in ['DESKTOP', 'DOCUMENTS', 'DOWNLOAD', 'MUSIC', 'PICTURES', + 'PUBLIC_SHARE', 'TEMPLATES', 'VIDEOS']: + globals()['USER_DIRECTORY_' + n] = getattr(GLib.UserDirectory, 'DIRECTORY_' + n) + __all__.append('USER_DIRECTORY_' + n) + +for n in ['ERR', 'HUP', 'IN', 'NVAL', 'OUT', 'PRI']: + globals()['IO_' + n] = getattr(GLib.IOCondition, n) + __all__.append('IO_' + n) + +for n in ['APPEND', 'GET_MASK', 'IS_READABLE', 'IS_SEEKABLE', + 'MASK', 'NONBLOCK', 'SET_MASK']: + globals()['IO_FLAG_' + n] = getattr(GLib.IOFlags, n) + __all__.append('IO_FLAG_' + n) +# spelling for the win +IO_FLAG_IS_WRITEABLE = GLib.IOFlags.IS_WRITABLE +__all__.append('IO_FLAG_IS_WRITEABLE') + +for n in ['AGAIN', 'EOF', 'ERROR', 'NORMAL']: + globals()['IO_STATUS_' + n] = getattr(GLib.IOStatus, n) + __all__.append('IO_STATUS_' + n) + +for n in ['CHILD_INHERITS_STDIN', 'DO_NOT_REAP_CHILD', 'FILE_AND_ARGV_ZERO', + 'LEAVE_DESCRIPTORS_OPEN', 'SEARCH_PATH', 'STDERR_TO_DEV_NULL', + 'STDOUT_TO_DEV_NULL']: + globals()['SPAWN_' + n] = getattr(GLib.SpawnFlags, n) + __all__.append('SPAWN_' + n) + +for n in ['HIDDEN', 'IN_MAIN', 'REVERSE', 'NO_ARG', 'FILENAME', 'OPTIONAL_ARG', + 'NOALIAS']: + globals()['OPTION_FLAG_' + n] = getattr(GLib.OptionFlags, n) + __all__.append('OPTION_FLAG_' + n) + +for n in ['UNKNOWN_OPTION', 'BAD_VALUE', 'FAILED']: + globals()['OPTION_ERROR_' + n] = getattr(GLib.OptionError, n) + __all__.append('OPTION_ERROR_' + n) + + +class MainLoop(GLib.MainLoop): + # Backwards compatible constructor API + def __new__(cls, context=None): + return GLib.MainLoop.new(context, False) + + # Retain classic pygobject behaviour of quitting main loops on SIGINT + def __init__(self, context=None): + def _handler(loop): + loop.quit() + loop._quit_by_sigint = True + if sys.platform != 'win32': + # compatibility shim, keep around until we depend on glib 2.36 + if hasattr(GLib, 'unix_signal_add'): + fn = GLib.unix_signal_add + else: + fn = GLib.unix_signal_add_full + self._signal_source = fn(GLib.PRIORITY_DEFAULT, signal.SIGINT, _handler, self) + + def __del__(self): + if hasattr(self, '_signal_source'): + GLib.source_remove(self._signal_source) + + def run(self): + super(MainLoop, self).run() + if hasattr(self, '_quit_by_sigint'): + # caught by _main_loop_sigint_handler() + raise KeyboardInterrupt + +MainLoop = override(MainLoop) +__all__.append('MainLoop') + + +class MainContext(GLib.MainContext): + # Backwards compatible API with default value + def iteration(self, may_block=True): + return super(MainContext, self).iteration(may_block) + +MainContext = override(MainContext) +__all__.append('MainContext') + + +class Source(GLib.Source): + def __new__(cls, *args, **kwargs): + # use our custom pyg_source_new() here as g_source_new() is not + # bindable + source = source_new() + source.__class__ = cls + setattr(source, '__pygi_custom_source', True) + return source + + def __del__(self): + if hasattr(self, '__pygi_custom_source'): + self.unref() + + # Backwards compatible API for optional arguments + def attach(self, context=None): + id = super(Source, self).attach(context) + return id + + def set_callback(self, fn, user_data=None): + if hasattr(self, '__pygi_custom_source'): + # use our custom pyg_source_set_callback() if for a GSource object + # with custom functions + source_set_callback(self, fn, user_data) + else: + # otherwise, for Idle and Timeout, use the standard method + super(Source, self).set_callback(fn, user_data) + + def get_current_time(self): + return GLib.get_real_time() * 0.000001 + + get_current_time = deprecated(get_current_time, + 'GLib.Source.get_time() or GLib.get_real_time()') + + # as get/set_priority are introspected, we can't use the static + # property(get_priority, ..) here + def __get_priority(self): + return self.get_priority() + + def __set_priority(self, value): + self.set_priority(value) + + priority = property(__get_priority, __set_priority) + + def __get_can_recurse(self): + return self.get_can_recurse() + + def __set_can_recurse(self, value): + self.set_can_recurse(value) + + can_recurse = property(__get_can_recurse, __set_can_recurse) + +Source = override(Source) +__all__.append('Source') + + +class Idle(Source): + def __new__(cls, priority=GLib.PRIORITY_DEFAULT): + source = GLib.idle_source_new() + source.__class__ = cls + return source + + def __init__(self, priority=GLib.PRIORITY_DEFAULT): + super(Source, self).__init__() + if priority != GLib.PRIORITY_DEFAULT: + self.set_priority(priority) + +__all__.append('Idle') + + +class Timeout(Source): + def __new__(cls, interval=0, priority=GLib.PRIORITY_DEFAULT): + source = GLib.timeout_source_new(interval) + source.__class__ = cls + return source + + def __init__(self, interval=0, priority=GLib.PRIORITY_DEFAULT): + if priority != GLib.PRIORITY_DEFAULT: + self.set_priority(priority) + +__all__.append('Timeout') + + +def user_data_varargs_shim(callback, user_data, cb_num_args=0): + '''Adjust callback and user_data varargs for PyGTK backwards compatibility + + GLib only accepts exactly one user_data argument, but older pygtk + traditionally accepted zero or more for some specific functions. For "one + argument", use the actual user-supplied callback for efficiency; for all + others, rewire it to accept zero or more than one. + + Return the adjusted callback and the real user data to pass to GLib. + ''' + if len(user_data) == 1: + return (callback, user_data[0]) + + if cb_num_args == 0: + return (lambda data: callback(*data), user_data) + if cb_num_args == 2: + return (lambda a1, a2, data: callback(a1, a2, *data), user_data) + raise NotImplementedError('%i number of callback arguments not supported' % cb_num_args) + + +# backwards compatible API +def idle_add(function, *user_data, **kwargs): + (fn, data) = user_data_varargs_shim(function, user_data) + priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT_IDLE) + return GLib.idle_add(priority, fn, data) + +__all__.append('idle_add') + + +def timeout_add(interval, function, *user_data, **kwargs): + (fn, data) = user_data_varargs_shim(function, user_data) + priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT) + return GLib.timeout_add(priority, interval, fn, data) + +__all__.append('timeout_add') + + +def timeout_add_seconds(interval, function, *user_data, **kwargs): + (fn, data) = user_data_varargs_shim(function, user_data) + priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT) + return GLib.timeout_add_seconds(priority, interval, fn, data) + +__all__.append('timeout_add_seconds') + + +# The real GLib API is io_add_watch(IOChannel, priority, condition, callback, +# user_data). This needs to take into account several historical APIs: +# - calling with an fd as first argument +# - calling with a Python file object as first argument (we keep this one as +# it's really convenient and does not change the number of arguments) +# - calling without a priority as second argument +# and the usual "call without or multiple user_data", in which case the +# callback gets the same user data arguments. +def io_add_watch(channel, priority_, condition, *cb_and_user_data, **kwargs): + if not isinstance(priority_, int) or isinstance(priority_, GLib.IOCondition): + warnings.warn('Calling io_add_watch without priority as second argument is deprecated', + PyGIDeprecationWarning) + # shift the arguments around + user_data = cb_and_user_data + callback = condition + condition = priority_ + if not callable(callback): + raise TypeError('third argument must be callable') + + # backwards compatibility: Call with priority kwarg + if 'priority' in kwargs: + warnings.warn('Calling io_add_watch with priority keyword argument is deprecated, put it as second positional argument', + PyGIDeprecationWarning) + priority_ = kwargs['priority'] + else: + priority_ = GLib.PRIORITY_DEFAULT + else: + if len(cb_and_user_data) < 1 or not callable(cb_and_user_data[0]): + raise TypeError('expecting callback as fourth argument') + callback = cb_and_user_data[0] + user_data = cb_and_user_data[1:] + + (func, user_data) = user_data_varargs_shim(callback, user_data, 2) + + # backwards compatibility: Allow calling with fd + if isinstance(channel, int): + func_fdtransform = lambda _, cond, data: func(channel, cond, data) + real_channel = GLib.IOChannel.unix_new(channel) + elif hasattr(channel, 'fileno'): + # backwards compatibility: Allow calling with Python file + func_fdtransform = lambda _, cond, data: func(channel, cond, data) + real_channel = GLib.IOChannel.unix_new(channel.fileno()) + else: + assert isinstance(channel, GLib.IOChannel) + func_fdtransform = func + real_channel = channel + + return GLib.io_add_watch(real_channel, priority_, condition, + func_fdtransform, user_data) + +__all__.append('io_add_watch') + + +# backwards compatible API +class IOChannel(GLib.IOChannel): + def __new__(cls, filedes=None, filename=None, mode=None, hwnd=None): + if filedes is not None: + return GLib.IOChannel.unix_new(filedes) + if filename is not None: + return GLib.IOChannel.new_file(filename, mode or 'r') + if hwnd is not None: + return GLib.IOChannel.win32_new_fd(hwnd) + raise TypeError('either a valid file descriptor, file name, or window handle must be supplied') + + def read(self, max_count=-1): + return io_channel_read(self, max_count) + + def readline(self, size_hint=-1): + # note, size_hint is just to maintain backwards compatible API; the + # old static binding did not actually use it + (status, buf, length, terminator_pos) = self.read_line() + if buf is None: + return '' + return buf + + def readlines(self, size_hint=-1): + # note, size_hint is just to maintain backwards compatible API; + # the old static binding did not actually use it + lines = [] + status = GLib.IOStatus.NORMAL + while status == GLib.IOStatus.NORMAL: + (status, buf, length, terminator_pos) = self.read_line() + # note, this appends an empty line after EOF; this is + # bug-compatible with the old static bindings + if buf is None: + buf = '' + lines.append(buf) + return lines + + def write(self, buf, buflen=-1): + if not isinstance(buf, bytes): + buf = buf.encode('UTF-8') + if buflen == -1: + buflen = len(buf) + (status, written) = self.write_chars(buf, buflen) + return written + + def writelines(self, lines): + for line in lines: + self.write(line) + + _whence_map = {0: GLib.SeekType.SET, 1: GLib.SeekType.CUR, 2: GLib.SeekType.END} + + def seek(self, offset, whence=0): + try: + w = self._whence_map[whence] + except KeyError: + raise ValueError("invalid 'whence' value") + return self.seek_position(offset, w) + + def add_watch(self, condition, callback, *user_data, **kwargs): + priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT) + return io_add_watch(self, priority, condition, callback, *user_data) + + add_watch = deprecated(add_watch, 'GLib.io_add_watch()') + + def __iter__(self): + return self + + def __next__(self): + (status, buf, length, terminator_pos) = self.read_line() + if status == GLib.IOStatus.NORMAL: + return buf + raise StopIteration + + # Python 2.x compatibility + next = __next__ + +IOChannel = override(IOChannel) +__all__.append('IOChannel') + + +class PollFD(GLib.PollFD): + def __new__(cls, fd, events): + pollfd = GLib.PollFD() + pollfd.__class__ = cls + return pollfd + + def __init__(self, fd, events): + self.fd = fd + self.events = events + +PollFD = override(PollFD) +__all__.append('PollFD') + + +# The real GLib API is child_watch_add(priority, pid, callback, data). +# The old static bindings had the following API which we still need to support +# for a while: +# child_watch_add(pid, callback, data=None, priority=GLib.PRIORITY_DEFAULT) +# and the usual "call without user_data", in which case the callback does not +# get an user_data either. +def child_watch_add(priority_or_pid, pid_or_callback, *args, **kwargs): + _unspecified = object() + + if callable(pid_or_callback): + warnings.warn('Calling child_watch_add without priority as first argument is deprecated', + PyGIDeprecationWarning) + pid = priority_or_pid + callback = pid_or_callback + if len(args) == 0: + user_data = kwargs.get('data', _unspecified) + priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT) + elif len(args) == 1: + user_data = args[0] + priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT) + elif len(args) == 2: + user_data = args[0] + priority = args[1] + else: + raise TypeError('expected at most 4 positional arguments') + else: + priority = priority_or_pid + pid = pid_or_callback + if len(args) == 0 or not callable(args[0]): + raise TypeError('expected callback as third argument') + callback = args[0] + if len(args) == 1: + user_data = kwargs.get('data', _unspecified) + else: + user_data = args[1] + + if user_data is _unspecified: + # we have to call the callback without the user_data argument + func = lambda pid, status, data: callback(pid, status) + user_data = None + else: + func = callback + + return GLib.child_watch_add(priority, pid, func, user_data) + +__all__.append('child_watch_add') + + +def get_current_time(): + return GLib.get_real_time() * 0.000001 + +get_current_time = deprecated(get_current_time, 'GLib.get_real_time()') + +__all__.append('get_current_time') + + +# backwards compatible API with default argument, and ignoring bytes_read +# output argument +def filename_from_utf8(utf8string, len=-1): + return GLib.filename_from_utf8(utf8string, len)[0] + +__all__.append('filename_from_utf8') + + +# backwards compatible API for renamed function +if not hasattr(GLib, 'unix_signal_add_full'): + def add_full_compat(*args): + warnings.warn('GLib.unix_signal_add_full() was renamed to GLib.unix_signal_add()', + PyGIDeprecationWarning) + return GLib.unix_signal_add(*args) + + GLib.unix_signal_add_full = add_full_compat + + +# obsolete constants for backwards compatibility +glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) +__all__.append('glib_version') +pyglib_version = version_info +__all__.append('pyglib_version') |