295 lines
8.0 KiB
Python
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
|