Merge remote-tracking branch 'upstream/main'

This commit is contained in:
jacob.eva 2024-09-12 14:46:55 +01:00
commit 39f00280a5
No known key found for this signature in database
GPG Key ID: 0B92E083BBCCAA1E
184 changed files with 17826 additions and 3619 deletions

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
prune sbapp/patches
prune sbapp/dist
prune recipes
prune docs
prune libs
prune .github

View File

@ -146,15 +146,24 @@ sideband
## On macOS
A DMG file containing a macOS app bundle is available on the [latest release](https://github.com/markqvist/Sideband/releases/latest) page.
On macOS, you can install Sideband with `pip3` or `pipx`. Due to the many different potential Python versions and install paths across macOS versions, the easiest install method is to use `pipx`.
Please note that audio messaging functionality isn't supported on macOS yet. Please support the development if you'd like to see this feature added faster.
Alternatively, you can install Sideband with ``pip3`` on macOS:
If you don't already have the `pipx` package manager installed, it can be installed via [Homebrew](https://brew.sh/) with `brew install pipx`.
```bash
# Install Sideband and dependencies on macOS:
pip3 install sbapp
# Install Sideband and dependencies on macOS using pipx:
pipx install sbapp
pipx ensurepath
# Run it
sideband
```
Or, if you prefer to use `pip` directly, follow the instructions below. In this case, if you have not already installed Python and `pip3` on your macOS system, [download and install](https://www.python.org/downloads/) the latest version first.
```bash
# Install Sideband and dependencies on macOS using pip:
pip3 install sbapp --user --break-system-packages
# Run it:
python3 -m sbapp.main
@ -166,8 +175,6 @@ sideband
```
If you have not already installed Python and `pip3` on your macOS system, [download and install](https://www.python.org/downloads/) the latest version first.
## On Windows
Even though there is currently not an automated installer, or packaged `.exe` file for Sideband on Windows, you can still install it through `pip`. If you don't already have Python installed, [download and install](https://www.python.org/downloads/) the latest version of Python.

View File

@ -1,4 +1,4 @@
# This is a bare-minimum telemetry plugin
# This is a basic telemetry plugin
# example that you can build upon to
# implement your own telemetry plugins.
@ -23,15 +23,43 @@ class BasicTelemetryPlugin(SidebandTelemetryPlugin):
def update_telemetry(self, telemeter):
if telemeter != None:
RNS.log("Updating power sensors")
# Create power consumption sensors
telemeter.synthesize("power_consumption")
telemeter.sensors["power_consumption"].update_consumer(2163.15, type_label="Heater consumption")
telemeter.sensors["power_consumption"].update_consumer(12.7/1e6, type_label="Receiver consumption")
telemeter.sensors["power_consumption"].update_consumer(0.055, type_label="LED consumption")
telemeter.sensors["power_consumption"].update_consumer(0.055, type_label="LED consumption", custom_icon="led-on")
telemeter.sensors["power_consumption"].update_consumer(982.22*1e9, type_label="Smelter consumption")
# Create power production sensor
telemeter.synthesize("power_production")
telemeter.sensors["power_production"].update_producer(5732.15, type_label="Solar production")
telemeter.sensors["power_production"].update_producer(5732.15, type_label="Solar production", custom_icon="solar-power-variant")
# Create storage sensor
telemeter.synthesize("nvm")
telemeter.sensors["nvm"].update_entry(capacity=256e9, used=38.48e9, type_label="SSD")
# Create RAM sensors
telemeter.synthesize("ram")
telemeter.sensors["ram"].update_entry(capacity=8e9, used=3.48e9, type_label="RAM")
telemeter.sensors["ram"].update_entry(capacity=16e9, used=0.72e9, type_label="Swap")
# Create CPU sensor
telemeter.synthesize("processor")
telemeter.sensors["processor"].update_entry(current_load=0.42, clock=2.45e9, load_avgs=[0.27, 0.43, 0.49], type_label="CPU")
# Create custom sensor
telemeter.synthesize("custom")
telemeter.sensors["custom"].update_entry("311 seconds", type_label="Specific impulse is", custom_icon="rocket-launch")
telemeter.sensors["custom"].update_entry("a lie", type_label="The cake is", custom_icon="cake-variant")
# Create tank sensors
telemeter.synthesize("tank")
telemeter.sensors["tank"].update_entry(capacity=1500, level=728, type_label="Fresh water", custom_icon="cup-water")
telemeter.sensors["tank"].update_entry(capacity=2000, level=122, unit="L", type_label="Waste tank")
# Create fuel sensor
telemeter.synthesize("fuel")
telemeter.sensors["fuel"].update_entry(capacity=75, level=61)
# Finally, tell Sideband what class in this
# file is the actual plugin class.

BIN
libs/arm64/libcodec2.so Executable file

Binary file not shown.

BIN
libs/armeabi/libcodec2.so Executable file

Binary file not shown.

View File

@ -0,0 +1,50 @@
from os.path import join
from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import current_directory, shprint
import sh
# For debugging, clean with
# buildozer android p4a -- clean_recipe_build codec2 --local-recipes ~/Information/Source/Sideband/recipes
class Codec2Recipe(Recipe):
url = "https://github.com/markqvist/codec2/archive/00e01c9d72d3b1607e165c71c4c9c942d277dfac.tar.gz"
built_libraries = {'libcodec2.so': 'build_android/src'}
def include_flags(self, arch):
'''Returns a string with the include folders'''
codec2_includes = join(self.get_build_dir(arch.arch), 'build_android')
return (' -I' + codec2_includes)
def link_dirs_flags(self, arch):
'''Returns a string with the appropriate `-L<lib directory>` to link
with the libs. This string is usually added to the environment
variable `LDFLAGS`'''
return ' -L' + self.get_build_dir(arch.arch)
# def link_libs_flags(self):
# '''Returns a string with the appropriate `-l<lib>` flags to link with
# the libs. This string is usually added to the environment
# variable `LIBS`'''
# return ' -lcodec2{version} -lssl{version}'.format(version=self.version)
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
env = self.get_recipe_env(arch)
flags = [
"..",
"--log-level=TRACE",
"--fresh",
"-DCMAKE_BUILD_TYPE=Release",
]
mkdir = sh.mkdir("-p", "build_android")
# cd = sh.cd("build_android")
os.chdir("build_android")
cmake = sh.Command('cmake')
shprint(cmake, *flags, _env=env)
shprint(sh.make, _env=env)
sh.cp("../src/codec2.h", "./codec2/")
recipe = Codec2Recipe()

BIN
recipes/codec2/generate_codebook Executable file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
--- ffpyplayer/setup.py 2024-06-02 11:10:49.691183467 +0530
+++ ffpyplayer.mod/setup.py 2024-06-02 11:20:16.220966873 +0530
@@ -27,12 +27,6 @@
# This sets whether or not Cython gets added to setup_requires.
declare_cython = False
-if platform in ('ios', 'android'):
- # NEVER use or declare cython on these platforms
- print('Not using cython on %s' % platform)
- can_use_cython = False
-else:
- declare_cython = True
src_path = build_path = dirname(__file__)
print(f'Source/build path: {src_path}')

View File

@ -0,0 +1,22 @@
from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import current_directory, shprint
import sh
class OpusRecipe(Recipe):
version = '1.5.2'
url = "https://downloads.xiph.org/releases/opus/opus-{version}.tar.gz"
built_libraries = {'libopus.so': '.libs'}
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
env = self.get_recipe_env(arch)
flags = [
'--host=' + arch.command_prefix,
]
configure = sh.Command('./configure')
shprint(configure, *flags, _env=env)
shprint(sh.make, _env=env)
recipe = OpusRecipe()

152
recipes/mffmpeg/__init__.py Normal file
View File

@ -0,0 +1,152 @@
from pythonforandroid.toolchain import Recipe, current_directory, shprint
from os.path import exists, join, realpath
import sh
class FFMpegRecipe(Recipe):
version = 'n4.3.1'
# Moved to github.com instead of ffmpeg.org to improve download speed
url = 'https://github.com/FFmpeg/FFmpeg/archive/{version}.zip'
depends = ['sdl2'] # Need this to build correct recipe order
opts_depends = ['openssl', 'ffpyplayer_codecs']
patches = ['patches/configure.patch']
def should_build(self, arch):
build_dir = self.get_build_dir(arch.arch)
return not exists(join(build_dir, 'lib', 'libavcodec.so'))
def prebuild_arch(self, arch):
self.apply_patches(arch)
def get_recipe_env(self, arch):
env = super().get_recipe_env(arch)
env['NDK'] = self.ctx.ndk_dir
return env
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
env = arch.get_env()
# flags = ['--disable-everything']
flags = []
cflags = []
ldflags = []
if 'openssl' in self.ctx.recipe_build_order:
flags += [
'--enable-openssl',
'--enable-nonfree',
'--enable-protocol=https,tls_openssl',
]
build_dir = Recipe.get_recipe(
'openssl', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/',
'-DOPENSSL_API_COMPAT=0x10002000L']
ldflags += ['-L' + build_dir]
if 'ffpyplayer_codecs' in self.ctx.recipe_build_order:
# Enable GPL
flags += ['--enable-gpl']
# libx264
flags += ['--enable-libx264']
build_dir = Recipe.get_recipe(
'libx264', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lx264', '-L' + build_dir + '/lib/']
# libshine
flags += ['--enable-libshine']
build_dir = Recipe.get_recipe('libshine', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lshine', '-L' + build_dir + '/lib/']
ldflags += ['-lm']
# libvpx
flags += ['--enable-libvpx']
build_dir = Recipe.get_recipe(
'libvpx', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lvpx', '-L' + build_dir + '/lib/']
# Enable all codecs:
flags += [
'--enable-parsers',
'--enable-decoders',
'--enable-encoders',
'--enable-muxers',
'--enable-demuxers',
]
else:
# Enable codecs only for .mp4:
flags += [
'--enable-parser=aac,ac3,h261,h264,mpegaudio,mpeg4video,mpegvideo,vc1',
'--enable-decoder=aac,h264,mpeg4,mpegvideo',
'--enable-muxer=h264,mov,mp4,mpeg2video',
'--enable-demuxer=aac,h264,m4v,mov,mpegvideo,vc1,rtsp',
]
# needed to prevent _ffmpeg.so: version node not found for symbol av_init_packet@LIBAVFORMAT_52
# /usr/bin/ld: failed to set dynamic section sizes: Bad value
flags += [
'--disable-symver',
]
# disable binaries / doc
flags += [
# '--disable-programs',
'--disable-doc',
]
# other flags:
flags += [
'--enable-filter=aresample,resample,crop,adelay,volume,scale',
'--enable-protocol=file,http,hls,udp,tcp',
'--enable-small',
'--enable-hwaccels',
'--enable-pic',
'--disable-static',
'--disable-debug',
'--enable-shared',
]
if 'arm64' in arch.arch:
arch_flag = 'aarch64'
elif 'x86' in arch.arch:
arch_flag = 'x86'
flags += ['--disable-asm']
else:
arch_flag = 'arm'
# android:
flags += [
'--target-os=android',
'--enable-cross-compile',
'--cross-prefix={}-'.format(arch.target),
'--arch={}'.format(arch_flag),
'--strip={}'.format(self.ctx.ndk.llvm_strip),
'--sysroot={}'.format(self.ctx.ndk.sysroot),
'--enable-neon',
'--prefix={}'.format(realpath('.')),
]
if arch_flag == 'arm':
cflags += [
'-mfpu=vfpv3-d16',
'-mfloat-abi=softfp',
'-fPIC',
]
env['CFLAGS'] += ' ' + ' '.join(cflags)
env['LDFLAGS'] += ' ' + ' '.join(ldflags)
configure = sh.Command('./configure')
shprint(configure, *flags, _env=env)
shprint(sh.make, '-j4', _env=env)
shprint(sh.make, 'install', _env=env)
# copy libs:
sh.cp('-a', sh.glob('./lib/lib*.so'),
self.ctx.get_libs_dir(arch.arch))
recipe = FFMpegRecipe()

View File

@ -0,0 +1,11 @@
--- ./configure 2020-10-11 19:12:16.759760904 +0200
+++ ./configure.patch 2020-10-11 19:15:49.059533563 +0200
@@ -6361,7 +6361,7 @@
enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_render_cairo
enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket
enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++"
-enabled libshine && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer
+enabled libshine && require "shine" shine/layer3.h shine_encode_buffer -lshine -lm
enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
require libsmbclient libsmbclient.h smbc_init -lsmbclient; }
enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++

75
recipes/numpy/__init__.py Normal file
View File

@ -0,0 +1,75 @@
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
from pythonforandroid.logger import shprint, info
from pythonforandroid.util import current_directory
from multiprocessing import cpu_count
from os.path import join
import glob
import sh
import shutil
class NumpyRecipe(CompiledComponentsPythonRecipe):
version = '1.22.3'
url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.zip'
site_packages_name = 'numpy'
depends = ['setuptools', 'cython']
install_in_hostpython = True
call_hostpython_via_targetpython = False
patches = [
join("patches", "remove-default-paths.patch"),
join("patches", "add_libm_explicitly_to_build.patch"),
join("patches", "ranlib.patch"),
]
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
env = super().get_recipe_env(arch, with_flags_in_cc)
# _PYTHON_HOST_PLATFORM declares that we're cross-compiling
# and avoids issues when building on macOS for Android targets.
env["_PYTHON_HOST_PLATFORM"] = arch.command_prefix
# NPY_DISABLE_SVML=1 allows numpy to build for non-AVX512 CPUs
# See: https://github.com/numpy/numpy/issues/21196
env["NPY_DISABLE_SVML"] = "1"
return env
def _build_compiled_components(self, arch):
info('Building compiled components in {}'.format(self.name))
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
hostpython = sh.Command(self.hostpython_location)
shprint(hostpython, 'setup.py', self.build_cmd, '-v',
_env=env, *self.setup_extra_args)
build_dir = glob.glob('build/lib.*')[0]
shprint(sh.find, build_dir, '-name', '"*.o"', '-exec',
env['STRIP'], '{}', ';', _env=env)
def _rebuild_compiled_components(self, arch, env):
info('Rebuilding compiled components in {}'.format(self.name))
hostpython = sh.Command(self.real_hostpython_location)
shprint(hostpython, 'setup.py', 'clean', '--all', '--force', _env=env)
shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,
*self.setup_extra_args)
def build_compiled_components(self, arch):
self.setup_extra_args = ['-j', str(cpu_count())]
self._build_compiled_components(arch)
self.setup_extra_args = []
def rebuild_compiled_components(self, arch, env):
self.setup_extra_args = ['-j', str(cpu_count())]
self._rebuild_compiled_components(arch, env)
self.setup_extra_args = []
def get_hostrecipe_env(self, arch):
env = super().get_hostrecipe_env(arch)
env['RANLIB'] = shutil.which('ranlib')
return env
recipe = NumpyRecipe()

View File

@ -0,0 +1,20 @@
diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py
index 66c07c9..d34bd93 100644
--- a/numpy/linalg/setup.py
+++ b/numpy/linalg/setup.py
@@ -46,6 +46,7 @@ def configuration(parent_package='', top_path=None):
sources=['lapack_litemodule.c', get_lapack_lite_sources],
depends=['lapack_lite/f2c.h'],
extra_info=lapack_info,
+ libraries=['m'],
)
# umath_linalg module
@@ -54,7 +54,7 @@ def configuration(parent_package='', top_path=None):
sources=['umath_linalg.c.src', get_lapack_lite_sources],
depends=['lapack_lite/f2c.h'],
extra_info=lapack_info,
- libraries=['npymath'],
+ libraries=['npymath', 'm'],
)
return config

View File

@ -0,0 +1,11 @@
diff -Naur numpy.orig/numpy/distutils/unixccompiler.py numpy/numpy/distutils/unixccompiler.py
--- numpy.orig/numpy/distutils/unixccompiler.py 2022-05-28 10:22:10.000000000 +0200
+++ numpy/numpy/distutils/unixccompiler.py 2022-05-28 10:22:24.000000000 +0200
@@ -124,6 +124,7 @@
# platform intelligence here to skip ranlib if it's not
# needed -- or maybe Python's configure script took care of
# it for us, hence the check for leading colon.
+ self.ranlib = [os.environ.get('RANLIB')]
if self.ranlib:
display = '%s:@ %s' % (os.path.basename(self.ranlib[0]),
output_filename)

View File

@ -0,0 +1,28 @@
diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py
index fc7018a..7b514bc 100644
--- a/numpy/distutils/system_info.py
+++ b/numpy/distutils/system_info.py
@@ -340,10 +340,10 @@ if os.path.join(sys.prefix, 'lib') not in default_lib_dirs:
default_include_dirs.append(os.path.join(sys.prefix, 'include'))
default_src_dirs.append(os.path.join(sys.prefix, 'src'))
-default_lib_dirs = [_m for _m in default_lib_dirs if os.path.isdir(_m)]
-default_runtime_dirs = [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
-default_include_dirs = [_m for _m in default_include_dirs if os.path.isdir(_m)]
-default_src_dirs = [_m for _m in default_src_dirs if os.path.isdir(_m)]
+default_lib_dirs = [] #[_m for _m in default_lib_dirs if os.path.isdir(_m)]
+default_runtime_dirs =[] # [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
+default_include_dirs =[] # [_m for _m in default_include_dirs if os.path.isdir(_m)]
+default_src_dirs =[] # [_m for _m in default_src_dirs if os.path.isdir(_m)]
so_ext = get_shared_lib_extension()
@@ -814,7 +814,7 @@ class system_info(object):
path = self.get_paths(self.section, key)
if path == ['']:
path = []
- return path
+ return []
def get_include_dirs(self, key='include_dirs'):
return self.get_paths(self.section, key)

View File

@ -0,0 +1,46 @@
from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import current_directory, shprint
import sh
import os
import time
class OpusFileRecipe(Recipe):
version = "0.12"
url = "https://downloads.xiph.org/releases/opus/opusfile-{version}.tar.gz"
depends = ['libogg']
built_libraries = {'libopusfile.so': '.libs'}
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
env = self.get_recipe_env(arch)
flags = [
"--host=" + arch.command_prefix,
"--disable-http",
"--disable-examples",
"--disable-doc",
"--disable-largefile",
]
cwd = os.getcwd()
ogg_include_path = cwd.replace("opusfile", "libogg")
env["CPPFLAGS"] += f" -I{ogg_include_path}/include"
# libogg_recipe = Recipe.get_recipe('libogg', self.ctx)
# env['CFLAGS'] += libogg_recipe.include_flags(arch)
# openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
# env['CFLAGS'] += openssl_recipe.include_flags(arch)
# env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
# env['LIBS'] = openssl_recipe.link_libs_flags()
from rich.pretty import pprint
pprint(env)
time.sleep(5)
configure = sh.Command('./configure')
shprint(configure, *flags, _env=env)
shprint(sh.make, _env=env)
recipe = OpusFileRecipe()

View File

@ -0,0 +1,57 @@
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
from pythonforandroid.toolchain import current_directory, shprint
from os.path import join
import sh
# class PyCodec2Recipe(IncludedFilesBehaviour, CythonRecipe):
class PyCodec2Recipe(CythonRecipe):
url = "https://github.com/markqvist/pycodec2/archive/refs/heads/main.zip"
# src_filename = "../../../pycodec2"
depends = ["setuptools", "numpy", "Cython", "codec2"]
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch, with_flags_in_cc=True):
"""
Adds codec2 recipe to include and library path.
"""
env = super().get_recipe_env(arch, with_flags_in_cc)
codec2_recipe = self.get_recipe('codec2', self.ctx)
env['CFLAGS'] += codec2_recipe.include_flags(arch) +" -l:libcodec2.so"
env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))
env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir)
env['LDFLAGS'] += codec2_recipe.link_dirs_flags(arch)
return env
def build_arch(self, arch):
super().build_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
pass
# print(arch.arch)
# print(arch)
# shprint(sh.Command("pwd"))
# shprint(sh.Command("ls"))
# pe_args = ["--replace-needed", "libcodec2.so.1.2", "libcodec2.so", "build/lib.linux-x86_64-3.11/pycodec2/pycodec2.cpython-311-x86_64-linux-gnu.so"]
# shprint(sh.Command("patchelf"), *pe_args)
# pe_args = ["--replace-needed", "libcodec2.so.1.2", "libcodec2.so", f"../../../../python-installs/sideband/{arch.arch}/pycodec2/pycodec2.cpython-311-x86_64-linux-gnu.so"]
# shprint(sh.Command("patchelf"), *pe_args)
# ../../../../python-installs/sideband/armeabi-v7a/pycodec2/pycodec2.cpython-311-x86_64-linux-gnu.so
# sbapp/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/other_builds/pycodec2/armeabi-v7a__ndk_target_24/pycodec2/build/lib.linux-x86_64-3.11/pycodec2/pycodec2.cpython-311-x86_64-linux-gnu.so
# sbapp/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/sideband/armeabi-v7a/pycodec2/pycodec2.cpython-311-x86_64-linux-gnu.so
# print("=========================")
# input()
def postbuild_arch(self, arch):
super().postbuild_arch(arch)
recipe = PyCodec2Recipe()
# patchelf --replace-needed libcodec2.so.1.2 libcodec2.so sbapp/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/_python_bundle__arm64-v8a/_python_bundle/site-packages/pycodec2/pycodec2.so
# patchelf --replace-needed libcodec2.so.1.2 libcodec2.so sbapp/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/_python_bundle__armeabi-v7a/_python_bundle/site-packages/pycodec2/pycodec2.so
# patchelf --replace-needed libcodec2.so.1.2 libcodec2.so sbapp/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/_python_bundle__arm64-v8a/_python_bundle/site-packages/pycodec2/pycodec2.so; patchelf --replace-needed libcodec2.so.1.2 libcodec2.so sbapp/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/_python_bundle__armeabi-v7a/_python_bundle/site-packages/pycodec2/pycodec2.so

View File

@ -15,7 +15,7 @@ cleanlibs:
cleanall: clean cleanlibs
pacthfiles: patchsdl injectxml
pacthfiles: patchsdl injectxml patchpycodec2
patchsdl:
# Pach USB HID behaviour
@ -28,6 +28,10 @@ patchsdl:
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/bootstrap_builds/sdl2/src/main/java/org/kivy/android/PythonService.java
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/src/main/java/org/kivy/android/PythonService.java
patchpycodec2:
patchelf --replace-needed libcodec2.so.1.2 libcodec2.so .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/_python_bundle__arm64-v8a/_python_bundle/site-packages/pycodec2/pycodec2.so
patchelf --replace-needed libcodec2.so.1.2 libcodec2.so .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/_python_bundle__armeabi-v7a/_python_bundle/site-packages/pycodec2/pycodec2.so
injectxml:
# mkdir /home/markqvist/.local/lib/python3.11/site-packages/pythonforandroid/bootstraps/sdl2/build/src/main/xml
# Inject XML on arm64-v8a
@ -62,7 +66,7 @@ fetchshare:
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/nomadnet-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/rnsh-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/sbapp-*-py3-none-any.whl ./share/pkg/
# cp ../../dist_archive/sbapp-*-py3-none-any.whl ./share/pkg/
cp ../../dist_archive/RNode_Firmware_*_Source.zip ./share/pkg/
zip --junk-paths ./share/pkg/example_plugins.zip ../docs/example_plugins/*.py
cp -r ../../dist_archive/reticulum.network ./share/mirrors/

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,12 +1,13 @@
# Entry version 20240630
[Desktop Entry]
Comment[en_US]=
Comment=
Comment[en_US]=Messaging, telemetry and remote control over LXMF
Comment=Messaging, telemetry and remote control over LXMF
Encoding=UTF-8
Exec=sideband
GenericName[en_US]=LXMF messaging, telemetry and remote control
GenericName=LXMF messaging, telemetry and remote control
GenericName[en_US]=LXMF client
GenericName=LXMF client
Icon=io.unsigned.sideband.png
Categories=InstantMessaging,Network
Categories=Utility
MimeType=
Name[en_US]=Sideband
Name=Sideband

View File

@ -6,31 +6,32 @@ package.domain = io.unsigned
source.dir = .
source.include_exts = py,png,jpg,jpeg,webp,ttf,kv,pyi,typed,so,0,1,2,3,atlas,frag,html,css,js,whl,zip,gz,woff2,pdf,epub,pgm
source.include_patterns = assets/*,assets/fonts/*,share/*
source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,precompiled/*,parked/*,./setup.py,Makef*,./Makefile,Makefile
source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,precompiled/*,parked/*,./setup.py,Makef*,./Makefile,Makefile,bin/*,build/*,dist/*,__pycache__/*
version.regex = __version__ = ['"](.*)['"]
version.filename = %(source.dir)s/main.py
android.numeric_version = 20240522
android.numeric_version = 20240911
# Cryptography recipe is currently broken, using RNS-internal crypto for now. Since
# relevant PRs have now been merged in Kivy/P4A, the next release will hopefully allow
# building a non-ancient PyCa/Cryptography distribution again. When this happens, add
# the "cryptography" dependency back in here.
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,android,able_recipe
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,android,able_recipe
p4a.local_recipes = ../Others/python-for-android/pythonforandroid/recipes
android.gradle_dependencies = com.android.support:support-compat:28.0.0
#android.enable_androidx = True
#android.add_aars = patches/support-compat-28.0.0.aar
p4a.local_recipes = ../recipes/
icon.filename = %(source.dir)s/assets/icon.png
presplash.filename = %(source.dir)s/assets/presplash_small.png
android.presplash_color = #00000000
# TODO: Fix
# TODO: Fix inability to set "user" orientation from spec
# This is currently handled by patching the APK manifest
orientation = portrait
fullscreen = 0
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION,RECORD_AUDIO
android.api = 30
android.api = 31
android.minapi = 24
android.ndk = 25b
android.skip_update = False
@ -44,7 +45,9 @@ android.add_gradle_repositories = flatDir { dirs("../../../../../../patches") }
services = sidebandservice:services/sidebandservice.py:foreground
android.whitelist = lib-dynload/termios.so
android.manifest.intent_filters = patches/intent-filter.xml
android.add_aars = patches/support-compat-28.0.0.aar
# android.add_libs_armeabi_v7a = ../libs/armeabi/*.so*
# android.add_libs_arm64_v8a = ../libs/arm64/*.so*
[buildozer]
log_level = 2

95
sbapp/freeze.py Normal file
View File

@ -0,0 +1,95 @@
import os
import re
import setuptools
import cx_Freeze
from pathlib import Path
build_appimage = True
def get_version() -> str:
version_file = os.path.join(
os.path.dirname(__file__), "main.py"
)
version_file_data = open(version_file, "rt", encoding="utf-8").read()
version_regex = r"(?<=^__version__ = ['\"])[^'\"]+(?=['\"]$)"
try:
version = re.findall(version_regex, version_file_data, re.M)[0]
return version
except IndexError:
raise ValueError(f"Unable to find version string in {version_file}.")
def get_variant() -> str:
version_file = os.path.join(
os.path.dirname(__file__), "main.py"
)
version_file_data = open(version_file, "rt", encoding="utf-8").read()
version_regex = r"(?<=^__variant__ = ['\"])[^'\"]+(?=['\"]$)"
try:
version = re.findall(version_regex, version_file_data, re.M)[0]
return version
except IndexError:
raise ValueError(f"Unable to find version string in {version_file}.")
__version__ = get_version()
__variant__ = get_variant()
def glob_paths(pattern):
out_files = []
src_path = os.path.join(os.path.dirname(__file__), "kivymd")
for root, dirs, files in os.walk(src_path):
for file in files:
if file.endswith(pattern):
filepath = os.path.join(str(Path(*Path(root).parts[1:])), file)
out_files.append(filepath.split(f"kivymd{os.sep}")[1])
return out_files
package_data = {
"": [
"assets/*",
"assets/fonts/*",
"assets/geoids/*",
"kivymd/fonts/*",
"kivymd/images/*",
"kivymd/*",
"mapview/icons/*",
*glob_paths(".kv")
]
}
print("Freezing Sideband "+__version__+" "+__variant__)
if build_appimage:
global_excludes = [".buildozer", "build", "dist"]
# Dependencies are automatically detected, but they might need fine-tuning.
appimage_options = {
"target_name": "Sideband",
"target_version": __version__+" "+__variant__,
"include_files": [],
"excludes": [],
"packages": ["kivy"],
"zip_include_packages": [],
"bin_path_excludes": global_excludes,
}
cx_Freeze.setup(
name="Sideband",
version=__version__,
author="Mark Qvist",
author_email="mark@unsigned.io",
url="https://unsigned.io/sideband",
executables=[
cx_Freeze.Executable(
script="main.py",
base="console",
target_name="Sideband",
shortcut_name="Sideband",
icon="assets/icon.png",
copyright="Copyright (c) 2024 Mark Qvist",
),
],
options={"build_appimage": appimage_options},
)

View File

@ -413,7 +413,7 @@ class MDDialog(BaseDialog):
and defaults to `[]`.
"""
width_offset = NumericProperty(dp(48))
width_offset = NumericProperty(dp(32))
"""
Dialog offset from device width.
@ -601,7 +601,7 @@ class MDDialog(BaseDialog):
self.width = min(dp(560), Window.width - self.width_offset)
elif self.size_hint == [1, 1] and DEVICE_TYPE == "mobile":
self.size_hint = (None, None)
self.width = min(dp(280), Window.width - self.width_offset)
self.width = min(dp(560), Window.width - self.width_offset)
if not self.title:
self._spacer_top = 0
@ -634,7 +634,7 @@ class MDDialog(BaseDialog):
self.width = max(
self.height + self.width_offset,
min(
dp(560) if DEVICE_TYPE != "mobile" else dp(280),
dp(560) if DEVICE_TYPE != "mobile" else dp(560),
Window.width - self.width_offset,
),
)

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{ args.package }}"
android:versionCode="{{ args.numeric_version }}"
android:versionName="{{ args.version }}"
android:installLocation="auto">
@ -46,16 +45,7 @@
{{ args.extra_manifest_xml }}
<!-- Create a Java class extending SDLActivity and place it in a
directory under src matching the package, e.g.
src/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
An example Java class can be found in README-android.txt
-->
<!-- Add android:extractNativeLibs="true" when building for API level >= 28 -->
<application android:label="@string/app_name"
{% if debug %}android:debuggable="true"{% endif %}
android:icon="@mipmap/icon"
@ -83,19 +73,22 @@
{% endif %}
>
{% if args.launcher %}
<intent-filter>
{% if args.launcher %}
<action android:name="org.kivy.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="{{ url_scheme }}" />
</intent-filter>
{% else %}
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
{% endif %}
{% if args.home_app %}
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
{% endif %}
</intent-filter>
{%- if args.intent_filters -%}
{{- args.intent_filters -}}
{%- endif -%}

View File

@ -113,7 +113,7 @@ public class PythonService extends Service implements Runnable {
Context context = getApplicationContext();
Intent contextIntent = new Intent(context, PythonActivity.class);
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
notification = new Notification(

View File

@ -39,4 +39,7 @@
<!-- Adafruit RAK4630 -->
<usb-device vendor-id="9114" product-id="32809" /><!-- 0x239a / 0x8029: RAK4630 -->
<!-- LilyGO T3S3 v1.2 -->
<usb-device vendor-id="12346" product-id="4097" /><!-- 0x303A / 0x1001: LilyGO T3S3 v1.2 -->
</resources>

View File

@ -13,11 +13,15 @@ __all__ = (
'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename'
)
__version__ = '2.1.0.dev0'
__version__ = '2.2.0.dev0'
from plyer import facades
from plyer.utils import Proxy
import RNS
if RNS.vendor.platformutils.is_android():
from plyer import facades
from plyer.utils import Proxy
else:
from sbapp.plyer import facades
from sbapp.plyer.utils import Proxy
#: Accelerometer proxy to :class:`plyer.facades.Accelerometer`
accelerometer = Proxy('accelerometer', facades.Accelerometer)

View File

@ -14,38 +14,76 @@ __all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera',
'Processors', 'StoragePath', 'Keystore', 'Bluetooth', 'Screenshot',
'STT', 'DeviceName')
from plyer.facades.accelerometer import Accelerometer
from plyer.facades.audio import Audio
from plyer.facades.barometer import Barometer
from plyer.facades.battery import Battery
from plyer.facades.call import Call
from plyer.facades.camera import Camera
from plyer.facades.compass import Compass
from plyer.facades.email import Email
from plyer.facades.filechooser import FileChooser
from plyer.facades.flash import Flash
from plyer.facades.gps import GPS
from plyer.facades.gravity import Gravity
from plyer.facades.gyroscope import Gyroscope
from plyer.facades.irblaster import IrBlaster
from plyer.facades.light import Light
from plyer.facades.proximity import Proximity
from plyer.facades.orientation import Orientation
from plyer.facades.notification import Notification
from plyer.facades.sms import Sms
from plyer.facades.stt import STT
from plyer.facades.tts import TTS
from plyer.facades.uniqueid import UniqueID
from plyer.facades.vibrator import Vibrator
from plyer.facades.wifi import Wifi
from plyer.facades.temperature import Temperature
from plyer.facades.humidity import Humidity
from plyer.facades.spatialorientation import SpatialOrientation
from plyer.facades.brightness import Brightness
from plyer.facades.keystore import Keystore
from plyer.facades.storagepath import StoragePath
from plyer.facades.bluetooth import Bluetooth
from plyer.facades.processors import Processors
from plyer.facades.cpu import CPU
from plyer.facades.screenshot import Screenshot
from plyer.facades.devicename import DeviceName
import RNS
if RNS.vendor.platformutils.is_android():
from plyer.facades.accelerometer import Accelerometer
from plyer.facades.audio import Audio
from plyer.facades.barometer import Barometer
from plyer.facades.battery import Battery
from plyer.facades.call import Call
from plyer.facades.camera import Camera
from plyer.facades.compass import Compass
from plyer.facades.email import Email
from plyer.facades.filechooser import FileChooser
from plyer.facades.flash import Flash
from plyer.facades.gps import GPS
from plyer.facades.gravity import Gravity
from plyer.facades.gyroscope import Gyroscope
from plyer.facades.irblaster import IrBlaster
from plyer.facades.light import Light
from plyer.facades.proximity import Proximity
from plyer.facades.orientation import Orientation
from plyer.facades.notification import Notification
from plyer.facades.sms import Sms
from plyer.facades.stt import STT
from plyer.facades.tts import TTS
from plyer.facades.uniqueid import UniqueID
from plyer.facades.vibrator import Vibrator
from plyer.facades.wifi import Wifi
from plyer.facades.temperature import Temperature
from plyer.facades.humidity import Humidity
from plyer.facades.spatialorientation import SpatialOrientation
from plyer.facades.brightness import Brightness
from plyer.facades.keystore import Keystore
from plyer.facades.storagepath import StoragePath
from plyer.facades.bluetooth import Bluetooth
from plyer.facades.processors import Processors
from plyer.facades.cpu import CPU
from plyer.facades.screenshot import Screenshot
from plyer.facades.devicename import DeviceName
else:
from sbapp.plyer.facades.accelerometer import Accelerometer
from sbapp.plyer.facades.audio import Audio
from sbapp.plyer.facades.barometer import Barometer
from sbapp.plyer.facades.battery import Battery
from sbapp.plyer.facades.call import Call
from sbapp.plyer.facades.camera import Camera
from sbapp.plyer.facades.compass import Compass
from sbapp.plyer.facades.email import Email
from sbapp.plyer.facades.filechooser import FileChooser
from sbapp.plyer.facades.flash import Flash
from sbapp.plyer.facades.gps import GPS
from sbapp.plyer.facades.gravity import Gravity
from sbapp.plyer.facades.gyroscope import Gyroscope
from sbapp.plyer.facades.irblaster import IrBlaster
from sbapp.plyer.facades.light import Light
from sbapp.plyer.facades.proximity import Proximity
from sbapp.plyer.facades.orientation import Orientation
from sbapp.plyer.facades.notification import Notification
from sbapp.plyer.facades.sms import Sms
from sbapp.plyer.facades.stt import STT
from sbapp.plyer.facades.tts import TTS
from sbapp.plyer.facades.uniqueid import UniqueID
from sbapp.plyer.facades.vibrator import Vibrator
from sbapp.plyer.facades.wifi import Wifi
from sbapp.plyer.facades.temperature import Temperature
from sbapp.plyer.facades.humidity import Humidity
from sbapp.plyer.facades.spatialorientation import SpatialOrientation
from sbapp.plyer.facades.brightness import Brightness
from sbapp.plyer.facades.keystore import Keystore
from sbapp.plyer.facades.storagepath import StoragePath
from sbapp.plyer.facades.bluetooth import Bluetooth
from sbapp.plyer.facades.processors import Processors
from sbapp.plyer.facades.cpu import CPU
from sbapp.plyer.facades.screenshot import Screenshot
from sbapp.plyer.facades.devicename import DeviceName

View File

@ -94,6 +94,7 @@ class Audio:
# private
def _start(self):
raise IOError("JUICE")
raise NotImplementedError()
def _stop(self):

View File

@ -0,0 +1,88 @@
'''
Maps
=======
The :class:`Maps` creates a client for accessing the default Maps API.
Holds features such as opening a location by
address & latitude/longitude, create queries, or find directions between
two points
Simple Examples
---------------
Perform a search::
>>> from plyer import maps
>>> maps.search('Mexican Restaurant')
>>> maps.search('Taco Bell', latitude=38.5810606, longitude=-121.493895)
Get directions to a location::
>>> from plyer import maps
>>> maps.route('Cupertino', 'San Francisco')
>>> maps.route('41.9156316,-72.6130726', '42.65228271484,-73.7577362060')
View a specific location::
>>> from plyer import maps
>>> maps.open_by_address('25 Leshin Lane, Hightstown, NJ')
>>> maps.open_by_lat_long(30.451468, -91.187149)
>>> maps.open_by_lat_long(30.451468, -91.187149, name='Home')
Supported Platforms
-------------------
macOS, iOS
---------------
'''
class Maps:
'''
Maps facade.
'''
def open_by_address(self, address, **kwargs):
'''
Open the specificed location by address in the default Maps API
'''
self._open_by_address(address, **kwargs)
def open_by_lat_long(self, latitude, longitude, **kwargs):
'''
Open the specificed location by latitude & longitude coordinates
in the default Maps API
'''
self._open_by_lat_long(latitude, longitude, **kwargs)
def search(self, query, **kwargs):
'''
The query. This parameter is treated as if its value had been typed
into the Maps search field by the user.
Note that query=* is not supported
'''
self._search(query, **kwargs)
def route(self, saddr, daddr, **kwargs):
'''
To provide navigation directions from one location to another.
:param saddr: The source address to be used as the starting
point for directions.
:param daddr: The destination address to be used as the
destination point for directions.
'''
self._route(saddr, daddr, **kwargs)
def _open_by_address(self, address, **kwargs):
raise NotImplementedError()
def _open_by_lat_long(self, latitude, longitude, **kwargs):
raise NotImplementedError()
def _search(self, query, **kwargs):
raise NotImplementedError()
def _route(self, saddr, daddr, **kwargs):
raise NotImplementedError()

View File

@ -45,8 +45,8 @@ class Notification:
Notification facade.
'''
def notify(self, title='', message='', app_name='', app_icon='', notification_icon=None,
timeout=10, ticker='', toast=False, hints={}, context_override=None):
def notify(self, title='', message='', app_name='', app_icon='',
timeout=10, ticker='', toast=False, hints={}):
'''
Send a notification.
@ -83,8 +83,8 @@ class Notification:
self._notify(
title=title, message=message,
app_icon=app_icon, app_name=app_name, notification_icon=notification_icon,
timeout=timeout, ticker=ticker, toast=toast, hints=hints, context_override=context_override
app_icon=app_icon, app_name=app_name,
timeout=timeout, ticker=ticker, toast=toast, hints=hints
)
# private

View File

@ -30,7 +30,7 @@ To set sensor::
Supported Platforms
-------------------
Android
Android, Linux
'''

View File

@ -23,7 +23,7 @@ To send sms::
Supported Platforms
-------------------
Android, iOS
Android, iOS, macOS
'''
@ -33,17 +33,23 @@ class Sms:
Sms facade.
'''
def send(self, recipient, message):
def send(self, recipient, message, mode=None, **kwargs):
'''
Send SMS or open SMS interface.
Includes optional `mode` parameter for macOS that can be set to
`'SMS'` if carrier-activated device is correctly paired and
configured to macOS.
:param recipient: The receiver
:param message: the message
:param mode: (optional, macOS only), can be set to 'iMessage'
(default) or 'SMS'
:type recipient: number
:type message: str
:type mode: str
'''
self._send(recipient=recipient, message=message)
self._send(recipient=recipient, message=message, mode=mode, **kwargs)
# private

View File

@ -70,7 +70,7 @@ class AndroidAccelerometer(Accelerometer):
return (None, None, None)
def __del__(self):
if(self.bState):
if self.bState:
self._disable()
super().__del__()

View File

@ -1,3 +1,5 @@
import time
import threading
from jnius import autoclass
from plyer.facades.audio import Audio
@ -20,17 +22,45 @@ class AndroidAudio(Audio):
'''
def __init__(self, file_path=None):
default_path = '/sdcard/testrecorder.3gp'
default_path = None
super().__init__(file_path or default_path)
self._recorder = None
self._player = None
self._check_thread = None
self._finished_callback = None
self._format = "opus"
self.is_playing = False
def _check_playback(self):
while self._player and self._player.isPlaying():
time.sleep(0.25)
self.is_playing = False
if self._finished_callback and callable(self._finished_callback):
self._check_thread = None
self._finished_callback(self)
def _start(self):
self._recorder = MediaRecorder()
self._recorder.setAudioSource(AudioSource.DEFAULT)
self._recorder.setOutputFormat(OutputFormat.DEFAULT)
self._recorder.setAudioEncoder(AudioEncoder.DEFAULT)
if self._format == "aac":
self._recorder.setAudioSource(AudioSource.DEFAULT)
self._recorder.setAudioSamplingRate(48000)
self._recorder.setAudioEncodingBitRate(64000)
self._recorder.setAudioChannels(1)
self._recorder.setOutputFormat(OutputFormat.MPEG_4)
self._recorder.setAudioEncoder(AudioEncoder.AAC)
else:
self._recorder.setAudioSource(AudioSource.DEFAULT)
self._recorder.setAudioSamplingRate(48000)
self._recorder.setAudioEncodingBitRate(12000)
self._recorder.setAudioChannels(1)
self._recorder.setOutputFormat(OutputFormat.OGG)
self._recorder.setAudioEncoder(AudioEncoder.OPUS)
self._recorder.setOutputFile(self.file_path)
self._recorder.prepare()
@ -38,20 +68,40 @@ class AndroidAudio(Audio):
def _stop(self):
if self._recorder:
self._recorder.stop()
self._recorder.release()
try:
self._recorder.stop()
self._recorder.release()
except Exception as e:
print("Could not stop recording: "+str(e))
self._recorder = None
if self._player:
self._player.stop()
self._player.release()
try:
self._player.stop()
self._player.release()
except Exception as e:
print("Could not stop playback: "+str(e))
self._player = None
self.is_playing = False
def _play(self):
self._player = MediaPlayer()
self._player.setDataSource(self.file_path)
self._player.prepare()
self._player.start()
self.is_playing = True
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
self._check_thread.start()
def reload(self):
self._stop()
def playing(self):
return self.is_playing
def instance():

View File

@ -14,7 +14,7 @@ Uri = autoclass('android.net.Uri')
class AndroidCamera(Camera):
def _take_picture(self, on_complete, filename=None):
assert(on_complete is not None)
assert on_complete is not None
self.on_complete = on_complete
self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result)
@ -26,7 +26,7 @@ class AndroidCamera(Camera):
activity.startActivityForResult(intent, 0x123)
def _take_video(self, on_complete, filename=None):
assert(on_complete is not None)
assert on_complete is not None
self.on_complete = on_complete
self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result)

View File

@ -110,7 +110,7 @@ class AndroidCompass(Compass):
return (None, None, None, None, None, None)
def __del__(self):
if(self.bState):
if self.bState:
self._disable()
super().__del__()

View File

@ -43,7 +43,7 @@ using that result will use an incorrect one i.e. the default value of
.. versionadded:: 1.4.0
'''
from os.path import join, basename
from os.path import join
from random import randint
from android import activity, mActivity
@ -62,6 +62,8 @@ Long = autoclass('java.lang.Long')
IMedia = autoclass('android.provider.MediaStore$Images$Media')
VMedia = autoclass('android.provider.MediaStore$Video$Media')
AMedia = autoclass('android.provider.MediaStore$Audio$Media')
Files = autoclass('android.provider.MediaStore$Files')
FileOutputStream = autoclass('java.io.FileOutputStream')
class AndroidFileChooser(FileChooser):
@ -74,6 +76,7 @@ class AndroidFileChooser(FileChooser):
# filechooser activity <-> result pair identification
select_code = None
save_code = None
# default selection value
selection = None
@ -105,6 +108,7 @@ class AndroidFileChooser(FileChooser):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.select_code = randint(123456, 654321)
self.save_code = randint(123456, 654321)
self.selection = None
# bind a function for a response from filechooser activity
@ -139,9 +143,11 @@ class AndroidFileChooser(FileChooser):
# create Intent for opening
file_intent = Intent(Intent.ACTION_GET_CONTENT)
if not self.selected_mime_type or \
type(self.selected_mime_type) != str or \
self.selected_mime_type not in self.mime_type:
if (
not self.selected_mime_type
or not isinstance(self.selected_mime_type, str)
or self.selected_mime_type not in self.mime_type
):
file_intent.setType("*/*")
else:
file_intent.setType(self.mime_type[self.selected_mime_type])
@ -163,6 +169,38 @@ class AndroidFileChooser(FileChooser):
self.select_code
)
def _save_file(self, **kwargs):
self._save_callback = kwargs.pop("callback")
title = kwargs.pop("title", None)
self.selected_mime_type = \
kwargs.pop("filters")[0] if "filters" in kwargs else ""
file_intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
if (
not self.selected_mime_type
or not isinstance(self.selected_mime_type, str)
or self.selected_mime_type not in self.mime_type
):
file_intent.setType("*/*")
else:
file_intent.setType(self.mime_type[self.selected_mime_type])
file_intent.addCategory(
Intent.CATEGORY_OPENABLE
)
if title:
file_intent.putExtra(Intent.EXTRA_TITLE, title)
mActivity.startActivityForResult(
Intent.createChooser(file_intent, cast(
'java.lang.CharSequence',
String("FileChooser")
)),
self.save_code
)
def _on_activity_result(self, request_code, result_code, data):
'''
Listener for ``android.app.Activity.onActivityResult()`` assigned
@ -171,28 +209,41 @@ class AndroidFileChooser(FileChooser):
.. versionadded:: 1.4.0
'''
# not our response
if request_code != self.select_code:
# bad data
if data is None:
return
if result_code != Activity.RESULT_OK:
# The action had been cancelled.
return
selection = []
# Process multiple URI if multiple files selected
try:
for count in range(data.getClipData().getItemCount()):
ele = self._resolve_uri(
data.getClipData().getItemAt(count).getUri()) or []
selection.append(ele)
except Exception:
selection = [self._resolve_uri(data.getData()), ]
if request_code == self.select_code:
selection = []
# Process multiple URI if multiple files selected
try:
for count in range(data.getClipData().getItemCount()):
ele = self._resolve_uri(
data.getClipData().getItemAt(count).getUri()) or []
selection.append(ele)
except Exception:
selection = [self._resolve_uri(data.getData()), ]
# return value to object
self.selection = selection
# return value via callback
self._handle_selection(selection)
# return value to object
self.selection = selection
# return value via callback
self._handle_selection(selection)
elif request_code == self.save_code:
uri = data.getData()
with mActivity.getContentResolver().openFileDescriptor(
uri, "w"
) as pfd:
with FileOutputStream(
pfd.getFileDescriptor()
) as fileOutputStream:
# return value via callback
self._save_callback(fileOutputStream)
@staticmethod
def _handle_external_documents(uri):
@ -206,28 +257,19 @@ class AndroidFileChooser(FileChooser):
file_id = DocumentsContract.getDocumentId(uri)
file_type, file_name = file_id.split(':')
# internal SD card mostly mounted as a files storage in phone
internal = storagepath.get_external_storage_dir()
primary_storage = storagepath.get_external_storage_dir()
sdcard_storage = storagepath.get_sdcard_dir()
# external (removable) SD card i.e. microSD
external = storagepath.get_sdcard_dir()
try:
external_base = basename(external)
except TypeError:
external_base = basename(internal)
directory = primary_storage
# resolve sdcard path
sd_card = internal
# because external might have /storage/.../1 or other suffix
# and file_type might be only a part of the real folder in /storage
if file_type in external_base or external_base in file_type:
sd_card = external
if file_type == "primary":
directory = primary_storage
elif file_type == "home":
sd_card = join(Environment.getExternalStorageDirectory(
).getAbsolutePath(), Environment.DIRECTORY_DOCUMENTS)
directory = join(primary_storage, Environment.DIRECTORY_DOCUMENTS)
elif sdcard_storage and file_type in sdcard_storage:
directory = sdcard_storage
return join(sd_card, file_name)
return join(directory, file_name)
@staticmethod
def _handle_media_documents(uri):
@ -248,6 +290,11 @@ class AndroidFileChooser(FileChooser):
uri = VMedia.EXTERNAL_CONTENT_URI
elif file_type == 'audio':
uri = AMedia.EXTERNAL_CONTENT_URI
# Other file type was selected (probably in the Documents folder)
else:
uri = Files.getContentUri("external")
return file_name, selection, uri
@staticmethod
@ -279,6 +326,23 @@ class AndroidFileChooser(FileChooser):
.. versionadded:: 1.4.0
'''
try:
download_dir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
).getPath()
path = AndroidFileChooser._parse_content(
uri=uri,
projection=["_display_name"],
selection=None,
selection_args=None,
sort_order=None,
)
return join(download_dir, path)
except Exception:
import traceback
traceback.print_exc()
# known locations, differ between machines
downloads = [
'content://downloads/public_downloads',
@ -441,6 +505,8 @@ class AndroidFileChooser(FileChooser):
mode = kwargs.pop('mode', None)
if mode == 'open':
self._open_file(**kwargs)
elif mode == 'save':
self._save_file(**kwargs)
def instance():

View File

@ -110,7 +110,7 @@ class AndroidGyroscope(Gyroscope):
return (None, None, None, None, None, None)
def __del__(self):
if(self.bState):
if self.bState:
self._disable()
super().__del__()

View File

@ -154,10 +154,15 @@ class AndroidNotification(Notification):
notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
notification_intent.setAction(Intent.ACTION_MAIN)
notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
if SDK_INT >= 23:
# FLAG_IMMUTABLE added in SDK 23, required since SDK 31:
pending_flags = PendingIntent.FLAG_IMMUTABLE
else:
pending_flags = 0
# get our application Activity
pending_intent = PendingIntent.getActivity(
app_context, 0, notification_intent, 0
app_context, 0, notification_intent, pending_flags
)
notification.setContentIntent(pending_intent)
@ -179,8 +184,6 @@ class AndroidNotification(Notification):
kwargs.get('title', '').encode('utf-8')
)
icon = kwargs.get('app_icon')
notification_icon = kwargs.get('notification_icon')
context_override = kwargs.get('context_override')
# decide whether toast only or proper notification
if kwargs.get('toast'):

View File

@ -3,14 +3,13 @@ Android Storage Path
--------------------
'''
from os import listdir, access, R_OK
from os.path import join
from plyer.facades import StoragePath
from jnius import autoclass
from plyer.platforms.android import SDK_INT
from jnius import autoclass, cast
from android import mActivity
Environment = autoclass('android.os.Environment')
Context = autoclass('android.content.Context')
Environment = autoclass("android.os.Environment")
Context = autoclass("android.content.Context")
class AndroidStoragePath(StoragePath):
@ -25,17 +24,29 @@ class AndroidStoragePath(StoragePath):
'''
.. versionadded:: 1.4.0
'''
# folder in /storage/ that is readable
# and is not internal SD card
path = None
for folder in listdir('/storage'):
folder = join('/storage', folder)
if folder in self._get_external_storage_dir():
continue
if not access(folder, R_OK):
continue
path = folder
break
context = mActivity.getApplicationContext()
storage_manager = cast(
"android.os.storage.StorageManager",
context.getSystemService(Context.STORAGE_SERVICE),
)
if storage_manager is not None:
if SDK_INT >= 24:
storage_volumes = storage_manager.getStorageVolumes()
for storage_volume in storage_volumes:
if storage_volume.isRemovable():
try:
directory = storage_volume.getDirectory()
except AttributeError:
directory = storage_volume.getPathFile()
path = directory.getAbsolutePath()
else:
storage_volumes = storage_manager.getVolumeList()
for storage_volume in storage_volumes:
if storage_volume.isRemovable():
path = storage_volume.getPath()
return path
def _get_root_dir(self):

View File

@ -6,7 +6,7 @@ Taken from: http://pyobjus.readthedocs.org/en/latest/pyobjus_ios.html \
#accessing-accelerometer
'''
from plyer.facades import Accelerometer
from sbapp.plyer.facades import Accelerometer
from pyobjus import autoclass

View File

@ -3,7 +3,7 @@ iOS Barometer
-------------
'''
from plyer.facades import Barometer
from sbapp.plyer.facades import Barometer
from pyobjus import autoclass

View File

@ -4,7 +4,7 @@ Module of iOS API for plyer.battery.
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework
from plyer.facades import Battery
from sbapp.plyer.facades import Battery
load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice')

View File

@ -4,7 +4,7 @@ iOS Brightness
'''
from pyobjus import autoclass
from plyer.facades import Brightness
from sbapp.plyer.facades import Brightness
from pyobjus.dylib_manager import load_framework
load_framework('/System/Library/Frameworks/UIKit.framework')

View File

@ -3,7 +3,7 @@ IOS Call
----------
'''
from plyer.facades import Call
from sbapp.plyer.facades import Call
from pyobjus import autoclass, objc_str
NSURL = autoclass('NSURL')

View File

@ -1,7 +1,7 @@
from os import remove
from plyer.facades import Camera
from sbapp.plyer.facades import Camera
from plyer.utils import reify
from sbapp.plyer.utils import reify
class iOSCamera(Camera):
@ -14,7 +14,7 @@ class iOSCamera(Camera):
return PhotosLibrary()
def _take_picture(self, on_complete, filename=None):
assert(on_complete is not None)
assert on_complete is not None
self.on_complete = on_complete
self.filename = filename
photos = self.photos
@ -38,7 +38,7 @@ class iOSCamera(Camera):
self._remove(self.filename)
def _take_video(self, on_complete, filename=None):
assert(on_complete is not None)
assert on_complete is not None
raise NotImplementedError
def _remove(self, fn):

View File

@ -3,7 +3,7 @@ iOS Compass
-----------
'''
from plyer.facades import Compass
from sbapp.plyer.facades import Compass
from pyobjus import autoclass

View File

@ -7,7 +7,7 @@ try:
except ImportError:
from urllib import quote
from plyer.facades import Email
from sbapp.plyer.facades import Email
from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework

View File

@ -7,7 +7,7 @@ This module houses the iOS implementation of the plyer FileChooser.
.. versionadded:: 1.4.4
'''
from plyer.facades import FileChooser
from sbapp.plyer.facades import FileChooser
from pyobjus import autoclass, protocol
from pyobjus.dylib_manager import load_framework

View File

@ -3,7 +3,7 @@
Flash
-----
"""
from plyer.facades import Flash
from sbapp.plyer.facades import Flash
from pyobjus import autoclass
NSString = autoclass("NSString")

View File

@ -5,7 +5,7 @@ iOS GPS
from pyobjus import autoclass, protocol
from pyobjus.dylib_manager import load_framework
from plyer.facades import GPS
from sbapp.plyer.facades import GPS
load_framework('/System/Library/Frameworks/CoreLocation.framework')
CLLocationManager = autoclass('CLLocationManager')

View File

@ -4,7 +4,7 @@ iOS Gravity
'''
from plyer.facades import Gravity
from sbapp.plyer.facades import Gravity
from pyobjus import autoclass

View File

@ -3,7 +3,7 @@ iOS Gyroscope
---------------------
'''
from plyer.facades import Gyroscope
from sbapp.plyer.facades import Gyroscope
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework

View File

@ -1,4 +1,4 @@
from plyer.facades import Keystore
from sbapp.plyer.facades import Keystore
from pyobjus import autoclass, objc_str
NSUserDefaults = autoclass('NSUserDefaults')

View File

@ -0,0 +1,78 @@
'''
Module of iOS API for plyer.maps.
'''
import webbrowser
from sbapp.plyer.facades import Maps
from urllib.parse import quote_plus
class iOSMaps(Maps):
'''
Implementation of iOS Maps API.
'''
def _open_by_address(self, address, **kwargs):
'''
:param address: An address string that geolocation can understand.
'''
address = quote_plus(address, safe=',')
maps_address = 'http://maps.apple.com/?address=' + address
webbrowser.open(maps_address)
def _open_by_lat_long(self, latitude, longitude, **kwargs):
'''
Open a coordinate span denoting a latitudinal delta and a
longitudinal delta (similar to MKCoordinateSpan)
:param name: (optional), will set the name of the dropped pin
'''
name = kwargs.get("name", "Selected Location")
maps_address = 'http://maps.apple.com/?ll={},{}&q={}'.format(
latitude, longitude, name)
webbrowser.open(maps_address)
def _search(self, query, **kwargs):
'''
:param query: A string that describes the search object (ex. "Pizza")
:param latitude: (optional), narrow down query within area,
MUST BE USED WITH LONGITUDE
:param longitude: (optional), narrow down query within area,
MUST BE USED WITH LATITUDE
'''
latitude = kwargs.get('latitude')
longitude = kwargs.get('longitude')
query = quote_plus(query, safe=',')
maps_address = 'http://maps.apple.com/?q=' + query
if latitude is not None and longitude is not None:
maps_address += '&sll={},{}'.format(latitude, longitude)
webbrowser.open(maps_address)
def _route(self, saddr, daddr, **kwargs):
'''
:param saddr: can be given as 'address' or 'lat,long'
:param daddr: can be given as 'address' or 'lat,long'
'''
saddr = quote_plus(saddr, safe=',')
daddr = quote_plus(daddr, safe=',')
maps_address = 'http://maps.apple.com/?saddr={}&daddr={}'.format(
saddr, daddr)
webbrowser.open(maps_address)
def instance():
'''
Instance for facade proxy.
'''
return iOSMaps()

View File

@ -3,7 +3,7 @@ IOS Sms
----------
'''
from plyer.facades import Sms
from sbapp.plyer.facades import Sms
from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework

View File

@ -4,7 +4,7 @@ iOS Spatial Orientation
'''
from plyer.facades import SpatialOrientation
from sbapp.plyer.facades import SpatialOrientation
from pyobjus import autoclass

View File

@ -3,7 +3,7 @@ iOS Storage Path
--------------------
'''
from plyer.facades import StoragePath
from sbapp.plyer.facades import StoragePath
from pyobjus import autoclass
import os

View File

@ -1,7 +1,7 @@
from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework
from plyer.facades import TTS
from sbapp.plyer.facades import TTS
load_framework('/System/Library/Frameworks/AVFoundation.framework')
AVSpeechUtterance = autoclass('AVSpeechUtterance')
@ -23,7 +23,7 @@ class iOSTextToSpeech(TTS):
def _speak(self, **kwargs):
message = kwargs.get('message')
if(not self.voice):
if not self.voice:
self._set_locale()
utterance = \

View File

@ -4,7 +4,7 @@ Module of iOS API for plyer.uniqueid.
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework
from plyer.facades import UniqueID
from sbapp.plyer.facades import UniqueID
load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice')

View File

@ -4,7 +4,7 @@ Install: Add AudioToolbox framework to your application.
'''
import ctypes
from plyer.facades import Vibrator
from sbapp.plyer.facades import Vibrator
class IosVibrator(Vibrator):

View File

@ -3,7 +3,7 @@ Linux accelerometer
---------------------
'''
from plyer.facades import Accelerometer
from sbapp.plyer.facades import Accelerometer
import glob
import re

View File

@ -0,0 +1,139 @@
import time
import threading
import RNS
import io
from sbapp.plyer.facades.audio import Audio
from ffpyplayer.player import MediaPlayer
from sbapp.pyogg import OpusFile, OpusBufferedEncoder, OggOpusWriter
import pyaudio
class LinuxAudio(Audio):
def __init__(self, file_path=None):
default_path = None
super().__init__(file_path or default_path)
self._recorder = None
self._player = None
self._check_thread = None
self._finished_callback = None
self._loaded_path = None
self.sound = None
self.pa = None
self.is_playing = False
self.recorder = None
self.should_record = False
def _check_playback(self):
run = True
while run and self.sound != None and not self.sound.get_pause():
time.sleep(0.25)
if self.duration:
pts = self.sound.get_pts()
if pts > self.duration:
run = False
self.is_playing = False
if self._finished_callback and callable(self._finished_callback):
self._check_thread = None
self._finished_callback(self)
def _record_job(self):
samples_per_second = self.default_rate;
bytes_per_sample = 2; frame_duration_ms = 20
opus_buffered_encoder = OpusBufferedEncoder()
opus_buffered_encoder.set_application("voip")
opus_buffered_encoder.set_sampling_frequency(samples_per_second)
opus_buffered_encoder.set_channels(1)
opus_buffered_encoder.set_frame_size(frame_duration_ms)
ogg_opus_writer = OggOpusWriter(self._file_path, opus_buffered_encoder)
frame_duration = frame_duration_ms/1000
frame_size = int(frame_duration * samples_per_second)
bytes_per_frame = frame_size*bytes_per_sample
read_bytes = 0
pcm_buf = b""
should_continue = True
while self.should_record and self.recorder:
samples_available = self.recorder.get_read_available()
bytes_available = samples_available*bytes_per_sample
if bytes_available > 0:
read_req = bytes_per_frame - len(pcm_buf)
read_n = min(bytes_available, read_req)
read_s = read_n//bytes_per_sample
rb = self.recorder.read(read_s); read_bytes += len(rb)
pcm_buf += rb
if len(pcm_buf) == bytes_per_frame:
ogg_opus_writer.write(memoryview(bytearray(pcm_buf)))
# RNS.log("Wrote frame of "+str(len(pcm_buf))+", expected size "+str(bytes_per_frame))
pcm_buf = b""
# Finish up anything left in buffer
time.sleep(frame_duration)
samples_available = self.recorder.get_read_available()
bytes_available = samples_available*bytes_per_sample
if bytes_available > 0:
read_req = bytes_per_frame - len(pcm_buf)
read_n = min(bytes_available, read_req)
read_s = read_n//bytes_per_sample
rb = self.recorder.read(read_s); read_bytes += len(rb)
pcm_buf += rb
if len(pcm_buf) == bytes_per_frame:
ogg_opus_writer.write(memoryview(bytearray(pcm_buf)))
# RNS.log("Wrote frame of "+str(len(pcm_buf))+", expected size "+str(bytes_per_frame))
pcm_buf = b""
ogg_opus_writer.close()
if self.recorder:
self.recorder.close()
def _start(self):
self.should_record = True
if self.pa == None:
self.pa = pyaudio.PyAudio()
self.default_input_device = self.pa.get_default_input_device_info()
self.default_rate = 48000
# self.default_rate = int(self.default_input_device["defaultSampleRate"])
if self.recorder:
self.recorder.close()
self.recorder = None
self.recorder = self.pa.open(self.default_rate, 1, pyaudio.paInt16, input=True)
threading.Thread(target=self._record_job, daemon=True).start()
def _stop(self):
if self.should_record == True:
self.should_record = False
elif self.sound != None:
self.sound.set_pause(True)
self.sound.seek(0, relative=False)
self.is_playing = False
def _play(self):
self.sound = MediaPlayer(self._file_path)
self.metadata = self.sound.get_metadata()
self.duration = self.metadata["duration"]
if self.duration == None:
time.sleep(0.15)
self.metadata = self.sound.get_metadata()
self.duration = self.metadata["duration"]
self._loaded_path = self._file_path
self.is_playing = True
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
self._check_thread.start()
def reload(self):
self._loaded_path = None
def playing(self):
return self.is_playing
def instance():
return LinuxAudio()

View File

@ -2,13 +2,12 @@
Module of Linux API for plyer.battery.
'''
import os
from math import floor
from os import environ
from os.path import exists, join
from subprocess import Popen, PIPE
from plyer.facades import Battery
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Battery
from sbapp.plyer.utils import whereis_exe
class LinuxBattery(Battery):
@ -20,10 +19,10 @@ class LinuxBattery(Battery):
def _get_state(self):
status = {"isCharging": None, "percentage": None}
kernel_bat_path = join('/sys', 'class', 'power_supply', self.node_name)
kernel_bat_path = join('/sys', 'class', 'power_supply', 'BAT0')
uevent = join(kernel_bat_path, 'uevent')
with open(uevent, "rb") as fle:
with open(uevent) as fle:
lines = [
line.decode('utf-8').strip()
for line in fle.readlines()
@ -34,34 +33,70 @@ class LinuxBattery(Battery):
}
is_charging = output['POWER_SUPPLY_STATUS'] == 'Charging'
charge_percent = float(output['POWER_SUPPLY_CAPACITY'])
total = float(output['POWER_SUPPLY_CHARGE_FULL'])
now = float(output['POWER_SUPPLY_CHARGE_NOW'])
status['percentage'] = charge_percent
capacity = floor(now / total * 100)
status['percentage'] = capacity
status['isCharging'] = is_charging
return status
class UPowerBattery(Battery):
'''
Implementation of UPower battery API.
'''
def _get_state(self):
# if no LANG specified, return empty string
old_lang = environ.get('LANG', '')
environ['LANG'] = 'C'
status = {"isCharging": None, "percentage": None}
# We are supporting only one battery now
# this will fail if there is no object with such path,
# however it's safer than 'upower -d' which provides
# multiple unrelated 'state' and 'percentage' keywords
dev = "/org/freedesktop/UPower/devices/battery_BAT0"
upower_process = Popen(
["upower", "--show-info", dev],
stdout=PIPE
)
output = upower_process.communicate()[0].decode()
environ['LANG'] = old_lang
if not output:
return status
state = percentage = None
for line in output.splitlines():
if 'state' in line:
state = line.rpartition(':')[-1].strip()
if 'percentage' in line:
percentage = line.rpartition(':')[-1].strip()[:-1]
# switching decimal comma to dot
# (different LC_NUMERIC locale)
percentage = float(
percentage.replace(',', '.')
)
if state:
status['isCharging'] = state == "charging"
status['percentage'] = percentage
return status
def instance():
'''
Instance for facade proxy.
'''
import sys
# if whereis_exe('upower'):
# return UPowerBattery()
# sys.stderr.write("upower not found.")
node_exists = False
bn = 0
node_name = None
for bi in range(0,10):
path = join('/sys', 'class', 'power_supply', 'BAT'+str(bi))
if os.path.isdir(path):
node_name = "BAT"+str(bi)
break
if node_name:
b = LinuxBattery()
b.node_name = node_name
return b
if whereis_exe('upower'):
return UPowerBattery()
sys.stderr.write("upower not found.")
if exists(join('/sys', 'class', 'power_supply', 'BAT0')):
return LinuxBattery()
return Battery()

View File

@ -4,7 +4,7 @@ Linux Brightness
'''
from plyer.facades import Brightness
from sbapp.plyer.facades import Brightness
import subprocess
import os

View File

@ -5,8 +5,8 @@ Module of Linux API for plyer.cpu.
from os.path import join
from os import environ, listdir
from subprocess import Popen, PIPE
from plyer.facades import CPU
from plyer.utils import whereis_exe
from sbapp.plyer.facades import CPU
from sbapp.plyer.utils import whereis_exe
class LinuxCPU(CPU):

View File

@ -3,7 +3,7 @@ Module of Linux API for plyer.devicename.
'''
import socket
from plyer.facades import DeviceName
from sbapp.plyer.facades import DeviceName
class LinuxDeviceName(DeviceName):

View File

@ -7,8 +7,8 @@ try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from plyer.facades import Email
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Email
from sbapp.plyer.utils import whereis_exe
class LinuxEmail(Email):

View File

@ -3,7 +3,7 @@ Linux file chooser
------------------
'''
from plyer.facades import FileChooser
from sbapp.plyer.facades import FileChooser
from distutils.spawn import find_executable as which
import os
import subprocess as sp
@ -122,7 +122,7 @@ class ZenityFileChooser(SubprocessFileChooser):
if self.icon:
cmdline += ["--window-icon", self.icon]
for f in self.filters:
if type(f) == str:
if isinstance(f, str):
cmdline += ["--file-filter", f]
else:
cmdline += [
@ -150,7 +150,7 @@ class KDialogFileChooser(SubprocessFileChooser):
filt = []
for f in self.filters:
if type(f) == str:
if isinstance(f, str):
filt += [f]
else:
filt += list(f[1:])
@ -195,7 +195,7 @@ class YADFileChooser(SubprocessFileChooser):
def _gen_cmdline(self):
cmdline = [
which(self.executable),
"--file-selection",
"--file",
"--confirm-overwrite",
"--geometry",
"800x600+150+150"
@ -215,7 +215,7 @@ class YADFileChooser(SubprocessFileChooser):
if self.icon:
cmdline += ["--window-icon", self.icon]
for f in self.filters:
if type(f) == str:
if isinstance(f, str):
cmdline += ["--file-filter", f]
else:
cmdline += [

View File

@ -3,7 +3,7 @@ try:
except ImportError:
raise NotImplementedError()
from plyer.facades import Keystore
from sbapp.plyer.facades import Keystore
class LinuxKeystore(Keystore):

View File

@ -4,8 +4,8 @@ Module of Linux API for plyer.notification.
import warnings
import subprocess
from plyer.facades import Notification
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Notification
from sbapp.plyer.utils import whereis_exe
import os
@ -63,7 +63,7 @@ class NotifyDbus(Notification):
def _notify(self, **kwargs):
summary = kwargs.get('title', "title")
body = kwargs.get('message', "body")
app_name = kwargs.get('app_name', '')
app_name = "Sideband"
app_icon = kwargs.get('app_icon', '')
timeout = kwargs.get('timeout', 10)
actions = kwargs.get('actions', [])

View File

@ -1,5 +1,5 @@
import subprocess as sb
from plyer.facades import Orientation
from sbapp.plyer.facades import Orientation
class LinuxOrientation(Orientation):

View File

@ -1,6 +1,6 @@
from subprocess import Popen, PIPE
from plyer.facades import Processors
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Processors
from sbapp.plyer.utils import whereis_exe
from os import environ

View File

@ -1,8 +1,8 @@
import subprocess
from os.path import join
from plyer.facades import Screenshot
from plyer.utils import whereis_exe
from plyer.platforms.linux.storagepath import LinuxStoragePath
from sbapp.plyer.facades import Screenshot
from sbapp.plyer.utils import whereis_exe
from sbapp.plyer.platforms.linux.storagepath import LinuxStoragePath
class LinuxScreenshot(Screenshot):

View File

@ -3,7 +3,7 @@ Linux Storage Path
--------------------
'''
from plyer.facades import StoragePath
from sbapp.plyer.facades import StoragePath
from os.path import expanduser, dirname, abspath, join, exists
# Default paths for each name

View File

@ -1,6 +1,6 @@
import subprocess
from plyer.facades import TTS
from plyer.utils import whereis_exe
from sbapp.plyer.facades import TTS
from sbapp.plyer.utils import whereis_exe
class EspeakTextToSpeech(TTS):

View File

@ -4,8 +4,8 @@ Module of Linux API for plyer.uniqueid.
from os import environ
from subprocess import Popen, PIPE
from plyer.facades import UniqueID
from plyer.utils import whereis_exe
from sbapp.plyer.facades import UniqueID
from sbapp.plyer.utils import whereis_exe
class LinuxUniqueID(UniqueID):

View File

@ -6,8 +6,8 @@
'''
from subprocess import Popen, PIPE, call
from plyer.facades import Wifi
from plyer.utils import whereis_exe, deprecated
from sbapp.plyer.facades import Wifi
from sbapp.plyer.utils import whereis_exe, deprecated
try:
import wifi

View File

@ -3,8 +3,8 @@ MacOSX accelerometer
---------------------
'''
from plyer.facades import Accelerometer
from plyer.platforms.macosx.libs import osx_motion_sensor
from sbapp.plyer.facades import Accelerometer
from sbapp.plyer.platforms.macosx.libs import osx_motion_sensor
class OSXAccelerometer(Accelerometer):

View File

@ -3,8 +3,10 @@ from os.path import join
from pyobjus import autoclass
from pyobjus.dylib_manager import INCLUDE, load_framework
from plyer.facades import Audio
from plyer.platforms.macosx.storagepath import OSXStoragePath
from sbapp.plyer.facades import Audio
from sbapp.plyer.platforms.macosx.storagepath import OSXStoragePath
import threading
load_framework(INCLUDE.Foundation)
load_framework(INCLUDE.AVFoundation)
@ -19,16 +21,31 @@ NSError = autoclass('NSError').alloc()
class OSXAudio(Audio):
def __init__(self, file_path=None):
default_path = join(
OSXStoragePath().get_music_dir(),
'audio.wav'
)
default_path = None
super().__init__(file_path or default_path)
self._recorder = None
self._player = None
self._current_file = None
self._check_thread = None
self._finished_callback = None
self._loaded_path = None
self.is_playing = False
self.sound = None
self.pa = None
self.is_playing = False
self.recorder = None
self.should_record = False
def _check_playback(self):
while self._player and self._player.isPlaying:
time.sleep(0.25)
if self._finished_callback and callable(self._finished_callback):
self._check_thread = None
self._finished_callback(self)
def _start(self):
# Conversion of Python file path string to Objective-C NSString
file_path_NSString = NSString.alloc()
@ -44,7 +61,7 @@ class OSXAudio(Audio):
# Internal audio file format specification
af = AVAudioFormat.alloc()
af = af.initWithCommonFormat_sampleRate_channels_interleaved_(
1, 44100.0, 2, True
1, 44100.0, 1, True
)
# Audio recorder instance initialization with specified file NSURL
@ -73,6 +90,18 @@ class OSXAudio(Audio):
self._player = None
def _play(self):
# Conversion of Python file path string to Objective-C NSString
file_path_NSString = NSString.alloc()
file_path_NSString = file_path_NSString.initWithUTF8String_(
self._file_path
)
# Definition of Objective-C NSURL object for the output record file
# specified by NSString file path
file_NSURL = NSURL.alloc()
file_NSURL = file_NSURL.initWithString_(file_path_NSString)
self._current_file = file_NSURL
# Audio player instance initialization with the file NSURL
# of the last recorded audio file
self._player = AVAudioPlayer.alloc()
@ -85,6 +114,15 @@ class OSXAudio(Audio):
self._player.play()
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
self._check_thread.start()
def reload(self):
self._loaded_path = None
def playing(self):
return self.is_playing
def instance():
return OSXAudio()

View File

@ -4,8 +4,8 @@ Module of MacOS API for plyer.battery.
from os import environ
from subprocess import Popen, PIPE
from plyer.facades import Battery
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Battery
from sbapp.plyer.utils import whereis_exe
class OSXBattery(Battery):

View File

@ -3,8 +3,8 @@ Module of MacOS API for plyer.bluetooth.
'''
from subprocess import Popen, PIPE
from plyer.facades import Bluetooth
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Bluetooth
from sbapp.plyer.utils import whereis_exe
from os import environ

View File

@ -3,8 +3,8 @@ Module of MacOS API for plyer.cpu.
'''
from subprocess import Popen, PIPE
from plyer.facades import CPU
from plyer.utils import whereis_exe
from sbapp.plyer.facades import CPU
from sbapp.plyer.utils import whereis_exe
class OSXCPU(CPU):

View File

@ -3,7 +3,7 @@ Module of MacOSX API for plyer.devicename.
'''
import socket
from plyer.facades import DeviceName
from sbapp.plyer.facades import DeviceName
class OSXDeviceName(DeviceName):

View File

@ -9,8 +9,8 @@ try:
except ImportError:
from urllib import quote
from plyer.facades import Email
from plyer.utils import whereis_exe
from sbapp.plyer.facades import Email
from sbapp.plyer.utils import whereis_exe
class MacOSXEmail(Email):

Some files were not shown because too many files have changed in this diff Show More