Table Of Contents

Source code for kivy.core

'''
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


[docs] class CoreCriticalException(Exception): pass
def _is_strict_mode(): # Check if provider strict mode is enabled. # Returns True if KIVY_PROVIDER_STRICT is set to '1', 'true', or 'yes' # (case-insensitive). # value = os.environ.get('KIVY_PROVIDER_STRICT', '').lower() return value in ('1', 'true', 'yes') #: Centralized provider configuration registry. #: Single source of truth for all Kivy core providers. #: #: Each provider list defines both the available providers and their #: priority order (first = highest priority). #: #: .. versionadded:: 3.0.0 PROVIDER_CONFIGS = { 'window': [ ('egl_rpi', 'window_egl_rpi'), ('sdl3', 'window_sdl3'), ('x11', 'window_x11'), ], 'text': [ ('pil', 'text_pil'), ('sdl3', 'text_sdl3'), ('pango', 'text_pango'), ], 'video': [ ('avfoundation', 'video_avfoundation'), ('gstplayer', 'video_gstplayer'), ('ffmpeg', 'video_ffmpeg'), ('ffpyplayer', 'video_ffpyplayer'), ('null', 'video_null'), ], 'audio_output': [ ('gstplayer', 'audio_gstplayer'), ('ffpyplayer', 'audio_ffpyplayer'), ('sdl3', 'audio_sdl3'), ('avplayer', 'audio_avplayer'), ('android', 'audio_android'), ], 'image': [ ('tex', 'img_tex'), ('imageio', 'img_imageio'), ('dds', 'img_dds'), ('sdl3', 'img_sdl3'), ('pil', 'img_pil'), ('ffpy', 'img_ffpyplayer'), ('thorvg_svg', 'img_thorvg_svg'), ], 'camera': [ ('opencv', 'camera_opencv'), ('gi', 'camera_gi'), ('avfoundation', 'camera_avfoundation'), ('android', 'camera_android'), ('picamera', 'camera_picamera'), ], 'lottie': [ ('thorvg', 'lottie_thorvg'), ('null', 'lottie_null'), ], 'spelling': [ ('enchant', 'spelling_enchant'), ('osxappkit', 'spelling_osxappkit'), ], 'clipboard': [ ('android', 'clipboard_android'), ('winctypes', 'clipboard_winctypes'), ('xsel', 'clipboard_xsel'), ('xclip', 'clipboard_xclip'), ('dbusklipper', 'clipboard_dbusklipper'), ('nspaste', 'clipboard_nspaste'), ('sdl3', 'clipboard_sdl3'), ('dummy', 'clipboard_dummy'), ('gtk3', 'clipboard_gtk3'), ], 'svg': [ ('thorvg', 'svg_thorvg'), ], }
[docs] def get_provider_options(category): '''Get tuple of provider names in priority order for kivy_options. :Parameters: `category`: str The provider category (e.g., 'window', 'text', 'image') :Returns: tuple: Tuple of provider names in priority order .. versionadded:: 3.0.0 ''' return tuple(name for name, module in PROVIDER_CONFIGS[category])
[docs] def get_provider_modules(category): '''Get dict mapping provider names to module names. :Parameters: `category`: str The provider category (e.g., 'window', 'text', 'image') :Returns: dict: Dictionary mapping provider_name to module_name .. versionadded:: 3.0.0 ''' return dict(PROVIDER_CONFIGS[category])
[docs] def make_provider_tuple(provider_name, all_providers, class_name=None): '''Create a provider tuple for core_register_libs or core_select_lib. Helper function to construct provider tuples, eliminating duplication where the provider name appears both as a string and as a dict key. :Parameters: `provider_name`: str The provider name (e.g., 'sdl3', 'pil', 'android') `all_providers`: dict Dictionary returned by get_provider_modules() `class_name`: str, optional Class name for 3-tuple format (used by core_select_lib) :Returns: tuple: Either (provider_name, module_name) for core_register_libs, or (provider_name, module_name, class_name) for core_select_lib :Example: >>> all_providers = get_provider_modules('audio_output') >>> # 2-tuple format for core_register_libs >>> make_provider_tuple('sdl3', all_providers) ('sdl3', 'audio_sdl3') >>> # 3-tuple format for core_select_lib >>> make_provider_tuple('android', all_providers, 'CameraAndroid') ('android', 'camera_android', 'CameraAndroid') .. versionadded:: 3.0.0 ''' if class_name is None: return (provider_name, all_providers[provider_name]) return (provider_name, all_providers[provider_name], class_name)
[docs] def get_all_categories(): '''Get list of all provider categories. :Returns: list: List of all provider category names .. versionadded:: 3.0.0 ''' return list(PROVIDER_CONFIGS.keys())
def load_with_provider_selection( filename, extension, provider_name, providers_by_name, category_name, check_compatibility, try_load, fallback_load ): # # Generic provider selection and loading logic. # This function implements the common pattern for loading resources with # optional provider selection, validation, and fallback behavior used across # audio, image provider systems. # :param filename: File to load (used for error messages) # :param extension: File extension (e.g., 'mp3', 'png', 'ttf') # :param provider_name: Requested provider name (or None for auto-selection) # :param providers_by_name: Dict mapping provider names to provider classes # :param category_name: Category for logging (e.g., 'Audio', 'Image', 'Text') # :param check_compatibility: Callable(provider_class, extension) -> bool # Function to check if provider supports the given extension # :param try_load: Callable(provider_class, filename) -> result or None # Function to attempt loading with the given provider # :param fallback_load: Callable() -> result or None # Function to load using default provider priority # :returns: Loaded resource or None if loading fails # :raises ValueError: If provider not found or incompatible (strict mode only) # :raises Exception: If provider fails to load (strict mode only) # .. versionadded:: 3.0.0 # strict_mode = _is_strict_mode() # If specific provider requested if provider_name: target_provider = providers_by_name.get(provider_name.lower()) if target_provider is None: # Provider not found/available available = list(providers_by_name.keys()) msg = ( f"{category_name}: Provider {provider_name!r} not found. " f"Available: {available}" ) if strict_mode: raise ValueError(msg) else: Logger.warning(msg + " Falling back to default priority.") return fallback_load() # Check if provider is compatible if not check_compatibility(target_provider, extension): msg = ( f"{category_name}: Provider {provider_name!r} does not support " f"{extension!r} format." ) if strict_mode: raise ValueError(msg) else: Logger.warning(msg + " Falling back to default priority.") return fallback_load() # Try the requested provider result = try_load(target_provider, filename) if result is not None: return result # Provider failed to load msg = ( f"{category_name}: Provider {provider_name!r} failed to load " f"<{filename}>" ) if strict_mode: raise Exception(msg) else: Logger.warning(msg + " Falling back to default priority.") return fallback_load() # No specific provider requested - use default priority return fallback_load() 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))