feat: add various system and theme icons, configuration files, and support assets

This commit is contained in:
jschantli committed 2026-10-03 08:14:42 -07:00
1 parent 848dac4264
commit 54baea79f4
15893 files changed
+781426 -2

No files matched your search

@@ -0,0 +1,175 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
# support overrides in different directories than our gi module
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
import sys
import os
import importlib
import types
_static_binding_error = ('When using gi.repository you must not import static '
'modules like "gobject". Please change all occurrences '
'of "import gobject" to "from gi.repository import GObject". '
'See: https://bugzilla.gnome.org/show_bug.cgi?id=709183')
# we can't have pygobject 2 loaded at the same time we load the internal _gobject
if 'gobject' in sys.modules:
raise ImportError(_static_binding_error)
from . import _gi
from ._gi import _API # noqa: F401
from ._gi import Repository
from ._gi import PyGIDeprecationWarning # noqa: F401
from ._gi import PyGIWarning # noqa: F401
_versions = {}
_overridesdir = os.path.join(os.path.dirname(__file__), 'overrides')
# Needed for compatibility with "pygobject.h"/pygobject_init()
_gobject = types.ModuleType("gi._gobject")
sys.modules[_gobject.__name__] = _gobject
_gobject._PyGObject_API = _gi._PyGObject_API
_gobject.pygobject_version = _gi.pygobject_version
version_info = _gi.pygobject_version[:]
__version__ = "{0}.{1}.{2}".format(*version_info)
_gi.register_foreign()
class _DummyStaticModule(types.ModuleType):
__path__ = None
def __getattr__(self, name):
raise AttributeError(_static_binding_error)
sys.modules['glib'] = _DummyStaticModule('glib', _static_binding_error)
sys.modules['gobject'] = _DummyStaticModule('gobject', _static_binding_error)
sys.modules['gio'] = _DummyStaticModule('gio', _static_binding_error)
sys.modules['gtk'] = _DummyStaticModule('gtk', _static_binding_error)
sys.modules['gtk.gdk'] = _DummyStaticModule('gtk.gdk', _static_binding_error)
def check_version(version):
if isinstance(version, str):
version_list = tuple(map(int, version.split(".")))
else:
version_list = version
if version_list > version_info:
raise ValueError((
"pygobject's version %s required, and available version "
"%s is not recent enough") % (version, __version__)
)
def require_version(namespace, version):
""" Ensures the correct versions are loaded when importing `gi` modules.
:param namespace: The name of module to require.
:type namespace: str
:param version: The version of module to require.
:type version: str
:raises ValueError: If module/version is already loaded, already required, or unavailable.
:Example:
.. code-block:: python
import gi
gi.require_version('Gtk', '3.0')
"""
repository = Repository.get_default()
if not isinstance(version, str):
raise ValueError('Namespace version needs to be a string.')
if namespace in repository.get_loaded_namespaces():
loaded_version = repository.get_version(namespace)
if loaded_version != version:
raise ValueError('Namespace %s is already loaded with version %s' %
(namespace, loaded_version))
if namespace in _versions and _versions[namespace] != version:
raise ValueError('Namespace %s already requires version %s' %
(namespace, _versions[namespace]))
available_versions = repository.enumerate_versions(namespace)
if not available_versions:
raise ValueError('Namespace %s not available' % namespace)
if version not in available_versions:
raise ValueError('Namespace %s not available for version %s' %
(namespace, version))
_versions[namespace] = version
def require_versions(requires):
""" Utility function for consolidating multiple `gi.require_version()` calls.
:param requires: The names and versions of modules to require.
:type requires: dict
:Example:
.. code-block:: python
import gi
gi.require_versions({'Gtk': '3.0', 'GLib': '2.0', 'Gio': '2.0'})
"""
for module_name, module_version in requires.items():
require_version(module_name, module_version)
def get_required_version(namespace):
return _versions.get(namespace, None)
def require_foreign(namespace, symbol=None):
"""Ensure the given foreign marshaling module is available and loaded.
:param str namespace:
Introspection namespace of the foreign module (e.g. "cairo")
:param symbol:
Optional symbol typename to ensure a converter exists.
:type symbol: str or None
:raises: ImportError
:Example:
.. code-block:: python
import gi
import cairo
gi.require_foreign('cairo')
"""
try:
_gi.require_foreign(namespace, symbol)
except Exception as e:
raise ImportError(str(e))
importlib.import_module('gi.repository', namespace)
@@ -0,0 +1,47 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# pygobject - Python bindings for the GObject library
# Copyright (C) 2006-2007 Johan Dahlin
#
# gi/_constants.py: GObject type constants
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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, see <http://www.gnu.org/licenses/>.
from . import _gi
TYPE_INVALID = _gi.TYPE_INVALID
TYPE_NONE = _gi.GType.from_name('void')
TYPE_INTERFACE = _gi.GType.from_name('GInterface')
TYPE_CHAR = _gi.GType.from_name('gchar')
TYPE_UCHAR = _gi.GType.from_name('guchar')
TYPE_BOOLEAN = _gi.GType.from_name('gboolean')
TYPE_INT = _gi.GType.from_name('gint')
TYPE_UINT = _gi.GType.from_name('guint')
TYPE_LONG = _gi.GType.from_name('glong')
TYPE_ULONG = _gi.GType.from_name('gulong')
TYPE_INT64 = _gi.GType.from_name('gint64')
TYPE_UINT64 = _gi.GType.from_name('guint64')
TYPE_ENUM = _gi.GType.from_name('GEnum')
TYPE_FLAGS = _gi.GType.from_name('GFlags')
TYPE_FLOAT = _gi.GType.from_name('gfloat')
TYPE_DOUBLE = _gi.GType.from_name('gdouble')
TYPE_STRING = _gi.GType.from_name('gchararray')
TYPE_POINTER = _gi.GType.from_name('gpointer')
TYPE_BOXED = _gi.GType.from_name('GBoxed')
TYPE_PARAM = _gi.GType.from_name('GParam')
TYPE_OBJECT = _gi.GType.from_name('GObject')
TYPE_PYOBJECT = _gi.GType.from_name('PyObject')
TYPE_GTYPE = _gi.GType.from_name('GType')
TYPE_STRV = _gi.GType.from_name('GStrv')
TYPE_VARIANT = _gi.GType.from_name('GVariant')
TYPE_UNICHAR = TYPE_UINT
@@ -0,0 +1,55 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
#
# _error.py: GError Python implementation
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
# NOTE: This file should not have any dependencies on introspection libs
# like gi.repository.GLib because it would cause a circular dependency.
# Developers wanting to use the GError class in their applications should
# use gi.repository.GLib.GError
class GError(RuntimeError):
def __init__(self, message='unknown error', domain='pygi-error', code=0):
super(GError, self).__init__(message)
self.message = message
self.domain = domain
self.code = code
def __str__(self):
return "%s: %s (%d)" % (self.domain, self.message, self.code)
def __repr__(self):
return "%s.%s('%s', '%s', %d)" % (
GError.__module__.rsplit(".", 1)[-1], GError.__name__,
self.message, self.domain, self.code)
def copy(self):
return GError(self.message, self.domain, self.code)
def matches(self, domain, code):
"""Placeholder that will be monkey patched in GLib overrides."""
raise NotImplementedError
@staticmethod
def new_literal(domain, message, code):
"""Placeholder that will be monkey patched in GLib overrides."""
raise NotImplementedError
@@ -0,0 +1,307 @@
# Copyright 2015 Dustin Spicuzza <dustin@virtualroadside.com>
# 2018 Nikita Churaev <lamefun.x0r@gmail.com>
# 2018 Christoph Reiter <reiter.christoph@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import os
from collections import abc
from functools import partial
from gi.repository import GLib, GObject, Gio
def _extract_handler_and_args(obj_or_map, handler_name):
handler = None
if isinstance(obj_or_map, abc.Mapping):
handler = obj_or_map.get(handler_name, None)
else:
handler = getattr(obj_or_map, handler_name, None)
if handler is None:
raise AttributeError('Handler %s not found' % handler_name)
args = ()
if isinstance(handler, abc.Sequence):
if len(handler) == 0:
raise TypeError("Handler %s tuple can not be empty" % handler)
args = handler[1:]
handler = handler[0]
elif not callable(handler):
raise TypeError('Handler %s is not a method, function or tuple' % handler)
return handler, args
def define_builder_scope():
from gi.repository import Gtk
class BuilderScope(GObject.GObject, Gtk.BuilderScope):
def __init__(self, scope_object=None):
super().__init__()
self._scope_object = scope_object
def do_create_closure(self, builder, func_name, flags, obj):
current_object = builder.get_current_object() or self._scope_object
if not self._scope_object:
current_object = builder.get_current_object()
if func_name not in current_object.__gtktemplate_methods__:
return None
current_object.__gtktemplate_handlers__.add(func_name)
handler_name = current_object.__gtktemplate_methods__[func_name]
else:
current_object = self._scope_object
handler_name = func_name
swapped = int(flags & Gtk.BuilderClosureFlags.SWAPPED)
if swapped:
raise RuntimeError(
"%r not supported" % GObject.ConnectFlags.SWAPPED)
return None
handler, args = _extract_handler_and_args(current_object, handler_name)
if obj:
p = partial(handler, *args, swap_data=obj)
else:
p = partial(handler, *args)
p.__gtk_template__ = True
return p
return BuilderScope
def connect_func(builder, obj, signal_name, handler_name,
connect_object, flags, cls):
if handler_name not in cls.__gtktemplate_methods__:
return
method_name = cls.__gtktemplate_methods__[handler_name]
template_inst = builder.get_object(cls.__gtype_name__)
template_inst.__gtktemplate_handlers__.add(handler_name)
handler = getattr(template_inst, method_name)
after = int(flags & GObject.ConnectFlags.AFTER)
swapped = int(flags & GObject.ConnectFlags.SWAPPED)
if swapped:
raise RuntimeError(
"%r not supported" % GObject.ConnectFlags.SWAPPED)
if connect_object is not None:
if after:
func = obj.connect_object_after
else:
func = obj.connect_object
func(signal_name, handler, connect_object)
else:
if after:
func = obj.connect_after
else:
func = obj.connect
func(signal_name, handler)
def register_template(cls):
from gi.repository import Gtk
bound_methods = {}
bound_widgets = {}
for attr_name, obj in list(cls.__dict__.items()):
if isinstance(obj, CallThing):
setattr(cls, attr_name, obj._func)
handler_name = obj._name
if handler_name is None:
handler_name = attr_name
if handler_name in bound_methods:
old_attr_name = bound_methods[handler_name]
raise RuntimeError(
"Error while exposing handler %r as %r, "
"already available as %r" % (
handler_name, attr_name, old_attr_name))
else:
bound_methods[handler_name] = attr_name
elif isinstance(obj, Child):
widget_name = obj._name
if widget_name is None:
widget_name = attr_name
if widget_name in bound_widgets:
old_attr_name = bound_widgets[widget_name]
raise RuntimeError(
"Error while exposing child %r as %r, "
"already available as %r" % (
widget_name, attr_name, old_attr_name))
else:
bound_widgets[widget_name] = attr_name
cls.bind_template_child_full(widget_name, obj._internal, 0)
cls.__gtktemplate_methods__ = bound_methods
cls.__gtktemplate_widgets__ = bound_widgets
if Gtk._version == "4.0":
BuilderScope = define_builder_scope()
cls.set_template_scope(BuilderScope())
else:
cls.set_connect_func(connect_func, cls)
base_init_template = cls.init_template
cls.__dontuse_ginstance_init__ = \
lambda s: init_template(s, cls, base_init_template)
# To make this file work with older PyGObject we expose our init code
# as init_template() but make it a noop when we call it ourselves first
cls.init_template = cls.__dontuse_ginstance_init__
def init_template(self, cls, base_init_template):
self.init_template = lambda: None
if self.__class__ is not cls:
raise TypeError(
"Inheritance from classes with @Gtk.Template decorators "
"is not allowed at this time")
self.__gtktemplate_handlers__ = set()
base_init_template(self)
for widget_name, attr_name in self.__gtktemplate_widgets__.items():
self.__dict__[attr_name] = self.get_template_child(cls, widget_name)
for handler_name, attr_name in self.__gtktemplate_methods__.items():
if handler_name not in self.__gtktemplate_handlers__:
raise RuntimeError(
"Handler '%s' was declared with @Gtk.Template.Callback "
"but was not present in template" % handler_name)
class Child(object):
def __init__(self, name=None, **kwargs):
self._name = name
self._internal = kwargs.pop("internal", False)
if kwargs:
raise TypeError("Unhandled arguments: %r" % kwargs)
class CallThing(object):
def __init__(self, name, func):
self._name = name
self._func = func
class Callback(object):
def __init__(self, name=None):
self._name = name
def __call__(self, func):
return CallThing(self._name, func)
def validate_resource_path(path):
"""Raises GLib.Error in case the resource doesn't exist"""
try:
Gio.resources_get_info(path, Gio.ResourceLookupFlags.NONE)
except GLib.Error:
# resources_get_info() doesn't handle overlays but we keep using it
# as a fast path.
# https://gitlab.gnome.org/GNOME/pygobject/issues/230
Gio.resources_lookup_data(path, Gio.ResourceLookupFlags.NONE)
class Template(object):
def __init__(self, **kwargs):
self.string = None
self.filename = None
self.resource_path = None
if "string" in kwargs:
self.string = kwargs.pop("string")
elif "filename" in kwargs:
self.filename = kwargs.pop("filename")
elif "resource_path" in kwargs:
self.resource_path = kwargs.pop("resource_path")
else:
raise TypeError(
"Requires one of the following arguments: "
"string, filename, resource_path")
if kwargs:
raise TypeError("Unhandled keyword arguments %r" % kwargs)
@classmethod
def from_file(cls, filename):
return cls(filename=filename)
@classmethod
def from_string(cls, string):
return cls(string=string)
@classmethod
def from_resource(cls, resource_path):
return cls(resource_path=resource_path)
Callback = Callback
Child = Child
def __call__(self, cls):
from gi.repository import Gtk
if not isinstance(cls, type) or not issubclass(cls, Gtk.Widget):
raise TypeError("Can only use @Gtk.Template on Widgets")
if "__gtype_name__" not in cls.__dict__:
raise TypeError(
"%r does not have a __gtype_name__. Set it to the name "
"of the class in your template" % cls.__name__)
if hasattr(cls, "__gtktemplate_methods__"):
raise TypeError("Cannot nest template classes")
if self.string is not None:
data = self.string
if not isinstance(data, bytes):
data = data.encode("utf-8")
bytes_ = GLib.Bytes.new(data)
cls.set_template(bytes_)
register_template(cls)
return cls
elif self.resource_path is not None:
validate_resource_path(self.resource_path)
cls.set_template_from_resource(self.resource_path)
register_template(cls)
return cls
else:
assert self.filename is not None
file_ = Gio.File.new_for_path(os.fspath(self.filename))
bytes_ = GLib.Bytes.new(file_.load_contents()[1])
cls.set_template(bytes_)
register_template(cls)
return cls
__all__ = ["Template"]
@@ -0,0 +1,379 @@
# -*- Mode: Python -*-
# pygobject - Python bindings for the GObject library
# Copyright (C) 2006 Johannes Hoelzl
#
# glib/option.py: GOption command line parser
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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, see <http://www.gnu.org/licenses/>.
"""GOption command line parser
Extends optparse to use the GOptionGroup, GOptionEntry and GOptionContext
objects. So it is possible to use the gtk, gnome_program and gstreamer command
line groups and contexts.
Use this interface instead of the raw wrappers of GOptionContext and
GOptionGroup in glib.
"""
import sys
import optparse
import warnings
from optparse import OptParseError, OptionError, OptionValueError, \
BadOptionError, OptionConflictError
from .module import get_introspection_module
from gi import _gi, PyGIDeprecationWarning
from gi._error import GError
GLib = get_introspection_module('GLib')
OPTION_CONTEXT_ERROR_QUARK = GLib.quark_to_string(GLib.option_error_quark())
__all__ = [
"OptParseError",
"OptionError",
"OptionValueError",
"BadOptionError",
"OptionConflictError",
"Option",
"OptionGroup",
"OptionParser",
"make_option",
]
class Option(optparse.Option):
"""Represents a command line option
To use the extended possibilities of the GOption API Option
(and make_option) are extended with new types and attributes.
Types:
filename The supplied arguments are read as filename, GOption
parses this type in with the GLib filename encoding.
:ivar optional_arg:
This does not need a arguement, but it can be supplied.
:ivar hidden:
The help list does not show this option
:ivar in_main:
This option apears in the main group, this should only
be used for backwards compatibility.
Use Option.REMAINING as option name to get all positional arguments.
.. NOTE::
Every argument to an option is passed as utf-8 coded string, the only
exception are options which use the 'filename' type, its arguments
are passed as strings in the GLib filename encoding.
For further help, see optparse.Option.
"""
TYPES = optparse.Option.TYPES + (
'filename',
)
ATTRS = optparse.Option.ATTRS + [
'hidden',
'in_main',
'optional_arg',
]
REMAINING = '--' + GLib.OPTION_REMAINING
def __init__(self, *args, **kwargs):
warnings.warn(
"gi.repository.GLib.option.Option is depecated, use gi.repository.GLib.OptionEntry instead",
PyGIDeprecationWarning
)
optparse.Option.__init__(self, *args, **kwargs)
if not self._long_opts:
raise ValueError("%s at least one long option name.")
if len(self._long_opts) < len(self._short_opts):
raise ValueError(
"%s at least more long option names than short option names.")
if not self.help:
raise ValueError("%s needs a help message.", self._long_opts[0])
def _set_opt_string(self, opts):
if self.REMAINING in opts:
self._long_opts.append(self.REMAINING)
optparse.Option._set_opt_string(self, opts)
if len(self._short_opts) > len(self._long_opts):
raise OptionError("goption.Option needs more long option names "
"than short option names")
def _to_goptionentries(self):
flags = 0
if self.hidden:
flags |= GLib.OptionFlags.HIDDEN
if self.in_main:
flags |= GLib.OptionFlags.IN_MAIN
if self.takes_value():
if self.optional_arg:
flags |= GLib.OptionFlags.OPTIONAL_ARG
else:
flags |= GLib.OptionFlags.NO_ARG
if self.type == 'filename':
flags |= GLib.OptionFlags.FILENAME
for (long_name, short_name) in zip(self._long_opts, self._short_opts):
short_bytes = short_name[1]
if not isinstance(short_bytes, bytes):
short_bytes = short_bytes.encode("utf-8")
yield (long_name[2:], short_bytes, flags, self.help, self.metavar)
for long_name in self._long_opts[len(self._short_opts):]:
yield (long_name[2:], b'\0', flags, self.help, self.metavar)
class OptionGroup(optparse.OptionGroup):
"""A group of command line options.
:param str name:
The groups name, used to create the --help-{name} option
:param str description:
Shown as title of the groups help view
:param str help_description:
Shown as help to the --help-{name} option
:param list option_list:
The options used in this group, must be option.Option()
:param dict defaults:
A dicitionary of default values
:param translation_domain:
Sets the translation domain for gettext().
.. NOTE::
This OptionGroup does not exactly map the optparse.OptionGroup
interface. There is no parser object to supply, but it is possible
to set default values and option_lists. Also the default values and
values are not shared with the OptionParser.
To pass a OptionGroup into a function which expects a GOptionGroup (e.g.
gnome_program_init() ). OptionGroup.get_option_group() can be used.
For further help, see optparse.OptionGroup.
"""
def __init__(self, name, description, help_description="",
option_list=None, defaults=None,
translation_domain=None):
warnings.warn(
"gi.repository.GLib.option.OptionGroup is depecated, use gi.repository.GLib.OptionContext instead",
PyGIDeprecationWarning
)
optparse.OptionContainer.__init__(self, Option, 'error', description)
self.name = name
self.parser = None
self.help_description = help_description
if defaults:
self.defaults = defaults
self.values = None
self.translation_domain = translation_domain
if option_list:
for option in option_list:
self.add_option(option)
def _create_option_list(self):
self.option_list = []
self._create_option_mappings()
def _to_goptiongroup(self, parser):
def callback(option_name, option_value, group):
if option_name.startswith('--'):
opt = self._long_opt[option_name]
else:
opt = self._short_opt[option_name]
try:
opt.process(option_name, option_value, self.values, parser)
except OptionValueError:
error = sys.exc_info()[1]
gerror = GError(str(error))
gerror.domain = OPTION_CONTEXT_ERROR_QUARK
gerror.code = GLib.OptionError.BAD_VALUE
gerror.message = str(error)
raise gerror
group = _gi.OptionGroup(self.name, self.description,
self.help_description, callback)
if self.translation_domain:
group.set_translation_domain(self.translation_domain)
entries = []
for option in self.option_list:
entries.extend(option._to_goptionentries())
group.add_entries(entries)
return group
def get_option_group(self, parser=None):
""" Returns the corresponding GOptionGroup object.
Can be used as parameter for gnome_program_init(), gtk_init().
"""
self.set_values_to_defaults()
return self._to_goptiongroup(parser)
def set_values_to_defaults(self):
for option in self.option_list:
default = self.defaults.get(option.dest)
if isinstance(default, str):
opt_str = option.get_opt_string()
self.defaults[option.dest] = option.check_value(
opt_str, default)
self.values = optparse.Values(self.defaults)
class OptionParser(optparse.OptionParser):
"""Command line parser with GOption support.
:param bool help_enabled:
The --help, --help-all and --help-{group} options are enabled (default).
:param bool ignore_unknown_options:
Do not throw a exception when a option is not knwon, the option
will be in the result list.
.. NOTE::
The OptionParser interface is not the exactly the same as the
optparse.OptionParser interface. Especially the usage parameter
is only used to show the metavar of the arguements.
OptionParser.add_option_group() does not only accept OptionGroup instances
but also glib.OptionGroup, which is returned by gtk_get_option_group().
Only glib.option.OptionGroup and glib.option.Option instances should
be passed as groups and options.
For further help, see optparse.OptionParser.
"""
def __init__(self, *args, **kwargs):
warnings.warn(
"gi.repository.GLib.option.OptionParser is depecated, use gi.repository.GLib.OptionContext instead",
PyGIDeprecationWarning
)
if 'option_class' not in kwargs:
kwargs['option_class'] = Option
self.help_enabled = kwargs.pop('help_enabled', True)
self.ignore_unknown_options = kwargs.pop('ignore_unknown_options',
False)
optparse.OptionParser.__init__(self, add_help_option=False,
*args, **kwargs)
def set_usage(self, usage):
if usage is None:
self.usage = ''
elif usage.startswith("%prog"):
self.usage = usage[len("%prog"):]
else:
self.usage = usage
def _to_goptioncontext(self, values):
if self.description:
parameter_string = self.usage + " - " + self.description
else:
parameter_string = self.usage
context = _gi.OptionContext(parameter_string)
context.set_help_enabled(self.help_enabled)
context.set_ignore_unknown_options(self.ignore_unknown_options)
for option_group in self.option_groups:
if isinstance(option_group, _gi.OptionGroup):
g_group = option_group
else:
g_group = option_group.get_option_group(self)
context.add_group(g_group)
def callback(option_name, option_value, group):
if option_name.startswith('--'):
opt = self._long_opt[option_name]
else:
opt = self._short_opt[option_name]
opt.process(option_name, option_value, values, self)
main_group = _gi.OptionGroup(None, None, None, callback)
main_entries = []
for option in self.option_list:
main_entries.extend(option._to_goptionentries())
main_group.add_entries(main_entries)
context.set_main_group(main_group)
return context
def add_option_group(self, *args, **kwargs):
if isinstance(args[0], str):
optparse.OptionParser.add_option_group(self,
OptionGroup(self, *args, **kwargs))
return
elif len(args) == 1 and not kwargs:
if isinstance(args[0], OptionGroup):
if not args[0].parser:
args[0].parser = self
if args[0].parser is not self:
raise ValueError("invalid OptionGroup (wrong parser)")
if isinstance(args[0], _gi.OptionGroup):
self.option_groups.append(args[0])
return
optparse.OptionParser.add_option_group(self, *args, **kwargs)
def _get_all_options(self):
options = self.option_list[:]
for group in self.option_groups:
if isinstance(group, optparse.OptionGroup):
options.extend(group.option_list)
return options
def _process_args(self, largs, rargs, values):
context = self._to_goptioncontext(values)
# _process_args() returns the remaining parameters in rargs.
# The prepended program name is used to all g_set_prgname()
# The program name is cut away so it doesn't appear in the result.
rargs[:] = context.parse([sys.argv[0]] + rargs)[1:]
def parse_args(self, args=None, values=None):
try:
options, args = optparse.OptionParser.parse_args(
self, args, values)
except GError:
error = sys.exc_info()[1]
if error.domain != OPTION_CONTEXT_ERROR_QUARK:
raise
if error.code == GLib.OptionError.BAD_VALUE:
raise OptionValueError(error.message)
elif error.code == GLib.OptionError.UNKNOWN_OPTION:
raise BadOptionError(error.message)
elif error.code == GLib.OptionError.FAILED:
raise OptParseError(error.message)
else:
raise
for group in self.option_groups:
for key, value in group.values.__dict__.items():
options.ensure_value(key, value)
return options, args
make_option = Option
@@ -0,0 +1,239 @@
# Copyright 2017 Christoph Reiter
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import os
import socket
import signal
import threading
from contextlib import closing, contextmanager
from . import _gi
def ensure_socket_not_inheritable(sock):
"""Ensures that the socket is not inherited by child processes
Raises:
EnvironmentError
NotImplementedError: With Python <3.4 on Windows
"""
if hasattr(sock, "set_inheritable"):
sock.set_inheritable(False)
else:
try:
import fcntl
except ImportError:
raise NotImplementedError(
"Not implemented for older Python on Windows")
else:
fd = sock.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
_wakeup_fd_is_active = False
"""Since we can't check if set_wakeup_fd() is already used for nested event
loops without introducing a race condition we keep track of it globally.
"""
@contextmanager
def wakeup_on_signal():
"""A decorator for functions which create a glib event loop to keep
Python signal handlers working while the event loop is idling.
In case an OS signal is received will wake the default event loop up
shortly so that any registered Python signal handlers registered through
signal.signal() can run.
In case the wrapped function is not called from the main thread it will be
called as is and it will not wake up the default loop for signals.
"""
global _wakeup_fd_is_active
if _wakeup_fd_is_active:
yield
return
from gi.repository import GLib
read_socket, write_socket = socket.socketpair()
with closing(read_socket), closing(write_socket):
for sock in [read_socket, write_socket]:
sock.setblocking(False)
ensure_socket_not_inheritable(sock)
try:
orig_fd = signal.set_wakeup_fd(write_socket.fileno())
except ValueError:
# Raised in case this is not the main thread -> give up.
yield
return
else:
_wakeup_fd_is_active = True
def signal_notify(source, condition):
if condition & GLib.IO_IN:
try:
return bool(read_socket.recv(1))
except EnvironmentError as e:
print(e)
return False
return True
else:
return False
try:
if os.name == "nt":
channel = GLib.IOChannel.win32_new_socket(
read_socket.fileno())
else:
channel = GLib.IOChannel.unix_new(read_socket.fileno())
source_id = GLib.io_add_watch(
channel,
GLib.PRIORITY_DEFAULT,
(GLib.IOCondition.IN | GLib.IOCondition.HUP |
GLib.IOCondition.NVAL | GLib.IOCondition.ERR),
signal_notify)
try:
yield
finally:
GLib.source_remove(source_id)
finally:
write_fd = signal.set_wakeup_fd(orig_fd)
if write_fd != write_socket.fileno():
# Someone has called set_wakeup_fd while func() was active,
# so let's re-revert again.
signal.set_wakeup_fd(write_fd)
_wakeup_fd_is_active = False
PyOS_getsig = _gi.pyos_getsig
# We save the signal pointer so we can detect if glib has changed the
# signal handler behind Python's back (GLib.unix_signal_add)
if signal.getsignal(signal.SIGINT) is signal.default_int_handler:
startup_sigint_ptr = PyOS_getsig(signal.SIGINT)
else:
# Something has set the handler before import, we can't get a ptr
# for the default handler so make sure the pointer will never match.
startup_sigint_ptr = -1
def sigint_handler_is_default():
"""Returns if on SIGINT the default Python handler would be called"""
return (signal.getsignal(signal.SIGINT) is signal.default_int_handler and
PyOS_getsig(signal.SIGINT) == startup_sigint_ptr)
@contextmanager
def sigint_handler_set_and_restore_default(handler):
"""Context manager for saving/restoring the SIGINT handler default state.
Will only restore the default handler again if the handler is not changed
while the context is active.
"""
assert sigint_handler_is_default()
signal.signal(signal.SIGINT, handler)
sig_ptr = PyOS_getsig(signal.SIGINT)
try:
yield
finally:
if signal.getsignal(signal.SIGINT) is handler and \
PyOS_getsig(signal.SIGINT) == sig_ptr:
signal.signal(signal.SIGINT, signal.default_int_handler)
def is_main_thread():
"""Returns True in case the function is called from the main thread"""
return threading.current_thread().name == "MainThread"
_callback_stack = []
_sigint_called = False
@contextmanager
def register_sigint_fallback(callback):
"""Installs a SIGINT signal handler in case the default Python one is
active which calls 'callback' in case the signal occurs.
Only does something if called from the main thread.
In case of nested context managers the signal handler will be only
installed once and the callbacks will be called in the reverse order
of their registration.
The old signal handler will be restored in case no signal handler is
registered while the context is active.
"""
# To handle multiple levels of event loops we need to call the last
# callback first, wait until the inner most event loop returns control
# and only then call the next callback, and so on... until we
# reach the outer most which manages the signal handler and raises
# in the end
global _callback_stack, _sigint_called
if not is_main_thread():
yield
return
if not sigint_handler_is_default():
if _callback_stack:
# This is an inner event loop, append our callback
# to the stack so the parent context can call it.
_callback_stack.append(callback)
try:
yield
finally:
cb = _callback_stack.pop()
if _sigint_called:
cb()
else:
# There is a signal handler set by the user, just do nothing
yield
return
_sigint_called = False
def sigint_handler(sig_num, frame):
global _callback_stack, _sigint_called
if _sigint_called:
return
_sigint_called = True
_callback_stack.pop()()
_callback_stack.append(callback)
try:
with sigint_handler_set_and_restore_default(sigint_handler):
yield
finally:
if _sigint_called:
signal.default_int_handler(signal.SIGINT, None)
else:
_callback_stack.pop()
@@ -0,0 +1,402 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# pygobject - Python bindings for the GObject library
# Copyright (C) 2007 Johan Dahlin
#
# gi/_propertyhelper.py: GObject property wrapper/helper
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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, see <http://www.gnu.org/licenses/>.
from . import _gi
from ._constants import \
TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, \
TYPE_BOOLEAN, TYPE_INT, TYPE_UINT, TYPE_LONG, \
TYPE_ULONG, TYPE_INT64, TYPE_UINT64, TYPE_ENUM, TYPE_FLAGS, \
TYPE_FLOAT, TYPE_DOUBLE, TYPE_STRING, \
TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \
TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT
G_MAXFLOAT = _gi.G_MAXFLOAT
G_MAXDOUBLE = _gi.G_MAXDOUBLE
G_MININT = _gi.G_MININT
G_MAXINT = _gi.G_MAXINT
G_MAXUINT = _gi.G_MAXUINT
G_MINLONG = _gi.G_MINLONG
G_MAXLONG = _gi.G_MAXLONG
G_MAXULONG = _gi.G_MAXULONG
class Property(object):
"""Creates a new Property which when used in conjunction with
GObject subclass will create a Python property accessor for the
GObject ParamSpec.
:param callable getter:
getter to get the value of the property
:param callable setter:
setter to set the value of the property
:param type type:
type of property
:param default:
default value, must match the property type.
:param str nick:
short description
:param str blurb:
long description
:param GObject.ParamFlags flags:
parameter flags
:keyword minimum:
minimum allowed value (int, float, long only)
:keyword maximum:
maximum allowed value (int, float, long only)
.. code-block:: python
class MyObject(GObject.Object):
prop = GObject.Property(type=str)
obj = MyObject()
obj.prop = 'value'
obj.prop # now is 'value'
The API is similar to the builtin :py:func:`property`:
.. code-block:: python
class AnotherObject(GObject.Object):
value = 0
@GObject.Property
def prop(self):
'Read only property.'
return 1
@GObject.Property(type=int)
def propInt(self):
'Read-write integer property.'
return self.value
@propInt.setter
def propInt(self, value):
self.value = value
"""
_type_from_pytype_lookup = {
int: TYPE_INT,
bool: TYPE_BOOLEAN,
float: TYPE_DOUBLE,
str: TYPE_STRING,
object: TYPE_PYOBJECT,
}
_min_value_lookup = {
TYPE_UINT: 0,
TYPE_ULONG: 0,
TYPE_UINT64: 0,
# Remember that G_MINFLOAT and G_MINDOUBLE are something different.
TYPE_FLOAT: -G_MAXFLOAT,
TYPE_DOUBLE: -G_MAXDOUBLE,
TYPE_INT: G_MININT,
TYPE_LONG: G_MINLONG,
TYPE_INT64: -2 ** 63,
}
_max_value_lookup = {
TYPE_UINT: G_MAXUINT,
TYPE_ULONG: G_MAXULONG,
TYPE_INT64: 2 ** 63 - 1,
TYPE_UINT64: 2 ** 64 - 1,
TYPE_FLOAT: G_MAXFLOAT,
TYPE_DOUBLE: G_MAXDOUBLE,
TYPE_INT: G_MAXINT,
TYPE_LONG: G_MAXLONG,
}
_default_lookup = {
TYPE_INT: 0,
TYPE_UINT: 0,
TYPE_LONG: 0,
TYPE_ULONG: 0,
TYPE_INT64: 0,
TYPE_UINT64: 0,
TYPE_STRING: '',
TYPE_FLOAT: 0.0,
TYPE_DOUBLE: 0.0,
}
class __metaclass__(type):
def __repr__(self):
return "<class 'GObject.Property'>"
def __init__(self, getter=None, setter=None, type=None, default=None,
nick='', blurb='', flags=_gi.PARAM_READWRITE,
minimum=None, maximum=None):
self.name = None
if type is None:
type = object
self.type = self._type_from_python(type)
self.default = self._get_default(default)
self._check_default()
if not isinstance(nick, str):
raise TypeError("nick must be a string")
self.nick = nick
if not isinstance(blurb, str):
raise TypeError("blurb must be a string")
self.blurb = blurb
# Always clobber __doc__ with blurb even if blurb is empty because
# we don't want the lengthy Property class documentation showing up
# on instances.
self.__doc__ = blurb
self.flags = flags
# Call after setting blurb for potential __doc__ usage.
if getter and not setter:
setter = self._readonly_setter
elif setter and not getter:
getter = self._writeonly_getter
elif not setter and not getter:
getter = self._default_getter
setter = self._default_setter
self.getter(getter)
# do not call self.setter() here, as this defines the property name
# already
self.fset = setter
if minimum is not None:
if minimum < self._get_minimum():
raise TypeError(
"Minimum for type %s cannot be lower than %d" %
(self.type, self._get_minimum()))
else:
minimum = self._get_minimum()
self.minimum = minimum
if maximum is not None:
if maximum > self._get_maximum():
raise TypeError(
"Maximum for type %s cannot be higher than %d" %
(self.type, self._get_maximum()))
else:
maximum = self._get_maximum()
self.maximum = maximum
self._exc = None
def __repr__(self):
return '<GObject Property %s (%s)>' % (
self.name or '(uninitialized)',
self.type.name)
def __get__(self, instance, klass):
if instance is None:
return self
self._exc = None
value = self.fget(instance)
if self._exc:
exc = self._exc
self._exc = None
raise exc
return value
def __set__(self, instance, value):
if instance is None:
raise TypeError
self._exc = None
instance.set_property(self.name, value)
if self._exc:
exc = self._exc
self._exc = None
raise exc
def __call__(self, fget):
"""Allows application of the getter along with init arguments."""
return self.getter(fget)
def getter(self, fget):
"""Set the getter function to fget. For use as a decorator."""
if fget.__doc__:
# Always clobber docstring and blurb with the getter docstring.
self.blurb = fget.__doc__
self.__doc__ = fget.__doc__
self.fget = fget
return self
def setter(self, fset):
"""Set the setter function to fset. For use as a decorator."""
self.fset = fset
# with a setter decorator, we must ignore the name of the method in
# install_properties, as this does not need to be a valid property name
# and does not define the property name. So set the name here.
if not self.name:
self.name = self.fget.__name__
return self
def _type_from_python(self, type_):
if type_ in self._type_from_pytype_lookup:
return self._type_from_pytype_lookup[type_]
elif (isinstance(type_, type) and
issubclass(type_, (_gi.GObject,
_gi.GEnum,
_gi.GFlags,
_gi.GBoxed,
_gi.GInterface))):
return type_.__gtype__
elif type_ in (TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR,
TYPE_INT, TYPE_UINT, TYPE_BOOLEAN, TYPE_LONG,
TYPE_ULONG, TYPE_INT64, TYPE_UINT64,
TYPE_FLOAT, TYPE_DOUBLE, TYPE_POINTER,
TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, TYPE_STRING,
TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT):
return type_
else:
raise TypeError("Unsupported type: %r" % (type_,))
def _get_default(self, default):
if default is not None:
return default
return self._default_lookup.get(self.type, None)
def _check_default(self):
ptype = self.type
default = self.default
if (ptype == TYPE_BOOLEAN and (default not in (True, False))):
raise TypeError(
"default must be True or False, not %r" % (default,))
elif ptype == TYPE_PYOBJECT:
if default is not None:
raise TypeError("object types does not have default values")
elif ptype == TYPE_GTYPE:
if default is not None:
raise TypeError("GType types does not have default values")
elif ptype.is_a(TYPE_ENUM):
if default is None:
raise TypeError("enum properties needs a default value")
elif not _gi.GType(default).is_a(ptype):
raise TypeError("enum value %s must be an instance of %r" %
(default, ptype))
elif ptype.is_a(TYPE_FLAGS):
if not _gi.GType(default).is_a(ptype):
raise TypeError("flags value %s must be an instance of %r" %
(default, ptype))
elif ptype.is_a(TYPE_STRV) and default is not None:
if not isinstance(default, list):
raise TypeError("Strv value %s must be a list" % repr(default))
for val in default:
if type(val) not in (str, bytes):
raise TypeError("Strv value %s must contain only strings" % str(default))
elif ptype.is_a(TYPE_VARIANT) and default is not None:
if not hasattr(default, '__gtype__') or not _gi.GType(default).is_a(TYPE_VARIANT):
raise TypeError("variant value %s must be an instance of %r" %
(default, ptype))
def _get_minimum(self):
return self._min_value_lookup.get(self.type, None)
def _get_maximum(self):
return self._max_value_lookup.get(self.type, None)
#
# Getter and Setter
#
def _default_setter(self, instance, value):
setattr(instance, '_property_helper_' + self.name, value)
def _default_getter(self, instance):
return getattr(instance, '_property_helper_' + self.name, self.default)
def _readonly_setter(self, instance, value):
self._exc = TypeError("%s property of %s is read-only" % (
self.name, type(instance).__name__))
def _writeonly_getter(self, instance):
self._exc = TypeError("%s property of %s is write-only" % (
self.name, type(instance).__name__))
#
# Public API
#
def get_pspec_args(self):
ptype = self.type
if ptype in (TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG,
TYPE_INT64, TYPE_UINT64, TYPE_FLOAT, TYPE_DOUBLE):
args = self.minimum, self.maximum, self.default
elif (ptype == TYPE_STRING or ptype == TYPE_BOOLEAN or
ptype.is_a(TYPE_ENUM) or ptype.is_a(TYPE_FLAGS) or
ptype.is_a(TYPE_VARIANT)):
args = (self.default,)
elif ptype in (TYPE_PYOBJECT, TYPE_GTYPE):
args = ()
elif ptype.is_a(TYPE_OBJECT) or ptype.is_a(TYPE_BOXED):
args = ()
else:
raise NotImplementedError(ptype)
return (self.type, self.nick, self.blurb) + args + (self.flags,)
def install_properties(cls):
"""
Scans the given class for instances of Property and merges them
into the classes __gproperties__ dict if it exists or adds it if not.
"""
gproperties = cls.__dict__.get('__gproperties__', {})
props = []
for name, prop in cls.__dict__.items():
if isinstance(prop, Property): # not same as the built-in
# if a property was defined with a decorator, it may already have
# a name; if it was defined with an assignment (prop = Property(...))
# we set the property's name to the member name
if not prop.name:
prop.name = name
# we will encounter the same property multiple times in case of
# custom setter methods
if prop.name in gproperties:
if gproperties[prop.name] == prop.get_pspec_args():
continue
raise ValueError('Property %s was already found in __gproperties__' % prop.name)
gproperties[prop.name] = prop.get_pspec_args()
props.append(prop)
if not props:
return
cls.__gproperties__ = gproperties
if 'do_get_property' in cls.__dict__ or 'do_set_property' in cls.__dict__:
for prop in props:
if prop.fget != prop._default_getter or prop.fset != prop._default_setter:
raise TypeError(
"GObject subclass %r defines do_get/set_property"
" and it also uses a property with a custom setter"
" or getter. This is not allowed" %
(cls.__name__,))
def obj_get_property(self, pspec):
name = pspec.name.replace('-', '_')
return getattr(self, name, None)
cls.do_get_property = obj_get_property
def obj_set_property(self, pspec, value):
name = pspec.name.replace('-', '_')
prop = getattr(cls, name, None)
if prop:
prop.fset(self, value)
cls.do_set_property = obj_set_property
@@ -0,0 +1,249 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# pygobject - Python bindings for the GObject library
# Copyright (C) 2012 Simon Feltman
#
# gi/_signalhelper.py: GObject signal binding decorator object
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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, see <http://www.gnu.org/licenses/>.
from . import _gi
class Signal(str):
"""Object which gives a nice API for creating and binding signals.
:param name:
Name of signal or callable closure when used as a decorator.
:type name: str or callable
:param callable func:
Callable closure method.
:param GObject.SignalFlags flags:
Flags specifying when to run closure.
:param type return_type:
Return type of the Signal.
:param list arg_types:
List of argument types specifying the signals function signature
:param str doc:
Documentation of signal object.
:param callable accumulator:
Accumulator method with the signature:
func(ihint, return_accu, handler_return, accu_data) -> boolean
:param object accu_data:
User data passed to the accumulator.
:Example:
.. code-block:: python
class Spam(GObject.Object):
velocity = 0
@GObject.Signal
def pushed(self):
self.velocity += 1
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
def pulled(self):
self.velocity -= 1
stomped = GObject.Signal('stomped', arg_types=(int,))
@GObject.Signal
def annotated_signal(self, a:int, b:str):
"Python3 annotation support for parameter types.
def on_pushed(obj):
print(obj)
spam = Spam()
spam.pushed.connect(on_pushed)
spam.pushed.emit()
"""
class BoundSignal(str):
"""
Temporary binding object which can be used for connecting signals
without specifying the signal name string to connect.
"""
def __new__(cls, name, *args, **kargs):
return str.__new__(cls, name)
def __init__(self, signal, gobj):
str.__init__(self)
self.signal = signal
self.gobj = gobj
def __repr__(self):
return 'BoundSignal("%s")' % self
def __call__(self, *args, **kargs):
"""Call the signals closure."""
return self.signal.func(self.gobj, *args, **kargs)
def connect(self, callback, *args, **kargs):
"""Same as GObject.Object.connect except there is no need to specify
the signal name."""
return self.gobj.connect(self, callback, *args, **kargs)
def connect_detailed(self, callback, detail, *args, **kargs):
"""Same as GObject.Object.connect except there is no need to specify
the signal name. In addition concats "::<detail>" to the signal name
when connecting; for use with notifications like "notify" when a property
changes.
"""
return self.gobj.connect(self + '::' + detail, callback, *args, **kargs)
def disconnect(self, handler_id):
"""Same as GObject.Object.disconnect."""
self.gobj.disconnect(handler_id)
def emit(self, *args, **kargs):
"""Same as GObject.Object.emit except there is no need to specify
the signal name."""
return self.gobj.emit(str(self), *args, **kargs)
def __new__(cls, name='', *args, **kargs):
if callable(name):
name = name.__name__
return str.__new__(cls, name)
def __init__(self, name='', func=None, flags=_gi.SIGNAL_RUN_FIRST,
return_type=None, arg_types=None, doc='', accumulator=None, accu_data=None):
if func is None and callable(name):
func = name
if func and not doc:
doc = func.__doc__
str.__init__(self)
if func and not (return_type or arg_types):
return_type, arg_types = get_signal_annotations(func)
if arg_types is None:
arg_types = tuple()
self.func = func
self.flags = flags
self.return_type = return_type
self.arg_types = arg_types
self.__doc__ = doc
self.accumulator = accumulator
self.accu_data = accu_data
def __get__(self, instance, owner=None):
"""Returns a BoundSignal when accessed on an object instance."""
if instance is None:
return self
return self.BoundSignal(self, instance)
def __call__(self, obj, *args, **kargs):
"""Allows for instantiated Signals to be used as a decorator or calling
of the underlying signal method."""
# If obj is a GObject, than we call this signal as a closure otherwise
# it is used as a re-application of a decorator.
if isinstance(obj, _gi.GObject):
self.func(obj, *args, **kargs)
else:
# If self is already an allocated name, use it otherwise create a new named
# signal using the closure name as the name.
if str(self):
name = str(self)
else:
name = obj.__name__
# Return a new value of this type since it is based on an immutable string.
return type(self)(name=name, func=obj, flags=self.flags,
return_type=self.return_type, arg_types=self.arg_types,
doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
def copy(self, newName=None):
"""Returns a renamed copy of the Signal."""
return type(self)(name=newName, func=self.func, flags=self.flags,
return_type=self.return_type, arg_types=self.arg_types,
doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
def get_signal_args(self):
"""Returns a tuple of: (flags, return_type, arg_types, accumulator, accu_data)"""
return (self.flags, self.return_type, self.arg_types, self.accumulator, self.accu_data)
class SignalOverride(Signal):
"""Specialized sub-class of Signal which can be used as a decorator for overriding
existing signals on GObjects.
:Example:
.. code-block:: python
class MyWidget(Gtk.Widget):
@GObject.SignalOverride
def configure_event(self):
pass
"""
def get_signal_args(self):
"""Returns the string 'override'."""
return 'override'
def get_signal_annotations(func):
"""Attempt pulling python 3 function annotations off of 'func' for
use as a signals type information. Returns an ordered nested tuple
of (return_type, (arg_type1, arg_type2, ...)). If the given function
does not have annotations then (None, tuple()) is returned.
"""
arg_types = tuple()
return_type = None
if hasattr(func, '__annotations__'):
# import inspect only when needed because it takes ~10 msec to load
import inspect
spec = inspect.getfullargspec(func)
arg_types = tuple(spec.annotations[arg] for arg in spec.args
if arg in spec.annotations)
if 'return' in spec.annotations:
return_type = spec.annotations['return']
return return_type, arg_types
def install_signals(cls):
"""Adds Signal instances on a GObject derived class into the '__gsignals__'
dictionary to be picked up and registered as real GObject signals.
"""
gsignals = cls.__dict__.get('__gsignals__', {})
newsignals = {}
for name, signal in cls.__dict__.items():
if isinstance(signal, Signal):
signalName = str(signal)
# Fixup a signal which is unnamed by using the class variable name.
# Since Signal is based on string which immutable,
# we must copy and replace the class variable.
if not signalName:
signalName = name
signal = signal.copy(name)
setattr(cls, name, signal)
if signalName in gsignals:
raise ValueError('Signal "%s" has already been registered.' % name)
newsignals[signalName] = signal
gsignals[signalName] = signal.get_signal_args()
cls.__gsignals__ = gsignals
# Setup signal closures by adding the specially named
# method to the class in the form of "do_<signal_name>".
for name, signal in newsignals.items():
if signal.func is not None:
funcName = 'do_' + name.replace('-', '_')
if not hasattr(cls, funcName):
setattr(cls, funcName, signal.func)
@@ -0,0 +1,205 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
#
# docstring.py: documentation string generator for gi.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
from ._gi import \
VFuncInfo, \
FunctionInfo, \
CallableInfo, \
ObjectInfo, \
StructInfo, \
Direction, \
TypeTag
#: Module storage for currently registered doc string generator function.
_generate_doc_string_func = None
def set_doc_string_generator(func):
"""Set doc string generator function
:param callable func:
Callable which takes a GIInfoStruct and returns documentation for it.
"""
global _generate_doc_string_func
_generate_doc_string_func = func
def get_doc_string_generator():
"""Returns the currently registered doc string generator."""
return _generate_doc_string_func
def generate_doc_string(info):
"""Generate a doc string given a GIInfoStruct.
:param gi.types.BaseInfo info:
GI info instance to generate documentation for.
:returns:
Generated documentation as a string.
:rtype: str
This passes the info struct to the currently registered doc string
generator and returns the result.
"""
return _generate_doc_string_func(info)
_type_tag_to_py_type = {TypeTag.BOOLEAN: bool,
TypeTag.INT8: int,
TypeTag.UINT8: int,
TypeTag.INT16: int,
TypeTag.UINT16: int,
TypeTag.INT32: int,
TypeTag.UINT32: int,
TypeTag.INT64: int,
TypeTag.UINT64: int,
TypeTag.FLOAT: float,
TypeTag.DOUBLE: float,
TypeTag.GLIST: list,
TypeTag.GSLIST: list,
TypeTag.ARRAY: list,
TypeTag.GHASH: dict,
TypeTag.UTF8: str,
TypeTag.FILENAME: str,
TypeTag.UNICHAR: str,
TypeTag.INTERFACE: None,
TypeTag.GTYPE: None,
TypeTag.ERROR: None,
TypeTag.VOID: None,
}
def _get_pytype_hint(gi_type):
type_tag = gi_type.get_tag()
py_type = _type_tag_to_py_type.get(type_tag, None)
if py_type and hasattr(py_type, '__name__'):
return py_type.__name__
elif type_tag == TypeTag.INTERFACE:
iface = gi_type.get_interface()
info_name = iface.get_name()
if not info_name:
return gi_type.get_tag_as_string()
return '%s.%s' % (iface.get_namespace(), info_name)
return gi_type.get_tag_as_string()
def _generate_callable_info_doc(info):
in_args_strs = []
if isinstance(info, VFuncInfo):
in_args_strs = ['self']
elif isinstance(info, FunctionInfo):
if info.is_method():
in_args_strs = ['self']
args = info.get_arguments()
hint_blacklist = ('void',)
# Build lists of indices prior to adding the docs because it is possible
# the index retrieved comes before input arguments being used.
ignore_indices = {info.get_return_type().get_array_length()}
user_data_indices = set()
for arg in args:
ignore_indices.add(arg.get_destroy())
ignore_indices.add(arg.get_type().get_array_length())
user_data_indices.add(arg.get_closure())
# Build input argument strings
for i, arg in enumerate(args):
if arg.get_direction() == Direction.OUT:
continue # skip exclusively output args
if i in ignore_indices:
continue
argstr = arg.get_name()
hint = _get_pytype_hint(arg.get_type())
if hint not in hint_blacklist:
argstr += ':' + hint
if arg.may_be_null() or i in user_data_indices:
# allow-none or user_data from a closure
argstr += '=None'
elif arg.is_optional():
argstr += '=<optional>'
in_args_strs.append(argstr)
in_args_str = ', '.join(in_args_strs)
# Build return + output argument strings
out_args_strs = []
return_hint = _get_pytype_hint(info.get_return_type())
if not info.skip_return() and return_hint and return_hint not in hint_blacklist:
argstr = return_hint
if info.may_return_null():
argstr += ' or None'
out_args_strs.append(argstr)
for i, arg in enumerate(args):
if arg.get_direction() == Direction.IN:
continue # skip exclusively input args
if i in ignore_indices:
continue
argstr = arg.get_name()
hint = _get_pytype_hint(arg.get_type())
if hint not in hint_blacklist:
argstr += ':' + hint
out_args_strs.append(argstr)
if out_args_strs:
return '%s(%s) -> %s' % (info.__name__, in_args_str, ', '.join(out_args_strs))
else:
return '%s(%s)' % (info.__name__, in_args_str)
def _generate_class_info_doc(info):
header = '\n:Constructors:\n\n::\n\n' # start with \n to avoid auto indent of other lines
doc = ''
if isinstance(info, StructInfo):
# Don't show default constructor for disguised (0 length) structs
if info.get_size() > 0:
doc += ' ' + info.get_name() + '()\n'
else:
doc += ' ' + info.get_name() + '(**properties)\n'
for method_info in info.get_methods():
if method_info.is_constructor():
doc += ' ' + _generate_callable_info_doc(method_info) + '\n'
if doc:
return header + doc
else:
return ''
def _generate_doc_dispatch(info):
if isinstance(info, (ObjectInfo, StructInfo)):
return _generate_class_info_doc(info)
elif isinstance(info, CallableInfo):
return _generate_callable_info_doc(info)
return ''
set_doc_string_generator(_generate_doc_dispatch)
@@ -0,0 +1,153 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
# 2015 Christoph Reiter
#
# importer.py: dynamic importer for introspected libraries.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import sys
import warnings
import importlib
from contextlib import contextmanager
import gi
from ._gi import Repository, RepositoryError
from ._gi import PyGIWarning
from .module import get_introspection_module
from .overrides import load_overrides
repository = Repository.get_default()
# only for backwards compatibility
modules = {}
@contextmanager
def _check_require_version(namespace, stacklevel):
"""A context manager which tries to give helpful warnings
about missing gi.require_version() which could potentially
break code if only an older version than expected is installed
or a new version gets introduced.
::
with _check_require_version("Gtk", stacklevel):
load_namespace_and_overrides()
"""
was_loaded = repository.is_registered(namespace)
yield
if was_loaded:
# it was loaded before by another import which depended on this
# namespace or by C code like libpeas
return
if namespace in ("GLib", "GObject", "Gio"):
# part of glib (we have bigger problems if versions change there)
return
if gi.get_required_version(namespace) is not None:
# the version was forced using require_version()
return
version = repository.get_version(namespace)
warnings.warn(
"%(namespace)s was imported without specifying a version first. "
"Use gi.require_version('%(namespace)s', '%(version)s') before "
"import to ensure that the right version gets loaded."
% {"namespace": namespace, "version": version},
PyGIWarning, stacklevel=stacklevel)
def get_import_stacklevel(import_hook):
"""Returns the stacklevel value for warnings.warn() for when the warning
gets emitted by an imported module, but the warning should point at the
code doing the import.
Pass import_hook=True if the warning gets generated by an import hook
(warn() gets called in load_module(), see PEP302)
"""
py_version = sys.version_info[:2]
if py_version <= (3, 2):
# 2.7 included
return 4 if import_hook else 2
elif py_version == (3, 3):
return 8 if import_hook else 10
elif py_version == (3, 4):
return 10 if import_hook else 8
else:
# fixed again in 3.5+, see https://bugs.python.org/issue24305
return 4 if import_hook else 2
class DynamicImporter(object):
# Note: see PEP302 for the Importer Protocol implemented below.
def __init__(self, path):
self.path = path
def _find_module_check(self, fullname):
if not fullname.startswith(self.path):
return False
path, namespace = fullname.rsplit('.', 1)
return path == self.path
def find_spec(self, fullname, path=None, target=None):
if self._find_module_check(fullname):
return importlib.util.spec_from_loader(fullname, self)
def find_module(self, fullname, path=None):
if self._find_module_check(fullname):
return self
def create_module(self, spec):
path, namespace = spec.name.rsplit('.', 1)
# is_registered() is faster than enumerate_versions() and
# in the common case of a namespace getting loaded before its
# dependencies, is_registered() returns True for all dependencies.
if not repository.is_registered(namespace) and not \
repository.enumerate_versions(namespace):
raise ImportError('cannot import name %s, '
'introspection typelib not found' % namespace)
stacklevel = get_import_stacklevel(import_hook=True)
with _check_require_version(namespace, stacklevel=stacklevel):
try:
introspection_module = get_introspection_module(namespace)
except RepositoryError as e:
raise ImportError(e)
# Import all dependencies first so their init functions
# (gdk_init, ..) in overrides get called.
# https://bugzilla.gnome.org/show_bug.cgi?id=656314
for dep in repository.get_immediate_dependencies(namespace):
importlib.import_module('gi.repository.' + dep.split("-")[0])
dynamic_module = load_overrides(introspection_module)
return dynamic_module
def exec_module(self, fullname):
# “exec” the module and consequently populate the module’s namespace
pass
@@ -0,0 +1,269 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2007-2009 Johan Dahlin <johan@gnome.org>
#
# module.py: dynamic module for introspected libraries.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import importlib
from threading import Lock
import gi
from ._gi import \
Repository, \
FunctionInfo, \
RegisteredTypeInfo, \
EnumInfo, \
ObjectInfo, \
InterfaceInfo, \
ConstantInfo, \
StructInfo, \
UnionInfo, \
CallbackInfo, \
Struct, \
Boxed, \
Fundamental, \
CCallback, \
enum_add, \
enum_register_new_gtype_and_add, \
flags_add, \
flags_register_new_gtype_and_add, \
GInterface
from .types import \
GObjectMeta, \
StructMeta
from ._constants import \
TYPE_NONE, \
TYPE_BOXED, \
TYPE_POINTER, \
TYPE_ENUM, \
TYPE_FLAGS
repository = Repository.get_default()
# Cache of IntrospectionModules that have been loaded.
_introspection_modules = {}
def get_parent_for_object(object_info):
parent_object_info = object_info.get_parent()
if not parent_object_info:
# If we reach the end of the introspection info class hierarchy, look
# for an existing wrapper on the GType and use it as a base for the
# new introspection wrapper. This allows static C wrappers already
# registered with the GType to be used as the introspection base
# (_gi.GObject for example)
gtype = object_info.get_g_type()
if gtype and gtype.pytype:
return gtype.pytype
if object_info.get_fundamental() and gtype.is_instantiatable():
return Fundamental
# Otherwise use builtins.object as the base
return object
namespace = parent_object_info.get_namespace()
name = parent_object_info.get_name()
module = importlib.import_module('gi.repository.' + namespace)
return getattr(module, name)
def get_interfaces_for_object(object_info):
interfaces = []
for interface_info in object_info.get_interfaces():
namespace = interface_info.get_namespace()
name = interface_info.get_name()
module = importlib.import_module('gi.repository.' + namespace)
interfaces.append(getattr(module, name))
return interfaces
class IntrospectionModule(object):
"""An object which wraps an introspection typelib.
This wrapping creates a python module like representation of the typelib
using gi repository as a foundation. Accessing attributes of the module
will dynamically pull them in and create wrappers for the members.
These members are then cached on this introspection module.
"""
def __init__(self, namespace, version=None):
"""Might raise gi._gi.RepositoryError"""
repository.require(namespace, version)
self._namespace = namespace
self._version = version
self.__name__ = 'gi.repository.' + namespace
path = repository.get_typelib_path(self._namespace)
self.__path__ = [path]
if self._version is None:
self._version = repository.get_version(self._namespace)
self._lock = Lock()
def __getattr__(self, name):
info = repository.find_by_name(self._namespace, name)
if not info:
raise AttributeError("%r object has no attribute %r" % (
self.__name__, name))
if isinstance(info, EnumInfo):
g_type = info.get_g_type()
with self._lock:
wrapper = g_type.pytype
if wrapper is None:
if info.is_flags():
if g_type.is_a(TYPE_FLAGS):
wrapper = flags_add(g_type)
else:
assert g_type == TYPE_NONE
wrapper = flags_register_new_gtype_and_add(info)
else:
if g_type.is_a(TYPE_ENUM):
wrapper = enum_add(g_type)
else:
assert g_type == TYPE_NONE
wrapper = enum_register_new_gtype_and_add(info)
wrapper.__info__ = info
wrapper.__module__ = 'gi.repository.' + info.get_namespace()
# Don't use upper() here to avoid locale specific
# identifier conversion (e. g. in Turkish 'i'.upper() == 'i')
# see https://bugzilla.gnome.org/show_bug.cgi?id=649165
ascii_upper_trans = ''.maketrans(
'abcdefgjhijklmnopqrstuvwxyz',
'ABCDEFGJHIJKLMNOPQRSTUVWXYZ')
for value_info in info.get_values():
value_name = value_info.get_name_unescaped().translate(ascii_upper_trans)
setattr(wrapper, value_name, wrapper(value_info.get_value()))
for method_info in info.get_methods():
setattr(wrapper, method_info.__name__, method_info)
if g_type != TYPE_NONE:
g_type.pytype = wrapper
elif isinstance(info, RegisteredTypeInfo):
g_type = info.get_g_type()
# Create a wrapper.
if isinstance(info, ObjectInfo):
parent = get_parent_for_object(info)
interfaces = tuple(interface for interface in get_interfaces_for_object(info)
if not issubclass(parent, interface))
bases = (parent,) + interfaces
metaclass = GObjectMeta
elif isinstance(info, CallbackInfo):
bases = (CCallback,)
metaclass = GObjectMeta
elif isinstance(info, InterfaceInfo):
bases = (GInterface,)
metaclass = GObjectMeta
elif isinstance(info, (StructInfo, UnionInfo)):
if g_type.is_a(TYPE_BOXED):
bases = (Boxed,)
elif (g_type.is_a(TYPE_POINTER) or
g_type == TYPE_NONE or
g_type.fundamental == g_type):
bases = (Struct,)
else:
raise TypeError("unable to create a wrapper for %s.%s" % (info.get_namespace(), info.get_name()))
metaclass = StructMeta
else:
raise NotImplementedError(info)
with self._lock:
# Check if there is already a Python wrapper that is not a parent class
# of the wrapper being created. If it is a parent, it is ok to clobber
# g_type.pytype with a new child class wrapper of the existing parent.
# Note that the return here never occurs under normal circumstances due
# to caching on the __dict__ itself.
if g_type != TYPE_NONE:
type_ = g_type.pytype
if type_ is not None and type_ not in bases:
self.__dict__[name] = type_
return type_
dict_ = {
'__info__': info,
'__module__': 'gi.repository.' + self._namespace,
'__gtype__': g_type
}
wrapper = metaclass(name, bases, dict_)
# Register the new Python wrapper.
if g_type != TYPE_NONE:
g_type.pytype = wrapper
elif isinstance(info, FunctionInfo):
wrapper = info
elif isinstance(info, ConstantInfo):
wrapper = info.get_value()
else:
raise NotImplementedError(info)
# Cache the newly created wrapper which will then be
# available directly on this introspection module instead of being
# lazily constructed through the __getattr__ we are currently in.
self.__dict__[name] = wrapper
return wrapper
def __repr__(self):
path = repository.get_typelib_path(self._namespace)
return "<IntrospectionModule %r from %r>" % (self._namespace, path)
def __dir__(self):
# Python's default dir() is just dir(self.__class__) + self.__dict__.keys()
result = set(dir(self.__class__))
result.update(self.__dict__.keys())
# update *set* because some repository attributes have already been
# wrapped by __getattr__() and included in self.__dict__; but skip
# Callback types, as these are not real objects which we can actually
# get
namespace_infos = repository.get_infos(self._namespace)
result.update(info.get_name() for info in namespace_infos if
not isinstance(info, CallbackInfo))
return list(result)
def get_introspection_module(namespace):
"""
:Returns:
An object directly wrapping the gi module without overrides.
Might raise gi._gi.RepositoryError
"""
if namespace in _introspection_modules:
return _introspection_modules[namespace]
version = gi.get_required_version(namespace)
module = IntrospectionModule(namespace, version)
_introspection_modules[namespace] = module
return module
@@ -0,0 +1,72 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2010 Simon van der Linden <svdlinden@src.gnome.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
from ..overrides import override
from ..module import get_introspection_module
GIMarshallingTests = get_introspection_module('GIMarshallingTests')
__all__ = []
OVERRIDES_CONSTANT = 7
__all__.append('OVERRIDES_CONSTANT')
class OverridesStruct(GIMarshallingTests.OverridesStruct):
def __new__(cls, long_):
return GIMarshallingTests.OverridesStruct.__new__(cls)
def __init__(self, long_):
GIMarshallingTests.OverridesStruct.__init__(self)
self.long_ = long_
def method(self):
return GIMarshallingTests.OverridesStruct.method(self) / 7
OverridesStruct = override(OverridesStruct)
__all__.append('OverridesStruct')
class OverridesObject(GIMarshallingTests.OverridesObject):
def __new__(cls, long_):
return GIMarshallingTests.OverridesObject.__new__(cls)
def __init__(self, long_):
GIMarshallingTests.OverridesObject.__init__(self)
# FIXME: doesn't work yet
# self.long_ = long_
@classmethod
def new(cls, long_):
self = GIMarshallingTests.OverridesObject.new()
# FIXME: doesn't work yet
# self.long_ = long_
return self
def method(self):
"""Overridden doc string."""
return GIMarshallingTests.OverridesObject.method(self) / 7
OverridesObject = override(OverridesObject)
__all__.append('OverridesObject')
@@ -0,0 +1,884 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# 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
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import warnings
import sys
import socket
from .._ossighelper import wakeup_on_signal, register_sigint_fallback
from ..module import get_introspection_module
from .._gi import (variant_type_from_string, source_new,
source_set_callback, io_channel_read)
from ..overrides import override, deprecated, deprecated_attr
from gi import PyGIDeprecationWarning, version_info
GLib = get_introspection_module('GLib')
__all__ = []
from gi import _option as option
option # pyflakes
__all__.append('option')
# Types and functions still needed from static bindings
from gi import _gi
from gi._error import GError
Error = GError
OptionContext = _gi.OptionContext
OptionGroup = _gi.OptionGroup
Pid = _gi.Pid
spawn_async = _gi.spawn_async
def threads_init():
warnings.warn('Since version 3.11, calling threads_init is no longer needed. '
'See: https://wiki.gnome.org/PyGObject/Threading',
PyGIDeprecationWarning, stacklevel=2)
def gerror_matches(self, domain, code):
# Handle cases where self.domain was set to an integer for compatibility
# with the introspected GLib.Error.
if isinstance(self.domain, str):
self_domain_quark = GLib.quark_from_string(self.domain)
else:
self_domain_quark = self.domain
return (self_domain_quark, self.code) == (domain, code)
def gerror_new_literal(domain, message, code):
domain_quark = GLib.quark_to_string(domain)
return GError(message, domain_quark, code)
# Monkey patch methods that rely on GLib introspection to be loaded at runtime.
Error.__name__ = 'Error'
Error.__module__ = 'gi.repository.GLib'
Error.__gtype__ = GLib.Error.__gtype__
Error.matches = gerror_matches
Error.new_literal = staticmethod(gerror_new_literal)
__all__ += ['GError', 'Error', 'OptionContext', 'OptionGroup', 'Pid',
'spawn_async', 'threads_init']
class _VariantCreator(object):
_LEAF_CONSTRUCTORS = {
'b': GLib.Variant.new_boolean,
'y': GLib.Variant.new_byte,
'n': GLib.Variant.new_int16,
'q': GLib.Variant.new_uint16,
'i': GLib.Variant.new_int32,
'u': GLib.Variant.new_uint32,
'x': GLib.Variant.new_int64,
't': GLib.Variant.new_uint64,
'h': GLib.Variant.new_handle,
'd': GLib.Variant.new_double,
's': GLib.Variant.new_string,
'o': GLib.Variant.new_object_path,
'g': GLib.Variant.new_signature,
'v': GLib.Variant.new_variant,
}
def _create(self, format, value):
"""Create a GVariant object from given format and a value that matches
the format.
This method recursively calls itself for complex structures (arrays,
dictionaries, boxed).
Returns the generated GVariant.
If value is None it will generate an empty GVariant container type.
"""
gvtype = GLib.VariantType(format)
if format in self._LEAF_CONSTRUCTORS:
return self._LEAF_CONSTRUCTORS[format](value)
# Since we discarded all leaf types, this must be a container
builder = GLib.VariantBuilder.new(gvtype)
if value is None:
return builder.end()
if gvtype.is_maybe():
builder.add_value(self._create(gvtype.element().dup_string(), value))
return builder.end()
try:
iter(value)
except TypeError:
raise TypeError("Could not create array, tuple or dictionary entry from non iterable value %s %s" %
(format, value))
if gvtype.is_tuple() and gvtype.n_items() != len(value):
raise TypeError("Tuple mismatches value's number of elements %s %s" % (format, value))
if gvtype.is_dict_entry() and len(value) != 2:
raise TypeError("Dictionary entries must have two elements %s %s" % (format, value))
if gvtype.is_array():
element_type = gvtype.element().dup_string()
if isinstance(value, dict):
value = value.items()
for i in value:
builder.add_value(self._create(element_type, i))
else:
remainer_format = format[1:]
for i in value:
dup = variant_type_from_string(remainer_format).dup_string()
builder.add_value(self._create(dup, i))
remainer_format = remainer_format[len(dup):]
return builder.end()
LEAF_ACCESSORS = {
'b': 'get_boolean',
'y': 'get_byte',
'n': 'get_int16',
'q': 'get_uint16',
'i': 'get_int32',
'u': 'get_uint32',
'x': 'get_int64',
't': 'get_uint64',
'h': 'get_handle',
'd': 'get_double',
's': 'get_string',
'o': 'get_string', # object path
'g': 'get_string', # signature
}
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),
'bar': GLib.Variant('i', 2)}))
"""
if not GLib.VariantType.string_is_valid(format_string):
raise TypeError("Invalid GVariant format string '%s'", format_string)
creator = _VariantCreator()
v = creator._create(format_string, value)
v.format_string = format_string
return v
@staticmethod
def new_tuple(*elements):
return GLib.Variant.new_tuple(elements)
def __del__(self):
try:
self.unref()
except ImportError:
# Calling unref will cause gi and gi.repository.GLib to be
# imported. However, if the program is exiting, then these
# modules have likely been removed from sys.modules and will
# raise an exception. Assume that's the case for ImportError
# and ignore the exception since everything will be cleaned
# up, anyways.
pass
def __str__(self):
return self.print_(True)
def __repr__(self):
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:
return self.equal(other)
except TypeError:
return False
def __ne__(self, other):
try:
return not self.equal(other)
except TypeError:
return True
def __hash__(self):
# We're not using just hash(self.unpack()) because otherwise we'll have
# hash collisions between the same content in different variant types,
# which will cause a performance issue in set/dict/etc.
return hash((self.get_type_string(), self.unpack()))
def unpack(self):
"""Decompose a GVariant into a native Python object."""
type_string = self.get_type_string()
# simple values
la = LEAF_ACCESSORS.get(type_string)
if la:
return getattr(self, la)()
# tuple
if type_string.startswith('('):
return tuple(self.get_child_value(i).unpack()
for i in range(self.n_children()))
# dictionary
if type_string.startswith('a{'):
res = {}
for i in range(self.n_children()):
v = self.get_child_value(i)
res[v.get_child_value(0).unpack()] = v.get_child_value(1).unpack()
return res
# array
if type_string.startswith('a'):
return [self.get_child_value(i).unpack()
for i in range(self.n_children())]
# variant (just unbox transparently)
if type_string.startswith('v'):
return self.get_variant().unpack()
# maybe
if type_string.startswith('m'):
if not self.n_children():
return None
return self.get_child_value(0).unpack()
raise NotImplementedError('unsupported GVariant type ' + type_string)
@classmethod
def split_signature(klass, signature):
"""Return a list of the element signatures of the topmost signature tuple.
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.
"""
if signature == '()':
return []
if not signature.startswith('('):
return [signature]
result = []
head = ''
tail = signature[1:-1] # eat the surrounding ()
while tail:
c = tail[0]
head += c
tail = tail[1:]
if c in ('m', 'a'):
# prefixes, keep collecting
continue
if c in ('(', '{'):
# consume until corresponding )/}
level = 1
up = c
if up == '(':
down = ')'
else:
down = '}'
while level > 0:
c = tail[0]
head += c
tail = tail[1:]
if c == up:
level += 1
elif c == down:
level -= 1
# otherwise we have a simple type
result.append(head)
head = ''
return result
#
# Pythonic iterators
#
def __len__(self):
if self.get_type_string() in ['s', 'o', 'g']:
return len(self.get_string())
# Array, dict, tuple
if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
return self.n_children()
raise TypeError('GVariant type %s does not have a length' % self.get_type_string())
def __getitem__(self, key):
# dict
if self.get_type_string().startswith('a{'):
try:
val = self.lookup_value(key, variant_type_from_string('*'))
if val is None:
raise KeyError(key)
return val.unpack()
except TypeError:
# lookup_value() only works for string keys, which is certainly
# the common case; we have to do painful iteration for other
# key types
for i in range(self.n_children()):
v = self.get_child_value(i)
if v.get_child_value(0).unpack() == key:
return v.get_child_value(1).unpack()
raise KeyError(key)
# array/tuple
if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
key = int(key)
if key < 0:
key = self.n_children() + key
if key < 0 or key >= self.n_children():
raise IndexError('list index out of range')
return self.get_child_value(key).unpack()
# string
if self.get_type_string() in ['s', 'o', 'g']:
return self.get_string().__getitem__(key)
raise TypeError('GVariant type %s is not a container' % self.get_type_string())
#
# Pythonic bool operations
#
def __nonzero__(self):
return self.__bool__()
def __bool__(self):
if self.get_type_string() in ['y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd']:
return self.unpack() != 0
if self.get_type_string() in ['b']:
return self.get_boolean()
if self.get_type_string() in ['s', 'o', 'g']:
return len(self.get_string()) != 0
# Array, dict, tuple
if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
return self.n_children() != 0
# unpack works recursively, hence bool also works recursively
return bool(self.unpack())
def keys(self):
if not self.get_type_string().startswith('a{'):
raise TypeError('GVariant type %s is not a dictionary' % self.get_type_string())
res = []
for i in range(self.n_children()):
v = self.get_child_value(i)
res.append(v.get_child_value(0).unpack())
return res
def get_string(self):
value, length = GLib.Variant.get_string(self)
return value
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']:
attr = 'USER_DIRECTORY_' + n
deprecated_attr("GLib", attr, "GLib.UserDirectory.DIRECTORY_" + n)
globals()[attr] = getattr(GLib.UserDirectory, 'DIRECTORY_' + n)
__all__.append(attr)
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']:
attr = 'IO_FLAG_' + n
deprecated_attr("GLib", attr, "GLib.IOFlags." + n)
globals()[attr] = getattr(GLib.IOFlags, n)
__all__.append(attr)
# spelling for the win
IO_FLAG_IS_WRITEABLE = GLib.IOFlags.IS_WRITABLE
deprecated_attr("GLib", "IO_FLAG_IS_WRITEABLE", "GLib.IOFlags.IS_WRITABLE")
__all__.append('IO_FLAG_IS_WRITEABLE')
for n in ['AGAIN', 'EOF', 'ERROR', 'NORMAL']:
attr = 'IO_STATUS_' + n
globals()[attr] = getattr(GLib.IOStatus, n)
deprecated_attr("GLib", attr, "GLib.IOStatus." + n)
__all__.append(attr)
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']:
attr = 'SPAWN_' + n
globals()[attr] = getattr(GLib.SpawnFlags, n)
deprecated_attr("GLib", attr, "GLib.SpawnFlags." + n)
__all__.append(attr)
for n in ['HIDDEN', 'IN_MAIN', 'REVERSE', 'NO_ARG', 'FILENAME', 'OPTIONAL_ARG',
'NOALIAS']:
attr = 'OPTION_FLAG_' + n
globals()[attr] = getattr(GLib.OptionFlags, n)
deprecated_attr("GLib", attr, "GLib.OptionFlags." + n)
__all__.append(attr)
for n in ['UNKNOWN_OPTION', 'BAD_VALUE', 'FAILED']:
attr = 'OPTION_ERROR_' + n
deprecated_attr("GLib", attr, "GLib.OptionError." + n)
globals()[attr] = getattr(GLib.OptionError, n)
__all__.append(attr)
# these are not currently exported in GLib gir, presumably because they are
# platform dependent; so get them from our static bindings
for name in ['G_MINFLOAT', 'G_MAXFLOAT', 'G_MINDOUBLE', 'G_MAXDOUBLE',
'G_MINSHORT', 'G_MAXSHORT', 'G_MAXUSHORT', 'G_MININT', 'G_MAXINT',
'G_MAXUINT', 'G_MINLONG', 'G_MAXLONG', 'G_MAXULONG', 'G_MAXSIZE',
'G_MINSSIZE', 'G_MAXSSIZE', 'G_MINOFFSET', 'G_MAXOFFSET']:
attr = name.split("_", 1)[-1]
globals()[attr] = getattr(_gi, name)
__all__.append(attr)
class MainLoop(GLib.MainLoop):
# Backwards compatible constructor API
def __new__(cls, context=None):
return GLib.MainLoop.new(context, False)
def __init__(self, context=None):
pass
def run(self):
with register_sigint_fallback(self.quit):
with wakeup_on_signal():
super(MainLoop, self).run()
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 pygi_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 __init__(self, *args, **kwargs):
return super(Source, self).__init__()
def __del__(self):
if hasattr(self, '__pygi_custom_source'):
# We destroy and finalize the box from here, as GLib might hold
# a reference (e.g. while the source is pending), delaying the
# finalize call until a later point.
self.destroy()
self.finalize()
self._clear_boxed()
def finalize(self):
pass
def set_callback(self, fn, user_data=None):
if hasattr(self, '__pygi_custom_source'):
# use our custom pygi_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')
# backwards compatible API
def idle_add(function, *user_data, **kwargs):
priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT_IDLE)
return GLib.idle_add(priority, function, *user_data)
__all__.append('idle_add')
def timeout_add(interval, function, *user_data, **kwargs):
priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
return GLib.timeout_add(priority, interval, function, *user_data)
__all__.append('timeout_add')
def timeout_add_seconds(interval, function, *user_data, **kwargs):
priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
return GLib.timeout_add_seconds(priority, interval, function, *user_data)
__all__.append('timeout_add_seconds')
# The GI GLib API uses g_io_add_watch_full renamed to g_io_add_watch with
# a signature of (channel, priority, condition, func, user_data).
# Prior to PyGObject 3.8, this function was statically bound with an API closer to the
# non-full version with a signature of: (fd, condition, func, *user_data)
# We need to support this until we are okay with breaking API in a way which is
# not backwards compatible.
#
# 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
def _io_add_watch_get_args(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:]
# backwards compatibility: Allow calling with fd
if isinstance(channel, int):
func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
real_channel = GLib.IOChannel.unix_new(channel)
elif isinstance(channel, socket.socket) and sys.platform == 'win32':
func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
real_channel = GLib.IOChannel.win32_new_socket(channel.fileno())
elif hasattr(channel, 'fileno'):
# backwards compatibility: Allow calling with Python file
func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
real_channel = GLib.IOChannel.unix_new(channel.fileno())
else:
assert isinstance(channel, GLib.IOChannel)
func_fdtransform = callback
real_channel = channel
return real_channel, priority_, condition, func_fdtransform, user_data
__all__.append('_io_add_watch_get_args')
def io_add_watch(*args, **kwargs):
"""io_add_watch(channel, priority, condition, func, *user_data) -> event_source_id"""
channel, priority, condition, func, user_data = _io_add_watch_get_args(*args, **kwargs)
return GLib.io_add_watch(channel, priority, condition, func, *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 __init__(self, *args, **kwargs):
return super(IOChannel, self).__init__()
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 GI GLib API uses g_child_watch_add_full renamed to g_child_watch_add with
# a signature of (priority, pid, callback, data).
# Prior to PyGObject 3.8, this function was statically bound with an API closer to the
# non-full version with a signature of: (pid, callback, data=None, priority=GLib.PRIORITY_DEFAULT)
# We need to support this until we are okay with breaking API in a way which is
# not backwards compatible.
def _child_watch_add_get_args(priority_or_pid, pid_or_callback, *args, **kwargs):
user_data = []
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:
priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
elif len(args) == 1:
user_data = args
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 'function' in kwargs:
callback = kwargs['function']
user_data = args
elif len(args) > 0 and callable(args[0]):
callback = args[0]
user_data = args[1:]
else:
raise TypeError('expected callback as third argument')
if 'data' in kwargs:
if user_data:
raise TypeError('got multiple values for "data" argument')
user_data = (kwargs['data'],)
return priority, pid, callback, user_data
# we need this to be accessible for unit testing
__all__.append('_child_watch_add_get_args')
def child_watch_add(*args, **kwargs):
"""child_watch_add(priority, pid, function, *data)"""
priority, pid, function, data = _child_watch_add_get_args(*args, **kwargs)
return GLib.child_watch_add(priority, pid, function, *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')
if hasattr(GLib, "unix_signal_add"):
unix_signal_add_full = GLib.unix_signal_add
__all__.append('unix_signal_add_full')
deprecated_attr("GLib", "unix_signal_add_full", "GLib.unix_signal_add")
# obsolete constants for backwards compatibility
glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)
__all__.append('glib_version')
deprecated_attr("GLib", "glib_version",
"(GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)")
pyglib_version = version_info
__all__.append('pyglib_version')
deprecated_attr("GLib", "pyglib_version", "gi.version_info")
@@ -0,0 +1,669 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2012 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com>
# Copyright (C) 2012-2013 Simon Feltman <sfeltman@src.gnome.org>
# Copyright (C) 2012 Bastian Winkler <buz@netbuz.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import functools
import warnings
from collections import namedtuple
import gi.module
from gi.overrides import override, deprecated_attr
from gi.repository import GLib
from gi import PyGIDeprecationWarning
from gi import _propertyhelper as propertyhelper
from gi import _signalhelper as signalhelper
from gi import _gi
GObjectModule = gi.module.get_introspection_module('GObject')
__all__ = []
from gi import _option as option
option = option
# API aliases for backwards compatibility
for name in ['markup_escape_text', 'get_application_name',
'set_application_name', 'get_prgname', 'set_prgname',
'main_depth', 'filename_display_basename',
'filename_display_name', 'filename_from_utf8',
'uri_list_extract_uris',
'MainLoop', 'MainContext', 'main_context_default',
'source_remove', 'Source', 'Idle', 'Timeout', 'PollFD',
'idle_add', 'timeout_add', 'timeout_add_seconds',
'io_add_watch', 'child_watch_add', 'get_current_time',
'spawn_async']:
globals()[name] = getattr(GLib, name)
deprecated_attr("GObject", name, "GLib." + name)
__all__.append(name)
# deprecated constants
for name in ['PRIORITY_DEFAULT', 'PRIORITY_DEFAULT_IDLE', 'PRIORITY_HIGH',
'PRIORITY_HIGH_IDLE', 'PRIORITY_LOW',
'IO_IN', 'IO_OUT', 'IO_PRI', 'IO_ERR', 'IO_HUP', 'IO_NVAL',
'IO_STATUS_ERROR', 'IO_STATUS_NORMAL', 'IO_STATUS_EOF',
'IO_STATUS_AGAIN', 'IO_FLAG_APPEND', 'IO_FLAG_NONBLOCK',
'IO_FLAG_IS_READABLE', 'IO_FLAG_IS_WRITEABLE',
'IO_FLAG_IS_SEEKABLE', 'IO_FLAG_MASK', 'IO_FLAG_GET_MASK',
'IO_FLAG_SET_MASK',
'SPAWN_LEAVE_DESCRIPTORS_OPEN', 'SPAWN_DO_NOT_REAP_CHILD',
'SPAWN_SEARCH_PATH', 'SPAWN_STDOUT_TO_DEV_NULL',
'SPAWN_STDERR_TO_DEV_NULL', 'SPAWN_CHILD_INHERITS_STDIN',
'SPAWN_FILE_AND_ARGV_ZERO',
'OPTION_FLAG_HIDDEN', 'OPTION_FLAG_IN_MAIN', 'OPTION_FLAG_REVERSE',
'OPTION_FLAG_NO_ARG', 'OPTION_FLAG_FILENAME', 'OPTION_FLAG_OPTIONAL_ARG',
'OPTION_FLAG_NOALIAS', 'OPTION_ERROR_UNKNOWN_OPTION',
'OPTION_ERROR_BAD_VALUE', 'OPTION_ERROR_FAILED', 'OPTION_REMAINING',
'glib_version']:
with warnings.catch_warnings():
# TODO: this uses deprecated Glib attributes, silence for now
warnings.simplefilter('ignore', PyGIDeprecationWarning)
globals()[name] = getattr(GLib, name)
deprecated_attr("GObject", name, "GLib." + name)
__all__.append(name)
for name in ['G_MININT8', 'G_MAXINT8', 'G_MAXUINT8', 'G_MININT16',
'G_MAXINT16', 'G_MAXUINT16', 'G_MININT32', 'G_MAXINT32',
'G_MAXUINT32', 'G_MININT64', 'G_MAXINT64', 'G_MAXUINT64']:
new_name = name.split("_", 1)[-1]
globals()[name] = getattr(GLib, new_name)
deprecated_attr("GObject", name, "GLib." + new_name)
__all__.append(name)
# these are not currently exported in GLib gir, presumably because they are
# platform dependent; so get them from our static bindings
for name in ['G_MINFLOAT', 'G_MAXFLOAT', 'G_MINDOUBLE', 'G_MAXDOUBLE',
'G_MINSHORT', 'G_MAXSHORT', 'G_MAXUSHORT', 'G_MININT', 'G_MAXINT',
'G_MAXUINT', 'G_MINLONG', 'G_MAXLONG', 'G_MAXULONG', 'G_MAXSIZE',
'G_MINSSIZE', 'G_MAXSSIZE', 'G_MINOFFSET', 'G_MAXOFFSET']:
new_name = name.split("_", 1)[-1]
globals()[name] = getattr(GLib, new_name)
deprecated_attr("GObject", name, "GLib." + new_name)
__all__.append(name)
TYPE_INVALID = GObjectModule.type_from_name('invalid')
TYPE_NONE = GObjectModule.type_from_name('void')
TYPE_INTERFACE = GObjectModule.type_from_name('GInterface')
TYPE_CHAR = GObjectModule.type_from_name('gchar')
TYPE_UCHAR = GObjectModule.type_from_name('guchar')
TYPE_BOOLEAN = GObjectModule.type_from_name('gboolean')
TYPE_INT = GObjectModule.type_from_name('gint')
TYPE_UINT = GObjectModule.type_from_name('guint')
TYPE_LONG = GObjectModule.type_from_name('glong')
TYPE_ULONG = GObjectModule.type_from_name('gulong')
TYPE_INT64 = GObjectModule.type_from_name('gint64')
TYPE_UINT64 = GObjectModule.type_from_name('guint64')
TYPE_ENUM = GObjectModule.type_from_name('GEnum')
TYPE_FLAGS = GObjectModule.type_from_name('GFlags')
TYPE_FLOAT = GObjectModule.type_from_name('gfloat')
TYPE_DOUBLE = GObjectModule.type_from_name('gdouble')
TYPE_STRING = GObjectModule.type_from_name('gchararray')
TYPE_POINTER = GObjectModule.type_from_name('gpointer')
TYPE_BOXED = GObjectModule.type_from_name('GBoxed')
TYPE_PARAM = GObjectModule.type_from_name('GParam')
TYPE_OBJECT = GObjectModule.type_from_name('GObject')
TYPE_PYOBJECT = GObjectModule.type_from_name('PyObject')
TYPE_GTYPE = GObjectModule.type_from_name('GType')
TYPE_STRV = GObjectModule.type_from_name('GStrv')
TYPE_VARIANT = GObjectModule.type_from_name('GVariant')
TYPE_GSTRING = GObjectModule.type_from_name('GString')
TYPE_VALUE = GObjectModule.Value.__gtype__
TYPE_UNICHAR = TYPE_UINT
__all__ += ['TYPE_INVALID', 'TYPE_NONE', 'TYPE_INTERFACE', 'TYPE_CHAR',
'TYPE_UCHAR', 'TYPE_BOOLEAN', 'TYPE_INT', 'TYPE_UINT', 'TYPE_LONG',
'TYPE_ULONG', 'TYPE_INT64', 'TYPE_UINT64', 'TYPE_ENUM', 'TYPE_FLAGS',
'TYPE_FLOAT', 'TYPE_DOUBLE', 'TYPE_STRING', 'TYPE_POINTER',
'TYPE_BOXED', 'TYPE_PARAM', 'TYPE_OBJECT', 'TYPE_PYOBJECT',
'TYPE_GTYPE', 'TYPE_STRV', 'TYPE_VARIANT', 'TYPE_GSTRING',
'TYPE_UNICHAR', 'TYPE_VALUE']
# Deprecated, use GLib directly
for name in ['Pid', 'GError', 'OptionGroup', 'OptionContext']:
globals()[name] = getattr(GLib, name)
deprecated_attr("GObject", name, "GLib." + name)
__all__.append(name)
# Deprecated, use: GObject.ParamFlags.* directly
for name in ['PARAM_CONSTRUCT', 'PARAM_CONSTRUCT_ONLY', 'PARAM_LAX_VALIDATION',
'PARAM_READABLE', 'PARAM_WRITABLE']:
new_name = name.split("_", 1)[-1]
globals()[name] = getattr(GObjectModule.ParamFlags, new_name)
deprecated_attr("GObject", name, "GObject.ParamFlags." + new_name)
__all__.append(name)
# PARAM_READWRITE should come from the gi module but cannot due to:
# https://gitlab.gnome.org/GNOME/gobject-introspection/issues/75
PARAM_READWRITE = GObjectModule.ParamFlags.READABLE | \
GObjectModule.ParamFlags.WRITABLE
deprecated_attr("GObject", "PARAM_READWRITE", "GObject.ParamFlags.READWRITE")
__all__.append("PARAM_READWRITE")
# Deprecated, use: GObject.SignalFlags.* directly
for name in ['SIGNAL_ACTION', 'SIGNAL_DETAILED', 'SIGNAL_NO_HOOKS',
'SIGNAL_NO_RECURSE', 'SIGNAL_RUN_CLEANUP', 'SIGNAL_RUN_FIRST',
'SIGNAL_RUN_LAST']:
new_name = name.split("_", 1)[-1]
globals()[name] = getattr(GObjectModule.SignalFlags, new_name)
deprecated_attr("GObject", name, "GObject.SignalFlags." + new_name)
__all__.append(name)
# Static types
GBoxed = _gi.GBoxed
GEnum = _gi.GEnum
GFlags = _gi.GFlags
GInterface = _gi.GInterface
GObject = _gi.GObject
GObjectWeakRef = _gi.GObjectWeakRef
GParamSpec = _gi.GParamSpec
GPointer = _gi.GPointer
GType = _gi.GType
Warning = _gi.Warning
__all__ += ['GBoxed', 'GEnum', 'GFlags', 'GInterface', 'GObject',
'GObjectWeakRef', 'GParamSpec', 'GPointer', 'GType',
'Warning']
features = {'generic-c-marshaller': True}
list_properties = _gi.list_properties
new = _gi.new
pygobject_version = _gi.pygobject_version
threads_init = GLib.threads_init
type_register = _gi.type_register
__all__ += ['features', 'list_properties', 'new',
'pygobject_version', 'threads_init', 'type_register']
class Value(GObjectModule.Value):
def __init__(self, value_type=None, py_value=None):
GObjectModule.Value.__init__(self)
if value_type is not None:
self.init(value_type)
if py_value is not None:
self.set_value(py_value)
@property
def __g_type(self):
# XXX: This is the same as self.g_type, but the field marshalling
# code is currently very slow.
return _gi._gvalue_get_type(self)
def set_boxed(self, boxed):
if not self.__g_type.is_a(TYPE_BOXED):
warnings.warn('Calling set_boxed() on a non-boxed type deprecated',
PyGIDeprecationWarning, stacklevel=2)
# Workaround the introspection marshalers inability to know
# these methods should be marshaling boxed types. This is because
# the type information is stored on the GValue.
_gi._gvalue_set(self, boxed)
def get_boxed(self):
if not self.__g_type.is_a(TYPE_BOXED):
warnings.warn('Calling get_boxed() on a non-boxed type deprecated',
PyGIDeprecationWarning, stacklevel=2)
return _gi._gvalue_get(self)
def set_value(self, py_value):
gtype = self.__g_type
if gtype == TYPE_CHAR:
self.set_char(py_value)
elif gtype == TYPE_UCHAR:
self.set_uchar(py_value)
elif gtype == TYPE_STRING:
if not isinstance(py_value, str) and py_value is not None:
raise TypeError("Expected string but got %s%s" %
(py_value, type(py_value)))
_gi._gvalue_set(self, py_value)
elif gtype == TYPE_PARAM:
self.set_param(py_value)
elif gtype.is_a(TYPE_FLAGS):
self.set_flags(py_value)
elif gtype == TYPE_POINTER:
self.set_pointer(py_value)
elif gtype == TYPE_GTYPE:
self.set_gtype(py_value)
elif gtype == TYPE_VARIANT:
self.set_variant(py_value)
else:
# Fall back to _gvalue_set which handles some more cases
# like fundamentals for which a converter is registered
try:
_gi._gvalue_set(self, py_value)
except TypeError:
if gtype == TYPE_INVALID:
raise TypeError("GObject.Value needs to be initialized first")
raise
def get_value(self):
gtype = self.__g_type
if gtype == TYPE_CHAR:
return self.get_char()
elif gtype == TYPE_UCHAR:
return self.get_uchar()
elif gtype == TYPE_PARAM:
return self.get_param()
elif gtype.is_a(TYPE_ENUM):
return self.get_enum()
elif gtype.is_a(TYPE_FLAGS):
return self.get_flags()
elif gtype == TYPE_POINTER:
return self.get_pointer()
elif gtype == TYPE_GTYPE:
return self.get_gtype()
elif gtype == TYPE_VARIANT:
# get_variant was missing annotations
# https://gitlab.gnome.org/GNOME/glib/merge_requests/492
return self.dup_variant()
else:
try:
return _gi._gvalue_get(self)
except TypeError:
if gtype == TYPE_INVALID:
return None
raise
def __repr__(self):
return '<Value (%s) %s>' % (self.__g_type.name, self.get_value())
Value = override(Value)
__all__.append('Value')
def type_from_name(name):
type_ = GObjectModule.type_from_name(name)
if type_ == TYPE_INVALID:
raise RuntimeError('unknown type name: %s' % name)
return type_
__all__.append('type_from_name')
def type_parent(type_):
parent = GObjectModule.type_parent(type_)
if parent == TYPE_INVALID:
raise RuntimeError('no parent for type')
return parent
__all__.append('type_parent')
def _validate_type_for_signal_method(type_):
if hasattr(type_, '__gtype__'):
type_ = type_.__gtype__
if not type_.is_instantiatable() and not type_.is_interface():
raise TypeError('type must be instantiable or an interface, got %s' % type_)
def signal_list_ids(type_):
_validate_type_for_signal_method(type_)
return GObjectModule.signal_list_ids(type_)
__all__.append('signal_list_ids')
def signal_list_names(type_):
ids = signal_list_ids(type_)
return tuple(GObjectModule.signal_name(i) for i in ids)
__all__.append('signal_list_names')
def signal_lookup(name, type_):
_validate_type_for_signal_method(type_)
return GObjectModule.signal_lookup(name, type_)
__all__.append('signal_lookup')
SignalQuery = namedtuple('SignalQuery',
['signal_id',
'signal_name',
'itype',
'signal_flags',
'return_type',
# n_params',
'param_types'])
def signal_query(id_or_name, type_=None):
if type_ is not None:
id_or_name = signal_lookup(id_or_name, type_)
res = GObjectModule.signal_query(id_or_name)
assert res is not None
if res.signal_id == 0:
return None
# Return a named tuple to allows indexing which is compatible with the
# static bindings along with field like access of the gi struct.
# Note however that the n_params was not returned from the static bindings
# so we must skip over it.
return SignalQuery(res.signal_id, res.signal_name, res.itype,
res.signal_flags, res.return_type,
tuple(res.param_types))
__all__.append('signal_query')
class _HandlerBlockManager(object):
def __init__(self, obj, handler_id):
self.obj = obj
self.handler_id = handler_id
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
GObjectModule.signal_handler_unblock(self.obj, self.handler_id)
def signal_handler_block(obj, handler_id):
"""Blocks the signal handler from being invoked until
handler_unblock() is called.
:param GObject.Object obj:
Object instance to block handlers for.
:param int handler_id:
Id of signal to block.
:returns:
A context manager which optionally can be used to
automatically unblock the handler:
.. code-block:: python
with GObject.signal_handler_block(obj, id):
pass
"""
GObjectModule.signal_handler_block(obj, handler_id)
return _HandlerBlockManager(obj, handler_id)
__all__.append('signal_handler_block')
def signal_parse_name(detailed_signal, itype, force_detail_quark):
"""Parse a detailed signal name into (signal_id, detail).
:param str detailed_signal:
Signal name which can include detail.
For example: "notify:prop_name"
:returns:
Tuple of (signal_id, detail)
:raises ValueError:
If the given signal is unknown.
"""
res, signal_id, detail = GObjectModule.signal_parse_name(detailed_signal, itype,
force_detail_quark)
if res:
return signal_id, detail
else:
raise ValueError('%s: unknown signal name: %s' % (itype, detailed_signal))
__all__.append('signal_parse_name')
def remove_emission_hook(obj, detailed_signal, hook_id):
signal_id, detail = signal_parse_name(detailed_signal, obj, True)
GObjectModule.signal_remove_emission_hook(signal_id, hook_id)
__all__.append('remove_emission_hook')
# GObject accumulators with pure Python implementations
# These return a tuple of (continue_emission, accumulation_result)
def signal_accumulator_first_wins(ihint, return_accu, handler_return, user_data=None):
# Stop emission but return the result of the last handler
return (False, handler_return)
__all__.append('signal_accumulator_first_wins')
def signal_accumulator_true_handled(ihint, return_accu, handler_return, user_data=None):
# Stop emission if the last handler returns True
return (not handler_return, handler_return)
__all__.append('signal_accumulator_true_handled')
# Statically bound signal functions which need to clobber GI (for now)
add_emission_hook = _gi.add_emission_hook
signal_new = _gi.signal_new
__all__ += ['add_emission_hook', 'signal_new']
class _FreezeNotifyManager(object):
def __init__(self, obj):
self.obj = obj
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
self.obj.thaw_notify()
def _signalmethod(func):
# Function wrapper for signal functions used as instance methods.
# This is needed when the signal functions come directly from GI.
# (they are not already wrapped)
@functools.wraps(func)
def meth(*args, **kwargs):
return func(*args, **kwargs)
return meth
class Object(GObjectModule.Object):
def _unsupported_method(self, *args, **kargs):
raise RuntimeError('This method is currently unsupported.')
def _unsupported_data_method(self, *args, **kargs):
raise RuntimeError('Data access methods are unsupported. '
'Use normal Python attributes instead')
# Generic data methods are not needed in python as it can be handled
# with standard attribute access: https://bugzilla.gnome.org/show_bug.cgi?id=641944
get_data = _unsupported_data_method
get_qdata = _unsupported_data_method
set_data = _unsupported_data_method
steal_data = _unsupported_data_method
steal_qdata = _unsupported_data_method
replace_data = _unsupported_data_method
replace_qdata = _unsupported_data_method
# The following methods as unsupported until we verify
# they work as gi methods.
bind_property_full = _unsupported_method
compat_control = _unsupported_method
interface_find_property = _unsupported_method
interface_install_property = _unsupported_method
interface_list_properties = _unsupported_method
notify_by_pspec = _unsupported_method
watch_closure = _unsupported_method
# Make all reference management methods private but still accessible.
_ref = GObjectModule.Object.ref
_ref_sink = GObjectModule.Object.ref_sink
_unref = GObjectModule.Object.unref
_force_floating = GObjectModule.Object.force_floating
ref = _unsupported_method
ref_sink = _unsupported_method
unref = _unsupported_method
force_floating = _unsupported_method
# The following methods are static APIs which need to leap frog the
# gi methods until we verify the gi methods can replace them.
get_property = _gi.GObject.get_property
get_properties = _gi.GObject.get_properties
set_property = _gi.GObject.set_property
set_properties = _gi.GObject.set_properties
bind_property = _gi.GObject.bind_property
connect = _gi.GObject.connect
connect_after = _gi.GObject.connect_after
connect_object = _gi.GObject.connect_object
connect_object_after = _gi.GObject.connect_object_after
disconnect_by_func = _gi.GObject.disconnect_by_func
handler_block_by_func = _gi.GObject.handler_block_by_func
handler_unblock_by_func = _gi.GObject.handler_unblock_by_func
emit = _gi.GObject.emit
chain = _gi.GObject.chain
weak_ref = _gi.GObject.weak_ref
__copy__ = _gi.GObject.__copy__
__deepcopy__ = _gi.GObject.__deepcopy__
def freeze_notify(self):
"""Freezes the object's property-changed notification queue.
:returns:
A context manager which optionally can be used to
automatically thaw notifications.
This will freeze the object so that "notify" signals are blocked until
the thaw_notify() method is called.
.. code-block:: python
with obj.freeze_notify():
pass
"""
super(Object, self).freeze_notify()
return _FreezeNotifyManager(self)
def connect_data(self, detailed_signal, handler, *data, **kwargs):
"""Connect a callback to the given signal with optional user data.
:param str detailed_signal:
A detailed signal to connect to.
:param callable handler:
Callback handler to connect to the signal.
:param *data:
Variable data which is passed through to the signal handler.
:param GObject.ConnectFlags connect_flags:
Flags used for connection options.
:returns:
A signal id which can be used with disconnect.
"""
flags = kwargs.get('connect_flags', 0)
if flags & GObjectModule.ConnectFlags.AFTER:
connect_func = _gi.GObject.connect_after
else:
connect_func = _gi.GObject.connect
if flags & GObjectModule.ConnectFlags.SWAPPED:
if len(data) != 1:
raise ValueError('Using GObject.ConnectFlags.SWAPPED requires exactly '
'one argument for user data, got: %s' % [data])
def new_handler(obj, *args):
# Swap obj with the last element in args which will be the user
# data passed to the connect function.
args = list(args)
swap = args.pop()
args = args + [obj]
return handler(swap, *args)
else:
new_handler = handler
return connect_func(self, detailed_signal, new_handler, *data)
#
# Aliases
#
handler_block = signal_handler_block
handler_unblock = _signalmethod(GObjectModule.signal_handler_unblock)
disconnect = _signalmethod(GObjectModule.signal_handler_disconnect)
handler_disconnect = _signalmethod(GObjectModule.signal_handler_disconnect)
handler_is_connected = _signalmethod(GObjectModule.signal_handler_is_connected)
stop_emission_by_name = _signalmethod(GObjectModule.signal_stop_emission_by_name)
#
# Deprecated Methods
#
def stop_emission(self, detailed_signal):
"""Deprecated, please use stop_emission_by_name."""
warnings.warn(self.stop_emission.__doc__, PyGIDeprecationWarning, stacklevel=2)
return self.stop_emission_by_name(detailed_signal)
emit_stop_by_name = stop_emission
Object = override(Object)
GObject = Object
__all__ += ['Object', 'GObject']
class Binding(GObjectModule.Binding):
def __call__(self):
warnings.warn('Using parentheses (binding()) to retrieve the Binding object is no '
'longer needed because the binding is returned directly from "bind_property.',
PyGIDeprecationWarning, stacklevel=2)
return self
def unbind(self):
# Fixed in newer glib
if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) >= (2, 57, 3):
return super(Binding, self).unbind()
if hasattr(self, '_unbound'):
raise ValueError('binding has already been cleared out')
else:
setattr(self, '_unbound', True)
super(Binding, self).unbind()
Binding = override(Binding)
__all__.append('Binding')
Property = propertyhelper.Property
Signal = signalhelper.Signal
SignalOverride = signalhelper.SignalOverride
# Deprecated naming "property" available for backwards compatibility.
# Keep this at the end of the file to avoid clobbering the builtin.
property = Property
deprecated_attr("GObject", "property", "GObject.Property")
__all__ += ['Property', 'Signal', 'SignalOverride', 'property']
@@ -0,0 +1,484 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2009 Johan Dahlin <johan@gnome.org>
# 2010 Simon van der Linden <svdlinden@src.gnome.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import sys
import warnings
from ..overrides import override, strip_boolean_result
from ..module import get_introspection_module
from gi import PyGIDeprecationWarning, require_version
Gdk = get_introspection_module('Gdk')
GDK2 = Gdk._version == '2.0'
GDK3 = Gdk._version == '3.0'
GDK4 = Gdk._version == '4.0'
__all__ = []
# https://bugzilla.gnome.org/show_bug.cgi?id=673396
try:
require_version("GdkX11", Gdk._version)
from gi.repository import GdkX11
GdkX11 # pyflakes
except (ValueError, ImportError):
pass
if GDK2 or GDK3:
# Gdk.Color was deprecated since 3.14 and dropped in Gtk-4.0
class Color(Gdk.Color):
MAX_VALUE = 65535
def __init__(self, red, green, blue):
Gdk.Color.__init__(self)
self.red = red
self.green = green
self.blue = blue
def __eq__(self, other):
if not isinstance(other, Gdk.Color):
return False
return self.equal(other)
# This is required (even when __eq__ is defined) in order
# for != operator to work as expected
def __ne__(self, other):
return not self == other
def __repr__(self):
return 'Gdk.Color(red=%d, green=%d, blue=%d)' % (self.red, self.green, self.blue)
red_float = property(fget=lambda self: self.red / float(self.MAX_VALUE),
fset=lambda self, v: setattr(self, 'red', int(v * self.MAX_VALUE)))
green_float = property(fget=lambda self: self.green / float(self.MAX_VALUE),
fset=lambda self, v: setattr(self, 'green', int(v * self.MAX_VALUE)))
blue_float = property(fget=lambda self: self.blue / float(self.MAX_VALUE),
fset=lambda self, v: setattr(self, 'blue', int(v * self.MAX_VALUE)))
def to_floats(self):
"""Return (red_float, green_float, blue_float) triple."""
return (self.red_float, self.green_float, self.blue_float)
@staticmethod
def from_floats(red, green, blue):
"""Return a new Color object from red/green/blue values from 0.0 to 1.0."""
return Color(int(red * Color.MAX_VALUE),
int(green * Color.MAX_VALUE),
int(blue * Color.MAX_VALUE))
Color = override(Color)
__all__.append('Color')
if GDK3:
# Introduced since Gtk-3.0
class RGBA(Gdk.RGBA):
def __init__(self, red=1.0, green=1.0, blue=1.0, alpha=1.0):
Gdk.RGBA.__init__(self)
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
def __eq__(self, other):
if not isinstance(other, Gdk.RGBA):
return False
return self.equal(other)
# This is required (even when __eq__ is defined) in order
# for != operator to work as expected
def __ne__(self, other):
return not self == other
def __repr__(self):
return 'Gdk.RGBA(red=%f, green=%f, blue=%f, alpha=%f)' % (self.red, self.green, self.blue, self.alpha)
def __iter__(self):
"""Iterator which allows easy conversion to tuple and list types."""
yield self.red
yield self.green
yield self.blue
yield self.alpha
def to_color(self):
"""Converts this RGBA into a Color instance which excludes alpha."""
return Color(int(self.red * Color.MAX_VALUE),
int(self.green * Color.MAX_VALUE),
int(self.blue * Color.MAX_VALUE))
@classmethod
def from_color(cls, color):
"""Returns a new RGBA instance given a Color instance."""
return cls(color.red_float, color.green_float, color.blue_float)
RGBA = override(RGBA)
__all__.append('RGBA')
if GDK2:
class Rectangle(Gdk.Rectangle):
def __init__(self, x, y, width, height):
Gdk.Rectangle.__init__(self)
self.x = x
self.y = y
self.width = width
self.height = height
def __repr__(self):
return 'Gdk.Rectangle(x=%d, y=%d, width=%d, height=%d)' % (self.x, self.y, self.height, self.width)
Rectangle = override(Rectangle)
__all__.append('Rectangle')
elif GDK3:
# Newer GTK/gobject-introspection (3.17.x) include GdkRectangle in the
# typelib. See https://bugzilla.gnome.org/show_bug.cgi?id=748832 and
# https://bugzilla.gnome.org/show_bug.cgi?id=748833
if not hasattr(Gdk, 'Rectangle'):
from gi.repository import cairo as _cairo
Rectangle = _cairo.RectangleInt
__all__.append('Rectangle')
else:
# https://bugzilla.gnome.org/show_bug.cgi?id=756364
# These methods used to be functions, keep aliases for backwards compat
rectangle_intersect = Gdk.Rectangle.intersect
rectangle_union = Gdk.Rectangle.union
__all__.append('rectangle_intersect')
__all__.append('rectangle_union')
if GDK2:
class Drawable(Gdk.Drawable):
def cairo_create(self):
return Gdk.cairo_create(self)
Drawable = override(Drawable)
__all__.append('Drawable')
elif GDK3:
class Window(Gdk.Window):
def __new__(cls, parent, attributes, attributes_mask):
# Gdk.Window had to be made abstract,
# this override allows using the standard constructor
return Gdk.Window.new(parent, attributes, attributes_mask)
def __init__(self, parent, attributes, attributes_mask):
pass
def cairo_create(self):
return Gdk.cairo_create(self)
Window = override(Window)
__all__.append('Window')
if GDK2 or GDK3:
Gdk.EventType._2BUTTON_PRESS = getattr(Gdk.EventType, "2BUTTON_PRESS")
Gdk.EventType._3BUTTON_PRESS = getattr(Gdk.EventType, "3BUTTON_PRESS")
class Event(Gdk.Event):
_UNION_MEMBERS = {
Gdk.EventType.DELETE: 'any',
Gdk.EventType.DESTROY: 'any',
Gdk.EventType.MOTION_NOTIFY: 'motion',
Gdk.EventType.BUTTON_PRESS: 'button',
Gdk.EventType.BUTTON_RELEASE: 'button',
Gdk.EventType.KEY_PRESS: 'key',
Gdk.EventType.KEY_RELEASE: 'key',
Gdk.EventType.ENTER_NOTIFY: 'crossing',
Gdk.EventType.LEAVE_NOTIFY: 'crossing',
Gdk.EventType.FOCUS_CHANGE: 'focus_change',
Gdk.EventType.CONFIGURE: 'configure',
Gdk.EventType.PROXIMITY_IN: 'proximity',
Gdk.EventType.PROXIMITY_OUT: 'proximity',
Gdk.EventType.DRAG_ENTER: 'dnd',
Gdk.EventType.DRAG_LEAVE: 'dnd',
Gdk.EventType.DRAG_MOTION: 'dnd',
Gdk.EventType.DROP_START: 'dnd',
Gdk.EventType._2BUTTON_PRESS: 'button',
Gdk.EventType._3BUTTON_PRESS: 'button',
Gdk.EventType.PROPERTY_NOTIFY: 'property',
Gdk.EventType.SELECTION_CLEAR: 'selection',
Gdk.EventType.SELECTION_REQUEST: 'selection',
Gdk.EventType.SELECTION_NOTIFY: 'selection',
Gdk.EventType.DRAG_STATUS: 'dnd',
Gdk.EventType.DROP_FINISHED: 'dnd',
Gdk.EventType.CLIENT_EVENT: 'client',
Gdk.EventType.VISIBILITY_NOTIFY: 'visibility',
Gdk.EventType.SCROLL: 'scroll',
Gdk.EventType.EXPOSE: 'expose',
Gdk.EventType.MAP: 'any',
Gdk.EventType.UNMAP: 'any',
}
if GDK2:
_UNION_MEMBERS[Gdk.EventType.NO_EXPOSE] = 'no_expose'
if hasattr(Gdk.EventType, 'TOUCH_BEGIN'):
_UNION_MEMBERS.update(
{
Gdk.EventType.TOUCH_BEGIN: 'touch',
Gdk.EventType.TOUCH_UPDATE: 'touch',
Gdk.EventType.TOUCH_END: 'touch',
Gdk.EventType.TOUCH_CANCEL: 'touch',
})
def __getattr__(self, name):
real_event = getattr(self, '_UNION_MEMBERS').get(self.type)
if real_event:
return getattr(getattr(self, real_event), name)
else:
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
def __setattr__(self, name, value):
real_event = getattr(self, '_UNION_MEMBERS').get(self.type)
if real_event:
setattr(getattr(self, real_event), name, value)
else:
Gdk.Event.__setattr__(self, name, value)
def __repr__(self):
base_repr = Gdk.Event.__repr__(self).strip("><")
return "<%s type=%r>" % (base_repr, self.type)
Event = override(Event)
__all__.append('Event')
# manually bind GdkEvent members to GdkEvent
modname = globals()['__name__']
module = sys.modules[modname]
# right now we can't get the type_info from the
# field info so manually list the class names
event_member_classes = ['EventAny',
'EventExpose',
'EventMotion',
'EventButton',
'EventScroll',
'EventKey',
'EventCrossing',
'EventFocus',
'EventConfigure',
'EventProximity',
'EventDND',
'EventSetting',
'EventGrabBroken',
'EventVisibility',
'EventProperty',
'EventSelection',
'EventOwnerChange',
'EventWindowState',
'EventVisibility']
if GDK2:
event_member_classes.append('EventNoExpose')
if hasattr(Gdk, 'EventTouch'):
event_member_classes.append('EventTouch')
# whitelist all methods that have a success return we want to mask
gsuccess_mask_funcs = ['get_state',
'get_axis',
'get_coords',
'get_root_coords']
for event_class in event_member_classes:
override_class = type(event_class, (getattr(Gdk, event_class),), {})
# add the event methods
for method_info in Gdk.Event.__info__.get_methods():
name = method_info.get_name()
event_method = getattr(Gdk.Event, name)
# use the _gsuccess_mask decorator if this method is whitelisted
if name in gsuccess_mask_funcs:
event_method = strip_boolean_result(event_method)
setattr(override_class, name, event_method)
setattr(module, event_class, override_class)
__all__.append(event_class)
# end GdkEvent overrides
class DragContext(Gdk.DragContext):
def finish(self, success, del_, time):
Gtk = get_introspection_module('Gtk')
Gtk.drag_finish(self, success, del_, time)
DragContext = override(DragContext)
__all__.append('DragContext')
class Cursor(Gdk.Cursor):
def __new__(cls, *args, **kwds):
arg_len = len(args)
kwd_len = len(kwds)
total_len = arg_len + kwd_len
if total_len == 1:
# Since g_object_newv (super.__new__) does not seem valid for
# direct use with GdkCursor, we must assume usage of at least
# one of the C constructors to be valid.
return cls.new(*args, **kwds)
elif total_len == 2:
warnings.warn('Calling "Gdk.Cursor(display, cursor_type)" has been deprecated. '
'Please use Gdk.Cursor.new_for_display(display, cursor_type). '
'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
PyGIDeprecationWarning)
return cls.new_for_display(*args, **kwds)
elif total_len == 4:
warnings.warn('Calling "Gdk.Cursor(display, pixbuf, x, y)" has been deprecated. '
'Please use Gdk.Cursor.new_from_pixbuf(display, pixbuf, x, y). '
'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
PyGIDeprecationWarning)
return cls.new_from_pixbuf(*args, **kwds)
elif total_len == 6:
if not GDK2:
# pixmaps don't exist in Gdk 3.0
raise ValueError("Wrong number of parameters")
warnings.warn('Calling "Gdk.Cursor(source, mask, fg, bg, x, y)" has been deprecated. '
'Please use Gdk.Cursor.new_from_pixmap(source, mask, fg, bg, x, y). '
'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
PyGIDeprecationWarning)
return cls.new_from_pixmap(*args, **kwds)
else:
raise ValueError("Wrong number of parameters")
Cursor = override(Cursor)
__all__.append('Cursor')
# Gdk.Color was deprecated since 3.14 and dropped in Gtk-4.0
color_parse = strip_boolean_result(Gdk.color_parse)
__all__.append('color_parse')
# Note, we cannot override the entire class as Gdk.Atom has no gtype, so just
# hack some individual methods
def _gdk_atom_str(atom):
n = atom.name()
if n:
return n
# fall back to atom index
return 'Gdk.Atom<%i>' % hash(atom)
def _gdk_atom_repr(atom):
n = atom.name()
if n:
return 'Gdk.Atom.intern("%s", False)' % n
# fall back to atom index
return '<Gdk.Atom(%i)>' % hash(atom)
Gdk.Atom.__str__ = _gdk_atom_str
Gdk.Atom.__repr__ = _gdk_atom_repr
if GDK4:
from gi.repository import Gio
class FileList(Gdk.FileList):
if hasattr(Gdk.FileList, "new_from_list"):
def __new__(cls, files):
files_list = []
if isinstance(files, (tuple, list)):
for f in files:
if isinstance(f, Gio.File):
files_list.append(f)
else:
raise TypeError('Constructor requires a list or tuple of Gio.File instances')
else:
raise TypeError('Constructor requires a list or tuple of Gio.File instances')
return Gdk.FileList.new_from_list(files)
def __iter__(self):
return iter(self.get_files())
def __len__(self):
return len(self.get_files())
def __getitem__(self, index):
return self.get_files()[index]
FileList = override(FileList)
__all__.append('FileList')
# constants
if GDK3:
SELECTION_PRIMARY = Gdk.atom_intern('PRIMARY', True)
__all__.append('SELECTION_PRIMARY')
SELECTION_SECONDARY = Gdk.atom_intern('SECONDARY', True)
__all__.append('SELECTION_SECONDARY')
SELECTION_CLIPBOARD = Gdk.atom_intern('CLIPBOARD', True)
__all__.append('SELECTION_CLIPBOARD')
TARGET_BITMAP = Gdk.atom_intern('BITMAP', True)
__all__.append('TARGET_BITMAP')
TARGET_COLORMAP = Gdk.atom_intern('COLORMAP', True)
__all__.append('TARGET_COLORMAP')
TARGET_DRAWABLE = Gdk.atom_intern('DRAWABLE', True)
__all__.append('TARGET_DRAWABLE')
TARGET_PIXMAP = Gdk.atom_intern('PIXMAP', True)
__all__.append('TARGET_PIXMAP')
TARGET_STRING = Gdk.atom_intern('STRING', True)
__all__.append('TARGET_STRING')
SELECTION_TYPE_ATOM = Gdk.atom_intern('ATOM', True)
__all__.append('SELECTION_TYPE_ATOM')
SELECTION_TYPE_BITMAP = Gdk.atom_intern('BITMAP', True)
__all__.append('SELECTION_TYPE_BITMAP')
SELECTION_TYPE_COLORMAP = Gdk.atom_intern('COLORMAP', True)
__all__.append('SELECTION_TYPE_COLORMAP')
SELECTION_TYPE_DRAWABLE = Gdk.atom_intern('DRAWABLE', True)
__all__.append('SELECTION_TYPE_DRAWABLE')
SELECTION_TYPE_INTEGER = Gdk.atom_intern('INTEGER', True)
__all__.append('SELECTION_TYPE_INTEGER')
SELECTION_TYPE_PIXMAP = Gdk.atom_intern('PIXMAP', True)
__all__.append('SELECTION_TYPE_PIXMAP')
SELECTION_TYPE_WINDOW = Gdk.atom_intern('WINDOW', True)
__all__.append('SELECTION_TYPE_WINDOW')
SELECTION_TYPE_STRING = Gdk.atom_intern('STRING', True)
__all__.append('SELECTION_TYPE_STRING')
if GDK2 or GDK3:
import sys
initialized, argv = Gdk.init_check(sys.argv)
@@ -0,0 +1,53 @@
# Copyright 2018 Christoph Reiter <reiter.christoph@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import warnings
from gi import PyGIDeprecationWarning
from gi.repository import GLib
from ..overrides import override
from ..module import get_introspection_module
GdkPixbuf = get_introspection_module('GdkPixbuf')
__all__ = []
@override
class Pixbuf(GdkPixbuf.Pixbuf):
@classmethod
def new_from_data(
cls, data, colorspace, has_alpha, bits_per_sample,
width, height, rowstride,
destroy_fn=None, *destroy_fn_data):
if destroy_fn is not None:
w = PyGIDeprecationWarning("destroy_fn argument deprecated")
warnings.warn(w)
if destroy_fn_data:
w = PyGIDeprecationWarning("destroy_fn_data argument deprecated")
warnings.warn(w)
data = GLib.Bytes.new(data)
return cls.new_from_bytes(
data, colorspace, has_alpha, bits_per_sample,
width, height, rowstride)
__all__.append('Pixbuf')
@@ -0,0 +1,550 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2010 Ignacio Casal Quinteiro <icq@gnome.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import warnings
from .._ossighelper import wakeup_on_signal, register_sigint_fallback
from ..overrides import override, deprecated_init, wrap_list_store_sort_func
from ..module import get_introspection_module
from gi import PyGIWarning
from gi.repository import GLib
import sys
Gio = get_introspection_module('Gio')
__all__ = []
class Application(Gio.Application):
def run(self, *args, **kwargs):
with register_sigint_fallback(self.quit):
with wakeup_on_signal():
return Gio.Application.run(self, *args, **kwargs)
Application = override(Application)
__all__.append('Application')
def _warn_init(cls, instead=None):
def new_init(self, *args, **kwargs):
super(cls, self).__init__(*args, **kwargs)
name = cls.__module__.rsplit(".", 1)[-1] + "." + cls.__name__
if instead:
warnings.warn(
("%s shouldn't be instantiated directly, "
"use %s instead." % (name, instead)),
PyGIWarning, stacklevel=2)
else:
warnings.warn(
"%s shouldn't be instantiated directly." % (name,),
PyGIWarning, stacklevel=2)
return new_init
@override
class VolumeMonitor(Gio.VolumeMonitor):
# https://bugzilla.gnome.org/show_bug.cgi?id=744690
__init__ = _warn_init(Gio.VolumeMonitor, "Gio.VolumeMonitor.get()")
__all__.append('VolumeMonitor')
@override
class DBusAnnotationInfo(Gio.DBusAnnotationInfo):
__init__ = _warn_init(Gio.DBusAnnotationInfo)
__all__.append('DBusAnnotationInfo')
@override
class DBusArgInfo(Gio.DBusArgInfo):
__init__ = _warn_init(Gio.DBusArgInfo)
__all__.append('DBusArgInfo')
@override
class DBusMethodInfo(Gio.DBusMethodInfo):
__init__ = _warn_init(Gio.DBusMethodInfo)
__all__.append('DBusMethodInfo')
@override
class DBusSignalInfo(Gio.DBusSignalInfo):
__init__ = _warn_init(Gio.DBusSignalInfo)
__all__.append('DBusSignalInfo')
@override
class DBusInterfaceInfo(Gio.DBusInterfaceInfo):
__init__ = _warn_init(Gio.DBusInterfaceInfo)
__all__.append('DBusInterfaceInfo')
@override
class DBusNodeInfo(Gio.DBusNodeInfo):
__init__ = _warn_init(Gio.DBusNodeInfo)
__all__.append('DBusNodeInfo')
class ActionMap(Gio.ActionMap):
def add_action_entries(self, entries, user_data=None):
"""
The add_action_entries() method is a convenience function for creating
multiple Gio.SimpleAction instances and adding them to a Gio.ActionMap.
Each action is constructed as per one entry.
:param list entries:
List of entry tuples for add_action() method. The entry tuple can
vary in size with the following information:
* The name of the action. Must be specified.
* The callback to connect to the "activate" signal of the
action. Since GLib 2.40, this can be None for stateful
actions, in which case the default handler is used. For
boolean-stated actions with no parameter, this is a toggle.
For other state types (and parameter type equal to the state
type) this will be a function that just calls change_state
(which you should provide).
* The type of the parameter that must be passed to the activate
function for this action, given as a single GLib.Variant type
string (or None for no parameter)
* The initial state for this action, given in GLib.Variant text
format. The state is parsed with no extra type information, so
type tags must be added to the string if they are necessary.
Stateless actions should give None here.
* The callback to connect to the "change-state" signal of the
action. All stateful actions should provide a handler here;
stateless actions should not.
:param user_data:
The user data for signal connections, or None
"""
try:
iter(entries)
except (TypeError):
raise TypeError('entries must be iterable')
def _process_action(name, activate=None, parameter_type=None,
state=None, change_state=None):
if parameter_type:
if not GLib.VariantType.string_is_valid(parameter_type):
raise TypeError("The type string '%s' given as the "
"parameter type for action '%s' is "
"not a valid GVariant type string. " %
(parameter_type, name))
variant_parameter = GLib.VariantType.new(parameter_type)
else:
variant_parameter = None
if state is not None:
# stateful action
variant_state = GLib.Variant.parse(None, state, None, None)
action = Gio.SimpleAction.new_stateful(name, variant_parameter,
variant_state)
if change_state is not None:
action.connect('change-state', change_state, user_data)
else:
# stateless action
if change_state is not None:
raise ValueError("Stateless action '%s' should give "
"None for 'change_state', not '%s'." %
(name, change_state))
action = Gio.SimpleAction(name=name, parameter_type=variant_parameter)
if activate is not None:
action.connect('activate', activate, user_data)
self.add_action(action)
for entry in entries:
# using inner function above since entries can leave out optional arguments
_process_action(*entry)
ActionMap = override(ActionMap)
__all__.append('ActionMap')
class FileEnumerator(Gio.FileEnumerator):
def __iter__(self):
return self
def __next__(self):
file_info = self.next_file(None)
if file_info is not None:
return file_info
else:
raise StopIteration
# python 2 compat for the iter protocol
next = __next__
FileEnumerator = override(FileEnumerator)
__all__.append('FileEnumerator')
class MenuItem(Gio.MenuItem):
def set_attribute(self, attributes):
for (name, format_string, value) in attributes:
self.set_attribute_value(name, GLib.Variant(format_string, value))
MenuItem = override(MenuItem)
__all__.append('MenuItem')
class Settings(Gio.Settings):
'''Provide dictionary-like access to GLib.Settings.'''
__init__ = deprecated_init(Gio.Settings.__init__,
arg_names=('schema', 'path', 'backend'))
def __contains__(self, key):
return key in self.list_keys()
def __len__(self):
return len(self.list_keys())
def __iter__(self):
for key in self.list_keys():
yield key
def __bool__(self):
# for "if mysettings" we don't want a dictionary-like test here, just
# if the object isn't None
return True
# alias for Python 2.x object protocol
__nonzero__ = __bool__
def __getitem__(self, key):
# get_value() aborts the program on an unknown key
if key not in self:
raise KeyError('unknown key: %r' % (key,))
return self.get_value(key).unpack()
def __setitem__(self, key, value):
# set_value() aborts the program on an unknown key
if key not in self:
raise KeyError('unknown key: %r' % (key,))
# determine type string of this key
range = self.get_range(key)
type_ = range.get_child_value(0).get_string()
v = range.get_child_value(1)
if type_ == 'type':
# v is boxed empty array, type of its elements is the allowed value type
type_str = v.get_child_value(0).get_type_string()
assert type_str.startswith('a')
type_str = type_str[1:]
elif type_ == 'enum':
# v is an array with the allowed values
assert v.get_child_value(0).get_type_string().startswith('a')
type_str = v.get_child_value(0).get_child_value(0).get_type_string()
allowed = v.unpack()
if value not in allowed:
raise ValueError('value %s is not an allowed enum (%s)' % (value, allowed))
elif type_ == 'range':
tuple_ = v.get_child_value(0)
type_str = tuple_.get_child_value(0).get_type_string()
min_, max_ = tuple_.unpack()
if value < min_ or value > max_:
raise ValueError(
'value %s not in range (%s - %s)' % (value, min_, max_))
else:
raise NotImplementedError('Cannot handle allowed type range class ' + str(type_))
self.set_value(key, GLib.Variant(type_str, value))
def keys(self):
return self.list_keys()
Settings = override(Settings)
__all__.append('Settings')
class _DBusProxyMethodCall:
'''Helper class to implement DBusProxy method calls.'''
def __init__(self, dbus_proxy, method_name):
self.dbus_proxy = dbus_proxy
self.method_name = method_name
def __async_result_handler(self, obj, result, user_data):
(result_callback, error_callback, real_user_data) = user_data
try:
ret = obj.call_finish(result)
except Exception:
etype, e = sys.exc_info()[:2]
# return exception as value
if error_callback:
error_callback(obj, e, real_user_data)
else:
result_callback(obj, e, real_user_data)
return
result_callback(obj, self._unpack_result(ret), real_user_data)
def __call__(self, *args, **kwargs):
# the first positional argument is the signature, unless we are calling
# a method without arguments; then signature is implied to be '()'.
if args:
signature = args[0]
args = args[1:]
if not isinstance(signature, str):
raise TypeError('first argument must be the method signature string: %r' % signature)
else:
signature = '()'
arg_variant = GLib.Variant(signature, tuple(args))
if 'result_handler' in kwargs:
# asynchronous call
user_data = (kwargs['result_handler'],
kwargs.get('error_handler'),
kwargs.get('user_data'))
self.dbus_proxy.call(self.method_name, arg_variant,
kwargs.get('flags', 0), kwargs.get('timeout', -1), None,
self.__async_result_handler, user_data)
else:
# synchronous call
result = self.dbus_proxy.call_sync(self.method_name, arg_variant,
kwargs.get('flags', 0),
kwargs.get('timeout', -1),
None)
return self._unpack_result(result)
@classmethod
def _unpack_result(klass, result):
'''Convert a D-BUS return variant into an appropriate return value'''
result = result.unpack()
# to be compatible with standard Python behaviour, unbox
# single-element tuples and return None for empty result tuples
if len(result) == 1:
result = result[0]
elif len(result) == 0:
result = None
return result
class DBusProxy(Gio.DBusProxy):
'''Provide comfortable and pythonic method calls.
This marshalls the method arguments into a GVariant, invokes the
call_sync() method on the DBusProxy object, and unmarshalls the result
GVariant back into a Python tuple.
The first argument always needs to be the D-Bus signature tuple of the
method call. Example:
proxy = Gio.DBusProxy.new_sync(...)
result = proxy.MyMethod('(is)', 42, 'hello')
The exception are methods which take no arguments, like
proxy.MyMethod('()'). For these you can omit the signature and just write
proxy.MyMethod().
Optional keyword arguments:
- timeout: timeout for the call in milliseconds (default to D-Bus timeout)
- flags: Combination of Gio.DBusCallFlags.*
- result_handler: Do an asynchronous method call and invoke
result_handler(proxy_object, result, user_data) when it finishes.
- error_handler: If the asynchronous call raises an exception,
error_handler(proxy_object, exception, user_data) is called when it
finishes. If error_handler is not given, result_handler is called with
the exception object as result instead.
- user_data: Optional user data to pass to result_handler for
asynchronous calls.
Example for asynchronous calls:
def mymethod_done(proxy, result, user_data):
if isinstance(result, Exception):
# handle error
else:
# do something with result
proxy.MyMethod('(is)', 42, 'hello',
result_handler=mymethod_done, user_data='data')
'''
def __getattr__(self, name):
return _DBusProxyMethodCall(self, name)
DBusProxy = override(DBusProxy)
__all__.append('DBusProxy')
class ListModel(Gio.ListModel):
def __getitem__(self, key):
if isinstance(key, slice):
return [self.get_item(i) for i in range(*key.indices(len(self)))]
elif isinstance(key, int):
if key < 0:
key += len(self)
if key < 0:
raise IndexError
ret = self.get_item(key)
if ret is None:
raise IndexError
return ret
else:
raise TypeError
def __contains__(self, item):
pytype = self.get_item_type().pytype
if not isinstance(item, pytype):
raise TypeError(
"Expected type %s.%s" % (pytype.__module__, pytype.__name__))
for i in self:
if i == item:
return True
return False
def __len__(self):
return self.get_n_items()
def __iter__(self):
for i in range(len(self)):
yield self.get_item(i)
ListModel = override(ListModel)
__all__.append('ListModel')
if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) < (2, 57, 1):
# The "additions" functionality in splice() was broken in older glib
# https://bugzilla.gnome.org/show_bug.cgi?id=795307
# This is a slower fallback which emits a signal per added item
def _list_store_splice(self, position, n_removals, additions):
self.splice(position, n_removals, [])
for v in reversed(additions):
self.insert(position, v)
else:
def _list_store_splice(self, position, n_removals, additions):
self.splice(position, n_removals, additions)
class ListStore(Gio.ListStore):
def sort(self, compare_func, *user_data):
compare_func = wrap_list_store_sort_func(compare_func)
return super(ListStore, self).sort(compare_func, *user_data)
def insert_sorted(self, item, compare_func, *user_data):
compare_func = wrap_list_store_sort_func(compare_func)
return super(ListStore, self).insert_sorted(
item, compare_func, *user_data)
def __delitem__(self, key):
if isinstance(key, slice):
start, stop, step = key.indices(len(self))
if step == 1:
_list_store_splice(self, start, max(stop - start, 0), [])
elif step == -1:
_list_store_splice(self, stop + 1, max(start - stop, 0), [])
else:
for i in sorted(range(start, stop, step), reverse=True):
self.remove(i)
elif isinstance(key, int):
if key < 0:
key += len(self)
if key < 0 or key >= len(self):
raise IndexError
self.remove(key)
else:
raise TypeError
def __setitem__(self, key, value):
if isinstance(key, slice):
pytype = self.get_item_type().pytype
valuelist = []
for v in value:
if not isinstance(v, pytype):
raise TypeError(
"Expected type %s.%s" % (
pytype.__module__, pytype.__name__))
valuelist.append(v)
start, stop, step = key.indices(len(self))
if step == 1:
_list_store_splice(
self, start, max(stop - start, 0), valuelist)
else:
indices = list(range(start, stop, step))
if len(indices) != len(valuelist):
raise ValueError
if step == -1:
_list_store_splice(
self, stop + 1, max(start - stop, 0), valuelist[::-1])
else:
for i, v in zip(indices, valuelist):
_list_store_splice(self, i, 1, [v])
elif isinstance(key, int):
if key < 0:
key += len(self)
if key < 0 or key >= len(self):
raise IndexError
pytype = self.get_item_type().pytype
if not isinstance(value, pytype):
raise TypeError(
"Expected type %s.%s" % (
pytype.__module__, pytype.__name__))
_list_store_splice(self, key, 1, [value])
else:
raise TypeError
ListStore = override(ListStore)
__all__.append('ListStore')
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,58 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2010 Paolo Borelli <pborelli@gnome.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
from ..overrides import override
from ..module import get_introspection_module
Pango = get_introspection_module('Pango')
__all__ = []
class FontDescription(Pango.FontDescription):
def __new__(cls, string=None):
if string is not None:
return Pango.font_description_from_string(string)
else:
return Pango.FontDescription.__new__(cls)
def __init__(self, *args, **kwargs):
return super(FontDescription, self).__init__()
FontDescription = override(FontDescription)
__all__.append('FontDescription')
class Layout(Pango.Layout):
def __new__(cls, context):
return Pango.Layout.new(context)
def set_markup(self, text, length=-1):
super(Layout, self).set_markup(text, length)
def set_text(self, text, length=-1):
super(Layout, self).set_text(text, length)
Layout = override(Layout)
__all__.append('Layout')
@@ -0,0 +1,347 @@
import functools
import types
import warnings
import importlib
import sys
from gi import PyGIDeprecationWarning
from gi._gi import CallableInfo, pygobject_new_full
from gi._constants import \
TYPE_NONE, \
TYPE_INVALID
# support overrides in different directories than our gi module
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
# namespace -> (attr, replacement)
_deprecated_attrs = {}
class OverridesProxyModule(types.ModuleType):
"""Wraps a introspection module and contains all overrides"""
def __init__(self, introspection_module):
super(OverridesProxyModule, self).__init__(
introspection_module.__name__)
self._introspection_module = introspection_module
def __getattr__(self, name):
return getattr(self._introspection_module, name)
def __dir__(self):
result = set(dir(self.__class__))
result.update(self.__dict__.keys())
result.update(dir(self._introspection_module))
return sorted(result)
def __repr__(self):
return "<%s %r>" % (type(self).__name__, self._introspection_module)
class _DeprecatedAttribute(object):
"""A deprecation descriptor for OverridesProxyModule subclasses.
Emits a PyGIDeprecationWarning on every access and tries to act as a
normal instance attribute (can be replaced and deleted).
"""
def __init__(self, namespace, attr, value, replacement):
self._attr = attr
self._value = value
self._warning = PyGIDeprecationWarning(
'%s.%s is deprecated; use %s instead' % (
namespace, attr, replacement))
def __get__(self, instance, owner):
if instance is None:
raise AttributeError(self._attr)
warnings.warn(self._warning, stacklevel=2)
return self._value
def __set__(self, instance, value):
attr = self._attr
# delete the descriptor, then set the instance value
delattr(type(instance), attr)
setattr(instance, attr, value)
def __delete__(self, instance):
# delete the descriptor
delattr(type(instance), self._attr)
def load_overrides(introspection_module):
"""Loads overrides for an introspection module.
Either returns the same module again in case there are no overrides or a
proxy module including overrides. Doesn't cache the result.
"""
namespace = introspection_module.__name__.rsplit(".", 1)[-1]
module_key = 'gi.repository.' + namespace
# We use sys.modules so overrides can import from gi.repository
# but restore everything at the end so this doesn't have any side effects
has_old = module_key in sys.modules
old_module = sys.modules.get(module_key)
# Create a new sub type, so we can separate descriptors like
# _DeprecatedAttribute for each namespace.
proxy_type = type(namespace + "ProxyModule", (OverridesProxyModule, ), {})
proxy = proxy_type(introspection_module)
sys.modules[module_key] = proxy
# backwards compat:
# gedit uses gi.importer.modules['Gedit']._introspection_module
from ..importer import modules
assert hasattr(proxy, "_introspection_module")
modules[namespace] = proxy
try:
override_package_name = 'gi.overrides.' + namespace
spec = importlib.util.find_spec(override_package_name)
override_loader = spec.loader if spec is not None else None
# Avoid checking for an ImportError, an override might
# depend on a missing module thus causing an ImportError
if override_loader is None:
return introspection_module
override_mod = importlib.import_module(override_package_name)
finally:
del modules[namespace]
del sys.modules[module_key]
if has_old:
sys.modules[module_key] = old_module
# backwards compat: for gst-python/gstmodule.c,
# which tries to access Gst.Fraction through
# Gst._overrides_module.Fraction. We assign the proxy instead as that
# contains all overridden classes like Fraction during import anyway and
# there is no need to keep the real override module alive.
proxy._overrides_module = proxy
override_all = []
if hasattr(override_mod, "__all__"):
override_all = override_mod.__all__
for var in override_all:
try:
item = getattr(override_mod, var)
except (AttributeError, TypeError):
# Gedit puts a non-string in __all__, so catch TypeError here
continue
setattr(proxy, var, item)
# Replace deprecated module level attributes with a descriptor
# which emits a warning when accessed.
for attr, replacement in _deprecated_attrs.pop(namespace, []):
try:
value = getattr(proxy, attr)
except AttributeError:
raise AssertionError(
"%s was set deprecated but wasn't added to __all__" % attr)
delattr(proxy, attr)
deprecated_attr = _DeprecatedAttribute(
namespace, attr, value, replacement)
setattr(proxy_type, attr, deprecated_attr)
return proxy
def override(type_):
"""Decorator for registering an override.
Other than objects added to __all__, these can get referenced in the same
override module via the gi.repository module (get_parent_for_object() does
for example), so they have to be added to the module immediately.
"""
if isinstance(type_, CallableInfo):
func = type_
namespace = func.__module__.rsplit('.', 1)[-1]
module = sys.modules["gi.repository." + namespace]
def wrapper(func):
setattr(module, func.__name__, func)
return func
return wrapper
elif isinstance(type_, types.FunctionType):
raise TypeError("func must be a gi function, got %s" % type_)
else:
try:
info = getattr(type_, '__info__')
except AttributeError:
raise TypeError(
'Can not override a type %s, which is not in a gobject '
'introspection typelib' % type_.__name__)
if not type_.__module__.startswith('gi.overrides'):
raise KeyError(
'You have tried override outside of the overrides module. '
'This is not allowed (%s, %s)' % (type_, type_.__module__))
g_type = info.get_g_type()
assert g_type != TYPE_NONE
if g_type != TYPE_INVALID:
g_type.pytype = type_
namespace = type_.__module__.rsplit(".", 1)[-1]
module = sys.modules["gi.repository." + namespace]
setattr(module, type_.__name__, type_)
return type_
overridefunc = override
"""Deprecated"""
def deprecated(fn, replacement):
"""Decorator for marking methods and classes as deprecated"""
@functools.wraps(fn)
def wrapped(*args, **kwargs):
warnings.warn('%s is deprecated; use %s instead' % (fn.__name__, replacement),
PyGIDeprecationWarning, stacklevel=2)
return fn(*args, **kwargs)
return wrapped
def deprecated_attr(namespace, attr, replacement):
"""Marks a module level attribute as deprecated. Accessing it will emit
a PyGIDeprecationWarning warning.
e.g. for ``deprecated_attr("GObject", "STATUS_FOO", "GLib.Status.FOO")``
accessing GObject.STATUS_FOO will emit:
"GObject.STATUS_FOO is deprecated; use GLib.Status.FOO instead"
:param str namespace:
The namespace of the override this is called in.
:param str namespace:
The attribute name (which gets added to __all__).
:param str replacement:
The replacement text which will be included in the warning.
"""
_deprecated_attrs.setdefault(namespace, []).append((attr, replacement))
def deprecated_init(super_init_func, arg_names, ignore=tuple(),
deprecated_aliases={}, deprecated_defaults={},
category=PyGIDeprecationWarning,
stacklevel=2):
"""Wrapper for deprecating GObject based __init__ methods which specify
defaults already available or non-standard defaults.
:param callable super_init_func:
Initializer to wrap.
:param list arg_names:
Ordered argument name list.
:param list ignore:
List of argument names to ignore when calling the wrapped function.
This is useful for function which take a non-standard keyword that is munged elsewhere.
:param dict deprecated_aliases:
Dictionary mapping a keyword alias to the actual g_object_newv keyword.
:param dict deprecated_defaults:
Dictionary of non-standard defaults that will be used when the
keyword is not explicitly passed.
:param Exception category:
Exception category of the error.
:param int stacklevel:
Stack level for the deprecation passed on to warnings.warn
:returns: Wrapped version of ``super_init_func`` which gives a deprecation
warning when non-keyword args or aliases are used.
:rtype: callable
"""
# We use a list of argument names to maintain order of the arguments
# being deprecated. This allows calls with positional arguments to
# continue working but with a deprecation message.
def new_init(self, *args, **kwargs):
"""Initializer for a GObject based classes with support for property
sets through the use of explicit keyword arguments.
"""
# Print warnings for calls with positional arguments.
if args:
warnings.warn('Using positional arguments with the GObject constructor has been deprecated. '
'Please specify keyword(s) for "%s" or use a class specific constructor. '
'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
', '.join(arg_names[:len(args)]),
category, stacklevel=stacklevel)
new_kwargs = dict(zip(arg_names, args))
else:
new_kwargs = {}
new_kwargs.update(kwargs)
# Print warnings for alias usage and transfer them into the new key.
aliases_used = []
for key, alias in deprecated_aliases.items():
if alias in new_kwargs:
new_kwargs[key] = new_kwargs.pop(alias)
aliases_used.append(key)
if aliases_used:
warnings.warn('The keyword(s) "%s" have been deprecated in favor of "%s" respectively. '
'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
(', '.join(deprecated_aliases[k] for k in sorted(aliases_used)),
', '.join(sorted(aliases_used))),
category, stacklevel=stacklevel)
# Print warnings for defaults different than what is already provided by the property
defaults_used = []
for key, value in deprecated_defaults.items():
if key not in new_kwargs:
new_kwargs[key] = deprecated_defaults[key]
defaults_used.append(key)
if defaults_used:
warnings.warn('Initializer is relying on deprecated non-standard '
'defaults. Please update to explicitly use: %s '
'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
', '.join('%s=%s' % (k, deprecated_defaults[k]) for k in sorted(defaults_used)),
category, stacklevel=stacklevel)
# Remove keywords that should be ignored.
for key in ignore:
if key in new_kwargs:
new_kwargs.pop(key)
return super_init_func(self, **new_kwargs)
return new_init
def strip_boolean_result(method, exc_type=None, exc_str=None, fail_ret=None):
"""Translate method's return value for stripping off success flag.
There are a lot of methods which return a "success" boolean and have
several out arguments. Translate such a method to return the out arguments
on success and None on failure.
"""
@functools.wraps(method)
def wrapped(*args, **kwargs):
ret = method(*args, **kwargs)
if ret[0]:
if len(ret) == 2:
return ret[1]
else:
return ret[1:]
else:
if exc_type:
raise exc_type(exc_str or 'call failed')
return fail_ret
return wrapped
def wrap_list_store_sort_func(func):
def wrap(a, b, *user_data):
a = pygobject_new_full(a, False)
b = pygobject_new_full(b, False)
return func(a, b, *user_data)
return wrap
@@ -0,0 +1,53 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# pygtk - Python bindings for the GTK toolkit.
# Copyright (C) 1998-2003 James Henstridge
#
# gtk/keysyms.py: list of keysyms.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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, see <http://www.gnu.org/licenses/>.
import sys
import warnings
from ..module import get_introspection_module
Gdk = get_introspection_module('Gdk')
warnings.warn('keysyms has been deprecated. Please use Gdk.KEY_<name> instead.',
RuntimeWarning)
_modname = globals()['__name__']
_keysyms = sys.modules[_modname]
for name in dir(Gdk):
if name.startswith('KEY_'):
target = name[4:]
if target[0] in '0123456789':
target = '_' + target
value = getattr(Gdk, name)
setattr(_keysyms, target, value)
# Not found in Gdk but left for compatibility.
Armenian_eternity = 0x14a1
Armenian_section_sign = 0x14a2
Armenian_parenleft = 0x14a5
Armenian_guillemotright = 0x14a6
Armenian_guillemotleft = 0x14a7
Armenian_em_dash = 0x14a8
Armenian_dot = 0x14a9
Armenian_mijaket = 0x14a9
Armenian_comma = 0x14ab
Armenian_en_dash = 0x14ac
Armenian_ellipsis = 0x14ae
@@ -0,0 +1,26 @@
import warnings
from gi import PyGIDeprecationWarning
warnings.warn('gi.pygtkcompat is being deprecated in favor of using "pygtkcompat" directly.',
PyGIDeprecationWarning)
# pyflakes.ignore
from pygtkcompat import (enable,
enable_gtk,
enable_vte,
enable_poppler,
enable_webkit,
enable_gudev,
enable_gst,
enable_goocanvas)
__all__ = ['enable',
'enable_gtk',
'enable_vte',
'enable_poppler',
'enable_webkit',
'enable_gudev',
'enable_gst',
'enable_goocanvas']
@@ -0,0 +1,28 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2009 Johan Dahlin <johan@gnome.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import sys
from ..importer import DynamicImporter
sys.meta_path.append(DynamicImporter('gi.repository'))
del DynamicImporter
del sys
@@ -0,0 +1,350 @@
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
#
# types.py: base types for introspected items.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser 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 St, Fifth Floor, Boston, MA 02110-1301
# USA
import re
from ._constants import TYPE_INVALID
from .docstring import generate_doc_string
from ._gi import \
InterfaceInfo, \
ObjectInfo, \
StructInfo, \
VFuncInfo, \
register_interface_info, \
hook_up_vfunc_implementation, \
GInterface
from . import _gi
StructInfo, GInterface # pyflakes
from . import _propertyhelper as propertyhelper
from . import _signalhelper as signalhelper
def snake_case(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
class MetaClassHelper(object):
def _setup_methods(cls):
for method_info in cls.__info__.get_methods():
setattr(cls, method_info.__name__, method_info)
def _setup_class_methods(cls):
info = cls.__info__
class_struct = info.get_class_struct()
if class_struct is None:
return
for method_info in class_struct.get_methods():
name = method_info.__name__
# Don't mask regular methods or base class methods with TypeClass methods.
if not hasattr(cls, name):
setattr(cls, name, classmethod(method_info))
def _setup_fields(cls):
for field_info in cls.__info__.get_fields():
name = field_info.get_name().replace('-', '_')
setattr(cls, name, property(field_info.get_value, field_info.set_value))
def _setup_constants(cls):
for constant_info in cls.__info__.get_constants():
name = constant_info.get_name()
value = constant_info.get_value()
setattr(cls, name, value)
def _setup_vfuncs(cls):
for vfunc_name, py_vfunc in cls.__dict__.items():
if not vfunc_name.startswith("do_") or not callable(py_vfunc):
continue
skip_ambiguity_check = False
# If a method name starts with "do_" assume it is a vfunc, and search
# in the base classes for a method with the same name to override.
# Recursion is necessary as overriden methods in most immediate parent
# classes may shadow vfuncs from classes higher in the hierarchy.
vfunc_info = None
for base in cls.__mro__:
method = getattr(base, vfunc_name, None)
if method is not None and isinstance(method, VFuncInfo):
vfunc_info = method
break
if not hasattr(base, '__info__') or not hasattr(base.__info__, 'get_vfuncs'):
continue
base_name = snake_case(base.__info__.get_type_name())
for v in base.__info__.get_vfuncs():
if vfunc_name == 'do_%s_%s' % (base_name, v.get_name()):
vfunc_info = v
skip_ambiguity_check = True
break
if vfunc_info:
break
# If we did not find a matching method name in the bases, we might
# be overriding an interface virtual method. Since interfaces do not
# provide implementations, there will be no method attribute installed
# on the object. Instead we have to search through
# InterfaceInfo.get_vfuncs(). Note that the infos returned by
# get_vfuncs() use the C vfunc name (ie. there is no "do_" prefix).
if vfunc_info is None:
vfunc_info = find_vfunc_info_in_interface(cls.__bases__, vfunc_name[len("do_"):])
if vfunc_info is not None:
# Check to see if there are vfuncs with the same name in the bases.
# We have no way of specifying which one we are supposed to override.
if not skip_ambiguity_check:
ambiguous_base = find_vfunc_conflict_in_bases(vfunc_info, cls.__bases__)
if ambiguous_base is not None:
base_info = vfunc_info.get_container()
raise TypeError('Method %s() on class %s.%s is ambiguous '
'with methods in base classes %s.%s and %s.%s' %
(vfunc_name,
cls.__info__.get_namespace(),
cls.__info__.get_name(),
base_info.get_namespace(),
base_info.get_name(),
ambiguous_base.__info__.get_namespace(),
ambiguous_base.__info__.get_name()
))
hook_up_vfunc_implementation(vfunc_info, cls.__gtype__,
py_vfunc)
def _setup_native_vfuncs(cls):
# Only InterfaceInfo and ObjectInfo have the get_vfuncs() method.
# We skip InterfaceInfo because interfaces have no implementations for vfuncs.
# Also check if __info__ in __dict__, not hasattr('__info__', ...)
# because we do not want to accidentally retrieve __info__ from a base class.
class_info = cls.__dict__.get('__info__')
if class_info is None or not isinstance(class_info, ObjectInfo):
return
# Special case skipping of vfuncs for GObject.Object because they will break
# the static bindings which will try to use them.
if cls.__module__ == 'gi.repository.GObject' and cls.__name__ == 'Object':
return
for vfunc_info in class_info.get_vfuncs():
name = 'do_%s' % vfunc_info.__name__
setattr(cls, name, vfunc_info)
def find_vfunc_info_in_interface(bases, vfunc_name):
for base in bases:
# All wrapped interfaces inherit from GInterface.
# This can be seen in IntrospectionModule.__getattr__() in module.py.
# We do not need to search regular classes here, only wrapped interfaces.
# We also skip GInterface, because it is not wrapped and has no __info__ attr.
# Skip bases without __info__ (static _gi.GObject)
if base is GInterface or\
not issubclass(base, GInterface) or\
not hasattr(base, '__info__'):
continue
# Only look at this classes vfuncs if it is an interface.
if isinstance(base.__info__, InterfaceInfo):
for vfunc in base.__info__.get_vfuncs():
if vfunc.get_name() == vfunc_name:
return vfunc
# Recurse into the parent classes
vfunc = find_vfunc_info_in_interface(base.__bases__, vfunc_name)
if vfunc is not None:
return vfunc
return None
def find_vfunc_conflict_in_bases(vfunc, bases):
for klass in bases:
if not hasattr(klass, '__info__') or \
not hasattr(klass.__info__, 'get_vfuncs'):
continue
vfuncs = klass.__info__.get_vfuncs()
vfunc_name = vfunc.get_name()
for v in vfuncs:
if v.get_name() == vfunc_name and v != vfunc:
return klass
aklass = find_vfunc_conflict_in_bases(vfunc, klass.__bases__)
if aklass is not None:
return aklass
return None
class _GObjectMetaBase(type):
"""Metaclass for automatically registering GObject classes."""
def __init__(cls, name, bases, dict_):
type.__init__(cls, name, bases, dict_)
propertyhelper.install_properties(cls)
signalhelper.install_signals(cls)
cls._type_register(cls.__dict__)
def _type_register(cls, namespace):
# don't register the class if already registered
if '__gtype__' in namespace:
return
# Do not register a new GType for the overrides, as this would sort of
# defeat the purpose of overrides...
if cls.__module__.startswith('gi.overrides.'):
return
_gi.type_register(cls, namespace.get('__gtype_name__'))
_gi._install_metaclass(_GObjectMetaBase)
class GObjectMeta(_GObjectMetaBase, MetaClassHelper):
"""Meta class used for GI GObject based types."""
def __init__(cls, name, bases, dict_):
super(GObjectMeta, cls).__init__(name, bases, dict_)
is_gi_defined = False
if cls.__module__ == 'gi.repository.' + cls.__info__.get_namespace():
is_gi_defined = True
is_python_defined = False
if not is_gi_defined and cls.__module__ != GObjectMeta.__module__:
is_python_defined = True
if is_python_defined:
cls._setup_vfuncs()
elif is_gi_defined:
if isinstance(cls.__info__, ObjectInfo):
cls._setup_class_methods()
cls._setup_methods()
cls._setup_constants()
cls._setup_native_vfuncs()
if isinstance(cls.__info__, ObjectInfo):
cls._setup_fields()
elif isinstance(cls.__info__, InterfaceInfo):
register_interface_info(cls.__info__.get_g_type())
def mro(cls):
return mro(cls)
@property
def __doc__(cls):
"""Meta class property which shows up on any class using this meta-class."""
if cls == GObjectMeta:
return ''
doc = cls.__dict__.get('__doc__', None)
if doc is not None:
return doc
# For repository classes, dynamically generate a doc string if it wasn't overridden.
if cls.__module__.startswith(('gi.repository.', 'gi.overrides')):
return generate_doc_string(cls.__info__)
return None
def mro(C):
"""Compute the class precedence list (mro) according to C3, with GObject
interface considerations.
We override Python's MRO calculation to account for the fact that
GObject classes are not affected by the diamond problem:
http://en.wikipedia.org/wiki/Diamond_problem
Based on http://www.python.org/download/releases/2.3/mro/
"""
# TODO: If this turns out being too slow, consider using generators
bases = []
bases_of_subclasses = [[C]]
if C.__bases__:
for base in C.__bases__:
# Python causes MRO's to be calculated starting with the lowest
# base class and working towards the descendant, storing the result
# in __mro__ at each point. Therefore at this point we know that
# we already have our base class MRO's available to us, there is
# no need for us to (re)calculate them.
bases_of_subclasses += [list(base.__mro__)]
bases_of_subclasses += [list(C.__bases__)]
while bases_of_subclasses:
for subclass_bases in bases_of_subclasses:
candidate = subclass_bases[0]
not_head = [s for s in bases_of_subclasses if candidate in s[1:]]
if not_head and GInterface not in candidate.__bases__:
candidate = None # conflict, reject candidate
else:
break
if candidate is None:
raise TypeError('Cannot create a consistent method resolution '
'order (MRO)')
bases.append(candidate)
for subclass_bases in bases_of_subclasses[:]: # remove candidate
if subclass_bases and subclass_bases[0] == candidate:
del subclass_bases[0]
if not subclass_bases:
bases_of_subclasses.remove(subclass_bases)
return bases
def nothing(*args, **kwargs):
pass
class StructMeta(type, MetaClassHelper):
"""Meta class used for GI Struct based types."""
def __init__(cls, name, bases, dict_):
super(StructMeta, cls).__init__(name, bases, dict_)
# Avoid touching anything else than the base class.
g_type = cls.__info__.get_g_type()
if g_type != TYPE_INVALID and g_type.pytype is not None:
return
cls._setup_fields()
cls._setup_methods()
for method_info in cls.__info__.get_methods():
if method_info.is_constructor() and \
method_info.__name__ == 'new' and \
(not method_info.get_arguments() or
cls.__info__.get_size() == 0):
cls.__new__ = staticmethod(method_info)
# Boxed will raise an exception
# if arguments are given to __init__
cls.__init__ = nothing
break
@property
def __doc__(cls):
if cls == StructMeta:
return ''
return generate_doc_string(cls.__info__)