mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2025-05-10 05:23:16 +02:00
Compare commits
62 Commits
d639ffcf9a
...
55f6574297
Author | SHA1 | Date | |
---|---|---|---|
|
55f6574297 | ||
|
1c461d644d | ||
|
426c9d9617 | ||
|
bc8dcb82a3 | ||
|
19cae41062 | ||
|
15600d5172 | ||
|
887c0a9a16 | ||
|
a4e22c7868 | ||
|
02aadc4442 | ||
|
7d1de23ea9 | ||
|
7759264b37 | ||
|
cc1e42cc66 | ||
|
443c8938be | ||
|
c210754aa4 | ||
|
b301dc569b | ||
|
55baede2fc | ||
|
79ad4bb353 | ||
|
1989330d21 | ||
|
79ba89373a | ||
|
0573af2ba0 | ||
|
fbb58eb7b9 | ||
|
3b8700c197 | ||
|
b2a5b8c193 | ||
|
ad58ab335a | ||
|
a2575559cb | ||
|
d2efd8e91a | ||
|
f71a3934d7 | ||
|
2cc4182376 | ||
|
401d152935 | ||
|
afac322859 | ||
|
9e992c83fd | ||
|
fc09ac1cf7 | ||
|
922107df50 | ||
|
83b8130311 | ||
|
211a0ad16b | ||
|
ba0c80dd80 | ||
|
25e31d8d9d | ||
|
dcfc76e459 | ||
|
5d64fe1f8d | ||
|
7f54cbfb17 | ||
|
be8051240f | ||
|
970ec7b3b3 | ||
|
63a96dea37 | ||
|
3979a806b0 | ||
|
ee18dcab31 | ||
|
2e5d557aa7 | ||
|
364463c541 | ||
|
32b6fd0a81 | ||
|
10b073d1c2 | ||
|
4004151f39 | ||
|
7885f39708 | ||
|
fe4c61880e | ||
|
0cbd4c71ab | ||
|
a9269f20d4 | ||
|
bf8fbe5f86 | ||
|
9f86c4130c | ||
|
6fb9a94a43 | ||
|
46450098b4 | ||
|
c14699151d | ||
|
293023be12 | ||
|
91883a0510 | ||
|
2221629315 |
@ -20,7 +20,7 @@ RUN git clone https://github.com/jacobeva/Reticulum \
|
|||||||
|
|
||||||
# Switch branches
|
# Switch branches
|
||||||
WORKDIR "Reticulum"
|
WORKDIR "Reticulum"
|
||||||
RUN git switch ble-dev
|
#RUN git switch ble-dev
|
||||||
|
|
||||||
WORKDIR ".."
|
WORKDIR ".."
|
||||||
|
|
||||||
|
3
Makefile
3
Makefile
@ -31,6 +31,9 @@ preparewheel:
|
|||||||
build_wheel:
|
build_wheel:
|
||||||
. sbapp/venv/bin/activate; python3 setup.py sdist bdist_wheel
|
. sbapp/venv/bin/activate; python3 setup.py sdist bdist_wheel
|
||||||
|
|
||||||
|
build_win_exe:
|
||||||
|
python -m PyInstaller sideband.spec --noconfirm
|
||||||
|
|
||||||
release: build_wheel apk fetchapk
|
release: build_wheel apk fetchapk
|
||||||
|
|
||||||
release_docker:
|
release_docker:
|
||||||
|
98
README.md
98
README.md
@ -39,7 +39,7 @@ Sideband can run on most computing devices, but installation methods vary by dev
|
|||||||
|
|
||||||
## On Android
|
## On Android
|
||||||
|
|
||||||
For your Android devices, you can install Sideband through F-Droid, by adding the [Between the Borders Repo](https://reticulum.betweentheborders.com/fdroid/repo/), or you can download an [APK on the latest release](https://github.com/markqvist/Sideband/releases/latest) page. Both sources are signed with the same release keys, and can be used interchangably.
|
For your Android devices, you can install Sideband through F-Droid, by adding the [Between the Borders Repo](https://reticulum.betweentheborders.com/fdroid/repo/), or you can download an [APK on the latest release page](https://github.com/markqvist/Sideband/releases/latest). Both sources are signed with the same release keys, and can be used interchangably.
|
||||||
|
|
||||||
After the application is installed on your Android device, it is also possible to pull updates directly through the **Repository** section of the application.
|
After the application is installed on your Android device, it is also possible to pull updates directly through the **Repository** section of the application.
|
||||||
|
|
||||||
@ -47,6 +47,8 @@ After the application is installed on your Android device, it is also possible t
|
|||||||
|
|
||||||
On all Linux-based operating systems, Sideband is available as a `pipx`/`pip` package. This installation method **includes desktop integration**, so that Sideband will show up in your applications menu and launchers. Below are install steps for the most common recent Linux distros. For Debian 11, see the end of this section.
|
On all Linux-based operating systems, Sideband is available as a `pipx`/`pip` package. This installation method **includes desktop integration**, so that Sideband will show up in your applications menu and launchers. Below are install steps for the most common recent Linux distros. For Debian 11, see the end of this section.
|
||||||
|
|
||||||
|
**Please note!** The very latest Python release, Python 3.13 is currently **not** compatible with the Kivy framework, that Sideband uses to render its user interface. If your Linux distribution uses Python 3.13 as its default Python installation, you will need to install an earlier version as well. Using [the latest release of Python 3.12](https://www.python.org/downloads/release/python-3127/) is recommended.
|
||||||
|
|
||||||
You will first need to install a few dependencies for audio messaging and Codec2 support to work:
|
You will first need to install a few dependencies for audio messaging and Codec2 support to work:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -66,6 +68,10 @@ Once those are installed, install the Sideband application itself:
|
|||||||
```bash
|
```bash
|
||||||
# Finally, install Sideband using pipx:
|
# Finally, install Sideband using pipx:
|
||||||
pipx install sbapp
|
pipx install sbapp
|
||||||
|
|
||||||
|
# If you need to specify a specific Python version,
|
||||||
|
# use something like the following:
|
||||||
|
pipx install sbapp --python python3.12
|
||||||
```
|
```
|
||||||
|
|
||||||
After installation, you can now run Sideband in a number of different ways:
|
After installation, you can now run Sideband in a number of different ways:
|
||||||
@ -109,8 +115,9 @@ pip install sbapp --break-system-packages
|
|||||||
# any of the normal UI dependencies:
|
# any of the normal UI dependencies:
|
||||||
pip install sbapp --no-dependencies
|
pip install sbapp --no-dependencies
|
||||||
|
|
||||||
# In the above case, you will still need to
|
# In the case of using --no-dependencies, you
|
||||||
# manually install the RNS and LXMF dependencies:
|
# will still need to manually install the RNS
|
||||||
|
# and LXMF dependencies:
|
||||||
pip install rns lxmf
|
pip install rns lxmf
|
||||||
|
|
||||||
# Install Sideband on Debian 11 and derivatives:
|
# Install Sideband on Debian 11 and derivatives:
|
||||||
@ -154,28 +161,54 @@ sideband
|
|||||||
|
|
||||||
## On macOS
|
## On macOS
|
||||||
|
|
||||||
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`.
|
To install Sideband on macOS, you have two options available:
|
||||||
|
|
||||||
If you don't already have the `pipx` package manager installed, it can be installed via [Homebrew](https://brew.sh/) with `brew install pipx`.
|
1. An easy to install pre-built disk image package
|
||||||
|
2. A source package install for more advanced setups
|
||||||
|
|
||||||
|
#### Prebuilt Executable
|
||||||
|
|
||||||
|
You can download a disk image with Sideband for macOS (ARM and Intel) from the [latest release page](https://github.com/markqvist/Sideband/releases/latest). Simply mount the downloaded disk image, drag `Sideband` to your applications folder, and run it.
|
||||||
|
|
||||||
|
**Please note!** If you have application install restrictions enabled on your macOS install, or have restricted your system to only allow installation of application from the Apple App Store, you will need to create an exception for Sideband. The Sideband application will *never* be distributed with an Apple-controlled digital signature, as this will allow Apple to simply disable Sideband from running on your system if they decide to do so, or are forced to by authorities or other circumstances.
|
||||||
|
|
||||||
|
If you install Sideband from the DMG file, it is still recommended to install the `rns` package via the `pip` or `pipx` package manager, so you can use the RNS utility programs, like `rnstatus` to see interface and connectivity status from the terminal. If you already have Python and `pip` installed on your system, simply open a terminal window and use one of the following commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install Sideband and dependencies on macOS using pipx:
|
# Install Reticulum and utilities with pip:
|
||||||
pipx install sbapp
|
pip3 install rns
|
||||||
pipx ensurepath
|
|
||||||
|
|
||||||
# Run it
|
# On some versions, you may need to use the
|
||||||
sideband
|
# flag --break-system-packages to install:
|
||||||
|
pip3 install rns --break-system-packages
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
If you do not have Python and `pip` available, [download and install it](https://www.python.org/downloads/) first.
|
||||||
|
|
||||||
|
#### Source Package Install
|
||||||
|
|
||||||
|
For more advanced setups, including the ability to run Sideband in headless daemon mode, enable debug logging output, configuration import and export and more, you may want to install it from the source package via `pip` instead.
|
||||||
|
|
||||||
|
**Please note!** The very latest Python release, Python 3.13 is currently **not** compatible with the Kivy framework, that Sideband uses to render its user interface. If your version of macOS uses Python 3.13 as its default Python installation, you will need to install an earlier version as well. Using [the latest release of Python 3.12](https://www.python.org/downloads/release/python-3127/) is recommended.
|
||||||
|
|
||||||
|
To install Sideband via `pip`, follow these instructions:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install Sideband and dependencies on macOS using pip:
|
# Install Sideband and dependencies on macOS using pip:
|
||||||
pip3 install sbapp --user --break-system-packages
|
pip3 install sbapp --user --break-system-packages
|
||||||
|
|
||||||
# Run it:
|
# Optionally install RNS command line utilities:
|
||||||
|
pip3 install rns
|
||||||
|
|
||||||
|
# Run Sideband from the terminal:
|
||||||
python3 -m sbapp.main
|
python3 -m sbapp.main
|
||||||
|
|
||||||
|
# Enable debug logging:
|
||||||
|
python3 -m sbapp.main -v
|
||||||
|
|
||||||
|
# Start Sideband in daemon mode:
|
||||||
|
python3 -m sbapp.main -d
|
||||||
|
|
||||||
# If you add your pip install location to
|
# If you add your pip install location to
|
||||||
# the PATH environment variable, you can
|
# the PATH environment variable, you can
|
||||||
# also run Sideband simply using:
|
# also run Sideband simply using:
|
||||||
@ -185,13 +218,32 @@ sideband
|
|||||||
|
|
||||||
## On Windows
|
## 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.
|
To install Sideband on Windows, you have two options available:
|
||||||
|
|
||||||
Please note that audio messaging functionality isn't supported on Windows yet. Please support the development if you'd like to see this feature added faster.
|
1. An easy to install pre-built executable package
|
||||||
|
2. A source package install for more advanced setups
|
||||||
|
|
||||||
**Important!** When asked by the installer, make sure to add the Python program to your PATH environment variables. If you don't do this, you will not be able to use the `pip` installer, or run the `sideband` command.
|
#### Prebuilt Executable
|
||||||
|
|
||||||
When Python has been installed, you can open a command prompt and install sideband via `pip`:
|
Simply download the packaged Windows ZIP file from the [latest release page](https://github.com/markqvist/Sideband/releases/latest), unzip the file, and run `Sideband.exe` from the unzipped directory. You can create desktop or start menu shortcuts from this executable if needed.
|
||||||
|
|
||||||
|
When running Sideband for the first time, a default Reticulum configuration file will be created, if you don't already have one. If you don't have any existing Reticulum connectivity available locally, you may want to edit the file, located at `C:\Users\USERNAME\.reticulum\config` and manually add an interface that provides connectivity to a wider network. If you just want to connect over the Internet, you can add one of the public hubs on the [Reticulum Testnet](https://reticulum.network/connect.html).
|
||||||
|
|
||||||
|
Though the ZIP file contains everything necessary to run Sideband, it is also recommended to install the Reticulum command line utilities separately, so that you can use commands like `rnstatus` and `rnsd` from the command line. This will make it easier to manage Reticulum connectivity on your system. If you do not already have Python installed on your system, [download and install it](https://www.python.org/downloads/) first.
|
||||||
|
|
||||||
|
**Important!** When asked by the installer, make sure to add the Python program to your `PATH` environment variables. If you don't do this, you will not be able to use the `pip` installer, or run any of the installed commands. When Python has been installed, you can open a command prompt and install the Reticulum package via `pip`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install rns
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Source Package Install
|
||||||
|
|
||||||
|
For more advanced setups, including the ability to run Sideband in headless daemon mode, enable debug logging output, configuration import and export and more, you may want to install it from the source package via `pip` instead.
|
||||||
|
|
||||||
|
In this case, you will need to [download and install the latest supported version of Python](https://www.python.org/downloads/release/python-3127/) (currently Python 3.12.7), since very latest Python release, Python 3.13 is currently **not** compatible with the Kivy framework, that Sideband uses to render its user interface. The binary package already includes a compatible Python version, so if you are running Sideband from that, there is no need to install a specific version of Python.
|
||||||
|
|
||||||
|
When Python has been installed, you can open a command prompt and install Sideband via `pip`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install sbapp
|
pip install sbapp
|
||||||
@ -199,7 +251,7 @@ pip install sbapp
|
|||||||
|
|
||||||
The Sideband application can now be launched by running the command `sideband` in the command prompt. If needed, you can create a shortcut for Sideband on your desktop or in the start menu.
|
The Sideband application can now be launched by running the command `sideband` in the command prompt. If needed, you can create a shortcut for Sideband on your desktop or in the start menu.
|
||||||
|
|
||||||
When running Sideband for the first time, a default Reticulum configuration file will be created, if you don't already have one. If you don't have any existing Reticulum connectivity available locally, you may want to edit the file, located at `C:\Users\USERNAME\.reticulum\config` and manually add an interface that provides connectivity to a wider network. If you just want to connect over the Internet, you can add one of the public hubs on the [Reticulum Testnet](https://reticulum.network/connect.html).
|
Since this installation method automatically installs the `rns` and `lxmf` packages as well, you will also have access to using all the included RNS and LXMF utilities like `rnstatus`, `rnsd` and `lxmd` on your system.
|
||||||
|
|
||||||
# Paper Messaging Example
|
# Paper Messaging Example
|
||||||
|
|
||||||
@ -236,7 +288,7 @@ You can help support the continued development of open, free and private communi
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
# Development Roadmap
|
# Planned Features
|
||||||
|
|
||||||
- <s>Secure and private location and telemetry sharing</s>
|
- <s>Secure and private location and telemetry sharing</s>
|
||||||
- <s>Including images in messages</s>
|
- <s>Including images in messages</s>
|
||||||
@ -246,15 +298,15 @@ You can help support the continued development of open, free and private communi
|
|||||||
- <s>Using Sideband as a Reticulum Transport Instance</s>
|
- <s>Using Sideband as a Reticulum Transport Instance</s>
|
||||||
- <s>Encryption keys export and import</s>
|
- <s>Encryption keys export and import</s>
|
||||||
- <s>Plugin support for commands, services and telemetry</s>
|
- <s>Plugin support for commands, services and telemetry</s>
|
||||||
- <s>Adding Linux .desktop file integration</s>
|
|
||||||
- <s>Sending voice messages (using Codec2 and Opus)</s>
|
- <s>Sending voice messages (using Codec2 and Opus)</s>
|
||||||
- Implementing the Local Broadcasts feature
|
- <s>Adding a Linux desktop integration</s>
|
||||||
|
- <s>Adding prebuilt Windows binaries to the releases</s>
|
||||||
|
- <s>Adding prebuilt macOS binaries to the releases</s>
|
||||||
|
- Adding a Nomad Net page browser
|
||||||
- LXMF sneakernet functionality
|
- LXMF sneakernet functionality
|
||||||
- Network visualisation and test tools
|
- Network visualisation and test tools
|
||||||
- A debug log viewer
|
|
||||||
- Better message sorting mechanism
|
- Better message sorting mechanism
|
||||||
- Fix I2P status not being displayed correctly when the I2P router disappears unexpectedly
|
- A debug log viewer
|
||||||
- Adding a Nomad Net page browser
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
Unless otherwise noted, this work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa].
|
Unless otherwise noted, this work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa].
|
||||||
|
@ -67,7 +67,7 @@ class GpsdLocationPlugin(SidebandTelemetryPlugin):
|
|||||||
self.latitude = gpsd_latitude
|
self.latitude = gpsd_latitude
|
||||||
self.longitude = gpsd_longitude
|
self.longitude = gpsd_longitude
|
||||||
self.altitude = gpsd_altitude
|
self.altitude = gpsd_altitude
|
||||||
self.speed = gpsd_speed
|
self.speed = gpsd_speed*3.6 # Convert from m/s to km/h
|
||||||
self.bearing = gpsd_bearing
|
self.bearing = gpsd_bearing
|
||||||
|
|
||||||
epx = result.get("epx", None); epy = result.get("epy", None)
|
epx = result.get("epx", None); epy = result.get("epy", None)
|
||||||
|
@ -7,7 +7,7 @@ clean:
|
|||||||
-(rm ./__pycache__ -r)
|
-(rm ./__pycache__ -r)
|
||||||
-(rm ./app_storage -r)
|
-(rm ./app_storage -r)
|
||||||
-(rm ./share/pkg/* -r)
|
-(rm ./share/pkg/* -r)
|
||||||
-(rm ./share/mirrors/* -r)
|
-(rm ./share/mirrors/* -rf)
|
||||||
-(rm ./bin -r)
|
-(rm ./bin -r)
|
||||||
|
|
||||||
cleanlibs:
|
cleanlibs:
|
||||||
@ -66,13 +66,14 @@ fetchshare:
|
|||||||
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
|
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
|
||||||
cp ../../dist_archive/nomadnet-*-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/rnsh-*-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/
|
cp ../../dist_archive/RNode_Firmware_*_Source.zip ./share/pkg/
|
||||||
zip --junk-paths ./share/pkg/example_plugins.zip ../docs/example_plugins/*.py
|
zip --junk-paths ./share/pkg/example_plugins.zip ../docs/example_plugins/*.py
|
||||||
cp -r ../../dist_archive/reticulum.network ./share/mirrors/
|
cp -r ../../dist_archive/reticulum.network ./share/mirrors/
|
||||||
cp -r ../../dist_archive/unsigned.io ./share/mirrors/
|
cp -r ../../dist_archive/unsigned.io ./share/mirrors/
|
||||||
cp ../../dist_archive/Reticulum\ Manual.pdf ./share/mirrors/Reticulum_Manual.pdf
|
cp ../../dist_archive/Reticulum\ Manual.pdf ./share/mirrors/Reticulum_Manual.pdf
|
||||||
cp ../../dist_archive/Reticulum\ Manual.epub ./share/mirrors/Reticulum_Manual.epub
|
cp ../../dist_archive/Reticulum\ Manual.epub ./share/mirrors/Reticulum_Manual.epub
|
||||||
|
cp -r ../../rnode-flasher ./share/mirrors/
|
||||||
|
-(rm ./share/mirrors/rnode-flasher/.git -rf)
|
||||||
|
|
||||||
release:
|
release:
|
||||||
. venv/bin/activate; buildozer android release
|
. venv/bin/activate; buildozer android release
|
||||||
|
BIN
sbapp/assets/fonts/BigBlueTerm437NerdFont-Regular.ttf
Normal file
BIN
sbapp/assets/fonts/BigBlueTerm437NerdFont-Regular.ttf
Normal file
Binary file not shown.
BIN
sbapp/assets/fonts/RobotoMonoNerdFont-Regular.ttf
Normal file
BIN
sbapp/assets/fonts/RobotoMonoNerdFont-Regular.ttf
Normal file
Binary file not shown.
@ -1,4 +1,4 @@
|
|||||||
# Entry version 20240630
|
# Entry version 20241128
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Comment[en_US]=Messaging, telemetry and remote control over LXMF
|
Comment[en_US]=Messaging, telemetry and remote control over LXMF
|
||||||
Comment=Messaging, telemetry and remote control over LXMF
|
Comment=Messaging, telemetry and remote control over LXMF
|
||||||
|
@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,
|
|||||||
|
|
||||||
version.regex = __version__ = ['"](.*)['"]
|
version.regex = __version__ = ['"](.*)['"]
|
||||||
version.filename = %(source.dir)s/main.py
|
version.filename = %(source.dir)s/main.py
|
||||||
android.numeric_version = 20241013
|
android.numeric_version = 20241213
|
||||||
|
|
||||||
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions
|
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions
|
||||||
|
|
||||||
|
@ -30,10 +30,13 @@ def get_variant() -> str:
|
|||||||
version = re.findall(version_regex, version_file_data, re.M)[0]
|
version = re.findall(version_regex, version_file_data, re.M)[0]
|
||||||
return version
|
return version
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise ValueError(f"Unable to find version string in {version_file}.")
|
return None
|
||||||
|
|
||||||
__version__ = get_version()
|
__version__ = get_version()
|
||||||
__variant__ = get_variant()
|
__variant__ = get_variant()
|
||||||
|
variant_str = ""
|
||||||
|
if __variant__:
|
||||||
|
variant_str = " "+__variant__
|
||||||
|
|
||||||
def glob_paths(pattern):
|
def glob_paths(pattern):
|
||||||
out_files = []
|
out_files = []
|
||||||
@ -60,14 +63,14 @@ package_data = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Freezing openCom Companion "+__version__+" "+__variant__)
|
print("Freezing openCom Companion "+__version__+" "+variant_str)
|
||||||
|
|
||||||
if build_appimage:
|
if build_appimage:
|
||||||
global_excludes = [".buildozer", "build", "dist"]
|
global_excludes = [".buildozer", "build", "dist"]
|
||||||
# Dependencies are automatically detected, but they might need fine-tuning.
|
# Dependencies are automatically detected, but they might need fine-tuning.
|
||||||
appimage_options = {
|
appimage_options = {
|
||||||
"target_name": "openCom Companion",
|
"target_name": "openCom Companion",
|
||||||
"target_version": __version__+" "+__variant__,
|
"target_version": __version__+" "+variant_str,
|
||||||
"include_files": [],
|
"include_files": [],
|
||||||
"excludes": [],
|
"excludes": [],
|
||||||
"packages": ["kivy"],
|
"packages": ["kivy"],
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
sbapp/kivymd/images/folder_orange.png
Normal file
BIN
sbapp/kivymd/images/folder_orange.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
588
sbapp/main.py
588
sbapp/main.py
@ -1,6 +1,6 @@
|
|||||||
__debug_build__ = False
|
__debug_build__ = False
|
||||||
__disable_shaders__ = False
|
__disable_shaders__ = False
|
||||||
__version__ = "1.1.3"
|
__version__ = "1.2.0"
|
||||||
__variant__ = ""
|
__variant__ = ""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -24,6 +24,54 @@ import base64
|
|||||||
import threading
|
import threading
|
||||||
import RNS.vendor.umsgpack as msgpack
|
import RNS.vendor.umsgpack as msgpack
|
||||||
|
|
||||||
|
WINDOW_DEFAULT_WIDTH = "494"
|
||||||
|
WINDOW_DEFAULT_HEIGHT = "800"
|
||||||
|
|
||||||
|
app_ui_scaling_path = None
|
||||||
|
def apply_ui_scale():
|
||||||
|
global app_ui_scaling_path
|
||||||
|
default_scale = os.environ["KIVY_METRICS_DENSITY"] if "KIVY_METRICS_DENSITY" in os.environ else "unknown"
|
||||||
|
config_path = None
|
||||||
|
ui_scale_path = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
import plyer
|
||||||
|
ui_scale_path = plyer.storagepath.get_application_dir()+"/io.unsigned.sideband/files/app_storage/ui_scale"
|
||||||
|
else:
|
||||||
|
if config_path == None:
|
||||||
|
import sbapp.plyer as plyer
|
||||||
|
ui_scale_path = plyer.storagepath.get_home_dir()+"/.config/sideband/app_storage/ui_scale"
|
||||||
|
if ui_scale_path.startswith("file://"):
|
||||||
|
ui_scale_path = ui_scale_path.replace("file://", "")
|
||||||
|
else:
|
||||||
|
ui_scale_path = config_path+"/app_storage/ui_scale"
|
||||||
|
|
||||||
|
app_ui_scaling_path = ui_scale_path
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while locating UI scale file: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
if ui_scale_path != None:
|
||||||
|
RNS.log("Default scaling factor on this platform is "+str(default_scale), RNS.LOG_NOTICE)
|
||||||
|
try:
|
||||||
|
RNS.log("Looking for scaling info in "+str(ui_scale_path))
|
||||||
|
if os.path.isfile(ui_scale_path):
|
||||||
|
scale_factor = None
|
||||||
|
with open(ui_scale_path, "r") as sf:
|
||||||
|
scale_factor = float(sf.readline())
|
||||||
|
|
||||||
|
if scale_factor != None:
|
||||||
|
if scale_factor >= 0.3 and scale_factor <= 5.0:
|
||||||
|
os.environ["KIVY_METRICS_DENSITY"] = str(scale_factor)
|
||||||
|
RNS.log("UI scaling factor set to "+str(os.environ["KIVY_METRICS_DENSITY"]), RNS.LOG_NOTICE)
|
||||||
|
elif scale_factor == 0.0:
|
||||||
|
RNS.log("Using default UI scaling factor", RNS.LOG_NOTICE)
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while reading UI scaling factor: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
if args.export_settings:
|
if args.export_settings:
|
||||||
from .sideband.core import SidebandCore
|
from .sideband.core import SidebandCore
|
||||||
sideband = SidebandCore(
|
sideband = SidebandCore(
|
||||||
@ -126,6 +174,11 @@ if not args.daemon:
|
|||||||
local = os.path.dirname(__file__)
|
local = os.path.dirname(__file__)
|
||||||
sys.path.append(local)
|
sys.path.append(local)
|
||||||
|
|
||||||
|
if not RNS.vendor.platformutils.is_android():
|
||||||
|
from kivy.config import Config
|
||||||
|
Config.set("graphics", "width", WINDOW_DEFAULT_WIDTH)
|
||||||
|
Config.set("graphics", "height", WINDOW_DEFAULT_HEIGHT)
|
||||||
|
|
||||||
if args.daemon:
|
if args.daemon:
|
||||||
from .sideband.core import SidebandCore
|
from .sideband.core import SidebandCore
|
||||||
class DaemonElement():
|
class DaemonElement():
|
||||||
@ -143,9 +196,11 @@ if args.daemon:
|
|||||||
NewConv = DaemonElement; Telemetry = DaemonElement; ObjectDetails = DaemonElement; Announces = DaemonElement;
|
NewConv = DaemonElement; Telemetry = DaemonElement; ObjectDetails = DaemonElement; Announces = DaemonElement;
|
||||||
Messages = DaemonElement; ts_format = DaemonElement; messages_screen_kv = DaemonElement; plyer = DaemonElement; multilingual_markup = DaemonElement;
|
Messages = DaemonElement; ts_format = DaemonElement; messages_screen_kv = DaemonElement; plyer = DaemonElement; multilingual_markup = DaemonElement;
|
||||||
ContentNavigationDrawer = DaemonElement; DrawerList = DaemonElement; IconListItem = DaemonElement; escape_markup = DaemonElement;
|
ContentNavigationDrawer = DaemonElement; DrawerList = DaemonElement; IconListItem = DaemonElement; escape_markup = DaemonElement;
|
||||||
SoundLoader = DaemonElement;
|
SoundLoader = DaemonElement; BoxLayout = DaemonElement;
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
apply_ui_scale()
|
||||||
|
|
||||||
from kivymd.app import MDApp
|
from kivymd.app import MDApp
|
||||||
app_superclass = MDApp
|
app_superclass = MDApp
|
||||||
from kivy.core.window import Window
|
from kivy.core.window import Window
|
||||||
@ -157,6 +212,7 @@ else:
|
|||||||
from kivy.effects.scroll import ScrollEffect
|
from kivy.effects.scroll import ScrollEffect
|
||||||
from kivy.uix.screenmanager import ScreenManager
|
from kivy.uix.screenmanager import ScreenManager
|
||||||
from kivy.uix.screenmanager import FadeTransition, NoTransition, SlideTransition
|
from kivy.uix.screenmanager import FadeTransition, NoTransition, SlideTransition
|
||||||
|
from kivy.uix.boxlayout import BoxLayout
|
||||||
from kivymd.uix.list import OneLineIconListItem, IconLeftWidget
|
from kivymd.uix.list import OneLineIconListItem, IconLeftWidget
|
||||||
from kivy.properties import StringProperty
|
from kivy.properties import StringProperty
|
||||||
from kivymd.uix.button import BaseButton, MDIconButton
|
from kivymd.uix.button import BaseButton, MDIconButton
|
||||||
@ -181,6 +237,7 @@ else:
|
|||||||
from ui.layouts import *
|
from ui.layouts import *
|
||||||
from ui.conversations import Conversations, MsgSync, NewConv
|
from ui.conversations import Conversations, MsgSync, NewConv
|
||||||
from ui.telemetry import Telemetry
|
from ui.telemetry import Telemetry
|
||||||
|
from ui.utilities import Utilities
|
||||||
from ui.objectdetails import ObjectDetails
|
from ui.objectdetails import ObjectDetails
|
||||||
from ui.announces import Announces
|
from ui.announces import Announces
|
||||||
from ui.messages import Messages, ts_format, messages_screen_kv
|
from ui.messages import Messages, ts_format, messages_screen_kv
|
||||||
@ -208,6 +265,7 @@ else:
|
|||||||
from .ui.conversations import Conversations, MsgSync, NewConv
|
from .ui.conversations import Conversations, MsgSync, NewConv
|
||||||
from .ui.announces import Announces
|
from .ui.announces import Announces
|
||||||
from .ui.telemetry import Telemetry
|
from .ui.telemetry import Telemetry
|
||||||
|
from .ui.utilities import Utilities
|
||||||
from .ui.objectdetails import ObjectDetails
|
from .ui.objectdetails import ObjectDetails
|
||||||
from .ui.messages import Messages, ts_format, messages_screen_kv
|
from .ui.messages import Messages, ts_format, messages_screen_kv
|
||||||
from .ui.helpers import ContentNavigationDrawer, DrawerList, IconListItem
|
from .ui.helpers import ContentNavigationDrawer, DrawerList, IconListItem
|
||||||
@ -294,6 +352,7 @@ class SidebandApp(MDApp):
|
|||||||
self.sync_dialog = None
|
self.sync_dialog = None
|
||||||
self.settings_ready = False
|
self.settings_ready = False
|
||||||
self.telemetry_ready = False
|
self.telemetry_ready = False
|
||||||
|
self.utilities_ready = False
|
||||||
self.connectivity_ready = False
|
self.connectivity_ready = False
|
||||||
self.hardware_ready = False
|
self.hardware_ready = False
|
||||||
self.repository_ready = False
|
self.repository_ready = False
|
||||||
@ -306,9 +365,11 @@ class SidebandApp(MDApp):
|
|||||||
self.service_last_available = 0
|
self.service_last_available = 0
|
||||||
self.closing_app = False
|
self.closing_app = False
|
||||||
|
|
||||||
|
self.file_manager = None
|
||||||
self.attach_path = None
|
self.attach_path = None
|
||||||
self.attach_type = None
|
self.attach_type = None
|
||||||
self.attach_dialog = None
|
self.attach_dialog = None
|
||||||
|
self.shared_attach_dialog = None
|
||||||
self.rec_dialog = None
|
self.rec_dialog = None
|
||||||
self.last_msg_audio = None
|
self.last_msg_audio = None
|
||||||
self.msg_sound = None
|
self.msg_sound = None
|
||||||
@ -505,6 +566,15 @@ class SidebandApp(MDApp):
|
|||||||
fn_italic=fb_path+"NotoSans-Italic.ttf",
|
fn_italic=fb_path+"NotoSans-Italic.ttf",
|
||||||
fn_bolditalic=fb_path+"NotoSans-BoldItalic.ttf")
|
fn_bolditalic=fb_path+"NotoSans-BoldItalic.ttf")
|
||||||
|
|
||||||
|
LabelBase.register(name="mono",
|
||||||
|
fn_regular=fb_path+"RobotoMonoNerdFont-Regular.ttf")
|
||||||
|
|
||||||
|
LabelBase.register(name="term",
|
||||||
|
fn_regular=fb_path+"BigBlueTerm437NerdFont-Regular.ttf")
|
||||||
|
|
||||||
|
LabelBase.register(name="nf",
|
||||||
|
fn_regular=fb_path+"RobotoMonoNerdFont-Regular.ttf")
|
||||||
|
|
||||||
def update_input_language(self):
|
def update_input_language(self):
|
||||||
language = self.sideband.config["input_language"]
|
language = self.sideband.config["input_language"]
|
||||||
if language == None:
|
if language == None:
|
||||||
@ -699,6 +769,13 @@ class SidebandApp(MDApp):
|
|||||||
else:
|
else:
|
||||||
RNS.log("Conversations view did not exist", RNS.LOG_DEBUG)
|
RNS.log("Conversations view did not exist", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
def ui_update_job():
|
||||||
|
time.sleep(0.05)
|
||||||
|
def cb(dt):
|
||||||
|
self.perform_wake_update()
|
||||||
|
Clock.schedule_once(cb, 0.1)
|
||||||
|
threading.Thread(target=ui_update_job, daemon=True).start()
|
||||||
|
|
||||||
RNS.log("App resumed", RNS.LOG_DEBUG)
|
RNS.log("App resumed", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
@ -714,6 +791,21 @@ class SidebandApp(MDApp):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def perform_wake_update(self):
|
||||||
|
# This workaround mitigates a bug in Kivy on Android
|
||||||
|
# which causes the UI to turn black on app resume,
|
||||||
|
# probably due to an invalid GL draw context. By
|
||||||
|
# simply opening and immediately closing the nav
|
||||||
|
# drawer, we force the UI to do a frame redraw, which
|
||||||
|
# results in the UI actually being visible again.
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
RNS.log("Performing app wake UI update", RNS.LOG_DEBUG)
|
||||||
|
self.root.ids.nav_drawer.set_state("open")
|
||||||
|
def cb(dt):
|
||||||
|
self.root.ids.nav_drawer.set_state("closed")
|
||||||
|
Clock.schedule_once(cb, 0)
|
||||||
|
|
||||||
|
|
||||||
def check_bluetooth_permissions(self):
|
def check_bluetooth_permissions(self):
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
|
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
|
||||||
@ -870,6 +962,37 @@ class SidebandApp(MDApp):
|
|||||||
if data.lower().startswith(LXMF.LXMessage.URI_SCHEMA):
|
if data.lower().startswith(LXMF.LXMessage.URI_SCHEMA):
|
||||||
action = "lxm_uri"
|
action = "lxm_uri"
|
||||||
|
|
||||||
|
if intent_action == "android.intent.action.SEND":
|
||||||
|
try:
|
||||||
|
Intent = autoclass("android.content.Intent")
|
||||||
|
extras = intent.getExtras()
|
||||||
|
target = extras.get(Intent.EXTRA_STREAM)
|
||||||
|
mime_types = extras.get(Intent.EXTRA_MIME_TYPES)
|
||||||
|
target_uri = target.toString()
|
||||||
|
target_path = target.getPath()
|
||||||
|
target_filename = target.getLastPathSegment()
|
||||||
|
|
||||||
|
RNS.log(f"Received share intent: {target_uri} / {target_path} / {target_filename}", RNS.LOG_DEBUG)
|
||||||
|
for cf in os.listdir(self.sideband.share_cache):
|
||||||
|
rt = os.path.join(self.sideband.share_cache, cf)
|
||||||
|
os.unlink(rt)
|
||||||
|
RNS.log("Removed previously cached data: "+str(rt), RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
ContentResolver = autoclass("android.content.ContentResolver")
|
||||||
|
cr = mActivity.getContentResolver()
|
||||||
|
cache_path = os.path.join(self.sideband.share_cache, target_filename)
|
||||||
|
input_stream = cr.openInputStream(target)
|
||||||
|
with open(cache_path, "wb") as cache_file:
|
||||||
|
cache_file.write(bytes(input_stream.readAllBytes()))
|
||||||
|
RNS.log("Cached shared data from Android intent", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
action = "shared_data"
|
||||||
|
data = {"filename": target_filename, "data_path": cache_path}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while getting intent action data: {e}", RNS.LOG_ERROR)
|
||||||
|
RNS.trace_exception(e)
|
||||||
|
|
||||||
if action != None:
|
if action != None:
|
||||||
self.handle_action(action, data)
|
self.handle_action(action, data)
|
||||||
|
|
||||||
@ -877,6 +1000,16 @@ class SidebandApp(MDApp):
|
|||||||
if action == "lxm_uri":
|
if action == "lxm_uri":
|
||||||
self.ingest_lxm_uri(data)
|
self.ingest_lxm_uri(data)
|
||||||
|
|
||||||
|
if action == "shared_data":
|
||||||
|
RNS.log("Got shared data: "+str(data))
|
||||||
|
def cb(dt):
|
||||||
|
try:
|
||||||
|
self.shared_attachment_action(data)
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while handling external message attachment", RNS.LOG_ERROR)
|
||||||
|
RNS.trace_exception(e)
|
||||||
|
Clock.schedule_once(cb, 0.1)
|
||||||
|
|
||||||
def ingest_lxm_uri(self, lxm_uri):
|
def ingest_lxm_uri(self, lxm_uri):
|
||||||
RNS.log("Ingesting LXMF paper message from URI: "+str(lxm_uri), RNS.LOG_DEBUG)
|
RNS.log("Ingesting LXMF paper message from URI: "+str(lxm_uri), RNS.LOG_DEBUG)
|
||||||
self.sideband.lxm_ingest_uri(lxm_uri)
|
self.sideband.lxm_ingest_uri(lxm_uri)
|
||||||
@ -1064,6 +1197,9 @@ class SidebandApp(MDApp):
|
|||||||
self.quit_action(None)
|
self.quit_action(None)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def file_dropped(self, window, file_path, x, y, *args):
|
||||||
|
self.shared_attachment_action({"data_path": file_path.decode("utf-8")})
|
||||||
|
|
||||||
def on_start(self):
|
def on_start(self):
|
||||||
self.last_exit_event = time.time()
|
self.last_exit_event = time.time()
|
||||||
self.root.ids.screen_manager.transition = self.slide_transition
|
self.root.ids.screen_manager.transition = self.slide_transition
|
||||||
@ -1074,6 +1210,7 @@ class SidebandApp(MDApp):
|
|||||||
EventLoop.window.bind(on_key_down=self.keydown_event)
|
EventLoop.window.bind(on_key_down=self.keydown_event)
|
||||||
EventLoop.window.bind(on_key_up=self.keyup_event)
|
EventLoop.window.bind(on_key_up=self.keyup_event)
|
||||||
Window.bind(on_request_close=self.close_requested)
|
Window.bind(on_request_close=self.close_requested)
|
||||||
|
Window.bind(on_drop_file=self.file_dropped)
|
||||||
|
|
||||||
if __variant__ != "":
|
if __variant__ != "":
|
||||||
variant_str = " "+__variant__
|
variant_str = " "+__variant__
|
||||||
@ -1180,6 +1317,8 @@ class SidebandApp(MDApp):
|
|||||||
self.close_sub_telemetry_action()
|
self.close_sub_telemetry_action()
|
||||||
elif self.root.ids.screen_manager.current == "icons_screen":
|
elif self.root.ids.screen_manager.current == "icons_screen":
|
||||||
self.close_sub_telemetry_action()
|
self.close_sub_telemetry_action()
|
||||||
|
elif self.root.ids.screen_manager.current == "utilities_screen":
|
||||||
|
self.close_sub_utilities_action()
|
||||||
else:
|
else:
|
||||||
self.open_conversations(direction="right")
|
self.open_conversations(direction="right")
|
||||||
|
|
||||||
@ -1219,9 +1358,12 @@ class SidebandApp(MDApp):
|
|||||||
else:
|
else:
|
||||||
self.telemetry_action(self)
|
self.telemetry_action(self)
|
||||||
|
|
||||||
if text == "u":
|
if text == "y":
|
||||||
self.map_display_own_telemetry()
|
self.map_display_own_telemetry()
|
||||||
|
|
||||||
|
if text == "u":
|
||||||
|
self.utilities_action()
|
||||||
|
|
||||||
if text == "o":
|
if text == "o":
|
||||||
self.objects_action()
|
self.objects_action()
|
||||||
|
|
||||||
@ -1233,6 +1375,8 @@ class SidebandApp(MDApp):
|
|||||||
self.lxmf_sync_action(self)
|
self.lxmf_sync_action(self)
|
||||||
elif self.root.ids.screen_manager.current == "telemetry_screen":
|
elif self.root.ids.screen_manager.current == "telemetry_screen":
|
||||||
self.conversations_action(self, direction="right")
|
self.conversations_action(self, direction="right")
|
||||||
|
elif self.root.ids.screen_manager.current == "rnstatus_screen":
|
||||||
|
self.utilities_screen.update_rnstatus()
|
||||||
elif self.root.ids.screen_manager.current == "object_details_screen":
|
elif self.root.ids.screen_manager.current == "object_details_screen":
|
||||||
if not self.object_details_screen.object_hash == self.sideband.lxmf_destination.hash:
|
if not self.object_details_screen.object_hash == self.sideband.lxmf_destination.hash:
|
||||||
self.converse_from_telemetry(self)
|
self.converse_from_telemetry(self)
|
||||||
@ -1277,6 +1421,10 @@ class SidebandApp(MDApp):
|
|||||||
self.close_sub_telemetry_action()
|
self.close_sub_telemetry_action()
|
||||||
elif self.root.ids.screen_manager.current == "icons_screen":
|
elif self.root.ids.screen_manager.current == "icons_screen":
|
||||||
self.close_sub_telemetry_action()
|
self.close_sub_telemetry_action()
|
||||||
|
elif self.root.ids.screen_manager.current == "rnstatus_screen":
|
||||||
|
self.close_sub_utilities_action()
|
||||||
|
elif self.root.ids.screen_manager.current == "logviewer_screen":
|
||||||
|
self.close_sub_utilities_action()
|
||||||
else:
|
else:
|
||||||
self.open_conversations(direction="right")
|
self.open_conversations(direction="right")
|
||||||
|
|
||||||
@ -1679,7 +1827,8 @@ class SidebandApp(MDApp):
|
|||||||
|
|
||||||
def message_fm_exited(self, *args):
|
def message_fm_exited(self, *args):
|
||||||
self.manager_open = False
|
self.manager_open = False
|
||||||
self.file_manager.close()
|
if self.file_manager != None:
|
||||||
|
self.file_manager.close()
|
||||||
|
|
||||||
def message_select_file_action(self, sender=None):
|
def message_select_file_action(self, sender=None):
|
||||||
perm_ok = False
|
perm_ok = False
|
||||||
@ -1694,11 +1843,20 @@ class SidebandApp(MDApp):
|
|||||||
|
|
||||||
if perm_ok and path != None:
|
if perm_ok and path != None:
|
||||||
try:
|
try:
|
||||||
self.file_manager = MDFileManager(
|
if self.attach_type in ["lbimg", "defimg", "hqimg"]:
|
||||||
exit_manager=self.message_fm_exited,
|
self.file_manager = MDFileManager(
|
||||||
select_path=self.message_fm_got_path,
|
exit_manager=self.message_fm_exited,
|
||||||
)
|
select_path=self.message_fm_got_path,
|
||||||
# self.file_manager.ext = ["*"]
|
# Current KivyMD preview implementation is too slow to be reliable on Android
|
||||||
|
preview=False)
|
||||||
|
else:
|
||||||
|
self.file_manager = MDFileManager(
|
||||||
|
exit_manager=self.message_fm_exited,
|
||||||
|
select_path=self.message_fm_got_path,
|
||||||
|
preview=False)
|
||||||
|
|
||||||
|
# self.file_manager.ext = []
|
||||||
|
# self.file_manager.search = "all"
|
||||||
self.file_manager.show(path)
|
self.file_manager.show(path)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -2175,6 +2333,65 @@ class SidebandApp(MDApp):
|
|||||||
ate_dialog.open()
|
ate_dialog.open()
|
||||||
|
|
||||||
|
|
||||||
|
def shared_attachment_action(self, attachment_data):
|
||||||
|
if not self.root.ids.screen_manager.current == "messages_screen":
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
toast("Please select a conversation first")
|
||||||
|
else:
|
||||||
|
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||||
|
ate_dialog = MDDialog(
|
||||||
|
title="No active conversation",
|
||||||
|
text="To drop files as attachments, please open a conversation first",
|
||||||
|
buttons=[ ok_button ],
|
||||||
|
)
|
||||||
|
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||||
|
ate_dialog.open()
|
||||||
|
else:
|
||||||
|
self.rec_dialog_is_open = False
|
||||||
|
|
||||||
|
def a_img_lb(sender):
|
||||||
|
self.attach_type="lbimg"
|
||||||
|
self.shared_attach_dialog.dismiss()
|
||||||
|
self.shared_attach_dialog.att_exc()
|
||||||
|
def a_img_def(sender):
|
||||||
|
self.attach_type="defimg"
|
||||||
|
self.shared_attach_dialog.dismiss()
|
||||||
|
self.shared_attach_dialog.att_exc()
|
||||||
|
def a_img_hq(sender):
|
||||||
|
self.attach_type="hqimg"
|
||||||
|
self.shared_attach_dialog.dismiss()
|
||||||
|
self.shared_attach_dialog.att_exc()
|
||||||
|
def a_file(sender):
|
||||||
|
self.attach_type="file"
|
||||||
|
self.shared_attach_dialog.dismiss()
|
||||||
|
self.shared_attach_dialog.att_exc()
|
||||||
|
|
||||||
|
if self.shared_attach_dialog == None:
|
||||||
|
ss = int(dp(18))
|
||||||
|
cancel_button = MDRectangleFlatButton(text="Cancel", font_size=dp(18))
|
||||||
|
ad_items = [
|
||||||
|
DialogItem(IconLeftWidget(icon="message-image-outline", on_release=a_img_lb), text="[size="+str(ss)+"]Low-bandwidth Image[/size]", on_release=a_img_lb),
|
||||||
|
DialogItem(IconLeftWidget(icon="file-image", on_release=a_img_def), text="[size="+str(ss)+"]Medium Image[/size]", on_release=a_img_def),
|
||||||
|
DialogItem(IconLeftWidget(icon="image-outline", on_release=a_img_hq), text="[size="+str(ss)+"]High-res Image[/size]", on_release=a_img_hq),
|
||||||
|
DialogItem(IconLeftWidget(icon="file-outline", on_release=a_file), text="[size="+str(ss)+"]File Attachment[/size]", on_release=a_file)]
|
||||||
|
|
||||||
|
self.shared_attach_dialog = MDDialog(
|
||||||
|
title="Add Attachment",
|
||||||
|
type="simple",
|
||||||
|
text="Select how you want to attach this data to the next message sent\n",
|
||||||
|
items=ad_items,
|
||||||
|
buttons=[ cancel_button ],
|
||||||
|
width_offset=dp(32),
|
||||||
|
)
|
||||||
|
|
||||||
|
cancel_button.bind(on_release=self.shared_attach_dialog.dismiss)
|
||||||
|
|
||||||
|
def att_exc():
|
||||||
|
self.message_fm_got_path(attachment_data["data_path"])
|
||||||
|
|
||||||
|
self.shared_attach_dialog.att_exc = att_exc
|
||||||
|
self.shared_attach_dialog.open()
|
||||||
|
|
||||||
def update_message_widgets(self):
|
def update_message_widgets(self):
|
||||||
toolbar_items = self.messages_view.ids.messages_toolbar.ids.right_actions.children
|
toolbar_items = self.messages_view.ids.messages_toolbar.ids.right_actions.children
|
||||||
mode_item = toolbar_items[1]
|
mode_item = toolbar_items[1]
|
||||||
@ -2303,30 +2520,35 @@ class SidebandApp(MDApp):
|
|||||||
return "Could not retrieve connectivity status"
|
return "Could not retrieve connectivity status"
|
||||||
|
|
||||||
def connectivity_status(self, sender):
|
def connectivity_status(self, sender):
|
||||||
hs = dp(22)
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
hs = dp(22)
|
||||||
|
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||||
|
dialog = MDDialog(
|
||||||
|
title="Connectivity Status",
|
||||||
|
text=str(self.get_connectivity_text()),
|
||||||
|
buttons=[ yes_button ],
|
||||||
|
# elevation=0,
|
||||||
|
)
|
||||||
|
def cs_updater(dt):
|
||||||
|
dialog.text = str(self.get_connectivity_text())
|
||||||
|
def dl_yes(s):
|
||||||
|
self.connectivity_updater.cancel()
|
||||||
|
dialog.dismiss()
|
||||||
|
if self.connectivity_updater != None:
|
||||||
|
self.connectivity_updater.cancel()
|
||||||
|
|
||||||
|
yes_button.bind(on_release=dl_yes)
|
||||||
|
dialog.open()
|
||||||
|
|
||||||
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
|
||||||
dialog = MDDialog(
|
|
||||||
title="Connectivity Status",
|
|
||||||
text=str(self.get_connectivity_text()),
|
|
||||||
buttons=[ yes_button ],
|
|
||||||
# elevation=0,
|
|
||||||
)
|
|
||||||
def cs_updater(dt):
|
|
||||||
dialog.text = str(self.get_connectivity_text())
|
|
||||||
def dl_yes(s):
|
|
||||||
self.connectivity_updater.cancel()
|
|
||||||
dialog.dismiss()
|
|
||||||
if self.connectivity_updater != None:
|
if self.connectivity_updater != None:
|
||||||
self.connectivity_updater.cancel()
|
self.connectivity_updater.cancel()
|
||||||
|
|
||||||
yes_button.bind(on_release=dl_yes)
|
self.connectivity_updater = Clock.schedule_interval(cs_updater, 2.0)
|
||||||
dialog.open()
|
|
||||||
|
|
||||||
if self.connectivity_updater != None:
|
else:
|
||||||
self.connectivity_updater.cancel()
|
if not self.utilities_ready:
|
||||||
|
self.utilities_init()
|
||||||
self.connectivity_updater = Clock.schedule_interval(cs_updater, 2.0)
|
self.utilities_screen.rnstatus_action()
|
||||||
|
|
||||||
def ingest_lxm_action(self, sender):
|
def ingest_lxm_action(self, sender):
|
||||||
def cb(dt):
|
def cb(dt):
|
||||||
@ -2400,13 +2622,14 @@ class SidebandApp(MDApp):
|
|||||||
else:
|
else:
|
||||||
sl = None
|
sl = None
|
||||||
|
|
||||||
|
sync_title = "LXMF Sync"
|
||||||
if not hasattr(self, "message_sync_dialog") or self.message_sync_dialog == None:
|
if not hasattr(self, "message_sync_dialog") or self.message_sync_dialog == None:
|
||||||
close_button = MDRectangleFlatButton(text="Close",font_size=dp(18))
|
close_button = MDRectangleFlatButton(text="Close",font_size=dp(18))
|
||||||
stop_button = MDRectangleFlatButton(text="Stop",font_size=dp(18), theme_text_color="Custom", line_color=self.color_reject, text_color=self.color_reject)
|
stop_button = MDRectangleFlatButton(text="Stop",font_size=dp(18), theme_text_color="Custom", line_color=self.color_reject, text_color=self.color_reject)
|
||||||
|
|
||||||
dialog_content = MsgSync()
|
dialog_content = MsgSync()
|
||||||
dialog = MDDialog(
|
dialog = MDDialog(
|
||||||
title="LXMF Sync via "+RNS.prettyhexrep(self.sideband.message_router.get_outbound_propagation_node()),
|
title=sync_title,
|
||||||
type="custom",
|
type="custom",
|
||||||
content_cls=dialog_content,
|
content_cls=dialog_content,
|
||||||
buttons=[ stop_button, close_button ],
|
buttons=[ stop_button, close_button ],
|
||||||
@ -2443,7 +2666,8 @@ class SidebandApp(MDApp):
|
|||||||
dsp = 0
|
dsp = 0
|
||||||
|
|
||||||
self.sideband.setstate("app.flags.lxmf_sync_dialog_open", True)
|
self.sideband.setstate("app.flags.lxmf_sync_dialog_open", True)
|
||||||
self.message_sync_dialog.title = f"LXMF Sync via "+RNS.prettyhexrep(self.sideband.message_router.get_outbound_propagation_node())
|
self.message_sync_dialog.title = sync_title
|
||||||
|
self.message_sync_dialog.d_content.ids.node_info.text = f"Via {RNS.prettyhexrep(self.sideband.message_router.get_outbound_propagation_node())}\n"
|
||||||
self.message_sync_dialog.d_content.ids.sync_status.text = self.sideband.get_sync_status()
|
self.message_sync_dialog.d_content.ids.sync_status.text = self.sideband.get_sync_status()
|
||||||
self.message_sync_dialog.d_content.ids.sync_progress.value = dsp
|
self.message_sync_dialog.d_content.ids.sync_progress.value = dsp
|
||||||
self.message_sync_dialog.d_content.ids.sync_progress.start()
|
self.message_sync_dialog.d_content.ids.sync_progress.start()
|
||||||
@ -2487,11 +2711,15 @@ class SidebandApp(MDApp):
|
|||||||
RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
if new_result:
|
if new_result:
|
||||||
|
dialog.d_content.ids["n_address_field"].helper_text = ""
|
||||||
|
dialog.d_content.ids["n_address_field"].helper_text_mode = "on_focus"
|
||||||
dialog.d_content.ids["n_address_field"].error = False
|
dialog.d_content.ids["n_address_field"].error = False
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
if self.conversations_view != None:
|
if self.conversations_view != None:
|
||||||
self.conversations_view.update()
|
self.conversations_view.update()
|
||||||
else:
|
else:
|
||||||
|
dialog.d_content.ids["n_address_field"].helper_text = "Invalid address, check your input"
|
||||||
|
dialog.d_content.ids["n_address_field"].helper_text_mode = "persistent"
|
||||||
dialog.d_content.ids["n_address_field"].error = True
|
dialog.d_content.ids["n_address_field"].error = True
|
||||||
# dialog.d_content.ids["n_error_field"].text = "Could not create conversation. Check your input."
|
# dialog.d_content.ids["n_error_field"].text = "Could not create conversation. Check your input."
|
||||||
|
|
||||||
@ -2585,6 +2813,72 @@ class SidebandApp(MDApp):
|
|||||||
if no_transition:
|
if no_transition:
|
||||||
self.root.ids.screen_manager.transition = self.slide_transition
|
self.root.ids.screen_manager.transition = self.slide_transition
|
||||||
|
|
||||||
|
def configure_ui_scaling_action(self, sender=None):
|
||||||
|
global app_ui_scaling_path
|
||||||
|
try:
|
||||||
|
cancel_button = MDRectangleFlatButton(text="Cancel",font_size=dp(18))
|
||||||
|
set_button = MDRectangleFlatButton(text="Set",font_size=dp(18), theme_text_color="Custom", line_color=self.color_accept, text_color=self.color_accept)
|
||||||
|
|
||||||
|
dialog_content = UIScaling()
|
||||||
|
dialog = MDDialog(
|
||||||
|
title="UI Scaling",
|
||||||
|
type="custom",
|
||||||
|
content_cls=dialog_content,
|
||||||
|
buttons=[ set_button, cancel_button ],
|
||||||
|
# elevation=0,
|
||||||
|
)
|
||||||
|
dialog.d_content = dialog_content
|
||||||
|
dialog.d_content.ids["scaling_factor"].text = os.environ["KIVY_METRICS_DENSITY"] if "KIVY_METRICS_DENSITY" in os.environ else "0.0"
|
||||||
|
def dl_yes(s):
|
||||||
|
new_sf = 1.0
|
||||||
|
scaling_ok = False
|
||||||
|
try:
|
||||||
|
si = dialog.d_content.ids["scaling_factor"].text
|
||||||
|
sf = float(si)
|
||||||
|
if (sf >= 0.3 and sf <= 5.0) or sf == 0.0:
|
||||||
|
new_sf = sf
|
||||||
|
scaling_ok = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while getting scaling factor from user: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
if scaling_ok:
|
||||||
|
dialog.d_content.ids["scaling_factor"].helper_text = ""
|
||||||
|
dialog.d_content.ids["scaling_factor"].helper_text_mode = "on_focus"
|
||||||
|
dialog.d_content.ids["scaling_factor"].error = False
|
||||||
|
dialog.dismiss()
|
||||||
|
if app_ui_scaling_path == None:
|
||||||
|
RNS.log("No path to UI scaling factor file could be found, cannot save scaling factor", RNS.LOG_ERROR)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(app_ui_scaling_path, "w") as sfile:
|
||||||
|
sfile.write(str(new_sf))
|
||||||
|
RNS.log(f"Saved configured scaling factor {new_sf} to {app_ui_scaling_path}", RNS.LOG_DEBUG)
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while saving scaling factor {new_sf} to {app_ui_scaling_path}: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
else:
|
||||||
|
dialog.d_content.ids["scaling_factor"].helper_text = "Invalid scale factor, check your input"
|
||||||
|
dialog.d_content.ids["scaling_factor"].helper_text_mode = "persistent"
|
||||||
|
dialog.d_content.ids["scaling_factor"].error = True
|
||||||
|
|
||||||
|
def dl_no(s):
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
def dl_ds(s):
|
||||||
|
self.dialog_open = False
|
||||||
|
|
||||||
|
set_button.bind(on_release=dl_yes)
|
||||||
|
cancel_button.bind(on_release=dl_no)
|
||||||
|
|
||||||
|
dialog.bind(on_dismiss=dl_ds)
|
||||||
|
dialog.open()
|
||||||
|
self.dialog_open = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while creating UI scaling dialog: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
|
||||||
def settings_action(self, sender=None, direction="left"):
|
def settings_action(self, sender=None, direction="left"):
|
||||||
if self.settings_ready:
|
if self.settings_ready:
|
||||||
self.settings_open(direction=direction)
|
self.settings_open(direction=direction)
|
||||||
@ -2739,6 +3033,11 @@ class SidebandApp(MDApp):
|
|||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
self.update_ui_theme()
|
self.update_ui_theme()
|
||||||
|
|
||||||
|
def save_classic_message_colors(sender=None, event=None):
|
||||||
|
self.sideband.config["classic_message_colors"] = self.settings_screen.ids.settings_classic_message_colors.active
|
||||||
|
self.sideband.save_configuration()
|
||||||
|
self.update_ui_theme()
|
||||||
|
|
||||||
def save_display_style_in_contact_list(sender=None, event=None):
|
def save_display_style_in_contact_list(sender=None, event=None):
|
||||||
self.sideband.config["display_style_in_contact_list"] = self.settings_screen.ids.display_style_in_contact_list.active
|
self.sideband.config["display_style_in_contact_list"] = self.settings_screen.ids.display_style_in_contact_list.active
|
||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
@ -2749,6 +3048,10 @@ class SidebandApp(MDApp):
|
|||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
self.sideband.setstate("wants.viewupdate.conversations", True)
|
self.sideband.setstate("wants.viewupdate.conversations", True)
|
||||||
|
|
||||||
|
def save_trusted_markup_only(sender=None, event=None):
|
||||||
|
self.sideband.config["trusted_markup_only"] = self.settings_screen.ids.settings_trusted_markup_only.active
|
||||||
|
self.sideband.save_configuration()
|
||||||
|
|
||||||
def save_advanced_stats(sender=None, event=None):
|
def save_advanced_stats(sender=None, event=None):
|
||||||
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
@ -2900,6 +3203,9 @@ class SidebandApp(MDApp):
|
|||||||
self.settings_screen.ids.settings_eink_mode.active = self.sideband.config["eink_mode"]
|
self.settings_screen.ids.settings_eink_mode.active = self.sideband.config["eink_mode"]
|
||||||
self.settings_screen.ids.settings_eink_mode.bind(active=save_eink_mode)
|
self.settings_screen.ids.settings_eink_mode.bind(active=save_eink_mode)
|
||||||
|
|
||||||
|
self.settings_screen.ids.settings_classic_message_colors.active = self.sideband.config["classic_message_colors"]
|
||||||
|
self.settings_screen.ids.settings_classic_message_colors.bind(active=save_classic_message_colors)
|
||||||
|
|
||||||
self.settings_screen.ids.display_style_in_contact_list.active = self.sideband.config["display_style_in_contact_list"]
|
self.settings_screen.ids.display_style_in_contact_list.active = self.sideband.config["display_style_in_contact_list"]
|
||||||
self.settings_screen.ids.display_style_in_contact_list.bind(active=save_display_style_in_contact_list)
|
self.settings_screen.ids.display_style_in_contact_list.bind(active=save_display_style_in_contact_list)
|
||||||
|
|
||||||
@ -2921,6 +3227,9 @@ class SidebandApp(MDApp):
|
|||||||
self.settings_screen.ids.settings_lxmf_ignore_unknown.active = self.sideband.config["lxmf_ignore_unknown"]
|
self.settings_screen.ids.settings_lxmf_ignore_unknown.active = self.sideband.config["lxmf_ignore_unknown"]
|
||||||
self.settings_screen.ids.settings_lxmf_ignore_unknown.bind(active=save_lxmf_ignore_unknown)
|
self.settings_screen.ids.settings_lxmf_ignore_unknown.bind(active=save_lxmf_ignore_unknown)
|
||||||
|
|
||||||
|
self.settings_screen.ids.settings_trusted_markup_only.active = self.sideband.config["trusted_markup_only"]
|
||||||
|
self.settings_screen.ids.settings_trusted_markup_only.bind(active=save_trusted_markup_only)
|
||||||
|
|
||||||
self.settings_screen.ids.settings_ignore_invalid_stamps.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
|
self.settings_screen.ids.settings_ignore_invalid_stamps.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
|
||||||
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_lxmf_ignore_invalid_stamps)
|
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_lxmf_ignore_invalid_stamps)
|
||||||
|
|
||||||
@ -3369,6 +3678,7 @@ class SidebandApp(MDApp):
|
|||||||
######################################
|
######################################
|
||||||
def repository_action(self, sender=None, direction="left"):
|
def repository_action(self, sender=None, direction="left"):
|
||||||
if self.repository_ready:
|
if self.repository_ready:
|
||||||
|
self.repository_update_info()
|
||||||
self.repository_open(direction=direction)
|
self.repository_open(direction=direction)
|
||||||
else:
|
else:
|
||||||
self.loader_action(direction=direction)
|
self.loader_action(direction=direction)
|
||||||
@ -3405,9 +3715,9 @@ class SidebandApp(MDApp):
|
|||||||
info += "If you want to share the openCom Companion application itself via the repository server, you must first download it into the local repository, using the \"Update Content\" button below.\n\n"
|
info += "If you want to share the openCom Companion application itself via the repository server, you must first download it into the local repository, using the \"Update Content\" button below.\n\n"
|
||||||
info += "To make the repository available on your local network, simply start it below, and it will become browsable on a local IP address for anyone connected to the same WiFi or wired network.\n\n"
|
info += "To make the repository available on your local network, simply start it below, and it will become browsable on a local IP address for anyone connected to the same WiFi or wired network.\n\n"
|
||||||
if self.sideband.webshare_server != None:
|
if self.sideband.webshare_server != None:
|
||||||
if RNS.vendor.platformutils.is_android():
|
def getIP():
|
||||||
def getIP():
|
adrs = []
|
||||||
adrs = []
|
if RNS.vendor.platformutils.is_android():
|
||||||
try:
|
try:
|
||||||
from jnius import autoclass
|
from jnius import autoclass
|
||||||
import ipaddress
|
import ipaddress
|
||||||
@ -3430,24 +3740,30 @@ class SidebandApp(MDApp):
|
|||||||
RNS.log("Error while getting repository IP address: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while getting repository IP address: "+str(e), RNS.LOG_ERROR)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return adrs
|
|
||||||
|
|
||||||
ips = getIP()
|
|
||||||
if ips == None or len(ips) == 0:
|
|
||||||
info += "The repository server is running, but the local device IP address could not be determined.\n\nYou can access the repository by pointing a browser to: http://DEVICE_IP:4444/"
|
|
||||||
self.reposository_url = None
|
|
||||||
else:
|
else:
|
||||||
ipstr = ""
|
import socket
|
||||||
for ip in ips:
|
adrs.append(socket.gethostbyname(socket.gethostname()))
|
||||||
ipstr += "http://"+str(ip)+":4444/\n"
|
|
||||||
self.reposository_url = ipstr
|
|
||||||
|
|
||||||
ms = "" if len(ips) == 1 else "es"
|
return adrs
|
||||||
info += "The repository server is running at the following address"+ms+":\n [u][ref=link]"+ipstr+"[/ref][u]"
|
|
||||||
self.repository_screen.ids.repository_info.bind(on_ref_press=self.repository_link_action)
|
|
||||||
|
|
||||||
self.repository_screen.ids.repository_enable_button.disabled = True
|
ips = getIP()
|
||||||
self.repository_screen.ids.repository_disable_button.disabled = False
|
if ips == None or len(ips) == 0:
|
||||||
|
info += "The repository server is running, but the local device IP address could not be determined.\n\nYou can access the repository by pointing a browser to: https://DEVICE_IP:4444/"
|
||||||
|
self.reposository_url = None
|
||||||
|
else:
|
||||||
|
ipstr = ""
|
||||||
|
for ip in ips:
|
||||||
|
ipstr += "https://"+str(ip)+":4444/\n"
|
||||||
|
self.reposository_url = ipstr
|
||||||
|
|
||||||
|
ms = "" if len(ips) == 1 else "es"
|
||||||
|
info += "The repository server is running at the following address"+ms+":\n [u][ref=link]"+ipstr+"[/ref][u]"
|
||||||
|
self.repository_screen.ids.repository_info.bind(on_ref_press=self.repository_link_action)
|
||||||
|
|
||||||
|
def cb(dt):
|
||||||
|
self.repository_screen.ids.repository_enable_button.disabled = True
|
||||||
|
self.repository_screen.ids.repository_disable_button.disabled = False
|
||||||
|
Clock.schedule_once(cb, 0.1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.repository_screen.ids.repository_enable_button.disabled = False
|
self.repository_screen.ids.repository_enable_button.disabled = False
|
||||||
@ -3469,39 +3785,85 @@ class SidebandApp(MDApp):
|
|||||||
def update_job(sender=None):
|
def update_job(sender=None):
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
|
### RNode Firmwares ###########
|
||||||
|
if True:
|
||||||
|
downloads = []
|
||||||
|
try:
|
||||||
|
release_url = "https://api.github.com/repos/markqvist/rnode_firmware/releases"
|
||||||
|
with requests.get(release_url) as response:
|
||||||
|
releases = response.json()
|
||||||
|
release = releases[0]
|
||||||
|
assets = release["assets"]
|
||||||
|
for asset in assets:
|
||||||
|
if asset["name"].lower().startswith("rnode_firmware"):
|
||||||
|
fw_url = asset["browser_download_url"]
|
||||||
|
pkgname = asset["name"]
|
||||||
|
fw_version = release["tag_name"]
|
||||||
|
RNS.log(f"Found version {fw_version} artefact {pkgname} at {fw_url}", RNS.LOG_DEBUG)
|
||||||
|
downloads.append([fw_url, pkgname, fw_version])
|
||||||
|
|
||||||
# Get release info
|
except Exception as e:
|
||||||
apk_version = None
|
self.repository_screen.ids.repository_update.text = f"Downloading RNode firmware release info failed with the error:\n"+str(e)
|
||||||
apk_url = None
|
return
|
||||||
pkgname = None
|
|
||||||
try:
|
|
||||||
release_url = "https://api.github.com/repos/markqvist/sideband/releases"
|
|
||||||
with requests.get(release_url) as response:
|
|
||||||
releases = response.json()
|
|
||||||
release = releases[0]
|
|
||||||
assets = release["assets"]
|
|
||||||
for asset in assets:
|
|
||||||
if asset["name"].lower().endswith(".apk"):
|
|
||||||
apk_url = asset["browser_download_url"]
|
|
||||||
pkgname = asset["name"]
|
|
||||||
apk_version = release["tag_name"]
|
|
||||||
RNS.log(f"Found version {apk_version} artefact {pkgname} at {apk_url}")
|
|
||||||
except Exception as e:
|
|
||||||
self.repository_screen.ids.repository_update.text = f"Downloading release info failed with the error:\n"+str(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.repository_screen.ids.repository_update.text = "Downloading: "+str(apk_url)
|
try:
|
||||||
with requests.get(apk_url, stream=True) as response:
|
for download in downloads:
|
||||||
with open("./dl_tmp", "wb") as tmp_file:
|
fw_url = download[0]
|
||||||
cs = 32*1024
|
pkgname = download[1]
|
||||||
tds = 0
|
self.repository_screen.ids.repository_update.text = "Downloading: "+str(pkgname)
|
||||||
for chunk in response.iter_content(chunk_size=cs):
|
with requests.get(fw_url, stream=True) as response:
|
||||||
tmp_file.write(chunk)
|
with open("./dl_tmp", "wb") as tmp_file:
|
||||||
tds += cs
|
cs = 32*1024
|
||||||
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
tds = 0
|
||||||
|
for chunk in response.iter_content(chunk_size=cs):
|
||||||
|
tmp_file.write(chunk)
|
||||||
|
tds += cs
|
||||||
|
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
||||||
|
|
||||||
|
os.rename("./dl_tmp", f"{self.sideband.webshare_dir}/pkg/{pkgname}")
|
||||||
|
self.repository_screen.ids.repository_update.text = f"Added {pkgname} to the repository!"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.repository_screen.ids.repository_update.text = f"Downloading RNode firmware failed with the error:\n"+str(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
### Sideband APK File #########
|
||||||
|
if True:
|
||||||
|
# Get release info
|
||||||
|
apk_version = None
|
||||||
|
apk_url = None
|
||||||
|
pkgname = None
|
||||||
|
try:
|
||||||
|
release_url = "https://api.github.com/repos/markqvist/sideband/releases"
|
||||||
|
with requests.get(release_url) as response:
|
||||||
|
releases = response.json()
|
||||||
|
release = releases[0]
|
||||||
|
assets = release["assets"]
|
||||||
|
for asset in assets:
|
||||||
|
if asset["name"].lower().endswith(".apk"):
|
||||||
|
apk_url = asset["browser_download_url"]
|
||||||
|
pkgname = asset["name"]
|
||||||
|
apk_version = release["tag_name"]
|
||||||
|
RNS.log(f"Found version {apk_version} artefact {pkgname} at {apk_url}", RNS.LOG_DEBUG)
|
||||||
|
except Exception as e:
|
||||||
|
self.repository_screen.ids.repository_update.text = f"Downloading Sideband APK release info failed with the error:\n"+str(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.repository_screen.ids.repository_update.text = "Downloading: "+str(pkgname)
|
||||||
|
with requests.get(apk_url, stream=True) as response:
|
||||||
|
with open("./dl_tmp", "wb") as tmp_file:
|
||||||
|
cs = 32*1024
|
||||||
|
tds = 0
|
||||||
|
for chunk in response.iter_content(chunk_size=cs):
|
||||||
|
tmp_file.write(chunk)
|
||||||
|
tds += cs
|
||||||
|
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
||||||
|
|
||||||
|
os.rename("./dl_tmp", f"{self.sideband.webshare_dir}/pkg/{pkgname}")
|
||||||
|
self.repository_screen.ids.repository_update.text = f"Added {pkgname} to the repository!"
|
||||||
|
|
||||||
|
self.repository_screen.ids.repository_update.text = f"Repository contents updated successfully!"
|
||||||
|
|
||||||
os.rename("./dl_tmp", f"./share/pkg/{pkgname}")
|
|
||||||
self.repository_screen.ids.repository_update.text = f"Added {pkgname} to the repository!"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.repository_screen.ids.repository_update.text = f"Downloading contents failed with the error:\n"+str(e)
|
self.repository_screen.ids.repository_update.text = f"Downloading contents failed with the error:\n"+str(e)
|
||||||
|
|
||||||
@ -3518,15 +3880,7 @@ class SidebandApp(MDApp):
|
|||||||
self.root.ids.screen_manager.add_widget(self.repository_screen)
|
self.root.ids.screen_manager.add_widget(self.repository_screen)
|
||||||
|
|
||||||
self.repository_screen.ids.repository_scrollview.effect_cls = ScrollEffect
|
self.repository_screen.ids.repository_scrollview.effect_cls = ScrollEffect
|
||||||
|
|
||||||
self.repository_update_info()
|
self.repository_update_info()
|
||||||
|
|
||||||
if not RNS.vendor.platformutils.is_android():
|
|
||||||
self.widget_hide(self.repository_screen.ids.repository_enable_button)
|
|
||||||
self.widget_hide(self.repository_screen.ids.repository_disable_button)
|
|
||||||
self.widget_hide(self.repository_screen.ids.repository_download_button)
|
|
||||||
self.repository_screen.ids.repository_info.text = "\nThe [b]Repository Webserver[/b] feature is currently only available on mobile devices."
|
|
||||||
|
|
||||||
self.repository_ready = True
|
self.repository_ready = True
|
||||||
|
|
||||||
def close_repository_action(self, sender=None):
|
def close_repository_action(self, sender=None):
|
||||||
@ -4096,14 +4450,14 @@ class SidebandApp(MDApp):
|
|||||||
|
|
||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
|
|
||||||
def hardware_rnode_ble_toggle_action(self, sender=None, event=None):
|
#def hardware_rnode_ble_toggle_action(self, sender=None, event=None):
|
||||||
if sender.active:
|
# if sender.active:
|
||||||
self.sideband.config["hw_rnode_ble"] = True
|
# self.sideband.config["hw_rnode_ble"] = True
|
||||||
self.request_bluetooth_permissions()
|
# self.request_bluetooth_permissions()
|
||||||
else:
|
# else:
|
||||||
self.sideband.config["hw_rnode_ble"] = False
|
# self.sideband.config["hw_rnode_ble"] = False
|
||||||
|
|
||||||
self.sideband.save_configuration()
|
# self.sideband.save_configuration()
|
||||||
|
|
||||||
def hardware_rnode_framebuffer_toggle_action(self, sender=None, event=None):
|
def hardware_rnode_framebuffer_toggle_action(self, sender=None, event=None):
|
||||||
if sender.active:
|
if sender.active:
|
||||||
@ -4210,7 +4564,7 @@ class SidebandApp(MDApp):
|
|||||||
t_btd = ""
|
t_btd = ""
|
||||||
|
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.active = self.sideband.config["hw_rnode_bluetooth"]
|
self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.active = self.sideband.config["hw_rnode_bluetooth"]
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_ble.active = self.sideband.config["hw_rnode_ble"]
|
#self.hardware_rnode_screen.ids.hardware_rnode_ble.active = self.sideband.config["hw_rnode_ble"]
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.active = self.sideband.config["hw_rnode_enable_framebuffer"]
|
self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.active = self.sideband.config["hw_rnode_enable_framebuffer"]
|
||||||
|
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.active = self.sideband.config["hw_rnode_advanced_cfg"]
|
self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.active = self.sideband.config["hw_rnode_advanced_cfg"]
|
||||||
@ -4277,7 +4631,7 @@ class SidebandApp(MDApp):
|
|||||||
self.hardware_rnode_screen.ids.hardware_rnode_beaconinterval.bind(on_text_validate=save_connectivity)
|
self.hardware_rnode_screen.ids.hardware_rnode_beaconinterval.bind(on_text_validate=save_connectivity)
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_beacondata.bind(on_text_validate=save_connectivity)
|
self.hardware_rnode_screen.ids.hardware_rnode_beacondata.bind(on_text_validate=save_connectivity)
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.bind(active=self.hardware_rnode_bt_toggle_action)
|
self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.bind(active=self.hardware_rnode_bt_toggle_action)
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_ble.bind(active=self.hardware_rnode_ble_toggle_action)
|
#self.hardware_rnode_screen.ids.hardware_rnode_ble.bind(active=self.hardware_rnode_ble_toggle_action)
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.bind(active=self.hardware_rnode_framebuffer_toggle_action)
|
self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.bind(active=self.hardware_rnode_framebuffer_toggle_action)
|
||||||
|
|
||||||
self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.bind(active=self.hardware_rnode_advanced_cfg_toggle_action)
|
self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.bind(active=self.hardware_rnode_advanced_cfg_toggle_action)
|
||||||
@ -5280,6 +5634,44 @@ class SidebandApp(MDApp):
|
|||||||
ate_dialog.open()
|
ate_dialog.open()
|
||||||
|
|
||||||
|
|
||||||
|
### Utilities Screen
|
||||||
|
######################################
|
||||||
|
|
||||||
|
def utilities_init(self):
|
||||||
|
if not self.utilities_ready:
|
||||||
|
self.utilities_screen = Utilities(self)
|
||||||
|
self.utilities_ready = True
|
||||||
|
|
||||||
|
def utilities_open(self, sender=None, direction="left", no_transition=False):
|
||||||
|
if no_transition:
|
||||||
|
self.root.ids.screen_manager.transition = self.no_transition
|
||||||
|
else:
|
||||||
|
self.root.ids.screen_manager.transition = self.slide_transition
|
||||||
|
self.root.ids.screen_manager.transition.direction = direction
|
||||||
|
|
||||||
|
self.root.ids.screen_manager.current = "utilities_screen"
|
||||||
|
self.root.ids.nav_drawer.set_state("closed")
|
||||||
|
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
|
||||||
|
|
||||||
|
if no_transition:
|
||||||
|
self.root.ids.screen_manager.transition = self.slide_transition
|
||||||
|
|
||||||
|
def utilities_action(self, sender=None, direction="left"):
|
||||||
|
if self.utilities_ready:
|
||||||
|
self.utilities_open(direction=direction)
|
||||||
|
else:
|
||||||
|
self.loader_action(direction=direction)
|
||||||
|
def final(dt):
|
||||||
|
self.utilities_init()
|
||||||
|
def o(dt):
|
||||||
|
self.utilities_open(no_transition=True)
|
||||||
|
Clock.schedule_once(o, ll_ot)
|
||||||
|
Clock.schedule_once(final, ll_ft)
|
||||||
|
|
||||||
|
def close_sub_utilities_action(self, sender=None):
|
||||||
|
self.utilities_action(direction="right")
|
||||||
|
|
||||||
|
|
||||||
### Telemetry Screen
|
### Telemetry Screen
|
||||||
######################################
|
######################################
|
||||||
|
|
||||||
@ -6275,6 +6667,9 @@ class DialogItem(OneLineIconListItem):
|
|||||||
class MDMapIconButton(MDIconButton):
|
class MDMapIconButton(MDIconButton):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class UIScaling(BoxLayout):
|
||||||
|
pass
|
||||||
|
|
||||||
if not args.daemon:
|
if not args.daemon:
|
||||||
from kivy.base import ExceptionManager, ExceptionHandler
|
from kivy.base import ExceptionManager, ExceptionHandler
|
||||||
class SidebandExceptionHandler(ExceptionHandler):
|
class SidebandExceptionHandler(ExceptionHandler):
|
||||||
@ -6310,3 +6705,6 @@ def run():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run()
|
run()
|
||||||
|
|
||||||
|
if __name__ == "sbapp.main":
|
||||||
|
run()
|
||||||
|
@ -13,11 +13,22 @@
|
|||||||
|
|
||||||
<!-- This intent filter allows opening scanned LXM URLs directly in Sideband -->
|
<!-- This intent filter allows opening scanned LXM URLs directly in Sideband -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.WEB_SEARCH" />
|
<action android:name="android.intent.action.WEB_SEARCH" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||||
android:resource="@xml/device_filter" />
|
android:resource="@xml/device_filter" />
|
33
sbapp/share/flasher.html
Normal file
33
sbapp/share/flasher.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="css/water.css">
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="gfx/icon.png">
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Sideband Repository</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="logo">RNode Flasher</span>
|
||||||
|
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> | <a href="guides.html">Guides</a></span></center></p><hr>
|
||||||
|
<br/>Sideband includes a copy of the web-based RNode Flasher developed by <a href="https://github.com/liamcottle/rnode-flasher">Liam Cottle</a>. You can use this flasher to install and provision the RNode firmware on any compatible boards.<br/>
|
||||||
|
<br/>
|
||||||
|
<b>Please note!</b> Your browser must support Web-USB for this to work.<br/>
|
||||||
|
<br/>
|
||||||
|
To use the flasher, you will need firmware packages for the boards you want use. You can obtain these in different ways:
|
||||||
|
<ul>
|
||||||
|
<li>Sideband can automatically download the latest release of these, and include them directly in this repository.
|
||||||
|
<ul>
|
||||||
|
<li>To do so, go to the <b>Repository</b> menu item, and select <b>Update Contents</b>.</li>
|
||||||
|
<li>After that, they will be available on the <a href="pkgs.html">Software</a> page of this repository.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>You can download them manually from <a href="https://github.com/markqvist/RNode_Firmware/releases/latest">the latest release page on GitHub</a>.</li>
|
||||||
|
<li>You can compile them yourself from the RNode Firmware source code package <a href="pkgs.html">included in this repository</a>.</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
<center><a href="/mirrors/rnode-flasher/index.html"><button type="button" id="task-replicate">Launch Web Flasher</button></a></center>
|
||||||
|
<br/>
|
||||||
|
<hr>
|
||||||
|
<p><center></p>
|
||||||
|
</body></html>
|
@ -9,7 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<span class="logo">Guides</span>
|
<span class="logo">Guides</span>
|
||||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="guides.html">Guides</a></span></center></p><hr>
|
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> | <a href="guides.html">Guides</a></span></center></p><hr><br/>
|
||||||
Welcome to the <b>Guide Section</b>!<br/><br/>From here, you can browse or download various included manuals, documentation, references and guides.
|
Welcome to the <b>Guide Section</b>!<br/><br/>From here, you can browse or download various included manuals, documentation, references and guides.
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
@ -19,6 +19,7 @@ Welcome to the <b>Guide Section</b>!<br/><br/>From here, you can browse or downl
|
|||||||
<li><a href="./mirrors/Reticulum_Manual.epub">Download the Reticulum Manual in EPUB format</a></li>
|
<li><a href="./mirrors/Reticulum_Manual.epub">Download the Reticulum Manual in EPUB format</a></li>
|
||||||
<li><a href="./mirrors/reticulum.network/index.html">Browse a local copy of the Reticulum website</a></li>
|
<li><a href="./mirrors/reticulum.network/index.html">Browse a local copy of the Reticulum website</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<br/>
|
||||||
<hr>
|
<hr>
|
||||||
<p><center></p>
|
<p><center></p>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<<<<<<< HEAD
|
||||||
<span class="logo">openCom Companion Repo</span>
|
<span class="logo">openCom Companion Repo</span>
|
||||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="guides.html">Guides</a></span></center></p><hr><h2>Hello!</h2>
|
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> |<a href="guides.html">Guides</a></span></center></p><hr><h2>Hello!</h2>
|
||||||
<table style="margin-bottom: 1.5em;">
|
<table style="margin-bottom: 1.5em;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<span class="logo">Software</span>
|
<span class="logo">Software</span>
|
||||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="guides.html">Guides</a></span></center></p><hr>
|
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> | <a href="guides.html">Guides</a></span></center></p><hr><br/>
|
||||||
Welcome to the <b>Software Library</b>!<br/><br/>From here, you can download installable Python Wheel packages of Reticulum and various other auxillary programs and utilities.
|
Welcome to the <b>Software Library</b>!<br/><br/>From here, you can download installable Python Wheel packages of Reticulum and various other auxillary programs and utilities.
|
||||||
<ul id="filelist">
|
<ul id="filelist">
|
||||||
</ul>
|
</ul><br/>
|
||||||
<hr>
|
<hr>
|
||||||
<p><center></p>
|
<p><center></p>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
101
sbapp/sideband/certgen.py
Normal file
101
sbapp/sideband/certgen.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024 Mark Qvist / unsigned.io.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
KEY_PASSPHRASE = None
|
||||||
|
LOADED_KEY = None
|
||||||
|
|
||||||
|
import os
|
||||||
|
import RNS
|
||||||
|
import datetime
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.x509.oid import NameOID
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||||
|
|
||||||
|
from cryptography import __version__ as cryptography_version_str
|
||||||
|
try:
|
||||||
|
cryptography_major_version = int(cryptography_version_str.split(".")[0])
|
||||||
|
except:
|
||||||
|
RNS.log(f"Could not determine PyCA/cryptography version: {e}", RNS.LOG_ERROR)
|
||||||
|
RNS.log(f"Assuming recent version with automatic backend selection", RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
def get_key(key_path, force_reload=False):
|
||||||
|
KEY_PATH = key_path
|
||||||
|
key = None
|
||||||
|
if LOADED_KEY != None and not force_reload:
|
||||||
|
return LOADED_KEY
|
||||||
|
elif os.path.isfile(KEY_PATH):
|
||||||
|
with open(KEY_PATH, "rb") as f:
|
||||||
|
key = load_pem_private_key(f.read(), KEY_PASSPHRASE)
|
||||||
|
else:
|
||||||
|
if cryptography_major_version > 3:
|
||||||
|
key = ec.generate_private_key(curve=ec.SECP256R1())
|
||||||
|
else:
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
key = ec.generate_private_key(curve=ec.SECP256R1(), backend=default_backend())
|
||||||
|
|
||||||
|
if KEY_PASSPHRASE == None:
|
||||||
|
key_encryption = serialization.NoEncryption()
|
||||||
|
else:
|
||||||
|
key_encryption = serialization.BestAvailableEncryption(KEY_PASSPHRASE)
|
||||||
|
|
||||||
|
with open(KEY_PATH, "wb") as f:
|
||||||
|
f.write(key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.PKCS8,
|
||||||
|
encryption_algorithm=key_encryption))
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
def gen_cert(cert_path, key):
|
||||||
|
CERT_PATH = cert_path
|
||||||
|
cert_attrs = [x509.NameAttribute(NameOID.COUNTRY_NAME, "NA"),
|
||||||
|
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "None"),
|
||||||
|
x509.NameAttribute(NameOID.LOCALITY_NAME, "Earth"),
|
||||||
|
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Sideband"),
|
||||||
|
x509.NameAttribute(NameOID.COMMON_NAME, "Sideband Repository")]
|
||||||
|
|
||||||
|
issuer = x509.Name(cert_attrs)
|
||||||
|
subject = issuer
|
||||||
|
|
||||||
|
cb = x509.CertificateBuilder()
|
||||||
|
cb = cb.subject_name(subject)
|
||||||
|
cb = cb.issuer_name(issuer)
|
||||||
|
cb = cb.public_key(key.public_key())
|
||||||
|
cb = cb.serial_number(x509.random_serial_number())
|
||||||
|
cb = cb.not_valid_before(datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(days=-14))
|
||||||
|
cb = cb.not_valid_after(datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(days=3652))
|
||||||
|
cb = cb.add_extension(x509.SubjectAlternativeName([x509.DNSName("localhost")]), critical=False)
|
||||||
|
if cryptography_major_version > 3:
|
||||||
|
cert = cb.sign(key, hashes.SHA256())
|
||||||
|
else:
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
cert = cb.sign(key, hashes.SHA256(), backend=default_backend())
|
||||||
|
|
||||||
|
with open(CERT_PATH, "wb") as f:
|
||||||
|
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
||||||
|
|
||||||
|
def ensure_certificate(key_path, cert_path):
|
||||||
|
gen_cert(cert_path, get_key(key_path))
|
||||||
|
return cert_path
|
@ -10,11 +10,13 @@ import shlex
|
|||||||
|
|
||||||
import RNS.vendor.umsgpack as msgpack
|
import RNS.vendor.umsgpack as msgpack
|
||||||
import RNS.Interfaces.Interface as Interface
|
import RNS.Interfaces.Interface as Interface
|
||||||
|
from LXMF import pn_announce_data_is_valid
|
||||||
|
|
||||||
import multiprocessing.connection
|
import multiprocessing.connection
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from collections import deque
|
||||||
from .res import sideband_fb_data
|
from .res import sideband_fb_data
|
||||||
from .sense import Telemeter, Commands
|
from .sense import Telemeter, Commands
|
||||||
from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin
|
from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin
|
||||||
@ -43,38 +45,49 @@ class PropagationNodeDetector():
|
|||||||
|
|
||||||
aspect_filter = "lxmf.propagation"
|
aspect_filter = "lxmf.propagation"
|
||||||
|
|
||||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
def received_announce(self, destination_hash, announced_identity, app_data, announce_packet_hash):
|
||||||
try:
|
try:
|
||||||
if app_data != None and len(app_data) > 0:
|
if app_data != None and len(app_data) > 0:
|
||||||
unpacked = msgpack.unpackb(app_data)
|
if pn_announce_data_is_valid(app_data):
|
||||||
node_active = unpacked[0]
|
unpacked = msgpack.unpackb(app_data)
|
||||||
emitted = unpacked[1]
|
node_active = unpacked[0]
|
||||||
hops = RNS.Transport.hops_to(destination_hash)
|
emitted = unpacked[1]
|
||||||
|
hops = RNS.Transport.hops_to(destination_hash)
|
||||||
|
|
||||||
age = time.time() - emitted
|
age = time.time() - emitted
|
||||||
if age < 0:
|
if age < 0:
|
||||||
RNS.log("Warning, propagation node announce emitted in the future, possible timing inconsistency or tampering attempt.")
|
RNS.log("Warning, propagation node announce emitted in the future, possible timing inconsistency or tampering attempt.")
|
||||||
if age < -1*PropagationNodeDetector.EMITTED_DELTA_GRACE:
|
if age < -1*PropagationNodeDetector.EMITTED_DELTA_GRACE:
|
||||||
raise ValueError("Announce timestamp too far in the future, discarding it")
|
raise ValueError("Announce timestamp too far in the future, discarding it")
|
||||||
|
|
||||||
if age > -1*PropagationNodeDetector.EMITTED_DELTA_IGNORE:
|
if age > -1*PropagationNodeDetector.EMITTED_DELTA_IGNORE:
|
||||||
# age = 0
|
# age = 0
|
||||||
pass
|
pass
|
||||||
|
|
||||||
RNS.log("Detected active propagation node "+RNS.prettyhexrep(destination_hash)+" emission "+str(age)+" seconds ago, "+str(hops)+" hops away")
|
link_stats = {"rssi": self.owner_app.sideband.reticulum.get_packet_rssi(announce_packet_hash),
|
||||||
self.owner.log_announce(destination_hash, RNS.prettyhexrep(destination_hash).encode("utf-8"), dest_type=PropagationNodeDetector.aspect_filter)
|
"snr": self.owner_app.sideband.reticulum.get_packet_snr(announce_packet_hash),
|
||||||
|
"q": self.owner_app.sideband.reticulum.get_packet_q(announce_packet_hash)}
|
||||||
|
|
||||||
if self.owner.config["lxmf_propagation_node"] == None:
|
RNS.log("Detected active propagation node "+RNS.prettyhexrep(destination_hash)+" emission "+str(age)+" seconds ago, "+str(hops)+" hops away")
|
||||||
if self.owner.active_propagation_node == None:
|
self.owner.log_announce(destination_hash, app_data, dest_type=PropagationNodeDetector.aspect_filter, link_stats=link_stats)
|
||||||
self.owner.set_active_propagation_node(destination_hash)
|
|
||||||
else:
|
if self.owner.config["lxmf_propagation_node"] == None:
|
||||||
prev_hops = RNS.Transport.hops_to(self.owner.active_propagation_node)
|
if self.owner.active_propagation_node == None:
|
||||||
if hops <= prev_hops:
|
|
||||||
self.owner.set_active_propagation_node(destination_hash)
|
self.owner.set_active_propagation_node(destination_hash)
|
||||||
else:
|
else:
|
||||||
pass
|
prev_hops = RNS.Transport.hops_to(self.owner.active_propagation_node)
|
||||||
|
if hops <= prev_hops:
|
||||||
|
self.owner.set_active_propagation_node(destination_hash)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
RNS.log(f"Received malformed propagation node announce from {RNS.prettyhexrep(destination_hash)} with data: {app_data}", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
|
else:
|
||||||
|
RNS.log(f"Received malformed propagation node announce from {RNS.prettyhexrep(destination_hash)} with data: {app_data}", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while processing received propagation node announce: "+str(e))
|
RNS.log("Error while processing received propagation node announce: "+str(e))
|
||||||
@ -102,11 +115,17 @@ class SidebandCore():
|
|||||||
|
|
||||||
DEFAULT_APPEARANCE = ["account", [0,0,0,1], [1,1,1,1]]
|
DEFAULT_APPEARANCE = ["account", [0,0,0,1], [1,1,1,1]]
|
||||||
|
|
||||||
|
LOG_DEQUE_MAXLEN = 128
|
||||||
|
|
||||||
aspect_filter = "lxmf.delivery"
|
aspect_filter = "lxmf.delivery"
|
||||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
def received_announce(self, destination_hash, announced_identity, app_data, announce_packet_hash):
|
||||||
# Add the announce to the directory announce
|
# Add the announce to the directory announce
|
||||||
# stream logger
|
# stream logger
|
||||||
|
|
||||||
|
link_stats = {"rssi": self.reticulum.get_packet_rssi(announce_packet_hash),
|
||||||
|
"snr": self.reticulum.get_packet_snr(announce_packet_hash),
|
||||||
|
"q": self.reticulum.get_packet_q(announce_packet_hash)}
|
||||||
|
|
||||||
# This reformats the new v0.5.0 announce data back to the expected format
|
# This reformats the new v0.5.0 announce data back to the expected format
|
||||||
# for Sidebands database and other handling functions.
|
# for Sidebands database and other handling functions.
|
||||||
dn = LXMF.display_name_from_app_data(app_data)
|
dn = LXMF.display_name_from_app_data(app_data)
|
||||||
@ -115,7 +134,7 @@ class SidebandCore():
|
|||||||
if dn != None:
|
if dn != None:
|
||||||
app_data = dn.encode("utf-8")
|
app_data = dn.encode("utf-8")
|
||||||
|
|
||||||
self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter, stamp_cost=sc)
|
self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter, stamp_cost=sc, link_stats=link_stats)
|
||||||
|
|
||||||
def __init__(self, owner_app, config_path = None, is_service=False, is_client=False, android_app_dir=None, verbose=False, owner_service=None, service_context=None, is_daemon=False, load_config_only=False):
|
def __init__(self, owner_app, config_path = None, is_service=False, is_client=False, android_app_dir=None, verbose=False, owner_service=None, service_context=None, is_daemon=False, load_config_only=False):
|
||||||
self.is_service = is_service
|
self.is_service = is_service
|
||||||
@ -134,6 +153,7 @@ class SidebandCore():
|
|||||||
self.is_standalone = False
|
self.is_standalone = False
|
||||||
|
|
||||||
self.log_verbose = verbose
|
self.log_verbose = verbose
|
||||||
|
self.log_deque = deque(maxlen=self.LOG_DEQUE_MAXLEN)
|
||||||
self.owner_app = owner_app
|
self.owner_app = owner_app
|
||||||
self.reticulum = None
|
self.reticulum = None
|
||||||
self.webshare_server = None
|
self.webshare_server = None
|
||||||
@ -148,6 +168,7 @@ class SidebandCore():
|
|||||||
self.telemetry_send_blocked_until = 0
|
self.telemetry_send_blocked_until = 0
|
||||||
self.pending_telemetry_request = False
|
self.pending_telemetry_request = False
|
||||||
self.telemetry_request_max_history = 7*24*60*60
|
self.telemetry_request_max_history = 7*24*60*60
|
||||||
|
self.live_tracked_objects = {}
|
||||||
self.default_lxm_limit = 128*1000
|
self.default_lxm_limit = 128*1000
|
||||||
self.state_db = {}
|
self.state_db = {}
|
||||||
self.state_lock = Lock()
|
self.state_lock = Lock()
|
||||||
@ -159,6 +180,7 @@ class SidebandCore():
|
|||||||
self.owner_service = owner_service
|
self.owner_service = owner_service
|
||||||
self.allow_service_dispatch = True
|
self.allow_service_dispatch = True
|
||||||
self.version_str = ""
|
self.version_str = ""
|
||||||
|
self.config_template = rns_config
|
||||||
|
|
||||||
if config_path == None:
|
if config_path == None:
|
||||||
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/occ"
|
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/occ"
|
||||||
@ -170,19 +192,21 @@ class SidebandCore():
|
|||||||
self.cache_dir = self.app_dir+"/cache"
|
self.cache_dir = self.app_dir+"/cache"
|
||||||
|
|
||||||
self.rns_configdir = None
|
self.rns_configdir = None
|
||||||
|
|
||||||
|
core_path = os.path.abspath(__file__)
|
||||||
|
if "core.pyc" in core_path:
|
||||||
|
core_path = core_path.replace("core.pyc", "core.py")
|
||||||
|
|
||||||
if RNS.vendor.platformutils.is_android():
|
if RNS.vendor.platformutils.is_android():
|
||||||
self.app_dir = android_app_dir+"/uk.co.liberatedsystems.occ/files/"
|
self.app_dir = android_app_dir+"/uk.co.liberatedsystems.occ/files/"
|
||||||
self.cache_dir = self.app_dir+"/cache"
|
self.cache_dir = self.app_dir+"/cache"
|
||||||
self.rns_configdir = self.app_dir+"/app_storage/reticulum"
|
self.rns_configdir = self.app_dir+"/app_storage/reticulum"
|
||||||
self.asset_dir = self.app_dir+"/app/assets"
|
self.asset_dir = self.app_dir+"/app/assets"
|
||||||
elif RNS.vendor.platformutils.is_darwin():
|
elif RNS.vendor.platformutils.is_darwin():
|
||||||
core_path = os.path.abspath(__file__)
|
|
||||||
self.asset_dir = core_path.replace("/occ/core.py", "/assets")
|
self.asset_dir = core_path.replace("/occ/core.py", "/assets")
|
||||||
elif RNS.vendor.platformutils.get_platform() == "linux":
|
elif RNS.vendor.platformutils.get_platform() == "linux":
|
||||||
core_path = os.path.abspath(__file__)
|
|
||||||
self.asset_dir = core_path.replace("/occ/core.py", "/assets")
|
self.asset_dir = core_path.replace("/occ/core.py", "/assets")
|
||||||
elif RNS.vendor.platformutils.is_windows():
|
elif RNS.vendor.platformutils.is_windows():
|
||||||
core_path = os.path.abspath(__file__)
|
|
||||||
self.asset_dir = core_path.replace("\\occ\\core.py", "\\assets")
|
self.asset_dir = core_path.replace("\\occ\\core.py", "\\assets")
|
||||||
else:
|
else:
|
||||||
self.asset_dir = plyer.storagepath.get_application_dir()+"/sbapp/assets"
|
self.asset_dir = plyer.storagepath.get_application_dir()+"/sbapp/assets"
|
||||||
@ -195,6 +219,10 @@ class SidebandCore():
|
|||||||
if not os.path.isdir(self.rec_cache):
|
if not os.path.isdir(self.rec_cache):
|
||||||
os.makedirs(self.rec_cache)
|
os.makedirs(self.rec_cache)
|
||||||
|
|
||||||
|
self.share_cache = self.cache_dir+"/share"
|
||||||
|
if not os.path.isdir(self.share_cache):
|
||||||
|
os.makedirs(self.share_cache)
|
||||||
|
|
||||||
self.icon = self.asset_dir+"/icon.png"
|
self.icon = self.asset_dir+"/icon.png"
|
||||||
self.icon_48 = self.asset_dir+"/icon_48.png"
|
self.icon_48 = self.asset_dir+"/icon_48.png"
|
||||||
self.icon_32 = self.asset_dir+"/icon_32.png"
|
self.icon_32 = self.asset_dir+"/icon_32.png"
|
||||||
@ -214,7 +242,14 @@ class SidebandCore():
|
|||||||
self.log_dir = self.app_dir+"/app_storage/"
|
self.log_dir = self.app_dir+"/app_storage/"
|
||||||
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
||||||
self.exports_dir = self.app_dir+"/exports"
|
self.exports_dir = self.app_dir+"/exports"
|
||||||
self.webshare_dir = "./share/"
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
self.webshare_dir = "./share/"
|
||||||
|
else:
|
||||||
|
sideband_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
self.webshare_dir = os.path.abspath(os.path.join(sideband_dir, "..", "share"))
|
||||||
|
|
||||||
|
self.webshare_ssl_key_path = self.app_dir+"/app_storage/ssl_key.pem"
|
||||||
|
self.webshare_ssl_cert_path = self.app_dir+"/app_storage/ssl_cert.pem"
|
||||||
|
|
||||||
self.first_run = True
|
self.first_run = True
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
@ -253,6 +288,29 @@ class SidebandCore():
|
|||||||
if load_config_only:
|
if load_config_only:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
if self.config["config_template"] != None:
|
||||||
|
try:
|
||||||
|
if not os.path.isfile(self.rns_configdir+"/config_template_invalid"):
|
||||||
|
if self.is_service:
|
||||||
|
with open(self.rns_configdir+"/config_template_invalid", "w") as invalidation_file:
|
||||||
|
invalidation_file.write("\n")
|
||||||
|
|
||||||
|
ct = self.config["config_template"]
|
||||||
|
RNS.log(f"Loading modified RNS config template", RNS.LOG_WARNING)
|
||||||
|
self.config_template = ct
|
||||||
|
|
||||||
|
else:
|
||||||
|
RNS.log("Custom configuration template invalid, using default configuration template", RNS.LOG_WARNING)
|
||||||
|
self.config_template = rns_config
|
||||||
|
if self.is_service:
|
||||||
|
self.setstate("hardware_operation.error", "At the previous start, Sideband could not initialise Reticulum. Custom configuration template loading has been temporarily disabled. Please check and fix any errors in your configuration template.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"An error occurred while setting RNS configuration template: {e}", RNS.LOG_ERROR)
|
||||||
|
RNS.log(f"Using default configuration template", RNS.LOG_ERROR)
|
||||||
|
self.config_template = rns_config
|
||||||
|
|
||||||
# Initialise Reticulum configuration
|
# Initialise Reticulum configuration
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
try:
|
try:
|
||||||
@ -263,11 +321,10 @@ class SidebandCore():
|
|||||||
RNS.log("Configuring Reticulum instance...")
|
RNS.log("Configuring Reticulum instance...")
|
||||||
if self.config["connect_transport"]:
|
if self.config["connect_transport"]:
|
||||||
RNS.log("Enabling Reticulum Transport")
|
RNS.log("Enabling Reticulum Transport")
|
||||||
generated_config = rns_config.replace("TRANSPORT_IS_ENABLED", "Yes")
|
generated_config = self.config_template.replace("TRANSPORT_IS_ENABLED", "Yes")
|
||||||
else:
|
else:
|
||||||
RNS.log("Not enabling Reticulum Transport")
|
RNS.log("Not enabling Reticulum Transport")
|
||||||
generated_config = rns_config.replace("TRANSPORT_IS_ENABLED", "No")
|
generated_config = self.config_template.replace("TRANSPORT_IS_ENABLED", "No")
|
||||||
|
|
||||||
|
|
||||||
config_file = open(self.rns_configdir+"/config", "wb")
|
config_file = open(self.rns_configdir+"/config", "wb")
|
||||||
config_file.write(generated_config.encode("utf-8"))
|
config_file.write(generated_config.encode("utf-8"))
|
||||||
@ -373,7 +430,7 @@ class SidebandCore():
|
|||||||
self.config["debug"] = False
|
self.config["debug"] = False
|
||||||
self.config["display_name"] = "Anonymous Peer"
|
self.config["display_name"] = "Anonymous Peer"
|
||||||
self.config["notifications_on"] = True
|
self.config["notifications_on"] = True
|
||||||
self.config["dark_ui"] = False
|
self.config["dark_ui"] = True
|
||||||
self.config["start_announce"] = True
|
self.config["start_announce"] = True
|
||||||
self.config["propagation_by_default"] = False
|
self.config["propagation_by_default"] = False
|
||||||
self.config["home_node_as_broadcast_repeater"] = False
|
self.config["home_node_as_broadcast_repeater"] = False
|
||||||
@ -389,8 +446,9 @@ class SidebandCore():
|
|||||||
self.config["last_lxmf_propagation_node"] = None
|
self.config["last_lxmf_propagation_node"] = None
|
||||||
self.config["nn_home_node"] = None
|
self.config["nn_home_node"] = None
|
||||||
self.config["print_command"] = "lp"
|
self.config["print_command"] = "lp"
|
||||||
self.config["eink_mode"] = False
|
self.config["eink_mode"] = True
|
||||||
self.config["lxm_limit_1mb"] = True
|
self.config["lxm_limit_1mb"] = True
|
||||||
|
self.config["trusted_markup_only"] = False
|
||||||
|
|
||||||
# Connectivity
|
# Connectivity
|
||||||
self.config["connect_transport"] = False
|
self.config["connect_transport"] = False
|
||||||
@ -555,7 +613,7 @@ class SidebandCore():
|
|||||||
if not "dark_ui" in self.config:
|
if not "dark_ui" in self.config:
|
||||||
self.config["dark_ui"] = True
|
self.config["dark_ui"] = True
|
||||||
if not "advanced_stats" in self.config:
|
if not "advanced_stats" in self.config:
|
||||||
self.config["advanced_stats"] = False
|
self.config["advanced_stats"] = True
|
||||||
if not "lxmf_periodic_sync" in self.config:
|
if not "lxmf_periodic_sync" in self.config:
|
||||||
self.config["lxmf_periodic_sync"] = False
|
self.config["lxmf_periodic_sync"] = False
|
||||||
if not "lxmf_ignore_unknown" in self.config:
|
if not "lxmf_ignore_unknown" in self.config:
|
||||||
@ -575,13 +633,17 @@ class SidebandCore():
|
|||||||
if not "print_command" in self.config:
|
if not "print_command" in self.config:
|
||||||
self.config["print_command"] = "lp"
|
self.config["print_command"] = "lp"
|
||||||
if not "eink_mode" in self.config:
|
if not "eink_mode" in self.config:
|
||||||
self.config["eink_mode"] = False
|
self.config["eink_mode"] = True
|
||||||
|
if not "classic_message_colors" in self.config:
|
||||||
|
self.config["classic_message_colors"] = False
|
||||||
if not "display_style_in_contact_list" in self.config:
|
if not "display_style_in_contact_list" in self.config:
|
||||||
self.config["display_style_in_contact_list"] = False
|
self.config["display_style_in_contact_list"] = True
|
||||||
if not "lxm_limit_1mb" in self.config:
|
if not "lxm_limit_1mb" in self.config:
|
||||||
self.config["lxm_limit_1mb"] = True
|
self.config["lxm_limit_1mb"] = True
|
||||||
if not "hq_ptt" in self.config:
|
if not "hq_ptt" in self.config:
|
||||||
self.config["hq_ptt"] = False
|
self.config["hq_ptt"] = False
|
||||||
|
if not "trusted_markup_only" in self.config:
|
||||||
|
self.config["trusted_markup_only"] = False
|
||||||
|
|
||||||
if not "input_language" in self.config:
|
if not "input_language" in self.config:
|
||||||
self.config["input_language"] = None
|
self.config["input_language"] = None
|
||||||
@ -590,6 +652,8 @@ class SidebandCore():
|
|||||||
if not "block_predictive_text" in self.config:
|
if not "block_predictive_text" in self.config:
|
||||||
self.config["block_predictive_text"] = False
|
self.config["block_predictive_text"] = False
|
||||||
|
|
||||||
|
if not "config_template" in self.config:
|
||||||
|
self.config["config_template"] = None
|
||||||
if not "connect_transport" in self.config:
|
if not "connect_transport" in self.config:
|
||||||
self.config["connect_transport"] = False
|
self.config["connect_transport"] = False
|
||||||
if not "connect_rnode" in self.config:
|
if not "connect_rnode" in self.config:
|
||||||
@ -725,11 +789,11 @@ class SidebandCore():
|
|||||||
if not "telemetry_bg" in self.config:
|
if not "telemetry_bg" in self.config:
|
||||||
self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2]
|
self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2]
|
||||||
if not "telemetry_send_appearance" in self.config:
|
if not "telemetry_send_appearance" in self.config:
|
||||||
self.config["telemetry_send_appearance"] = False
|
self.config["telemetry_send_appearance"] = True
|
||||||
if not "telemetry_display_trusted_only" in self.config:
|
if not "telemetry_display_trusted_only" in self.config:
|
||||||
self.config["telemetry_display_trusted_only"] = False
|
self.config["telemetry_display_trusted_only"] = False
|
||||||
if not "display_style_from_all" in self.config:
|
if not "display_style_from_all" in self.config:
|
||||||
self.config["display_style_from_all"] = False
|
self.config["display_style_from_all"] = True
|
||||||
if not "telemetry_receive_trusted_only" in self.config:
|
if not "telemetry_receive_trusted_only" in self.config:
|
||||||
self.config["telemetry_receive_trusted_only"] = False
|
self.config["telemetry_receive_trusted_only"] = False
|
||||||
|
|
||||||
@ -840,9 +904,8 @@ class SidebandCore():
|
|||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
try:
|
try:
|
||||||
self.saving_configuration = True
|
self.saving_configuration = True
|
||||||
config_file = open(self.config_path, "wb")
|
with open(self.config_path, "wb") as config_file:
|
||||||
config_file.write(msgpack.packb(self.config))
|
config_file.write(msgpack.packb(self.config))
|
||||||
config_file.close()
|
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
@ -973,13 +1036,14 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
plyer.notification.notify(title, content, app_icon=self.icon_32)
|
plyer.notification.notify(title, content, app_icon=self.icon_32)
|
||||||
|
|
||||||
def log_announce(self, dest, app_data, dest_type, stamp_cost=None):
|
def log_announce(self, dest, app_data, dest_type, stamp_cost=None, link_stats=None):
|
||||||
try:
|
try:
|
||||||
if app_data == None:
|
if app_data == None:
|
||||||
app_data = b""
|
app_data = b""
|
||||||
app_data = msgpack.packb([app_data, stamp_cost])
|
if type(app_data) != bytes:
|
||||||
|
app_data = msgpack.packb([app_data, stamp_cost])
|
||||||
RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+str(app_data), RNS.LOG_DEBUG)
|
RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+str(app_data), RNS.LOG_DEBUG)
|
||||||
self._db_save_announce(dest, app_data, dest_type)
|
self._db_save_announce(dest, app_data, dest_type, link_stats)
|
||||||
self.setstate("app.flags.new_announces", True)
|
self.setstate("app.flags.new_announces", True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1301,7 +1365,7 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def request_latest_telemetry(self, from_addr=None):
|
def request_latest_telemetry(self, from_addr=None, is_livetrack=False):
|
||||||
if self.allow_service_dispatch and self.is_client:
|
if self.allow_service_dispatch and self.is_client:
|
||||||
try:
|
try:
|
||||||
return self._service_request_latest_telemetry(from_addr)
|
return self._service_request_latest_telemetry(from_addr)
|
||||||
@ -1335,7 +1399,11 @@ class SidebandCore():
|
|||||||
if self.config["telemetry_use_propagation_only"] == True:
|
if self.config["telemetry_use_propagation_only"] == True:
|
||||||
desired_method = LXMF.LXMessage.PROPAGATED
|
desired_method = LXMF.LXMessage.PROPAGATED
|
||||||
else:
|
else:
|
||||||
desired_method = LXMF.LXMessage.DIRECT
|
if not self.message_router.delivery_link_available(from_addr) and RNS.Identity.current_ratchet_id(from_addr) != None:
|
||||||
|
RNS.log(f"Have ratchet for {RNS.prettyhexrep(from_addr)}, requesting opportunistic delivery of telemetry request", RNS.LOG_DEBUG)
|
||||||
|
desired_method = LXMF.LXMessage.OPPORTUNISTIC
|
||||||
|
else:
|
||||||
|
desired_method = LXMF.LXMessage.DIRECT
|
||||||
|
|
||||||
request_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.timebase") or now - self.telemetry_request_max_history
|
request_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.timebase") or now - self.telemetry_request_max_history
|
||||||
lxm_fields = { LXMF.FIELD_COMMANDS: [
|
lxm_fields = { LXMF.FIELD_COMMANDS: [
|
||||||
@ -1348,7 +1416,7 @@ class SidebandCore():
|
|||||||
lxm.register_failed_callback(self.telemetry_request_finished)
|
lxm.register_failed_callback(self.telemetry_request_finished)
|
||||||
|
|
||||||
if self.message_router.get_outbound_propagation_node() != None:
|
if self.message_router.get_outbound_propagation_node() != None:
|
||||||
if self.config["telemetry_try_propagation_on_fail"]:
|
if self.config["telemetry_try_propagation_on_fail"] and not is_livetrack:
|
||||||
lxm.try_propagation_on_fail = True
|
lxm.try_propagation_on_fail = True
|
||||||
|
|
||||||
RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG)
|
RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG)
|
||||||
@ -1360,6 +1428,62 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
return "not_sent"
|
return "not_sent"
|
||||||
|
|
||||||
|
def _is_tracking(self, object_addr):
|
||||||
|
return object_addr in self.live_tracked_objects
|
||||||
|
|
||||||
|
def is_tracking(self, object_addr, allow_cache=False):
|
||||||
|
if not RNS.vendor.platformutils.is_android():
|
||||||
|
return self._is_tracking(object_addr)
|
||||||
|
else:
|
||||||
|
if self.is_service:
|
||||||
|
return self._is_tracking(object_addr)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return self.service_rpc_request({"is_tracking": object_addr})
|
||||||
|
except Exception as e:
|
||||||
|
ed = "Error while getting tracking state over RPC: "+str(e)
|
||||||
|
RNS.log(ed, RNS.LOG_DEBUG)
|
||||||
|
return ed
|
||||||
|
|
||||||
|
def _start_tracking(self, object_addr, interval, duration):
|
||||||
|
RNS.log("Starting tracking of "+RNS.prettyhexrep(object_addr), RNS.LOG_DEBUG)
|
||||||
|
self.live_tracked_objects[object_addr] = [interval, 0, time.time()+duration]
|
||||||
|
|
||||||
|
def start_tracking(self, object_addr, interval, duration, allow_cache=False):
|
||||||
|
if not RNS.vendor.platformutils.is_android():
|
||||||
|
return self._start_tracking(object_addr, interval, duration)
|
||||||
|
else:
|
||||||
|
if self.is_service:
|
||||||
|
return self._start_tracking(object_addr, interval, duration)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
args = {"object_addr": object_addr, "interval": interval, "duration": duration}
|
||||||
|
return self.service_rpc_request({"start_tracking": args})
|
||||||
|
except Exception as e:
|
||||||
|
ed = "Error while starting tracking over RPC: "+str(e)
|
||||||
|
RNS.log(ed, RNS.LOG_DEBUG)
|
||||||
|
return ed
|
||||||
|
|
||||||
|
def _stop_tracking(self, object_addr):
|
||||||
|
RNS.log("Stopping tracking of "+RNS.prettyhexrep(object_addr), RNS.LOG_DEBUG)
|
||||||
|
if object_addr in self.live_tracked_objects:
|
||||||
|
self.live_tracked_objects.pop(object_addr)
|
||||||
|
|
||||||
|
def stop_tracking(self, object_addr, allow_cache=False):
|
||||||
|
if not RNS.vendor.platformutils.is_android():
|
||||||
|
return self._stop_tracking(object_addr)
|
||||||
|
else:
|
||||||
|
if self.is_service:
|
||||||
|
return self._stop_tracking(object_addr)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
args = {"object_addr": object_addr}
|
||||||
|
return self.service_rpc_request({"stop_tracking": args})
|
||||||
|
except Exception as e:
|
||||||
|
ed = "Error while stopping tracking over RPC: "+str(e)
|
||||||
|
RNS.log(ed, RNS.LOG_DEBUG)
|
||||||
|
return ed
|
||||||
|
|
||||||
def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False):
|
def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False):
|
||||||
if not RNS.vendor.platformutils.is_android():
|
if not RNS.vendor.platformutils.is_android():
|
||||||
return False
|
return False
|
||||||
@ -1415,7 +1539,11 @@ class SidebandCore():
|
|||||||
if self.config["telemetry_use_propagation_only"] == True:
|
if self.config["telemetry_use_propagation_only"] == True:
|
||||||
desired_method = LXMF.LXMessage.PROPAGATED
|
desired_method = LXMF.LXMessage.PROPAGATED
|
||||||
else:
|
else:
|
||||||
desired_method = LXMF.LXMessage.DIRECT
|
if not self.message_router.delivery_link_available(to_addr) and RNS.Identity.current_ratchet_id(to_addr) != None:
|
||||||
|
RNS.log(f"Have ratchet for {RNS.prettyhexrep(to_addr)}, requesting opportunistic delivery of telemetry", RNS.LOG_DEBUG)
|
||||||
|
desired_method = LXMF.LXMessage.OPPORTUNISTIC
|
||||||
|
else:
|
||||||
|
desired_method = LXMF.LXMessage.DIRECT
|
||||||
|
|
||||||
lxm_fields = self.get_message_fields(to_addr, is_authorized_telemetry_request=is_authorized_telemetry_request, signal_already_sent=True)
|
lxm_fields = self.get_message_fields(to_addr, is_authorized_telemetry_request=is_authorized_telemetry_request, signal_already_sent=True)
|
||||||
if lxm_fields == False and stream == None:
|
if lxm_fields == False and stream == None:
|
||||||
@ -1826,6 +1954,14 @@ class SidebandCore():
|
|||||||
elif "get_lxm_stamp_cost" in call:
|
elif "get_lxm_stamp_cost" in call:
|
||||||
args = call["get_lxm_stamp_cost"]
|
args = call["get_lxm_stamp_cost"]
|
||||||
connection.send(self.get_lxm_stamp_cost(args["lxm_hash"]))
|
connection.send(self.get_lxm_stamp_cost(args["lxm_hash"]))
|
||||||
|
elif "is_tracking" in call:
|
||||||
|
connection.send(self.is_tracking(call["is_tracking"]))
|
||||||
|
elif "start_tracking" in call:
|
||||||
|
args = call["start_tracking"]
|
||||||
|
connection.send(self.start_tracking(object_addr=args["object_addr"], interval=args["interval"], duration=args["duration"]))
|
||||||
|
elif "stop_tracking" in call:
|
||||||
|
args = call["stop_tracking"]
|
||||||
|
connection.send(self.stop_tracking(object_addr=args["object_addr"]))
|
||||||
else:
|
else:
|
||||||
connection.send(None)
|
connection.send(None)
|
||||||
|
|
||||||
@ -1919,10 +2055,10 @@ class SidebandCore():
|
|||||||
# TODO: Remove this again at some point in the future
|
# TODO: Remove this again at some point in the future
|
||||||
db = self.__db_connect()
|
db = self.__db_connect()
|
||||||
dbc = db.cursor()
|
dbc = db.cursor()
|
||||||
dbc.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'lxm' AND sql LIKE '%extra%'")
|
dbc.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'announce' AND sql LIKE '%extra%'")
|
||||||
result = dbc.fetchall()
|
result = dbc.fetchall()
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
dbc.execute("ALTER TABLE lxm ADD COLUMN extra BLOB")
|
dbc.execute("ALTER TABLE announce ADD COLUMN extra BLOB")
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
def _db_initstate(self):
|
def _db_initstate(self):
|
||||||
@ -2479,13 +2615,29 @@ class SidebandCore():
|
|||||||
for entry in result:
|
for entry in result:
|
||||||
try:
|
try:
|
||||||
if not entry[2] in added_dests:
|
if not entry[2] in added_dests:
|
||||||
app_data = entry[3]
|
app_data = entry[3]
|
||||||
|
dest_type = entry[4]
|
||||||
|
if entry[5] != None:
|
||||||
|
try:
|
||||||
|
extras = msgpack.unpackb(entry[5])
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while unpacking extras from announce: {e}", RNS.LOG_ERROR)
|
||||||
|
extras = None
|
||||||
|
else:
|
||||||
|
extras = None
|
||||||
|
if dest_type == "lxmf.delivery":
|
||||||
|
announced_name = LXMF.display_name_from_app_data(app_data)
|
||||||
|
announced_cost = self.message_router.get_outbound_stamp_cost(entry[2])
|
||||||
|
else:
|
||||||
|
announced_name = None
|
||||||
|
announced_cost = None
|
||||||
announce = {
|
announce = {
|
||||||
"dest": entry[2],
|
"dest" : entry[2],
|
||||||
"name": LXMF.display_name_from_app_data(app_data),
|
"name" : announced_name,
|
||||||
"cost": LXMF.stamp_cost_from_app_data(app_data),
|
"cost" : announced_cost,
|
||||||
"time": entry[1],
|
"time" : entry[1],
|
||||||
"type": entry[4]
|
"type" : dest_type,
|
||||||
|
"extras": extras,
|
||||||
}
|
}
|
||||||
added_dests.append(entry[2])
|
added_dests.append(entry[2])
|
||||||
announces.append(announce)
|
announces.append(announce)
|
||||||
@ -2899,7 +3051,7 @@ class SidebandCore():
|
|||||||
|
|
||||||
self.__event_conversation_changed(context_dest)
|
self.__event_conversation_changed(context_dest)
|
||||||
|
|
||||||
def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery"):
|
def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery", link_stats = None):
|
||||||
with self.db_lock:
|
with self.db_lock:
|
||||||
db = self.__db_connect()
|
db = self.__db_connect()
|
||||||
dbc = db.cursor()
|
dbc = db.cursor()
|
||||||
@ -2913,14 +3065,16 @@ class SidebandCore():
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
hash_material = str(time).encode("utf-8")+destination_hash+app_data+dest_type.encode("utf-8")
|
hash_material = str(time).encode("utf-8")+destination_hash+app_data+dest_type.encode("utf-8")
|
||||||
announce_hash = RNS.Identity.full_hash(hash_material)
|
announce_hash = RNS.Identity.full_hash(hash_material)
|
||||||
|
extras = msgpack.packb({"link_stats": link_stats})
|
||||||
|
|
||||||
query = "INSERT INTO announce (id, received, source, data, dest_type) values (?, ?, ?, ?, ?)"
|
query = "INSERT INTO announce (id, received, source, data, dest_type, extra) values (?, ?, ?, ?, ?, ?)"
|
||||||
data = (
|
data = (
|
||||||
announce_hash,
|
announce_hash,
|
||||||
now,
|
now,
|
||||||
destination_hash,
|
destination_hash,
|
||||||
app_data,
|
app_data,
|
||||||
dest_type,
|
dest_type,
|
||||||
|
extras,
|
||||||
)
|
)
|
||||||
|
|
||||||
dbc.execute(query, data)
|
dbc.execute(query, data)
|
||||||
@ -3444,6 +3598,28 @@ class SidebandCore():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("An error occurred while requesting scheduled telemetry from collector: "+str(e), RNS.LOG_ERROR)
|
RNS.log("An error occurred while requesting scheduled telemetry from collector: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
stale_entries = []
|
||||||
|
if len(self.live_tracked_objects) > 0:
|
||||||
|
now = time.time()
|
||||||
|
for object_hash in self.live_tracked_objects:
|
||||||
|
tracking_entry = self.live_tracked_objects[object_hash]
|
||||||
|
tracking_int = tracking_entry[0]
|
||||||
|
tracking_last = tracking_entry[1]
|
||||||
|
tracking_end = tracking_entry[2]
|
||||||
|
|
||||||
|
if now < tracking_end:
|
||||||
|
if now > tracking_last+tracking_int:
|
||||||
|
RNS.log("Next live tracking request time reached for "+str(RNS.prettyhexrep(object_hash)))
|
||||||
|
self.request_latest_telemetry(from_addr=object_hash, is_livetrack=True)
|
||||||
|
tracking_entry[1] = time.time()
|
||||||
|
else:
|
||||||
|
stale_entries.append(object_hash)
|
||||||
|
|
||||||
|
for object_hash in stale_entries:
|
||||||
|
RNS.log("Terminating live tracking for "+RNS.prettyhexrep(object_hash)+", tracking duration reached", RNS.LOG_DEBUG)
|
||||||
|
self.live_tracked_objects.pop(object_hash)
|
||||||
|
|
||||||
|
|
||||||
def __start_jobs_deferred(self):
|
def __start_jobs_deferred(self):
|
||||||
if self.is_service:
|
if self.is_service:
|
||||||
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)
|
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)
|
||||||
@ -3488,11 +3664,12 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
ifac_netkey = self.config["connect_local_ifac_passphrase"]
|
ifac_netkey = self.config["connect_local_ifac_passphrase"]
|
||||||
|
|
||||||
autointerface = RNS.Interfaces.AutoInterface.AutoInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "AutoInterface",
|
||||||
name = "AutoInterface",
|
"group_id": group_id
|
||||||
group_id = group_id
|
}
|
||||||
)
|
|
||||||
|
autointerface = RNS.Interfaces.AutoInterface.AutoInterface(RNS.Transport, interface_config)
|
||||||
autointerface.OUT = True
|
autointerface.OUT = True
|
||||||
|
|
||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
@ -3542,7 +3719,6 @@ class SidebandCore():
|
|||||||
if self.config["hw_rnode_bluetooth"]:
|
if self.config["hw_rnode_bluetooth"]:
|
||||||
RNS.log("Allowing RNode bluetooth", RNS.LOG_DEBUG)
|
RNS.log("Allowing RNode bluetooth", RNS.LOG_DEBUG)
|
||||||
rnode_allow_bluetooth = True
|
rnode_allow_bluetooth = True
|
||||||
ble_dispatcher = RNS.Interfaces.Android.RNodeMultiInterface.AndroidBLEDispatcher()
|
|
||||||
if self.config["hw_rnode_bt_device"] != None:
|
if self.config["hw_rnode_bt_device"] != None:
|
||||||
bt_device_name = self.config["hw_rnode_bt_device"]
|
bt_device_name = self.config["hw_rnode_bt_device"]
|
||||||
|
|
||||||
@ -3573,6 +3749,8 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
atl_long = self.config["hw_rnode_atl_long"]
|
atl_long = self.config["hw_rnode_atl_long"]
|
||||||
|
|
||||||
|
interface_config = None
|
||||||
|
|
||||||
if self.config["hw_rnode_secondary_modem"]:
|
if self.config["hw_rnode_secondary_modem"]:
|
||||||
if self.config["hw_rnode_sec_atl_short"] == "":
|
if self.config["hw_rnode_sec_atl_short"] == "":
|
||||||
sec_atl_short = None
|
sec_atl_short = None
|
||||||
@ -3612,36 +3790,46 @@ class SidebandCore():
|
|||||||
subint_config[0][9] = sec_atl_long
|
subint_config[0][9] = sec_atl_long
|
||||||
subint_config[1][10] = True # outgoing
|
subint_config[1][10] = True # outgoing
|
||||||
|
|
||||||
rnodeinterface = RNS.Interfaces.Android.RNodeMultiInterface.RNodeMultiInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "RNodeMultiInterface",
|
||||||
"RNodeInterface",
|
"port": target_port,
|
||||||
target_port,
|
"subint_config": subint_config,
|
||||||
subint_config,
|
"flow_control": False,
|
||||||
ble_dispatcher = ble_dispatcher,
|
"id_interval": self.config["hw_rnode_beaconinterval"],
|
||||||
allow_bluetooth = rnode_allow_bluetooth,
|
"id_callsign": self.config["hw_rnode_beacondata"],
|
||||||
target_device_name = bt_device_name,
|
"st_alock": atl_short,
|
||||||
)
|
"lt_alock": atl_long,
|
||||||
|
"allow_bluetooth": False,
|
||||||
|
"target_device_name": None,
|
||||||
|
"force_ble": True,
|
||||||
|
"ble_name": bt_device_name,
|
||||||
|
"ble_addr": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
rnodeinterface = RNS.Interfaces.Android.RNodeMultiInterface.RNodeMultiInterface(RNS.Transport, interface_config)
|
||||||
rnodeinterface.start()
|
rnodeinterface.start()
|
||||||
else:
|
else:
|
||||||
rnodeinterface = RNS.Interfaces.Android.RNodeInterface.RNodeInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "RNodeInterface",
|
||||||
"RNodeInterface",
|
"port": target_port,
|
||||||
target_port,
|
"frequency": self.config["hw_rnode_frequency"],
|
||||||
frequency = self.config["hw_rnode_frequency"],
|
"bandwidth": self.config["hw_rnode_bandwidth"],
|
||||||
bandwidth = self.config["hw_rnode_bandwidth"],
|
"txpower": self.config["hw_rnode_tx_power"],
|
||||||
txpower = self.config["hw_rnode_tx_power"],
|
"spreadingfactor": self.config["hw_rnode_spreading_factor"],
|
||||||
sf = self.config["hw_rnode_spreading_factor"],
|
"codingrate": self.config["hw_rnode_coding_rate"],
|
||||||
cr = self.config["hw_rnode_coding_rate"],
|
"flow_control": False,
|
||||||
flow_control = None,
|
"id_interval": self.config["hw_rnode_beaconinterval"],
|
||||||
id_interval = self.config["hw_rnode_beaconinterval"],
|
"id_callsign": self.config["hw_rnode_beacondata"],
|
||||||
id_callsign = self.config["hw_rnode_beacondata"],
|
"st_alock": atl_short,
|
||||||
allow_bluetooth = rnode_allow_bluetooth,
|
"lt_alock": atl_long,
|
||||||
target_device_name = bt_device_name,
|
"allow_bluetooth": False,
|
||||||
st_alock = atl_short,
|
"target_device_name": bt_device_name,
|
||||||
lt_alock = atl_long,
|
"force_ble": True,
|
||||||
)
|
"ble_name": None,
|
||||||
|
"ble_addr": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
rnodeinterface = RNS.Interfaces.Android.RNodeInterface.RNodeInterface(RNS.Transport, interface_config)
|
||||||
rnodeinterface.OUT = True
|
rnodeinterface.OUT = True
|
||||||
|
|
||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
@ -3676,6 +3864,7 @@ class SidebandCore():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while adding RNode Interface. The contained exception was: "+str(e))
|
RNS.log("Error while adding RNode Interface. The contained exception was: "+str(e))
|
||||||
|
RNS.trace_exception(e)
|
||||||
self.interface_rnode = None
|
self.interface_rnode = None
|
||||||
|
|
||||||
def _reticulum_log_debug(self, debug=False):
|
def _reticulum_log_debug(self, debug=False):
|
||||||
@ -3689,6 +3878,14 @@ class SidebandCore():
|
|||||||
if self.is_client:
|
if self.is_client:
|
||||||
self.service_rpc_set_debug(debug)
|
self.service_rpc_set_debug(debug)
|
||||||
|
|
||||||
|
def _log_handler(self, message):
|
||||||
|
self.log_deque.append(message)
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
# TODO: Get service log on Android
|
||||||
|
def get_log(self):
|
||||||
|
return "\n".join(self.log_deque)
|
||||||
|
|
||||||
def __start_jobs_immediate(self):
|
def __start_jobs_immediate(self):
|
||||||
if self.log_verbose:
|
if self.log_verbose:
|
||||||
selected_level = 7 # debugging purposes
|
selected_level = 7 # debugging purposes
|
||||||
@ -3696,7 +3893,20 @@ class SidebandCore():
|
|||||||
selected_level = 2
|
selected_level = 2
|
||||||
|
|
||||||
self.setstate("init.loadingstate", "Substantiating Reticulum")
|
self.setstate("init.loadingstate", "Substantiating Reticulum")
|
||||||
self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=selected_level)
|
|
||||||
|
try:
|
||||||
|
self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=selected_level, logdest=self._log_handler)
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
if self.is_service:
|
||||||
|
if os.path.isfile(self.rns_configdir+"/config_template_invalid"):
|
||||||
|
os.unlink(self.rns_configdir+"/config_template_invalid")
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while instantiating Reticulum: {e}", RNS.LOG_ERROR)
|
||||||
|
RNS.log(f"Local configuration template changes will be ignored on next start", RNS.LOG_ERROR)
|
||||||
|
exit(255)
|
||||||
|
|
||||||
if self.is_service:
|
if self.is_service:
|
||||||
self.__start_rpc_listener()
|
self.__start_rpc_listener()
|
||||||
@ -3750,15 +3960,14 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
ifac_size = None
|
ifac_size = None
|
||||||
|
|
||||||
tcpinterface = RNS.Interfaces.TCPInterface.TCPClientInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "TCPClientInterface",
|
||||||
"TCPClientInterface",
|
"target_host": tcp_host,
|
||||||
tcp_host,
|
"target_port": tcp_port,
|
||||||
tcp_port,
|
"kiss_framing": False,
|
||||||
kiss_framing = False,
|
"i2p_tunneled": False,
|
||||||
i2p_tunneled = False
|
}
|
||||||
)
|
tcpinterface = RNS.Interfaces.TCPInterface.TCPClientInterface(RNS.Transport, interface_config)
|
||||||
|
|
||||||
tcpinterface.OUT = True
|
tcpinterface.OUT = True
|
||||||
|
|
||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
@ -3802,13 +4011,14 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
ifac_size = None
|
ifac_size = None
|
||||||
|
|
||||||
i2pinterface = RNS.Interfaces.I2PInterface.I2PInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "I2PInterface",
|
||||||
"I2PInterface",
|
"storagepath": RNS.Reticulum.storagepath,
|
||||||
RNS.Reticulum.storagepath,
|
"peers": [self.config["connect_i2p_b32"]],
|
||||||
[self.config["connect_i2p_b32"]],
|
"connectable": False,
|
||||||
connectable = False,
|
}
|
||||||
)
|
|
||||||
|
i2pinterface = RNS.Interfaces.I2PInterface.I2PInterface(RNS.Transport, interface_config)
|
||||||
|
|
||||||
i2pinterface.OUT = True
|
i2pinterface.OUT = True
|
||||||
|
|
||||||
@ -3861,16 +4071,15 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
ifac_netkey = self.config["connect_serial_ifac_passphrase"]
|
ifac_netkey = self.config["connect_serial_ifac_passphrase"]
|
||||||
|
|
||||||
serialinterface = RNS.Interfaces.Android.SerialInterface.SerialInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "SerialInterface",
|
||||||
"SerialInterface",
|
"port": target_device["port"],
|
||||||
target_device["port"],
|
"speed": self.config["hw_serial_baudrate"],
|
||||||
self.config["hw_serial_baudrate"],
|
"databits": self.config["hw_serial_databits"],
|
||||||
self.config["hw_serial_databits"],
|
"parity": self.config["hw_serial_parity"],
|
||||||
self.config["hw_serial_parity"],
|
"stopbits": self.config["hw_serial_stopbits"],
|
||||||
self.config["hw_serial_stopbits"],
|
}
|
||||||
)
|
serialinterface = RNS.Interfaces.Android.SerialInterface.SerialInterface(RNS.Transport, interface_config)
|
||||||
|
|
||||||
serialinterface.OUT = True
|
serialinterface.OUT = True
|
||||||
|
|
||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
@ -3914,23 +4123,22 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
ifac_netkey = self.config["connect_modem_ifac_passphrase"]
|
ifac_netkey = self.config["connect_modem_ifac_passphrase"]
|
||||||
|
|
||||||
modeminterface = RNS.Interfaces.Android.KISSInterface.KISSInterface(
|
interface_config = {
|
||||||
RNS.Transport,
|
"name": "ModemInterface",
|
||||||
"ModemInterface",
|
"port": target_device["port"],
|
||||||
target_device["port"],
|
"speed": self.config["hw_modem_baudrate"],
|
||||||
self.config["hw_modem_baudrate"],
|
"databits": self.config["hw_modem_databits"],
|
||||||
self.config["hw_modem_databits"],
|
"parity": self.config["hw_modem_parity"],
|
||||||
self.config["hw_modem_parity"],
|
"stopbits": self.config["hw_modem_stopbits"],
|
||||||
self.config["hw_modem_stopbits"],
|
"preamble": self.config["hw_modem_preamble"],
|
||||||
self.config["hw_modem_preamble"],
|
"txtail": self.config["hw_modem_tail"],
|
||||||
self.config["hw_modem_tail"],
|
"persistence": self.config["hw_modem_persistence"],
|
||||||
self.config["hw_modem_persistence"],
|
"slottime": self.config["hw_modem_slottime"],
|
||||||
self.config["hw_modem_slottime"],
|
"flow_control": False,
|
||||||
False, # flow control
|
"beacon_interval": self.config["hw_modem_beaconinterval"],
|
||||||
self.config["hw_modem_beaconinterval"],
|
"beacon_data": self.config["hw_modem_beacondata"],
|
||||||
self.config["hw_modem_beacondata"],
|
}
|
||||||
)
|
modeminterface = RNS.Interfaces.Android.KISSInterface.KISSInterface(RNS.Transport, interface_config)
|
||||||
|
|
||||||
modeminterface.OUT = True
|
modeminterface.OUT = True
|
||||||
|
|
||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
@ -4327,7 +4535,11 @@ class SidebandCore():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
addr_b = bytes.fromhex(dest_str)
|
addr_b = bytes.fromhex(dest_str)
|
||||||
self._db_create_conversation(addr_b, name, trusted)
|
if addr_b == self.lxmf_destination.hash:
|
||||||
|
RNS.log("Cannot create conversation with own LXMF address", RNS.LOG_ERROR)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self._db_create_conversation(addr_b, name, trusted)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR)
|
||||||
@ -4436,7 +4648,11 @@ class SidebandCore():
|
|||||||
|
|
||||||
if not originator and LXMF.FIELD_AUDIO in message.fields and ptt_enabled:
|
if not originator and LXMF.FIELD_AUDIO in message.fields and ptt_enabled:
|
||||||
self.ptt_event(message)
|
self.ptt_event(message)
|
||||||
should_notify = False
|
if self.gui_conversation() != context_dest:
|
||||||
|
if not RNS.vendor.platformutils.is_android():
|
||||||
|
should_notify = True
|
||||||
|
else:
|
||||||
|
should_notify = False
|
||||||
|
|
||||||
if self.is_client:
|
if self.is_client:
|
||||||
should_notify = False
|
should_notify = False
|
||||||
@ -4574,6 +4790,7 @@ class SidebandCore():
|
|||||||
from http import server
|
from http import server
|
||||||
import socketserver
|
import socketserver
|
||||||
import json
|
import json
|
||||||
|
import ssl
|
||||||
|
|
||||||
webshare_dir = self.webshare_dir
|
webshare_dir = self.webshare_dir
|
||||||
port = 4444
|
port = 4444
|
||||||
@ -4594,7 +4811,7 @@ class SidebandCore():
|
|||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-type", "text/json")
|
self.send_header("Content-type", "text/json")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
json_result = json.dumps(os.listdir(serve_root+"/pkg"))
|
json_result = json.dumps(sorted(os.listdir(serve_root+"/pkg")))
|
||||||
self.wfile.write(json_result.encode("utf-8"))
|
self.wfile.write(json_result.encode("utf-8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.send_response(500)
|
self.send_response(500)
|
||||||
@ -4609,6 +4826,8 @@ class SidebandCore():
|
|||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
if path.lower().endswith(".apk"):
|
if path.lower().endswith(".apk"):
|
||||||
self.send_header("Content-type", "application/vnd.android.package-archive")
|
self.send_header("Content-type", "application/vnd.android.package-archive")
|
||||||
|
elif path.lower().endswith(".js"):
|
||||||
|
self.send_header("Content-type", "text/javascript")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(data)
|
self.wfile.write(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -4618,7 +4837,36 @@ class SidebandCore():
|
|||||||
es = "Error"
|
es = "Error"
|
||||||
self.wfile.write(es.encode("utf-8"))
|
self.wfile.write(es.encode("utf-8"))
|
||||||
|
|
||||||
with socketserver.TCPServer(("", port), RequestHandler) as webserver:
|
#######################################################
|
||||||
|
# Override BaseHTTPRequestHandler method to squelch
|
||||||
|
# excessive exception logging when client signals
|
||||||
|
# invalid certificate to the server. This will always
|
||||||
|
# happen from some clients when using a self-signed
|
||||||
|
# certificate, so we don't care.
|
||||||
|
if not hasattr(server.BaseHTTPRequestHandler, "handle_orig"):
|
||||||
|
server.BaseHTTPRequestHandler.handle_orig = server.BaseHTTPRequestHandler.handle
|
||||||
|
def handle(self):
|
||||||
|
try:
|
||||||
|
self.handle_orig()
|
||||||
|
except ssl.SSLError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("HTTP server exception: "+str(e), RNS.LOG_ERROR)
|
||||||
|
server.BaseHTTPRequestHandler.handle = handle
|
||||||
|
#######################################################
|
||||||
|
|
||||||
|
socketserver.TCPServer.allow_reuse_address = True
|
||||||
|
class ThreadedHTTPServer(socketserver.ThreadingMixIn, server.HTTPServer):
|
||||||
|
daemon_threads = True
|
||||||
|
|
||||||
|
with ThreadedHTTPServer(("", port), RequestHandler) as webserver:
|
||||||
|
from sideband.certgen import ensure_certificate
|
||||||
|
|
||||||
|
ensure_certificate(self.webshare_ssl_key_path, self.webshare_ssl_cert_path)
|
||||||
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
ssl_context.load_cert_chain(certfile=self.webshare_ssl_cert_path, keyfile=self.webshare_ssl_key_path)
|
||||||
|
webserver.socket = ssl_context.wrap_socket(webserver.socket, do_handshake_on_connect=False, server_side=True)
|
||||||
|
|
||||||
self.webshare_server = webserver
|
self.webshare_server = webserver
|
||||||
webserver.serve_forever()
|
webserver.serve_forever()
|
||||||
self.webshare_server = None
|
self.webshare_server = None
|
||||||
@ -4823,15 +5071,41 @@ class SidebandCore():
|
|||||||
if not self.reticulum.is_connected_to_shared_instance:
|
if not self.reticulum.is_connected_to_shared_instance:
|
||||||
RNS.Transport.detach_interfaces()
|
RNS.Transport.detach_interfaces()
|
||||||
|
|
||||||
rns_config = """
|
rns_config = """# This template is used to generate a
|
||||||
|
# running configuration for Sideband's
|
||||||
|
# internal RNS instance. Incorrect changes
|
||||||
|
# or addition here may cause Sideband to
|
||||||
|
# fail starting up or working properly.
|
||||||
|
#
|
||||||
|
# If Sideband detects that Reticulum
|
||||||
|
# aborts at startup, due to an error in
|
||||||
|
# configuration, any template changes
|
||||||
|
# will be reset to this default.
|
||||||
|
|
||||||
[reticulum]
|
[reticulum]
|
||||||
enable_transport = TRANSPORT_IS_ENABLED
|
# Don't change this line, use the UI
|
||||||
share_instance = Yes
|
# setting for selecting whether RNS
|
||||||
shared_instance_port = 37428
|
# transport is enabled or disabled
|
||||||
instance_control_port = 37429
|
enable_transport = TRANSPORT_IS_ENABLED
|
||||||
panic_on_interface_error = No
|
|
||||||
|
|
||||||
|
# Changing this setting will cause
|
||||||
|
# Sideband to not work.
|
||||||
|
share_instance = Yes
|
||||||
|
|
||||||
|
# Changing these options should only
|
||||||
|
# be done if you know what you're doing.
|
||||||
|
shared_instance_port = 37428
|
||||||
|
instance_control_port = 37429
|
||||||
|
panic_on_interface_error = No
|
||||||
|
|
||||||
|
# Logging is controlled by settings
|
||||||
|
# in the UI, so this section is mostly
|
||||||
|
# not relevant in Sideband.
|
||||||
[logging]
|
[logging]
|
||||||
loglevel = 3
|
loglevel = 3
|
||||||
|
|
||||||
|
# No additional interfaces are currently
|
||||||
|
# defined, but you can use this section
|
||||||
|
# to do so.
|
||||||
|
[interfaces]
|
||||||
"""
|
"""
|
||||||
|
@ -744,7 +744,8 @@ class Location(Sensor):
|
|||||||
if "altitude" in self._raw:
|
if "altitude" in self._raw:
|
||||||
self.altitude = self._raw["altitude"]
|
self.altitude = self._raw["altitude"]
|
||||||
if "speed" in self._raw:
|
if "speed" in self._raw:
|
||||||
self.speed = self._raw["speed"]
|
# Android GPS reports speed in m/s, convert to km/h
|
||||||
|
self.speed = self._raw["speed"]*3.6
|
||||||
if self.speed < 0:
|
if self.speed < 0:
|
||||||
self.speed = 0
|
self.speed = 0
|
||||||
if "bearing" in self._raw:
|
if "bearing" in self._raw:
|
||||||
@ -777,9 +778,9 @@ class Location(Sensor):
|
|||||||
return [
|
return [
|
||||||
struct.pack("!i", int(round(d["latitude"], 6)*1e6)),
|
struct.pack("!i", int(round(d["latitude"], 6)*1e6)),
|
||||||
struct.pack("!i", int(round(d["longitude"], 6)*1e6)),
|
struct.pack("!i", int(round(d["longitude"], 6)*1e6)),
|
||||||
struct.pack("!I", int(round(d["altitude"], 2)*1e2)),
|
struct.pack("!i", int(round(d["altitude"], 2)*1e2)),
|
||||||
struct.pack("!I", int(round(d["speed"], 2)*1e2)),
|
struct.pack("!I", int(round(d["speed"], 2)*1e2)),
|
||||||
struct.pack("!I", int(round(d["bearing"], 2)*1e2)),
|
struct.pack("!i", int(round(d["bearing"], 2)*1e2)),
|
||||||
struct.pack("!H", int(round(d["accuracy"], 2)*1e2)),
|
struct.pack("!H", int(round(d["accuracy"], 2)*1e2)),
|
||||||
d["last_update"],
|
d["last_update"],
|
||||||
]
|
]
|
||||||
@ -795,9 +796,9 @@ class Location(Sensor):
|
|||||||
return {
|
return {
|
||||||
"latitude": struct.unpack("!i", packed[0])[0]/1e6,
|
"latitude": struct.unpack("!i", packed[0])[0]/1e6,
|
||||||
"longitude": struct.unpack("!i", packed[1])[0]/1e6,
|
"longitude": struct.unpack("!i", packed[1])[0]/1e6,
|
||||||
"altitude": struct.unpack("!I", packed[2])[0]/1e2,
|
"altitude": struct.unpack("!i", packed[2])[0]/1e2,
|
||||||
"speed": struct.unpack("!I", packed[3])[0]/1e2,
|
"speed": struct.unpack("!I", packed[3])[0]/1e2,
|
||||||
"bearing": struct.unpack("!I", packed[4])[0]/1e2,
|
"bearing": struct.unpack("!i", packed[4])[0]/1e2,
|
||||||
"accuracy": struct.unpack("!H", packed[5])[0]/1e2,
|
"accuracy": struct.unpack("!H", packed[5])[0]/1e2,
|
||||||
"last_update": packed[6],
|
"last_update": packed[6],
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ from kivy.lang.builder import Builder
|
|||||||
|
|
||||||
from kivy.utils import escape_markup
|
from kivy.utils import escape_markup
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
from ui.helpers import multilingual_markup
|
from ui.helpers import multilingual_markup, sig_icon_for_q
|
||||||
else:
|
else:
|
||||||
from .helpers import multilingual_markup
|
from .helpers import multilingual_markup, sig_icon_for_q
|
||||||
|
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
from ui.helpers import ts_format
|
from ui.helpers import ts_format
|
||||||
@ -92,6 +92,25 @@ class Announces():
|
|||||||
a_name = announce["name"]
|
a_name = announce["name"]
|
||||||
a_cost = announce["cost"]
|
a_cost = announce["cost"]
|
||||||
dest_type = announce["type"]
|
dest_type = announce["type"]
|
||||||
|
a_rssi = None
|
||||||
|
a_snr = None
|
||||||
|
a_q = None
|
||||||
|
|
||||||
|
link_extras_str = ""
|
||||||
|
link_extras_full = ""
|
||||||
|
if "extras" in announce and announce["extras"] != None:
|
||||||
|
extras = announce["extras"]
|
||||||
|
if "link_stats" in extras:
|
||||||
|
link_stats = extras["link_stats"]
|
||||||
|
if "rssi" in link_stats and "snr" in link_stats and "q" in link_stats:
|
||||||
|
a_rssi = link_stats["rssi"]
|
||||||
|
a_snr = link_stats["snr"]
|
||||||
|
a_q = link_stats["q"]
|
||||||
|
if a_rssi != None and a_snr != None and a_q != None:
|
||||||
|
link_extras_str = f" ([b]RSSI[/b] {a_rssi} [b]SNR[/b] {a_snr})"
|
||||||
|
link_extras_full = f"\n[b]Link Quality[/b] {a_q}%[/b]\n[b]RSSI[/b] {a_rssi}\n[b]SNR[/b] {a_snr}"
|
||||||
|
|
||||||
|
sig_icon = multilingual_markup(sig_icon_for_q(a_q).encode("utf-8")).decode("utf-8")
|
||||||
|
|
||||||
if not context_dest in self.added_item_dests:
|
if not context_dest in self.added_item_dests:
|
||||||
if self.app.sideband.is_trusted(context_dest):
|
if self.app.sideband.is_trusted(context_dest):
|
||||||
@ -99,16 +118,16 @@ class Announces():
|
|||||||
else:
|
else:
|
||||||
trust_icon = "account-question"
|
trust_icon = "account-question"
|
||||||
|
|
||||||
def gen_info(ts, dest, name, cost, dtype):
|
def gen_info(ts, dest, name, cost, dtype, link_extras):
|
||||||
name = multilingual_markup(escape_markup(str(name)).encode("utf-8")).decode("utf-8")
|
name = multilingual_markup(escape_markup(str(name)).encode("utf-8")).decode("utf-8")
|
||||||
cost = str(cost)
|
cost = str(cost)
|
||||||
def x(sender):
|
def x(sender):
|
||||||
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||||
if dtype == "lxmf.delivery":
|
if dtype == "lxmf.delivery":
|
||||||
ad_text = "[size=22dp]LXMF Peer[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)+"\n[b]Name[/b] "+name+"\n[b]Stamp Cost[/b] "+cost
|
ad_text = "[size=22dp]LXMF Peer[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)+"\n[b]Name[/b] "+name+"\n[b]Stamp Cost[/b] "+cost+link_extras
|
||||||
|
|
||||||
if dtype == "lxmf.propagation":
|
if dtype == "lxmf.propagation":
|
||||||
ad_text = "[size=22dp]LXMF Propagation Node[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)
|
ad_text = "[size=22dp]LXMF Propagation Node[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)+link_extras
|
||||||
|
|
||||||
dialog = MDDialog(
|
dialog = MDDialog(
|
||||||
text=ad_text,
|
text=ad_text,
|
||||||
@ -123,7 +142,8 @@ class Announces():
|
|||||||
dialog.open()
|
dialog.open()
|
||||||
return x
|
return x
|
||||||
|
|
||||||
time_string = time.strftime(ts_format, time.localtime(ts))
|
time_string = sig_icon + " " + time.strftime(ts_format, time.localtime(ts)) + link_extras_str
|
||||||
|
time_string_plain = time.strftime(ts_format, time.localtime(ts))
|
||||||
|
|
||||||
if dest_type == "lxmf.delivery":
|
if dest_type == "lxmf.delivery":
|
||||||
disp_name = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(context_dest))).encode("utf-8")).decode("utf-8")
|
disp_name = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(context_dest))).encode("utf-8")).decode("utf-8")
|
||||||
@ -137,7 +157,7 @@ class Announces():
|
|||||||
disp_name = "Unknown Announce"
|
disp_name = "Unknown Announce"
|
||||||
iconl = IconLeftWidget(icon="progress-question")
|
iconl = IconLeftWidget(icon="progress-question")
|
||||||
|
|
||||||
item = TwoLineAvatarIconListItem(text=time_string, secondary_text=disp_name, on_release=gen_info(time_string, context_dest, a_name, a_cost, dest_type))
|
item = TwoLineAvatarIconListItem(text=time_string, secondary_text=disp_name, on_release=gen_info(time_string_plain, context_dest, a_name, a_cost, dest_type, link_extras_full))
|
||||||
item.add_widget(iconl)
|
item.add_widget(iconl)
|
||||||
item.sb_uid = context_dest
|
item.sb_uid = context_dest
|
||||||
item.ts = ts
|
item.ts = ts
|
||||||
|
@ -133,6 +133,7 @@ class Conversations():
|
|||||||
unread = conv["unread"]
|
unread = conv["unread"]
|
||||||
last_activity = conv["last_activity"]
|
last_activity = conv["last_activity"]
|
||||||
trusted = conv["trust"] == 1
|
trusted = conv["trust"] == 1
|
||||||
|
appearance_from_all = self.app.sideband.config["display_style_from_all"]
|
||||||
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
|
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
|
||||||
is_object = self.app.sideband.is_object(context_dest, conv_data=conv)
|
is_object = self.app.sideband.is_object(context_dest, conv_data=conv)
|
||||||
da = self.app.sideband.DEFAULT_APPEARANCE
|
da = self.app.sideband.DEFAULT_APPEARANCE
|
||||||
@ -141,7 +142,7 @@ class Conversations():
|
|||||||
conv_icon = self.trust_icon(conv)
|
conv_icon = self.trust_icon(conv)
|
||||||
fg = None; bg = None; ti_color = None
|
fg = None; bg = None; ti_color = None
|
||||||
|
|
||||||
if trusted and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da:
|
if (trusted or appearance_from_all) and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da:
|
||||||
fg = appearance[1] or da[1]; bg = appearance[2] or da[2]
|
fg = appearance[1] or da[1]; bg = appearance[2] or da[2]
|
||||||
ti_color = "Custom"
|
ti_color = "Custom"
|
||||||
else:
|
else:
|
||||||
@ -524,8 +525,8 @@ Builder.load_string("""
|
|||||||
id: n_address_field
|
id: n_address_field
|
||||||
max_text_length: 32
|
max_text_length: 32
|
||||||
hint_text: "Address"
|
hint_text: "Address"
|
||||||
helper_text: "Error, check your input"
|
helper_text: ""
|
||||||
helper_text_mode: "on_error"
|
helper_text_mode: "on_focus"
|
||||||
text: ""
|
text: ""
|
||||||
font_size: dp(24)
|
font_size: dp(24)
|
||||||
|
|
||||||
@ -652,6 +653,10 @@ Builder.load_string("""
|
|||||||
padding: [0, 0, 0, dp(16)]
|
padding: [0, 0, 0, dp(16)]
|
||||||
height: self.minimum_height+dp(24)
|
height: self.minimum_height+dp(24)
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
id: node_info
|
||||||
|
text: "Unknown propagation node"
|
||||||
|
|
||||||
MDProgressBar:
|
MDProgressBar:
|
||||||
id: sync_progress
|
id: sync_progress
|
||||||
type: "determinate"
|
type: "determinate"
|
||||||
@ -659,7 +664,6 @@ Builder.load_string("""
|
|||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
id: sync_status
|
id: sync_status
|
||||||
hint_text: "Name"
|
|
||||||
text: "Initiating sync..."
|
text: "Initiating sync..."
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from kivy.uix.screenmanager import ScreenManager, Screen
|
|||||||
from kivymd.theming import ThemableBehavior
|
from kivymd.theming import ThemableBehavior
|
||||||
from kivymd.uix.list import OneLineIconListItem, MDList, IconLeftWidget, IconRightWidget
|
from kivymd.uix.list import OneLineIconListItem, MDList, IconLeftWidget, IconRightWidget
|
||||||
from kivy.properties import StringProperty
|
from kivy.properties import StringProperty
|
||||||
|
import re
|
||||||
|
|
||||||
ts_format = "%Y-%m-%d %H:%M:%S"
|
ts_format = "%Y-%m-%d %H:%M:%S"
|
||||||
file_ts_format = "%Y_%m_%d_%H_%M_%S"
|
file_ts_format = "%Y_%m_%d_%H_%M_%S"
|
||||||
@ -25,6 +26,19 @@ intensity_msgs_light = "500"
|
|||||||
intensity_play_dark = "600"
|
intensity_play_dark = "600"
|
||||||
intensity_play_light = "300"
|
intensity_play_light = "300"
|
||||||
|
|
||||||
|
|
||||||
|
intensity_msgs_dark_alt = "800"
|
||||||
|
intensity_msgs_light_alt = "400"
|
||||||
|
intensity_delivered_alt_dark = "800"
|
||||||
|
color_received_alt = "BlueGray"
|
||||||
|
color_received_alt_light = "BlueGray"
|
||||||
|
color_delivered_alt = "Indigo"
|
||||||
|
color_propagated_alt = "DeepPurple"
|
||||||
|
color_paper_alt = "DeepPurple"
|
||||||
|
color_playing_alt = "Amber"
|
||||||
|
color_failed_alt = "Red"
|
||||||
|
color_unknown_alt = "Gray"
|
||||||
|
|
||||||
class ContentNavigationDrawer(Screen):
|
class ContentNavigationDrawer(Screen):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -45,13 +59,12 @@ def strip_emojis(str_input):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def multilingual_markup(data):
|
def multilingual_markup(data):
|
||||||
# TODO: Remove
|
|
||||||
# import time
|
|
||||||
# ts = time.time()
|
|
||||||
|
|
||||||
do = ""
|
do = ""
|
||||||
rfont = "default"
|
rfont = "default"
|
||||||
ds = data.decode("utf-8")
|
ds = data.decode("utf-8")
|
||||||
|
di = 0
|
||||||
|
persistent_regions = [(m.start(), m.end()) for m in re.finditer("(?s)\[font=(?:nf|term)\].*?\[/font\]", ds)]
|
||||||
|
|
||||||
for cp in ds:
|
for cp in ds:
|
||||||
match = False
|
match = False
|
||||||
switch = False
|
switch = False
|
||||||
@ -63,6 +76,10 @@ def multilingual_markup(data):
|
|||||||
switch = True
|
switch = True
|
||||||
rfont = "emoji"
|
rfont = "emoji"
|
||||||
|
|
||||||
|
in_persistent = False
|
||||||
|
if any(x[0] < di and x[1] > di for x in persistent_regions):
|
||||||
|
in_persistent = True
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
for range_start in codepoint_map:
|
for range_start in codepoint_map:
|
||||||
range_end = codepoint_map[range_start][0]
|
range_end = codepoint_map[range_start][0]
|
||||||
@ -71,8 +88,9 @@ def multilingual_markup(data):
|
|||||||
if range_end >= ord(cp) >= range_start:
|
if range_end >= ord(cp) >= range_start:
|
||||||
match = True
|
match = True
|
||||||
if rfont != mapped_font:
|
if rfont != mapped_font:
|
||||||
rfont = mapped_font
|
if not in_persistent:
|
||||||
switch = True
|
rfont = mapped_font
|
||||||
|
switch = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if (not match) and rfont != "default":
|
if (not match) and rfont != "default":
|
||||||
@ -86,15 +104,30 @@ def multilingual_markup(data):
|
|||||||
do += "[font="+str(rfont)+"]"
|
do += "[font="+str(rfont)+"]"
|
||||||
|
|
||||||
do += cp
|
do += cp
|
||||||
|
di += 1
|
||||||
|
|
||||||
if rfont != "default":
|
if rfont != "default":
|
||||||
do += "[/font]"
|
do += "[/font]"
|
||||||
|
|
||||||
# TODO: Remove
|
|
||||||
# print(do+"\n\n"+str(time.time()-ts))
|
|
||||||
|
|
||||||
return do.encode("utf-8")
|
return do.encode("utf-8")
|
||||||
|
|
||||||
|
def sig_icon_for_q(q):
|
||||||
|
if q == None:
|
||||||
|
return ""
|
||||||
|
elif q > 90:
|
||||||
|
return ""
|
||||||
|
elif q > 70:
|
||||||
|
return ""
|
||||||
|
elif q > 50:
|
||||||
|
return ""
|
||||||
|
elif q > 30:
|
||||||
|
return ""
|
||||||
|
elif q > 10:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
persistent_fonts = ["nf", "term"]
|
||||||
|
nf_mapped = "nf"
|
||||||
|
|
||||||
codepoint_map = {
|
codepoint_map = {
|
||||||
0x0590: [0x05ff, "hebrew"],
|
0x0590: [0x05ff, "hebrew"],
|
||||||
0x2e3a: [0x2e3b, "chinese"],
|
0x2e3a: [0x2e3b, "chinese"],
|
||||||
@ -128,6 +161,29 @@ codepoint_map = {
|
|||||||
0xac00: [0xd7af, "korean"],
|
0xac00: [0xd7af, "korean"],
|
||||||
0xd7b0: [0xd7ff, "korean"],
|
0xd7b0: [0xd7ff, "korean"],
|
||||||
0x0900: [0x097f, "combined"], # Devanagari
|
0x0900: [0x097f, "combined"], # Devanagari
|
||||||
|
0xe5fa: [0xe6b7, nf_mapped], # Seti-UI + Custom
|
||||||
|
0xe700: [0xe8ef, nf_mapped], # Devicons
|
||||||
|
0xed00: [0xf2ff, nf_mapped], # Font Awesome
|
||||||
|
0xe200: [0xe2a9, nf_mapped], # Font Awesome Extension
|
||||||
|
0xf0001: [0xf1af0, nf_mapped], # Material Design Icons
|
||||||
|
0xe300: [0xe3e3, nf_mapped], # Weather
|
||||||
|
0xf400: [0xf533, nf_mapped], # Octicons
|
||||||
|
0x2665: [0x2665, nf_mapped], # Octicons
|
||||||
|
0x26a1: [0x26a1, nf_mapped], # Octicons
|
||||||
|
0xe0a0: [0xe0a2, nf_mapped], # Powerline Symbols
|
||||||
|
0xe0b0: [0xe0b3, nf_mapped], # Powerline Symbols
|
||||||
|
0xe0a3: [0xe0a3, nf_mapped], # Powerline Extra Symbols
|
||||||
|
0xe0b4: [0xe0c8, nf_mapped], # Powerline Extra Symbols
|
||||||
|
0xe0ca: [0xe0ca, nf_mapped], # Powerline Extra Symbols
|
||||||
|
0xe0cc: [0xe0d7, nf_mapped], # Powerline Extra Symbols
|
||||||
|
0x23fb: [0x23fe, nf_mapped], # IEC Power Symbols
|
||||||
|
0x2b58: [0x2b58, nf_mapped], # IEC Power Symbols
|
||||||
|
0xf300: [0xf381, nf_mapped], # Font logos
|
||||||
|
0xe000: [0xe00a, nf_mapped], # Pomicons
|
||||||
|
0xea60: [0xec1e, nf_mapped], # Codicons
|
||||||
|
0x276c: [0x2771, nf_mapped], # Heavy Angle Brackets
|
||||||
|
0x2500: [0x259f, nf_mapped], # Box Drawing
|
||||||
|
0xee00: [0xee0b, nf_mapped], # Progress
|
||||||
}
|
}
|
||||||
|
|
||||||
emoji_lookup = [
|
emoji_lookup = [
|
||||||
|
@ -80,6 +80,15 @@ MDNavigationLayout:
|
|||||||
on_release: root.ids.screen_manager.app.map_action(self)
|
on_release: root.ids.screen_manager.app.map_action(self)
|
||||||
|
|
||||||
|
|
||||||
|
# OneLineIconListItem:
|
||||||
|
# text: "Overview"
|
||||||
|
# on_release: root.ids.screen_manager.app.overview_action(self)
|
||||||
|
|
||||||
|
# IconLeftWidget:
|
||||||
|
# icon: "view-dashboard-outline"
|
||||||
|
# on_release: root.ids.screen_manager.app.overview_action(self)
|
||||||
|
|
||||||
|
|
||||||
OneLineIconListItem:
|
OneLineIconListItem:
|
||||||
text: "Announce Stream"
|
text: "Announce Stream"
|
||||||
on_release: root.ids.screen_manager.app.announces_action(self)
|
on_release: root.ids.screen_manager.app.announces_action(self)
|
||||||
@ -107,6 +116,15 @@ MDNavigationLayout:
|
|||||||
on_release: root.ids.screen_manager.app.telemetry_action(self)
|
on_release: root.ids.screen_manager.app.telemetry_action(self)
|
||||||
|
|
||||||
|
|
||||||
|
OneLineIconListItem:
|
||||||
|
text: "Utilities"
|
||||||
|
on_release: root.ids.screen_manager.app.utilities_action(self)
|
||||||
|
|
||||||
|
IconLeftWidget:
|
||||||
|
icon: "tools"
|
||||||
|
on_release: root.ids.screen_manager.app.utilities_action(self)
|
||||||
|
|
||||||
|
|
||||||
OneLineIconListItem:
|
OneLineIconListItem:
|
||||||
text: "Preferences"
|
text: "Preferences"
|
||||||
on_release: root.ids.screen_manager.app.settings_action(self)
|
on_release: root.ids.screen_manager.app.settings_action(self)
|
||||||
@ -1260,6 +1278,28 @@ MDScreen:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
layout_settings_screen = """
|
layout_settings_screen = """
|
||||||
|
<UIScaling>
|
||||||
|
orientation: "vertical"
|
||||||
|
spacing: "24dp"
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height+dp(0)
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
id: scaling_info
|
||||||
|
markup: True
|
||||||
|
text: "You can scale the entire Sideband UI by specifying a scaling factor in the field below. After setting it, restart sideband for the scaling to take effect.\\n\\nSet to 0.0 to disable scaling adjustments.\\n\\n[b]Please note![/b] On some devices, the default scaling factor will be higher than 1.0, and setting a smaller value will result in miniscule UI elements."
|
||||||
|
size_hint_y: None
|
||||||
|
text_size: self.width, None
|
||||||
|
height: self.texture_size[1]
|
||||||
|
|
||||||
|
MDTextField:
|
||||||
|
id: scaling_factor
|
||||||
|
hint_text: "Scaling Factor"
|
||||||
|
helper_text: "From 0.3 to 5.0"
|
||||||
|
helper_text_mode: "on_focus"
|
||||||
|
text: ""
|
||||||
|
font_size: dp(24)
|
||||||
|
|
||||||
MDScreen:
|
MDScreen:
|
||||||
name: "settings_screen"
|
name: "settings_screen"
|
||||||
|
|
||||||
@ -1399,11 +1439,21 @@ MDScreen:
|
|||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: self.texture_size[1]
|
height: self.texture_size[1]
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: appearance_ui_scaling
|
||||||
|
icon: "relative-scale"
|
||||||
|
text: "Configure UI Scaling"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.app.configure_ui_scaling_action(self)
|
||||||
|
|
||||||
MDBoxLayout:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
padding: [0,0,dp(24),dp(0)]
|
padding: [0,dp(14),dp(24),dp(0)]
|
||||||
height: dp(48)
|
height: dp(62)
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
text: "Notifications"
|
text: "Notifications"
|
||||||
@ -1444,6 +1494,21 @@ MDScreen:
|
|||||||
pos_hint: {"center_y": 0.3}
|
pos_hint: {"center_y": 0.3}
|
||||||
active: False
|
active: False
|
||||||
|
|
||||||
|
MDBoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
padding: [0,0,dp(24),dp(0)]
|
||||||
|
height: dp(48)
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
text: "Classic message colors"
|
||||||
|
font_style: "H6"
|
||||||
|
|
||||||
|
MDSwitch:
|
||||||
|
id: settings_classic_message_colors
|
||||||
|
pos_hint: {"center_y": 0.3}
|
||||||
|
active: False
|
||||||
|
|
||||||
MDBoxLayout:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
@ -1518,7 +1583,7 @@ MDScreen:
|
|||||||
height: dp(48)
|
height: dp(48)
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
text: "Announce Automatically"
|
text: "Announce automatically"
|
||||||
font_style: "H6"
|
font_style: "H6"
|
||||||
|
|
||||||
MDSwitch:
|
MDSwitch:
|
||||||
@ -1533,7 +1598,7 @@ MDScreen:
|
|||||||
height: dp(48)
|
height: dp(48)
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
text: "Try propagation on direct delivery failure"
|
text: "Try propagation automatically"
|
||||||
font_style: "H6"
|
font_style: "H6"
|
||||||
|
|
||||||
MDSwitch:
|
MDSwitch:
|
||||||
@ -1574,6 +1639,22 @@ MDScreen:
|
|||||||
disabled: False
|
disabled: False
|
||||||
active: False
|
active: False
|
||||||
|
|
||||||
|
MDBoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
padding: [0,0,dp(24),dp(0)]
|
||||||
|
height: dp(48)
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
text: "Only render markup from trusted"
|
||||||
|
font_style: "H6"
|
||||||
|
|
||||||
|
MDSwitch:
|
||||||
|
id: settings_trusted_markup_only
|
||||||
|
pos_hint: {"center_y": 0.3}
|
||||||
|
disabled: False
|
||||||
|
active: False
|
||||||
|
|
||||||
MDBoxLayout:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
@ -1702,21 +1783,21 @@ MDScreen:
|
|||||||
disabled: False
|
disabled: False
|
||||||
active: False
|
active: False
|
||||||
|
|
||||||
MDBoxLayout:
|
# MDBoxLayout:
|
||||||
orientation: "horizontal"
|
# orientation: "horizontal"
|
||||||
size_hint_y: None
|
# size_hint_y: None
|
||||||
padding: [0,0,dp(24),dp(0)]
|
# padding: [0,0,dp(24),dp(0)]
|
||||||
height: dp(48)
|
# height: dp(48)
|
||||||
|
|
||||||
MDLabel:
|
# MDLabel:
|
||||||
text: "Use Home Node as Broadcast Repeater"
|
# text: "Use Home Node as Broadcast Repeater"
|
||||||
font_style: "H6"
|
# font_style: "H6"
|
||||||
|
|
||||||
MDSwitch:
|
# MDSwitch:
|
||||||
id: settings_home_node_as_broadcast_repeater
|
# id: settings_home_node_as_broadcast_repeater
|
||||||
pos_hint: {"center_y": 0.3}
|
# pos_hint: {"center_y": 0.3}
|
||||||
active: False
|
# active: False
|
||||||
disabled: True
|
# disabled: True
|
||||||
|
|
||||||
MDBoxLayout:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
@ -2603,21 +2684,6 @@ MDScreen:
|
|||||||
pos_hint: {"center_y": 0.3}
|
pos_hint: {"center_y": 0.3}
|
||||||
active: False
|
active: False
|
||||||
|
|
||||||
MDBoxLayout:
|
|
||||||
orientation: "horizontal"
|
|
||||||
size_hint_y: None
|
|
||||||
padding: [0,0,dp(24),dp(0)]
|
|
||||||
height: dp(48)
|
|
||||||
|
|
||||||
MDLabel:
|
|
||||||
text: "Device requires BLE"
|
|
||||||
font_style: "H6"
|
|
||||||
|
|
||||||
MDSwitch:
|
|
||||||
id: hardware_rnode_ble
|
|
||||||
pos_hint: {"center_y": 0.3}
|
|
||||||
active: False
|
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
id: hardware_rnode_info
|
id: hardware_rnode_info
|
||||||
markup: True
|
markup: True
|
||||||
|
@ -35,11 +35,13 @@ if RNS.vendor.platformutils.get_platform() == "android":
|
|||||||
from sideband.sense import Telemeter, Commands
|
from sideband.sense import Telemeter, Commands
|
||||||
from ui.helpers import ts_format, file_ts_format, mdc
|
from ui.helpers import ts_format, file_ts_format, mdc
|
||||||
from ui.helpers import color_playing, color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light, intensity_play_dark, intensity_play_light
|
from ui.helpers import color_playing, color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light, intensity_play_dark, intensity_play_light
|
||||||
|
from ui.helpers import color_received_alt, color_received_alt_light, color_delivered_alt, color_propagated_alt, color_paper_alt, color_failed_alt, color_unknown_alt, color_playing_alt, intensity_msgs_dark_alt, intensity_msgs_light_alt, intensity_delivered_alt_dark
|
||||||
else:
|
else:
|
||||||
import sbapp.plyer as plyer
|
import sbapp.plyer as plyer
|
||||||
from sbapp.sideband.sense import Telemeter, Commands
|
from sbapp.sideband.sense import Telemeter, Commands
|
||||||
from .helpers import ts_format, file_ts_format, mdc
|
from .helpers import ts_format, file_ts_format, mdc
|
||||||
from .helpers import color_playing, color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light, intensity_play_dark, intensity_play_light
|
from .helpers import color_playing, color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light, intensity_play_dark, intensity_play_light
|
||||||
|
from .helpers import color_received_alt, color_received_alt_light, color_delivered_alt, color_propagated_alt, color_paper_alt, color_failed_alt, color_unknown_alt, color_playing_alt, intensity_msgs_dark_alt, intensity_msgs_light_alt, intensity_delivered_alt_dark
|
||||||
|
|
||||||
if RNS.vendor.platformutils.is_darwin():
|
if RNS.vendor.platformutils.is_darwin():
|
||||||
from PIL import Image as PilImage
|
from PIL import Image as PilImage
|
||||||
@ -203,6 +205,23 @@ class Messages():
|
|||||||
self.ids.message_text.input_type = "text"
|
self.ids.message_text.input_type = "text"
|
||||||
self.ids.message_text.keyboard_suggestions = True
|
self.ids.message_text.keyboard_suggestions = True
|
||||||
|
|
||||||
|
if not self.app.sideband.config["classic_message_colors"]:
|
||||||
|
c_delivered = color_delivered_alt
|
||||||
|
c_received = color_received_alt
|
||||||
|
c_propagated = color_propagated_alt
|
||||||
|
c_playing = color_playing_alt
|
||||||
|
c_paper = color_paper_alt
|
||||||
|
c_unknown = color_unknown_alt
|
||||||
|
c_failed = color_failed_alt
|
||||||
|
else:
|
||||||
|
c_delivered = color_delivered
|
||||||
|
c_received = color_received
|
||||||
|
c_propagated = color_propagated
|
||||||
|
c_playing = color_playing
|
||||||
|
c_paper = color_paper
|
||||||
|
c_unknown = color_unknown
|
||||||
|
c_failed = color_failed
|
||||||
|
|
||||||
for new_message in self.app.sideband.list_messages(self.context_dest, after=self.latest_message_timestamp,limit=limit):
|
for new_message in self.app.sideband.list_messages(self.context_dest, after=self.latest_message_timestamp,limit=limit):
|
||||||
self.new_messages.append(new_message)
|
self.new_messages.append(new_message)
|
||||||
|
|
||||||
@ -241,12 +260,24 @@ class Messages():
|
|||||||
if (len(self.added_item_hashes) < self.db_message_count) and not self.load_more_button in self.list.children:
|
if (len(self.added_item_hashes) < self.db_message_count) and not self.load_more_button in self.list.children:
|
||||||
self.list.add_widget(self.load_more_button, len(self.list.children))
|
self.list.add_widget(self.load_more_button, len(self.list.children))
|
||||||
|
|
||||||
if self.app.sideband.config["dark_ui"]:
|
if self.app.sideband.config["classic_message_colors"]:
|
||||||
intensity_msgs = intensity_msgs_dark
|
if self.app.sideband.config["dark_ui"]:
|
||||||
intensity_play = intensity_play_dark
|
intensity_msgs = intensity_msgs_dark
|
||||||
|
intensity_play = intensity_play_dark
|
||||||
|
intensity_delivered = intensity_msgs
|
||||||
|
else:
|
||||||
|
intensity_msgs = intensity_msgs_light
|
||||||
|
intensity_play = intensity_play_light
|
||||||
|
intensity_delivered = intensity_msgs
|
||||||
else:
|
else:
|
||||||
intensity_msgs = intensity_msgs_light
|
if self.app.sideband.config["dark_ui"]:
|
||||||
intensity_play = intensity_play_light
|
intensity_msgs = intensity_msgs_dark_alt
|
||||||
|
intensity_play = intensity_play_dark
|
||||||
|
intensity_delivered = intensity_delivered_alt_dark
|
||||||
|
else:
|
||||||
|
intensity_msgs = intensity_msgs_light_alt
|
||||||
|
intensity_play = intensity_play_light
|
||||||
|
intensity_delivered = intensity_msgs
|
||||||
|
|
||||||
for w in self.widgets:
|
for w in self.widgets:
|
||||||
m = w.m
|
m = w.m
|
||||||
@ -271,7 +302,7 @@ class Messages():
|
|||||||
delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8")
|
delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8")
|
||||||
|
|
||||||
if msg["state"] == LXMF.LXMessage.OUTBOUND or msg["state"] == LXMF.LXMessage.SENDING or msg["state"] == LXMF.LXMessage.SENT:
|
if msg["state"] == LXMF.LXMessage.OUTBOUND or msg["state"] == LXMF.LXMessage.SENDING or msg["state"] == LXMF.LXMessage.SENT:
|
||||||
w.md_bg_color = msg_color = mdc(color_unknown, intensity_msgs)
|
w.md_bg_color = msg_color = mdc(c_unknown, intensity_msgs)
|
||||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
prgstr = ""
|
prgstr = ""
|
||||||
@ -305,7 +336,7 @@ class Messages():
|
|||||||
|
|
||||||
|
|
||||||
if msg["state"] == LXMF.LXMessage.DELIVERED:
|
if msg["state"] == LXMF.LXMessage.DELIVERED:
|
||||||
w.md_bg_color = msg_color = mdc(color_delivered, intensity_msgs)
|
w.md_bg_color = msg_color = mdc(c_delivered, intensity_delivered)
|
||||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -317,7 +348,7 @@ class Messages():
|
|||||||
m["state"] = msg["state"]
|
m["state"] = msg["state"]
|
||||||
|
|
||||||
if msg["method"] == LXMF.LXMessage.PAPER:
|
if msg["method"] == LXMF.LXMessage.PAPER:
|
||||||
w.md_bg_color = msg_color = mdc(color_paper, intensity_msgs)
|
w.md_bg_color = msg_color = mdc(c_paper, intensity_msgs)
|
||||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -326,7 +357,7 @@ class Messages():
|
|||||||
m["state"] = msg["state"]
|
m["state"] = msg["state"]
|
||||||
|
|
||||||
if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT:
|
if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT:
|
||||||
w.md_bg_color = msg_color = mdc(color_propagated, intensity_msgs)
|
w.md_bg_color = msg_color = mdc(c_propagated, intensity_msgs)
|
||||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -338,7 +369,7 @@ class Messages():
|
|||||||
m["state"] = msg["state"]
|
m["state"] = msg["state"]
|
||||||
|
|
||||||
if msg["state"] == LXMF.LXMessage.FAILED:
|
if msg["state"] == LXMF.LXMessage.FAILED:
|
||||||
w.md_bg_color = msg_color = mdc(color_failed, intensity_msgs)
|
w.md_bg_color = msg_color = mdc(c_failed, intensity_msgs)
|
||||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -361,14 +392,49 @@ class Messages():
|
|||||||
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
|
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
|
||||||
|
|
||||||
def update_widget(self):
|
def update_widget(self):
|
||||||
if self.app.sideband.config["dark_ui"]:
|
|
||||||
intensity_msgs = intensity_msgs_dark
|
if self.app.sideband.config["classic_message_colors"]:
|
||||||
intensity_play = intensity_play_dark
|
if self.app.sideband.config["dark_ui"]:
|
||||||
mt_color = [1.0, 1.0, 1.0, 0.8]
|
intensity_msgs = intensity_msgs_dark
|
||||||
|
intensity_play = intensity_play_dark
|
||||||
|
intensity_delivered = intensity_msgs
|
||||||
|
mt_color = [1.0, 1.0, 1.0, 0.8]
|
||||||
|
else:
|
||||||
|
intensity_msgs = intensity_msgs_light
|
||||||
|
intensity_play = intensity_play_light
|
||||||
|
intensity_delivered = intensity_msgs
|
||||||
|
mt_color = [1.0, 1.0, 1.0, 0.95]
|
||||||
else:
|
else:
|
||||||
intensity_msgs = intensity_msgs_light
|
if self.app.sideband.config["dark_ui"]:
|
||||||
intensity_play = intensity_play_light
|
intensity_msgs = intensity_msgs_dark_alt
|
||||||
mt_color = [1.0, 1.0, 1.0, 0.95]
|
intensity_play = intensity_play_dark
|
||||||
|
intensity_delivered = intensity_delivered_alt_dark
|
||||||
|
mt_color = [1.0, 1.0, 1.0, 0.8]
|
||||||
|
else:
|
||||||
|
intensity_msgs = intensity_msgs_light_alt
|
||||||
|
intensity_play = intensity_play_light
|
||||||
|
intensity_delivered = intensity_msgs
|
||||||
|
mt_color = [1.0, 1.0, 1.0, 0.95]
|
||||||
|
|
||||||
|
if not self.app.sideband.config["classic_message_colors"]:
|
||||||
|
if self.app.sideband.config["dark_ui"]:
|
||||||
|
c_received = color_received_alt
|
||||||
|
else:
|
||||||
|
c_received = color_received_alt_light
|
||||||
|
c_delivered = color_delivered_alt
|
||||||
|
c_propagated = color_propagated_alt
|
||||||
|
c_playing = color_playing_alt
|
||||||
|
c_paper = color_paper_alt
|
||||||
|
c_unknown = color_unknown_alt
|
||||||
|
c_failed = color_failed_alt
|
||||||
|
else:
|
||||||
|
c_delivered = color_delivered
|
||||||
|
c_received = color_received
|
||||||
|
c_propagated = color_propagated
|
||||||
|
c_playing = color_playing
|
||||||
|
c_paper = color_paper
|
||||||
|
c_unknown = color_unknown
|
||||||
|
c_failed = color_failed
|
||||||
|
|
||||||
self.ids.message_text.font_name = self.app.input_font
|
self.ids.message_text.font_name = self.app.input_font
|
||||||
|
|
||||||
@ -378,7 +444,7 @@ class Messages():
|
|||||||
for m in self.new_messages:
|
for m in self.new_messages:
|
||||||
if not m["hash"] in self.added_item_hashes:
|
if not m["hash"] in self.added_item_hashes:
|
||||||
try:
|
try:
|
||||||
if not self.is_trusted:
|
if self.app.sideband.config["trusted_markup_only"] and not self.is_trusted:
|
||||||
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
|
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
|
||||||
else:
|
else:
|
||||||
message_input = m["content"]
|
message_input = m["content"]
|
||||||
@ -524,31 +590,31 @@ class Messages():
|
|||||||
|
|
||||||
if m["source"] == self.app.sideband.lxmf_destination.hash:
|
if m["source"] == self.app.sideband.lxmf_destination.hash:
|
||||||
if m["state"] == LXMF.LXMessage.DELIVERED:
|
if m["state"] == LXMF.LXMessage.DELIVERED:
|
||||||
msg_color = mdc(color_delivered, intensity_msgs)
|
msg_color = mdc(c_delivered, intensity_delivered)
|
||||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] Delivered"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] Delivered"
|
||||||
|
|
||||||
elif m["method"] == LXMF.LXMessage.PROPAGATED and m["state"] == LXMF.LXMessage.SENT:
|
elif m["method"] == LXMF.LXMessage.PROPAGATED and m["state"] == LXMF.LXMessage.SENT:
|
||||||
msg_color = mdc(color_propagated, intensity_msgs)
|
msg_color = mdc(c_propagated, intensity_msgs)
|
||||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net"
|
||||||
|
|
||||||
elif m["method"] == LXMF.LXMessage.PAPER:
|
elif m["method"] == LXMF.LXMessage.PAPER:
|
||||||
msg_color = mdc(color_paper, intensity_msgs)
|
msg_color = mdc(c_paper, intensity_msgs)
|
||||||
heading_str = titlestr+"[b]Created[/b] "+txstr+"\n[b]State[/b] Paper Message"
|
heading_str = titlestr+"[b]Created[/b] "+txstr+"\n[b]State[/b] Paper Message"
|
||||||
|
|
||||||
elif m["state"] == LXMF.LXMessage.FAILED:
|
elif m["state"] == LXMF.LXMessage.FAILED:
|
||||||
msg_color = mdc(color_failed, intensity_msgs)
|
msg_color = mdc(c_failed, intensity_msgs)
|
||||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed"
|
||||||
|
|
||||||
elif m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENDING:
|
elif m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENDING:
|
||||||
msg_color = mdc(color_unknown, intensity_msgs)
|
msg_color = mdc(c_unknown, intensity_msgs)
|
||||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Sending "
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Sending "
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg_color = mdc(color_unknown, intensity_msgs)
|
msg_color = mdc(c_unknown, intensity_msgs)
|
||||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Unknown"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Unknown"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg_color = mdc(color_received, intensity_msgs)
|
msg_color = mdc(c_received, intensity_msgs)
|
||||||
heading_str = titlestr
|
heading_str = titlestr
|
||||||
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
|
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
|
||||||
heading_str += phy_stats_str+"\n"
|
heading_str += phy_stats_str+"\n"
|
||||||
@ -598,9 +664,9 @@ class Messages():
|
|||||||
self.app.play_audio_field(sender.audio_field)
|
self.app.play_audio_field(sender.audio_field)
|
||||||
stored_color = sender.md_bg_color
|
stored_color = sender.md_bg_color
|
||||||
if sender.lsource == self.app.sideband.lxmf_destination.hash:
|
if sender.lsource == self.app.sideband.lxmf_destination.hash:
|
||||||
sender.md_bg_color = mdc(color_delivered, intensity_play)
|
sender.md_bg_color = mdc(c_delivered, intensity_play)
|
||||||
else:
|
else:
|
||||||
sender.md_bg_color = mdc(color_received, intensity_play)
|
sender.md_bg_color = mdc(c_received, intensity_play)
|
||||||
|
|
||||||
def cb(dt):
|
def cb(dt):
|
||||||
sender.md_bg_color = stored_color
|
sender.md_bg_color = stored_color
|
||||||
@ -653,6 +719,19 @@ class Messages():
|
|||||||
item.ids.content_text.owner = item
|
item.ids.content_text.owner = item
|
||||||
item.ids.content_text.bind(texture_size=check_textures)
|
item.ids.content_text.bind(texture_size=check_textures)
|
||||||
|
|
||||||
|
def cbf(w):
|
||||||
|
def x(dt):
|
||||||
|
if w.texture_size[0] == 0 and w.texture_size[1] == 0:
|
||||||
|
w.markup = False
|
||||||
|
escaped_content = escape_markup(w.text)
|
||||||
|
def deferred(dt):
|
||||||
|
w.text = "[i]This message could not be rendered correctly, likely due to an error in its markup. Falling back to plain-text rendering.[/i]\n\n"+escaped_content
|
||||||
|
w.markup = True
|
||||||
|
Clock.schedule_once(deferred, 0.1)
|
||||||
|
return x
|
||||||
|
|
||||||
|
Clock.schedule_once(cbf(item.ids.content_text), 0.25)
|
||||||
|
|
||||||
if not RNS.vendor.platformutils.is_android():
|
if not RNS.vendor.platformutils.is_android():
|
||||||
item.radius = dp(5)
|
item.radius = dp(5)
|
||||||
|
|
||||||
|
@ -148,6 +148,15 @@ class ObjectDetails():
|
|||||||
else:
|
else:
|
||||||
self.from_objects = False
|
self.from_objects = False
|
||||||
|
|
||||||
|
if self.viewing_self:
|
||||||
|
self.screen.ids.track_button.disabled = True
|
||||||
|
else:
|
||||||
|
self.screen.ids.track_button.disabled = False
|
||||||
|
if self.app.sideband.is_tracking(source_dest):
|
||||||
|
self.screen.ids.track_button.text = "Stop Live Tracking"
|
||||||
|
else:
|
||||||
|
self.screen.ids.track_button.text = "Start Live Tracking"
|
||||||
|
|
||||||
self.coords = None
|
self.coords = None
|
||||||
self.telemetry_list.data = []
|
self.telemetry_list.data = []
|
||||||
pds = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(source_dest))).encode("utf-8")).decode("utf-8")
|
pds = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(source_dest))).encode("utf-8")).decode("utf-8")
|
||||||
@ -218,6 +227,15 @@ class ObjectDetails():
|
|||||||
self.clear_widget()
|
self.clear_widget()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def live_tracking(self, sender):
|
||||||
|
if not self.viewing_self:
|
||||||
|
if not self.app.sideband.is_tracking(self.object_hash):
|
||||||
|
self.app.sideband.start_tracking(self.object_hash, interval=59, duration=7*24*60*60)
|
||||||
|
self.screen.ids.track_button.text = "Stop Live Tracking"
|
||||||
|
else:
|
||||||
|
self.app.sideband.stop_tracking(self.object_hash)
|
||||||
|
self.screen.ids.track_button.text = "Start Live Tracking"
|
||||||
|
|
||||||
def send_update(self):
|
def send_update(self):
|
||||||
if not self.viewing_self:
|
if not self.viewing_self:
|
||||||
result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash)
|
result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash)
|
||||||
@ -643,10 +661,9 @@ class RVDetails(MDRecycleView):
|
|||||||
alt_str = RNS.prettydistance(alt)
|
alt_str = RNS.prettydistance(alt)
|
||||||
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
|
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
|
||||||
if speed != None:
|
if speed != None:
|
||||||
if speed > 0.02:
|
if speed > 0.1:
|
||||||
speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]"
|
speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]"
|
||||||
else:
|
else:
|
||||||
# speed_formatted_values = f"Speed [b]0 Km/h[/b]"
|
|
||||||
speed_formatted_values = f"Object is [b]stationary[/b]"
|
speed_formatted_values = f"Object is [b]stationary[/b]"
|
||||||
else:
|
else:
|
||||||
speed_formatted_values = None
|
speed_formatted_values = None
|
||||||
@ -972,22 +989,22 @@ MDScreen:
|
|||||||
on_release: root.delegate.request_update()
|
on_release: root.delegate.request_update()
|
||||||
disabled: False
|
disabled: False
|
||||||
|
|
||||||
# MDBoxLayout:
|
MDBoxLayout:
|
||||||
# orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
# spacing: dp(16)
|
spacing: dp(16)
|
||||||
# size_hint_y: None
|
size_hint_y: None
|
||||||
# height: self.minimum_height
|
height: self.minimum_height
|
||||||
# padding: [dp(24), dp(16), dp(24), dp(24)]
|
padding: [dp(24), dp(0), dp(24), dp(24)]
|
||||||
|
|
||||||
# MDRectangleFlatIconButton:
|
MDRectangleFlatIconButton:
|
||||||
# id: delete_button
|
id: track_button
|
||||||
# icon: "trash-can-outline"
|
icon: "crosshairs-gps"
|
||||||
# text: "Delete All Telemetry"
|
text: "Start Live Tracking"
|
||||||
# padding: [dp(0), dp(14), dp(0), dp(14)]
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
# icon_size: dp(24)
|
icon_size: dp(24)
|
||||||
# font_size: dp(16)
|
font_size: dp(16)
|
||||||
# size_hint: [1.0, None]
|
size_hint: [1.0, None]
|
||||||
# on_release: root.delegate.copy_telemetry(self)
|
on_release: root.delegate.live_tracking(self)
|
||||||
# disabled: False
|
disabled: False
|
||||||
|
|
||||||
"""
|
"""
|
441
sbapp/ui/utilities.py
Normal file
441
sbapp/ui/utilities.py
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
import time
|
||||||
|
import RNS
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
from kivy.metrics import dp,sp
|
||||||
|
from kivy.lang.builder import Builder
|
||||||
|
from kivy.core.clipboard import Clipboard
|
||||||
|
from kivy.utils import escape_markup
|
||||||
|
from kivymd.uix.recycleview import MDRecycleView
|
||||||
|
from kivymd.uix.list import OneLineIconListItem
|
||||||
|
from kivymd.uix.pickers import MDColorPicker
|
||||||
|
from kivymd.uix.button import MDRectangleFlatButton
|
||||||
|
from kivymd.uix.dialog import MDDialog
|
||||||
|
from kivymd.icon_definitions import md_icons
|
||||||
|
from kivymd.toast import toast
|
||||||
|
from kivy.properties import StringProperty, BooleanProperty
|
||||||
|
from kivy.effects.scroll import ScrollEffect
|
||||||
|
from kivy.clock import Clock
|
||||||
|
from sideband.sense import Telemeter
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
|
from ui.helpers import ts_format
|
||||||
|
from android.permissions import request_permissions, check_permission
|
||||||
|
else:
|
||||||
|
from .helpers import ts_format
|
||||||
|
|
||||||
|
class Utilities():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.screen = None
|
||||||
|
self.rnstatus_screen = None
|
||||||
|
self.rnstatus_instance = None
|
||||||
|
self.logviewer_screen = None
|
||||||
|
|
||||||
|
if not self.app.root.ids.screen_manager.has_screen("utilities_screen"):
|
||||||
|
self.screen = Builder.load_string(layout_utilities_screen)
|
||||||
|
self.screen.app = self.app
|
||||||
|
self.screen.delegate = self
|
||||||
|
self.app.root.ids.screen_manager.add_widget(self.screen)
|
||||||
|
|
||||||
|
self.screen.ids.telemetry_scrollview.effect_cls = ScrollEffect
|
||||||
|
info = "This section contains various utilities and diagnostics tools, "
|
||||||
|
info += "that can be helpful while using Sideband and Reticulum."
|
||||||
|
|
||||||
|
if self.app.theme_cls.theme_style == "Dark":
|
||||||
|
info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]"
|
||||||
|
|
||||||
|
self.screen.ids.utilities_info.text = info
|
||||||
|
|
||||||
|
|
||||||
|
### RNode Flasher
|
||||||
|
######################################
|
||||||
|
|
||||||
|
def flasher_action(self, sender=None):
|
||||||
|
yes_button = MDRectangleFlatButton(text="Launch",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_accept, text_color=self.app.color_accept)
|
||||||
|
no_button = MDRectangleFlatButton(text="Back",font_size=dp(18))
|
||||||
|
dialog = MDDialog(
|
||||||
|
title="RNode Flasher",
|
||||||
|
text="You can use the included web-based RNode flasher, by starting Sideband's built-in repository server, and accessing the RNode Flasher page.",
|
||||||
|
buttons=[ no_button, yes_button ],
|
||||||
|
# elevation=0,
|
||||||
|
)
|
||||||
|
def dl_yes(s):
|
||||||
|
dialog.dismiss()
|
||||||
|
self.app.sideband.start_webshare()
|
||||||
|
def cb(dt):
|
||||||
|
self.app.repository_action()
|
||||||
|
Clock.schedule_once(cb, 0.6)
|
||||||
|
|
||||||
|
def dl_no(s):
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
yes_button.bind(on_release=dl_yes)
|
||||||
|
no_button.bind(on_release=dl_no)
|
||||||
|
dialog.open()
|
||||||
|
|
||||||
|
|
||||||
|
### rnstatus screen
|
||||||
|
######################################
|
||||||
|
|
||||||
|
def rnstatus_action(self, sender=None):
|
||||||
|
if not self.app.root.ids.screen_manager.has_screen("rnstatus_screen"):
|
||||||
|
self.rnstatus_screen = Builder.load_string(layout_rnstatus_screen)
|
||||||
|
self.rnstatus_screen.app = self.app
|
||||||
|
self.rnstatus_screen.delegate = self
|
||||||
|
self.app.root.ids.screen_manager.add_widget(self.rnstatus_screen)
|
||||||
|
|
||||||
|
self.app.root.ids.screen_manager.transition.direction = "left"
|
||||||
|
self.app.root.ids.screen_manager.current = "rnstatus_screen"
|
||||||
|
self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current)
|
||||||
|
|
||||||
|
self.update_rnstatus()
|
||||||
|
|
||||||
|
def update_rnstatus(self, sender=None):
|
||||||
|
threading.Thread(target=self.update_rnstatus_job, daemon=True).start()
|
||||||
|
|
||||||
|
def update_rnstatus_job(self, sender=None):
|
||||||
|
if self.rnstatus_instance == None:
|
||||||
|
import RNS.Utilities.rnstatus as rnstatus
|
||||||
|
self.rnstatus_instance = rnstatus
|
||||||
|
|
||||||
|
import io
|
||||||
|
from contextlib import redirect_stdout
|
||||||
|
output = "None"
|
||||||
|
with io.StringIO() as buffer, redirect_stdout(buffer):
|
||||||
|
with RNS.logging_lock:
|
||||||
|
self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance())
|
||||||
|
output = buffer.getvalue()
|
||||||
|
|
||||||
|
def cb(dt):
|
||||||
|
self.rnstatus_screen.ids.rnstatus_output.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]{output}[/size][/font]"
|
||||||
|
Clock.schedule_once(cb, 0.2)
|
||||||
|
|
||||||
|
if self.app.root.ids.screen_manager.current == "rnstatus_screen":
|
||||||
|
Clock.schedule_once(self.update_rnstatus, 1)
|
||||||
|
|
||||||
|
### Advanced Configuration screen
|
||||||
|
######################################
|
||||||
|
|
||||||
|
def advanced_action(self, sender=None):
|
||||||
|
if not self.app.root.ids.screen_manager.has_screen("advanced_screen"):
|
||||||
|
self.advanced_screen = Builder.load_string(layout_advanced_screen)
|
||||||
|
self.advanced_screen.app = self.app
|
||||||
|
self.advanced_screen.delegate = self
|
||||||
|
self.app.root.ids.screen_manager.add_widget(self.advanced_screen)
|
||||||
|
|
||||||
|
self.app.root.ids.screen_manager.transition.direction = "left"
|
||||||
|
self.app.root.ids.screen_manager.current = "advanced_screen"
|
||||||
|
self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current)
|
||||||
|
|
||||||
|
self.update_advanced()
|
||||||
|
|
||||||
|
def update_advanced(self, sender=None):
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
ct = self.app.sideband.config["config_template"]
|
||||||
|
self.advanced_screen.ids.config_template.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]{ct}[/size][/font]"
|
||||||
|
else:
|
||||||
|
self.advanced_screen.ids.config_template.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]On this platform, Reticulum configuration is managed by the system. You can change the configuration by editing the file located at:\n\n{self.app.sideband.reticulum.configpath}[/size][/font]"
|
||||||
|
|
||||||
|
def copy_config(self, sender=None):
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
Clipboard.copy(self.app.sideband.config_template)
|
||||||
|
|
||||||
|
def paste_config(self, sender=None):
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
|
self.app.sideband.config_template = Clipboard.paste()
|
||||||
|
self.app.sideband.config["config_template"] = self.app.sideband.config_template
|
||||||
|
self.app.sideband.save_configuration()
|
||||||
|
self.update_advanced()
|
||||||
|
|
||||||
|
### Log viewer screen
|
||||||
|
######################################
|
||||||
|
|
||||||
|
def logviewer_action(self, sender=None):
|
||||||
|
if not self.app.root.ids.screen_manager.has_screen("logviewer_screen"):
|
||||||
|
self.logviewer_screen = Builder.load_string(layout_logviewer_screen)
|
||||||
|
self.logviewer_screen.app = self.app
|
||||||
|
self.logviewer_screen.delegate = self
|
||||||
|
self.app.root.ids.screen_manager.add_widget(self.logviewer_screen)
|
||||||
|
|
||||||
|
self.app.root.ids.screen_manager.transition.direction = "left"
|
||||||
|
self.app.root.ids.screen_manager.current = "logviewer_screen"
|
||||||
|
self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current)
|
||||||
|
|
||||||
|
self.update_logviewer()
|
||||||
|
|
||||||
|
def update_logviewer(self, sender=None):
|
||||||
|
threading.Thread(target=self.update_logviewer_job, daemon=True).start()
|
||||||
|
|
||||||
|
def update_logviewer_job(self, sender=None):
|
||||||
|
try:
|
||||||
|
output = self.app.sideband.get_log()
|
||||||
|
except Exception as e:
|
||||||
|
output = f"An error occurred while retrieving log entries:\n{e}"
|
||||||
|
|
||||||
|
self.logviewer_screen.log_contents = output
|
||||||
|
def cb(dt):
|
||||||
|
self.logviewer_screen.ids.logviewer_output.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]{output}[/size][/font]"
|
||||||
|
Clock.schedule_once(cb, 0.2)
|
||||||
|
|
||||||
|
if self.app.root.ids.screen_manager.current == "logviewer_screen":
|
||||||
|
Clock.schedule_once(self.update_logviewer, 1)
|
||||||
|
|
||||||
|
def logviewer_copy(self, sender=None):
|
||||||
|
Clipboard.copy(self.logviewer_screen.log_contents)
|
||||||
|
if True or RNS.vendor.platformutils.is_android():
|
||||||
|
toast("Log copied to clipboard")
|
||||||
|
|
||||||
|
|
||||||
|
layout_utilities_screen = """
|
||||||
|
MDScreen:
|
||||||
|
name: "utilities_screen"
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
|
||||||
|
MDTopAppBar:
|
||||||
|
title: "Utilities"
|
||||||
|
anchor_title: "left"
|
||||||
|
elevation: 0
|
||||||
|
left_action_items:
|
||||||
|
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||||
|
right_action_items:
|
||||||
|
[
|
||||||
|
['close', lambda x: root.app.close_any_action(self)],
|
||||||
|
]
|
||||||
|
|
||||||
|
ScrollView:
|
||||||
|
id: telemetry_scrollview
|
||||||
|
|
||||||
|
MDBoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
padding: [dp(28), dp(32), dp(28), dp(16)]
|
||||||
|
|
||||||
|
# MDLabel:
|
||||||
|
# text: "Utilities & Tools"
|
||||||
|
# font_style: "H6"
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
id: utilities_info
|
||||||
|
markup: True
|
||||||
|
text: ""
|
||||||
|
size_hint_y: None
|
||||||
|
text_size: self.width, None
|
||||||
|
height: self.texture_size[1]
|
||||||
|
|
||||||
|
MDBoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
spacing: "24dp"
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
padding: [dp(0), dp(35), dp(0), dp(35)]
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: rnstatus_button
|
||||||
|
icon: "wifi-check"
|
||||||
|
text: "Reticulum Status"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.delegate.rnstatus_action(self)
|
||||||
|
disabled: False
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: logview_button
|
||||||
|
icon: "list-box-outline"
|
||||||
|
text: "Log Viewer"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.delegate.logviewer_action(self)
|
||||||
|
disabled: False
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: flasher_button
|
||||||
|
icon: "radio-handheld"
|
||||||
|
text: "RNode Flasher"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.delegate.flasher_action(self)
|
||||||
|
disabled: False
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: advanced_button
|
||||||
|
icon: "network-pos"
|
||||||
|
text: "Advanced RNS Configuration"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.delegate.advanced_action(self)
|
||||||
|
disabled: False
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
layout_rnstatus_screen = """
|
||||||
|
MDScreen:
|
||||||
|
name: "rnstatus_screen"
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
|
||||||
|
MDTopAppBar:
|
||||||
|
id: top_bar
|
||||||
|
title: "Reticulum Status"
|
||||||
|
anchor_title: "left"
|
||||||
|
elevation: 0
|
||||||
|
left_action_items:
|
||||||
|
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||||
|
right_action_items:
|
||||||
|
[
|
||||||
|
# ['refresh', lambda x: root.delegate.update_rnstatus()],
|
||||||
|
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||||
|
]
|
||||||
|
|
||||||
|
MDScrollView:
|
||||||
|
id: rnstatus_scrollview
|
||||||
|
size_hint_x: 1
|
||||||
|
size_hint_y: None
|
||||||
|
size: [root.width, root.height-root.ids.top_bar.height]
|
||||||
|
do_scroll_x: False
|
||||||
|
do_scroll_y: True
|
||||||
|
|
||||||
|
MDGridLayout:
|
||||||
|
cols: 1
|
||||||
|
padding: [dp(28), dp(14), dp(28), dp(28)]
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
id: rnstatus_output
|
||||||
|
markup: True
|
||||||
|
text: ""
|
||||||
|
size_hint_y: None
|
||||||
|
text_size: self.width, None
|
||||||
|
height: self.texture_size[1]
|
||||||
|
"""
|
||||||
|
|
||||||
|
layout_logviewer_screen = """
|
||||||
|
MDScreen:
|
||||||
|
name: "logviewer_screen"
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
|
||||||
|
MDTopAppBar:
|
||||||
|
id: top_bar
|
||||||
|
title: "Log Viewer"
|
||||||
|
anchor_title: "left"
|
||||||
|
elevation: 0
|
||||||
|
left_action_items:
|
||||||
|
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||||
|
right_action_items:
|
||||||
|
[
|
||||||
|
['content-copy', lambda x: root.delegate.logviewer_copy()],
|
||||||
|
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||||
|
]
|
||||||
|
|
||||||
|
MDScrollView:
|
||||||
|
id: logviewer_scrollview
|
||||||
|
size_hint_x: 1
|
||||||
|
size_hint_y: None
|
||||||
|
size: [root.width, root.height-root.ids.top_bar.height]
|
||||||
|
do_scroll_x: False
|
||||||
|
do_scroll_y: True
|
||||||
|
|
||||||
|
MDGridLayout:
|
||||||
|
cols: 1
|
||||||
|
padding: [dp(28), dp(14), dp(28), dp(28)]
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
id: logviewer_output
|
||||||
|
markup: True
|
||||||
|
text: ""
|
||||||
|
size_hint_y: None
|
||||||
|
text_size: self.width, None
|
||||||
|
height: self.texture_size[1]
|
||||||
|
"""
|
||||||
|
|
||||||
|
layout_advanced_screen = """
|
||||||
|
MDScreen:
|
||||||
|
name: "advanced_screen"
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
|
||||||
|
MDTopAppBar:
|
||||||
|
id: top_bar
|
||||||
|
title: "RNS Configuration"
|
||||||
|
anchor_title: "left"
|
||||||
|
elevation: 0
|
||||||
|
left_action_items:
|
||||||
|
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||||
|
right_action_items:
|
||||||
|
[
|
||||||
|
# ['refresh', lambda x: root.delegate.update_rnstatus()],
|
||||||
|
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||||
|
]
|
||||||
|
|
||||||
|
MDScrollView:
|
||||||
|
id: advanced_scrollview
|
||||||
|
size_hint_x: 1
|
||||||
|
size_hint_y: None
|
||||||
|
size: [root.width, root.height-root.ids.top_bar.height]
|
||||||
|
do_scroll_x: False
|
||||||
|
do_scroll_y: True
|
||||||
|
|
||||||
|
MDGridLayout:
|
||||||
|
cols: 1
|
||||||
|
padding: [dp(28), dp(14), dp(28), dp(28)]
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
|
||||||
|
MDBoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
spacing: dp(24)
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(24)]
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: telemetry_button
|
||||||
|
icon: "content-copy"
|
||||||
|
text: "Copy Configuration"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.delegate.copy_config(self)
|
||||||
|
disabled: False
|
||||||
|
|
||||||
|
MDRectangleFlatIconButton:
|
||||||
|
id: coordinates_button
|
||||||
|
icon: "download"
|
||||||
|
text: "Paste Configuration"
|
||||||
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
|
icon_size: dp(24)
|
||||||
|
font_size: dp(16)
|
||||||
|
size_hint: [1.0, None]
|
||||||
|
on_release: root.delegate.paste_config(self)
|
||||||
|
disabled: False
|
||||||
|
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
id: config_template
|
||||||
|
markup: True
|
||||||
|
text: ""
|
||||||
|
size_hint_y: None
|
||||||
|
text_size: self.width, None
|
||||||
|
height: self.texture_size[1]
|
||||||
|
"""
|
24
setup.py
24
setup.py
@ -47,6 +47,20 @@ def glob_paths(pattern):
|
|||||||
|
|
||||||
return out_files
|
return out_files
|
||||||
|
|
||||||
|
def glob_share():
|
||||||
|
out_files = []
|
||||||
|
src_path = os.path.join(os.path.dirname(__file__), "sbapp/share")
|
||||||
|
print(src_path)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(src_path):
|
||||||
|
for file in files:
|
||||||
|
filepath = os.path.join(str(Path(*Path(root).parts[1:])), file)
|
||||||
|
|
||||||
|
if not "mirrors/unsigned.io" in str(filepath):
|
||||||
|
out_files.append(filepath.split(f"sbapp{os.sep}")[1])
|
||||||
|
|
||||||
|
return out_files
|
||||||
|
|
||||||
packages = setuptools.find_packages(
|
packages = setuptools.find_packages(
|
||||||
exclude=[
|
exclude=[
|
||||||
"sbapp.plyer.platforms.android",
|
"sbapp.plyer.platforms.android",
|
||||||
@ -63,6 +77,7 @@ package_data = {
|
|||||||
"kivymd/images/*",
|
"kivymd/images/*",
|
||||||
"kivymd/*",
|
"kivymd/*",
|
||||||
"mapview/icons/*",
|
"mapview/icons/*",
|
||||||
|
*glob_share(),
|
||||||
*glob_paths(".kv")
|
*glob_paths(".kv")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -99,8 +114,8 @@ setuptools.setup(
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"rns>=0.8.4",
|
"rns>=0.8.8",
|
||||||
"lxmf>=0.5.7",
|
"lxmf>=0.5.8",
|
||||||
"kivy>=2.3.0",
|
"kivy>=2.3.0",
|
||||||
"pillow>=10.2.0",
|
"pillow>=10.2.0",
|
||||||
"qrcode",
|
"qrcode",
|
||||||
@ -108,11 +123,10 @@ setuptools.setup(
|
|||||||
"ffpyplayer",
|
"ffpyplayer",
|
||||||
"sh",
|
"sh",
|
||||||
"numpy<=1.26.4",
|
"numpy<=1.26.4",
|
||||||
"pycodec2;platform_system!='Windows'",
|
"pycodec2;sys.platform!='Windows' and sys.platform!='win32' and sys.platform!='darwin'",
|
||||||
"pyaudio;sys.platform=='linux'",
|
"pyaudio;sys.platform=='linux'",
|
||||||
"pyobjus;sys.platform=='darwin'",
|
"pyobjus;sys.platform=='darwin'",
|
||||||
"pyogg;sys.platform=='darwin'",
|
"pyogg;sys.platform=='Windows' and sys.platform!='win32'",
|
||||||
"pyogg;platform_system=='Windows'",
|
|
||||||
],
|
],
|
||||||
python_requires='>=3.7',
|
python_requires='>=3.7',
|
||||||
)
|
)
|
||||||
|
68
sideband.spec
Normal file
68
sideband.spec
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
from kivy_deps import sdl2, glew
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
def extra_datas(mydir):
|
||||||
|
def rec_glob(p, files):
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
for d in glob.glob(p):
|
||||||
|
if os.path.isfile(d):
|
||||||
|
files.append(d)
|
||||||
|
rec_glob("%s/*" % d, files)
|
||||||
|
files = []
|
||||||
|
rec_glob("%s/*" % mydir, files)
|
||||||
|
extra_datas = []
|
||||||
|
for f in files:
|
||||||
|
extra_datas.append((f, f, 'DATA'))
|
||||||
|
|
||||||
|
return extra_datas
|
||||||
|
|
||||||
|
a.datas += extra_datas('sbapp')
|
||||||
|
a.datas += extra_datas('RNS')
|
||||||
|
a.datas += extra_datas('LXMF')
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
|
name='Sideband',
|
||||||
|
icon="sbapp\\assets\\icon.png",
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
coll = COLLECT(
|
||||||
|
exe,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
name='main',
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user