first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
519
kivy_venv/lib/python3.11/site-packages/kivy/__init__.py
Normal file
519
kivy_venv/lib/python3.11/site-packages/kivy/__init__.py
Normal file
|
@ -0,0 +1,519 @@
|
|||
'''
|
||||
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
|
||||
import importlib
|
||||
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:
|
||||
module_spec = importer.find_spec(modname)
|
||||
mod = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(mod)
|
||||
|
||||
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('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()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
131
kivy_venv/lib/python3.11/site-packages/kivy/_clock.pxd
Normal file
131
kivy_venv/lib/python3.11/site-packages/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)
|
Binary file not shown.
68
kivy_venv/lib/python3.11/site-packages/kivy/_event.pxd
Normal file
68
kivy_venv/lib/python3.11/site-packages/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
|
Binary file not shown.
5
kivy_venv/lib/python3.11/site-packages/kivy/_metrics.pxd
Normal file
5
kivy_venv/lib/python3.11/site-packages/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_venv/lib/python3.11/site-packages/kivy/_version.py
Normal file
17
kivy_venv/lib/python3.11/site-packages/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 = 3
|
||||
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 = '9ef218027178115a66b417ae34b21f488bdd3617'
|
||||
_kivy_build_date = '20240105'
|
831
kivy_venv/lib/python3.11/site-packages/kivy/animation.py
Normal file
831
kivy_venv/lib/python3.11/site-packages/kivy/animation.py
Normal file
|
@ -0,0 +1,831 @@
|
|||
'''
|
||||
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):
|
||||
Animation._instances.discard(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
|
1213
kivy_venv/lib/python3.11/site-packages/kivy/app.py
Normal file
1213
kivy_venv/lib/python3.11/site-packages/kivy/app.py
Normal file
File diff suppressed because it is too large
Load diff
456
kivy_venv/lib/python3.11/site-packages/kivy/atlas.py
Normal file
456
kivy_venv/lib/python3.11/site-packages/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_venv/lib/python3.11/site-packages/kivy/base.py
Normal file
617
kivy_venv/lib/python3.11/site-packages/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_venv/lib/python3.11/site-packages/kivy/cache.py
Normal file
262
kivy_venv/lib/python3.11/site-packages/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_venv/lib/python3.11/site-packages/kivy/clock.py
Normal file
1172
kivy_venv/lib/python3.11/site-packages/kivy/clock.py
Normal file
File diff suppressed because it is too large
Load diff
82
kivy_venv/lib/python3.11/site-packages/kivy/compat.py
Normal file
82
kivy_venv/lib/python3.11/site-packages/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
|
1004
kivy_venv/lib/python3.11/site-packages/kivy/config.py
Normal file
1004
kivy_venv/lib/python3.11/site-packages/kivy/config.py
Normal file
File diff suppressed because it is too large
Load diff
102
kivy_venv/lib/python3.11/site-packages/kivy/context.py
Normal file
102
kivy_venv/lib/python3.11/site-packages/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_venv/lib/python3.11/site-packages/kivy/core/__init__.py
Normal file
247
kivy_venv/lib/python3.11/site-packages/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))
|
Binary file not shown.
|
@ -0,0 +1,223 @@
|
|||
'''
|
||||
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.'''
|
||||
|
||||
__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)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
Binary file not shown.
|
@ -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):
|
||||
'''Initialize 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 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))
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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 initialized.
|
||||
'''
|
||||
|
||||
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')
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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']
|
Binary file not shown.
|
@ -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)
|
|
@ -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']
|
|
@ -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())
|
|
@ -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']
|
|
@ -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',)
|
|
@ -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
|
|
@ -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']
|
|
@ -0,0 +1,107 @@
|
|||
'''
|
||||
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
|
||||
|
||||
GlobalLock = kernel32.GlobalLock
|
||||
GlobalLock.argtypes = [wintypes.HGLOBAL]
|
||||
GlobalLock.restype = wintypes.LPVOID
|
||||
|
||||
GlobalUnlock = kernel32.GlobalUnlock
|
||||
GlobalUnlock.argtypes = [wintypes.HGLOBAL]
|
||||
GlobalUnlock.restype = wintypes.BOOL
|
||||
|
||||
CF_UNICODETEXT = 13
|
||||
GMEM_MOVEABLE = 0x0002
|
||||
|
||||
|
||||
class ClipboardWindows(ClipboardBase):
|
||||
|
||||
def _copy(self, data):
|
||||
self._ensure_clipboard()
|
||||
self.put(data, self._clip_mime_type)
|
||||
|
||||
def get(self, mimetype='text/plain'):
|
||||
GetClipboardData = user32.GetClipboardData
|
||||
GetClipboardData.argtypes = [wintypes.UINT]
|
||||
GetClipboardData.restype = wintypes.HANDLE
|
||||
|
||||
user32.OpenClipboard(user32.GetActiveWindow())
|
||||
|
||||
# GetClipboardData returns a HANDLE to the clipboard data
|
||||
# which is a memory object containing the data
|
||||
# See: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboarddata # noqa: E501
|
||||
pcontents = GetClipboardData(CF_UNICODETEXT)
|
||||
|
||||
# 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 ''
|
||||
|
||||
# The handle returned by GetClipboardData is a memory object
|
||||
# and needs to be locked to get the actual pointer to the data
|
||||
pcontents_locked = GlobalLock(pcontents)
|
||||
data = c_wchar_p(pcontents_locked).value
|
||||
GlobalUnlock(pcontents)
|
||||
|
||||
user32.CloseClipboard()
|
||||
return data
|
||||
|
||||
def put(self, text, mimetype="text/plain"):
|
||||
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
|
||||
|
||||
user32.OpenClipboard(user32.GetActiveWindow())
|
||||
user32.EmptyClipboard()
|
||||
|
||||
# The wsclen function returns the number of
|
||||
# wide characters in a string (not including the null character)
|
||||
text_len = msvcrt.wcslen(text) + 1
|
||||
|
||||
# According to the docs regarding SetClipboardDatam, if the hMem
|
||||
# parameter identifies a memory object, the object must have
|
||||
# been allocated using the GMEM_MOVEABLE flag.
|
||||
# See: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata # noqa: E501
|
||||
# The size of the memory object is the number of wide characters in
|
||||
# the string plus one for the terminating null character
|
||||
hCd = GlobalAlloc(
|
||||
GMEM_MOVEABLE, ctypes.sizeof(ctypes.c_wchar) * text_len
|
||||
)
|
||||
|
||||
# Since the memory object is allocated with GMEM_MOVEABLE, should be
|
||||
# locked to get the actual pointer to the data.
|
||||
hCd_locked = GlobalLock(hCd)
|
||||
ctypes.memmove(
|
||||
c_wchar_p(hCd_locked),
|
||||
c_wchar_p(text),
|
||||
ctypes.sizeof(ctypes.c_wchar) * text_len,
|
||||
)
|
||||
GlobalUnlock(hCd)
|
||||
|
||||
# Finally, set the clipboard data (and then close the clipboard)
|
||||
SetClipboardData(CF_UNICODETEXT, hCd)
|
||||
user32.CloseClipboard()
|
||||
|
||||
def get_types(self):
|
||||
return ['text/plain']
|
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1,90 @@
|
|||
# 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
|
Binary file not shown.
1002
kivy_venv/lib/python3.11/site-packages/kivy/core/image/__init__.py
Normal file
1002
kivy_venv/lib/python3.11/site-packages/kivy/core/image/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue