1# -*- Mode: Python; py-indent-offset: 4 -*-
2# vim: tabstop=4 shiftwidth=4 expandtab
3#
4# Copyright (C) 2010 Tomeu Vizoso <tomeu.vizoso@collabora.co.uk>
5# Copyright (C) 2011, 2012 Canonical Ltd.
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20# USA
21
22import warnings
23import sys
24import socket
25
26from .._ossighelper import wakeup_on_signal, register_sigint_fallback
27from ..module import get_introspection_module
28from .._gi import (variant_type_from_string, source_new,
29                   source_set_callback, io_channel_read)
30from ..overrides import override, deprecated, deprecated_attr
31from gi import PyGIDeprecationWarning, version_info
32
33GLib = get_introspection_module('GLib')
34
35__all__ = []
36
37from gi import _option as option
38option  # pyflakes
39__all__.append('option')
40
41
42# Types and functions still needed from static bindings
43from gi import _gi
44from gi._error import GError
45
46Error = GError
47OptionContext = _gi.OptionContext
48OptionGroup = _gi.OptionGroup
49Pid = _gi.Pid
50spawn_async = _gi.spawn_async
51
52
53def threads_init():
54    warnings.warn('Since version 3.11, calling threads_init is no longer needed. '
55                  'See: https://wiki.gnome.org/PyGObject/Threading',
56                  PyGIDeprecationWarning, stacklevel=2)
57
58
59def gerror_matches(self, domain, code):
60    # Handle cases where self.domain was set to an integer for compatibility
61    # with the introspected GLib.Error.
62    if isinstance(self.domain, str):
63        self_domain_quark = GLib.quark_from_string(self.domain)
64    else:
65        self_domain_quark = self.domain
66    return (self_domain_quark, self.code) == (domain, code)
67
68
69def gerror_new_literal(domain, message, code):
70    domain_quark = GLib.quark_to_string(domain)
71    return GError(message, domain_quark, code)
72
73
74# Monkey patch methods that rely on GLib introspection to be loaded at runtime.
75Error.__name__ = 'Error'
76Error.__module__ = 'gi.repository.GLib'
77Error.__gtype__ = GLib.Error.__gtype__
78Error.matches = gerror_matches
79Error.new_literal = staticmethod(gerror_new_literal)
80
81
82__all__ += ['GError', 'Error', 'OptionContext', 'OptionGroup', 'Pid',
83            'spawn_async', 'threads_init']
84
85
86class _VariantCreator(object):
87
88    _LEAF_CONSTRUCTORS = {
89        'b': GLib.Variant.new_boolean,
90        'y': GLib.Variant.new_byte,
91        'n': GLib.Variant.new_int16,
92        'q': GLib.Variant.new_uint16,
93        'i': GLib.Variant.new_int32,
94        'u': GLib.Variant.new_uint32,
95        'x': GLib.Variant.new_int64,
96        't': GLib.Variant.new_uint64,
97        'h': GLib.Variant.new_handle,
98        'd': GLib.Variant.new_double,
99        's': GLib.Variant.new_string,
100        'o': GLib.Variant.new_object_path,
101        'g': GLib.Variant.new_signature,
102        'v': GLib.Variant.new_variant,
103    }
104
105    def _create(self, format, value):
106        """Create a GVariant object from given format and a value that matches
107        the format.
108
109        This method recursively calls itself for complex structures (arrays,
110        dictionaries, boxed).
111
112        Returns the generated GVariant.
113
114        If value is None it will generate an empty GVariant container type.
115        """
116        gvtype = GLib.VariantType(format)
117        if format in self._LEAF_CONSTRUCTORS:
118            return self._LEAF_CONSTRUCTORS[format](value)
119
120        # Since we discarded all leaf types, this must be a container
121        builder = GLib.VariantBuilder.new(gvtype)
122        if value is None:
123            return builder.end()
124
125        if gvtype.is_maybe():
126            builder.add_value(self._create(gvtype.element().dup_string(), value))
127            return builder.end()
128
129        try:
130            iter(value)
131        except TypeError:
132            raise TypeError("Could not create array, tuple or dictionary entry from non iterable value %s %s" %
133                            (format, value))
134
135        if gvtype.is_tuple() and gvtype.n_items() != len(value):
136            raise TypeError("Tuple mismatches value's number of elements %s %s" % (format, value))
137        if gvtype.is_dict_entry() and len(value) != 2:
138            raise TypeError("Dictionary entries must have two elements %s %s" % (format, value))
139
140        if gvtype.is_array():
141            element_type = gvtype.element().dup_string()
142            if isinstance(value, dict):
143                value = value.items()
144            for i in value:
145                builder.add_value(self._create(element_type, i))
146        else:
147            remainer_format = format[1:]
148            for i in value:
149                dup = variant_type_from_string(remainer_format).dup_string()
150                builder.add_value(self._create(dup, i))
151                remainer_format = remainer_format[len(dup):]
152
153        return builder.end()
154
155
156LEAF_ACCESSORS = {
157    'b': 'get_boolean',
158    'y': 'get_byte',
159    'n': 'get_int16',
160    'q': 'get_uint16',
161    'i': 'get_int32',
162    'u': 'get_uint32',
163    'x': 'get_int64',
164    't': 'get_uint64',
165    'h': 'get_handle',
166    'd': 'get_double',
167    's': 'get_string',
168    'o': 'get_string',  # object path
169    'g': 'get_string',  # signature
170}
171
172
173class Variant(GLib.Variant):
174    def __new__(cls, format_string, value):
175        """Create a GVariant from a native Python object.
176
177        format_string is a standard GVariant type signature, value is a Python
178        object whose structure has to match the signature.
179
180        Examples:
181          GLib.Variant('i', 1)
182          GLib.Variant('(is)', (1, 'hello'))
183          GLib.Variant('(asa{sv})', ([], {'foo': GLib.Variant('b', True),
184                                          'bar': GLib.Variant('i', 2)}))
185        """
186        if not GLib.VariantType.string_is_valid(format_string):
187            raise TypeError("Invalid GVariant format string '%s'", format_string)
188        creator = _VariantCreator()
189        v = creator._create(format_string, value)
190        v.format_string = format_string
191        return v
192
193    @staticmethod
194    def new_tuple(*elements):
195        return GLib.Variant.new_tuple(elements)
196
197    def __del__(self):
198        try:
199            self.unref()
200        except ImportError:
201            # Calling unref will cause gi and gi.repository.GLib to be
202            # imported. However, if the program is exiting, then these
203            # modules have likely been removed from sys.modules and will
204            # raise an exception. Assume that's the case for ImportError
205            # and ignore the exception since everything will be cleaned
206            # up, anyways.
207            pass
208
209    def __str__(self):
210        return self.print_(True)
211
212    def __repr__(self):
213        if hasattr(self, 'format_string'):
214            f = self.format_string
215        else:
216            f = self.get_type_string()
217        return "GLib.Variant('%s', %s)" % (f, self.print_(False))
218
219    def __eq__(self, other):
220        try:
221            return self.equal(other)
222        except TypeError:
223            return False
224
225    def __ne__(self, other):
226        try:
227            return not self.equal(other)
228        except TypeError:
229            return True
230
231    def __hash__(self):
232        # We're not using just hash(self.unpack()) because otherwise we'll have
233        # hash collisions between the same content in different variant types,
234        # which will cause a performance issue in set/dict/etc.
235        return hash((self.get_type_string(), self.unpack()))
236
237    def unpack(self):
238        """Decompose a GVariant into a native Python object."""
239
240        type_string = self.get_type_string()
241
242        # simple values
243        la = LEAF_ACCESSORS.get(type_string)
244        if la:
245            return getattr(self, la)()
246
247        # tuple
248        if type_string.startswith('('):
249            return tuple(self.get_child_value(i).unpack()
250                         for i in range(self.n_children()))
251
252        # dictionary
253        if type_string.startswith('a{'):
254            res = {}
255            for i in range(self.n_children()):
256                v = self.get_child_value(i)
257                res[v.get_child_value(0).unpack()] = v.get_child_value(1).unpack()
258            return res
259
260        # array
261        if type_string.startswith('a'):
262            return [self.get_child_value(i).unpack()
263                    for i in range(self.n_children())]
264
265        # variant (just unbox transparently)
266        if type_string.startswith('v'):
267            return self.get_variant().unpack()
268
269        # maybe
270        if type_string.startswith('m'):
271            if not self.n_children():
272                return None
273            return self.get_child_value(0).unpack()
274
275        raise NotImplementedError('unsupported GVariant type ' + type_string)
276
277    @classmethod
278    def split_signature(klass, signature):
279        """Return a list of the element signatures of the topmost signature tuple.
280
281        If the signature is not a tuple, it returns one element with the entire
282        signature. If the signature is an empty tuple, the result is [].
283
284        This is useful for e. g. iterating over method parameters which are
285        passed as a single Variant.
286        """
287        if signature == '()':
288            return []
289
290        if not signature.startswith('('):
291            return [signature]
292
293        result = []
294        head = ''
295        tail = signature[1:-1]  # eat the surrounding ()
296        while tail:
297            c = tail[0]
298            head += c
299            tail = tail[1:]
300
301            if c in ('m', 'a'):
302                # prefixes, keep collecting
303                continue
304            if c in ('(', '{'):
305                # consume until corresponding )/}
306                level = 1
307                up = c
308                if up == '(':
309                    down = ')'
310                else:
311                    down = '}'
312                while level > 0:
313                    c = tail[0]
314                    head += c
315                    tail = tail[1:]
316                    if c == up:
317                        level += 1
318                    elif c == down:
319                        level -= 1
320
321            # otherwise we have a simple type
322            result.append(head)
323            head = ''
324
325        return result
326
327    #
328    # Pythonic iterators
329    #
330
331    def __len__(self):
332        if self.get_type_string() in ['s', 'o', 'g']:
333            return len(self.get_string())
334        # Array, dict, tuple
335        if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
336            return self.n_children()
337        raise TypeError('GVariant type %s does not have a length' % self.get_type_string())
338
339    def __getitem__(self, key):
340        # dict
341        if self.get_type_string().startswith('a{'):
342            try:
343                val = self.lookup_value(key, variant_type_from_string('*'))
344                if val is None:
345                    raise KeyError(key)
346                return val.unpack()
347            except TypeError:
348                # lookup_value() only works for string keys, which is certainly
349                # the common case; we have to do painful iteration for other
350                # key types
351                for i in range(self.n_children()):
352                    v = self.get_child_value(i)
353                    if v.get_child_value(0).unpack() == key:
354                        return v.get_child_value(1).unpack()
355                raise KeyError(key)
356
357        # array/tuple
358        if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
359            key = int(key)
360            if key < 0:
361                key = self.n_children() + key
362            if key < 0 or key >= self.n_children():
363                raise IndexError('list index out of range')
364            return self.get_child_value(key).unpack()
365
366        # string
367        if self.get_type_string() in ['s', 'o', 'g']:
368            return self.get_string().__getitem__(key)
369
370        raise TypeError('GVariant type %s is not a container' % self.get_type_string())
371
372    #
373    # Pythonic bool operations
374    #
375
376    def __nonzero__(self):
377        return self.__bool__()
378
379    def __bool__(self):
380        if self.get_type_string() in ['y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd']:
381            return self.unpack() != 0
382        if self.get_type_string() in ['b']:
383            return self.get_boolean()
384        if self.get_type_string() in ['s', 'o', 'g']:
385            return len(self.get_string()) != 0
386        # Array, dict, tuple
387        if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
388            return self.n_children() != 0
389        # unpack works recursively, hence bool also works recursively
390        return bool(self.unpack())
391
392    def keys(self):
393        if not self.get_type_string().startswith('a{'):
394            raise TypeError('GVariant type %s is not a dictionary' % self.get_type_string())
395
396        res = []
397        for i in range(self.n_children()):
398            v = self.get_child_value(i)
399            res.append(v.get_child_value(0).unpack())
400        return res
401
402
403def get_string(self):
404    value, length = GLib.Variant.get_string(self)
405    return value
406
407
408setattr(Variant, 'get_string', get_string)
409
410__all__.append('Variant')
411
412
413def markup_escape_text(text, length=-1):
414    if isinstance(text, bytes):
415        return GLib.markup_escape_text(text.decode('UTF-8'), length)
416    else:
417        return GLib.markup_escape_text(text, length)
418
419
420__all__.append('markup_escape_text')
421
422
423# backwards compatible names from old static bindings
424for n in ['DESKTOP', 'DOCUMENTS', 'DOWNLOAD', 'MUSIC', 'PICTURES',
425          'PUBLIC_SHARE', 'TEMPLATES', 'VIDEOS']:
426    attr = 'USER_DIRECTORY_' + n
427    deprecated_attr("GLib", attr, "GLib.UserDirectory.DIRECTORY_" + n)
428    globals()[attr] = getattr(GLib.UserDirectory, 'DIRECTORY_' + n)
429    __all__.append(attr)
430
431for n in ['ERR', 'HUP', 'IN', 'NVAL', 'OUT', 'PRI']:
432    globals()['IO_' + n] = getattr(GLib.IOCondition, n)
433    __all__.append('IO_' + n)
434
435for n in ['APPEND', 'GET_MASK', 'IS_READABLE', 'IS_SEEKABLE',
436          'MASK', 'NONBLOCK', 'SET_MASK']:
437    attr = 'IO_FLAG_' + n
438    deprecated_attr("GLib", attr, "GLib.IOFlags." + n)
439    globals()[attr] = getattr(GLib.IOFlags, n)
440    __all__.append(attr)
441
442# spelling for the win
443IO_FLAG_IS_WRITEABLE = GLib.IOFlags.IS_WRITABLE
444deprecated_attr("GLib", "IO_FLAG_IS_WRITEABLE", "GLib.IOFlags.IS_WRITABLE")
445__all__.append('IO_FLAG_IS_WRITEABLE')
446
447for n in ['AGAIN', 'EOF', 'ERROR', 'NORMAL']:
448    attr = 'IO_STATUS_' + n
449    globals()[attr] = getattr(GLib.IOStatus, n)
450    deprecated_attr("GLib", attr, "GLib.IOStatus." + n)
451    __all__.append(attr)
452
453for n in ['CHILD_INHERITS_STDIN', 'DO_NOT_REAP_CHILD', 'FILE_AND_ARGV_ZERO',
454          'LEAVE_DESCRIPTORS_OPEN', 'SEARCH_PATH', 'STDERR_TO_DEV_NULL',
455          'STDOUT_TO_DEV_NULL']:
456    attr = 'SPAWN_' + n
457    globals()[attr] = getattr(GLib.SpawnFlags, n)
458    deprecated_attr("GLib", attr, "GLib.SpawnFlags." + n)
459    __all__.append(attr)
460
461for n in ['HIDDEN', 'IN_MAIN', 'REVERSE', 'NO_ARG', 'FILENAME', 'OPTIONAL_ARG',
462          'NOALIAS']:
463    attr = 'OPTION_FLAG_' + n
464    globals()[attr] = getattr(GLib.OptionFlags, n)
465    deprecated_attr("GLib", attr, "GLib.OptionFlags." + n)
466    __all__.append(attr)
467
468for n in ['UNKNOWN_OPTION', 'BAD_VALUE', 'FAILED']:
469    attr = 'OPTION_ERROR_' + n
470    deprecated_attr("GLib", attr, "GLib.OptionError." + n)
471    globals()[attr] = getattr(GLib.OptionError, n)
472    __all__.append(attr)
473
474
475# these are not currently exported in GLib gir, presumably because they are
476# platform dependent; so get them from our static bindings
477for name in ['G_MINFLOAT', 'G_MAXFLOAT', 'G_MINDOUBLE', 'G_MAXDOUBLE',
478             'G_MINSHORT', 'G_MAXSHORT', 'G_MAXUSHORT', 'G_MININT', 'G_MAXINT',
479             'G_MAXUINT', 'G_MINLONG', 'G_MAXLONG', 'G_MAXULONG', 'G_MAXSIZE',
480             'G_MINSSIZE', 'G_MAXSSIZE', 'G_MINOFFSET', 'G_MAXOFFSET']:
481    attr = name.split("_", 1)[-1]
482    globals()[attr] = getattr(_gi, name)
483    __all__.append(attr)
484
485
486class MainLoop(GLib.MainLoop):
487    # Backwards compatible constructor API
488    def __new__(cls, context=None):
489        return GLib.MainLoop.new(context, False)
490
491    def __init__(self, context=None):
492        pass
493
494    def run(self):
495        with register_sigint_fallback(self.quit):
496            with wakeup_on_signal():
497                super(MainLoop, self).run()
498
499
500MainLoop = override(MainLoop)
501__all__.append('MainLoop')
502
503
504class MainContext(GLib.MainContext):
505    # Backwards compatible API with default value
506    def iteration(self, may_block=True):
507        return super(MainContext, self).iteration(may_block)
508
509
510MainContext = override(MainContext)
511__all__.append('MainContext')
512
513
514class Source(GLib.Source):
515    def __new__(cls, *args, **kwargs):
516        # use our custom pygi_source_new() here as g_source_new() is not
517        # bindable
518        source = source_new()
519        source.__class__ = cls
520        setattr(source, '__pygi_custom_source', True)
521        return source
522
523    def __init__(self, *args, **kwargs):
524        return super(Source, self).__init__()
525
526    def __del__(self):
527        if hasattr(self, '__pygi_custom_source'):
528            self.destroy()
529            # XXX: We have to unref the underlying source while the Python
530            # wrapper is still valid, so the source can call into the
531            # wrapper methods for the finalized callback.
532            self._clear_boxed()
533
534    def set_callback(self, fn, user_data=None):
535        if hasattr(self, '__pygi_custom_source'):
536            # use our custom pygi_source_set_callback() if for a GSource object
537            # with custom functions
538            source_set_callback(self, fn, user_data)
539        else:
540            # otherwise, for Idle and Timeout, use the standard method
541            super(Source, self).set_callback(fn, user_data)
542
543    def get_current_time(self):
544        return GLib.get_real_time() * 0.000001
545
546    get_current_time = deprecated(get_current_time,
547                                  'GLib.Source.get_time() or GLib.get_real_time()')
548
549    # as get/set_priority are introspected, we can't use the static
550    # property(get_priority, ..) here
551    def __get_priority(self):
552        return self.get_priority()
553
554    def __set_priority(self, value):
555        self.set_priority(value)
556
557    priority = property(__get_priority, __set_priority)
558
559    def __get_can_recurse(self):
560        return self.get_can_recurse()
561
562    def __set_can_recurse(self, value):
563        self.set_can_recurse(value)
564
565    can_recurse = property(__get_can_recurse, __set_can_recurse)
566
567
568Source = override(Source)
569__all__.append('Source')
570
571
572class Idle(Source):
573    def __new__(cls, priority=GLib.PRIORITY_DEFAULT):
574        source = GLib.idle_source_new()
575        source.__class__ = cls
576        return source
577
578    def __init__(self, priority=GLib.PRIORITY_DEFAULT):
579        super(Source, self).__init__()
580        if priority != GLib.PRIORITY_DEFAULT:
581            self.set_priority(priority)
582
583
584__all__.append('Idle')
585
586
587class Timeout(Source):
588    def __new__(cls, interval=0, priority=GLib.PRIORITY_DEFAULT):
589        source = GLib.timeout_source_new(interval)
590        source.__class__ = cls
591        return source
592
593    def __init__(self, interval=0, priority=GLib.PRIORITY_DEFAULT):
594        if priority != GLib.PRIORITY_DEFAULT:
595            self.set_priority(priority)
596
597
598__all__.append('Timeout')
599
600
601# backwards compatible API
602def idle_add(function, *user_data, **kwargs):
603    priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT_IDLE)
604    return GLib.idle_add(priority, function, *user_data)
605
606
607__all__.append('idle_add')
608
609
610def timeout_add(interval, function, *user_data, **kwargs):
611    priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
612    return GLib.timeout_add(priority, interval, function, *user_data)
613
614
615__all__.append('timeout_add')
616
617
618def timeout_add_seconds(interval, function, *user_data, **kwargs):
619    priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
620    return GLib.timeout_add_seconds(priority, interval, function, *user_data)
621
622
623__all__.append('timeout_add_seconds')
624
625
626# The GI GLib API uses g_io_add_watch_full renamed to g_io_add_watch with
627# a signature of (channel, priority, condition, func, user_data).
628# Prior to PyGObject 3.8, this function was statically bound with an API closer to the
629# non-full version with a signature of: (fd, condition, func, *user_data)
630# We need to support this until we are okay with breaking API in a way which is
631# not backwards compatible.
632#
633# This needs to take into account several historical APIs:
634# - calling with an fd as first argument
635# - calling with a Python file object as first argument (we keep this one as
636#   it's really convenient and does not change the number of arguments)
637# - calling without a priority as second argument
638def _io_add_watch_get_args(channel, priority_, condition, *cb_and_user_data, **kwargs):
639    if not isinstance(priority_, int) or isinstance(priority_, GLib.IOCondition):
640        warnings.warn('Calling io_add_watch without priority as second argument is deprecated',
641                      PyGIDeprecationWarning)
642        # shift the arguments around
643        user_data = cb_and_user_data
644        callback = condition
645        condition = priority_
646        if not callable(callback):
647            raise TypeError('third argument must be callable')
648
649        # backwards compatibility: Call with priority kwarg
650        if 'priority' in kwargs:
651            warnings.warn('Calling io_add_watch with priority keyword argument is deprecated, put it as second positional argument',
652                          PyGIDeprecationWarning)
653            priority_ = kwargs['priority']
654        else:
655            priority_ = GLib.PRIORITY_DEFAULT
656    else:
657        if len(cb_and_user_data) < 1 or not callable(cb_and_user_data[0]):
658            raise TypeError('expecting callback as fourth argument')
659        callback = cb_and_user_data[0]
660        user_data = cb_and_user_data[1:]
661
662    # backwards compatibility: Allow calling with fd
663    if isinstance(channel, int):
664        func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
665        real_channel = GLib.IOChannel.unix_new(channel)
666    elif isinstance(channel, socket.socket) and sys.platform == 'win32':
667        func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
668        real_channel = GLib.IOChannel.win32_new_socket(channel.fileno())
669    elif hasattr(channel, 'fileno'):
670        # backwards compatibility: Allow calling with Python file
671        func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
672        real_channel = GLib.IOChannel.unix_new(channel.fileno())
673    else:
674        assert isinstance(channel, GLib.IOChannel)
675        func_fdtransform = callback
676        real_channel = channel
677
678    return real_channel, priority_, condition, func_fdtransform, user_data
679
680
681__all__.append('_io_add_watch_get_args')
682
683
684def io_add_watch(*args, **kwargs):
685    """io_add_watch(channel, priority, condition, func, *user_data) -> event_source_id"""
686    channel, priority, condition, func, user_data = _io_add_watch_get_args(*args, **kwargs)
687    return GLib.io_add_watch(channel, priority, condition, func, *user_data)
688
689
690__all__.append('io_add_watch')
691
692
693# backwards compatible API
694class IOChannel(GLib.IOChannel):
695    def __new__(cls, filedes=None, filename=None, mode=None, hwnd=None):
696        if filedes is not None:
697            return GLib.IOChannel.unix_new(filedes)
698        if filename is not None:
699            return GLib.IOChannel.new_file(filename, mode or 'r')
700        if hwnd is not None:
701            return GLib.IOChannel.win32_new_fd(hwnd)
702        raise TypeError('either a valid file descriptor, file name, or window handle must be supplied')
703
704    def __init__(self, *args, **kwargs):
705        return super(IOChannel, self).__init__()
706
707    def read(self, max_count=-1):
708        return io_channel_read(self, max_count)
709
710    def readline(self, size_hint=-1):
711        # note, size_hint is just to maintain backwards compatible API; the
712        # old static binding did not actually use it
713        (status, buf, length, terminator_pos) = self.read_line()
714        if buf is None:
715            return ''
716        return buf
717
718    def readlines(self, size_hint=-1):
719        # note, size_hint is just to maintain backwards compatible API;
720        # the old static binding did not actually use it
721        lines = []
722        status = GLib.IOStatus.NORMAL
723        while status == GLib.IOStatus.NORMAL:
724            (status, buf, length, terminator_pos) = self.read_line()
725            # note, this appends an empty line after EOF; this is
726            # bug-compatible with the old static bindings
727            if buf is None:
728                buf = ''
729            lines.append(buf)
730        return lines
731
732    def write(self, buf, buflen=-1):
733        if not isinstance(buf, bytes):
734            buf = buf.encode('UTF-8')
735        if buflen == -1:
736            buflen = len(buf)
737        (status, written) = self.write_chars(buf, buflen)
738        return written
739
740    def writelines(self, lines):
741        for line in lines:
742            self.write(line)
743
744    _whence_map = {0: GLib.SeekType.SET, 1: GLib.SeekType.CUR, 2: GLib.SeekType.END}
745
746    def seek(self, offset, whence=0):
747        try:
748            w = self._whence_map[whence]
749        except KeyError:
750            raise ValueError("invalid 'whence' value")
751        return self.seek_position(offset, w)
752
753    def add_watch(self, condition, callback, *user_data, **kwargs):
754        priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
755        return io_add_watch(self, priority, condition, callback, *user_data)
756
757    add_watch = deprecated(add_watch, 'GLib.io_add_watch()')
758
759    def __iter__(self):
760        return self
761
762    def __next__(self):
763        (status, buf, length, terminator_pos) = self.read_line()
764        if status == GLib.IOStatus.NORMAL:
765            return buf
766        raise StopIteration
767
768    # Python 2.x compatibility
769    next = __next__
770
771
772IOChannel = override(IOChannel)
773__all__.append('IOChannel')
774
775
776class PollFD(GLib.PollFD):
777    def __new__(cls, fd, events):
778        pollfd = GLib.PollFD()
779        pollfd.__class__ = cls
780        return pollfd
781
782    def __init__(self, fd, events):
783        self.fd = fd
784        self.events = events
785
786
787PollFD = override(PollFD)
788__all__.append('PollFD')
789
790
791# The GI GLib API uses g_child_watch_add_full renamed to g_child_watch_add with
792# a signature of (priority, pid, callback, data).
793# Prior to PyGObject 3.8, this function was statically bound with an API closer to the
794# non-full version with a signature of: (pid, callback, data=None, priority=GLib.PRIORITY_DEFAULT)
795# We need to support this until we are okay with breaking API in a way which is
796# not backwards compatible.
797def _child_watch_add_get_args(priority_or_pid, pid_or_callback, *args, **kwargs):
798    user_data = []
799
800    if callable(pid_or_callback):
801        warnings.warn('Calling child_watch_add without priority as first argument is deprecated',
802                      PyGIDeprecationWarning)
803        pid = priority_or_pid
804        callback = pid_or_callback
805        if len(args) == 0:
806            priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
807        elif len(args) == 1:
808            user_data = args
809            priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
810        elif len(args) == 2:
811            user_data = [args[0]]
812            priority = args[1]
813        else:
814            raise TypeError('expected at most 4 positional arguments')
815    else:
816        priority = priority_or_pid
817        pid = pid_or_callback
818        if 'function' in kwargs:
819            callback = kwargs['function']
820            user_data = args
821        elif len(args) > 0 and callable(args[0]):
822            callback = args[0]
823            user_data = args[1:]
824        else:
825            raise TypeError('expected callback as third argument')
826
827    if 'data' in kwargs:
828        if user_data:
829            raise TypeError('got multiple values for "data" argument')
830        user_data = (kwargs['data'],)
831
832    return priority, pid, callback, user_data
833
834
835# we need this to be accessible for unit testing
836__all__.append('_child_watch_add_get_args')
837
838
839def child_watch_add(*args, **kwargs):
840    """child_watch_add(priority, pid, function, *data)"""
841    priority, pid, function, data = _child_watch_add_get_args(*args, **kwargs)
842    return GLib.child_watch_add(priority, pid, function, *data)
843
844
845__all__.append('child_watch_add')
846
847
848def get_current_time():
849    return GLib.get_real_time() * 0.000001
850
851
852get_current_time = deprecated(get_current_time, 'GLib.get_real_time()')
853
854__all__.append('get_current_time')
855
856
857# backwards compatible API with default argument, and ignoring bytes_read
858# output argument
859def filename_from_utf8(utf8string, len=-1):
860    return GLib.filename_from_utf8(utf8string, len)[0]
861
862
863__all__.append('filename_from_utf8')
864
865
866if hasattr(GLib, "unix_signal_add"):
867    unix_signal_add_full = GLib.unix_signal_add
868    __all__.append('unix_signal_add_full')
869    deprecated_attr("GLib", "unix_signal_add_full", "GLib.unix_signal_add")
870
871
872# obsolete constants for backwards compatibility
873glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)
874__all__.append('glib_version')
875deprecated_attr("GLib", "glib_version",
876                "(GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)")
877
878pyglib_version = version_info
879__all__.append('pyglib_version')
880deprecated_attr("GLib", "pyglib_version", "gi.version_info")
881