Files
renpy/renpy/exports/platformexports.py
T
2025-01-30 23:12:21 +01:00

295 lines
8.0 KiB
Python

# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals # type: ignore
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
import os
import sys
import threading
import renpy
import pygame_sdl2
from renpy.exports.commonexports import renpy_pure
@renpy_pure
def variant(name):
"""
:doc: screens
Returns true if `name` is a screen variant that corresponds to the
context in which Ren'Py is currently executing. See :ref:`screen-variants`
for more details. This function can be used as the condition in an
if statement to switch behavior based on the selected screen variant.
`name` can also be a list of variants, in which case this function
returns True if any of the variants would.
"""
if isinstance(name, str):
return name in renpy.config.variants
else:
for n in name:
if n in renpy.config.variants:
return True
return False
def vibrate(duration):
"""
:doc: other
Causes the device to vibrate for `duration` seconds. Currently, this
is only supported on Android.
"""
if duration < 0.01:
duration = 0.01
if renpy.android:
import android # @UnresolvedImport
android.vibrate(duration)
def invoke_in_thread(fn, *args, **kwargs):
"""
:doc: other
Invokes the function `fn` in a background thread, passing it the
provided arguments and keyword arguments. Restarts the interaction
once the thread returns.
This function creates a daemon thread, which will be automatically
stopped when Ren'Py is shutting down.
This thread is very limited in what it can do with the Ren'Py API.
Changing store variables is allowed, as are calling the following
functions:
* :func:`renpy.restart_interaction`
* :func:`renpy.invoke_in_main_thread`
* :func:`renpy.queue_event`
Most other portions of the Ren'Py API are expected to be called from
the main thread.
This does not work on the web platform, except for immediately returning
without an error.
"""
if renpy.emscripten:
return
def run():
try:
fn(*args, **kwargs)
except Exception:
import traceback
traceback.print_exc()
renpy.exports.restart_interaction()
t = threading.Thread(target=run)
t.daemon = True
t.start()
def invoke_in_main_thread(fn, *args, **kwargs):
"""
:doc: other
This runs the given function with the given arguments in the main
thread. The function runs in an interaction context similar to an
event handler. This is meant to be called from a separate thread,
whose creation is handled by :func:`renpy.invoke_in_thread`.
If a single thread schedules multiple functions to be invoked, it is guaranteed
that they will be run in the order in which they have been scheduled::
def ran_in_a_thread():
renpy.invoke_in_main_thread(a)
renpy.invoke_in_main_thread(b)
In this example, it is guaranteed that ``a`` will return before
``b`` is called. The order of calls made from different threads is not
guaranteed.
This may not be called during the init phase.
"""
if renpy.game.context().init_phase:
raise Exception("invoke_in_main_thread may not be called during the init phase.")
renpy.display.interface.invoke_queue.append((fn, args, kwargs))
old_battery = False
def get_on_battery():
"""
:doc: other
Returns True if Ren'Py is running on a device that is powered by an internal
battery, or False if the device is being charged by some external source.
"""
global old_battery
pi = pygame_sdl2.power.get_power_info() # @UndefinedVariable
if pi.state == pygame_sdl2.POWERSTATE_UNKNOWN: # @UndefinedVariable
return old_battery
elif pi.state == pygame_sdl2.POWERSTATE_ON_BATTERY: # @UndefinedVariable
old_battery = True
return True
else:
old_battery = False
return False
sdl_dll = False
def get_sdl_dll():
"""
:doc: sdl
Returns a ctypes.cdll object that refers to the library that contains
the instance of SDL2 that Ren'Py is using. If this fails, None is returned.
"""
global sdl_dll
if sdl_dll is not False:
return sdl_dll
try:
lib = os.path.dirname(sys.executable) + "/"
DLLS = [ None, lib + "librenpython.dll", lib + "librenpython.dylib", lib + "librenpython.so", "librenpython.so", "SDL2.dll", "libSDL2.dylib", "libSDL2-2.0.so.0" ]
import ctypes
for i in DLLS:
try:
# Look for the DLL.
dll = ctypes.cdll[i]
# See if it has SDL_GetError..
dll.SDL_GetError
except Exception as e:
continue
sdl_dll = dll
return dll
except Exception:
pass
sdl_dll = None
return None
def get_sdl_window_pointer():
"""
:doc: sdl
:rtype: ctypes.c_void_p | None
Returns a pointer to the main window, or None if the main window is not
displayed (or some other problem occurs).
"""
try:
window = pygame_sdl2.display.get_window()
if window is None:
return None
return window.get_sdl_window_pointer()
except Exception:
return None
def check_permission(permission):
"""
:doc: android_permission
Checks to see if an Android permission has been granted to this application.
`permission`
A string giving the name of the permission, for example, "android.permission.WRITE_EXTERNAL_STORAGE".
Returns true if the permission has been granted, false if it has not or if called on
a non-Android platform.
"""
if not renpy.android:
return False
from jnius import autoclass
PythonSDLActivity = autoclass("org.renpy.android.PythonSDLActivity")
activity = PythonSDLActivity.mActivity
try:
return activity.checkSelfPermission(permission) == 0 # PackageManager.PERMISSION_GRANTED
except Exception:
return False
def request_permission(permission):
"""
:doc: android_permission
Asks Android to grant a permission to this application. The user may be
prompted to grant the permission.
`permission`
A string giving the name of the permission, for example, "android.permission.WRITE_EXTERNAL_STORAGE".
Returns true if the permission has been granted, false if not or if called on a
non-Android platform.
"""
if not renpy.android:
return False
return get_sdl_dll().SDL_AndroidRequestPermission(permission.encode("utf-8")) # type: ignore
def open_url(url):
"""
:doc: other
Opens a URL in the system's web browser, if possible.
"""
if not renpy.mobile:
renpy.game.preferences.fullscreen = False
try:
import webbrowser
webbrowser.open_new(url)
except Exception:
pass