mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-11-21 13:00:37 +01:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
39f00280a5
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@ -0,0 +1,6 @@
|
||||
prune sbapp/patches
|
||||
prune sbapp/dist
|
||||
prune recipes
|
||||
prune docs
|
||||
prune libs
|
||||
prune .github
|
23
README.md
23
README.md
@ -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.
|
||||
|
@ -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
BIN
libs/arm64/libcodec2.so
Executable file
Binary file not shown.
BIN
libs/armeabi/libcodec2.so
Executable file
BIN
libs/armeabi/libcodec2.so
Executable file
Binary file not shown.
50
recipes/codec2/__init__.py
Normal file
50
recipes/codec2/__init__.py
Normal 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
BIN
recipes/codec2/generate_codebook
Executable file
Binary file not shown.
1516
recipes/ffpyplayer/__init__.py
Normal file
1516
recipes/ffpyplayer/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
15
recipes/ffpyplayer/setup.py.patch
Normal file
15
recipes/ffpyplayer/setup.py.patch
Normal 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}')
|
22
recipes/libopus/__init__.py
Normal file
22
recipes/libopus/__init__.py
Normal 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
152
recipes/mffmpeg/__init__.py
Normal 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()
|
11
recipes/mffmpeg/patches/configure.patch
Normal file
11
recipes/mffmpeg/patches/configure.patch
Normal 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
75
recipes/numpy/__init__.py
Normal 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()
|
20
recipes/numpy/patches/add_libm_explicitly_to_build.patch
Normal file
20
recipes/numpy/patches/add_libm_explicitly_to_build.patch
Normal 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
|
11
recipes/numpy/patches/ranlib.patch
Normal file
11
recipes/numpy/patches/ranlib.patch
Normal 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)
|
28
recipes/numpy/patches/remove-default-paths.patch
Normal file
28
recipes/numpy/patches/remove-default-paths.patch
Normal 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)
|
46
recipes/opusfile/__init__.py
Normal file
46
recipes/opusfile/__init__.py
Normal 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()
|
57
recipes/pycodec2/__init__.py
Normal file
57
recipes/pycodec2/__init__.py
Normal 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
|
@ -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/
|
||||
|
BIN
sbapp/assets/fonts/DefaultInput.ttf
Normal file
BIN
sbapp/assets/fonts/DefaultInput.ttf
Normal file
Binary file not shown.
BIN
sbapp/assets/fonts/EmojiScaled.ttf
Normal file
BIN
sbapp/assets/fonts/EmojiScaled.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
@ -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
95
sbapp/freeze.py
Normal 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},
|
||||
)
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
861
sbapp/main.py
861
sbapp/main.py
File diff suppressed because it is too large
Load Diff
@ -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 -%}
|
||||
|
@ -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(
|
||||
|
@ -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>
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -94,6 +94,7 @@ class Audio:
|
||||
# private
|
||||
|
||||
def _start(self):
|
||||
raise IOError("JUICE")
|
||||
raise NotImplementedError()
|
||||
|
||||
def _stop(self):
|
||||
|
88
sbapp/plyer/facades/maps.py
Normal file
88
sbapp/plyer/facades/maps.py
Normal 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()
|
@ -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
|
||||
|
@ -30,7 +30,7 @@ To set sensor::
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
Android
|
||||
Android, Linux
|
||||
|
||||
'''
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -70,7 +70,7 @@ class AndroidAccelerometer(Accelerometer):
|
||||
return (None, None, None)
|
||||
|
||||
def __del__(self):
|
||||
if(self.bState):
|
||||
if self.bState:
|
||||
self._disable()
|
||||
super().__del__()
|
||||
|
||||
|
@ -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()
|
||||
if self._format == "aac":
|
||||
self._recorder.setAudioSource(AudioSource.DEFAULT)
|
||||
self._recorder.setOutputFormat(OutputFormat.DEFAULT)
|
||||
self._recorder.setAudioEncoder(AudioEncoder.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:
|
||||
try:
|
||||
self._recorder.stop()
|
||||
self._recorder.release()
|
||||
except Exception as e:
|
||||
print("Could not stop recording: "+str(e))
|
||||
|
||||
self._recorder = None
|
||||
|
||||
if self._player:
|
||||
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():
|
||||
|
@ -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)
|
||||
|
@ -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__()
|
||||
|
||||
|
@ -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,14 +209,15 @@ 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
|
||||
|
||||
if request_code == self.select_code:
|
||||
selection = []
|
||||
# Process multiple URI if multiple files selected
|
||||
try:
|
||||
@ -194,6 +233,18 @@ class AndroidFileChooser(FileChooser):
|
||||
# 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():
|
||||
|
@ -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__()
|
||||
|
||||
|
@ -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'):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ iOS Barometer
|
||||
-------------
|
||||
'''
|
||||
|
||||
from plyer.facades import Barometer
|
||||
from sbapp.plyer.facades import Barometer
|
||||
from pyobjus import autoclass
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -3,7 +3,7 @@ iOS Compass
|
||||
-----------
|
||||
'''
|
||||
|
||||
from plyer.facades import Compass
|
||||
from sbapp.plyer.facades import Compass
|
||||
from pyobjus import autoclass
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
Flash
|
||||
-----
|
||||
"""
|
||||
from plyer.facades import Flash
|
||||
from sbapp.plyer.facades import Flash
|
||||
from pyobjus import autoclass
|
||||
|
||||
NSString = autoclass("NSString")
|
||||
|
@ -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')
|
||||
|
@ -4,7 +4,7 @@ iOS Gravity
|
||||
|
||||
'''
|
||||
|
||||
from plyer.facades import Gravity
|
||||
from sbapp.plyer.facades import Gravity
|
||||
from pyobjus import autoclass
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
from plyer.facades import Keystore
|
||||
from sbapp.plyer.facades import Keystore
|
||||
from pyobjus import autoclass, objc_str
|
||||
|
||||
NSUserDefaults = autoclass('NSUserDefaults')
|
||||
|
78
sbapp/plyer/platforms/ios/maps.py
Normal file
78
sbapp/plyer/platforms/ios/maps.py
Normal 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()
|
@ -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
|
||||
|
||||
|
@ -4,7 +4,7 @@ iOS Spatial Orientation
|
||||
|
||||
'''
|
||||
|
||||
from plyer.facades import SpatialOrientation
|
||||
from sbapp.plyer.facades import SpatialOrientation
|
||||
from pyobjus import autoclass
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ iOS Storage Path
|
||||
--------------------
|
||||
'''
|
||||
|
||||
from plyer.facades import StoragePath
|
||||
from sbapp.plyer.facades import StoragePath
|
||||
from pyobjus import autoclass
|
||||
import os
|
||||
|
||||
|
@ -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 = \
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -3,7 +3,7 @@ Linux accelerometer
|
||||
---------------------
|
||||
'''
|
||||
|
||||
from plyer.facades import Accelerometer
|
||||
from sbapp.plyer.facades import Accelerometer
|
||||
import glob
|
||||
import re
|
||||
|
||||
|
139
sbapp/plyer/platforms/linux/audio.py
Normal file
139
sbapp/plyer/platforms/linux/audio.py
Normal 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()
|
@ -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()
|
||||
|
@ -4,7 +4,7 @@ Linux Brightness
|
||||
|
||||
'''
|
||||
|
||||
from plyer.facades import Brightness
|
||||
from sbapp.plyer.facades import Brightness
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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 += [
|
||||
|
@ -3,7 +3,7 @@ try:
|
||||
except ImportError:
|
||||
raise NotImplementedError()
|
||||
|
||||
from plyer.facades import Keystore
|
||||
from sbapp.plyer.facades import Keystore
|
||||
|
||||
|
||||
class LinuxKeystore(Keystore):
|
||||
|
@ -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', [])
|
||||
|
@ -1,5 +1,5 @@
|
||||
import subprocess as sb
|
||||
from plyer.facades import Orientation
|
||||
from sbapp.plyer.facades import Orientation
|
||||
|
||||
|
||||
class LinuxOrientation(Orientation):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user