Ajout du GUI
This commit is contained in:
parent
db362ccdca
commit
abd15f28b6
@ -1,2 +1,14 @@
|
||||
# It is not that we have a short time to live, but that we waste a lot of it. Life is long enough, and a sufficiently generous amount has been given to us for the highest achievements if it were all well invested
|
||||
# SENECA
|
||||
# SENECA
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
class PongGame(Widget):
|
||||
pass
|
||||
|
||||
class TestApp(App):
|
||||
def build(self):
|
||||
return PongGame()
|
||||
|
||||
TestApp().run()
|
||||
516
kivy/__init__.py
Normal file
516
kivy/__init__.py
Normal file
@ -0,0 +1,516 @@
|
||||
'''
|
||||
Kivy framework
|
||||
==============
|
||||
|
||||
Kivy is an open source library for developing multi-touch applications. It is
|
||||
cross-platform (Linux/OSX/Windows/Android/iOS) and released under
|
||||
the terms of the `MIT License <https://en.wikipedia.org/wiki/MIT_License>`_.
|
||||
|
||||
It comes with native support for many multi-touch input devices, a growing
|
||||
library of multi-touch aware widgets and hardware accelerated OpenGL drawing.
|
||||
Kivy is designed to let you focus on building custom and highly interactive
|
||||
applications as quickly and easily as possible.
|
||||
|
||||
With Kivy, you can take full advantage of the dynamic nature of Python. There
|
||||
are thousands of high-quality, free libraries that can be integrated in your
|
||||
application. At the same time, performance-critical parts are implemented
|
||||
using `Cython <http://cython.org/>`_.
|
||||
|
||||
See http://kivy.org for more information.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'require', 'parse_kivy_version',
|
||||
'kivy_configure', 'kivy_register_post_configuration',
|
||||
'kivy_options', 'kivy_base_dir',
|
||||
'kivy_modules_dir', 'kivy_data_dir', 'kivy_shader_dir',
|
||||
'kivy_icons_dir', 'kivy_home_dir',
|
||||
'kivy_config_fn', 'kivy_usermodules_dir', 'kivy_examples_dir'
|
||||
)
|
||||
|
||||
import sys
|
||||
import shutil
|
||||
from getopt import getopt, GetoptError
|
||||
import os
|
||||
from os import environ, mkdir
|
||||
from os.path import dirname, join, basename, exists, expanduser
|
||||
import pkgutil
|
||||
import re
|
||||
from kivy.logger import Logger, LOG_LEVELS
|
||||
from kivy.utils import platform
|
||||
from kivy._version import __version__, RELEASE as _KIVY_RELEASE, \
|
||||
_kivy_git_hash, _kivy_build_date
|
||||
|
||||
# internals for post-configuration
|
||||
__kivy_post_configuration = []
|
||||
|
||||
|
||||
if platform == 'macosx' and sys.maxsize < 9223372036854775807:
|
||||
r = '''Unsupported Python version detected!:
|
||||
Kivy requires a 64 bit version of Python to run on OS X. We strongly
|
||||
advise you to use the version of Python that is provided by Apple
|
||||
(don't use ports, fink or homebrew unless you know what you're
|
||||
doing).
|
||||
See http://kivy.org/docs/installation/installation-macosx.html for
|
||||
details.
|
||||
'''
|
||||
Logger.critical(r)
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
Logger.critical(
|
||||
'Unsupported Python version detected!: Kivy 2.0.0 and higher does not '
|
||||
'support Python 2. Please upgrade to Python 3, or downgrade Kivy to '
|
||||
'1.11.0 - the last Kivy release that still supports Python 2.')
|
||||
|
||||
|
||||
def parse_kivy_version(version):
|
||||
"""Parses the kivy version as described in :func:`require` into a 3-tuple
|
||||
of ([x, y, z], 'rc|a|b|dev|post', 'N') where N is the tag revision. The
|
||||
last two elements may be None.
|
||||
"""
|
||||
m = re.match(
|
||||
'^([0-9]+)\\.([0-9]+)\\.([0-9]+?)(rc|a|b|\\.dev|\\.post)?([0-9]+)?$',
|
||||
version)
|
||||
if m is None:
|
||||
raise Exception('Revision format must be X.Y.Z[-tag]')
|
||||
|
||||
major, minor, micro, tag, tagrev = m.groups()
|
||||
if tag == '.dev':
|
||||
tag = 'dev'
|
||||
if tag == '.post':
|
||||
tag = 'post'
|
||||
return [int(major), int(minor), int(micro)], tag, tagrev
|
||||
|
||||
|
||||
def require(version):
|
||||
'''Require can be used to check the minimum version required to run a Kivy
|
||||
application. For example, you can start your application code like this::
|
||||
|
||||
import kivy
|
||||
kivy.require('1.0.1')
|
||||
|
||||
If a user attempts to run your application with a version of Kivy that is
|
||||
older than the specified version, an Exception is raised.
|
||||
|
||||
The Kivy version string is built like this::
|
||||
|
||||
X.Y.Z[tag[tagrevision]]
|
||||
|
||||
X is the major version
|
||||
Y is the minor version
|
||||
Z is the bugfixes revision
|
||||
|
||||
The tag is optional, but may be one of '.dev', '.post', 'a', 'b', or 'rc'.
|
||||
The tagrevision is the revision number of the tag.
|
||||
|
||||
.. warning::
|
||||
|
||||
You must not ask for a version with a tag, except -dev. Asking for a
|
||||
'dev' version will just warn the user if the current Kivy
|
||||
version is not a -dev, but it will never raise an exception.
|
||||
You must not ask for a version with a tagrevision.
|
||||
|
||||
'''
|
||||
|
||||
# user version
|
||||
revision, tag, tagrev = parse_kivy_version(version)
|
||||
# current version
|
||||
sysrevision, systag, systagrev = parse_kivy_version(__version__)
|
||||
|
||||
if tag and not systag:
|
||||
Logger.warning('Application requested a dev version of Kivy. '
|
||||
'(You have %s, but the application requires %s)' % (
|
||||
__version__, version))
|
||||
# not tag rev (-alpha-1, -beta-x) allowed.
|
||||
if tagrev is not None:
|
||||
raise Exception('Revision format must not contain any tagrevision')
|
||||
|
||||
# finally, checking revision
|
||||
if sysrevision < revision:
|
||||
raise Exception('The version of Kivy installed on this system '
|
||||
'is too old. '
|
||||
'(You have %s, but the application requires %s)' % (
|
||||
__version__, version))
|
||||
|
||||
|
||||
def kivy_configure():
|
||||
'''Call post-configuration of Kivy.
|
||||
This function must be called if you create the window yourself.
|
||||
'''
|
||||
for callback in __kivy_post_configuration:
|
||||
callback()
|
||||
|
||||
|
||||
def get_includes():
|
||||
'''Retrieves the directories containing includes needed to build new Cython
|
||||
modules with Kivy as a dependency. Currently returns the location of the
|
||||
kivy.graphics module.
|
||||
|
||||
.. versionadded:: 1.9.1
|
||||
'''
|
||||
root_dir = dirname(__file__)
|
||||
return [join(root_dir, 'graphics'), join(root_dir, 'tools', 'gles_compat'),
|
||||
join(root_dir, 'include')]
|
||||
|
||||
|
||||
def kivy_register_post_configuration(callback):
|
||||
'''Register a function to be called when kivy_configure() is called.
|
||||
|
||||
.. warning::
|
||||
Internal use only.
|
||||
'''
|
||||
__kivy_post_configuration.append(callback)
|
||||
|
||||
|
||||
def kivy_usage():
|
||||
'''Kivy Usage: %s [KIVY OPTION...] [-- PROGRAM OPTIONS]::
|
||||
|
||||
Options placed after a '-- ' separator, will not be touched by kivy,
|
||||
and instead passed to your program.
|
||||
|
||||
Set KIVY_NO_ARGS=1 in your environment or before you import Kivy to
|
||||
disable Kivy's argument parser.
|
||||
|
||||
-h, --help
|
||||
Prints this help message.
|
||||
-d, --debug
|
||||
Shows debug log.
|
||||
-a, --auto-fullscreen
|
||||
Force 'auto' fullscreen mode (no resolution change).
|
||||
Uses your display's resolution. This is most likely what you want.
|
||||
-c, --config section:key[:value]
|
||||
Set a custom [section] key=value in the configuration object.
|
||||
-f, --fullscreen
|
||||
Force running in fullscreen mode.
|
||||
-k, --fake-fullscreen
|
||||
Force 'fake' fullscreen mode (no window border/decoration).
|
||||
Uses the resolution specified by width and height in your config.
|
||||
-w, --windowed
|
||||
Force running in a window.
|
||||
-p, --provider id:provider[,options]
|
||||
Add an input provider (eg: ccvtable1:tuio,192.168.0.1:3333).
|
||||
-m mod, --module=mod
|
||||
Activate a module (use "list" to get a list of available modules).
|
||||
-r, --rotation
|
||||
Rotate the window's contents (0, 90, 180, 270).
|
||||
-s, --save
|
||||
Save current Kivy configuration.
|
||||
--size=640x480
|
||||
Size of window geometry.
|
||||
--dpi=96
|
||||
Manually overload the Window DPI (for testing only.)
|
||||
'''
|
||||
print(kivy_usage.__doc__ % (basename(sys.argv[0])))
|
||||
|
||||
|
||||
#: Global settings options for kivy
|
||||
kivy_options = {
|
||||
'window': ('egl_rpi', 'sdl2', 'pygame', 'sdl', 'x11'),
|
||||
'text': ('pil', 'sdl2', 'pygame', 'sdlttf'),
|
||||
'video': (
|
||||
'gstplayer', 'ffmpeg', 'ffpyplayer', 'null'),
|
||||
'audio': (
|
||||
'gstplayer', 'pygame', 'ffpyplayer', 'sdl2',
|
||||
'avplayer'),
|
||||
'image': ('tex', 'imageio', 'dds', 'sdl2', 'pygame', 'pil', 'ffpy', 'gif'),
|
||||
'camera': ('opencv', 'gi', 'avfoundation',
|
||||
'android', 'picamera'),
|
||||
'spelling': ('enchant', 'osxappkit', ),
|
||||
'clipboard': (
|
||||
'android', 'winctypes', 'xsel', 'xclip', 'dbusklipper', 'nspaste',
|
||||
'sdl2', 'pygame', 'dummy', 'gtk3', )}
|
||||
|
||||
# Read environment
|
||||
for option in kivy_options:
|
||||
key = 'KIVY_%s' % option.upper()
|
||||
if key in environ:
|
||||
try:
|
||||
if type(kivy_options[option]) in (list, tuple):
|
||||
kivy_options[option] = environ[key].split(',')
|
||||
else:
|
||||
kivy_options[option] = environ[key].lower() in \
|
||||
('true', '1', 'yes')
|
||||
except Exception:
|
||||
Logger.warning('Core: Wrong value for %s environment key' % key)
|
||||
Logger.exception('')
|
||||
|
||||
# Extract all needed path in kivy
|
||||
#: Kivy directory
|
||||
kivy_base_dir = dirname(sys.modules[__name__].__file__)
|
||||
#: Kivy modules directory
|
||||
|
||||
kivy_modules_dir = environ.get('KIVY_MODULES_DIR',
|
||||
join(kivy_base_dir, 'modules'))
|
||||
#: Kivy data directory
|
||||
kivy_data_dir = environ.get('KIVY_DATA_DIR',
|
||||
join(kivy_base_dir, 'data'))
|
||||
#: Kivy binary deps directory
|
||||
kivy_binary_deps_dir = environ.get('KIVY_BINARY_DEPS',
|
||||
join(kivy_base_dir, 'binary_deps'))
|
||||
#: Kivy glsl shader directory
|
||||
kivy_shader_dir = join(kivy_data_dir, 'glsl')
|
||||
#: Kivy icons config path (don't remove the last '')
|
||||
kivy_icons_dir = join(kivy_data_dir, 'icons', '')
|
||||
#: Kivy user-home storage directory
|
||||
kivy_home_dir = ''
|
||||
#: Kivy configuration filename
|
||||
kivy_config_fn = ''
|
||||
#: Kivy user modules directory
|
||||
kivy_usermodules_dir = ''
|
||||
#: Kivy examples directory
|
||||
kivy_examples_dir = ''
|
||||
for examples_dir in (
|
||||
join(dirname(dirname(__file__)), 'examples'),
|
||||
join(sys.exec_prefix, 'share', 'kivy-examples'),
|
||||
join(sys.prefix, 'share', 'kivy-examples'),
|
||||
'/usr/share/kivy-examples', '/usr/local/share/kivy-examples',
|
||||
expanduser('~/.local/share/kivy-examples')):
|
||||
if exists(examples_dir):
|
||||
kivy_examples_dir = examples_dir
|
||||
break
|
||||
|
||||
|
||||
def _patch_mod_deps_win(dep_mod, mod_name):
|
||||
import site
|
||||
dep_bins = []
|
||||
|
||||
for d in [sys.prefix, site.USER_BASE]:
|
||||
p = join(d, 'share', mod_name, 'bin')
|
||||
if os.path.isdir(p):
|
||||
os.environ["PATH"] = p + os.pathsep + os.environ["PATH"]
|
||||
if hasattr(os, 'add_dll_directory'):
|
||||
os.add_dll_directory(p)
|
||||
dep_bins.append(p)
|
||||
|
||||
dep_mod.dep_bins = dep_bins
|
||||
|
||||
|
||||
# if there are deps, import them so they can do their magic.
|
||||
_packages = []
|
||||
try:
|
||||
from kivy import deps as old_deps
|
||||
for importer, modname, ispkg in pkgutil.iter_modules(old_deps.__path__):
|
||||
if not ispkg:
|
||||
continue
|
||||
if modname.startswith('gst'):
|
||||
_packages.insert(0, (importer, modname, 'kivy.deps'))
|
||||
else:
|
||||
_packages.append((importer, modname, 'kivy.deps'))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import kivy_deps
|
||||
for importer, modname, ispkg in pkgutil.iter_modules(kivy_deps.__path__):
|
||||
if not ispkg:
|
||||
continue
|
||||
if modname.startswith('gst'):
|
||||
_packages.insert(0, (importer, modname, 'kivy_deps'))
|
||||
else:
|
||||
_packages.append((importer, modname, 'kivy_deps'))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
_logging_msgs = []
|
||||
for importer, modname, package in _packages:
|
||||
try:
|
||||
mod = importer.find_module(modname).load_module(modname)
|
||||
|
||||
version = ''
|
||||
if hasattr(mod, '__version__'):
|
||||
version = ' {}'.format(mod.__version__)
|
||||
_logging_msgs.append(
|
||||
'deps: Successfully imported "{}.{}"{}'.
|
||||
format(package, modname, version))
|
||||
|
||||
if modname.startswith('gst') and version == '0.3.3':
|
||||
_patch_mod_deps_win(mod, modname)
|
||||
|
||||
except ImportError as e:
|
||||
Logger.warning(
|
||||
'deps: Error importing dependency "{}.{}": {}'.
|
||||
format(package, modname, str(e)))
|
||||
|
||||
# Don't go further if we generate documentation
|
||||
if any(name in sys.argv[0] for name in (
|
||||
'sphinx-build', 'autobuild.py', 'sphinx'
|
||||
)):
|
||||
environ['KIVY_DOC'] = '1'
|
||||
if 'sphinx-build' in sys.argv[0]:
|
||||
environ['KIVY_DOC_INCLUDE'] = '1'
|
||||
if any(('nosetests' in arg or 'pytest' in arg) for arg in sys.argv):
|
||||
environ['KIVY_UNITTEST'] = '1'
|
||||
if any('pyinstaller' in arg.lower() for arg in sys.argv):
|
||||
environ['KIVY_PACKAGING'] = '1'
|
||||
|
||||
if not environ.get('KIVY_DOC_INCLUDE'):
|
||||
# Configuration management
|
||||
if 'KIVY_HOME' in environ:
|
||||
kivy_home_dir = expanduser(environ['KIVY_HOME'])
|
||||
else:
|
||||
user_home_dir = expanduser('~')
|
||||
if platform == 'android':
|
||||
user_home_dir = environ['ANDROID_APP_PATH']
|
||||
elif platform == 'ios':
|
||||
user_home_dir = join(expanduser('~'), 'Documents')
|
||||
kivy_home_dir = join(user_home_dir, '.kivy')
|
||||
|
||||
kivy_config_fn = join(kivy_home_dir, 'config.ini')
|
||||
kivy_usermodules_dir = join(kivy_home_dir, 'mods')
|
||||
icon_dir = join(kivy_home_dir, 'icon')
|
||||
|
||||
if 'KIVY_NO_CONFIG' not in environ:
|
||||
if not exists(kivy_home_dir):
|
||||
mkdir(kivy_home_dir)
|
||||
if not exists(kivy_usermodules_dir):
|
||||
mkdir(kivy_usermodules_dir)
|
||||
if not exists(icon_dir):
|
||||
try:
|
||||
shutil.copytree(join(kivy_data_dir, 'logo'), icon_dir)
|
||||
except:
|
||||
Logger.exception('Error when copying logo directory')
|
||||
|
||||
# configuration
|
||||
from kivy.config import Config
|
||||
|
||||
# Set level of logger
|
||||
level = LOG_LEVELS.get(Config.get('kivy', 'log_level'))
|
||||
Logger.setLevel(level=level)
|
||||
|
||||
# Can be overridden in command line
|
||||
if ('KIVY_UNITTEST' not in environ and
|
||||
'KIVY_PACKAGING' not in environ and
|
||||
environ.get('KIVY_NO_ARGS', "false") not in ('true', '1', 'yes')):
|
||||
# save sys argv, otherwise, gstreamer use it and display help..
|
||||
sys_argv = sys.argv
|
||||
sys.argv = sys.argv[:1]
|
||||
|
||||
try:
|
||||
opts, args = getopt(sys_argv[1:], 'hp:fkawFem:sr:dc:', [
|
||||
'help', 'fullscreen', 'windowed', 'fps', 'event',
|
||||
'module=', 'save', 'fake-fullscreen', 'auto-fullscreen',
|
||||
'multiprocessing-fork', 'display=', 'size=', 'rotate=',
|
||||
'config=', 'debug', 'dpi='])
|
||||
|
||||
except GetoptError as err:
|
||||
Logger.error('Core: %s' % str(err))
|
||||
kivy_usage()
|
||||
sys.exit(2)
|
||||
|
||||
mp_fork = None
|
||||
try:
|
||||
for opt, arg in opts:
|
||||
if opt == '--multiprocessing-fork':
|
||||
mp_fork = True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
# set argv to the non-read args
|
||||
sys.argv = sys_argv[0:1] + args
|
||||
if mp_fork is not None:
|
||||
# Needs to be first opt for support_freeze to work
|
||||
sys.argv.insert(1, '--multiprocessing-fork')
|
||||
|
||||
else:
|
||||
opts = []
|
||||
args = []
|
||||
|
||||
need_save = False
|
||||
for opt, arg in opts:
|
||||
if opt in ('-h', '--help'):
|
||||
kivy_usage()
|
||||
sys.exit(0)
|
||||
elif opt in ('-p', '--provider'):
|
||||
try:
|
||||
pid, args = arg.split(':', 1)
|
||||
Config.set('input', pid, args)
|
||||
except ValueError:
|
||||
# when we are doing an executable on macosx with
|
||||
# pyinstaller, they are passing information with -p. so
|
||||
# it will conflict with our current -p option. since the
|
||||
# format is not the same, just avoid it.
|
||||
pass
|
||||
elif opt in ('-a', '--auto-fullscreen'):
|
||||
Config.set('graphics', 'fullscreen', 'auto')
|
||||
elif opt in ('-c', '--config'):
|
||||
ol = arg.split(':', 2)
|
||||
if len(ol) == 2:
|
||||
Config.set(ol[0], ol[1], '')
|
||||
elif len(ol) == 3:
|
||||
Config.set(ol[0], ol[1], ol[2])
|
||||
else:
|
||||
raise Exception('Invalid --config value')
|
||||
if ol[0] == 'kivy' and ol[1] == 'log_level':
|
||||
level = LOG_LEVELS.get(Config.get('kivy', 'log_level'))
|
||||
Logger.setLevel(level=level)
|
||||
elif opt in ('-k', '--fake-fullscreen'):
|
||||
Config.set('graphics', 'fullscreen', 'fake')
|
||||
elif opt in ('-f', '--fullscreen'):
|
||||
Config.set('graphics', 'fullscreen', '1')
|
||||
elif opt in ('-w', '--windowed'):
|
||||
Config.set('graphics', 'fullscreen', '0')
|
||||
elif opt in ('--size', ):
|
||||
w, h = str(arg).split('x')
|
||||
Config.set('graphics', 'width', w)
|
||||
Config.set('graphics', 'height', h)
|
||||
elif opt in ('--display', ):
|
||||
Config.set('graphics', 'display', str(arg))
|
||||
elif opt in ('-m', '--module'):
|
||||
if str(arg) == 'list':
|
||||
from kivy.modules import Modules
|
||||
Modules.usage_list()
|
||||
sys.exit(0)
|
||||
args = arg.split(':', 1)
|
||||
if len(args) == 1:
|
||||
args += ['']
|
||||
Config.set('modules', args[0], args[1])
|
||||
elif opt in ('-s', '--save'):
|
||||
need_save = True
|
||||
elif opt in ('-r', '--rotation'):
|
||||
Config.set('graphics', 'rotation', arg)
|
||||
elif opt in ('-d', '--debug'):
|
||||
level = LOG_LEVELS.get('debug')
|
||||
Logger.setLevel(level=level)
|
||||
elif opt == '--dpi':
|
||||
environ['KIVY_DPI'] = arg
|
||||
|
||||
if need_save and 'KIVY_NO_CONFIG' not in environ:
|
||||
try:
|
||||
with open(kivy_config_fn, 'w') as fd:
|
||||
Config.write(fd)
|
||||
except Exception as e:
|
||||
Logger.exception('Core: error while saving default'
|
||||
'configuration file:', str(e))
|
||||
Logger.info('Core: Kivy configuration saved.')
|
||||
sys.exit(0)
|
||||
|
||||
# configure all activated modules
|
||||
from kivy.modules import Modules
|
||||
Modules.configure()
|
||||
|
||||
# android hooks: force fullscreen and add android touch input provider
|
||||
if platform in ('android', 'ios'):
|
||||
from kivy.config import Config
|
||||
Config.set('graphics', 'fullscreen', 'auto')
|
||||
Config.remove_section('input')
|
||||
Config.add_section('input')
|
||||
|
||||
if platform == 'android':
|
||||
Config.set('input', 'androidtouch', 'android')
|
||||
|
||||
for msg in _logging_msgs:
|
||||
Logger.info(msg)
|
||||
|
||||
if not _KIVY_RELEASE and _kivy_git_hash and _kivy_build_date:
|
||||
Logger.info('Kivy: v%s, git-%s, %s' % (
|
||||
__version__, _kivy_git_hash[:7], _kivy_build_date))
|
||||
else:
|
||||
Logger.info('Kivy: v%s' % __version__)
|
||||
Logger.info('Kivy: Installed at "{}"'.format(__file__))
|
||||
Logger.info('Python: v{}'.format(sys.version))
|
||||
Logger.info('Python: Interpreter at "{}"'.format(sys.executable))
|
||||
|
||||
from kivy.logger import file_log_handler
|
||||
if file_log_handler is not None:
|
||||
file_log_handler.purge_logs()
|
||||
BIN
kivy/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/_version.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/_version.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/animation.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/animation.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/app.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/app.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/atlas.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/atlas.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/base.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/base.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/cache.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/cache.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/clock.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/clock.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/compat.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/compat.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/config.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/config.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/context.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/context.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/event.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/event.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/factory.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/factory.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/factory_registers.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/factory_registers.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/geometry.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/geometry.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/gesture.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/gesture.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/interactive.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/interactive.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/loader.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/loader.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/logger.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/logger.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/metrics.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/metrics.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/multistroke.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/multistroke.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/parser.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/parser.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/resources.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/resources.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/setupconfig.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/setupconfig.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/support.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/support.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/utils.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/utils.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/vector.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/vector.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/__pycache__/weakmethod.cpython-310.pyc
Normal file
BIN
kivy/__pycache__/weakmethod.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/_clock.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/_clock.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
131
kivy/_clock.pxd
Normal file
131
kivy/_clock.pxd
Normal file
@ -0,0 +1,131 @@
|
||||
|
||||
cdef class ClockEvent(object):
|
||||
|
||||
cdef int _is_triggered
|
||||
cdef public ClockEvent next
|
||||
'''The next :class:`ClockEvent` in order they were scheduled.
|
||||
'''
|
||||
cdef public ClockEvent prev
|
||||
'''The previous :class:`ClockEvent` in order they were scheduled.
|
||||
'''
|
||||
cdef public object cid
|
||||
cdef public CyClockBase clock
|
||||
'''The :class:`CyClockBase` instance associated with the event.
|
||||
'''
|
||||
cdef public int loop
|
||||
'''Whether this event repeats at intervals of :attr:`timeout`.
|
||||
'''
|
||||
cdef public object weak_callback
|
||||
cdef public object callback
|
||||
cdef public double timeout
|
||||
'''The duration after scheduling when the callback should be executed.
|
||||
'''
|
||||
cdef public double _last_dt
|
||||
cdef public double _dt
|
||||
cdef public list _del_queue
|
||||
|
||||
cdef public object clock_ended_callback
|
||||
"""A Optional callback for this event, which if provided is called by the clock
|
||||
when the clock is stopped and the event was not ticked.
|
||||
"""
|
||||
cdef public object weak_clock_ended_callback
|
||||
|
||||
cdef public int release_ref
|
||||
"""If True, the event should never release the reference to the callbacks.
|
||||
If False, a weakref may be created instead.
|
||||
"""
|
||||
|
||||
cpdef get_callback(self)
|
||||
cpdef get_clock_ended_callback(self)
|
||||
cpdef cancel(self)
|
||||
cpdef release(self)
|
||||
cpdef tick(self, double curtime)
|
||||
|
||||
|
||||
cdef class FreeClockEvent(ClockEvent):
|
||||
|
||||
cdef public int free
|
||||
'''Whether this event was scheduled as a free event.
|
||||
'''
|
||||
|
||||
|
||||
cdef class CyClockBase(object):
|
||||
|
||||
cdef public double _last_tick
|
||||
cdef public int max_iteration
|
||||
'''The maximum number of callback iterations at the end of the frame, before the next
|
||||
frame. If more iterations occur, a warning is issued.
|
||||
'''
|
||||
|
||||
cdef public double clock_resolution
|
||||
'''If the remaining time until the event timeout is less than :attr:`clock_resolution`,
|
||||
the clock will execute the callback even if it hasn't exactly timed out.
|
||||
|
||||
If -1, the default, the resolution will be computed from config's ``maxfps``.
|
||||
Otherwise, the provided value is used. Defaults to -1.
|
||||
'''
|
||||
|
||||
cdef public double _max_fps
|
||||
|
||||
cdef public ClockEvent _root_event
|
||||
'''The first event in the chain. Can be None.
|
||||
'''
|
||||
cdef public ClockEvent _next_event
|
||||
'''During frame processing when we service the events, this points to the next
|
||||
event that will be processed. After ticking an event, we continue with
|
||||
:attr:`_next_event`.
|
||||
|
||||
If a event that is canceled is the :attr:`_next_event`, :attr:`_next_event`
|
||||
is shifted to point to the after after this, or None if it's at the end of the
|
||||
chain.
|
||||
'''
|
||||
cdef public ClockEvent _cap_event
|
||||
'''The cap event is the last event in the chain for each frame.
|
||||
For a particular frame, events may be added dynamically after this event,
|
||||
and the current frame should not process them.
|
||||
|
||||
Similarly to :attr:`_next_event`,
|
||||
when canceling the :attr:`_cap_event`, :attr:`_cap_event` is shifted to the
|
||||
one previous to it.
|
||||
'''
|
||||
cdef public ClockEvent _last_event
|
||||
'''The last event in the chain. New events are added after this. Can be None.
|
||||
'''
|
||||
cdef public object _lock
|
||||
cdef public object _lock_acquire
|
||||
cdef public object _lock_release
|
||||
|
||||
cdef public int has_started
|
||||
cdef public int has_ended
|
||||
cdef public object _del_safe_lock
|
||||
cdef public int _del_safe_done
|
||||
|
||||
cpdef get_resolution(self)
|
||||
cpdef ClockEvent create_lifecycle_aware_trigger(
|
||||
self, callback, clock_ended_callback, timeout=*, interval=*, release_ref=*)
|
||||
cpdef ClockEvent create_trigger(self, callback, timeout=*, interval=*, release_ref=*)
|
||||
cpdef schedule_lifecycle_aware_del_safe(self, callback, clock_ended_callback)
|
||||
cpdef schedule_del_safe(self, callback)
|
||||
cpdef ClockEvent schedule_once(self, callback, timeout=*)
|
||||
cpdef ClockEvent schedule_interval(self, callback, timeout)
|
||||
cpdef unschedule(self, callback, all=*)
|
||||
cpdef _release_references(self)
|
||||
cpdef _process_del_safe_events(self)
|
||||
cpdef _process_events(self)
|
||||
cpdef _process_events_before_frame(self)
|
||||
cpdef get_min_timeout(self)
|
||||
cpdef get_events(self)
|
||||
cpdef get_before_frame_events(self)
|
||||
cpdef _process_clock_ended_del_safe_events(self)
|
||||
cpdef _process_clock_ended_callbacks(self)
|
||||
|
||||
|
||||
cdef class CyClockBaseFree(CyClockBase):
|
||||
|
||||
cpdef FreeClockEvent create_lifecycle_aware_trigger_free(
|
||||
self, callback, clock_ended_callback, timeout=*, interval=*, release_ref=*)
|
||||
cpdef FreeClockEvent create_trigger_free(self, callback, timeout=*, interval=*, release_ref=*)
|
||||
cpdef FreeClockEvent schedule_once_free(self, callback, timeout=*)
|
||||
cpdef FreeClockEvent schedule_interval_free(self, callback, timeout)
|
||||
cpdef _process_free_events(self, double last_tick)
|
||||
cpdef get_min_free_timeout(self)
|
||||
BIN
kivy/_event.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/_event.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
68
kivy/_event.pxd
Normal file
68
kivy/_event.pxd
Normal file
@ -0,0 +1,68 @@
|
||||
from cpython.ref cimport PyObject
|
||||
|
||||
cdef dict cache_properties_per_cls
|
||||
|
||||
cdef class ObjectWithUid(object):
|
||||
cdef readonly int uid
|
||||
|
||||
|
||||
cdef class Observable(ObjectWithUid):
|
||||
cdef object __fbind_mapping
|
||||
cdef object bound_uid
|
||||
|
||||
|
||||
cdef class EventDispatcher(ObjectWithUid):
|
||||
cdef dict __event_stack
|
||||
cdef dict __properties
|
||||
cdef dict __storage
|
||||
cdef object __weakref__
|
||||
cdef public set _kwargs_applied_init
|
||||
cdef public object _proxy_ref
|
||||
cpdef dict properties(self)
|
||||
|
||||
|
||||
cdef enum BoundLock:
|
||||
# the state of the BoundCallback, i.e. whether it can be deleted
|
||||
unlocked # whether the BoundCallback is unlocked and can be deleted
|
||||
locked # whether the BoundCallback is locked and cannot be deleted
|
||||
deleted # whether the locked BoundCallback was marked for deletion
|
||||
|
||||
cdef class BoundCallback:
|
||||
cdef object func
|
||||
cdef tuple largs
|
||||
cdef dict kwargs
|
||||
cdef int is_ref # if func is a ref to the function
|
||||
cdef BoundLock lock # see BoundLock
|
||||
cdef BoundCallback next # next callback in chain
|
||||
cdef BoundCallback prev # previous callback in chain
|
||||
cdef object uid # the uid given for this callback, None if not given
|
||||
cdef EventObservers observers
|
||||
|
||||
cdef void set_largs(self, tuple largs)
|
||||
|
||||
|
||||
cdef class EventObservers:
|
||||
# If dispatching should occur in normal or reverse order of binding.
|
||||
cdef int dispatch_reverse
|
||||
# If in dispatch, the value parameter is dispatched or ignored.
|
||||
cdef int dispatch_value
|
||||
# The first callback bound
|
||||
cdef BoundCallback first_callback
|
||||
# The last callback bound
|
||||
cdef BoundCallback last_callback
|
||||
# The uid to assign to the next bound callback.
|
||||
cdef object uid
|
||||
|
||||
cdef inline BoundCallback make_callback(self, object observer, tuple largs, dict kwargs, int is_ref, uid=*)
|
||||
cdef inline void bind(self, object observer, object src_observer, int is_ref) except *
|
||||
cdef inline object fbind(self, object observer, tuple largs, dict kwargs, int is_ref)
|
||||
cdef inline BoundCallback fbind_callback(self, object observer, tuple largs, dict kwargs, int is_ref)
|
||||
cdef inline void fbind_existing_callback(self, BoundCallback callback)
|
||||
cdef inline void unbind(self, object observer, int stop_on_first) except *
|
||||
cdef inline void funbind(self, object observer, tuple largs, dict kwargs) except *
|
||||
cdef inline object unbind_uid(self, object uid)
|
||||
cdef inline object unbind_callback(self, BoundCallback callback)
|
||||
cdef inline void remove_callback(self, BoundCallback callback, int force=*) except *
|
||||
cdef inline object _dispatch(
|
||||
self, object f, tuple slargs, dict skwargs, object obj, object value, tuple largs, dict kwargs)
|
||||
cdef inline int dispatch(self, object obj, object value, tuple largs, dict kwargs, int stop_on_true) except 2
|
||||
BIN
kivy/_metrics.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/_metrics.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
5
kivy/_metrics.pxd
Normal file
5
kivy/_metrics.pxd
Normal file
@ -0,0 +1,5 @@
|
||||
from kivy._event cimport EventObservers
|
||||
|
||||
cdef EventObservers pixel_scale_observers
|
||||
|
||||
cpdef float dpi2px(value, str ext) except *
|
||||
17
kivy/_version.py
Normal file
17
kivy/_version.py
Normal file
@ -0,0 +1,17 @@
|
||||
# This file is imported from __init__.py and exec'd from setup.py
|
||||
|
||||
MAJOR = 2
|
||||
MINOR = 1
|
||||
MICRO = 0
|
||||
RELEASE = True
|
||||
|
||||
__version__ = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
|
||||
|
||||
if not RELEASE:
|
||||
# if it's a rcx release, it's not proceeded by a period. If it is a
|
||||
# devx release, it must start with a period
|
||||
__version__ += ''
|
||||
|
||||
|
||||
_kivy_git_hash = '023bd79b90f9831b45bb8eb449346648aa5fe5f8'
|
||||
_kivy_build_date = '20220306'
|
||||
832
kivy/animation.py
Normal file
832
kivy/animation.py
Normal file
@ -0,0 +1,832 @@
|
||||
'''
|
||||
Animation
|
||||
=========
|
||||
|
||||
:class:`Animation` and :class:`AnimationTransition` are used to animate
|
||||
:class:`~kivy.uix.widget.Widget` properties. You must specify at least a
|
||||
property name and target value. To use an Animation, follow these steps:
|
||||
|
||||
* Setup an Animation object
|
||||
* Use the Animation object on a Widget
|
||||
|
||||
Simple animation
|
||||
----------------
|
||||
|
||||
To animate a Widget's x or y position, simply specify the target x/y values
|
||||
where you want the widget positioned at the end of the animation::
|
||||
|
||||
anim = Animation(x=100, y=100)
|
||||
anim.start(widget)
|
||||
|
||||
The animation will last for 1 second unless :attr:`duration` is specified.
|
||||
When anim.start() is called, the Widget will move smoothly from the current
|
||||
x/y position to (100, 100).
|
||||
|
||||
Multiple properties and transitions
|
||||
-----------------------------------
|
||||
|
||||
You can animate multiple properties and use built-in or custom transition
|
||||
functions using :attr:`transition` (or the `t=` shortcut). For example,
|
||||
to animate the position and size using the 'in_quad' transition::
|
||||
|
||||
anim = Animation(x=50, size=(80, 80), t='in_quad')
|
||||
anim.start(widget)
|
||||
|
||||
Note that the `t=` parameter can be the string name of a method in the
|
||||
:class:`AnimationTransition` class or your own animation function.
|
||||
|
||||
Sequential animation
|
||||
--------------------
|
||||
|
||||
To join animations sequentially, use the '+' operator. The following example
|
||||
will animate to x=50 over 1 second, then animate the size to (80, 80) over the
|
||||
next two seconds::
|
||||
|
||||
anim = Animation(x=50) + Animation(size=(80, 80), duration=2.)
|
||||
anim.start(widget)
|
||||
|
||||
Parallel animation
|
||||
------------------
|
||||
|
||||
To join animations in parallel, use the '&' operator. The following example
|
||||
will animate the position to (80, 10) over 1 second, whilst in parallel
|
||||
animating the size to (800, 800)::
|
||||
|
||||
anim = Animation(pos=(80, 10))
|
||||
anim &= Animation(size=(800, 800), duration=2.)
|
||||
anim.start(widget)
|
||||
|
||||
Keep in mind that creating overlapping animations on the same property may have
|
||||
unexpected results. If you want to apply multiple animations to the same
|
||||
property, you should either schedule them sequentially (via the '+' operator or
|
||||
using the *on_complete* callback) or cancel previous animations using the
|
||||
:attr:`~Animation.cancel_all` method.
|
||||
|
||||
Repeating animation
|
||||
-------------------
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
.. note::
|
||||
This is currently only implemented for 'Sequence' animations.
|
||||
|
||||
To set an animation to repeat, simply set the :attr:`Sequence.repeat`
|
||||
property to `True`::
|
||||
|
||||
anim = Animation(...) + Animation(...)
|
||||
anim.repeat = True
|
||||
anim.start(widget)
|
||||
|
||||
For flow control of animations such as stopping and cancelling, use the methods
|
||||
already in place in the animation module.
|
||||
'''
|
||||
|
||||
__all__ = ('Animation', 'AnimationTransition')
|
||||
|
||||
from math import sqrt, cos, sin, pi
|
||||
from collections import ChainMap
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.clock import Clock
|
||||
from kivy.compat import string_types, iterkeys
|
||||
from kivy.weakproxy import WeakProxy
|
||||
|
||||
|
||||
class Animation(EventDispatcher):
|
||||
'''Create an animation definition that can be used to animate a Widget.
|
||||
|
||||
:Parameters:
|
||||
`duration` or `d`: float, defaults to 1.
|
||||
Duration of the animation, in seconds.
|
||||
`transition` or `t`: str or func
|
||||
Transition function for animate properties. It can be the name of a
|
||||
method from :class:`AnimationTransition`.
|
||||
`step` or `s`: float
|
||||
Step in milliseconds of the animation. Defaults to 0, which means
|
||||
the animation is updated for every frame.
|
||||
|
||||
To update the animation less often, set the step value to a float.
|
||||
For example, if you want to animate at 30 FPS, use s=1/30.
|
||||
|
||||
:Events:
|
||||
`on_start`: animation, widget
|
||||
Fired when the animation is started on a widget.
|
||||
`on_complete`: animation, widget
|
||||
Fired when the animation is completed or stopped on a widget.
|
||||
`on_progress`: animation, widget, progression
|
||||
Fired when the progression of the animation is changing.
|
||||
|
||||
.. versionchanged:: 1.4.0
|
||||
Added s/step parameter.
|
||||
|
||||
.. versionchanged:: 1.10.0
|
||||
The default value of the step parameter was changed from 1/60. to 0.
|
||||
'''
|
||||
|
||||
_update_ev = None
|
||||
|
||||
_instances = set()
|
||||
|
||||
__events__ = ('on_start', 'on_progress', 'on_complete')
|
||||
|
||||
def __init__(self, **kw):
|
||||
super().__init__()
|
||||
# Initialize
|
||||
self._clock_installed = False
|
||||
self._duration = kw.pop('d', kw.pop('duration', 1.))
|
||||
self._transition = kw.pop('t', kw.pop('transition', 'linear'))
|
||||
self._step = kw.pop('s', kw.pop('step', 0))
|
||||
if isinstance(self._transition, string_types):
|
||||
self._transition = getattr(AnimationTransition, self._transition)
|
||||
self._animated_properties = kw
|
||||
self._widgets = {}
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
'''Return the duration of the animation.
|
||||
'''
|
||||
return self._duration
|
||||
|
||||
@property
|
||||
def transition(self):
|
||||
'''Return the transition of the animation.
|
||||
'''
|
||||
return self._transition
|
||||
|
||||
@property
|
||||
def animated_properties(self):
|
||||
'''Return the properties used to animate.
|
||||
'''
|
||||
return self._animated_properties
|
||||
|
||||
@staticmethod
|
||||
def stop_all(widget, *largs):
|
||||
'''Stop all animations that concern a specific widget / list of
|
||||
properties.
|
||||
|
||||
Example::
|
||||
|
||||
anim = Animation(x=50)
|
||||
anim.start(widget)
|
||||
|
||||
# and later
|
||||
Animation.stop_all(widget, 'x')
|
||||
'''
|
||||
if len(largs):
|
||||
for animation in list(Animation._instances):
|
||||
for x in largs:
|
||||
animation.stop_property(widget, x)
|
||||
else:
|
||||
for animation in set(Animation._instances):
|
||||
animation.stop(widget)
|
||||
|
||||
@staticmethod
|
||||
def cancel_all(widget, *largs):
|
||||
'''Cancel all animations that concern a specific widget / list of
|
||||
properties. See :attr:`cancel`.
|
||||
|
||||
Example::
|
||||
|
||||
anim = Animation(x=50)
|
||||
anim.start(widget)
|
||||
|
||||
# and later
|
||||
Animation.cancel_all(widget, 'x')
|
||||
|
||||
.. versionadded:: 1.4.0
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
If the parameter ``widget`` is None, all animated widgets will be
|
||||
the target and cancelled. If ``largs`` is also given, animation of
|
||||
these properties will be canceled for all animated widgets.
|
||||
'''
|
||||
if widget is None:
|
||||
if largs:
|
||||
for animation in Animation._instances.copy():
|
||||
for info in tuple(animation._widgets.values()):
|
||||
widget = info['widget']
|
||||
for x in largs:
|
||||
animation.cancel_property(widget, x)
|
||||
else:
|
||||
for animation in Animation._instances:
|
||||
animation._widgets.clear()
|
||||
animation._clock_uninstall()
|
||||
Animation._instances.clear()
|
||||
return
|
||||
if len(largs):
|
||||
for animation in list(Animation._instances):
|
||||
for x in largs:
|
||||
animation.cancel_property(widget, x)
|
||||
else:
|
||||
for animation in set(Animation._instances):
|
||||
animation.cancel(widget)
|
||||
|
||||
def start(self, widget):
|
||||
'''Start the animation on a widget.
|
||||
'''
|
||||
self.stop(widget)
|
||||
self._initialize(widget)
|
||||
self._register()
|
||||
self.dispatch('on_start', widget)
|
||||
|
||||
def stop(self, widget):
|
||||
'''Stop the animation previously applied to a widget, triggering the
|
||||
`on_complete` event.'''
|
||||
props = self._widgets.pop(widget.uid, None)
|
||||
if props:
|
||||
self.dispatch('on_complete', widget)
|
||||
self.cancel(widget)
|
||||
|
||||
def cancel(self, widget):
|
||||
'''Cancel the animation previously applied to a widget. Same
|
||||
effect as :attr:`stop`, except the `on_complete` event will
|
||||
*not* be triggered!
|
||||
|
||||
.. versionadded:: 1.4.0
|
||||
'''
|
||||
self._widgets.pop(widget.uid, None)
|
||||
self._clock_uninstall()
|
||||
if not self._widgets:
|
||||
self._unregister()
|
||||
|
||||
def stop_property(self, widget, prop):
|
||||
'''Even if an animation is running, remove a property. It will not be
|
||||
animated further. If it was the only/last property being animated,
|
||||
the animation will be stopped (see :attr:`stop`).
|
||||
'''
|
||||
props = self._widgets.get(widget.uid, None)
|
||||
if not props:
|
||||
return
|
||||
props['properties'].pop(prop, None)
|
||||
|
||||
# no more properties to animation ? kill the animation.
|
||||
if not props['properties']:
|
||||
self.stop(widget)
|
||||
|
||||
def cancel_property(self, widget, prop):
|
||||
'''Even if an animation is running, remove a property. It will not be
|
||||
animated further. If it was the only/last property being animated,
|
||||
the animation will be canceled (see :attr:`cancel`)
|
||||
|
||||
.. versionadded:: 1.4.0
|
||||
'''
|
||||
props = self._widgets.get(widget.uid, None)
|
||||
if not props:
|
||||
return
|
||||
props['properties'].pop(prop, None)
|
||||
|
||||
# no more properties to animation ? kill the animation.
|
||||
if not props['properties']:
|
||||
self.cancel(widget)
|
||||
|
||||
def have_properties_to_animate(self, widget):
|
||||
'''Return True if a widget still has properties to animate.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
'''
|
||||
props = self._widgets.get(widget.uid, None)
|
||||
if props and props['properties']:
|
||||
return True
|
||||
|
||||
#
|
||||
# Private
|
||||
#
|
||||
def _register(self):
|
||||
Animation._instances.add(self)
|
||||
|
||||
def _unregister(self):
|
||||
if self in Animation._instances:
|
||||
Animation._instances.remove(self)
|
||||
|
||||
def _initialize(self, widget):
|
||||
d = self._widgets[widget.uid] = {
|
||||
'widget': widget,
|
||||
'properties': {},
|
||||
'time': None}
|
||||
|
||||
# get current values
|
||||
p = d['properties']
|
||||
for key, value in self._animated_properties.items():
|
||||
original_value = getattr(widget, key)
|
||||
if isinstance(original_value, (tuple, list)):
|
||||
original_value = original_value[:]
|
||||
elif isinstance(original_value, dict):
|
||||
original_value = original_value.copy()
|
||||
p[key] = (original_value, value)
|
||||
|
||||
# install clock
|
||||
self._clock_install()
|
||||
|
||||
def _clock_install(self):
|
||||
if self._clock_installed:
|
||||
return
|
||||
self._update_ev = Clock.schedule_interval(self._update, self._step)
|
||||
self._clock_installed = True
|
||||
|
||||
def _clock_uninstall(self):
|
||||
if self._widgets or not self._clock_installed:
|
||||
return
|
||||
self._clock_installed = False
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = None
|
||||
|
||||
def _update(self, dt):
|
||||
widgets = self._widgets
|
||||
transition = self._transition
|
||||
calculate = self._calculate
|
||||
for uid in list(widgets.keys()):
|
||||
anim = widgets[uid]
|
||||
widget = anim['widget']
|
||||
|
||||
if isinstance(widget, WeakProxy) and not len(dir(widget)):
|
||||
# empty proxy, widget is gone. ref: #2458
|
||||
self._widgets.pop(uid, None)
|
||||
self._clock_uninstall()
|
||||
if not self._widgets:
|
||||
self._unregister()
|
||||
continue
|
||||
|
||||
if anim['time'] is None:
|
||||
anim['time'] = 0.
|
||||
else:
|
||||
anim['time'] += dt
|
||||
|
||||
# calculate progression
|
||||
if self._duration:
|
||||
progress = min(1., anim['time'] / self._duration)
|
||||
else:
|
||||
progress = 1
|
||||
t = transition(progress)
|
||||
|
||||
# apply progression on widget
|
||||
for key, values in anim['properties'].items():
|
||||
a, b = values
|
||||
value = calculate(a, b, t)
|
||||
setattr(widget, key, value)
|
||||
|
||||
self.dispatch('on_progress', widget, progress)
|
||||
|
||||
# time to stop ?
|
||||
if progress >= 1.:
|
||||
self.stop(widget)
|
||||
|
||||
def _calculate(self, a, b, t):
|
||||
_calculate = self._calculate
|
||||
if isinstance(a, list) or isinstance(a, tuple):
|
||||
if isinstance(a, list):
|
||||
tp = list
|
||||
else:
|
||||
tp = tuple
|
||||
return tp([_calculate(a[x], b[x], t) for x in range(len(a))])
|
||||
elif isinstance(a, dict):
|
||||
d = {}
|
||||
for x in iterkeys(a):
|
||||
if x not in b:
|
||||
# User requested to animate only part of the dict.
|
||||
# Copy the rest
|
||||
d[x] = a[x]
|
||||
else:
|
||||
d[x] = _calculate(a[x], b[x], t)
|
||||
return d
|
||||
else:
|
||||
return (a * (1. - t)) + (b * t)
|
||||
|
||||
#
|
||||
# Default handlers
|
||||
#
|
||||
def on_start(self, widget):
|
||||
pass
|
||||
|
||||
def on_progress(self, widget, progress):
|
||||
pass
|
||||
|
||||
def on_complete(self, widget):
|
||||
pass
|
||||
|
||||
def __add__(self, animation):
|
||||
return Sequence(self, animation)
|
||||
|
||||
def __and__(self, animation):
|
||||
return Parallel(self, animation)
|
||||
|
||||
|
||||
class CompoundAnimation(Animation):
|
||||
|
||||
def stop_property(self, widget, prop):
|
||||
self.anim1.stop_property(widget, prop)
|
||||
self.anim2.stop_property(widget, prop)
|
||||
if (not self.anim1.have_properties_to_animate(widget) and
|
||||
not self.anim2.have_properties_to_animate(widget)):
|
||||
self.stop(widget)
|
||||
|
||||
def cancel(self, widget):
|
||||
self.anim1.cancel(widget)
|
||||
self.anim2.cancel(widget)
|
||||
super().cancel(widget)
|
||||
|
||||
def cancel_property(self, widget, prop):
|
||||
'''Even if an animation is running, remove a property. It will not be
|
||||
animated further. If it was the only/last property being animated,
|
||||
the animation will be canceled (see :attr:`cancel`)
|
||||
|
||||
This method overrides `:class:kivy.animation.Animation`'s
|
||||
version, to cancel it on all animations of the Sequence.
|
||||
|
||||
.. versionadded:: 1.10.0
|
||||
'''
|
||||
self.anim1.cancel_property(widget, prop)
|
||||
self.anim2.cancel_property(widget, prop)
|
||||
if (not self.anim1.have_properties_to_animate(widget) and
|
||||
not self.anim2.have_properties_to_animate(widget)):
|
||||
self.cancel(widget)
|
||||
|
||||
def have_properties_to_animate(self, widget):
|
||||
return (self.anim1.have_properties_to_animate(widget) or
|
||||
self.anim2.have_properties_to_animate(widget))
|
||||
|
||||
@property
|
||||
def animated_properties(self):
|
||||
return ChainMap({},
|
||||
self.anim2.animated_properties,
|
||||
self.anim1.animated_properties)
|
||||
|
||||
@property
|
||||
def transition(self):
|
||||
# This property is impossible to implement
|
||||
raise AttributeError(
|
||||
"Can't lookup transition attribute of a CompoundAnimation")
|
||||
|
||||
|
||||
class Sequence(CompoundAnimation):
|
||||
|
||||
def __init__(self, anim1, anim2):
|
||||
super().__init__()
|
||||
|
||||
#: Repeat the sequence. See 'Repeating animation' in the header
|
||||
#: documentation.
|
||||
self.repeat = False
|
||||
|
||||
self.anim1 = anim1
|
||||
self.anim2 = anim2
|
||||
|
||||
self.anim1.bind(on_complete=self.on_anim1_complete,
|
||||
on_progress=self.on_anim1_progress)
|
||||
self.anim2.bind(on_complete=self.on_anim2_complete,
|
||||
on_progress=self.on_anim2_progress)
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
return self.anim1.duration + self.anim2.duration
|
||||
|
||||
def stop(self, widget):
|
||||
props = self._widgets.pop(widget.uid, None)
|
||||
self.anim1.stop(widget)
|
||||
self.anim2.stop(widget)
|
||||
if props:
|
||||
self.dispatch('on_complete', widget)
|
||||
super().cancel(widget)
|
||||
|
||||
def start(self, widget):
|
||||
self.stop(widget)
|
||||
self._widgets[widget.uid] = True
|
||||
self._register()
|
||||
self.dispatch('on_start', widget)
|
||||
self.anim1.start(widget)
|
||||
|
||||
def on_anim1_complete(self, instance, widget):
|
||||
if widget.uid not in self._widgets:
|
||||
return
|
||||
self.anim2.start(widget)
|
||||
|
||||
def on_anim1_progress(self, instance, widget, progress):
|
||||
self.dispatch('on_progress', widget, progress / 2.)
|
||||
|
||||
def on_anim2_complete(self, instance, widget):
|
||||
'''Repeating logic used with boolean variable "repeat".
|
||||
|
||||
.. versionadded:: 1.7.1
|
||||
'''
|
||||
if widget.uid not in self._widgets:
|
||||
return
|
||||
if self.repeat:
|
||||
self.anim1.start(widget)
|
||||
else:
|
||||
self.dispatch('on_complete', widget)
|
||||
self.cancel(widget)
|
||||
|
||||
def on_anim2_progress(self, instance, widget, progress):
|
||||
self.dispatch('on_progress', widget, .5 + progress / 2.)
|
||||
|
||||
|
||||
class Parallel(CompoundAnimation):
|
||||
|
||||
def __init__(self, anim1, anim2):
|
||||
super().__init__()
|
||||
self.anim1 = anim1
|
||||
self.anim2 = anim2
|
||||
|
||||
self.anim1.bind(on_complete=self.on_anim_complete)
|
||||
self.anim2.bind(on_complete=self.on_anim_complete)
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
return max(self.anim1.duration, self.anim2.duration)
|
||||
|
||||
def stop(self, widget):
|
||||
self.anim1.stop(widget)
|
||||
self.anim2.stop(widget)
|
||||
if self._widgets.pop(widget.uid, None):
|
||||
self.dispatch('on_complete', widget)
|
||||
super().cancel(widget)
|
||||
|
||||
def start(self, widget):
|
||||
self.stop(widget)
|
||||
self.anim1.start(widget)
|
||||
self.anim2.start(widget)
|
||||
self._widgets[widget.uid] = {'complete': 0}
|
||||
self._register()
|
||||
self.dispatch('on_start', widget)
|
||||
|
||||
def on_anim_complete(self, instance, widget):
|
||||
self._widgets[widget.uid]['complete'] += 1
|
||||
if self._widgets[widget.uid]['complete'] == 2:
|
||||
self.stop(widget)
|
||||
|
||||
|
||||
class AnimationTransition:
|
||||
'''Collection of animation functions to be used with the Animation object.
|
||||
Easing Functions ported to Kivy from the Clutter Project
|
||||
https://developer.gnome.org/clutter/stable/ClutterAlpha.html
|
||||
|
||||
The `progress` parameter in each animation function is in the range 0-1.
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def linear(progress):
|
||||
'''.. image:: images/anim_linear.png'''
|
||||
return progress
|
||||
|
||||
@staticmethod
|
||||
def in_quad(progress):
|
||||
'''.. image:: images/anim_in_quad.png
|
||||
'''
|
||||
return progress * progress
|
||||
|
||||
@staticmethod
|
||||
def out_quad(progress):
|
||||
'''.. image:: images/anim_out_quad.png
|
||||
'''
|
||||
return -1.0 * progress * (progress - 2.0)
|
||||
|
||||
@staticmethod
|
||||
def in_out_quad(progress):
|
||||
'''.. image:: images/anim_in_out_quad.png
|
||||
'''
|
||||
p = progress * 2
|
||||
if p < 1:
|
||||
return 0.5 * p * p
|
||||
p -= 1.0
|
||||
return -0.5 * (p * (p - 2.0) - 1.0)
|
||||
|
||||
@staticmethod
|
||||
def in_cubic(progress):
|
||||
'''.. image:: images/anim_in_cubic.png
|
||||
'''
|
||||
return progress * progress * progress
|
||||
|
||||
@staticmethod
|
||||
def out_cubic(progress):
|
||||
'''.. image:: images/anim_out_cubic.png
|
||||
'''
|
||||
p = progress - 1.0
|
||||
return p * p * p + 1.0
|
||||
|
||||
@staticmethod
|
||||
def in_out_cubic(progress):
|
||||
'''.. image:: images/anim_in_out_cubic.png
|
||||
'''
|
||||
p = progress * 2
|
||||
if p < 1:
|
||||
return 0.5 * p * p * p
|
||||
p -= 2
|
||||
return 0.5 * (p * p * p + 2.0)
|
||||
|
||||
@staticmethod
|
||||
def in_quart(progress):
|
||||
'''.. image:: images/anim_in_quart.png
|
||||
'''
|
||||
return progress * progress * progress * progress
|
||||
|
||||
@staticmethod
|
||||
def out_quart(progress):
|
||||
'''.. image:: images/anim_out_quart.png
|
||||
'''
|
||||
p = progress - 1.0
|
||||
return -1.0 * (p * p * p * p - 1.0)
|
||||
|
||||
@staticmethod
|
||||
def in_out_quart(progress):
|
||||
'''.. image:: images/anim_in_out_quart.png
|
||||
'''
|
||||
p = progress * 2
|
||||
if p < 1:
|
||||
return 0.5 * p * p * p * p
|
||||
p -= 2
|
||||
return -0.5 * (p * p * p * p - 2.0)
|
||||
|
||||
@staticmethod
|
||||
def in_quint(progress):
|
||||
'''.. image:: images/anim_in_quint.png
|
||||
'''
|
||||
return progress * progress * progress * progress * progress
|
||||
|
||||
@staticmethod
|
||||
def out_quint(progress):
|
||||
'''.. image:: images/anim_out_quint.png
|
||||
'''
|
||||
p = progress - 1.0
|
||||
return p * p * p * p * p + 1.0
|
||||
|
||||
@staticmethod
|
||||
def in_out_quint(progress):
|
||||
'''.. image:: images/anim_in_out_quint.png
|
||||
'''
|
||||
p = progress * 2
|
||||
if p < 1:
|
||||
return 0.5 * p * p * p * p * p
|
||||
p -= 2.0
|
||||
return 0.5 * (p * p * p * p * p + 2.0)
|
||||
|
||||
@staticmethod
|
||||
def in_sine(progress):
|
||||
'''.. image:: images/anim_in_sine.png
|
||||
'''
|
||||
return -1.0 * cos(progress * (pi / 2.0)) + 1.0
|
||||
|
||||
@staticmethod
|
||||
def out_sine(progress):
|
||||
'''.. image:: images/anim_out_sine.png
|
||||
'''
|
||||
return sin(progress * (pi / 2.0))
|
||||
|
||||
@staticmethod
|
||||
def in_out_sine(progress):
|
||||
'''.. image:: images/anim_in_out_sine.png
|
||||
'''
|
||||
return -0.5 * (cos(pi * progress) - 1.0)
|
||||
|
||||
@staticmethod
|
||||
def in_expo(progress):
|
||||
'''.. image:: images/anim_in_expo.png
|
||||
'''
|
||||
if progress == 0:
|
||||
return 0.0
|
||||
return pow(2, 10 * (progress - 1.0))
|
||||
|
||||
@staticmethod
|
||||
def out_expo(progress):
|
||||
'''.. image:: images/anim_out_expo.png
|
||||
'''
|
||||
if progress == 1.0:
|
||||
return 1.0
|
||||
return -pow(2, -10 * progress) + 1.0
|
||||
|
||||
@staticmethod
|
||||
def in_out_expo(progress):
|
||||
'''.. image:: images/anim_in_out_expo.png
|
||||
'''
|
||||
if progress == 0:
|
||||
return 0.0
|
||||
if progress == 1.:
|
||||
return 1.0
|
||||
p = progress * 2
|
||||
if p < 1:
|
||||
return 0.5 * pow(2, 10 * (p - 1.0))
|
||||
p -= 1.0
|
||||
return 0.5 * (-pow(2, -10 * p) + 2.0)
|
||||
|
||||
@staticmethod
|
||||
def in_circ(progress):
|
||||
'''.. image:: images/anim_in_circ.png
|
||||
'''
|
||||
return -1.0 * (sqrt(1.0 - progress * progress) - 1.0)
|
||||
|
||||
@staticmethod
|
||||
def out_circ(progress):
|
||||
'''.. image:: images/anim_out_circ.png
|
||||
'''
|
||||
p = progress - 1.0
|
||||
return sqrt(1.0 - p * p)
|
||||
|
||||
@staticmethod
|
||||
def in_out_circ(progress):
|
||||
'''.. image:: images/anim_in_out_circ.png
|
||||
'''
|
||||
p = progress * 2
|
||||
if p < 1:
|
||||
return -0.5 * (sqrt(1.0 - p * p) - 1.0)
|
||||
p -= 2.0
|
||||
return 0.5 * (sqrt(1.0 - p * p) + 1.0)
|
||||
|
||||
@staticmethod
|
||||
def in_elastic(progress):
|
||||
'''.. image:: images/anim_in_elastic.png
|
||||
'''
|
||||
p = .3
|
||||
s = p / 4.0
|
||||
q = progress
|
||||
if q == 1:
|
||||
return 1.0
|
||||
q -= 1.0
|
||||
return -(pow(2, 10 * q) * sin((q - s) * (2 * pi) / p))
|
||||
|
||||
@staticmethod
|
||||
def out_elastic(progress):
|
||||
'''.. image:: images/anim_out_elastic.png
|
||||
'''
|
||||
p = .3
|
||||
s = p / 4.0
|
||||
q = progress
|
||||
if q == 1:
|
||||
return 1.0
|
||||
return pow(2, -10 * q) * sin((q - s) * (2 * pi) / p) + 1.0
|
||||
|
||||
@staticmethod
|
||||
def in_out_elastic(progress):
|
||||
'''.. image:: images/anim_in_out_elastic.png
|
||||
'''
|
||||
p = .3 * 1.5
|
||||
s = p / 4.0
|
||||
q = progress * 2
|
||||
if q == 2:
|
||||
return 1.0
|
||||
if q < 1:
|
||||
q -= 1.0
|
||||
return -.5 * (pow(2, 10 * q) * sin((q - s) * (2.0 * pi) / p))
|
||||
else:
|
||||
q -= 1.0
|
||||
return pow(2, -10 * q) * sin((q - s) * (2.0 * pi) / p) * .5 + 1.0
|
||||
|
||||
@staticmethod
|
||||
def in_back(progress):
|
||||
'''.. image:: images/anim_in_back.png
|
||||
'''
|
||||
return progress * progress * ((1.70158 + 1.0) * progress - 1.70158)
|
||||
|
||||
@staticmethod
|
||||
def out_back(progress):
|
||||
'''.. image:: images/anim_out_back.png
|
||||
'''
|
||||
p = progress - 1.0
|
||||
return p * p * ((1.70158 + 1) * p + 1.70158) + 1.0
|
||||
|
||||
@staticmethod
|
||||
def in_out_back(progress):
|
||||
'''.. image:: images/anim_in_out_back.png
|
||||
'''
|
||||
p = progress * 2.
|
||||
s = 1.70158 * 1.525
|
||||
if p < 1:
|
||||
return 0.5 * (p * p * ((s + 1.0) * p - s))
|
||||
p -= 2.0
|
||||
return 0.5 * (p * p * ((s + 1.0) * p + s) + 2.0)
|
||||
|
||||
@staticmethod
|
||||
def _out_bounce_internal(t, d):
|
||||
p = t / d
|
||||
if p < (1.0 / 2.75):
|
||||
return 7.5625 * p * p
|
||||
elif p < (2.0 / 2.75):
|
||||
p -= (1.5 / 2.75)
|
||||
return 7.5625 * p * p + .75
|
||||
elif p < (2.5 / 2.75):
|
||||
p -= (2.25 / 2.75)
|
||||
return 7.5625 * p * p + .9375
|
||||
else:
|
||||
p -= (2.625 / 2.75)
|
||||
return 7.5625 * p * p + .984375
|
||||
|
||||
@staticmethod
|
||||
def _in_bounce_internal(t, d):
|
||||
return 1.0 - AnimationTransition._out_bounce_internal(d - t, d)
|
||||
|
||||
@staticmethod
|
||||
def in_bounce(progress):
|
||||
'''.. image:: images/anim_in_bounce.png
|
||||
'''
|
||||
return AnimationTransition._in_bounce_internal(progress, 1.)
|
||||
|
||||
@staticmethod
|
||||
def out_bounce(progress):
|
||||
'''.. image:: images/anim_out_bounce.png
|
||||
'''
|
||||
return AnimationTransition._out_bounce_internal(progress, 1.)
|
||||
|
||||
@staticmethod
|
||||
def in_out_bounce(progress):
|
||||
'''.. image:: images/anim_in_out_bounce.png
|
||||
'''
|
||||
p = progress * 2.
|
||||
if p < 1.:
|
||||
return AnimationTransition._in_bounce_internal(p, 1.) * .5
|
||||
return AnimationTransition._out_bounce_internal(p - 1., 1.) * .5 + .5
|
||||
1191
kivy/app.py
Normal file
1191
kivy/app.py
Normal file
File diff suppressed because it is too large
Load Diff
456
kivy/atlas.py
Normal file
456
kivy/atlas.py
Normal file
@ -0,0 +1,456 @@
|
||||
'''
|
||||
Atlas
|
||||
=====
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Atlas manages texture atlases: packing multiple textures into
|
||||
one. With it, you reduce the number of images loaded and speedup the
|
||||
application loading. This module contains both the Atlas class and command line
|
||||
processing for creating an atlas from a set of individual PNG files. The
|
||||
command line section requires the Pillow library, or the defunct Python Imaging
|
||||
Library (PIL), to be installed.
|
||||
|
||||
An Atlas is composed of 2 or more files:
|
||||
- a json file (.atlas) that contains the image file names and texture
|
||||
locations of the atlas.
|
||||
- one or multiple image files containing textures referenced by the .atlas
|
||||
file.
|
||||
|
||||
Definition of .atlas files
|
||||
--------------------------
|
||||
|
||||
A file with ``<basename>.atlas`` is a json file formatted like this::
|
||||
|
||||
{
|
||||
"<basename>-<index>.png": {
|
||||
"id1": [ <x>, <y>, <width>, <height> ],
|
||||
"id2": [ <x>, <y>, <width>, <height> ],
|
||||
# ...
|
||||
},
|
||||
# ...
|
||||
}
|
||||
|
||||
Example from the Kivy ``data/images/defaulttheme.atlas``::
|
||||
|
||||
{
|
||||
"defaulttheme-0.png": {
|
||||
"progressbar_background": [431, 224, 59, 24],
|
||||
"image-missing": [253, 344, 48, 48],
|
||||
"filechooser_selected": [1, 207, 118, 118],
|
||||
"bubble_btn": [83, 174, 32, 32],
|
||||
# ... and more ...
|
||||
}
|
||||
}
|
||||
|
||||
In this example, "defaulttheme-0.png" is a large image, with the pixels in the
|
||||
rectangle from (431, 224) to (431 + 59, 224 + 24) usable as
|
||||
``atlas://data/images/defaulttheme/progressbar_background`` in
|
||||
any image parameter.
|
||||
|
||||
How to create an Atlas
|
||||
----------------------
|
||||
|
||||
.. warning::
|
||||
|
||||
The atlas creation requires the Pillow library (or the defunct Imaging/PIL
|
||||
library). This requirement will be removed in the future when the Kivy core
|
||||
Image is able to support loading, blitting, and saving operations.
|
||||
|
||||
You can directly use this module to create atlas files with this command::
|
||||
|
||||
$ python -m kivy.atlas <basename> <size> <list of images...>
|
||||
|
||||
|
||||
Let's say you have a list of images that you want to put into an Atlas. The
|
||||
directory is named ``images`` with lots of 64x64 png files inside::
|
||||
|
||||
$ ls
|
||||
images
|
||||
$ cd images
|
||||
$ ls
|
||||
bubble.png bubble-red.png button.png button-down.png
|
||||
|
||||
You can combine all the png's into one and generate the atlas file with::
|
||||
|
||||
$ python -m kivy.atlas myatlas 256x256 *.png
|
||||
Atlas created at myatlas.atlas
|
||||
1 image has been created
|
||||
$ ls
|
||||
bubble.png bubble-red.png button.png button-down.png myatlas.atlas
|
||||
myatlas-0.png
|
||||
|
||||
As you can see, we get 2 new files: ``myatlas.atlas`` and ``myatlas-0.png``.
|
||||
``myatlas-0.png`` is a new 256x256 .png composed of all your images. If the
|
||||
size you specify is not large enough to fit all of the source images, more
|
||||
atlas images will be created as required e.g. ``myatlas-1.png``,
|
||||
``myatlas-2.png`` etc.
|
||||
|
||||
.. note::
|
||||
|
||||
When using this script, the ids referenced in the atlas are the base names
|
||||
of the images without the extension. So, if you are going to name a file
|
||||
``../images/button.png``, the id for this image will be ``button``.
|
||||
|
||||
If you need path information included, you should include ``use_path`` as
|
||||
follows::
|
||||
|
||||
$ python -m kivy.atlas -- --use_path myatlas 256 *.png
|
||||
|
||||
In which case the id for ``../images/button.png`` will be ``images_button``
|
||||
|
||||
|
||||
How to use an Atlas
|
||||
-------------------
|
||||
|
||||
Usually, you would specify the images by supplying the path::
|
||||
|
||||
a = Button(background_normal='images/button.png',
|
||||
background_down='images/button_down.png')
|
||||
|
||||
In our previous example, we have created the atlas containing both images and
|
||||
put them in ``images/myatlas.atlas``. You can use url notation to reference
|
||||
them::
|
||||
|
||||
a = Button(background_normal='atlas://images/myatlas/button',
|
||||
background_down='atlas://images/myatlas/button_down')
|
||||
|
||||
In other words, the path to the images is replaced by::
|
||||
|
||||
atlas://path/to/myatlas/id
|
||||
# will search for the ``path/to/myatlas.atlas`` and get the image ``id``
|
||||
|
||||
.. note::
|
||||
|
||||
In the atlas url, there is no need to add the ``.atlas`` extension. It will
|
||||
be automatically append to the filename.
|
||||
|
||||
Manual usage of the Atlas
|
||||
-------------------------
|
||||
|
||||
::
|
||||
|
||||
>>> from kivy.atlas import Atlas
|
||||
>>> atlas = Atlas('path/to/myatlas.atlas')
|
||||
>>> print(atlas.textures.keys())
|
||||
['bubble', 'bubble-red', 'button', 'button-down']
|
||||
>>> print(atlas['button'])
|
||||
<kivy.graphics.texture.TextureRegion object at 0x2404d10>
|
||||
'''
|
||||
|
||||
__all__ = ('Atlas', )
|
||||
|
||||
import json
|
||||
from os.path import basename, dirname, join, splitext
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.logger import Logger
|
||||
from kivy.properties import AliasProperty, DictProperty, ListProperty
|
||||
import os
|
||||
|
||||
|
||||
# late import to prevent recursion
|
||||
CoreImage = None
|
||||
|
||||
|
||||
class Atlas(EventDispatcher):
|
||||
'''Manage texture atlas. See module documentation for more information.
|
||||
'''
|
||||
|
||||
original_textures = ListProperty([])
|
||||
'''List of original atlas textures (which contain the :attr:`textures`).
|
||||
|
||||
:attr:`original_textures` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [].
|
||||
|
||||
.. versionadded:: 1.9.1
|
||||
'''
|
||||
|
||||
textures = DictProperty({})
|
||||
'''List of available textures within the atlas.
|
||||
|
||||
:attr:`textures` is a :class:`~kivy.properties.DictProperty` and defaults
|
||||
to {}.
|
||||
'''
|
||||
|
||||
def _get_filename(self):
|
||||
return self._filename
|
||||
|
||||
filename = AliasProperty(_get_filename, None)
|
||||
'''Filename of the current Atlas.
|
||||
|
||||
:attr:`filename` is an :class:`~kivy.properties.AliasProperty` and defaults
|
||||
to None.
|
||||
'''
|
||||
|
||||
def __init__(self, filename):
|
||||
self._filename = filename
|
||||
super(Atlas, self).__init__()
|
||||
self._load()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.textures[key]
|
||||
|
||||
def _load(self):
|
||||
# late import to prevent recursive import.
|
||||
global CoreImage
|
||||
if CoreImage is None:
|
||||
from kivy.core.image import Image as CoreImage
|
||||
|
||||
# must be a name finished by .atlas ?
|
||||
filename = self._filename
|
||||
assert(filename.endswith('.atlas'))
|
||||
filename = filename.replace('/', os.sep)
|
||||
|
||||
Logger.debug('Atlas: Load <%s>' % filename)
|
||||
with open(filename, 'r') as fd:
|
||||
meta = json.load(fd)
|
||||
|
||||
Logger.debug('Atlas: Need to load %d images' % len(meta))
|
||||
d = dirname(filename)
|
||||
textures = {}
|
||||
for subfilename, ids in meta.items():
|
||||
subfilename = join(d, subfilename)
|
||||
Logger.debug('Atlas: Load <%s>' % subfilename)
|
||||
|
||||
# load the image
|
||||
ci = CoreImage(subfilename)
|
||||
atlas_texture = ci.texture
|
||||
self.original_textures.append(atlas_texture)
|
||||
|
||||
# for all the uid, load the image, get the region, and put
|
||||
# it in our dict.
|
||||
for meta_id, meta_coords in ids.items():
|
||||
x, y, w, h = meta_coords
|
||||
textures[meta_id] = atlas_texture.get_region(*meta_coords)
|
||||
|
||||
self.textures = textures
|
||||
|
||||
@staticmethod
|
||||
def create(outname, filenames, size, padding=2, use_path=False):
|
||||
'''This method can be used to create an atlas manually from a set of
|
||||
images.
|
||||
|
||||
:Parameters:
|
||||
`outname`: str
|
||||
Basename to use for ``.atlas`` creation and ``-<idx>.png``
|
||||
associated images.
|
||||
`filenames`: list
|
||||
List of filenames to put in the atlas.
|
||||
`size`: int or list (width, height)
|
||||
Size of the atlas image. If the size is not large enough to
|
||||
fit all of the source images, more atlas images will created
|
||||
as required.
|
||||
`padding`: int, defaults to 2
|
||||
Padding to put around each image.
|
||||
|
||||
Be careful. If you're using a padding < 2, you might have
|
||||
issues with the borders of the images. Because of the OpenGL
|
||||
linearization, it might use the pixels of the adjacent image.
|
||||
|
||||
If you're using a padding >= 2, we'll automatically generate a
|
||||
"border" of 1px around your image. If you look at
|
||||
the result, don't be scared if the image inside is not
|
||||
exactly the same as yours :).
|
||||
|
||||
`use_path`: bool, defaults to False
|
||||
If True, the relative path of the source png
|
||||
file names will be included in the atlas ids rather
|
||||
that just in the file names. Leading dots and slashes will be
|
||||
excluded and all other slashes in the path will be replaced
|
||||
with underscores. For example, if `use_path` is False
|
||||
(the default) and the file name is
|
||||
``../data/tiles/green_grass.png``, the id will be
|
||||
``green_grass``. If `use_path` is True, it will be
|
||||
``data_tiles_green_grass``.
|
||||
|
||||
.. versionchanged:: 1.8.0
|
||||
Parameter use_path added
|
||||
'''
|
||||
# Thanks to
|
||||
# omnisaurusgames.com/2011/06/texture-atlas-generation-using-python/
|
||||
# for its initial implementation.
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
Logger.critical('Atlas: Imaging/PIL are missing')
|
||||
raise
|
||||
|
||||
if isinstance(size, (tuple, list)):
|
||||
size_w, size_h = list(map(int, size))
|
||||
else:
|
||||
size_w = size_h = int(size)
|
||||
|
||||
# open all of the images
|
||||
ims = list()
|
||||
for f in filenames:
|
||||
fp = open(f, 'rb')
|
||||
im = Image.open(fp)
|
||||
im.load()
|
||||
fp.close()
|
||||
ims.append((f, im))
|
||||
|
||||
# sort by image area
|
||||
ims = sorted(ims, key=lambda im: im[1].size[0] * im[1].size[1],
|
||||
reverse=True)
|
||||
|
||||
# free boxes are empty space in our output image set
|
||||
# the freebox tuple format is: outidx, x, y, w, h
|
||||
freeboxes = [(0, 0, 0, size_w, size_h)]
|
||||
numoutimages = 1
|
||||
|
||||
# full boxes are areas where we have placed images in the atlas
|
||||
# the full box tuple format is: image, outidx, x, y, w, h, filename
|
||||
fullboxes = []
|
||||
|
||||
# do the actual atlasing by sticking the largest images we can
|
||||
# have into the smallest valid free boxes
|
||||
for imageinfo in ims:
|
||||
im = imageinfo[1]
|
||||
imw, imh = im.size
|
||||
imw += padding
|
||||
imh += padding
|
||||
if imw > size_w or imh > size_h:
|
||||
Logger.error(
|
||||
'Atlas: image %s (%d by %d) is larger than the atlas size!'
|
||||
% (imageinfo[0], imw, imh))
|
||||
return
|
||||
|
||||
inserted = False
|
||||
while not inserted:
|
||||
for idx, fb in enumerate(freeboxes):
|
||||
# find the smallest free box that will contain this image
|
||||
if fb[3] >= imw and fb[4] >= imh:
|
||||
# we found a valid spot! Remove the current
|
||||
# freebox, and split the leftover space into (up to)
|
||||
# two new freeboxes
|
||||
del freeboxes[idx]
|
||||
if fb[3] > imw:
|
||||
freeboxes.append((
|
||||
fb[0], fb[1] + imw, fb[2],
|
||||
fb[3] - imw, imh))
|
||||
|
||||
if fb[4] > imh:
|
||||
freeboxes.append((
|
||||
fb[0], fb[1], fb[2] + imh,
|
||||
fb[3], fb[4] - imh))
|
||||
|
||||
# keep this sorted!
|
||||
freeboxes = sorted(freeboxes,
|
||||
key=lambda fb: fb[3] * fb[4])
|
||||
fullboxes.append((im,
|
||||
fb[0], fb[1] + padding,
|
||||
fb[2] + padding, imw - padding,
|
||||
imh - padding, imageinfo[0]))
|
||||
inserted = True
|
||||
break
|
||||
|
||||
if not inserted:
|
||||
# oh crap - there isn't room in any of our free
|
||||
# boxes, so we have to add a new output image
|
||||
freeboxes.append((numoutimages, 0, 0, size_w, size_h))
|
||||
numoutimages += 1
|
||||
|
||||
# now that we've figured out where everything goes, make the output
|
||||
# images and blit the source images to the appropriate locations
|
||||
Logger.info('Atlas: create an {0}x{1} rgba image'.format(size_w,
|
||||
size_h))
|
||||
outimages = [Image.new('RGBA', (size_w, size_h))
|
||||
for i in range(0, int(numoutimages))]
|
||||
for fb in fullboxes:
|
||||
x, y = fb[2], fb[3]
|
||||
out = outimages[fb[1]]
|
||||
out.paste(fb[0], (fb[2], fb[3]))
|
||||
w, h = fb[0].size
|
||||
if padding > 1:
|
||||
out.paste(fb[0].crop((0, 0, w, 1)), (x, y - 1))
|
||||
out.paste(fb[0].crop((0, h - 1, w, h)), (x, y + h))
|
||||
out.paste(fb[0].crop((0, 0, 1, h)), (x - 1, y))
|
||||
out.paste(fb[0].crop((w - 1, 0, w, h)), (x + w, y))
|
||||
|
||||
# save the output images
|
||||
for idx, outimage in enumerate(outimages):
|
||||
outimage.save('%s-%d.png' % (outname, idx))
|
||||
|
||||
# write out an json file that says where everything ended up
|
||||
meta = {}
|
||||
for fb in fullboxes:
|
||||
fn = '%s-%d.png' % (basename(outname), fb[1])
|
||||
if fn not in meta:
|
||||
d = meta[fn] = {}
|
||||
else:
|
||||
d = meta[fn]
|
||||
|
||||
# fb[6] contain the filename
|
||||
if use_path:
|
||||
# use the path with separators replaced by _
|
||||
# example '../data/tiles/green_grass.png' becomes
|
||||
# 'data_tiles_green_grass'
|
||||
uid = splitext(fb[6])[0]
|
||||
# remove leading dots and slashes
|
||||
uid = uid.lstrip('./\\')
|
||||
# replace remaining slashes with _
|
||||
uid = uid.replace('/', '_').replace('\\', '_')
|
||||
else:
|
||||
# for example, '../data/tiles/green_grass.png'
|
||||
# just get only 'green_grass' as the uniq id.
|
||||
uid = splitext(basename(fb[6]))[0]
|
||||
|
||||
x, y, w, h = fb[2:6]
|
||||
d[uid] = x, size_h - y - h, w, h
|
||||
|
||||
outfn = '%s.atlas' % outname
|
||||
with open(outfn, 'w') as fd:
|
||||
json.dump(meta, fd)
|
||||
|
||||
return outfn, meta
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
""" Main line program. Process command line arguments
|
||||
to make a new atlas. """
|
||||
|
||||
import sys
|
||||
from glob import glob
|
||||
argv = sys.argv[1:]
|
||||
# earlier import of kivy has already called getopt to remove kivy system
|
||||
# arguments from this line. That is all arguments up to the first '--'
|
||||
if len(argv) < 3:
|
||||
print('Usage: python -m kivy.atlas [-- [--use-path] '
|
||||
'[--padding=2]] <outname> '
|
||||
'<size|512x256> <img1.png> [<img2.png>, ...]')
|
||||
sys.exit(1)
|
||||
|
||||
options = {'use_path': False}
|
||||
while True:
|
||||
option = argv[0]
|
||||
if option == '--use-path':
|
||||
options['use_path'] = True
|
||||
elif option.startswith('--padding='):
|
||||
options['padding'] = int(option.split('=', 1)[-1])
|
||||
elif option[:2] == '--':
|
||||
print('Unknown option {}'.format(option))
|
||||
sys.exit(1)
|
||||
else:
|
||||
break
|
||||
argv = argv[1:]
|
||||
|
||||
outname = argv[0]
|
||||
try:
|
||||
if 'x' in argv[1]:
|
||||
size = list(map(int, argv[1].split('x', 1)))
|
||||
else:
|
||||
size = int(argv[1])
|
||||
except ValueError:
|
||||
print('Error: size must be an integer or <integer>x<integer>')
|
||||
sys.exit(1)
|
||||
|
||||
filenames = [fname for fnames in argv[2:] for fname in glob(fnames)]
|
||||
ret = Atlas.create(outname, filenames, size, **options)
|
||||
if not ret:
|
||||
print('Error while creating atlas!')
|
||||
sys.exit(1)
|
||||
|
||||
fn, meta = ret
|
||||
print('Atlas created at', fn)
|
||||
print('%d image%s been created' % (len(meta),
|
||||
's have' if len(meta) > 1 else ' has'))
|
||||
617
kivy/base.py
Normal file
617
kivy/base.py
Normal file
@ -0,0 +1,617 @@
|
||||
# pylint: disable=W0611
|
||||
'''
|
||||
Kivy Base
|
||||
=========
|
||||
|
||||
This module contains the Kivy core functionality and is not intended for end
|
||||
users. Feel free to look through it, but bare in mind that calling any of
|
||||
these methods directly may result in an unpredictable behavior as the calls
|
||||
access directly the event loop of an application.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'EventLoop',
|
||||
'EventLoopBase',
|
||||
'ExceptionHandler',
|
||||
'ExceptionManagerBase',
|
||||
'ExceptionManager',
|
||||
'runTouchApp',
|
||||
'async_runTouchApp',
|
||||
'stopTouchApp',
|
||||
)
|
||||
|
||||
import sys
|
||||
import os
|
||||
from kivy.config import Config
|
||||
from kivy.logger import Logger
|
||||
from kivy.utils import platform
|
||||
from kivy.clock import Clock
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.lang import Builder
|
||||
from kivy.context import register_context
|
||||
|
||||
# private vars
|
||||
EventLoop = None
|
||||
|
||||
|
||||
class ExceptionHandler(object):
|
||||
'''Base handler that catches exceptions in :func:`runTouchApp`.
|
||||
You can subclass and extend it as follows::
|
||||
|
||||
class E(ExceptionHandler):
|
||||
def handle_exception(self, inst):
|
||||
Logger.exception('Exception caught by ExceptionHandler')
|
||||
return ExceptionManager.PASS
|
||||
|
||||
ExceptionManager.add_handler(E())
|
||||
|
||||
Then, all exceptions will be set to PASS, and logged to the console!
|
||||
'''
|
||||
|
||||
def handle_exception(self, exception):
|
||||
'''Called by :class:`ExceptionManagerBase` to handle a exception.
|
||||
|
||||
Defaults to returning :attr:`ExceptionManager.RAISE` that re-raises the
|
||||
exception. Return :attr:`ExceptionManager.PASS` to indicate that the
|
||||
exception was handled and should be ignored.
|
||||
|
||||
This may be called multiple times with the same exception, if
|
||||
:attr:`ExceptionManager.RAISE` is returned as the exception bubbles
|
||||
through multiple kivy exception handling levels.
|
||||
'''
|
||||
return ExceptionManager.RAISE
|
||||
|
||||
|
||||
class ExceptionManagerBase:
|
||||
'''ExceptionManager manages exceptions handlers.'''
|
||||
|
||||
RAISE = 0
|
||||
"""The exception should be re-raised.
|
||||
"""
|
||||
PASS = 1
|
||||
"""The exception should be ignored as it was handled by the handler.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = []
|
||||
self.policy = ExceptionManagerBase.RAISE
|
||||
|
||||
def add_handler(self, cls):
|
||||
'''Add a new exception handler to the stack.'''
|
||||
if cls not in self.handlers:
|
||||
self.handlers.append(cls)
|
||||
|
||||
def remove_handler(self, cls):
|
||||
'''Remove the exception handler from the stack.'''
|
||||
if cls in self.handlers:
|
||||
self.handlers.remove(cls)
|
||||
|
||||
def handle_exception(self, inst):
|
||||
'''Called when an exception occurred in the :func:`runTouchApp`
|
||||
main loop.'''
|
||||
ret = self.policy
|
||||
for handler in self.handlers:
|
||||
r = handler.handle_exception(inst)
|
||||
if r == ExceptionManagerBase.PASS:
|
||||
ret = r
|
||||
return ret
|
||||
|
||||
|
||||
#: Instance of a :class:`ExceptionManagerBase` implementation.
|
||||
ExceptionManager: ExceptionManagerBase = register_context(
|
||||
'ExceptionManager', ExceptionManagerBase)
|
||||
"""The :class:`ExceptionManagerBase` instance that handles kivy exceptions.
|
||||
"""
|
||||
|
||||
|
||||
class EventLoopBase(EventDispatcher):
|
||||
'''Main event loop. This loop handles the updating of input and
|
||||
dispatching events.
|
||||
'''
|
||||
|
||||
__events__ = ('on_start', 'on_pause', 'on_stop')
|
||||
|
||||
def __init__(self):
|
||||
super(EventLoopBase, self).__init__()
|
||||
self.quit = False
|
||||
self.input_events = []
|
||||
self.postproc_modules = []
|
||||
self.status = 'idle'
|
||||
self.stopping = False
|
||||
self.input_providers = []
|
||||
self.input_providers_autoremove = []
|
||||
self.event_listeners = []
|
||||
self.window = None
|
||||
self.me_list = []
|
||||
|
||||
@property
|
||||
def touches(self):
|
||||
'''Return the list of all touches currently in down or move states.
|
||||
'''
|
||||
return self.me_list
|
||||
|
||||
def ensure_window(self):
|
||||
'''Ensure that we have a window.
|
||||
'''
|
||||
import kivy.core.window # NOQA
|
||||
if not self.window:
|
||||
Logger.critical('App: Unable to get a Window, abort.')
|
||||
sys.exit(1)
|
||||
|
||||
def set_window(self, window):
|
||||
'''Set the window used for the event loop.
|
||||
'''
|
||||
self.window = window
|
||||
|
||||
def add_input_provider(self, provider, auto_remove=False):
|
||||
'''Add a new input provider to listen for touch events.
|
||||
'''
|
||||
if provider not in self.input_providers:
|
||||
self.input_providers.append(provider)
|
||||
if auto_remove:
|
||||
self.input_providers_autoremove.append(provider)
|
||||
|
||||
def remove_input_provider(self, provider):
|
||||
'''Remove an input provider.
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
Provider will be also removed if it exist in auto-remove list.
|
||||
'''
|
||||
if provider in self.input_providers:
|
||||
self.input_providers.remove(provider)
|
||||
if provider in self.input_providers_autoremove:
|
||||
self.input_providers_autoremove.remove(provider)
|
||||
|
||||
def add_event_listener(self, listener):
|
||||
'''Add a new event listener for getting touch events.
|
||||
'''
|
||||
if listener not in self.event_listeners:
|
||||
self.event_listeners.append(listener)
|
||||
|
||||
def remove_event_listener(self, listener):
|
||||
'''Remove an event listener from the list.
|
||||
'''
|
||||
if listener in self.event_listeners:
|
||||
self.event_listeners.remove(listener)
|
||||
|
||||
def start(self):
|
||||
'''Must be called before :meth:`EventLoopBase.run()`. This starts all
|
||||
configured input providers.
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
Method can be called multiple times, but event loop will start only
|
||||
once.
|
||||
'''
|
||||
if self.status == 'started':
|
||||
return
|
||||
self.status = 'started'
|
||||
self.quit = False
|
||||
Clock.start_clock()
|
||||
for provider in self.input_providers:
|
||||
provider.start()
|
||||
self.dispatch('on_start')
|
||||
|
||||
def close(self):
|
||||
'''Exit from the main loop and stop all configured
|
||||
input providers.'''
|
||||
self.quit = True
|
||||
self.stop()
|
||||
self.status = 'closed'
|
||||
|
||||
def stop(self):
|
||||
'''Stop all input providers and call callbacks registered using
|
||||
`EventLoop.add_stop_callback()`.
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
Method can be called multiple times, but event loop will stop only
|
||||
once.
|
||||
'''
|
||||
if self.status != 'started':
|
||||
return
|
||||
# XXX stop in reverse order that we started them!! (like push
|
||||
# pop), very important because e.g. wm_touch and WM_PEN both
|
||||
# store old window proc and the restore, if order is messed big
|
||||
# problem happens, crashing badly without error
|
||||
for provider in reversed(self.input_providers[:]):
|
||||
provider.stop()
|
||||
self.remove_input_provider(provider)
|
||||
|
||||
# ensure any restart will not break anything later.
|
||||
self.input_events = []
|
||||
|
||||
Clock.stop_clock()
|
||||
self.stopping = False
|
||||
self.status = 'stopped'
|
||||
self.dispatch('on_stop')
|
||||
|
||||
def add_postproc_module(self, mod):
|
||||
'''Add a postproc input module (DoubleTap, TripleTap, DeJitter
|
||||
RetainTouch are defaults).'''
|
||||
if mod not in self.postproc_modules:
|
||||
self.postproc_modules.append(mod)
|
||||
|
||||
def remove_postproc_module(self, mod):
|
||||
'''Remove a postproc module.'''
|
||||
if mod in self.postproc_modules:
|
||||
self.postproc_modules.remove(mod)
|
||||
|
||||
def remove_android_splash(self, *args):
|
||||
'''Remove android presplash in SDL2 bootstrap.'''
|
||||
try:
|
||||
from android import remove_presplash
|
||||
remove_presplash()
|
||||
except ImportError:
|
||||
Logger.warning(
|
||||
'Base: Failed to import "android" module. '
|
||||
'Could not remove android presplash.')
|
||||
return
|
||||
|
||||
def post_dispatch_input(self, etype, me):
|
||||
'''This function is called by :meth:`EventLoopBase.dispatch_input()`
|
||||
when we want to dispatch an input event. The event is dispatched to
|
||||
all listeners and if grabbed, it's dispatched to grabbed widgets.
|
||||
'''
|
||||
# update available list
|
||||
if etype == 'begin':
|
||||
self.me_list.append(me)
|
||||
elif etype == 'end':
|
||||
if me in self.me_list:
|
||||
self.me_list.remove(me)
|
||||
# dispatch to listeners
|
||||
if not me.grab_exclusive_class:
|
||||
for listener in self.event_listeners:
|
||||
listener.dispatch('on_motion', etype, me)
|
||||
# dispatch grabbed touch
|
||||
if not me.is_touch:
|
||||
# Non-touch event must be handled by the event manager
|
||||
return
|
||||
me.grab_state = True
|
||||
for weak_widget in me.grab_list[:]:
|
||||
# weak_widget is a weak reference to widget
|
||||
wid = weak_widget()
|
||||
if wid is None:
|
||||
# object is gone, stop.
|
||||
me.grab_list.remove(weak_widget)
|
||||
continue
|
||||
root_window = wid.get_root_window()
|
||||
if wid != root_window and root_window is not None:
|
||||
me.push()
|
||||
try:
|
||||
root_window.transform_motion_event_2d(me, wid)
|
||||
except AttributeError:
|
||||
me.pop()
|
||||
continue
|
||||
me.grab_current = wid
|
||||
wid._context.push()
|
||||
if etype == 'begin':
|
||||
# don't dispatch again touch in on_touch_down
|
||||
# a down event are nearly uniq here.
|
||||
# wid.dispatch('on_touch_down', touch)
|
||||
pass
|
||||
elif etype == 'update':
|
||||
if wid._context.sandbox:
|
||||
with wid._context.sandbox:
|
||||
wid.dispatch('on_touch_move', me)
|
||||
else:
|
||||
wid.dispatch('on_touch_move', me)
|
||||
elif etype == 'end':
|
||||
if wid._context.sandbox:
|
||||
with wid._context.sandbox:
|
||||
wid.dispatch('on_touch_up', me)
|
||||
else:
|
||||
wid.dispatch('on_touch_up', me)
|
||||
wid._context.pop()
|
||||
me.grab_current = None
|
||||
if wid != root_window and root_window is not None:
|
||||
me.pop()
|
||||
me.grab_state = False
|
||||
me.dispatch_done()
|
||||
|
||||
def _dispatch_input(self, *ev):
|
||||
# remove the save event for the touch if exist
|
||||
if ev in self.input_events:
|
||||
self.input_events.remove(ev)
|
||||
self.input_events.append(ev)
|
||||
|
||||
def dispatch_input(self):
|
||||
'''Called by :meth:`EventLoopBase.idle()` to read events from input
|
||||
providers, pass events to postproc, and dispatch final events.
|
||||
'''
|
||||
|
||||
# first, acquire input events
|
||||
for provider in self.input_providers:
|
||||
provider.update(dispatch_fn=self._dispatch_input)
|
||||
|
||||
# execute post-processing modules
|
||||
for mod in self.postproc_modules:
|
||||
self.input_events = mod.process(events=self.input_events)
|
||||
|
||||
# real dispatch input
|
||||
input_events = self.input_events
|
||||
pop = input_events.pop
|
||||
post_dispatch_input = self.post_dispatch_input
|
||||
while input_events:
|
||||
post_dispatch_input(*pop(0))
|
||||
|
||||
def mainloop(self):
|
||||
while not self.quit and self.status == 'started':
|
||||
try:
|
||||
self.idle()
|
||||
if self.window:
|
||||
self.window.mainloop()
|
||||
except BaseException as inst:
|
||||
# use exception manager first
|
||||
r = ExceptionManager.handle_exception(inst)
|
||||
if r == ExceptionManager.RAISE:
|
||||
stopTouchApp()
|
||||
raise
|
||||
else:
|
||||
pass
|
||||
|
||||
async def async_mainloop(self):
|
||||
while not self.quit and self.status == 'started':
|
||||
try:
|
||||
await self.async_idle()
|
||||
if self.window:
|
||||
self.window.mainloop()
|
||||
except BaseException as inst:
|
||||
# use exception manager first
|
||||
r = ExceptionManager.handle_exception(inst)
|
||||
if r == ExceptionManager.RAISE:
|
||||
stopTouchApp()
|
||||
raise
|
||||
else:
|
||||
pass
|
||||
|
||||
Logger.info("Window: exiting mainloop and closing.")
|
||||
self.close()
|
||||
|
||||
def idle(self):
|
||||
'''This function is called after every frame. By default:
|
||||
|
||||
* it "ticks" the clock to the next frame.
|
||||
* it reads all input and dispatches events.
|
||||
* it dispatches `on_update`, `on_draw` and `on_flip` events to the
|
||||
window.
|
||||
'''
|
||||
|
||||
# update dt
|
||||
Clock.tick()
|
||||
|
||||
# read and dispatch input from providers
|
||||
if not self.quit:
|
||||
self.dispatch_input()
|
||||
|
||||
# flush all the canvas operation
|
||||
if not self.quit:
|
||||
Builder.sync()
|
||||
|
||||
# tick before draw
|
||||
if not self.quit:
|
||||
Clock.tick_draw()
|
||||
|
||||
# flush all the canvas operation
|
||||
if not self.quit:
|
||||
Builder.sync()
|
||||
|
||||
if not self.quit:
|
||||
window = self.window
|
||||
if window and window.canvas.needs_redraw:
|
||||
window.dispatch('on_draw')
|
||||
window.dispatch('on_flip')
|
||||
|
||||
# don't loop if we don't have listeners !
|
||||
if len(self.event_listeners) == 0:
|
||||
Logger.error('Base: No event listeners have been created')
|
||||
Logger.error('Base: Application will leave')
|
||||
self.exit()
|
||||
return False
|
||||
|
||||
return self.quit
|
||||
|
||||
async def async_idle(self):
|
||||
'''Identical to :meth:`idle`, but instead used when running
|
||||
within an async event loop.
|
||||
'''
|
||||
|
||||
# update dt
|
||||
await Clock.async_tick()
|
||||
|
||||
# read and dispatch input from providers
|
||||
if not self.quit:
|
||||
self.dispatch_input()
|
||||
|
||||
# flush all the canvas operation
|
||||
if not self.quit:
|
||||
Builder.sync()
|
||||
|
||||
# tick before draw
|
||||
if not self.quit:
|
||||
Clock.tick_draw()
|
||||
|
||||
# flush all the canvas operation
|
||||
if not self.quit:
|
||||
Builder.sync()
|
||||
|
||||
if not self.quit:
|
||||
window = self.window
|
||||
if window and window.canvas.needs_redraw:
|
||||
window.dispatch('on_draw')
|
||||
window.dispatch('on_flip')
|
||||
|
||||
# don't loop if we don't have listeners !
|
||||
if len(self.event_listeners) == 0:
|
||||
Logger.error('Base: No event listeners have been created')
|
||||
Logger.error('Base: Application will leave')
|
||||
self.exit()
|
||||
return False
|
||||
|
||||
return self.quit
|
||||
|
||||
def run(self):
|
||||
'''Main loop'''
|
||||
while not self.quit:
|
||||
self.idle()
|
||||
self.exit()
|
||||
|
||||
def exit(self):
|
||||
'''Close the main loop and close the window.'''
|
||||
self.close()
|
||||
if self.window:
|
||||
self.window.close()
|
||||
|
||||
def on_stop(self):
|
||||
'''Event handler for `on_stop` events which will be fired right
|
||||
after all input providers have been stopped.'''
|
||||
pass
|
||||
|
||||
def on_pause(self):
|
||||
'''Event handler for `on_pause` which will be fired when
|
||||
the event loop is paused.'''
|
||||
pass
|
||||
|
||||
def on_start(self):
|
||||
'''Event handler for `on_start` which will be fired right
|
||||
after all input providers have been started.'''
|
||||
pass
|
||||
|
||||
|
||||
#: EventLoop instance
|
||||
EventLoop = EventLoopBase()
|
||||
|
||||
|
||||
def _runTouchApp_prepare(widget=None):
|
||||
from kivy.input import MotionEventFactory, kivy_postproc_modules
|
||||
|
||||
# Ok, we got one widget, and we are not in embedded mode
|
||||
# so, user don't create the window, let's create it for him !
|
||||
if widget:
|
||||
EventLoop.ensure_window()
|
||||
|
||||
# Instance all configured input
|
||||
for key, value in Config.items('input'):
|
||||
Logger.debug('Base: Create provider from %s' % (str(value)))
|
||||
|
||||
# split value
|
||||
args = str(value).split(',', 1)
|
||||
if len(args) == 1:
|
||||
args.append('')
|
||||
provider_id, args = args
|
||||
provider = MotionEventFactory.get(provider_id)
|
||||
if provider is None:
|
||||
Logger.warning('Base: Unknown <%s> provider' % str(provider_id))
|
||||
continue
|
||||
|
||||
# create provider
|
||||
p = provider(key, args)
|
||||
if p:
|
||||
EventLoop.add_input_provider(p, True)
|
||||
|
||||
# add postproc modules
|
||||
for mod in list(kivy_postproc_modules.values()):
|
||||
EventLoop.add_postproc_module(mod)
|
||||
|
||||
# add main widget
|
||||
if widget and EventLoop.window:
|
||||
if widget not in EventLoop.window.children:
|
||||
EventLoop.window.add_widget(widget)
|
||||
|
||||
# start event loop
|
||||
Logger.info('Base: Start application main loop')
|
||||
EventLoop.start()
|
||||
|
||||
# remove presplash on the next frame
|
||||
if platform == 'android':
|
||||
Clock.schedule_once(EventLoop.remove_android_splash)
|
||||
|
||||
# in non-embedded mode, there are 2 issues
|
||||
#
|
||||
# 1. if user created a window, call the mainloop from window.
|
||||
# This is due to glut, it need to be called with
|
||||
# glutMainLoop(). Only FreeGLUT got a gluMainLoopEvent().
|
||||
# So, we are executing the dispatching function inside
|
||||
# a redisplay event.
|
||||
#
|
||||
# 2. if no window is created, we are dispatching event loop
|
||||
# ourself (previous behavior.)
|
||||
#
|
||||
|
||||
|
||||
def runTouchApp(widget=None, embedded=False):
|
||||
'''Static main function that starts the application loop.
|
||||
You can access some magic via the following arguments:
|
||||
|
||||
See :mod:`kivy.app` for example usage.
|
||||
|
||||
:Parameters:
|
||||
`<empty>`
|
||||
To make dispatching work, you need at least one
|
||||
input listener. If not, application will leave.
|
||||
(MTWindow act as an input listener)
|
||||
|
||||
`widget`
|
||||
If you pass only a widget, a MTWindow will be created
|
||||
and your widget will be added to the window as the root
|
||||
widget.
|
||||
|
||||
`embedded`
|
||||
No event dispatching is done. This will be your job.
|
||||
|
||||
`widget + embedded`
|
||||
No event dispatching is done. This will be your job but
|
||||
we try to get the window (must be created by you beforehand)
|
||||
and add the widget to it. Very useful for embedding Kivy
|
||||
in another toolkit. (like Qt, check kivy-designed)
|
||||
|
||||
'''
|
||||
_runTouchApp_prepare(widget=widget)
|
||||
|
||||
# we are in embedded mode, don't do dispatching.
|
||||
if embedded:
|
||||
return
|
||||
|
||||
try:
|
||||
EventLoop.mainloop()
|
||||
finally:
|
||||
stopTouchApp()
|
||||
|
||||
|
||||
async def async_runTouchApp(widget=None, embedded=False, async_lib=None):
|
||||
'''Identical to :func:`runTouchApp` but instead it is a coroutine
|
||||
that can be run in an existing async event loop.
|
||||
|
||||
``async_lib`` is the async library to use. See :mod:`kivy.app` for details
|
||||
and example usage.
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
'''
|
||||
if async_lib is not None:
|
||||
Clock.init_async_lib(async_lib)
|
||||
_runTouchApp_prepare(widget=widget)
|
||||
|
||||
# we are in embedded mode, don't do dispatching.
|
||||
if embedded:
|
||||
return
|
||||
|
||||
try:
|
||||
await EventLoop.async_mainloop()
|
||||
finally:
|
||||
stopTouchApp()
|
||||
|
||||
|
||||
def stopTouchApp():
|
||||
'''Stop the current application by leaving the main loop.
|
||||
|
||||
See :mod:`kivy.app` for example usage.
|
||||
'''
|
||||
if EventLoop is None:
|
||||
return
|
||||
if EventLoop.status in ('stopped', 'closed'):
|
||||
return
|
||||
if EventLoop.status != 'started':
|
||||
if not EventLoop.stopping:
|
||||
EventLoop.stopping = True
|
||||
Clock.schedule_once(lambda dt: stopTouchApp(), 0)
|
||||
return
|
||||
Logger.info('Base: Leaving application in progress...')
|
||||
EventLoop.close()
|
||||
262
kivy/cache.py
Normal file
262
kivy/cache.py
Normal file
@ -0,0 +1,262 @@
|
||||
'''
|
||||
Cache manager
|
||||
=============
|
||||
|
||||
The cache manager can be used to store python objects attached to a unique
|
||||
key. The cache can be controlled in two ways: with a object limit or a
|
||||
timeout.
|
||||
|
||||
For example, we can create a new cache with a limit of 10 objects and a
|
||||
timeout of 5 seconds::
|
||||
|
||||
# register a new Cache
|
||||
Cache.register('mycache', limit=10, timeout=5)
|
||||
|
||||
# create an object + id
|
||||
key = 'objectid'
|
||||
instance = Label(text=text)
|
||||
Cache.append('mycache', key, instance)
|
||||
|
||||
# retrieve the cached object
|
||||
instance = Cache.get('mycache', key)
|
||||
|
||||
If the instance is NULL, the cache may have trashed it because you've
|
||||
not used the label for 5 seconds and you've reach the limit.
|
||||
'''
|
||||
|
||||
from os import environ
|
||||
from kivy.logger import Logger
|
||||
from kivy.clock import Clock
|
||||
|
||||
__all__ = ('Cache', )
|
||||
|
||||
|
||||
class Cache(object):
|
||||
'''See module documentation for more information.
|
||||
'''
|
||||
|
||||
_categories = {}
|
||||
_objects = {}
|
||||
|
||||
@staticmethod
|
||||
def register(category, limit=None, timeout=None):
|
||||
'''Register a new category in the cache with the specified limit.
|
||||
|
||||
:Parameters:
|
||||
`category`: str
|
||||
Identifier of the category.
|
||||
`limit`: int (optional)
|
||||
Maximum number of objects allowed in the cache.
|
||||
If None, no limit is applied.
|
||||
`timeout`: double (optional)
|
||||
Time after which to delete the object if it has not been used.
|
||||
If None, no timeout is applied.
|
||||
'''
|
||||
Cache._categories[category] = {
|
||||
'limit': limit,
|
||||
'timeout': timeout}
|
||||
Cache._objects[category] = {}
|
||||
Logger.debug(
|
||||
'Cache: register <%s> with limit=%s, timeout=%s' %
|
||||
(category, str(limit), str(timeout)))
|
||||
|
||||
@staticmethod
|
||||
def append(category, key, obj, timeout=None):
|
||||
'''Add a new object to the cache.
|
||||
|
||||
:Parameters:
|
||||
`category`: str
|
||||
Identifier of the category.
|
||||
`key`: str
|
||||
Unique identifier of the object to store.
|
||||
`obj`: object
|
||||
Object to store in cache.
|
||||
`timeout`: double (optional)
|
||||
Time after which to delete the object if it has not been used.
|
||||
If None, no timeout is applied.
|
||||
|
||||
:raises:
|
||||
`ValueError`: If `None` is used as `key`.
|
||||
|
||||
.. versionchanged:: 2.0.0
|
||||
Raises `ValueError` if `None` is used as `key`.
|
||||
|
||||
'''
|
||||
# check whether obj should not be cached first
|
||||
if getattr(obj, '_nocache', False):
|
||||
return
|
||||
if key is None:
|
||||
# This check is added because of the case when key is None and
|
||||
# one of purge methods gets called. Then loop in purge method will
|
||||
# call Cache.remove with key None which then clears entire
|
||||
# category from Cache making next iteration of loop to raise a
|
||||
# KeyError because next key will not exist.
|
||||
# See: https://github.com/kivy/kivy/pull/6950
|
||||
raise ValueError('"None" cannot be used as key in Cache')
|
||||
try:
|
||||
cat = Cache._categories[category]
|
||||
except KeyError:
|
||||
Logger.warning('Cache: category <%s> does not exist' % category)
|
||||
return
|
||||
|
||||
timeout = timeout or cat['timeout']
|
||||
|
||||
limit = cat['limit']
|
||||
|
||||
if limit is not None and len(Cache._objects[category]) >= limit:
|
||||
Cache._purge_oldest(category)
|
||||
|
||||
Cache._objects[category][key] = {
|
||||
'object': obj,
|
||||
'timeout': timeout,
|
||||
'lastaccess': Clock.get_time(),
|
||||
'timestamp': Clock.get_time()}
|
||||
|
||||
@staticmethod
|
||||
def get(category, key, default=None):
|
||||
'''Get a object from the cache.
|
||||
|
||||
:Parameters:
|
||||
`category`: str
|
||||
Identifier of the category.
|
||||
`key`: str
|
||||
Unique identifier of the object in the store.
|
||||
`default`: anything, defaults to None
|
||||
Default value to be returned if the key is not found.
|
||||
'''
|
||||
try:
|
||||
Cache._objects[category][key]['lastaccess'] = Clock.get_time()
|
||||
return Cache._objects[category][key]['object']
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def get_timestamp(category, key, default=None):
|
||||
'''Get the object timestamp in the cache.
|
||||
|
||||
:Parameters:
|
||||
`category`: str
|
||||
Identifier of the category.
|
||||
`key`: str
|
||||
Unique identifier of the object in the store.
|
||||
`default`: anything, defaults to None
|
||||
Default value to be returned if the key is not found.
|
||||
'''
|
||||
try:
|
||||
return Cache._objects[category][key]['timestamp']
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def get_lastaccess(category, key, default=None):
|
||||
'''Get the objects last access time in the cache.
|
||||
|
||||
:Parameters:
|
||||
`category`: str
|
||||
Identifier of the category.
|
||||
`key`: str
|
||||
Unique identifier of the object in the store.
|
||||
`default`: anything, defaults to None
|
||||
Default value to be returned if the key is not found.
|
||||
'''
|
||||
try:
|
||||
return Cache._objects[category][key]['lastaccess']
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def remove(category, key=None):
|
||||
'''Purge the cache.
|
||||
|
||||
:Parameters:
|
||||
`category`: str
|
||||
Identifier of the category.
|
||||
`key`: str (optional)
|
||||
Unique identifier of the object in the store. If this
|
||||
argument is not supplied, the entire category will be purged.
|
||||
'''
|
||||
try:
|
||||
if key is not None:
|
||||
del Cache._objects[category][key]
|
||||
Logger.trace('Cache: Removed %s:%s from cache' %
|
||||
(category, key))
|
||||
else:
|
||||
Cache._objects[category] = {}
|
||||
Logger.trace('Cache: Flushed category %s from cache' %
|
||||
category)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _purge_oldest(category, maxpurge=1):
|
||||
Logger.trace('Cache: Remove oldest in %s' % category)
|
||||
import heapq
|
||||
time = Clock.get_time()
|
||||
heap_list = []
|
||||
for key in Cache._objects[category]:
|
||||
obj = Cache._objects[category][key]
|
||||
if obj['lastaccess'] == obj['timestamp'] == time:
|
||||
continue
|
||||
heapq.heappush(heap_list, (obj['lastaccess'], key))
|
||||
Logger.trace('Cache: <<< %f' % obj['lastaccess'])
|
||||
n = 0
|
||||
while n <= maxpurge:
|
||||
try:
|
||||
n += 1
|
||||
lastaccess, key = heapq.heappop(heap_list)
|
||||
Logger.trace('Cache: %d => %s %f %f' %
|
||||
(n, key, lastaccess, Clock.get_time()))
|
||||
except Exception:
|
||||
return
|
||||
Cache.remove(category, key)
|
||||
|
||||
@staticmethod
|
||||
def _purge_by_timeout(dt):
|
||||
curtime = Clock.get_time()
|
||||
|
||||
for category in Cache._objects:
|
||||
if category not in Cache._categories:
|
||||
continue
|
||||
timeout = Cache._categories[category]['timeout']
|
||||
if timeout is not None and dt > timeout:
|
||||
# XXX got a lag ! that may be because the frame take lot of
|
||||
# time to draw. and the timeout is not adapted to the current
|
||||
# framerate. So, increase the timeout by two.
|
||||
# ie: if the timeout is 1 sec, and framerate go to 0.7, newly
|
||||
# object added will be automatically trashed.
|
||||
timeout *= 2
|
||||
Cache._categories[category]['timeout'] = timeout
|
||||
continue
|
||||
|
||||
for key in list(Cache._objects[category].keys()):
|
||||
lastaccess = Cache._objects[category][key]['lastaccess']
|
||||
objtimeout = Cache._objects[category][key]['timeout']
|
||||
|
||||
# take the object timeout if available
|
||||
if objtimeout is not None:
|
||||
timeout = objtimeout
|
||||
|
||||
# no timeout, cancel
|
||||
if timeout is None:
|
||||
continue
|
||||
|
||||
if curtime - lastaccess > timeout:
|
||||
Logger.trace('Cache: Removed %s:%s from cache due to '
|
||||
'timeout' % (category, key))
|
||||
Cache.remove(category, key)
|
||||
|
||||
@staticmethod
|
||||
def print_usage():
|
||||
'''Print the cache usage to the console.'''
|
||||
print('Cache usage :')
|
||||
for category in Cache._categories:
|
||||
print(' * %s : %d / %s, timeout=%s' % (
|
||||
category.capitalize(),
|
||||
len(Cache._objects[category]),
|
||||
str(Cache._categories[category]['limit']),
|
||||
str(Cache._categories[category]['timeout'])))
|
||||
|
||||
|
||||
if 'KIVY_DOC_INCLUDE' not in environ:
|
||||
# install the schedule clock for purging
|
||||
Clock.schedule_interval(Cache._purge_by_timeout, 1)
|
||||
1172
kivy/clock.py
Normal file
1172
kivy/clock.py
Normal file
File diff suppressed because it is too large
Load Diff
82
kivy/compat.py
Normal file
82
kivy/compat.py
Normal file
@ -0,0 +1,82 @@
|
||||
'''
|
||||
Compatibility module for Python 2.7 and >= 3.4
|
||||
==============================================
|
||||
|
||||
This module provides a set of utility types and functions for optimization and
|
||||
to aid in writing Python 2/3 compatible code.
|
||||
'''
|
||||
|
||||
__all__ = ('PY2', 'clock', 'string_types', 'queue', 'iterkeys',
|
||||
'itervalues', 'iteritems', 'isclose')
|
||||
|
||||
import sys
|
||||
import time
|
||||
from math import isinf, fabs
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
import Queue as queue
|
||||
try:
|
||||
from math import isclose
|
||||
except ImportError:
|
||||
isclose = None
|
||||
|
||||
PY2 = False
|
||||
'''False, because we don't support Python 2 anymore.'''
|
||||
|
||||
clock = None
|
||||
'''A clock with the highest available resolution on your current Operating
|
||||
System.'''
|
||||
|
||||
string_types = str
|
||||
'''A utility type for detecting string in a Python 2/3 friendly way. For
|
||||
example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if isinstance(s, string_types):
|
||||
print("It's a string or unicode type")
|
||||
else:
|
||||
print("It's something else.")
|
||||
'''
|
||||
|
||||
text_type = str
|
||||
|
||||
#: unichr is just chr in py3, since all strings are unicode
|
||||
unichr = chr
|
||||
|
||||
iterkeys = lambda d: iter(d.keys())
|
||||
itervalues = lambda d: iter(d.values())
|
||||
iteritems = lambda d: iter(d.items())
|
||||
|
||||
|
||||
clock = time.perf_counter
|
||||
|
||||
|
||||
def _isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
|
||||
'''Measures whether two floats are "close" to each other. Identical to
|
||||
https://docs.python.org/3.6/library/math.html#math.isclose, for older
|
||||
versions of python.
|
||||
'''
|
||||
|
||||
if a == b: # short-circuit exact equality
|
||||
return True
|
||||
|
||||
if rel_tol < 0.0 or abs_tol < 0.0:
|
||||
raise ValueError('error tolerances must be non-negative')
|
||||
|
||||
# use cmath so it will work with complex or float
|
||||
if isinf(abs(a)) or isinf(abs(b)):
|
||||
# This includes the case of two infinities of opposite sign, or
|
||||
# one infinity and one finite number. Two infinities of opposite sign
|
||||
# would otherwise have an infinite relative tolerance.
|
||||
return False
|
||||
diff = fabs(b - a)
|
||||
|
||||
return (((diff <= fabs(rel_tol * b)) or
|
||||
(diff <= fabs(rel_tol * a))) or
|
||||
(diff <= abs_tol))
|
||||
|
||||
|
||||
if isclose is None:
|
||||
isclose = _isclose
|
||||
969
kivy/config.py
Normal file
969
kivy/config.py
Normal file
@ -0,0 +1,969 @@
|
||||
'''
|
||||
Configuration object
|
||||
====================
|
||||
|
||||
The :class:`Config` object is an instance of a modified Python ConfigParser.
|
||||
See the `ConfigParser documentation
|
||||
<http://docs.python.org/library/configparser.html>`_ for more information.
|
||||
|
||||
Kivy has a configuration file which determines the default settings. In
|
||||
order to change these settings, you can alter this file manually or use
|
||||
the Config object. Please see the :ref:`Configure Kivy` section for more
|
||||
information.
|
||||
|
||||
Applying configurations
|
||||
-----------------------
|
||||
|
||||
Configuration options control the initialization of the :class:`~kivy.app.App`.
|
||||
In order to avoid situations where the config settings do not work or are not
|
||||
applied before window creation (like setting an initial window size),
|
||||
:meth:`Config.set <kivy.config.ConfigParser.set>` should be used before
|
||||
importing any other Kivy modules. Ideally, this means setting them right at
|
||||
the start of your main.py script.
|
||||
|
||||
Alternatively, you can save these settings permanently using
|
||||
:meth:`Config.set <ConfigParser.set>` then
|
||||
:meth:`Config.write <ConfigParser.write>`. In this case, you will need to
|
||||
restart the app for the changes to take effect. Note that this approach will
|
||||
effect all Kivy apps system wide.
|
||||
|
||||
Please note that no underscores (`_`) are allowed in the section name.
|
||||
|
||||
Usage of the Config object
|
||||
--------------------------
|
||||
|
||||
To read a configuration token from a particular section::
|
||||
|
||||
>>> from kivy.config import Config
|
||||
>>> Config.getint('kivy', 'show_fps')
|
||||
0
|
||||
|
||||
Change the configuration and save it::
|
||||
|
||||
>>> Config.set('postproc', 'retain_time', '50')
|
||||
>>> Config.write()
|
||||
|
||||
For information on configuring your :class:`~kivy.app.App`, please see the
|
||||
:ref:`Application configuration` section.
|
||||
|
||||
.. versionchanged:: 1.7.1
|
||||
The ConfigParser should work correctly with utf-8 now. The values are
|
||||
converted from ascii to unicode only when needed. The method get() returns
|
||||
utf-8 strings.
|
||||
|
||||
Changing configuration with environment variables
|
||||
-------------------------------------------------
|
||||
|
||||
Since 1.11.0, it is now possible to change the configuration using
|
||||
environment variables. They take precedence on the loaded config.ini.
|
||||
The format is::
|
||||
|
||||
KCFG_<section>_<key> = <value>
|
||||
|
||||
For example:
|
||||
|
||||
KCFG_GRAPHICS_FULLSCREEN=auto ...
|
||||
KCFG_KIVY_LOG_LEVEL=warning ...
|
||||
|
||||
Or in your file before any kivy import:
|
||||
|
||||
import os
|
||||
os.environ["KCFG_KIVY_LOG_LEVEL"] = "warning"
|
||||
|
||||
If you don't want to map any environment variables, you can disable
|
||||
the behavior::
|
||||
|
||||
os.environ["KIVY_NO_ENV_CONFIG"] = "1"
|
||||
|
||||
|
||||
.. _configuration-tokens:
|
||||
|
||||
Available configuration tokens
|
||||
------------------------------
|
||||
|
||||
.. |log_levels| replace::
|
||||
'trace', 'debug', 'info', 'warning', 'error' or 'critical'
|
||||
|
||||
:kivy:
|
||||
|
||||
`default_font`: list
|
||||
Default fonts used for widgets displaying any text. It defaults to
|
||||
['Roboto', 'data/fonts/Roboto-Regular.ttf',
|
||||
'data/fonts/Roboto-Italic.ttf', 'data/fonts/Roboto-Bold.ttf',
|
||||
'data/fonts/Roboto-BoldItalic.ttf'].
|
||||
`desktop`: int, 0 or 1
|
||||
This option controls desktop OS specific features, such as enabling
|
||||
drag-able scroll-bar in scroll views, disabling of bubbles in
|
||||
TextInput etc. 0 is disabled, 1 is enabled.
|
||||
`exit_on_escape`: int, 0 or 1
|
||||
Enables exiting kivy when escape is pressed.
|
||||
0 is disabled, 1 is enabled.
|
||||
`pause_on_minimize`: int, 0 or 1
|
||||
If set to `1`, the main loop is paused and the `on_pause` event
|
||||
is dispatched when the window is minimized. This option is intended
|
||||
for desktop use only. Defaults to `0`.
|
||||
`keyboard_layout`: string
|
||||
Identifier of the layout to use.
|
||||
`keyboard_mode`: string
|
||||
Specifies the keyboard mode to use. If can be one of the following:
|
||||
|
||||
* '' - Let Kivy choose the best option for your current platform.
|
||||
* 'system' - real keyboard.
|
||||
* 'dock' - one virtual keyboard docked to a screen side.
|
||||
* 'multi' - one virtual keyboard for every widget request.
|
||||
* 'systemanddock' - virtual docked keyboard plus input from real
|
||||
keyboard.
|
||||
* 'systemandmulti' - analogous.
|
||||
`kivy_clock`: one of `default`, `interrupt`, `free_all`, `free_only`
|
||||
The clock type to use with kivy. See :mod:`kivy.clock`.
|
||||
`log_dir`: string
|
||||
Path of log directory.
|
||||
`log_enable`: int, 0 or 1
|
||||
Activate file logging. 0 is disabled, 1 is enabled.
|
||||
`log_level`: string, one of |log_levels|
|
||||
Set the minimum log level to use.
|
||||
`log_name`: string
|
||||
Format string to use for the filename of log file.
|
||||
|
||||
`log_maxfiles`: int
|
||||
Keep log_maxfiles recent logfiles while purging the log directory. Set
|
||||
'log_maxfiles' to -1 to disable logfile purging (eg keep all logfiles).
|
||||
|
||||
.. note::
|
||||
You end up with 'log_maxfiles + 1' logfiles because the logger
|
||||
adds a new one after purging.
|
||||
|
||||
`window_icon`: string
|
||||
Path of the window icon. Use this if you want to replace the default
|
||||
pygame icon.
|
||||
|
||||
:postproc:
|
||||
|
||||
`double_tap_distance`: float
|
||||
Maximum distance allowed for a double tap, normalized inside the range
|
||||
0 - 1000.
|
||||
`double_tap_time`: int
|
||||
Time allowed for the detection of double tap, in milliseconds.
|
||||
`ignore`: list of tuples
|
||||
List of regions where new touches are ignored.
|
||||
This configuration token can be used to resolve hotspot problems
|
||||
with DIY hardware. The format of the list must be::
|
||||
|
||||
ignore = [(xmin, ymin, xmax, ymax), ...]
|
||||
|
||||
All the values must be inside the range 0 - 1.
|
||||
`jitter_distance`: int
|
||||
Maximum distance for jitter detection, normalized inside the range 0
|
||||
- 1000.
|
||||
`jitter_ignore_devices`: string, separated with commas
|
||||
List of devices to ignore from jitter detection.
|
||||
`retain_distance`: int
|
||||
If the touch moves more than is indicated by retain_distance, it will
|
||||
not be retained. Argument should be an int between 0 and 1000.
|
||||
`retain_time`: int
|
||||
Time allowed for a retain touch, in milliseconds.
|
||||
`triple_tap_distance`: float
|
||||
Maximum distance allowed for a triple tap, normalized inside the range
|
||||
0 - 1000.
|
||||
`triple_tap_time`: int
|
||||
Time allowed for the detection of triple tap, in milliseconds.
|
||||
|
||||
:graphics:
|
||||
`borderless`: int, one of 0 or 1
|
||||
If set to `1`, removes the window border/decoration. Window resizing
|
||||
must also be disabled to hide the resizing border.
|
||||
`custom_titlebar`: int, one of 0 or 1
|
||||
If set to `1`, removes the window border and allows user to set a Widget
|
||||
as a titlebar
|
||||
see :meth:`~kivy.core.window.WindowBase.set_custom_titlebar`
|
||||
for detailed usage
|
||||
`custom_titlebar_border`: int, defaults to 5
|
||||
sets the how many pixles off the border should be used as the
|
||||
rezising frame
|
||||
`window_state`: string , one of 'visible', 'hidden', 'maximized'
|
||||
or 'minimized'
|
||||
|
||||
Sets the window state, defaults to 'visible'. This option is available
|
||||
only for the SDL2 window provider and it should be used on desktop
|
||||
OSes.
|
||||
`fbo`: string, one of 'hardware', 'software' or 'force-hardware'
|
||||
Selects the FBO backend to use.
|
||||
`fullscreen`: int or string, one of 0, 1, 'fake' or 'auto'
|
||||
Activate fullscreen. If set to `1`, a resolution of `width`
|
||||
times `height` pixels will be used.
|
||||
If set to `auto`, your current display's resolution will be
|
||||
used instead. This is most likely what you want.
|
||||
If you want to place the window in another display,
|
||||
use `fake`, or set the `borderless` option from the graphics section,
|
||||
then adjust `width`, `height`, `top` and `left`.
|
||||
`height`: int
|
||||
Height of the :class:`~kivy.core.window.Window`, not used if
|
||||
`fullscreen` is set to `auto`.
|
||||
`left`: int
|
||||
Left position of the :class:`~kivy.core.window.Window`.
|
||||
`maxfps`: int, defaults to 60
|
||||
Maximum FPS allowed.
|
||||
|
||||
.. warning::
|
||||
Setting maxfps to 0 will lead to max CPU usage.
|
||||
|
||||
'multisamples': int, defaults to 2
|
||||
Sets the `MultiSample Anti-Aliasing (MSAA)
|
||||
<http://en.wikipedia.org/wiki/Multisample_anti-aliasing>`_ level.
|
||||
Increasing this value results in smoother graphics but at the cost of
|
||||
processing time.
|
||||
|
||||
.. note::
|
||||
This feature is limited by device hardware support and will have no
|
||||
effect on devices which do not support the level of MSAA requested.
|
||||
|
||||
`position`: string, one of 'auto' or 'custom'
|
||||
Position of the window on your display. If `auto` is used, you have no
|
||||
control of the initial position: `top` and `left` are ignored.
|
||||
`show_cursor`: int, one of 0 or 1
|
||||
Set whether or not the cursor is shown on the window.
|
||||
`top`: int
|
||||
Top position of the :class:`~kivy.core.window.Window`.
|
||||
`resizable`: int, one of 0 or 1
|
||||
If 0, the window will have a fixed size. If 1, the window will be
|
||||
resizable.
|
||||
`rotation`: int, one of 0, 90, 180 or 270
|
||||
Rotation of the :class:`~kivy.core.window.Window`.
|
||||
`width`: int
|
||||
Width of the :class:`~kivy.core.window.Window`, not used if
|
||||
`fullscreen` is set to `auto`.
|
||||
`minimum_width`: int
|
||||
Minimum width to restrict the window to. (sdl2 only)
|
||||
`minimum_height`: int
|
||||
Minimum height to restrict the window to. (sdl2 only)
|
||||
`min_state_time`: float, defaults to .035
|
||||
Minimum time for widgets to display a given visual state.
|
||||
This attrib is currently used by widgets like
|
||||
:class:`~kivy.uix.dropdown.DropDown` &
|
||||
:class:`~kivy.uix.behaviors.buttonbehavior.ButtonBehavior` to
|
||||
make sure they display their current visual state for the given
|
||||
time.
|
||||
`allow_screensaver`: int, one of 0 or 1, defaults to 1
|
||||
Allow the device to show a screen saver, or to go to sleep
|
||||
on mobile devices. Only works for the sdl2 window provider.
|
||||
`vsync`: `none`, empty value, or integers
|
||||
Whether vsync is enabled, currently only used with sdl2 window.
|
||||
Possible values are `none` or empty value -- leaves it unchanged,
|
||||
``0`` -- disables vsync, ``1`` or larger -- sets vsync interval,
|
||||
``-1`` sets adaptive vsync. It falls back to 1 if setting to ``2+``
|
||||
or ``-1`` failed. See ``SDL_GL_SetSwapInterval``.
|
||||
`verify_gl_main_thread`: int, 1 or 0, defaults to 1
|
||||
Whether to check if code that changes any gl instructions is
|
||||
running outside the main thread and then raise an error.
|
||||
|
||||
:input:
|
||||
|
||||
You can create new input devices using this syntax::
|
||||
|
||||
# example of input provider instance
|
||||
yourid = providerid,parameters
|
||||
|
||||
# example for tuio provider
|
||||
default = tuio,127.0.0.1:3333
|
||||
mytable = tuio,192.168.0.1:3334
|
||||
|
||||
.. seealso::
|
||||
|
||||
Check the providers in :mod:`kivy.input.providers` for the syntax to
|
||||
use inside the configuration file.
|
||||
|
||||
:widgets:
|
||||
|
||||
`scroll_distance`: int
|
||||
Default value of the
|
||||
:attr:`~kivy.uix.scrollview.ScrollView.scroll_distance`
|
||||
property used by the :class:`~kivy.uix.scrollview.ScrollView` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
`scroll_friction`: float
|
||||
Default value of the
|
||||
:attr:`~kivy.uix.scrollview.ScrollView.scroll_friction`
|
||||
property used by the :class:`~kivy.uix.scrollview.ScrollView` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
.. deprecated:: 1.7.0
|
||||
Please use
|
||||
:class:`~kivy.uix.scrollview.ScrollView.effect_cls` instead.
|
||||
|
||||
`scroll_timeout`: int
|
||||
Default value of the
|
||||
:attr:`~kivy.uix.scrollview.ScrollView.scroll_timeout`
|
||||
property used by the :class:`~kivy.uix.scrollview.ScrollView` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
`scroll_stoptime`: int
|
||||
Default value of the
|
||||
:attr:`~kivy.uix.scrollview.ScrollView.scroll_stoptime`
|
||||
property used by the :class:`~kivy.uix.scrollview.ScrollView` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
.. deprecated:: 1.7.0
|
||||
Please use
|
||||
:class:`~kivy.uix.scrollview.ScrollView.effect_cls` instead.
|
||||
|
||||
`scroll_moves`: int
|
||||
Default value of the
|
||||
:attr:`~kivy.uix.scrollview.ScrollView.scroll_moves`
|
||||
property used by the :class:`~kivy.uix.scrollview.ScrollView` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
.. deprecated:: 1.7.0
|
||||
Please use
|
||||
:class:`~kivy.uix.scrollview.ScrollView.effect_cls` instead.
|
||||
|
||||
:modules:
|
||||
|
||||
You can activate modules with this syntax::
|
||||
|
||||
modulename =
|
||||
|
||||
Anything after the = will be passed to the module as arguments.
|
||||
Check the specific module's documentation for a list of accepted
|
||||
arguments.
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
`vsync` has been added to the graphics section.
|
||||
`verify_gl_main_thread` has been added to the graphics section.
|
||||
|
||||
.. versionchanged:: 1.10.0
|
||||
`min_state_time` and `allow_screensaver` have been added
|
||||
to the `graphics` section.
|
||||
`kivy_clock` has been added to the kivy section.
|
||||
`default_font` has beed added to the kivy section.
|
||||
|
||||
.. versionchanged:: 1.9.0
|
||||
`borderless` and `window_state` have been added to the graphics section.
|
||||
The `fake` setting of the `fullscreen` option has been deprecated,
|
||||
use the `borderless` option instead.
|
||||
`pause_on_minimize` has been added to the kivy section.
|
||||
|
||||
.. versionchanged:: 1.8.0
|
||||
`systemanddock` and `systemandmulti` has been added as possible values for
|
||||
`keyboard_mode` in the kivy section. `exit_on_escape` has been added
|
||||
to the kivy section.
|
||||
|
||||
.. versionchanged:: 1.2.0
|
||||
`resizable` has been added to graphics section.
|
||||
|
||||
.. versionchanged:: 1.1.0
|
||||
tuio no longer listens by default. Window icons are not copied to
|
||||
user directory anymore. You can still set a new window icon by using the
|
||||
``window_icon`` config setting.
|
||||
|
||||
.. versionchanged:: 1.0.8
|
||||
`scroll_timeout`, `scroll_distance` and `scroll_friction` have been added.
|
||||
`list_friction`, `list_trigger_distance` and `list_friction_bound`
|
||||
have been removed. `keyboard_type` and `keyboard_layout` have been
|
||||
removed from the widget. `keyboard_mode` and `keyboard_layout` have
|
||||
been added to the kivy section.
|
||||
'''
|
||||
|
||||
__all__ = ('Config', 'ConfigParser')
|
||||
|
||||
try:
|
||||
from ConfigParser import ConfigParser as PythonConfigParser
|
||||
except ImportError:
|
||||
from configparser import RawConfigParser as PythonConfigParser
|
||||
from os import environ
|
||||
from os.path import exists
|
||||
from kivy import kivy_config_fn
|
||||
from kivy.logger import Logger, logger_config_update
|
||||
from collections import OrderedDict
|
||||
from kivy.utils import platform
|
||||
from kivy.compat import PY2, string_types
|
||||
from weakref import ref
|
||||
|
||||
_is_rpi = exists('/opt/vc/include/bcm_host.h')
|
||||
|
||||
# Version number of current configuration format
|
||||
KIVY_CONFIG_VERSION = 24
|
||||
|
||||
Config = None
|
||||
'''The default Kivy configuration object. This is a :class:`ConfigParser`
|
||||
instance with the :attr:`~kivy.config.ConfigParser.name` set to 'kivy'.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
Config = ConfigParser(name='kivy')
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class ConfigParser(PythonConfigParser, object):
|
||||
'''Enhanced ConfigParser class that supports the addition of default
|
||||
sections and default values.
|
||||
|
||||
By default, the kivy ConfigParser instance, :attr:`~kivy.config.Config`,
|
||||
is named `'kivy'` and the ConfigParser instance used by the
|
||||
:meth:`App.build_settings <~kivy.app.App.build_settings>` method is named
|
||||
`'app'`.
|
||||
|
||||
:Parameters:
|
||||
`name`: string
|
||||
The name of the instance. See :attr:`name`. Defaults to `''`.
|
||||
|
||||
.. versionchanged:: 1.9.0
|
||||
Each ConfigParser can now be :attr:`named <name>`. You can get the
|
||||
ConfigParser associated with a name using :meth:`get_configparser`.
|
||||
In addition, you can now control the config values with
|
||||
:class:`~kivy.properties.ConfigParserProperty`.
|
||||
|
||||
.. versionadded:: 1.0.7
|
||||
'''
|
||||
|
||||
def __init__(self, name='', **kwargs):
|
||||
PythonConfigParser.__init__(self, **kwargs)
|
||||
self._sections = OrderedDict()
|
||||
self.filename = None
|
||||
self._callbacks = []
|
||||
self.name = name
|
||||
|
||||
def add_callback(self, callback, section=None, key=None):
|
||||
'''Add a callback to be called when a specific section or key has
|
||||
changed. If you don't specify a section or key, it will call the
|
||||
callback for all section/key changes.
|
||||
|
||||
Callbacks will receive 3 arguments: the section, key and value.
|
||||
|
||||
.. versionadded:: 1.4.1
|
||||
'''
|
||||
if section is None and key is not None:
|
||||
raise Exception('You cannot specify a key without a section')
|
||||
self._callbacks.append((callback, section, key))
|
||||
|
||||
def remove_callback(self, callback, section=None, key=None):
|
||||
'''Removes a callback added with :meth:`add_callback`.
|
||||
:meth:`remove_callback` must be called with the same parameters as
|
||||
:meth:`add_callback`.
|
||||
|
||||
Raises a `ValueError` if not found.
|
||||
|
||||
.. versionadded:: 1.9.0
|
||||
'''
|
||||
self._callbacks.remove((callback, section, key))
|
||||
|
||||
def _do_callbacks(self, section, key, value):
|
||||
for callback, csection, ckey in self._callbacks:
|
||||
if csection is not None and csection != section:
|
||||
continue
|
||||
elif ckey is not None and ckey != key:
|
||||
continue
|
||||
callback(section, key, value)
|
||||
|
||||
def read(self, filename):
|
||||
'''Read only one filename. In contrast to the original ConfigParser of
|
||||
Python, this one is able to read only one file at a time. The last
|
||||
read file will be used for the :meth:`write` method.
|
||||
|
||||
.. versionchanged:: 1.9.0
|
||||
:meth:`read` now calls the callbacks if read changed any values.
|
||||
|
||||
'''
|
||||
if not isinstance(filename, string_types):
|
||||
raise Exception('Only one filename is accepted ({})'.format(
|
||||
string_types.__name__))
|
||||
self.filename = filename
|
||||
# If we try to open directly the configuration file in utf-8,
|
||||
# we correctly get the unicode value by default.
|
||||
# But, when we try to save it again, all the values we didn't changed
|
||||
# are still unicode, and then the PythonConfigParser internal do
|
||||
# a str() conversion -> fail.
|
||||
# Instead we currently to the conversion to utf-8 when value are
|
||||
# "get()", but we internally store them in ascii.
|
||||
# with codecs.open(filename, 'r', encoding='utf-8') as f:
|
||||
# self.readfp(f)
|
||||
old_vals = {sect: {k: v for k, v in self.items(sect)} for sect in
|
||||
self.sections()}
|
||||
PythonConfigParser.read(self, filename)
|
||||
|
||||
# when reading new file, sections/keys are only increased, not removed
|
||||
f = self._do_callbacks
|
||||
for section in self.sections():
|
||||
if section not in old_vals: # new section
|
||||
for k, v in self.items(section):
|
||||
f(section, k, v)
|
||||
continue
|
||||
|
||||
old_keys = old_vals[section]
|
||||
for k, v in self.items(section): # just update new/changed keys
|
||||
if k not in old_keys or v != old_keys[k]:
|
||||
f(section, k, v)
|
||||
|
||||
def set(self, section, option, value):
|
||||
'''Functions similarly to PythonConfigParser's set method, except that
|
||||
the value is implicitly converted to a string.
|
||||
'''
|
||||
e_value = value
|
||||
if not isinstance(value, string_types):
|
||||
# might be boolean, int, etc.
|
||||
e_value = str(value)
|
||||
ret = PythonConfigParser.set(self, section, option, e_value)
|
||||
self._do_callbacks(section, option, value)
|
||||
return ret
|
||||
|
||||
def setall(self, section, keyvalues):
|
||||
'''Sets multiple key-value pairs in a section. keyvalues should be a
|
||||
dictionary containing the key-value pairs to be set.
|
||||
'''
|
||||
for key, value in keyvalues.items():
|
||||
self.set(section, key, value)
|
||||
|
||||
def get(self, section, option, **kwargs):
|
||||
value = PythonConfigParser.get(self, section, option, **kwargs)
|
||||
if PY2:
|
||||
if type(value) is str:
|
||||
return value.decode('utf-8')
|
||||
return value
|
||||
|
||||
def setdefaults(self, section, keyvalues):
|
||||
'''Set multiple key-value defaults in a section. keyvalues should be
|
||||
a dictionary containing the new key-value defaults.
|
||||
'''
|
||||
self.adddefaultsection(section)
|
||||
for key, value in keyvalues.items():
|
||||
self.setdefault(section, key, value)
|
||||
|
||||
def setdefault(self, section, option, value):
|
||||
'''Set the default value for an option in the specified section.
|
||||
'''
|
||||
if self.has_option(section, option):
|
||||
return
|
||||
self.set(section, option, value)
|
||||
|
||||
def getdefault(self, section, option, defaultvalue):
|
||||
'''Get the value of an option in the specified section. If not found,
|
||||
it will return the default value.
|
||||
'''
|
||||
if not self.has_section(section):
|
||||
return defaultvalue
|
||||
if not self.has_option(section, option):
|
||||
return defaultvalue
|
||||
return self.get(section, option)
|
||||
|
||||
def getdefaultint(self, section, option, defaultvalue):
|
||||
'''Get the value of an option in the specified section. If not found,
|
||||
it will return the default value. The value will always be
|
||||
returned as an integer.
|
||||
|
||||
.. versionadded:: 1.6.0
|
||||
'''
|
||||
return int(self.getdefault(section, option, defaultvalue))
|
||||
|
||||
def adddefaultsection(self, section):
|
||||
'''Add a section if the section is missing.
|
||||
'''
|
||||
assert("_" not in section)
|
||||
if self.has_section(section):
|
||||
return
|
||||
self.add_section(section)
|
||||
|
||||
def write(self):
|
||||
'''Write the configuration to the last file opened using the
|
||||
:meth:`read` method.
|
||||
|
||||
Return True if the write finished successfully, False otherwise.
|
||||
'''
|
||||
if self.filename is None:
|
||||
return False
|
||||
try:
|
||||
with open(self.filename, 'w') as fd:
|
||||
PythonConfigParser.write(self, fd)
|
||||
except IOError:
|
||||
Logger.exception('Unable to write the config <%s>' % self.filename)
|
||||
return False
|
||||
return True
|
||||
|
||||
def update_config(self, filename, overwrite=False):
|
||||
'''Upgrade the configuration based on a new default config file.
|
||||
Overwrite any existing values if overwrite is True.
|
||||
'''
|
||||
pcp = PythonConfigParser()
|
||||
pcp.read(filename)
|
||||
confset = self.setall if overwrite else self.setdefaults
|
||||
for section in pcp.sections():
|
||||
confset(section, dict(pcp.items(section)))
|
||||
self.write()
|
||||
|
||||
@staticmethod
|
||||
def _register_named_property(name, widget_ref, *largs):
|
||||
''' Called by the ConfigParserProperty to register a property which
|
||||
was created with a config name instead of a config object.
|
||||
|
||||
When a ConfigParser with this name is later created, the properties
|
||||
are then notified that this parser now exists so they can use it.
|
||||
If the parser already exists, the property is notified here. See
|
||||
:meth:`~kivy.properties.ConfigParserProperty.set_config`.
|
||||
|
||||
:Parameters:
|
||||
`name`: a non-empty string
|
||||
The name of the ConfigParser that is associated with the
|
||||
property. See :attr:`name`.
|
||||
`widget_ref`: 2-tuple.
|
||||
The first element is a reference to the widget containing the
|
||||
property, the second element is the name of the property. E.g.:
|
||||
|
||||
class House(Widget):
|
||||
address = ConfigParserProperty('', 'info', 'street',
|
||||
'directory')
|
||||
|
||||
Then, the first element is a ref to a House instance, and the
|
||||
second is `'address'`.
|
||||
'''
|
||||
configs = ConfigParser._named_configs
|
||||
try:
|
||||
config, props = configs[name]
|
||||
except KeyError:
|
||||
configs[name] = (None, [widget_ref])
|
||||
return
|
||||
|
||||
props.append(widget_ref)
|
||||
if config:
|
||||
config = config()
|
||||
widget = widget_ref[0]()
|
||||
|
||||
if config and widget: # associate this config with property
|
||||
widget.property(widget_ref[1]).set_config(config)
|
||||
|
||||
@staticmethod
|
||||
def get_configparser(name):
|
||||
'''Returns the :class:`ConfigParser` instance whose name is `name`, or
|
||||
None if not found.
|
||||
|
||||
:Parameters:
|
||||
`name`: string
|
||||
The name of the :class:`ConfigParser` instance to return.
|
||||
'''
|
||||
try:
|
||||
config = ConfigParser._named_configs[name][0]
|
||||
if config is not None:
|
||||
config = config()
|
||||
if config is not None:
|
||||
return config
|
||||
del ConfigParser._named_configs[name]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
# keys are configparser names, values are 2-tuple of (ref(configparser),
|
||||
# widget_ref), where widget_ref is same as in _register_named_property
|
||||
_named_configs = {}
|
||||
_name = ''
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
''' The name associated with this ConfigParser instance, if not `''`.
|
||||
Defaults to `''`. It can be safely changed dynamically or set to `''`.
|
||||
|
||||
When a ConfigParser is given a name, that config object can be
|
||||
retrieved using :meth:`get_configparser`. In addition, that config
|
||||
instance can also be used with a
|
||||
:class:`~kivy.properties.ConfigParserProperty` instance that set its
|
||||
`config` value to this name.
|
||||
|
||||
Setting more than one ConfigParser with the same name will raise a
|
||||
`ValueError`.
|
||||
'''
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
old_name = self._name
|
||||
if value is old_name:
|
||||
return
|
||||
self._name = value
|
||||
configs = ConfigParser._named_configs
|
||||
|
||||
if old_name: # disconnect this parser from previously connected props
|
||||
_, props = configs.get(old_name, (None, []))
|
||||
for widget, prop in props:
|
||||
widget = widget()
|
||||
if widget:
|
||||
widget.property(prop).set_config(None)
|
||||
configs[old_name] = (None, props)
|
||||
|
||||
if not value:
|
||||
return
|
||||
|
||||
# if given new name, connect it with property that used this name
|
||||
try:
|
||||
config, props = configs[value]
|
||||
except KeyError:
|
||||
configs[value] = (ref(self), [])
|
||||
return
|
||||
|
||||
if config is not None and config() is not None:
|
||||
raise ValueError('A parser named {} already exists'.format(value))
|
||||
for widget, prop in props:
|
||||
widget = widget()
|
||||
if widget:
|
||||
widget.property(prop).set_config(self)
|
||||
configs[value] = (ref(self), props)
|
||||
|
||||
|
||||
if not environ.get('KIVY_DOC_INCLUDE'):
|
||||
|
||||
#
|
||||
# Read, analyse configuration file
|
||||
# Support upgrade of older config file versions
|
||||
#
|
||||
|
||||
# Create default configuration
|
||||
Config = ConfigParser(name='kivy')
|
||||
Config.add_callback(logger_config_update, 'kivy', 'log_level')
|
||||
|
||||
# Read config file if exist
|
||||
if (exists(kivy_config_fn) and
|
||||
'KIVY_USE_DEFAULTCONFIG' not in environ and
|
||||
'KIVY_NO_CONFIG' not in environ):
|
||||
try:
|
||||
Config.read(kivy_config_fn)
|
||||
except Exception as e:
|
||||
Logger.exception('Core: error while reading local'
|
||||
'configuration')
|
||||
|
||||
version = Config.getdefaultint('kivy', 'config_version', 0)
|
||||
|
||||
# Add defaults section
|
||||
Config.adddefaultsection('kivy')
|
||||
Config.adddefaultsection('graphics')
|
||||
Config.adddefaultsection('input')
|
||||
Config.adddefaultsection('postproc')
|
||||
Config.adddefaultsection('widgets')
|
||||
Config.adddefaultsection('modules')
|
||||
Config.adddefaultsection('network')
|
||||
|
||||
# Upgrade default configuration until we have the current version
|
||||
need_save = False
|
||||
if version != KIVY_CONFIG_VERSION and 'KIVY_NO_CONFIG' not in environ:
|
||||
Logger.warning('Config: Older configuration version detected'
|
||||
' ({0} instead of {1})'.format(
|
||||
version, KIVY_CONFIG_VERSION))
|
||||
Logger.warning('Config: Upgrading configuration in progress.')
|
||||
need_save = True
|
||||
|
||||
while version < KIVY_CONFIG_VERSION:
|
||||
Logger.debug('Config: Upgrading from %d to %d' %
|
||||
(version, version + 1))
|
||||
|
||||
if version == 0:
|
||||
|
||||
# log level
|
||||
Config.setdefault('kivy', 'keyboard_repeat_delay', '300')
|
||||
Config.setdefault('kivy', 'keyboard_repeat_rate', '30')
|
||||
Config.setdefault('kivy', 'log_dir', 'logs')
|
||||
Config.setdefault('kivy', 'log_enable', '1')
|
||||
Config.setdefault('kivy', 'log_level', 'info')
|
||||
Config.setdefault('kivy', 'log_name', 'kivy_%y-%m-%d_%_.txt')
|
||||
Config.setdefault('kivy', 'window_icon', '')
|
||||
|
||||
# default graphics parameters
|
||||
Config.setdefault('graphics', 'display', '-1')
|
||||
Config.setdefault('graphics', 'fullscreen', 'no')
|
||||
Config.setdefault('graphics', 'height', '600')
|
||||
Config.setdefault('graphics', 'left', '0')
|
||||
Config.setdefault('graphics', 'maxfps', '0')
|
||||
Config.setdefault('graphics', 'multisamples', '2')
|
||||
Config.setdefault('graphics', 'position', 'auto')
|
||||
Config.setdefault('graphics', 'rotation', '0')
|
||||
Config.setdefault('graphics', 'show_cursor', '1')
|
||||
Config.setdefault('graphics', 'top', '0')
|
||||
Config.setdefault('graphics', 'width', '800')
|
||||
|
||||
# input configuration
|
||||
Config.setdefault('input', 'mouse', 'mouse')
|
||||
|
||||
# activate native input provider in configuration
|
||||
# from 1.0.9, don't activate mactouch by default, or app are
|
||||
# unusable.
|
||||
if platform == 'win':
|
||||
Config.setdefault('input', 'wm_touch', 'wm_touch')
|
||||
Config.setdefault('input', 'wm_pen', 'wm_pen')
|
||||
elif platform == 'linux':
|
||||
probesysfs = 'probesysfs'
|
||||
if _is_rpi:
|
||||
probesysfs += ',provider=hidinput'
|
||||
Config.setdefault('input', '%(name)s', probesysfs)
|
||||
|
||||
# input postprocessing configuration
|
||||
Config.setdefault('postproc', 'double_tap_distance', '20')
|
||||
Config.setdefault('postproc', 'double_tap_time', '250')
|
||||
Config.setdefault('postproc', 'ignore', '[]')
|
||||
Config.setdefault('postproc', 'jitter_distance', '0')
|
||||
Config.setdefault('postproc', 'jitter_ignore_devices',
|
||||
'mouse,mactouch,')
|
||||
Config.setdefault('postproc', 'retain_distance', '50')
|
||||
Config.setdefault('postproc', 'retain_time', '0')
|
||||
|
||||
# default configuration for keyboard repetition
|
||||
Config.setdefault('widgets', 'keyboard_layout', 'qwerty')
|
||||
Config.setdefault('widgets', 'keyboard_type', '')
|
||||
Config.setdefault('widgets', 'list_friction', '10')
|
||||
Config.setdefault('widgets', 'list_friction_bound', '20')
|
||||
Config.setdefault('widgets', 'list_trigger_distance', '5')
|
||||
|
||||
elif version == 1:
|
||||
Config.set('graphics', 'maxfps', '60')
|
||||
|
||||
elif version == 2:
|
||||
# was a version to automatically copy windows icon in the user
|
||||
# directory, but it's now not used anymore. User can still change
|
||||
# the window icon by touching the config.
|
||||
pass
|
||||
|
||||
elif version == 3:
|
||||
# add token for scrollview
|
||||
Config.setdefault('widgets', 'scroll_timeout', '55')
|
||||
Config.setdefault('widgets', 'scroll_distance', '20')
|
||||
Config.setdefault('widgets', 'scroll_friction', '1.')
|
||||
|
||||
# remove old list_* token
|
||||
Config.remove_option('widgets', 'list_friction')
|
||||
Config.remove_option('widgets', 'list_friction_bound')
|
||||
Config.remove_option('widgets', 'list_trigger_distance')
|
||||
|
||||
elif version == 4:
|
||||
Config.remove_option('widgets', 'keyboard_type')
|
||||
Config.remove_option('widgets', 'keyboard_layout')
|
||||
|
||||
# add keyboard token
|
||||
Config.setdefault('kivy', 'keyboard_mode', '')
|
||||
Config.setdefault('kivy', 'keyboard_layout', 'qwerty')
|
||||
|
||||
elif version == 5:
|
||||
Config.setdefault('graphics', 'resizable', '1')
|
||||
|
||||
elif version == 6:
|
||||
# if the timeout is still the default value, change it
|
||||
Config.setdefault('widgets', 'scroll_stoptime', '300')
|
||||
Config.setdefault('widgets', 'scroll_moves', '5')
|
||||
|
||||
elif version == 7:
|
||||
# desktop bool indicating whether to use desktop specific features
|
||||
is_desktop = int(platform in ('win', 'macosx', 'linux'))
|
||||
Config.setdefault('kivy', 'desktop', is_desktop)
|
||||
Config.setdefault('postproc', 'triple_tap_distance', '20')
|
||||
Config.setdefault('postproc', 'triple_tap_time', '375')
|
||||
|
||||
elif version == 8:
|
||||
if Config.getint('widgets', 'scroll_timeout') == 55:
|
||||
Config.set('widgets', 'scroll_timeout', '250')
|
||||
|
||||
elif version == 9:
|
||||
Config.setdefault('kivy', 'exit_on_escape', '1')
|
||||
|
||||
elif version == 10:
|
||||
Config.set('graphics', 'fullscreen', '0')
|
||||
Config.setdefault('graphics', 'borderless', '0')
|
||||
|
||||
elif version == 11:
|
||||
Config.setdefault('kivy', 'pause_on_minimize', '0')
|
||||
|
||||
elif version == 12:
|
||||
Config.setdefault('graphics', 'window_state', 'visible')
|
||||
|
||||
elif version == 13:
|
||||
Config.setdefault('graphics', 'minimum_width', '0')
|
||||
Config.setdefault('graphics', 'minimum_height', '0')
|
||||
|
||||
elif version == 14:
|
||||
Config.setdefault('graphics', 'min_state_time', '.035')
|
||||
|
||||
elif version == 15:
|
||||
Config.setdefault('kivy', 'kivy_clock', 'default')
|
||||
|
||||
elif version == 16:
|
||||
Config.setdefault('kivy', 'default_font', [
|
||||
'Roboto',
|
||||
'data/fonts/Roboto-Regular.ttf',
|
||||
'data/fonts/Roboto-Italic.ttf',
|
||||
'data/fonts/Roboto-Bold.ttf',
|
||||
'data/fonts/Roboto-BoldItalic.ttf'])
|
||||
|
||||
elif version == 17:
|
||||
Config.setdefault('graphics', 'allow_screensaver', '1')
|
||||
|
||||
elif version == 18:
|
||||
Config.setdefault('kivy', 'log_maxfiles', '100')
|
||||
|
||||
elif version == 19:
|
||||
Config.setdefault('graphics', 'shaped', '0')
|
||||
Config.setdefault(
|
||||
'kivy', 'window_shape',
|
||||
'data/images/defaultshape.png'
|
||||
)
|
||||
|
||||
elif version == 20:
|
||||
Config.setdefault('network', 'useragent', 'curl')
|
||||
|
||||
elif version == 21:
|
||||
Config.setdefault('graphics', 'vsync', '')
|
||||
|
||||
elif version == 22:
|
||||
Config.setdefault('graphics', 'verify_gl_main_thread', '1')
|
||||
|
||||
elif version == 23:
|
||||
Config.setdefault('graphics', 'custom_titlebar', '0')
|
||||
Config.setdefault('graphics', 'custom_titlebar_border', '5')
|
||||
|
||||
else:
|
||||
# for future.
|
||||
break
|
||||
|
||||
# Pass to the next version
|
||||
version += 1
|
||||
|
||||
# Indicate to the Config that we've upgrade to the latest version.
|
||||
Config.set('kivy', 'config_version', KIVY_CONFIG_VERSION)
|
||||
|
||||
# Now, activate log file
|
||||
Logger.logfile_activated = bool(Config.getint('kivy', 'log_enable'))
|
||||
|
||||
# If no configuration exist, write the default one.
|
||||
if ((not exists(kivy_config_fn) or need_save) and
|
||||
'KIVY_NO_CONFIG' not in environ):
|
||||
try:
|
||||
Config.filename = kivy_config_fn
|
||||
Config.write()
|
||||
except Exception as e:
|
||||
Logger.exception('Core: Error while saving default config file')
|
||||
|
||||
# Load configuration from env
|
||||
if environ.get('KIVY_NO_ENV_CONFIG', '0') != '1':
|
||||
for key, value in environ.items():
|
||||
if not key.startswith("KCFG_"):
|
||||
continue
|
||||
try:
|
||||
_, section, name = key.split("_", 2)
|
||||
except ValueError:
|
||||
Logger.warning((
|
||||
"Config: Environ `{}` invalid format, "
|
||||
"must be KCFG_section_name").format(key))
|
||||
continue
|
||||
|
||||
# extract and check section
|
||||
section = section.lower()
|
||||
if not Config.has_section(section):
|
||||
Logger.warning(
|
||||
"Config: Environ `{}`: unknown section `{}`".format(
|
||||
key, section))
|
||||
continue
|
||||
|
||||
# extract and check the option name
|
||||
name = name.lower()
|
||||
sections_to_check = {
|
||||
"kivy", "graphics", "widgets", "postproc", "network"}
|
||||
if (section in sections_to_check and
|
||||
not Config.has_option(section, name)):
|
||||
Logger.warning((
|
||||
"Config: Environ `{}` unknown `{}` "
|
||||
"option in `{}` section.").format(
|
||||
key, name, section))
|
||||
# we don't avoid to set an unknown option, because maybe
|
||||
# an external modules or widgets (in garden?) may want to
|
||||
# save its own configuration here.
|
||||
|
||||
Config.set(section, name, value)
|
||||
102
kivy/context.py
Normal file
102
kivy/context.py
Normal file
@ -0,0 +1,102 @@
|
||||
'''
|
||||
Context
|
||||
=======
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
.. warning::
|
||||
|
||||
This is experimental and subject to change as long as this warning notice
|
||||
is present.
|
||||
|
||||
Kivy has a few "global" instances that are used directly by many pieces of the
|
||||
framework: `Cache`, `Builder`, `Clock`.
|
||||
|
||||
TODO: document this module.
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('Context', 'ProxyContext', 'register_context',
|
||||
'get_current_context')
|
||||
|
||||
_contexts = {}
|
||||
_default_context = None
|
||||
_context_stack = []
|
||||
|
||||
|
||||
class ProxyContext(object):
|
||||
|
||||
__slots__ = ['_obj']
|
||||
|
||||
def __init__(self, obj):
|
||||
object.__init__(self)
|
||||
object.__setattr__(self, '_obj', obj)
|
||||
|
||||
def __getattribute__(self, name):
|
||||
return getattr(object.__getattribute__(self, '_obj'), name)
|
||||
|
||||
def __delattr__(self, name):
|
||||
delattr(object.__getattribute__(self, '_obj'), name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
setattr(object.__getattribute__(self, '_obj'), name, value)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(object.__getattribute__(self, '_obj'))
|
||||
|
||||
def __str__(self):
|
||||
return str(object.__getattribute__(self, '_obj'))
|
||||
|
||||
def __repr__(self):
|
||||
return repr(object.__getattribute__(self, '_obj'))
|
||||
|
||||
|
||||
class Context(dict):
|
||||
|
||||
def __init__(self, init=False):
|
||||
dict.__init__(self)
|
||||
self.sandbox = None
|
||||
if not init:
|
||||
return
|
||||
|
||||
for name in _contexts:
|
||||
context = _contexts[name]
|
||||
instance = context['cls'](*context['args'], **context['kwargs'])
|
||||
self[name] = instance
|
||||
|
||||
def push(self):
|
||||
_context_stack.append(self)
|
||||
for name, instance in self.items():
|
||||
object.__setattr__(_contexts[name]['proxy'], '_obj', instance)
|
||||
|
||||
def pop(self):
|
||||
# After popping context from stack. Update proxy's _obj with
|
||||
# instances in current context
|
||||
_context_stack.pop(-1)
|
||||
for name, instance in get_current_context().items():
|
||||
object.__setattr__(_contexts[name]['proxy'], '_obj', instance)
|
||||
|
||||
|
||||
def register_context(name, cls, *args, **kwargs):
|
||||
'''Register a new context.
|
||||
'''
|
||||
instance = cls(*args, **kwargs)
|
||||
proxy = ProxyContext(instance)
|
||||
_contexts[name] = {
|
||||
'cls': cls,
|
||||
'args': args,
|
||||
'kwargs': kwargs,
|
||||
'proxy': proxy}
|
||||
_default_context[name] = instance
|
||||
return proxy
|
||||
|
||||
|
||||
def get_current_context():
|
||||
'''Return the current context.
|
||||
'''
|
||||
if not _context_stack:
|
||||
return _default_context
|
||||
return _context_stack[-1]
|
||||
|
||||
|
||||
_default_context = Context(init=False)
|
||||
247
kivy/core/__init__.py
Normal file
247
kivy/core/__init__.py
Normal file
@ -0,0 +1,247 @@
|
||||
'''
|
||||
Core Abstraction
|
||||
================
|
||||
|
||||
This module defines the abstraction layers for our core providers and their
|
||||
implementations. For further information, please refer to
|
||||
:ref:`architecture` and the :ref:`providers` section of the documentation.
|
||||
|
||||
In most cases, you shouldn't directly use a library that's already covered
|
||||
by the core abstraction. Always try to use our providers first.
|
||||
In case we are missing a feature or method, please let us know by
|
||||
opening a new Bug report instead of relying on your library.
|
||||
|
||||
.. warning::
|
||||
These are **not** widgets! These are just abstractions of the respective
|
||||
functionality. For example, you cannot add a core image to your window.
|
||||
You have to use the image **widget** class instead. If you're really
|
||||
looking for widgets, please refer to :mod:`kivy.uix` instead.
|
||||
'''
|
||||
|
||||
|
||||
import os
|
||||
import sysconfig
|
||||
import sys
|
||||
import traceback
|
||||
import tempfile
|
||||
import subprocess
|
||||
import importlib
|
||||
import kivy
|
||||
from kivy.logger import Logger
|
||||
|
||||
|
||||
class CoreCriticalException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def core_select_lib(category, llist, create_instance=False,
|
||||
base='kivy.core', basemodule=None):
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
return
|
||||
category = category.lower()
|
||||
basemodule = basemodule or category
|
||||
libs_ignored = []
|
||||
errs = []
|
||||
for option, modulename, classname in llist:
|
||||
try:
|
||||
# module activated in config ?
|
||||
try:
|
||||
if option not in kivy.kivy_options[category]:
|
||||
libs_ignored.append(modulename)
|
||||
Logger.debug(
|
||||
'{0}: Provider <{1}> ignored by config'.format(
|
||||
category.capitalize(), option))
|
||||
continue
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# import module
|
||||
mod = importlib.__import__(name='{2}.{0}.{1}'.format(
|
||||
basemodule, modulename, base),
|
||||
globals=globals(),
|
||||
locals=locals(),
|
||||
fromlist=[modulename], level=0)
|
||||
cls = mod.__getattribute__(classname)
|
||||
|
||||
# ok !
|
||||
Logger.info('{0}: Provider: {1}{2}'.format(
|
||||
category.capitalize(), option,
|
||||
'({0} ignored)'.format(libs_ignored) if libs_ignored else ''))
|
||||
if create_instance:
|
||||
cls = cls()
|
||||
return cls
|
||||
|
||||
except ImportError as e:
|
||||
errs.append((option, e, sys.exc_info()[2]))
|
||||
libs_ignored.append(modulename)
|
||||
Logger.debug('{0}: Ignored <{1}> (import error)'.format(
|
||||
category.capitalize(), option))
|
||||
Logger.trace('', exc_info=e)
|
||||
|
||||
except CoreCriticalException as e:
|
||||
errs.append((option, e, sys.exc_info()[2]))
|
||||
Logger.error('{0}: Unable to use {1}'.format(
|
||||
category.capitalize(), option))
|
||||
Logger.error(
|
||||
'{0}: The module raised an important error: {1!r}'.format(
|
||||
category.capitalize(), e.message))
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
errs.append((option, e, sys.exc_info()[2]))
|
||||
libs_ignored.append(modulename)
|
||||
Logger.trace('{0}: Unable to use {1}'.format(
|
||||
category.capitalize(), option))
|
||||
Logger.trace('', exc_info=e)
|
||||
|
||||
err = '\n'.join(['{} - {}: {}\n{}'.format(opt, e.__class__.__name__, e,
|
||||
''.join(traceback.format_tb(tb))) for opt, e, tb in errs])
|
||||
Logger.critical(
|
||||
'{0}: Unable to find any valuable {0} provider. Please enable '
|
||||
'debug logging (e.g. add -d if running from the command line, or '
|
||||
'change the log level in the config) and re-run your app to '
|
||||
'identify potential causes\n{1}'.format(category.capitalize(), err))
|
||||
|
||||
|
||||
def core_register_libs(category, libs, base='kivy.core'):
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
return
|
||||
category = category.lower()
|
||||
kivy_options = kivy.kivy_options[category]
|
||||
libs_loadable = {}
|
||||
libs_ignored = []
|
||||
|
||||
for option, lib in libs:
|
||||
# module activated in config ?
|
||||
if option not in kivy_options:
|
||||
Logger.debug('{0}: option <{1}> ignored by config'.format(
|
||||
category.capitalize(), option))
|
||||
libs_ignored.append(lib)
|
||||
continue
|
||||
libs_loadable[option] = lib
|
||||
|
||||
libs_loaded = []
|
||||
for item in kivy_options:
|
||||
try:
|
||||
# import module
|
||||
try:
|
||||
lib = libs_loadable[item]
|
||||
except KeyError:
|
||||
continue
|
||||
importlib.__import__(name='{2}.{0}.{1}'.format(category, lib, base),
|
||||
globals=globals(),
|
||||
locals=locals(),
|
||||
fromlist=[lib],
|
||||
level=0)
|
||||
|
||||
libs_loaded.append(lib)
|
||||
|
||||
except Exception as e:
|
||||
Logger.trace('{0}: Unable to use <{1}> as loader!'.format(
|
||||
category.capitalize(), option))
|
||||
Logger.trace('', exc_info=e)
|
||||
libs_ignored.append(lib)
|
||||
|
||||
Logger.info('{0}: Providers: {1} {2}'.format(
|
||||
category.capitalize(),
|
||||
', '.join(libs_loaded),
|
||||
'({0} ignored)'.format(
|
||||
', '.join(libs_ignored)) if libs_ignored else ''))
|
||||
return libs_loaded
|
||||
|
||||
|
||||
def handle_win_lib_import_error(category, provider, mod_name):
|
||||
if sys.platform != 'win32':
|
||||
return
|
||||
|
||||
assert mod_name.startswith('kivy.')
|
||||
kivy_root = os.path.dirname(kivy.__file__)
|
||||
dirs = mod_name[5:].split('.')
|
||||
mod_path = os.path.join(kivy_root, *dirs)
|
||||
|
||||
# get the full expected path to the compiled pyd file
|
||||
# filename is <debug>.cp<major><minor>-<platform>.pyd
|
||||
# https://github.com/python/cpython/blob/master/Doc/whatsnew/3.5.rst
|
||||
if hasattr(sys, 'gettotalrefcount'): # debug
|
||||
mod_path += '._d'
|
||||
mod_path += '.cp{}{}-{}.pyd'.format(
|
||||
sys.version_info.major, sys.version_info.minor,
|
||||
sysconfig.get_platform().replace('-', '_'))
|
||||
|
||||
# does the compiled pyd exist at all?
|
||||
if not os.path.exists(mod_path):
|
||||
Logger.debug(
|
||||
'{}: Failed trying to import "{}" for provider {}. Compiled file '
|
||||
'does not exist. Have you perhaps forgotten to compile Kivy, or '
|
||||
'did not install all required dependencies?'.format(
|
||||
category, provider, mod_path))
|
||||
return
|
||||
|
||||
# tell user to provide dependency walker
|
||||
env_var = 'KIVY_{}_DEPENDENCY_WALKER'.format(provider.upper())
|
||||
if env_var not in os.environ:
|
||||
Logger.debug(
|
||||
'{0}: Failed trying to import the "{1}" provider from "{2}". '
|
||||
'This error is often encountered when a dependency is missing,'
|
||||
' or if there are multiple copies of the same dependency dll on '
|
||||
'the Windows PATH and they are incompatible with each other. '
|
||||
'This can occur if you are mixing installations (such as different'
|
||||
' python installations, like anaconda python and a system python) '
|
||||
'or if another unrelated program added its directory to the PATH. '
|
||||
'Please examine your PATH and python installation for potential '
|
||||
'issues. To further troubleshoot a "DLL load failed" error, '
|
||||
'please download '
|
||||
'"Dependency Walker" (64 or 32 bit version - matching your python '
|
||||
'bitness) from dependencywalker.com and set the environment '
|
||||
'variable {3} to the full path of the downloaded depends.exe file '
|
||||
'and rerun your application to generate an error report'.
|
||||
format(category, provider, mod_path, env_var))
|
||||
return
|
||||
|
||||
depends_bin = os.environ[env_var]
|
||||
if not os.path.exists(depends_bin):
|
||||
raise ValueError('"{}" provided in {} does not exist'.format(
|
||||
depends_bin, env_var))
|
||||
|
||||
# make file for the resultant log
|
||||
fd, temp_file = tempfile.mkstemp(
|
||||
suffix='.dwi', prefix='kivy_depends_{}_log_'.format(provider),
|
||||
dir=os.path.expanduser('~/'))
|
||||
os.close(fd)
|
||||
|
||||
Logger.info(
|
||||
'{}: Running dependency walker "{}" on "{}" to generate '
|
||||
'troubleshooting log. Please wait for it to complete'.format(
|
||||
category, depends_bin, mod_path))
|
||||
Logger.debug(
|
||||
'{}: Dependency walker command is "{}"'.format(
|
||||
category,
|
||||
[depends_bin, '/c', '/od:{}'.format(temp_file), mod_path]))
|
||||
|
||||
try:
|
||||
subprocess.check_output([
|
||||
depends_bin, '/c', '/od:{}'.format(temp_file), mod_path])
|
||||
except subprocess.CalledProcessError as exc:
|
||||
if exc.returncode >= 0x00010000:
|
||||
Logger.error(
|
||||
'{}: Dependency walker failed with error code "{}". No '
|
||||
'error report was generated'.
|
||||
format(category, exc.returncode))
|
||||
return
|
||||
|
||||
Logger.info(
|
||||
'{}: dependency walker generated "{}" containing troubleshooting '
|
||||
'information about provider {} and its failing file "{} ({})". You '
|
||||
'can open the file in dependency walker to view any potential issues '
|
||||
'and troubleshoot it yourself. '
|
||||
'To share the file with the Kivy developers and request support, '
|
||||
'please contact us at our support channels '
|
||||
'https://kivy.org/doc/master/contact.html (not on github, unless '
|
||||
'it\'s truly a bug). Make sure to provide the generated file as well '
|
||||
'as the *complete* Kivy log being printed here. Keep in mind the '
|
||||
'generated dependency walker log file contains paths to dlls on your '
|
||||
'system used by kivy or its dependencies to help troubleshoot them, '
|
||||
'and these paths may include your name in them. Please view the '
|
||||
'log file in dependency walker before sharing to ensure you are not '
|
||||
'sharing sensitive paths'.format(
|
||||
category, temp_file, provider, mod_name, mod_path))
|
||||
BIN
kivy/core/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
244
kivy/core/audio/__init__.py
Normal file
244
kivy/core/audio/__init__.py
Normal file
@ -0,0 +1,244 @@
|
||||
'''
|
||||
Audio
|
||||
=====
|
||||
|
||||
Load an audio sound and play it with::
|
||||
|
||||
from kivy.core.audio import SoundLoader
|
||||
|
||||
sound = SoundLoader.load('mytest.wav')
|
||||
if sound:
|
||||
print("Sound found at %s" % sound.source)
|
||||
print("Sound is %.3f seconds" % sound.length)
|
||||
sound.play()
|
||||
|
||||
You should not use the Sound class directly. The class returned by
|
||||
:func:`SoundLoader.load` will be the best sound provider for that particular
|
||||
file type, so it might return different Sound classes depending the file type.
|
||||
|
||||
Event dispatching and state changes
|
||||
-----------------------------------
|
||||
|
||||
Audio is often processed in parallel to your code. This means you often need to
|
||||
enter the Kivy :func:`eventloop <kivy.base.EventLoopBase>` in order to allow
|
||||
events and state changes to be dispatched correctly.
|
||||
|
||||
You seldom need to worry about this as Kivy apps typically always
|
||||
require this event loop for the GUI to remain responsive, but it is good to
|
||||
keep this in mind when debugging or running in a
|
||||
`REPL <https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop>`_
|
||||
(Read-eval-print loop).
|
||||
|
||||
.. versionchanged:: 1.10.0
|
||||
The pygst and gi providers have been removed.
|
||||
|
||||
.. versionchanged:: 1.8.0
|
||||
There are now 2 distinct Gstreamer implementations: one using Gi/Gst
|
||||
working for both Python 2+3 with Gstreamer 1.0, and one using PyGST
|
||||
working only for Python 2 + Gstreamer 0.10.
|
||||
|
||||
.. note::
|
||||
|
||||
The core audio library does not support recording audio. If you require
|
||||
this functionality, please refer to the
|
||||
`audiostream <https://github.com/kivy/audiostream>`_ extension.
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('Sound', 'SoundLoader')
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.core import core_register_libs
|
||||
from kivy.resources import resource_find
|
||||
from kivy.properties import StringProperty, NumericProperty, OptionProperty, \
|
||||
AliasProperty, BooleanProperty, BoundedNumericProperty
|
||||
from kivy.utils import platform
|
||||
from kivy.setupconfig import USE_SDL2
|
||||
|
||||
from sys import float_info
|
||||
|
||||
|
||||
class SoundLoader:
|
||||
'''Load a sound, using the best loader for the given file type.
|
||||
'''
|
||||
|
||||
_classes = []
|
||||
|
||||
@staticmethod
|
||||
def register(classobj):
|
||||
'''Register a new class to load the sound.'''
|
||||
Logger.debug('Audio: register %s' % classobj.__name__)
|
||||
SoundLoader._classes.append(classobj)
|
||||
|
||||
@staticmethod
|
||||
def load(filename):
|
||||
'''Load a sound, and return a Sound() instance.'''
|
||||
rfn = resource_find(filename)
|
||||
if rfn is not None:
|
||||
filename = rfn
|
||||
ext = filename.split('.')[-1].lower()
|
||||
if '?' in ext:
|
||||
ext = ext.split('?')[0]
|
||||
for classobj in SoundLoader._classes:
|
||||
if ext in classobj.extensions():
|
||||
return classobj(source=filename)
|
||||
Logger.warning('Audio: Unable to find a loader for <%s>' %
|
||||
filename)
|
||||
return None
|
||||
|
||||
|
||||
class Sound(EventDispatcher):
|
||||
'''Represents a sound to play. This class is abstract, and cannot be used
|
||||
directly.
|
||||
|
||||
Use SoundLoader to load a sound.
|
||||
|
||||
:Events:
|
||||
`on_play`: None
|
||||
Fired when the sound is played.
|
||||
`on_stop`: None
|
||||
Fired when the sound is stopped.
|
||||
'''
|
||||
|
||||
source = StringProperty(None)
|
||||
'''Filename / source of your audio file.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
|
||||
:attr:`source` is a :class:`~kivy.properties.StringProperty` that defaults
|
||||
to None and is read-only. Use the :meth:`SoundLoader.load` for loading
|
||||
audio.
|
||||
'''
|
||||
|
||||
volume = NumericProperty(1.)
|
||||
'''Volume, in the range 0-1. 1 means full volume, 0 means mute.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
|
||||
:attr:`volume` is a :class:`~kivy.properties.NumericProperty` and defaults
|
||||
to 1.
|
||||
'''
|
||||
|
||||
pitch = BoundedNumericProperty(1., min=float_info.epsilon)
|
||||
'''Pitch of a sound. 2 is an octave higher, .5 one below. This is only
|
||||
implemented for SDL2 audio provider yet.
|
||||
|
||||
.. versionadded:: 1.10.0
|
||||
|
||||
:attr:`pitch` is a :class:`~kivy.properties.NumericProperty` and defaults
|
||||
to 1.
|
||||
'''
|
||||
|
||||
state = OptionProperty('stop', options=('stop', 'play'))
|
||||
'''State of the sound, one of 'stop' or 'play'.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
|
||||
:attr:`state` is a read-only :class:`~kivy.properties.OptionProperty`.'''
|
||||
|
||||
loop = BooleanProperty(False)
|
||||
'''Set to True if the sound should automatically loop when it finishes.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
:attr:`loop` is a :class:`~kivy.properties.BooleanProperty` and defaults to
|
||||
False.'''
|
||||
|
||||
#
|
||||
# deprecated
|
||||
#
|
||||
def _get_status(self):
|
||||
return self.state
|
||||
status = AliasProperty(
|
||||
_get_status, None, bind=('state', ), deprecated=True)
|
||||
'''
|
||||
.. deprecated:: 1.3.0
|
||||
Use :attr:`state` instead.
|
||||
'''
|
||||
|
||||
def _get_filename(self):
|
||||
return self.source
|
||||
filename = AliasProperty(
|
||||
_get_filename, None, bind=('source', ), deprecated=True)
|
||||
'''
|
||||
.. deprecated:: 1.3.0
|
||||
Use :attr:`source` instead.
|
||||
'''
|
||||
|
||||
__events__ = ('on_play', 'on_stop')
|
||||
|
||||
def on_source(self, instance, filename):
|
||||
self.unload()
|
||||
if filename is None:
|
||||
return
|
||||
self.load()
|
||||
|
||||
def get_pos(self):
|
||||
'''
|
||||
Returns the current position of the audio file.
|
||||
Returns 0 if not playing.
|
||||
|
||||
.. versionadded:: 1.4.1
|
||||
'''
|
||||
return 0
|
||||
|
||||
def _get_length(self):
|
||||
return 0
|
||||
|
||||
length = property(lambda self: self._get_length(),
|
||||
doc='Get length of the sound (in seconds).')
|
||||
|
||||
def load(self):
|
||||
'''Load the file into memory.'''
|
||||
pass
|
||||
|
||||
def unload(self):
|
||||
'''Unload the file from memory.'''
|
||||
pass
|
||||
|
||||
def play(self):
|
||||
'''Play the file.'''
|
||||
self.state = 'play'
|
||||
self.dispatch('on_play')
|
||||
|
||||
def stop(self):
|
||||
'''Stop playback.'''
|
||||
self.state = 'stop'
|
||||
self.dispatch('on_stop')
|
||||
|
||||
def seek(self, position):
|
||||
'''Go to the <position> (in seconds).
|
||||
|
||||
.. note::
|
||||
Most sound providers cannot seek when the audio is stopped.
|
||||
Play then seek.
|
||||
'''
|
||||
pass
|
||||
|
||||
def on_play(self):
|
||||
pass
|
||||
|
||||
def on_stop(self):
|
||||
pass
|
||||
|
||||
|
||||
# Little trick here, don't activate gstreamer on window
|
||||
# seem to have lot of crackle or something...
|
||||
audio_libs = []
|
||||
if platform == 'android':
|
||||
audio_libs += [('android', 'audio_android')]
|
||||
elif platform in ('macosx', 'ios'):
|
||||
audio_libs += [('avplayer', 'audio_avplayer')]
|
||||
try:
|
||||
from kivy.lib.gstplayer import GstPlayer # NOQA
|
||||
audio_libs += [('gstplayer', 'audio_gstplayer')]
|
||||
except ImportError:
|
||||
pass
|
||||
audio_libs += [('ffpyplayer', 'audio_ffpyplayer')]
|
||||
if USE_SDL2:
|
||||
audio_libs += [('sdl2', 'audio_sdl2')]
|
||||
else:
|
||||
audio_libs += [('pygame', 'audio_pygame')]
|
||||
|
||||
libs_loaded = core_register_libs('audio', audio_libs)
|
||||
BIN
kivy/core/audio/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/audio/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/audio/__pycache__/audio_android.cpython-310.pyc
Normal file
BIN
kivy/core/audio/__pycache__/audio_android.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/audio/__pycache__/audio_avplayer.cpython-310.pyc
Normal file
BIN
kivy/core/audio/__pycache__/audio_avplayer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/audio/__pycache__/audio_ffpyplayer.cpython-310.pyc
Normal file
BIN
kivy/core/audio/__pycache__/audio_ffpyplayer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/audio/__pycache__/audio_gstplayer.cpython-310.pyc
Normal file
BIN
kivy/core/audio/__pycache__/audio_gstplayer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/audio/__pycache__/audio_pygame.cpython-310.pyc
Normal file
BIN
kivy/core/audio/__pycache__/audio_pygame.cpython-310.pyc
Normal file
Binary file not shown.
104
kivy/core/audio/audio_android.py
Normal file
104
kivy/core/audio/audio_android.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""
|
||||
AudioAndroid: Kivy audio implementation for Android using native API
|
||||
"""
|
||||
|
||||
__all__ = ("SoundAndroidPlayer", )
|
||||
|
||||
from jnius import autoclass, java_method, PythonJavaClass
|
||||
from android import api_version
|
||||
from kivy.core.audio import Sound, SoundLoader
|
||||
|
||||
|
||||
MediaPlayer = autoclass("android.media.MediaPlayer")
|
||||
AudioManager = autoclass("android.media.AudioManager")
|
||||
if api_version >= 21:
|
||||
AudioAttributesBuilder = autoclass("android.media.AudioAttributes$Builder")
|
||||
|
||||
|
||||
class OnCompletionListener(PythonJavaClass):
|
||||
__javainterfaces__ = ["android/media/MediaPlayer$OnCompletionListener"]
|
||||
__javacontext__ = "app"
|
||||
|
||||
def __init__(self, callback, **kwargs):
|
||||
super(OnCompletionListener, self).__init__(**kwargs)
|
||||
self.callback = callback
|
||||
|
||||
@java_method("(Landroid/media/MediaPlayer;)V")
|
||||
def onCompletion(self, mp):
|
||||
self.callback()
|
||||
|
||||
|
||||
class SoundAndroidPlayer(Sound):
|
||||
@staticmethod
|
||||
def extensions():
|
||||
return ("mp3", "mp4", "aac", "3gp", "flac", "mkv", "wav", "ogg", "m4a",
|
||||
"gsm", "mid", "xmf", "mxmf", "rtttl", "rtx", "ota", "imy")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._mediaplayer = None
|
||||
self._completion_listener = None
|
||||
super(SoundAndroidPlayer, self).__init__(**kwargs)
|
||||
|
||||
def load(self):
|
||||
self.unload()
|
||||
self._mediaplayer = MediaPlayer()
|
||||
if api_version >= 21:
|
||||
self._mediaplayer.setAudioAttributes(
|
||||
AudioAttributesBuilder()
|
||||
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
|
||||
.build())
|
||||
else:
|
||||
self._mediaplayer.setAudioStreamType(AudioManager.STREAM_MUSIC)
|
||||
self._mediaplayer.setDataSource(self.source)
|
||||
self._completion_listener = OnCompletionListener(
|
||||
self._completion_callback
|
||||
)
|
||||
self._mediaplayer.setOnCompletionListener(self._completion_listener)
|
||||
self._mediaplayer.prepare()
|
||||
|
||||
def unload(self):
|
||||
if self._mediaplayer:
|
||||
self._mediaplayer.release()
|
||||
self._mediaplayer = None
|
||||
|
||||
def play(self):
|
||||
if not self._mediaplayer:
|
||||
return
|
||||
self._mediaplayer.start()
|
||||
super(SoundAndroidPlayer, self).play()
|
||||
|
||||
def stop(self):
|
||||
if not self._mediaplayer:
|
||||
return
|
||||
self._mediaplayer.stop()
|
||||
self._mediaplayer.prepare()
|
||||
|
||||
def seek(self, position):
|
||||
if not self._mediaplayer:
|
||||
return
|
||||
self._mediaplayer.seekTo(float(position) * 1000)
|
||||
|
||||
def get_pos(self):
|
||||
if self._mediaplayer:
|
||||
return self._mediaplayer.getCurrentPosition() / 1000.
|
||||
return super(SoundAndroidPlayer, self).get_pos()
|
||||
|
||||
def on_volume(self, instance, volume):
|
||||
if self._mediaplayer:
|
||||
volume = float(volume)
|
||||
self._mediaplayer.setVolume(volume, volume)
|
||||
|
||||
def _completion_callback(self):
|
||||
super(SoundAndroidPlayer, self).stop()
|
||||
|
||||
def _get_length(self):
|
||||
if self._mediaplayer:
|
||||
return self._mediaplayer.getDuration() / 1000.
|
||||
return super(SoundAndroidPlayer, self)._get_length()
|
||||
|
||||
def on_loop(self, instance, loop):
|
||||
if self._mediaplayer:
|
||||
self._mediaplayer.setLooping(loop)
|
||||
|
||||
|
||||
SoundLoader.register(SoundAndroidPlayer)
|
||||
72
kivy/core/audio/audio_avplayer.py
Normal file
72
kivy/core/audio/audio_avplayer.py
Normal file
@ -0,0 +1,72 @@
|
||||
'''
|
||||
AudioAvplayer: implementation of Sound using pyobjus / AVFoundation.
|
||||
Works on iOS / OSX.
|
||||
'''
|
||||
|
||||
__all__ = ('SoundAvplayer', )
|
||||
|
||||
from kivy.core.audio import Sound, SoundLoader
|
||||
from pyobjus import autoclass
|
||||
from pyobjus.dylib_manager import load_framework, INCLUDE
|
||||
|
||||
load_framework(INCLUDE.AVFoundation)
|
||||
AVAudioPlayer = autoclass("AVAudioPlayer")
|
||||
NSURL = autoclass("NSURL")
|
||||
NSString = autoclass("NSString")
|
||||
|
||||
|
||||
class SoundAvplayer(Sound):
|
||||
@staticmethod
|
||||
def extensions():
|
||||
# taken from https://goo.gl/015kvU
|
||||
return ("aac", "adts", "aif", "aiff", "aifc", "caf", "mp3", "mp4",
|
||||
"m4a", "snd", "au", "sd2", "wav")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._avplayer = None
|
||||
super(SoundAvplayer, self).__init__(**kwargs)
|
||||
|
||||
def load(self):
|
||||
self.unload()
|
||||
fn = NSString.alloc().initWithUTF8String_(self.source)
|
||||
url = NSURL.alloc().initFileURLWithPath_(fn)
|
||||
self._avplayer = AVAudioPlayer.alloc().initWithContentsOfURL_error_(
|
||||
url, None)
|
||||
|
||||
def unload(self):
|
||||
self.stop()
|
||||
self._avplayer = None
|
||||
|
||||
def play(self):
|
||||
if not self._avplayer:
|
||||
return
|
||||
self._avplayer.play()
|
||||
super(SoundAvplayer, self).play()
|
||||
|
||||
def stop(self):
|
||||
if not self._avplayer:
|
||||
return
|
||||
self._avplayer.stop()
|
||||
super(SoundAvplayer, self).stop()
|
||||
|
||||
def seek(self, position):
|
||||
if not self._avplayer:
|
||||
return
|
||||
self._avplayer.playAtTime_(float(position))
|
||||
|
||||
def get_pos(self):
|
||||
if self._avplayer:
|
||||
return self._avplayer.currentTime
|
||||
return super(SoundAvplayer, self).get_pos()
|
||||
|
||||
def on_volume(self, instance, volume):
|
||||
if self._avplayer:
|
||||
self._avplayer.volume = float(volume)
|
||||
|
||||
def _get_length(self):
|
||||
if self._avplayer:
|
||||
return self._avplayer.duration
|
||||
return super(SoundAvplayer, self)._get_length()
|
||||
|
||||
|
||||
SoundLoader.register(SoundAvplayer)
|
||||
185
kivy/core/audio/audio_ffpyplayer.py
Normal file
185
kivy/core/audio/audio_ffpyplayer.py
Normal file
@ -0,0 +1,185 @@
|
||||
'''
|
||||
FFmpeg based audio player
|
||||
=========================
|
||||
|
||||
To use, you need to install ffpyplayer and have a compiled ffmpeg shared
|
||||
library.
|
||||
|
||||
https://github.com/matham/ffpyplayer
|
||||
|
||||
The docs there describe how to set this up. But briefly, first you need to
|
||||
compile ffmpeg using the shared flags while disabling the static flags (you'll
|
||||
probably have to set the fPIC flag, e.g. CFLAGS=-fPIC). Here's some
|
||||
instructions: https://trac.ffmpeg.org/wiki/CompilationGuide. For Windows, you
|
||||
can download compiled GPL binaries from http://ffmpeg.zeranoe.com/builds/.
|
||||
Similarly, you should download SDL.
|
||||
|
||||
Now, you should a ffmpeg and sdl directory. In each, you should have a include,
|
||||
bin, and lib directory, where e.g. for Windows, lib contains the .dll.a files,
|
||||
while bin contains the actual dlls. The include directory holds the headers.
|
||||
The bin directory is only needed if the shared libraries are not already on
|
||||
the path. In the environment define FFMPEG_ROOT and SDL_ROOT, each pointing to
|
||||
the ffmpeg, and SDL directories, respectively. (If you're using SDL2,
|
||||
the include directory will contain a directory called SDL2, which then holds
|
||||
the headers).
|
||||
|
||||
Once defined, download the ffpyplayer git and run
|
||||
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
Finally, before running you need to ensure that ffpyplayer is in python's path.
|
||||
|
||||
..Note::
|
||||
|
||||
When kivy exits by closing the window while the audio is playing,
|
||||
it appears that the __del__method of SoundFFPy
|
||||
is not called. Because of this the SoundFFPy object is not
|
||||
properly deleted when kivy exits. The consequence is that because
|
||||
MediaPlayer creates internal threads which do not have their daemon
|
||||
flag set, when the main threads exists it'll hang and wait for the other
|
||||
MediaPlayer threads to exit. But since __del__ is not called to delete the
|
||||
MediaPlayer object, those threads will remain alive hanging kivy. What this
|
||||
means is that you have to be sure to delete the MediaPlayer object before
|
||||
kivy exits by setting it to None.
|
||||
'''
|
||||
|
||||
__all__ = ('SoundFFPy', )
|
||||
|
||||
try:
|
||||
import ffpyplayer
|
||||
from ffpyplayer.player import MediaPlayer
|
||||
from ffpyplayer.tools import set_log_callback, get_log_callback, formats_in
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.logger import Logger
|
||||
from kivy.core.audio import Sound, SoundLoader
|
||||
from kivy.weakmethod import WeakMethod
|
||||
import time
|
||||
|
||||
try:
|
||||
Logger.info(
|
||||
'SoundFFPy: Using ffpyplayer {}'.format(ffpyplayer.__version__))
|
||||
except:
|
||||
Logger.info('SoundFFPy: Using ffpyplayer {}'.format(ffpyplayer.version))
|
||||
|
||||
|
||||
logger_func = {'quiet': Logger.critical, 'panic': Logger.critical,
|
||||
'fatal': Logger.critical, 'error': Logger.error,
|
||||
'warning': Logger.warning, 'info': Logger.info,
|
||||
'verbose': Logger.debug, 'debug': Logger.debug}
|
||||
|
||||
|
||||
def _log_callback(message, level):
|
||||
message = message.strip()
|
||||
if message:
|
||||
logger_func[level]('ffpyplayer: {}'.format(message))
|
||||
|
||||
|
||||
class SoundFFPy(Sound):
|
||||
|
||||
@staticmethod
|
||||
def extensions():
|
||||
return formats_in
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._ffplayer = None
|
||||
self.quitted = False
|
||||
self._log_callback_set = False
|
||||
self._state = ''
|
||||
self.state = 'stop'
|
||||
|
||||
if not get_log_callback():
|
||||
set_log_callback(_log_callback)
|
||||
self._log_callback_set = True
|
||||
|
||||
super(SoundFFPy, self).__init__(**kwargs)
|
||||
|
||||
def __del__(self):
|
||||
self.unload()
|
||||
if self._log_callback_set:
|
||||
set_log_callback(None)
|
||||
|
||||
def _player_callback(self, selector, value):
|
||||
if self._ffplayer is None:
|
||||
return
|
||||
if selector == 'quit':
|
||||
def close(*args):
|
||||
self.quitted = True
|
||||
self.unload()
|
||||
Clock.schedule_once(close, 0)
|
||||
elif selector == 'eof':
|
||||
Clock.schedule_once(self._do_eos, 0)
|
||||
|
||||
def load(self):
|
||||
self.unload()
|
||||
ff_opts = {'vn': True, 'sn': True} # only audio
|
||||
self._ffplayer = MediaPlayer(self.source,
|
||||
callback=self._player_callback,
|
||||
loglevel='info', ff_opts=ff_opts)
|
||||
player = self._ffplayer
|
||||
player.set_volume(self.volume)
|
||||
player.toggle_pause()
|
||||
self._state = 'paused'
|
||||
# wait until loaded or failed, shouldn't take long, but just to make
|
||||
# sure metadata is available.
|
||||
s = time.perf_counter()
|
||||
while (player.get_metadata()['duration'] is None and
|
||||
not self.quitted and time.perf_counter() - s < 10.):
|
||||
time.sleep(0.005)
|
||||
|
||||
def unload(self):
|
||||
if self._ffplayer:
|
||||
self._ffplayer = None
|
||||
self._state = ''
|
||||
self.state = 'stop'
|
||||
self.quitted = False
|
||||
|
||||
def play(self):
|
||||
if self._state == 'playing':
|
||||
super(SoundFFPy, self).play()
|
||||
return
|
||||
if not self._ffplayer:
|
||||
self.load()
|
||||
self._ffplayer.toggle_pause()
|
||||
self._state = 'playing'
|
||||
self.state = 'play'
|
||||
super(SoundFFPy, self).play()
|
||||
self.seek(0)
|
||||
|
||||
def stop(self):
|
||||
if self._ffplayer and self._state == 'playing':
|
||||
self._ffplayer.toggle_pause()
|
||||
self._state = 'paused'
|
||||
self.state = 'stop'
|
||||
super(SoundFFPy, self).stop()
|
||||
|
||||
def seek(self, position):
|
||||
if self._ffplayer is None:
|
||||
return
|
||||
self._ffplayer.seek(position, relative=False)
|
||||
|
||||
def get_pos(self):
|
||||
if self._ffplayer is not None:
|
||||
return self._ffplayer.get_pts()
|
||||
return 0
|
||||
|
||||
def on_volume(self, instance, volume):
|
||||
if self._ffplayer is not None:
|
||||
self._ffplayer.set_volume(volume)
|
||||
|
||||
def _get_length(self):
|
||||
if self._ffplayer is None:
|
||||
return super(SoundFFPy, self)._get_length()
|
||||
return self._ffplayer.get_metadata()['duration']
|
||||
|
||||
def _do_eos(self, *args):
|
||||
if not self.loop:
|
||||
self.stop()
|
||||
else:
|
||||
self.seek(0.)
|
||||
|
||||
|
||||
SoundLoader.register(SoundFFPy)
|
||||
101
kivy/core/audio/audio_gstplayer.py
Normal file
101
kivy/core/audio/audio_gstplayer.py
Normal file
@ -0,0 +1,101 @@
|
||||
'''
|
||||
Audio Gstplayer
|
||||
===============
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
Implementation of a VideoBase with Kivy :class:`~kivy.lib.gstplayer.GstPlayer`
|
||||
This player is the preferred player, using Gstreamer 1.0, working on both
|
||||
Python 2 and 3.
|
||||
'''
|
||||
|
||||
from kivy.lib.gstplayer import GstPlayer, get_gst_version
|
||||
from kivy.core.audio import Sound, SoundLoader
|
||||
from kivy.logger import Logger
|
||||
from kivy.compat import PY2
|
||||
from kivy.clock import Clock
|
||||
from os.path import realpath
|
||||
|
||||
if PY2:
|
||||
from urllib import pathname2url
|
||||
else:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
Logger.info('AudioGstplayer: Using Gstreamer {}'.format(
|
||||
'.'.join(map(str, get_gst_version()))))
|
||||
|
||||
|
||||
def _on_gstplayer_message(mtype, message):
|
||||
if mtype == 'error':
|
||||
Logger.error('AudioGstplayer: {}'.format(message))
|
||||
elif mtype == 'warning':
|
||||
Logger.warning('AudioGstplayer: {}'.format(message))
|
||||
elif mtype == 'info':
|
||||
Logger.info('AudioGstplayer: {}'.format(message))
|
||||
|
||||
|
||||
class SoundGstplayer(Sound):
|
||||
|
||||
@staticmethod
|
||||
def extensions():
|
||||
return ('wav', 'ogg', 'mp3', 'm4a', 'flac', 'mp4')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.player = None
|
||||
super(SoundGstplayer, self).__init__(**kwargs)
|
||||
|
||||
def _on_gst_eos_sync(self):
|
||||
Clock.schedule_once(self._on_gst_eos, 0)
|
||||
|
||||
def _on_gst_eos(self, *dt):
|
||||
if self.loop:
|
||||
self.player.stop()
|
||||
self.player.play()
|
||||
else:
|
||||
self.stop()
|
||||
|
||||
def load(self):
|
||||
self.unload()
|
||||
uri = self._get_uri()
|
||||
self.player = GstPlayer(uri, None, self._on_gst_eos_sync,
|
||||
_on_gstplayer_message)
|
||||
self.player.load()
|
||||
|
||||
def play(self):
|
||||
# we need to set the volume everytime, it seems that stopping + playing
|
||||
# the sound reset the volume.
|
||||
self.player.set_volume(self.volume)
|
||||
self.player.play()
|
||||
super(SoundGstplayer, self).play()
|
||||
|
||||
def stop(self):
|
||||
self.player.stop()
|
||||
super(SoundGstplayer, self).stop()
|
||||
|
||||
def unload(self):
|
||||
if self.player:
|
||||
self.player.unload()
|
||||
self.player = None
|
||||
|
||||
def seek(self, position):
|
||||
self.player.seek(position / self.length)
|
||||
|
||||
def get_pos(self):
|
||||
return self.player.get_position()
|
||||
|
||||
def _get_length(self):
|
||||
return self.player.get_duration()
|
||||
|
||||
def on_volume(self, instance, volume):
|
||||
self.player.set_volume(volume)
|
||||
|
||||
def _get_uri(self):
|
||||
uri = self.source
|
||||
if not uri:
|
||||
return
|
||||
if '://' not in uri:
|
||||
uri = 'file:' + pathname2url(realpath(uri))
|
||||
return uri
|
||||
|
||||
|
||||
SoundLoader.register(SoundGstplayer)
|
||||
127
kivy/core/audio/audio_pygame.py
Normal file
127
kivy/core/audio/audio_pygame.py
Normal file
@ -0,0 +1,127 @@
|
||||
'''
|
||||
AudioPygame: implementation of Sound with Pygame
|
||||
|
||||
.. warning::
|
||||
|
||||
Pygame has been deprecated and will be removed in the release after Kivy
|
||||
1.11.0.
|
||||
'''
|
||||
|
||||
__all__ = ('SoundPygame', )
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.utils import platform, deprecated
|
||||
from kivy.core.audio import Sound, SoundLoader
|
||||
|
||||
_platform = platform
|
||||
try:
|
||||
if _platform == 'android':
|
||||
try:
|
||||
import android.mixer as mixer
|
||||
except ImportError:
|
||||
# old python-for-android version
|
||||
import android_mixer as mixer
|
||||
else:
|
||||
from pygame import mixer
|
||||
except:
|
||||
raise
|
||||
|
||||
# init pygame sound
|
||||
mixer.pre_init(44100, -16, 2, 1024)
|
||||
mixer.init()
|
||||
mixer.set_num_channels(32)
|
||||
|
||||
|
||||
class SoundPygame(Sound):
|
||||
|
||||
# XXX we don't set __slots__ here, to automatically add
|
||||
# a dictionary. We need that to be able to use weakref for
|
||||
# SoundPygame object. Otherwise, it failed with:
|
||||
# TypeError: cannot create weak reference to 'SoundPygame' object
|
||||
# We use our clock in play() method.
|
||||
# __slots__ = ('_data', '_channel')
|
||||
_check_play_ev = None
|
||||
|
||||
@staticmethod
|
||||
def extensions():
|
||||
if _platform == 'android':
|
||||
return ('wav', 'ogg', 'mp3', 'm4a')
|
||||
return ('wav', 'ogg')
|
||||
|
||||
@deprecated(
|
||||
msg='Pygame has been deprecated and will be removed after 1.11.0')
|
||||
def __init__(self, **kwargs):
|
||||
self._data = None
|
||||
self._channel = None
|
||||
super(SoundPygame, self).__init__(**kwargs)
|
||||
|
||||
def _check_play(self, dt):
|
||||
if self._channel is None:
|
||||
return False
|
||||
if self._channel.get_busy():
|
||||
return
|
||||
if self.loop:
|
||||
def do_loop(dt):
|
||||
self.play()
|
||||
Clock.schedule_once(do_loop)
|
||||
else:
|
||||
self.stop()
|
||||
return False
|
||||
|
||||
def play(self):
|
||||
if not self._data:
|
||||
return
|
||||
self._data.set_volume(self.volume)
|
||||
self._channel = self._data.play()
|
||||
self.start_time = Clock.time()
|
||||
# schedule event to check if the sound is still playing or not
|
||||
self._check_play_ev = Clock.schedule_interval(self._check_play, 0.1)
|
||||
super(SoundPygame, self).play()
|
||||
|
||||
def stop(self):
|
||||
if not self._data:
|
||||
return
|
||||
self._data.stop()
|
||||
# ensure we don't have anymore the callback
|
||||
if self._check_play_ev is not None:
|
||||
self._check_play_ev.cancel()
|
||||
self._check_play_ev = None
|
||||
self._channel = None
|
||||
super(SoundPygame, self).stop()
|
||||
|
||||
def load(self):
|
||||
self.unload()
|
||||
if self.source is None:
|
||||
return
|
||||
self._data = mixer.Sound(self.source)
|
||||
|
||||
def unload(self):
|
||||
self.stop()
|
||||
self._data = None
|
||||
|
||||
def seek(self, position):
|
||||
if not self._data:
|
||||
return
|
||||
if _platform == 'android' and self._channel:
|
||||
self._channel.seek(position)
|
||||
|
||||
def get_pos(self):
|
||||
if self._data is not None and self._channel:
|
||||
if _platform == 'android':
|
||||
return self._channel.get_pos()
|
||||
return Clock.time() - self.start_time
|
||||
return 0
|
||||
|
||||
def on_volume(self, instance, volume):
|
||||
if self._data is not None:
|
||||
self._data.set_volume(volume)
|
||||
|
||||
def _get_length(self):
|
||||
if _platform == 'android' and self._channel:
|
||||
return self._channel.get_length()
|
||||
if self._data is not None:
|
||||
return self._data.get_length()
|
||||
return super(SoundPygame, self)._get_length()
|
||||
|
||||
|
||||
SoundLoader.register(SoundPygame)
|
||||
BIN
kivy/core/audio/audio_sdl2.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/core/audio/audio_sdl2.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
151
kivy/core/camera/__init__.py
Normal file
151
kivy/core/camera/__init__.py
Normal file
@ -0,0 +1,151 @@
|
||||
'''
|
||||
Camera
|
||||
======
|
||||
|
||||
Core class for acquiring the camera and converting its input into a
|
||||
:class:`~kivy.graphics.texture.Texture`.
|
||||
|
||||
.. versionchanged:: 1.10.0
|
||||
The pygst and videocapture providers have been removed.
|
||||
|
||||
.. versionchanged:: 1.8.0
|
||||
There is now 2 distinct Gstreamer implementation: one using Gi/Gst
|
||||
working for both Python 2+3 with Gstreamer 1.0, and one using PyGST
|
||||
working only for Python 2 + Gstreamer 0.10.
|
||||
'''
|
||||
|
||||
__all__ = ('CameraBase', 'Camera')
|
||||
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.logger import Logger
|
||||
from kivy.core import core_select_lib
|
||||
|
||||
|
||||
class CameraBase(EventDispatcher):
|
||||
'''Abstract Camera Widget class.
|
||||
|
||||
Concrete camera classes must implement initialization and
|
||||
frame capturing to a buffer that can be uploaded to the gpu.
|
||||
|
||||
:Parameters:
|
||||
`index`: int
|
||||
Source index of the camera.
|
||||
`size`: tuple (int, int)
|
||||
Size at which the image is drawn. If no size is specified,
|
||||
it defaults to the resolution of the camera image.
|
||||
`resolution`: tuple (int, int)
|
||||
Resolution to try to request from the camera.
|
||||
Used in the gstreamer pipeline by forcing the appsink caps
|
||||
to this resolution. If the camera doesn't support the resolution,
|
||||
a negotiation error might be thrown.
|
||||
|
||||
:Events:
|
||||
`on_load`
|
||||
Fired when the camera is loaded and the texture has become
|
||||
available.
|
||||
`on_texture`
|
||||
Fired each time the camera texture is updated.
|
||||
'''
|
||||
|
||||
__events__ = ('on_load', 'on_texture')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault('stopped', False)
|
||||
kwargs.setdefault('resolution', (640, 480))
|
||||
kwargs.setdefault('index', 0)
|
||||
|
||||
self.stopped = kwargs.get('stopped')
|
||||
self._resolution = kwargs.get('resolution')
|
||||
self._index = kwargs.get('index')
|
||||
self._buffer = None
|
||||
self._format = 'rgb'
|
||||
self._texture = None
|
||||
self.capture_device = None
|
||||
kwargs.setdefault('size', self._resolution)
|
||||
|
||||
super(CameraBase, self).__init__()
|
||||
|
||||
self.init_camera()
|
||||
|
||||
if not self.stopped:
|
||||
self.start()
|
||||
|
||||
def _set_resolution(self, res):
|
||||
self._resolution = res
|
||||
self.init_camera()
|
||||
|
||||
def _get_resolution(self):
|
||||
return self._resolution
|
||||
|
||||
resolution = property(lambda self: self._get_resolution(),
|
||||
lambda self, x: self._set_resolution(x),
|
||||
doc='Resolution of camera capture (width, height)')
|
||||
|
||||
def _set_index(self, x):
|
||||
if x == self._index:
|
||||
return
|
||||
self._index = x
|
||||
self.init_camera()
|
||||
|
||||
def _get_index(self):
|
||||
return self._x
|
||||
|
||||
index = property(lambda self: self._get_index(),
|
||||
lambda self, x: self._set_index(x),
|
||||
doc='Source index of the camera')
|
||||
|
||||
def _get_texture(self):
|
||||
return self._texture
|
||||
texture = property(lambda self: self._get_texture(),
|
||||
doc='Return the camera texture with the latest capture')
|
||||
|
||||
def init_camera(self):
|
||||
'''Initialise the camera (internal)'''
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
'''Start the camera acquire'''
|
||||
self.stopped = False
|
||||
|
||||
def stop(self):
|
||||
'''Release the camera'''
|
||||
self.stopped = True
|
||||
|
||||
def _update(self, dt):
|
||||
'''Update the camera (internal)'''
|
||||
pass
|
||||
|
||||
def _copy_to_gpu(self):
|
||||
'''Copy the the buffer into the texture'''
|
||||
if self._texture is None:
|
||||
Logger.debug('Camera: copy_to_gpu() failed, _texture is None !')
|
||||
return
|
||||
self._texture.blit_buffer(self._buffer, colorfmt=self._format)
|
||||
self._buffer = None
|
||||
self.dispatch('on_texture')
|
||||
|
||||
def on_texture(self):
|
||||
pass
|
||||
|
||||
def on_load(self):
|
||||
pass
|
||||
|
||||
|
||||
# Load the appropriate providers
|
||||
providers = ()
|
||||
|
||||
if platform in ['macosx', 'ios']:
|
||||
providers += (('avfoundation', 'camera_avfoundation',
|
||||
'CameraAVFoundation'), )
|
||||
elif platform == 'android':
|
||||
providers += (('android', 'camera_android', 'CameraAndroid'), )
|
||||
else:
|
||||
providers += (('picamera', 'camera_picamera', 'CameraPiCamera'), )
|
||||
providers += (('gi', 'camera_gi', 'CameraGi'), )
|
||||
|
||||
providers += (('opencv', 'camera_opencv', 'CameraOpenCV'), )
|
||||
|
||||
|
||||
Camera = core_select_lib('camera', (providers))
|
||||
BIN
kivy/core/camera/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/camera/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/camera/__pycache__/camera_android.cpython-310.pyc
Normal file
BIN
kivy/core/camera/__pycache__/camera_android.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/camera/__pycache__/camera_gi.cpython-310.pyc
Normal file
BIN
kivy/core/camera/__pycache__/camera_gi.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/camera/__pycache__/camera_opencv.cpython-310.pyc
Normal file
BIN
kivy/core/camera/__pycache__/camera_opencv.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/camera/__pycache__/camera_picamera.cpython-310.pyc
Normal file
BIN
kivy/core/camera/__pycache__/camera_picamera.cpython-310.pyc
Normal file
Binary file not shown.
206
kivy/core/camera/camera_android.py
Normal file
206
kivy/core/camera/camera_android.py
Normal file
@ -0,0 +1,206 @@
|
||||
from jnius import autoclass, PythonJavaClass, java_method
|
||||
from kivy.clock import Clock
|
||||
from kivy.graphics.texture import Texture
|
||||
from kivy.graphics import Fbo, Callback, Rectangle
|
||||
from kivy.core.camera import CameraBase
|
||||
import threading
|
||||
|
||||
|
||||
Camera = autoclass('android.hardware.Camera')
|
||||
SurfaceTexture = autoclass('android.graphics.SurfaceTexture')
|
||||
GL_TEXTURE_EXTERNAL_OES = autoclass(
|
||||
'android.opengl.GLES11Ext').GL_TEXTURE_EXTERNAL_OES
|
||||
ImageFormat = autoclass('android.graphics.ImageFormat')
|
||||
|
||||
|
||||
class PreviewCallback(PythonJavaClass):
|
||||
"""
|
||||
Interface used to get back the preview frame of the Android Camera
|
||||
"""
|
||||
__javainterfaces__ = ('android.hardware.Camera$PreviewCallback', )
|
||||
|
||||
def __init__(self, callback):
|
||||
super(PreviewCallback, self).__init__()
|
||||
self._callback = callback
|
||||
|
||||
@java_method('([BLandroid/hardware/Camera;)V')
|
||||
def onPreviewFrame(self, data, camera):
|
||||
self._callback(data, camera)
|
||||
|
||||
|
||||
class CameraAndroid(CameraBase):
|
||||
"""
|
||||
Implementation of CameraBase using Android API
|
||||
"""
|
||||
|
||||
_update_ev = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._android_camera = None
|
||||
self._preview_cb = PreviewCallback(self._on_preview_frame)
|
||||
self._buflock = threading.Lock()
|
||||
super(CameraAndroid, self).__init__(**kwargs)
|
||||
|
||||
def __del__(self):
|
||||
self._release_camera()
|
||||
|
||||
def init_camera(self):
|
||||
self._release_camera()
|
||||
self._android_camera = Camera.open(self._index)
|
||||
params = self._android_camera.getParameters()
|
||||
width, height = self._resolution
|
||||
params.setPreviewSize(width, height)
|
||||
supported_focus_modes = self._android_camera.getParameters() \
|
||||
.getSupportedFocusModes()
|
||||
if supported_focus_modes.contains('continuous-picture'):
|
||||
params.setFocusMode('continuous-picture')
|
||||
self._android_camera.setParameters(params)
|
||||
# self._android_camera.setDisplayOrientation()
|
||||
self.fps = 30.
|
||||
|
||||
pf = params.getPreviewFormat()
|
||||
assert(pf == ImageFormat.NV21) # default format is NV21
|
||||
self._bufsize = int(ImageFormat.getBitsPerPixel(pf) / 8. *
|
||||
width * height)
|
||||
|
||||
self._camera_texture = Texture(width=width, height=height,
|
||||
target=GL_TEXTURE_EXTERNAL_OES,
|
||||
colorfmt='rgba')
|
||||
self._surface_texture = SurfaceTexture(int(self._camera_texture.id))
|
||||
self._android_camera.setPreviewTexture(self._surface_texture)
|
||||
|
||||
self._fbo = Fbo(size=self._resolution)
|
||||
self._fbo['resolution'] = (float(width), float(height))
|
||||
self._fbo.shader.fs = '''
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
/* Outputs from the vertex shader */
|
||||
varying vec4 frag_color;
|
||||
varying vec2 tex_coord0;
|
||||
|
||||
/* uniform texture samplers */
|
||||
uniform sampler2D texture0;
|
||||
uniform samplerExternalOES texture1;
|
||||
uniform vec2 resolution;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 coord = vec2(tex_coord0.y * (
|
||||
resolution.y / resolution.x), 1. -tex_coord0.x);
|
||||
gl_FragColor = texture2D(texture1, tex_coord0);
|
||||
}
|
||||
'''
|
||||
with self._fbo:
|
||||
self._texture_cb = Callback(lambda instr:
|
||||
self._camera_texture.bind)
|
||||
Rectangle(size=self._resolution)
|
||||
|
||||
def _release_camera(self):
|
||||
if self._android_camera is None:
|
||||
return
|
||||
|
||||
self.stop()
|
||||
self._android_camera.release()
|
||||
self._android_camera = None
|
||||
|
||||
# clear texture and it'll be reset in `_update` pointing to new FBO
|
||||
self._texture = None
|
||||
del self._fbo, self._surface_texture, self._camera_texture
|
||||
|
||||
def _on_preview_frame(self, data, camera):
|
||||
with self._buflock:
|
||||
if self._buffer is not None:
|
||||
# add buffer back for reuse
|
||||
self._android_camera.addCallbackBuffer(self._buffer)
|
||||
self._buffer = data
|
||||
# check if frame grabbing works
|
||||
# print self._buffer, len(self.frame_data)
|
||||
|
||||
def _refresh_fbo(self):
|
||||
self._texture_cb.ask_update()
|
||||
self._fbo.draw()
|
||||
|
||||
def start(self):
|
||||
super(CameraAndroid, self).start()
|
||||
|
||||
with self._buflock:
|
||||
self._buffer = None
|
||||
for k in range(2): # double buffer
|
||||
buf = b'\x00' * self._bufsize
|
||||
self._android_camera.addCallbackBuffer(buf)
|
||||
self._android_camera.setPreviewCallbackWithBuffer(self._preview_cb)
|
||||
|
||||
self._android_camera.startPreview()
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = Clock.schedule_interval(self._update, 1 / self.fps)
|
||||
|
||||
def stop(self):
|
||||
super(CameraAndroid, self).stop()
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = None
|
||||
self._android_camera.stopPreview()
|
||||
|
||||
self._android_camera.setPreviewCallbackWithBuffer(None)
|
||||
# buffer queue cleared as well, to be recreated on next start
|
||||
with self._buflock:
|
||||
self._buffer = None
|
||||
|
||||
def _update(self, dt):
|
||||
self._surface_texture.updateTexImage()
|
||||
self._refresh_fbo()
|
||||
if self._texture is None:
|
||||
self._texture = self._fbo.texture
|
||||
self.dispatch('on_load')
|
||||
self._copy_to_gpu()
|
||||
|
||||
def _copy_to_gpu(self):
|
||||
"""
|
||||
A dummy placeholder (the image is already in GPU) to be consistent
|
||||
with other providers.
|
||||
"""
|
||||
self.dispatch('on_texture')
|
||||
|
||||
def grab_frame(self):
|
||||
"""
|
||||
Grab current frame (thread-safe, minimal overhead)
|
||||
"""
|
||||
with self._buflock:
|
||||
if self._buffer is None:
|
||||
return None
|
||||
buf = self._buffer.tostring()
|
||||
return buf
|
||||
|
||||
def decode_frame(self, buf):
|
||||
"""
|
||||
Decode image data from grabbed frame.
|
||||
|
||||
This method depends on OpenCV and NumPy - however it is only used for
|
||||
fetching the current frame as a NumPy array, and not required when
|
||||
this :class:`CameraAndroid` provider is simply used by a
|
||||
:class:`~kivy.uix.camera.Camera` widget.
|
||||
"""
|
||||
import numpy as np
|
||||
from cv2 import cvtColor
|
||||
|
||||
w, h = self._resolution
|
||||
arr = np.fromstring(buf, 'uint8').reshape((h + h / 2, w))
|
||||
arr = cvtColor(arr, 93) # NV21 -> BGR
|
||||
return arr
|
||||
|
||||
def read_frame(self):
|
||||
"""
|
||||
Grab and decode frame in one call
|
||||
"""
|
||||
return self.decode_frame(self.grab_frame())
|
||||
|
||||
@staticmethod
|
||||
def get_camera_count():
|
||||
"""
|
||||
Get the number of available cameras.
|
||||
"""
|
||||
return Camera.getNumberOfCameras()
|
||||
170
kivy/core/camera/camera_gi.py
Normal file
170
kivy/core/camera/camera_gi.py
Normal file
@ -0,0 +1,170 @@
|
||||
'''
|
||||
Gi Camera
|
||||
=========
|
||||
|
||||
Implement CameraBase with Gi / Gstreamer, working on both Python 2 and 3
|
||||
'''
|
||||
|
||||
__all__ = ('CameraGi', )
|
||||
|
||||
from gi.repository import Gst
|
||||
from kivy.clock import Clock
|
||||
from kivy.graphics.texture import Texture
|
||||
from kivy.core.camera import CameraBase
|
||||
from kivy.support import install_gobject_iteration
|
||||
from kivy.logger import Logger
|
||||
from ctypes import Structure, c_void_p, c_int, string_at
|
||||
from weakref import ref
|
||||
import atexit
|
||||
|
||||
# initialize the camera/gi. if the older version is used, don't use camera_gi.
|
||||
Gst.init(None)
|
||||
version = Gst.version()
|
||||
if version < (1, 0, 0, 0):
|
||||
raise Exception('Cannot use camera_gi, Gstreamer < 1.0 is not supported.')
|
||||
Logger.info('CameraGi: Using Gstreamer {}'.format(
|
||||
'.'.join(['{}'.format(x) for x in Gst.version()])))
|
||||
install_gobject_iteration()
|
||||
|
||||
|
||||
class _MapInfo(Structure):
|
||||
_fields_ = [
|
||||
('memory', c_void_p),
|
||||
('flags', c_int),
|
||||
('data', c_void_p)]
|
||||
# we don't care about the rest
|
||||
|
||||
|
||||
def _on_cameragi_unref(obj):
|
||||
if obj in CameraGi._instances:
|
||||
CameraGi._instances.remove(obj)
|
||||
|
||||
|
||||
class CameraGi(CameraBase):
|
||||
'''Implementation of CameraBase using GStreamer
|
||||
|
||||
:Parameters:
|
||||
`video_src`: str, default is 'v4l2src'
|
||||
Other tested options are: 'dc1394src' for firewire
|
||||
dc camera (e.g. firefly MV). Any gstreamer video source
|
||||
should potentially work.
|
||||
Theoretically a longer string using "!" can be used
|
||||
describing the first part of a gstreamer pipeline.
|
||||
'''
|
||||
|
||||
_instances = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._pipeline = None
|
||||
self._camerasink = None
|
||||
self._decodebin = None
|
||||
self._texturesize = None
|
||||
self._video_src = kwargs.get('video_src', 'v4l2src')
|
||||
wk = ref(self, _on_cameragi_unref)
|
||||
CameraGi._instances.append(wk)
|
||||
super(CameraGi, self).__init__(**kwargs)
|
||||
|
||||
def init_camera(self):
|
||||
# TODO: This doesn't work when camera resolution is resized at runtime.
|
||||
# There must be some other way to release the camera?
|
||||
if self._pipeline:
|
||||
self._pipeline = None
|
||||
|
||||
video_src = self._video_src
|
||||
if video_src == 'v4l2src':
|
||||
video_src += ' device=/dev/video%d' % self._index
|
||||
elif video_src == 'dc1394src':
|
||||
video_src += ' camera-number=%d' % self._index
|
||||
|
||||
if Gst.version() < (1, 0, 0, 0):
|
||||
caps = ('video/x-raw-rgb,red_mask=(int)0xff0000,'
|
||||
'green_mask=(int)0x00ff00,blue_mask=(int)0x0000ff')
|
||||
pl = ('{} ! decodebin name=decoder ! ffmpegcolorspace ! '
|
||||
'appsink name=camerasink emit-signals=True caps={}')
|
||||
else:
|
||||
caps = 'video/x-raw,format=RGB'
|
||||
pl = '{} ! decodebin name=decoder ! videoconvert ! appsink ' + \
|
||||
'name=camerasink emit-signals=True caps={}'
|
||||
|
||||
self._pipeline = Gst.parse_launch(pl.format(video_src, caps))
|
||||
self._camerasink = self._pipeline.get_by_name('camerasink')
|
||||
self._camerasink.connect('new-sample', self._gst_new_sample)
|
||||
self._decodebin = self._pipeline.get_by_name('decoder')
|
||||
|
||||
if self._camerasink and not self.stopped:
|
||||
self.start()
|
||||
|
||||
def _gst_new_sample(self, *largs):
|
||||
sample = self._camerasink.emit('pull-sample')
|
||||
if sample is None:
|
||||
return False
|
||||
|
||||
self._sample = sample
|
||||
|
||||
if self._texturesize is None:
|
||||
# try to get the camera image size
|
||||
for pad in self._decodebin.srcpads:
|
||||
s = pad.get_current_caps().get_structure(0)
|
||||
self._texturesize = (
|
||||
s.get_value('width'),
|
||||
s.get_value('height'))
|
||||
Clock.schedule_once(self._update)
|
||||
return False
|
||||
|
||||
Clock.schedule_once(self._update)
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
super(CameraGi, self).start()
|
||||
self._pipeline.set_state(Gst.State.PLAYING)
|
||||
|
||||
def stop(self):
|
||||
super(CameraGi, self).stop()
|
||||
self._pipeline.set_state(Gst.State.PAUSED)
|
||||
|
||||
def unload(self):
|
||||
self._pipeline.set_state(Gst.State.NULL)
|
||||
|
||||
def _update(self, dt):
|
||||
sample, self._sample = self._sample, None
|
||||
if sample is None:
|
||||
return
|
||||
|
||||
if self._texture is None and self._texturesize is not None:
|
||||
self._texture = Texture.create(
|
||||
size=self._texturesize, colorfmt='rgb')
|
||||
self._texture.flip_vertical()
|
||||
self.dispatch('on_load')
|
||||
|
||||
# decode sample
|
||||
# read the data from the buffer memory
|
||||
try:
|
||||
buf = sample.get_buffer()
|
||||
result, mapinfo = buf.map(Gst.MapFlags.READ)
|
||||
|
||||
# We cannot get the data out of mapinfo, using Gst 1.0.6 + Gi 3.8.0
|
||||
# related bug report:
|
||||
# https://bugzilla.gnome.org/show_bug.cgi?id=6t8663
|
||||
# ie: mapinfo.data is normally a char*, but here, we have an int
|
||||
# So right now, we use ctypes instead to read the mapinfo ourself.
|
||||
addr = mapinfo.__hash__()
|
||||
c_mapinfo = _MapInfo.from_address(addr)
|
||||
|
||||
# now get the memory
|
||||
self._buffer = string_at(c_mapinfo.data, mapinfo.size)
|
||||
self._copy_to_gpu()
|
||||
finally:
|
||||
if mapinfo is not None:
|
||||
buf.unmap(mapinfo)
|
||||
|
||||
|
||||
@atexit.register
|
||||
def camera_gi_clean():
|
||||
# if we leave the python process with some video running, we can hit a
|
||||
# segfault. This is forcing the stop/unload of all remaining videos before
|
||||
# exiting the python process.
|
||||
for weakcamera in CameraGi._instances:
|
||||
camera = weakcamera()
|
||||
if isinstance(camera, CameraGi):
|
||||
camera.stop()
|
||||
camera.unload()
|
||||
163
kivy/core/camera/camera_opencv.py
Normal file
163
kivy/core/camera/camera_opencv.py
Normal file
@ -0,0 +1,163 @@
|
||||
'''
|
||||
OpenCV Camera: Implement CameraBase with OpenCV
|
||||
'''
|
||||
|
||||
#
|
||||
# TODO: make usage of thread or multiprocess
|
||||
#
|
||||
|
||||
from __future__ import division
|
||||
|
||||
__all__ = ('CameraOpenCV')
|
||||
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.clock import Clock
|
||||
from kivy.graphics.texture import Texture
|
||||
from kivy.core.camera import CameraBase
|
||||
|
||||
try:
|
||||
# opencv 1 case
|
||||
import opencv as cv
|
||||
|
||||
try:
|
||||
import opencv.highgui as hg
|
||||
except ImportError:
|
||||
class Hg(object):
|
||||
'''
|
||||
On OSX, not only are the import names different,
|
||||
but the API also differs.
|
||||
There is no module called 'highgui' but the names are
|
||||
directly available in the 'cv' module.
|
||||
Some of them even have a different names.
|
||||
|
||||
Therefore we use this proxy object.
|
||||
'''
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith('cv'):
|
||||
attr = attr[2:]
|
||||
got = getattr(cv, attr)
|
||||
return got
|
||||
|
||||
hg = Hg()
|
||||
|
||||
except ImportError:
|
||||
# opencv 2 case (and also opencv 3, because it still uses cv2 module name)
|
||||
try:
|
||||
import cv2
|
||||
# here missing this OSX specific highgui thing.
|
||||
# I'm not on OSX so don't know if it is still valid in opencv >= 2
|
||||
except ImportError:
|
||||
raise
|
||||
|
||||
|
||||
class CameraOpenCV(CameraBase):
|
||||
'''
|
||||
Implementation of CameraBase using OpenCV
|
||||
'''
|
||||
_update_ev = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# we will need it, because constants have
|
||||
# different access paths between ver. 2 and 3
|
||||
try:
|
||||
self.opencvMajorVersion = int(cv.__version__[0])
|
||||
except NameError:
|
||||
self.opencvMajorVersion = int(cv2.__version__[0])
|
||||
|
||||
self._device = None
|
||||
super(CameraOpenCV, self).__init__(**kwargs)
|
||||
|
||||
def init_camera(self):
|
||||
# consts have changed locations between versions 2 and 3
|
||||
if self.opencvMajorVersion in (3, 4):
|
||||
PROPERTY_WIDTH = cv2.CAP_PROP_FRAME_WIDTH
|
||||
PROPERTY_HEIGHT = cv2.CAP_PROP_FRAME_HEIGHT
|
||||
PROPERTY_FPS = cv2.CAP_PROP_FPS
|
||||
elif self.opencvMajorVersion == 2:
|
||||
PROPERTY_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
|
||||
PROPERTY_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
|
||||
PROPERTY_FPS = cv2.cv.CV_CAP_PROP_FPS
|
||||
elif self.opencvMajorVersion == 1:
|
||||
PROPERTY_WIDTH = cv.CV_CAP_PROP_FRAME_WIDTH
|
||||
PROPERTY_HEIGHT = cv.CV_CAP_PROP_FRAME_HEIGHT
|
||||
PROPERTY_FPS = cv.CV_CAP_PROP_FPS
|
||||
|
||||
Logger.debug('Using opencv ver.' + str(self.opencvMajorVersion))
|
||||
|
||||
if self.opencvMajorVersion == 1:
|
||||
# create the device
|
||||
self._device = hg.cvCreateCameraCapture(self._index)
|
||||
# Set preferred resolution
|
||||
cv.SetCaptureProperty(self._device, cv.CV_CAP_PROP_FRAME_WIDTH,
|
||||
self.resolution[0])
|
||||
cv.SetCaptureProperty(self._device, cv.CV_CAP_PROP_FRAME_HEIGHT,
|
||||
self.resolution[1])
|
||||
# and get frame to check if it's ok
|
||||
frame = hg.cvQueryFrame(self._device)
|
||||
# Just set the resolution to the frame we just got, but don't use
|
||||
# self.resolution for that as that would cause an infinite
|
||||
# recursion with self.init_camera (but slowly as we'd have to
|
||||
# always get a frame).
|
||||
self._resolution = (int(frame.width), int(frame.height))
|
||||
# get fps
|
||||
self.fps = cv.GetCaptureProperty(self._device, cv.CV_CAP_PROP_FPS)
|
||||
|
||||
elif self.opencvMajorVersion in (2, 3, 4):
|
||||
# create the device
|
||||
self._device = cv2.VideoCapture(self._index)
|
||||
# Set preferred resolution
|
||||
self._device.set(PROPERTY_WIDTH,
|
||||
self.resolution[0])
|
||||
self._device.set(PROPERTY_HEIGHT,
|
||||
self.resolution[1])
|
||||
# and get frame to check if it's ok
|
||||
ret, frame = self._device.read()
|
||||
|
||||
# source:
|
||||
# http://stackoverflow.com/questions/32468371/video-capture-propid-parameters-in-opencv # noqa
|
||||
self._resolution = (int(frame.shape[1]), int(frame.shape[0]))
|
||||
# get fps
|
||||
self.fps = self._device.get(PROPERTY_FPS)
|
||||
|
||||
if self.fps == 0 or self.fps == 1:
|
||||
self.fps = 1.0 / 30
|
||||
elif self.fps > 1:
|
||||
self.fps = 1.0 / self.fps
|
||||
|
||||
if not self.stopped:
|
||||
self.start()
|
||||
|
||||
def _update(self, dt):
|
||||
if self.stopped:
|
||||
return
|
||||
if self._texture is None:
|
||||
# Create the texture
|
||||
self._texture = Texture.create(self._resolution)
|
||||
self._texture.flip_vertical()
|
||||
self.dispatch('on_load')
|
||||
try:
|
||||
ret, frame = self._device.read()
|
||||
self._format = 'bgr'
|
||||
try:
|
||||
self._buffer = frame.imageData
|
||||
except AttributeError:
|
||||
# frame is already of type ndarray
|
||||
# which can be reshaped to 1-d.
|
||||
self._buffer = frame.reshape(-1)
|
||||
self._copy_to_gpu()
|
||||
except:
|
||||
Logger.exception('OpenCV: Couldn\'t get image from Camera')
|
||||
|
||||
def start(self):
|
||||
super(CameraOpenCV, self).start()
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = Clock.schedule_interval(self._update, self.fps)
|
||||
|
||||
def stop(self):
|
||||
super(CameraOpenCV, self).stop()
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = None
|
||||
96
kivy/core/camera/camera_picamera.py
Normal file
96
kivy/core/camera/camera_picamera.py
Normal file
@ -0,0 +1,96 @@
|
||||
'''
|
||||
PiCamera Camera: Implement CameraBase with PiCamera
|
||||
'''
|
||||
|
||||
#
|
||||
# TODO: make usage of thread or multiprocess
|
||||
#
|
||||
|
||||
__all__ = ('CameraPiCamera', )
|
||||
|
||||
from math import ceil
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.clock import Clock
|
||||
from kivy.graphics.texture import Texture
|
||||
from kivy.core.camera import CameraBase
|
||||
|
||||
from picamera import PiCamera
|
||||
import numpy
|
||||
|
||||
|
||||
class CameraPiCamera(CameraBase):
|
||||
'''Implementation of CameraBase using PiCamera
|
||||
'''
|
||||
_update_ev = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._camera = None
|
||||
self._format = 'bgr'
|
||||
self._framerate = kwargs.get('framerate', 30)
|
||||
super(CameraPiCamera, self).__init__(**kwargs)
|
||||
|
||||
def init_camera(self):
|
||||
if self._camera is not None:
|
||||
self._camera.close()
|
||||
|
||||
self._camera = PiCamera()
|
||||
self._camera.resolution = self.resolution
|
||||
self._camera.framerate = self._framerate
|
||||
self._camera.iso = 800
|
||||
|
||||
self.fps = 1. / self._framerate
|
||||
|
||||
if not self.stopped:
|
||||
self.start()
|
||||
|
||||
def raw_buffer_size(self):
|
||||
'''Round buffer size up to 32x16 blocks.
|
||||
|
||||
See https://picamera.readthedocs.io/en/release-1.13/recipes2.html#capturing-to-a-numpy-array
|
||||
''' # noqa
|
||||
return (
|
||||
ceil(self.resolution[0] / 32.) * 32,
|
||||
ceil(self.resolution[1] / 16.) * 16
|
||||
)
|
||||
|
||||
def _update(self, dt):
|
||||
if self.stopped:
|
||||
return
|
||||
|
||||
if self._texture is None:
|
||||
# Create the texture
|
||||
self._texture = Texture.create(self._resolution)
|
||||
self._texture.flip_vertical()
|
||||
self.dispatch('on_load')
|
||||
|
||||
try:
|
||||
bufsize = self.raw_buffer_size()
|
||||
output = numpy.empty(
|
||||
(bufsize[0] * bufsize[1] * 3,), dtype=numpy.uint8)
|
||||
self._camera.capture(output, self._format, use_video_port=True)
|
||||
|
||||
# Trim the buffer to fit the actual requested resolution.
|
||||
# TODO: Is there a simpler way to do all this reshuffling?
|
||||
output = output.reshape((bufsize[0], bufsize[1], 3))
|
||||
output = output[:self.resolution[0], :self.resolution[1], :]
|
||||
self._buffer = output.reshape(
|
||||
(self.resolution[0] * self.resolution[1] * 3,))
|
||||
|
||||
self._copy_to_gpu()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
Logger.exception('PiCamera: Couldn\'t get image from Camera')
|
||||
|
||||
def start(self):
|
||||
super(CameraPiCamera, self).start()
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = Clock.schedule_interval(self._update, self.fps)
|
||||
|
||||
def stop(self):
|
||||
super(CameraPiCamera, self).stop()
|
||||
if self._update_ev is not None:
|
||||
self._update_ev.cancel()
|
||||
self._update_ev = None
|
||||
157
kivy/core/clipboard/__init__.py
Normal file
157
kivy/core/clipboard/__init__.py
Normal file
@ -0,0 +1,157 @@
|
||||
'''
|
||||
Clipboard
|
||||
=========
|
||||
|
||||
Core class for accessing the Clipboard. If we are not able to access the
|
||||
system clipboard, a fake one will be used.
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
#:import Clipboard kivy.core.clipboard.Clipboard
|
||||
|
||||
Button:
|
||||
on_release:
|
||||
self.text = Clipboard.paste()
|
||||
Clipboard.copy('Data')
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardBase', 'Clipboard')
|
||||
|
||||
from kivy import Logger
|
||||
from kivy.core import core_select_lib
|
||||
from kivy.utils import platform
|
||||
from kivy.setupconfig import USE_SDL2
|
||||
|
||||
|
||||
class ClipboardBase(object):
|
||||
|
||||
def get(self, mimetype):
|
||||
'''Get the current data in clipboard, using the mimetype if possible.
|
||||
You not use this method directly. Use :meth:`paste` instead.
|
||||
'''
|
||||
pass
|
||||
|
||||
def put(self, data, mimetype):
|
||||
'''Put data on the clipboard, and attach a mimetype.
|
||||
You should not use this method directly. Use :meth:`copy` instead.
|
||||
'''
|
||||
pass
|
||||
|
||||
def get_types(self):
|
||||
'''Return a list of supported mimetypes
|
||||
'''
|
||||
return []
|
||||
|
||||
def _ensure_clipboard(self):
|
||||
''' Ensure that the clipboard has been properly initialised.
|
||||
'''
|
||||
|
||||
if hasattr(self, '_clip_mime_type'):
|
||||
return
|
||||
|
||||
if platform == 'win':
|
||||
self._clip_mime_type = 'text/plain;charset=utf-8'
|
||||
# windows clipboard uses a utf-16 little endian encoding
|
||||
self._encoding = 'utf-16-le'
|
||||
elif platform == 'linux':
|
||||
self._clip_mime_type = 'text/plain;charset=utf-8'
|
||||
self._encoding = 'utf-8'
|
||||
else:
|
||||
self._clip_mime_type = 'text/plain'
|
||||
self._encoding = 'utf-8'
|
||||
|
||||
def copy(self, data=''):
|
||||
''' Copy the value provided in argument `data` into current clipboard.
|
||||
If data is not of type string it will be converted to string.
|
||||
|
||||
.. versionadded:: 1.9.0
|
||||
|
||||
'''
|
||||
if data:
|
||||
self._copy(data)
|
||||
|
||||
def paste(self):
|
||||
''' Get text from the system clipboard and return it a usable string.
|
||||
|
||||
.. versionadded:: 1.9.0
|
||||
|
||||
'''
|
||||
return self._paste()
|
||||
|
||||
def _copy(self, data):
|
||||
self._ensure_clipboard()
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode(self._encoding)
|
||||
self.put(data, self._clip_mime_type)
|
||||
|
||||
def _paste(self):
|
||||
self._ensure_clipboard()
|
||||
_clip_types = Clipboard.get_types()
|
||||
|
||||
mime_type = self._clip_mime_type
|
||||
if mime_type not in _clip_types:
|
||||
mime_type = 'text/plain'
|
||||
|
||||
data = self.get(mime_type)
|
||||
if data is not None:
|
||||
# decode only if we don't have unicode
|
||||
# we would still need to decode from utf-16 (windows)
|
||||
# data is of type bytes in PY3
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode(self._encoding, 'ignore')
|
||||
# remove null strings mostly a windows issue
|
||||
data = data.replace(u'\x00', u'')
|
||||
return data
|
||||
return u''
|
||||
|
||||
|
||||
# load clipboard implementation
|
||||
_clipboards = []
|
||||
if platform == 'android':
|
||||
_clipboards.append(
|
||||
('android', 'clipboard_android', 'ClipboardAndroid'))
|
||||
elif platform == 'macosx':
|
||||
_clipboards.append(
|
||||
('nspaste', 'clipboard_nspaste', 'ClipboardNSPaste'))
|
||||
elif platform == 'win':
|
||||
_clipboards.append(
|
||||
('winctypes', 'clipboard_winctypes', 'ClipboardWindows'))
|
||||
elif platform == 'linux':
|
||||
_clipboards.append(
|
||||
('xclip', 'clipboard_xclip', 'ClipboardXclip'))
|
||||
_clipboards.append(
|
||||
('xsel', 'clipboard_xsel', 'ClipboardXsel'))
|
||||
_clipboards.append(
|
||||
('dbusklipper', 'clipboard_dbusklipper', 'ClipboardDbusKlipper'))
|
||||
_clipboards.append(
|
||||
('gtk3', 'clipboard_gtk3', 'ClipboardGtk3'))
|
||||
|
||||
if USE_SDL2:
|
||||
_clipboards.append(
|
||||
('sdl2', 'clipboard_sdl2', 'ClipboardSDL2'))
|
||||
else:
|
||||
_clipboards.append(
|
||||
('pygame', 'clipboard_pygame', 'ClipboardPygame'))
|
||||
|
||||
_clipboards.append(
|
||||
('dummy', 'clipboard_dummy', 'ClipboardDummy'))
|
||||
|
||||
Clipboard = core_select_lib('clipboard', _clipboards, True)
|
||||
CutBuffer = None
|
||||
|
||||
if platform == 'linux':
|
||||
_cutbuffers = [
|
||||
('xclip', 'clipboard_xclip', 'ClipboardXclip'),
|
||||
('xsel', 'clipboard_xsel', 'ClipboardXsel'),
|
||||
]
|
||||
|
||||
if Clipboard.__class__.__name__ in (c[2] for c in _cutbuffers):
|
||||
CutBuffer = Clipboard
|
||||
else:
|
||||
CutBuffer = core_select_lib('cutbuffer', _cutbuffers, True,
|
||||
basemodule='clipboard')
|
||||
|
||||
if CutBuffer:
|
||||
Logger.info('CutBuffer: cut buffer support enabled')
|
||||
BIN
kivy/core/clipboard/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/_clipboard_ext.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/_clipboard_ext.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/clipboard_dummy.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/clipboard_dummy.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/clipboard_gtk3.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/clipboard_gtk3.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/clipboard_pygame.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/clipboard_pygame.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/clipboard_sdl2.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/clipboard_sdl2.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/clipboard_xclip.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/clipboard_xclip.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/clipboard/__pycache__/clipboard_xsel.cpython-310.pyc
Normal file
BIN
kivy/core/clipboard/__pycache__/clipboard_xsel.cpython-310.pyc
Normal file
Binary file not shown.
36
kivy/core/clipboard/_clipboard_ext.py
Normal file
36
kivy/core/clipboard/_clipboard_ext.py
Normal file
@ -0,0 +1,36 @@
|
||||
'''
|
||||
Clipboard ext: base class for external command clipboards
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardExternalBase', )
|
||||
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
|
||||
|
||||
class ClipboardExternalBase(ClipboardBase):
|
||||
@staticmethod
|
||||
def _clip(inout, selection):
|
||||
raise NotImplementedError('clip method not implemented')
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
p = self._clip('out', 'clipboard')
|
||||
data, _ = p.communicate()
|
||||
return data
|
||||
|
||||
def put(self, data, mimetype='text/plain'):
|
||||
p = self._clip('in', 'clipboard')
|
||||
p.communicate(data)
|
||||
|
||||
def get_cutbuffer(self):
|
||||
p = self._clip('out', 'primary')
|
||||
data, _ = p.communicate()
|
||||
return data.decode('utf8')
|
||||
|
||||
def set_cutbuffer(self, data):
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf8')
|
||||
p = self._clip('in', 'primary')
|
||||
p.communicate(data)
|
||||
|
||||
def get_types(self):
|
||||
return [u'text/plain']
|
||||
BIN
kivy/core/clipboard/_clipboard_sdl2.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/core/clipboard/_clipboard_sdl2.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
91
kivy/core/clipboard/clipboard_android.py
Normal file
91
kivy/core/clipboard/clipboard_android.py
Normal file
@ -0,0 +1,91 @@
|
||||
'''
|
||||
Clipboard Android
|
||||
=================
|
||||
|
||||
Android implementation of Clipboard provider, using Pyjnius.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardAndroid', )
|
||||
|
||||
from kivy import Logger
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
from jnius import autoclass, cast
|
||||
from android.runnable import run_on_ui_thread
|
||||
from android import python_act
|
||||
|
||||
AndroidString = autoclass('java.lang.String')
|
||||
PythonActivity = python_act
|
||||
Context = autoclass('android.content.Context')
|
||||
VER = autoclass('android.os.Build$VERSION')
|
||||
sdk = VER.SDK_INT
|
||||
|
||||
|
||||
class ClipboardAndroid(ClipboardBase):
|
||||
|
||||
def __init__(self):
|
||||
super(ClipboardAndroid, self).__init__()
|
||||
self._clipboard = None
|
||||
self._data = dict()
|
||||
self._data['text/plain'] = None
|
||||
self._data['application/data'] = None
|
||||
PythonActivity._clipboard = None
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
return self._get(mimetype).encode('utf-8')
|
||||
|
||||
def put(self, data, mimetype='text/plain'):
|
||||
self._set(data, mimetype)
|
||||
|
||||
def get_types(self):
|
||||
return list(self._data.keys())
|
||||
|
||||
@run_on_ui_thread
|
||||
def _initialize_clipboard(self):
|
||||
PythonActivity._clipboard = cast(
|
||||
'android.app.Activity',
|
||||
PythonActivity.mActivity).getSystemService(
|
||||
Context.CLIPBOARD_SERVICE)
|
||||
|
||||
def _get_clipboard(f):
|
||||
def called(*args, **kargs):
|
||||
self = args[0]
|
||||
if not PythonActivity._clipboard:
|
||||
self._initialize_clipboard()
|
||||
import time
|
||||
while not PythonActivity._clipboard:
|
||||
time.sleep(.01)
|
||||
return f(*args, **kargs)
|
||||
return called
|
||||
|
||||
@_get_clipboard
|
||||
def _get(self, mimetype='text/plain'):
|
||||
clippy = PythonActivity._clipboard
|
||||
data = ''
|
||||
if sdk < 11:
|
||||
data = clippy.getText()
|
||||
else:
|
||||
ClipDescription = autoclass('android.content.ClipDescription')
|
||||
primary_clip = clippy.getPrimaryClip()
|
||||
if primary_clip:
|
||||
try:
|
||||
data = primary_clip.getItemAt(0)
|
||||
if data:
|
||||
data = data.coerceToText(
|
||||
PythonActivity.mActivity.getApplicationContext())
|
||||
except Exception:
|
||||
Logger.exception('Clipboard: failed to paste')
|
||||
return data
|
||||
|
||||
@_get_clipboard
|
||||
def _set(self, data, mimetype):
|
||||
clippy = PythonActivity._clipboard
|
||||
|
||||
if sdk < 11:
|
||||
# versions previous to honeycomb
|
||||
clippy.setText(AndroidString(data))
|
||||
else:
|
||||
ClipData = autoclass('android.content.ClipData')
|
||||
new_clip = ClipData.newPlainText(AndroidString(""),
|
||||
AndroidString(data))
|
||||
# put text data onto clipboard
|
||||
clippy.setPrimaryClip(new_clip)
|
||||
41
kivy/core/clipboard/clipboard_dbusklipper.py
Normal file
41
kivy/core/clipboard/clipboard_dbusklipper.py
Normal file
@ -0,0 +1,41 @@
|
||||
'''
|
||||
Clipboard Dbus: an implementation of the Clipboard using dbus and klipper.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardDbusKlipper', )
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
|
||||
if platform != 'linux':
|
||||
raise SystemError('unsupported platform for dbus kde clipboard')
|
||||
|
||||
try:
|
||||
import dbus
|
||||
bus = dbus.SessionBus()
|
||||
proxy = bus.get_object("org.kde.klipper", "/klipper")
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
class ClipboardDbusKlipper(ClipboardBase):
|
||||
|
||||
_is_init = False
|
||||
|
||||
def init(self):
|
||||
if ClipboardDbusKlipper._is_init:
|
||||
return
|
||||
self.iface = dbus.Interface(proxy, "org.kde.klipper.klipper")
|
||||
ClipboardDbusKlipper._is_init = True
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
self.init()
|
||||
return str(self.iface.getClipboardContents())
|
||||
|
||||
def put(self, data, mimetype='text/plain'):
|
||||
self.init()
|
||||
self.iface.setClipboardContents(data.replace('\x00', ''))
|
||||
|
||||
def get_types(self):
|
||||
self.init()
|
||||
return [u'text/plain']
|
||||
26
kivy/core/clipboard/clipboard_dummy.py
Normal file
26
kivy/core/clipboard/clipboard_dummy.py
Normal file
@ -0,0 +1,26 @@
|
||||
'''
|
||||
Clipboard Dummy: an internal implementation that does not use the system
|
||||
clipboard.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardDummy', )
|
||||
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
|
||||
|
||||
class ClipboardDummy(ClipboardBase):
|
||||
|
||||
def __init__(self):
|
||||
super(ClipboardDummy, self).__init__()
|
||||
self._data = dict()
|
||||
self._data['text/plain'] = None
|
||||
self._data['application/data'] = None
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
return self._data.get(mimetype, None)
|
||||
|
||||
def put(self, data, mimetype='text/plain'):
|
||||
self._data[mimetype] = data
|
||||
|
||||
def get_types(self):
|
||||
return list(self._data.keys())
|
||||
47
kivy/core/clipboard/clipboard_gtk3.py
Normal file
47
kivy/core/clipboard/clipboard_gtk3.py
Normal file
@ -0,0 +1,47 @@
|
||||
'''
|
||||
Clipboard Gtk3: an implementation of the Clipboard using Gtk3.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardGtk3',)
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.support import install_gobject_iteration
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
|
||||
if platform != 'linux':
|
||||
raise SystemError('unsupported platform for gtk3 clipboard')
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
|
||||
|
||||
class ClipboardGtk3(ClipboardBase):
|
||||
|
||||
_is_init = False
|
||||
|
||||
def init(self):
|
||||
if self._is_init:
|
||||
return
|
||||
install_gobject_iteration()
|
||||
self._is_init = True
|
||||
|
||||
def get(self, mimetype='text/plain;charset=utf-8'):
|
||||
self.init()
|
||||
if mimetype == 'text/plain;charset=utf-8':
|
||||
contents = clipboard.wait_for_text()
|
||||
if contents:
|
||||
return contents
|
||||
return ''
|
||||
|
||||
def put(self, data, mimetype='text/plain;charset=utf-8'):
|
||||
self.init()
|
||||
if mimetype == 'text/plain;charset=utf-8':
|
||||
text = data.decode(self._encoding)
|
||||
clipboard.set_text(text, -1)
|
||||
clipboard.store()
|
||||
|
||||
def get_types(self):
|
||||
self.init()
|
||||
return ['text/plain;charset=utf-8']
|
||||
44
kivy/core/clipboard/clipboard_nspaste.py
Normal file
44
kivy/core/clipboard/clipboard_nspaste.py
Normal file
@ -0,0 +1,44 @@
|
||||
'''
|
||||
Clipboard OsX: implementation of clipboard using Appkit
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardNSPaste', )
|
||||
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
from kivy.utils import platform
|
||||
|
||||
if platform != 'macosx':
|
||||
raise SystemError('Unsupported platform for appkit clipboard.')
|
||||
try:
|
||||
from pyobjus import autoclass
|
||||
from pyobjus.dylib_manager import load_framework, INCLUDE
|
||||
load_framework(INCLUDE.AppKit)
|
||||
except ImportError:
|
||||
raise SystemError('Pyobjus not installed. Please run the following'
|
||||
' command to install it. `pip install --user pyobjus`')
|
||||
|
||||
NSPasteboard = autoclass('NSPasteboard')
|
||||
NSString = autoclass('NSString')
|
||||
|
||||
|
||||
class ClipboardNSPaste(ClipboardBase):
|
||||
|
||||
def __init__(self):
|
||||
super(ClipboardNSPaste, self).__init__()
|
||||
self._clipboard = NSPasteboard.generalPasteboard()
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
pb = self._clipboard
|
||||
data = pb.stringForType_('public.utf8-plain-text')
|
||||
if not data:
|
||||
return ""
|
||||
return data.UTF8String()
|
||||
|
||||
def put(self, data, mimetype='text/plain'):
|
||||
pb = self._clipboard
|
||||
pb.clearContents()
|
||||
utf8 = NSString.alloc().initWithUTF8String_(data)
|
||||
pb.setString_forType_(utf8, 'public.utf8-plain-text')
|
||||
|
||||
def get_types(self):
|
||||
return list('text/plain',)
|
||||
67
kivy/core/clipboard/clipboard_pygame.py
Normal file
67
kivy/core/clipboard/clipboard_pygame.py
Normal file
@ -0,0 +1,67 @@
|
||||
'''
|
||||
Clipboard Pygame: an implementation of the Clipboard using pygame.scrap.
|
||||
|
||||
.. warning::
|
||||
|
||||
Pygame has been deprecated and will be removed in the release after Kivy
|
||||
1.11.0.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardPygame', )
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
from kivy.utils import deprecated
|
||||
|
||||
if platform not in ('win', 'linux', 'macosx'):
|
||||
raise SystemError('unsupported platform for pygame clipboard')
|
||||
|
||||
try:
|
||||
import pygame
|
||||
import pygame.scrap
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
class ClipboardPygame(ClipboardBase):
|
||||
|
||||
_is_init = False
|
||||
_types = None
|
||||
|
||||
_aliases = {
|
||||
'text/plain;charset=utf-8': 'UTF8_STRING'
|
||||
}
|
||||
|
||||
@deprecated(
|
||||
msg='Pygame has been deprecated and will be removed after 1.11.0')
|
||||
def __init__(self, *largs, **kwargs):
|
||||
super(ClipboardPygame, self).__init__(*largs, **kwargs)
|
||||
|
||||
def init(self):
|
||||
if ClipboardPygame._is_init:
|
||||
return
|
||||
pygame.scrap.init()
|
||||
ClipboardPygame._is_init = True
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
self.init()
|
||||
mimetype = self._aliases.get(mimetype, mimetype)
|
||||
text = pygame.scrap.get(mimetype)
|
||||
return text
|
||||
|
||||
def put(self, data, mimetype='text/plain'):
|
||||
self.init()
|
||||
mimetype = self._aliases.get(mimetype, mimetype)
|
||||
pygame.scrap.put(mimetype, data)
|
||||
|
||||
def get_types(self):
|
||||
if not self._types:
|
||||
self.init()
|
||||
types = pygame.scrap.get_types()
|
||||
for mime, pygtype in list(self._aliases.items())[:]:
|
||||
if mime in types:
|
||||
del self._aliases[mime]
|
||||
if pygtype in types:
|
||||
types.append(mime)
|
||||
self._types = types
|
||||
return self._types
|
||||
36
kivy/core/clipboard/clipboard_sdl2.py
Normal file
36
kivy/core/clipboard/clipboard_sdl2.py
Normal file
@ -0,0 +1,36 @@
|
||||
'''
|
||||
Clipboard SDL2: an implementation of the Clipboard using sdl2.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardSDL2', )
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
|
||||
if platform not in ('win', 'linux', 'macosx', 'android', 'ios'):
|
||||
raise SystemError('unsupported platform for sdl2 clipboard')
|
||||
|
||||
try:
|
||||
from kivy.core.clipboard._clipboard_sdl2 import (
|
||||
_get_text, _has_text, _set_text)
|
||||
except ImportError:
|
||||
from kivy.core import handle_win_lib_import_error
|
||||
handle_win_lib_import_error(
|
||||
'Clipboard', 'sdl2', 'kivy.core.clipboard._clipboard_sdl2')
|
||||
raise
|
||||
|
||||
|
||||
class ClipboardSDL2(ClipboardBase):
|
||||
|
||||
def get(self, mimetype):
|
||||
return _get_text() if _has_text() else ''
|
||||
|
||||
def _ensure_clipboard(self):
|
||||
super(ClipboardSDL2, self)._ensure_clipboard()
|
||||
self._encoding = 'utf8'
|
||||
|
||||
def put(self, data=b'', mimetype='text/plain'):
|
||||
_set_text(data)
|
||||
|
||||
def get_types(self):
|
||||
return ['text/plain']
|
||||
66
kivy/core/clipboard/clipboard_winctypes.py
Normal file
66
kivy/core/clipboard/clipboard_winctypes.py
Normal file
@ -0,0 +1,66 @@
|
||||
'''
|
||||
Clipboard windows: an implementation of the Clipboard using ctypes.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardWindows', )
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.core.clipboard import ClipboardBase
|
||||
|
||||
if platform != 'win':
|
||||
raise SystemError('unsupported platform for Windows clipboard')
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
user32 = ctypes.windll.user32
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
msvcrt = ctypes.cdll.msvcrt
|
||||
c_char_p = ctypes.c_char_p
|
||||
c_wchar_p = ctypes.c_wchar_p
|
||||
|
||||
|
||||
class ClipboardWindows(ClipboardBase):
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
GetClipboardData = user32.GetClipboardData
|
||||
GetClipboardData.argtypes = [wintypes.UINT]
|
||||
GetClipboardData.restype = wintypes.HANDLE
|
||||
|
||||
user32.OpenClipboard(user32.GetActiveWindow())
|
||||
# Standard Clipboard Format "1" is "CF_TEXT"
|
||||
pcontents = GetClipboardData(13)
|
||||
|
||||
# if someone pastes a FILE, the content is None for SCF 13
|
||||
# and the clipboard is locked if not closed properly
|
||||
if not pcontents:
|
||||
user32.CloseClipboard()
|
||||
return ''
|
||||
data = c_wchar_p(pcontents).value.encode(self._encoding)
|
||||
user32.CloseClipboard()
|
||||
return data
|
||||
|
||||
def put(self, text, mimetype='text/plain'):
|
||||
text = text.decode(self._encoding) # auto converted later
|
||||
text += u'\x00'
|
||||
|
||||
SetClipboardData = user32.SetClipboardData
|
||||
SetClipboardData.argtypes = [wintypes.UINT, wintypes.HANDLE]
|
||||
SetClipboardData.restype = wintypes.HANDLE
|
||||
|
||||
GlobalAlloc = kernel32.GlobalAlloc
|
||||
GlobalAlloc.argtypes = [wintypes.UINT, ctypes.c_size_t]
|
||||
GlobalAlloc.restype = wintypes.HGLOBAL
|
||||
|
||||
CF_UNICODETEXT = 13
|
||||
|
||||
user32.OpenClipboard(user32.GetActiveWindow())
|
||||
user32.EmptyClipboard()
|
||||
hCd = GlobalAlloc(0, len(text) * ctypes.sizeof(ctypes.c_wchar))
|
||||
|
||||
# ignore null character for strSource pointer
|
||||
msvcrt.wcscpy_s(c_wchar_p(hCd), len(text), c_wchar_p(text[:-1]))
|
||||
SetClipboardData(CF_UNICODETEXT, hCd)
|
||||
user32.CloseClipboard()
|
||||
|
||||
def get_types(self):
|
||||
return ['text/plain']
|
||||
29
kivy/core/clipboard/clipboard_xclip.py
Normal file
29
kivy/core/clipboard/clipboard_xclip.py
Normal file
@ -0,0 +1,29 @@
|
||||
'''
|
||||
Clipboard xclip: an implementation of the Clipboard using xclip
|
||||
command line tool.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardXclip', )
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.core.clipboard._clipboard_ext import ClipboardExternalBase
|
||||
|
||||
if platform != 'linux':
|
||||
raise SystemError('unsupported platform for xclip clipboard')
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
p = subprocess.Popen(['xclip', '-version'], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL)
|
||||
p.communicate()
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
class ClipboardXclip(ClipboardExternalBase):
|
||||
@staticmethod
|
||||
def _clip(inout, selection):
|
||||
pipe = {'std' + inout: subprocess.PIPE}
|
||||
return subprocess.Popen(
|
||||
['xclip', '-' + inout, '-selection', selection], **pipe)
|
||||
29
kivy/core/clipboard/clipboard_xsel.py
Normal file
29
kivy/core/clipboard/clipboard_xsel.py
Normal file
@ -0,0 +1,29 @@
|
||||
'''
|
||||
Clipboard xsel: an implementation of the Clipboard using xsel command line
|
||||
tool.
|
||||
'''
|
||||
|
||||
__all__ = ('ClipboardXsel', )
|
||||
|
||||
from kivy.utils import platform
|
||||
from kivy.core.clipboard._clipboard_ext import ClipboardExternalBase
|
||||
|
||||
if platform != 'linux':
|
||||
raise SystemError('unsupported platform for xsel clipboard')
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
p = subprocess.Popen(['xsel'], stdout=subprocess.PIPE)
|
||||
p.communicate()
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
class ClipboardXsel(ClipboardExternalBase):
|
||||
@staticmethod
|
||||
def _clip(inout, selection):
|
||||
pipe = {'std' + inout: subprocess.PIPE}
|
||||
sel = 'b' if selection == 'clipboard' else selection[0]
|
||||
io = inout[0]
|
||||
return subprocess.Popen(
|
||||
['xsel', '-' + sel + io], **pipe)
|
||||
83
kivy/core/gl/__init__.py
Normal file
83
kivy/core/gl/__init__.py
Normal file
@ -0,0 +1,83 @@
|
||||
# pylint: disable=W0611
|
||||
'''
|
||||
OpenGL
|
||||
======
|
||||
|
||||
Select and use the best OpenGL library available. Depending on your system, the
|
||||
core provider can select an OpenGL ES or a 'classic' desktop OpenGL library.
|
||||
'''
|
||||
|
||||
import sys
|
||||
from os import environ
|
||||
|
||||
MIN_REQUIRED_GL_VERSION = (2, 0)
|
||||
|
||||
|
||||
def msgbox(message):
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import LPCWSTR
|
||||
ctypes.windll.user32.MessageBoxW(None, LPCWSTR(message),
|
||||
u"Kivy Fatal Error", 0)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if 'KIVY_DOC' not in environ:
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.graphics import gl_init_resources
|
||||
from kivy.graphics.opengl_utils import gl_get_version
|
||||
from kivy.graphics.opengl import GL_VERSION, GL_VENDOR, GL_RENDERER, \
|
||||
GL_MAX_TEXTURE_IMAGE_UNITS, GL_MAX_TEXTURE_SIZE, \
|
||||
GL_SHADING_LANGUAGE_VERSION,\
|
||||
glGetString, glGetIntegerv, gl_init_symbols
|
||||
from kivy.graphics.cgl import cgl_get_initialized_backend_name
|
||||
from kivy.utils import platform
|
||||
|
||||
def init_gl(allowed=[], ignored=[]):
|
||||
gl_init_symbols(allowed, ignored)
|
||||
print_gl_version()
|
||||
gl_init_resources()
|
||||
|
||||
def print_gl_version():
|
||||
backend = cgl_get_initialized_backend_name()
|
||||
Logger.info('GL: Backend used <{}>'.format(backend))
|
||||
version = glGetString(GL_VERSION)
|
||||
vendor = glGetString(GL_VENDOR)
|
||||
renderer = glGetString(GL_RENDERER)
|
||||
Logger.info('GL: OpenGL version <{0}>'.format(version))
|
||||
Logger.info('GL: OpenGL vendor <{0}>'.format(vendor))
|
||||
Logger.info('GL: OpenGL renderer <{0}>'.format(renderer))
|
||||
|
||||
# Let the user know if his graphics hardware/drivers are too old
|
||||
major, minor = gl_get_version()
|
||||
Logger.info('GL: OpenGL parsed version: %d, %d' % (major, minor))
|
||||
if ((major, minor) < MIN_REQUIRED_GL_VERSION and backend != "mock"):
|
||||
if hasattr(sys, "_kivy_opengl_required_func"):
|
||||
sys._kivy_opengl_required_func(major, minor, version, vendor,
|
||||
renderer)
|
||||
else:
|
||||
msg = (
|
||||
'GL: Minimum required OpenGL version (2.0) NOT found!\n\n'
|
||||
'OpenGL version detected: {0}.{1}\n\n'
|
||||
'Version: {2}\nVendor: {3}\nRenderer: {4}\n\n'
|
||||
'Try upgrading your graphics drivers and/or your '
|
||||
'graphics hardware in case of problems.\n\n'
|
||||
'The application will leave now.').format(
|
||||
major, minor, version, vendor, renderer)
|
||||
Logger.critical(msg)
|
||||
msgbox(msg)
|
||||
|
||||
if platform != 'android':
|
||||
# XXX in the android emulator (latest version at 22 march 2013),
|
||||
# this call was segfaulting the gl stack.
|
||||
Logger.info('GL: Shading version <{0}>'.format(glGetString(
|
||||
GL_SHADING_LANGUAGE_VERSION)))
|
||||
Logger.info('GL: Texture max size <{0}>'.format(glGetIntegerv(
|
||||
GL_MAX_TEXTURE_SIZE)[0]))
|
||||
Logger.info('GL: Texture max units <{0}>'.format(glGetIntegerv(
|
||||
GL_MAX_TEXTURE_IMAGE_UNITS)[0]))
|
||||
|
||||
# To be able to use our GL provider, we must have a window
|
||||
# Automatically import window auto to ensure the default window creation
|
||||
import kivy.core.window # NOQA
|
||||
BIN
kivy/core/gl/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/gl/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
1002
kivy/core/image/__init__.py
Normal file
1002
kivy/core/image/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
kivy/core/image/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/image/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user