Compare commits

...

104 commits

Author SHA1 Message Date
Florian Märkl
a1fd418685 Update Ubuntu for AppImage to 24.04 2025-06-25 16:56:44 +02:00
Florian Märkl
bb5a79f234 Update dependencies in BSDs CI 2025-06-22 11:57:41 +02:00
Florian Märkl
4eb90a7a65 Refresh switch to build again 2025-06-09 12:20:27 +02:00
Florian Märkl
94fcdc3c61 Add reference to chiaki-ng 2024-07-31 15:54:39 +02:00
Florian Märkl
8911a44766 Remove Play Store from README.md 2023-08-20 12:53:52 +02:00
Florian Märkl
89368f63c9 Add script for local macOS distribution 2023-08-20 11:42:20 +02:00
Florian Märkl
d4a0603bf2 Fix switch host_addr regex for more arbitrary strings 2023-08-20 11:17:05 +02:00
Florian Märkl
666238ba9f Bump version to 2.2.0 2023-08-20 11:12:11 +02:00
Florian Märkl
bcdd0dd7fd Update CI images 2023-08-20 09:45:15 +02:00
Florian Märkl
6096de8c13 Do not handle server shutdown as error
When the console is powered off or put into sleep mode during streaming,
the remote will disconnect and report the string "Server shutting down".
This is expected by the user and thus should not be shown as an error
message.
2023-02-08 14:08:47 +01:00
Florian Märkl
582ec7aa54 Allow specifying command in switch podman script 2023-02-08 14:08:17 +01:00
Florian Märkl
e14083c87c Update android target SDK to 33 and dependencies 2023-02-05 19:46:34 +01:00
Johannes Baiter
c2f0932670 gui: Support for DualSense haptics and trigger effects
Haptics with PulseAudio does not seem to be working properly, so using
Pipewire as a backend is recommended (and picked by default, if
available via an SDL hint).
2023-02-01 18:05:59 +01:00
Johannes Baiter
7a490b5aae Fix feedback state position 0x1b when DualSense is connected.
The previous value of `0` caused the PS5 to expect a set of 'trigger status'
values in the 0x19 and 0x1a position, which would have required reading
raw values from the DualSense HID device, since these values are not
reported by the SDL DualSense driver (code that does so can be checked
out from the `trigger-feedback` branch on https://git.sr.ht/~jbaiter/chiaki).

Fortunately this is not neccessary, simply setting the value to `1`
seems to make the PS5 to rely on fallback logic (presumably based on the
L2/R2 value) and games that would otherwise have relied on the trigger
status (Astro's Playroom climbing levels) now work without any problems.
2023-01-29 13:39:30 +01:00
Johannes Baiter
4c8209762c Add support for touchpad and sensor handling via SDL
This should enable support for more controllers besides the DS4 and
DualSense, basically any controller supported by SDL that has at least
one touchpad, an accelerometer and a gyroscope.

Older SDL versions have been tested down to 2.0.9. Versions older than
2.0.14 won't have sensors and touchpad support, though.

Setsu is deprecated and remains in-tree for now, but defaults to being
disabled if SDL2 is found and >= 2.0.14. If Setsu is enabled explicitly,
touchpad and sensors are not handled by SDL.
2022-12-14 20:22:40 +01:00
Florian Märkl
76690a319c Fix testing on AppVeyor and make appveyor-win.sh more portable
test/chiaki-unit.exe failed to load some OpenSSL dlls somehow, which
broke the build. Moving them next to the executable fixes that.
The APPVEYOR_BUILD_FOLDER env var is also not needed anymore now.
2022-12-14 19:27:15 +01:00
Street Pea
36816db7ac Add quit (Ctrl+Q) shortcut to GUI 2022-12-10 15:13:22 +01:00
Street Pea
801f902bea Add transform/scaling modes to GUI
Added zoom and stretch modes to GUI to mirror the transform modes
available on Android. They are reachable through a context menu or
shortcuts (Ctrl+S/Ctrl+Z).
CLI options --stretch and --zoom have been added as well.

Co-authored-by: Florian Märkl <info@florianmaerkl.de>
2022-12-10 15:13:03 +01:00
Johannes Baiter
74d39e6314 lib: Add support for trigger effects and controller haptics
By default, no trigger effects and haptics are requested from the
console, lib users have to explicitly enable them for a session by
setting the new `enable_dualsense` flag on the session's
`ChiakiConnectInfo` struct.

Trigger Effects are simply a new Takion message type `11`  and
include the type of each effect and the effect data (10 bytes) for
each of the triggers. They are exposed as a new Chiaki event type
`CHIAKI_EVENT_TRIGGER_EFFECTS`.

Haptic effects are implemented in the protocol as a separate audio
stream, for which packets are only sent when there are actually
effects being played, i.e. silence is not explicitly encoded.
Audio data is 3kHz little endian 16 bit stereo sent in frames of
10 samples every 100ms. Note that the Takion AV header has the
codec field set to Opus, however this is not true.
Users can provide a new `ChiakiAudioSink` dedicated to haptics
via the new `chiaki_session_set_haptics_sink` API, which behaves
identical to the regular audio sink, except that it has a lower
frequency.
2022-11-01 18:29:05 +01:00
Florian Märkl
40a9dee4ed Fix some EINTR handling 2022-10-23 13:42:12 +02:00
Florian Märkl
6bfbcfc456 Update chiaki-build-switch image in build script 2022-09-25 14:24:43 +02:00
Florian Märkl
e00f5fae9d Update macOS icon in Big Sur style 2022-09-25 10:29:17 +02:00
Florian Märkl
4164255ef9 Update dependencies and fix CI
* nanopb 0.4.6.4 with PB_C99_STATIC_ASSERT to avoid depending on C11
* OpenSSL 1.1.1q on windows
* Switched from docker to podman in CI
* Appdir in appimage build container is now in /build/appdir to fix
  "Invalid cross-device link" errors in linuxdeploy when appdir is in
  mount
* Use python protobuf==3.19.5 in bionic image, which still supports
  python 3.6, and on windows where nanopb otherwise has issues
* Purge nanopb_pb2.py to force regeneration of it for possibly different
  python protobuf versions in subsequent builds
* FreeBSD needs py39 packages now
2022-09-24 15:18:53 +02:00
Florian Märkl
b790fb3fb5 Set PATH to find protoc for nanopb generator 2022-06-02 18:26:42 +02:00
Florian Märkl
b4f051395f Reduce targets in fetched mbedtls and show progress 2022-06-02 17:58:57 +02:00
Florian Märkl
420809b24e Add option to fetch mbedtls with cmake 2022-06-02 17:10:19 +02:00
Florian Märkl
7d820bd4ab Update and fix CI
* appimage: switched to bionic
* android: updated container for nanopb changes
* switch: updated devkitpro and simpler cmake scripts
2022-03-26 18:33:15 +01:00
Florian Märkl
aa3a3b8bbc Update Windows Dependencies in CI 2022-03-26 11:33:50 +01:00
MiniMeOSc
ccecc67d74 Fix stream command not working for second or later host in configuration 2022-03-22 11:38:47 +01:00
Florian Märkl
dcd2e6af4a Update nanopb to fix unaligned pointers on arm64 2021-12-03 11:47:32 +01:00
Florian Märkl
7a01ac0d41 Update BSD CI 2021-12-03 11:46:47 +01:00
Florian Märkl
796a128456 Fix fec.c extension 2021-04-11 18:19:46 +02:00
Florian Märkl
695da18473 Make CLI Wakeup work for PS5 2021-04-11 18:11:35 +02:00
Florian Märkl
7870a28cdd Fix Discovery in CLI 2021-04-11 18:11:35 +02:00
Florian Märkl
a44000ea2b Enable CLI in CI 2021-04-11 18:11:35 +02:00
Florian Märkl
2b4a7426ff Install CLI 2021-04-11 18:11:35 +02:00
Florian Märkl
f50b060795 Fix AppVeyor 2021-04-11 18:11:35 +02:00
Florian Märkl
a049ed43ec
Force-disable Lib Decoders on Android 2021-02-02 10:59:26 +01:00
Florian Märkl
ae3ca1ac7a
Update Flatpak to v2.1.1 2021-01-24 14:02:17 +01:00
Florian Märkl
2257030ade
Version 2.1.1 2021-01-24 10:26:53 +01:00
Florian Märkl
6df937a57c
Remove removed sdl2-static from Alpine 2021-01-24 10:17:49 +01:00
Florian Märkl
078e83ec70
Fix Regist on Switch 2021-01-24 10:11:46 +01:00
Florian Märkl
ac8a3a3a3c
Update Flatpak to v2.1.0 2021-01-15 22:49:56 +01:00
Florian Märkl
fcdc414692
Version 2.1.0 2021-01-15 22:35:50 +01:00
Florian Märkl
505910bc5f
Enable Setsu in AppImage 2021-01-15 22:01:35 +01:00
Florian Märkl
f8b34febbe
Fix FindFFMPEG.cmake for ancient cmake 2021-01-15 22:01:35 +01:00
Florian Märkl
35130b08b7
Refine LR Touch on Android 2021-01-15 19:16:54 +01:00
Florian Märkl
5699c06dd8
Fix RegistActivity on Android 2021-01-15 18:50:49 +01:00
Florian Märkl
cb5870f30d
Add Touch Button Haptics to Android 2021-01-15 18:43:09 +01:00
Florian Märkl
28f017d640
Add Option to disable Motion on Android 2021-01-15 18:23:20 +01:00
Florian Märkl
bae081d5b3
Trigger Touchpad Button from TouchpadView on Android 2021-01-15 18:00:05 +01:00
Florian Märkl
baa034fa3f
Finish Basic Touchpad on Android 2021-01-15 17:00:05 +01:00
Florian Märkl
fa44a3269c
Propagate Touchpad State through TouchControlsFragment on Android 2021-01-15 15:32:37 +01:00
Florian Märkl
5914ceec77
Add TouchpadView to Android 2021-01-15 14:37:48 +01:00
Florian Märkl
402782b4af
Replace Deprecated Android Kotlin Extensions and Others 2021-01-15 14:36:33 +01:00
Florian Märkl
e6af02a35c
Update Android Dependencies 2021-01-15 12:29:56 +01:00
Florian Märkl
367489e230
Extend Touch Areas on Android 2021-01-14 20:50:38 +01:00
Florian Märkl
9a0ef224a0
Add nicer LR Buttons to Android 2021-01-14 20:44:56 +01:00
Florian Märkl
510064c899
Use SurfaceView on Android 2021-01-14 18:56:42 +01:00
Florian Märkl
3a90ef0a65
Extend DPad Touch Area on Android 2021-01-13 20:24:25 +01:00
Florian Märkl
b69bf280f8
Extend Face Button Touch Areas on Android 2021-01-13 19:33:54 +01:00
Florian Märkl
c1a4504470
Add L3 and R3 to Android Touch Controls 2021-01-13 17:14:07 +01:00
Florian Märkl
7c00b13818
Add Rumble Fallback for Pre-O Android 2021-01-13 15:23:03 +01:00
Florian Märkl
3b85e147b6
Add Rumble to Android 2021-01-13 15:00:53 +01:00
Florian Märkl
2906cfdd69
Add Motion to Android 2021-01-12 21:08:33 +01:00
Florian Märkl
2a4b67b58e
Complete ControllerState in JNI 2021-01-12 16:30:06 +01:00
Florian Märkl
bb4e5398b2
Update Switch Metadata and Icons 2021-01-12 13:35:40 +01:00
Florian Märkl
0b6e479e0b
Fix some Double Free and uninitialized Memory in Switch 2021-01-12 12:59:04 +01:00
Florian Märkl
fc58e83e9c
Track Finger IDs on Switch 2021-01-12 12:23:02 +01:00
h0neybadger
1b8fa556f8
Fix switch touchpad resolution 2021-01-12 11:33:49 +01:00
Florian Märkl
12054a91c9
Fix Motion Data on Switch 2021-01-12 11:31:40 +01:00
h0neybadger
acf15480f2 Add switch rumble and motion feedbacks 2021-01-11 20:04:41 +01:00
Florian Märkl
9ab84e6054
Prefer fixed local Port for Discovery 2021-01-10 16:01:37 +01:00
Florian Märkl
a0c3768edb
Fix Idle Controller State on Android 2021-01-10 14:46:24 +01:00
Florian Märkl
7785c310a9
Fix some Uninitialized Memory 2021-01-10 11:04:19 +01:00
Florian Märkl
96cbd5d9b8
Fix Madgwick Filter for Orientation 2021-01-09 10:53:50 +01:00
Florian Märkl
42a3b864d0
Add _USE_MATH_DEFINES for Windows 2021-01-06 22:04:53 +01:00
Florian Märkl
24d73064db
Format Fixes 2021-01-06 21:51:18 +01:00
Florian Märkl
cb827a525a
Add Motion to Feedback 2021-01-06 21:50:13 +01:00
Florian Märkl
170dcd4d65
Connect and read Setsu Motion Device in GUI 2021-01-06 21:47:08 +01:00
Florian Märkl
32e1539c22
Add Orientation Tracker 2021-01-06 21:47:06 +01:00
Florian Märkl
88c03aa744
Finish Motion in Setsu 2021-01-06 15:58:23 +01:00
Florian Märkl
698bce8022
Connect Motion Devices in Setsu 2021-01-06 15:58:23 +01:00
Florian Märkl
20c54b05ad
Print error on Setsu open failure 2021-01-06 15:58:22 +01:00
Florian Märkl
abc9a27208
Add Motion Stub to Setsu 2021-01-06 15:58:22 +01:00
Florian Märkl
0e324a41a0
Fix setting a Feedback State Byte 2021-01-06 15:58:22 +01:00
Roy P
3272f47dc6
Update Flatpak to v2.0.1 2021-01-06 15:56:35 +01:00
h0neybadger
c5246541a9 Fix switch target chiaki.conf format 2021-01-04 23:07:16 +01:00
h0neybadger
0af3ae27d4 Use borealis keyboard for the nintendo switch 2021-01-04 23:07:16 +01:00
Florian Märkl
1ee23e0fa2 Don't mess with the OMX Buffer Size 2021-01-04 12:46:38 +01:00
h0neybadger
b9a9ea497c
Fix switch audio delay with SDL_ClearQueuedAudio 2021-01-04 11:50:36 +01:00
h0neybadger
e531a90d26
Fix switch settings psn id regex 2021-01-04 11:50:36 +01:00
Florian Märkl
7cf370c70d
Show SDL GUID in Controller Name in GUI 2021-01-04 11:16:28 +01:00
h0neybadger
d4dc0ffee1 Fix switch README info and nxlink push script 2021-01-02 13:23:14 +01:00
Alexander Millin
a2955d21fc
Enable hevc_vaapi in FFMPEG Builds 2021-01-01 21:24:51 +01:00
Florian Märkl
da051803f5
Fall back to H264 on Pi 2021-01-01 21:19:52 +01:00
Florian Märkl
49d65ad14a
Minor Style Fixes 2021-01-01 15:31:39 +01:00
Florian Märkl
042e02eb3e
Add Rumble to GUI 2021-01-01 13:56:30 +01:00
Florian Märkl
3c2e9a0418
Use correct Target in GUI CLI Start 2021-01-01 11:52:49 +01:00
Florian Märkl
6c46920adb
Fix some Warnings 2021-01-01 11:09:13 +01:00
Florian Märkl
81984b7d48
Add Rumble to Lib 2020-12-31 23:45:47 +01:00
Florian Märkl
fbb19f94ea
Fix err in streamsession.cpp for pi 2020-12-31 21:24:30 +01:00
Florian Märkl
85d9594ebc
Add DualSense to Setsu 2020-12-30 16:11:06 +01:00
Florian Märkl
89c3175d71
Add fallback if getnameinfo fails 2020-12-30 12:37:28 +01:00
195 changed files with 5284 additions and 1976 deletions

View file

@ -5,6 +5,7 @@ image:
branches: branches:
only: only:
- master - master
- develop
- /^v\d.*$/ - /^v\d.*$/
- /^deploy-test(-.*)?$/ - /^deploy-test(-.*)?$/
@ -36,14 +37,14 @@ for:
install: install:
- git submodule update --init --recursive - git submodule update --init --recursive
- sudo pip3 install protobuf - sudo pip3 install protobuf
- brew install qt opus openssl@1.1 nasm sdl2 protobuf - HOMEBREW_NO_AUTO_UPDATE=1 brew install qt@5 opus openssl@1.1 nasm sdl2 protobuf
- scripts/build-ffmpeg.sh - scripts/build-ffmpeg.sh
build_script: build_script:
- export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt" - export CMAKE_PREFIX_PATH="`pwd`/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt@5"
- scripts/build-common.sh - scripts/build-common.sh
- cp -a build/gui/chiaki.app Chiaki.app - cp -a build/gui/chiaki.app Chiaki.app
- /usr/local/opt/qt/bin/macdeployqt Chiaki.app -dmg - /usr/local/opt/qt@5/bin/macdeployqt Chiaki.app -dmg
artifacts: artifacts:
- path: Chiaki.dmg - path: Chiaki.dmg

View file

@ -22,7 +22,7 @@ tasks:
sudo docker run \ sudo docker run \
-v /home/build:/home/build \ -v /home/build:/home/build \
-u $(id -u):$(id -g) \ -u $(id -u):$(id -g) \
thestr4ng3r/android:f064ea6 \ thestr4ng3r/android:90d826e \
/bin/bash -c "cd /home/build/chiaki/android && ./gradlew assembleRelease bundleRelease" /bin/bash -c "cd /home/build/chiaki/android && ./gradlew assembleRelease bundleRelease"
cp chiaki/android/app/build/outputs/apk/release/app-release*.apk Chiaki.apk cp chiaki/android/app/build/outputs/apk/release/app-release*.apk Chiaki.apk
cp chiaki/android/app/build/outputs/bundle/release/app-release*.aab Chiaki.aab cp chiaki/android/app/build/outputs/bundle/release/app-release*.aab Chiaki.aab

View file

@ -9,38 +9,47 @@ packages:
- ninja - ninja
- protoc - protoc
- py3-protobuf - py3-protobuf
- py3-setuptools
- opus-dev - opus-dev
- qt5-qtbase-dev - qt5-qtbase-dev
- qt5-qtsvg-dev - qt5-qtsvg-dev
- qt5-qtmultimedia-dev - qt5-qtmultimedia-dev
- ffmpeg-dev - ffmpeg-dev
- sdl2-dev - sdl2-dev
- sdl2-static # this is gone on alpine edge so might be necessary to remove later - podman
- docker
- fuse - fuse
- udev
- argp-standalone
artifacts: artifacts:
- chiaki.nro - chiaki.nro
- Chiaki.AppImage - Chiaki.AppImage
tasks: tasks:
- start_docker: | - setup_podman: |
sudo service docker start sudo rc-service udev start
sudo chmod +s /usr/bin/docker # Yes, I know what I am doing sudo rc-service cgroups start
sudo service fuse start # Fuse for AppImages sudo rc-service fuse start # Fuse for AppImages
echo build:100000:65536 | sudo tee /etc/subuid
echo build:100000:65536 | sudo tee /etc/subgid
# https://www.kernel.org/doc/Documentation/networking/tuntap.txt
# for slirp4netns
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 0666 /dev/net/tun
- local_build_and_test: | - local_build_and_test: |
cd chiaki cd chiaki
cmake -Bbuild -GNinja cmake -Bbuild -GNinja -DCHIAKI_ENABLE_CLI=ON -DCHIAKI_ENABLE_GUI=ON -DCHIAKI_CLI_ARGP_STANDALONE=ON
ninja -C build ninja -C build
build/test/chiaki-unit build/test/chiaki-unit
- appimage: | - appimage: |
cd chiaki cd chiaki
scripts/run-docker-build-appimage.sh scripts/run-podman-build-appimage.sh
cp appimage/Chiaki.AppImage ../Chiaki.AppImage cp appimage/Chiaki.AppImage ../Chiaki.AppImage
- switch: | - switch: |
cd chiaki cd chiaki
scripts/switch/run-docker-build-chiaki.sh scripts/switch/run-podman-build-chiaki.sh
cp build_switch/switch/chiaki.nro ../chiaki.nro cp build_switch/switch/chiaki.nro ../chiaki.nro
- bullseye: | - bullseye: |
cd chiaki cd chiaki
scripts/run-docker-build-bullseye.sh scripts/run-podman-build-bullseye.sh

View file

@ -1,5 +1,5 @@
image: freebsd/latest image: freebsd/14.x
sources: sources:
- https://git.sr.ht/~thestr4ng3r/chiaki - https://git.sr.ht/~thestr4ng3r/chiaki
@ -7,7 +7,8 @@ sources:
packages: packages:
- cmake - cmake
- protobuf - protobuf
- py37-protobuf - py311-setuptools # should not be needed with nanopb >= 0.4.9
- py311-protobuf
- opus - opus
- qt5-core - qt5-core
- qt5-qmake - qt5-qmake

View file

@ -1,5 +1,5 @@
image: openbsd/6.7 image: openbsd/latest
sources: sources:
- https://git.sr.ht/~thestr4ng3r/chiaki - https://git.sr.ht/~thestr4ng3r/chiaki
@ -7,6 +7,7 @@ sources:
packages: packages:
- cmake - cmake
- protobuf - protobuf
- py3-setuptools # should not be needed with nanopb >= 0.4.9
- py3-protobuf - py3-protobuf
- opus - opus
- qtbase - qtbase

2
.gitignore vendored
View file

@ -29,3 +29,5 @@ compile_commands.json
chiaki.conf chiaki.conf
/appimage /appimage
.cache/ .cache/
/*.app
/*.dmg

2
.gitmodules vendored
View file

@ -15,4 +15,4 @@
url = https://github.com/google/oboe url = https://github.com/google/oboe
[submodule "switch/borealis"] [submodule "switch/borealis"]
path = switch/borealis path = switch/borealis
url = https://github.com/natinusala/borealis.git url = https://git.sr.ht/~thestr4ng3r/borealis

View file

@ -24,6 +24,7 @@ endif()
tri_option(CHIAKI_ENABLE_FFMPEG_DECODER "Enable FFMPEG video decoder" ${CHIAKI_FFMPEG_DEFAULT}) tri_option(CHIAKI_ENABLE_FFMPEG_DECODER "Enable FFMPEG video decoder" ${CHIAKI_FFMPEG_DEFAULT})
tri_option(CHIAKI_ENABLE_PI_DECODER "Enable Raspberry Pi-specific video decoder (requires libraspberrypi0 and libraspberrypi-doc)" AUTO) tri_option(CHIAKI_ENABLE_PI_DECODER "Enable Raspberry Pi-specific video decoder (requires libraspberrypi0 and libraspberrypi-doc)" AUTO)
option(CHIAKI_LIB_ENABLE_MBEDTLS "Use mbedtls instead of OpenSSL as part of Chiaki Lib" OFF) option(CHIAKI_LIB_ENABLE_MBEDTLS "Use mbedtls instead of OpenSSL as part of Chiaki Lib" OFF)
option(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT "Fetch Mbed TLS instead of using system-provided libs" OFF)
option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF) option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF)
option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON) option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON)
option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF) option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF)
@ -31,8 +32,8 @@ tri_option(CHIAKI_USE_SYSTEM_JERASURE "Use system-provided jerasure instead of s
tri_option(CHIAKI_USE_SYSTEM_NANOPB "Use system-provided nanopb instead of submodule" AUTO) tri_option(CHIAKI_USE_SYSTEM_NANOPB "Use system-provided nanopb instead of submodule" AUTO)
set(CHIAKI_VERSION_MAJOR 2) set(CHIAKI_VERSION_MAJOR 2)
set(CHIAKI_VERSION_MINOR 0) set(CHIAKI_VERSION_MINOR 2)
set(CHIAKI_VERSION_PATCH 1) set(CHIAKI_VERSION_PATCH 0)
set(CHIAKI_VERSION ${CHIAKI_VERSION_MAJOR}.${CHIAKI_VERSION_MINOR}.${CHIAKI_VERSION_PATCH}) set(CHIAKI_VERSION ${CHIAKI_VERSION_MAJOR}.${CHIAKI_VERSION_MINOR}.${CHIAKI_VERSION_PATCH})
set(CPACK_PACKAGE_NAME "chiaki") set(CPACK_PACKAGE_NAME "chiaki")
@ -89,6 +90,20 @@ endif()
if(CHIAKI_LIB_ENABLE_MBEDTLS) if(CHIAKI_LIB_ENABLE_MBEDTLS)
add_definitions(-DCHIAKI_LIB_ENABLE_MBEDTLS) add_definitions(-DCHIAKI_LIB_ENABLE_MBEDTLS)
if(CHIAKI_LIB_MBEDTLS_EXTERNAL_PROJECT)
set(FETCHCONTENT_QUIET CACHE BOOL FALSE)
include(FetchContent)
set(ENABLE_TESTING CACHE INTERNAL OFF)
set(ENABLE_PROGRAMS CACHE INTERNAL OFF)
set(USE_SHARED_MBEDTLS_LIBRARY CACHE INTERNAL OFF)
FetchContent_Declare(
mbedtls
GIT_REPOSITORY https://github.com/Mbed-TLS/mbedtls.git
GIT_TAG 8b3f26a5ac38d4fdccbc5c5366229f3e01dafcc0 # v2.28.0
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(mbedtls)
endif()
endif() endif()
if(CHIAKI_ENABLE_FFMPEG_DECODER) if(CHIAKI_ENABLE_FFMPEG_DECODER)
@ -135,21 +150,30 @@ if(CHIAKI_ENABLE_CLI)
add_subdirectory(cli) add_subdirectory(cli)
endif() endif()
if(CHIAKI_ENABLE_GUI AND CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
find_package(SDL2 MODULE REQUIRED)
endif()
if(CHIAKI_ENABLE_SETSU) if(CHIAKI_ENABLE_SETSU)
find_package(Udev QUIET) if(CHIAKI_ENABLE_SETSU STREQUAL AUTO AND SDL2_FOUND AND (SDL2_VERSION_MINOR GREATER 0 OR SDL2_VERSION_PATCH GREATER_EQUAL 14))
find_package(Evdev QUIET) message(STATUS "SDL version ${SDL2_VERSION} is >= 2.0.14, disabling Setsu")
if(Udev_FOUND AND Evdev_FOUND)
set(CHIAKI_ENABLE_SETSU ON)
else()
if(NOT CHIAKI_ENABLE_SETSU STREQUAL AUTO)
message(FATAL_ERROR "
CHIAKI_ENABLE_SETSU is set to ON, but its dependencies (udev and evdev) could not be resolved.
Keep in mind that setsu is only supported on Linux!")
endif()
set(CHIAKI_ENABLE_SETSU OFF) set(CHIAKI_ENABLE_SETSU OFF)
endif() else()
if(CHIAKI_ENABLE_SETSU) find_package(Udev QUIET)
add_subdirectory(setsu) find_package(Evdev QUIET)
if(Udev_FOUND AND Evdev_FOUND)
set(CHIAKI_ENABLE_SETSU ON)
else()
if(NOT CHIAKI_ENABLE_SETSU STREQUAL AUTO)
message(FATAL_ERROR "
CHIAKI_ENABLE_SETSU is set to ON, but its dependencies (udev and evdev) could not be resolved.
Keep in mind that setsu is only supported on Linux!")
endif()
set(CHIAKI_ENABLE_SETSU OFF)
endif()
if(CHIAKI_ENABLE_SETSU)
add_subdirectory(setsu)
endif()
endif() endif()
endif() endif()
@ -160,7 +184,6 @@ else()
endif() endif()
if(CHIAKI_ENABLE_GUI) if(CHIAKI_ENABLE_GUI)
#add_subdirectory(setsu)
add_subdirectory(gui) add_subdirectory(gui)
endif() endif()

View file

@ -8,33 +8,35 @@
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/c81ogebvsmo43dd3?svg=true)](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [![builds.sr.ht Status](https://builds.sr.ht/~thestr4ng3r/chiaki.svg)](https://builds.sr.ht/~thestr4ng3r/chiaki?) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/c81ogebvsmo43dd3?svg=true)](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [![builds.sr.ht Status](https://builds.sr.ht/~thestr4ng3r/chiaki.svg)](https://builds.sr.ht/~thestr4ng3r/chiaki?)
Chiaki is a Free and Open Source Software Client for PlayStation 4 and PlayStation 5 Remote Play Chiaki is a Free and Open Source Software Client for PlayStation 4 and PlayStation 5 Remote Play
for Linux, FreeBSD, OpenBSD, Android, macOS, Windows, Nintendo Switch and potentially even more platforms. for Linux, FreeBSD, OpenBSD, NetBSD, Android, macOS, Windows, Nintendo Switch and potentially even more platforms.
## Project Status and Contributing
As all relevant features are implemented, this project is considered to be finished and in maintenance mode only.
No major updates are planned and contributions are only accepted in special cases such as security issues.
The objective is to keep a stable base and not break existing support for less mainstream platforms such as BSDs.
**For a more active, fast moving and community-oriented project, refer
to [chiaki-ng](https://streetpea.github.io/chiaki-ng/) ("next generation").
If you would like to contribute, this will likely also be the best place to do so.**
![Screenshot](assets/screenshot.png) ![Screenshot](assets/screenshot.png)
## Features
Everything necessary for a full streaming session, including the initial
registration and wakeup of the console, is supported.
The following features however are yet to be implemented:
* Rumble
* Accelerometer/Gyroscope
## Installing ## Installing
You can either download a pre-built release (easier) or build Chiaki from source. You can either download a pre-built release or build Chiaki from source.
### Downloading a Release ### Downloading a Release
Builds are provided for Linux, Android, macOS and Windows. Builds are provided for Linux, Android, macOS, Nintendo Switch and Windows.
You can download them [here](https://git.sr.ht/~thestr4ng3r/chiaki/refs). You can download them [here](https://git.sr.ht/~thestr4ng3r/chiaki/refs).
* **Linux**: The provided file is an [AppImage](https://appimage.org/). Simply make it executable (`chmod +x <file>.AppImage`) and run it. * **Linux**: The provided file is an [AppImage](https://appimage.org/). Simply make it executable (`chmod +x <file>.AppImage`) and run it.
* **Android**: Install from [Google Play](https://play.google.com/store/apps/details?id=com.metallic.chiaki), [F-Droid](https://f-droid.org/packages/com.metallic.chiaki/) or download the APK from Sourcehut. * **Android**: Install from [F-Droid](https://f-droid.org/packages/com.metallic.chiaki/) or download the APK from Sourcehut.
* **macOS**: Drag the application from the `.dmg` into your Applications folder. * **macOS**: Drag the application from the `.dmg` into your Applications folder.
* **Windows**: Extract the `.zip` file and execute `chiaki.exe`. * **Windows**: Extract the `.zip` file and execute `chiaki.exe`.
* **Switch**: Follow README specific [instructions](./switch/README.md) * **Switch**: Download the `.nro` file and copy it into the `switch/` directory on your SD card.
### Building from Source ### Building from Source
@ -47,8 +49,8 @@ cmake ..
make make
``` ```
For more detailed platform-specific instructions, see [doc/platform-build.md](doc/platform-build.md). For more detailed platform-specific instructions, see [doc/platform-build.md](doc/platform-build.md) or [switch/](./switch/README.md) for Nintendo Switch.
in
## Usage ## Usage
If your Console is on your local network, is turned on or in standby mode and does not have Discovery explicitly disabled, Chiaki should find it. If your Console is on your local network, is turned on or in standby mode and does not have Discovery explicitly disabled, Chiaki should find it.
@ -71,13 +73,6 @@ Settings -> Remote Play -> Add Device, or on a PS5: Settings -> System -> Remote
You can now double-click your Console in Chiaki's main window to start Remote Play. You can now double-click your Console in Chiaki's main window to start Remote Play.
## Joining the Community or Getting Help
There are official groups for Chiaki on Telegram and IRC. They are bridged so you can join whichever you like:
- **Telegram:** https://t.me/chiakitg
- **IRC:** #chiaki on irc.freenode.net
## Acknowledgements ## Acknowledgements
This project has only been made possible because of the following Open Source projects: This project has only been made possible because of the following Open Source projects:

View file

@ -1,6 +1,6 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
def rootCMakeLists = "../../CMakeLists.txt" def rootCMakeLists = "../../CMakeLists.txt"
@ -18,13 +18,12 @@ def chiakiVersion = "$chiakiVersionMajor.$chiakiVersionMinor.$chiakiVersionPatch
println("Determined Chiaki Version: $chiakiVersion") println("Determined Chiaki Version: $chiakiVersion")
android { android {
compileSdkVersion 30 compileSdkVersion 33
buildToolsVersion "30.0.2"
defaultConfig { defaultConfig {
applicationId "com.metallic.chiaki" applicationId "com.metallic.chiaki"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 33
versionCode 9 versionCode 12
versionName chiakiVersion versionName chiakiVersion
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@ -33,11 +32,16 @@ android {
"-DCHIAKI_ENABLE_GUI=OFF", "-DCHIAKI_ENABLE_GUI=OFF",
"-DCHIAKI_ENABLE_SETSU=OFF", "-DCHIAKI_ENABLE_SETSU=OFF",
"-DCHIAKI_ENABLE_ANDROID=ON", "-DCHIAKI_ENABLE_ANDROID=ON",
"-DCHIAKI_ENABLE_FFMPEG_DECODER=OFF",
"-DCHIAKI_ENABLE_PI_DECODER=OFF",
"-DCHIAKI_LIB_ENABLE_OPUS=OFF", "-DCHIAKI_LIB_ENABLE_OPUS=OFF",
"-DCHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT=ON" "-DCHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT=ON"
} }
} }
} }
buildFeatures {
viewBinding true
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
@ -47,7 +51,7 @@ android {
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
version "3.10.2+" version "3.22.1"
path rootCMakeLists path rootCMakeLists
} }
} }
@ -61,6 +65,7 @@ android {
} }
} }
Properties properties = new Properties() Properties properties = new Properties()
def propertiesFile = file("../local.properties") def propertiesFile = file("../local.properties")
if (propertiesFile.exists()) { if (propertiesFile.exists()) {
@ -86,31 +91,26 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
} }
} }
androidExtensions {
// for @Parcelize
experimental = true
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.2.0'
implementation 'com.google.android.material:material:1.1.0-beta02' implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.2.0' implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.5.1'
implementation "io.reactivex.rxjava2:rxjava:2.2.12" implementation "io.reactivex.rxjava2:rxjava:2.2.20"
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0" implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
def room_version = "2.2.4" def room_version = "2.5.0"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-rxjava2:$room_version" implementation "androidx.room:room-rxjava2:$room_version"
implementation "com.squareup.moshi:moshi:1.9.2" implementation "com.squareup.moshi:moshi:1.14.0"
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.2" kapt "com.squareup.moshi:moshi-kotlin-codegen:1.14.0"
} }

View file

@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -27,7 +29,8 @@
</provider> </provider>
<activity android:name=".main.MainActivity" <activity android:name=".main.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"> android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View file

@ -110,9 +110,9 @@ JNIEXPORT jstring JNICALL JNI_FCN(quitReasonToString)(JNIEnv *env, jobject obj,
return E->NewStringUTF(env, chiaki_quit_reason_string((ChiakiQuitReason)value)); return E->NewStringUTF(env, chiaki_quit_reason_string((ChiakiQuitReason)value));
} }
JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsStopped)(JNIEnv *env, jobject obj, jint value) JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsError)(JNIEnv *env, jobject obj, jint value)
{ {
return value == CHIAKI_QUIT_REASON_STOPPED; return chiaki_quit_reason_is_error(value);
} }
JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset, jobject codec) JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset, jobject codec)
@ -133,6 +133,7 @@ typedef struct android_chiaki_session_t
jmethodID java_session_event_connected_meth; jmethodID java_session_event_connected_meth;
jmethodID java_session_event_login_pin_request_meth; jmethodID java_session_event_login_pin_request_meth;
jmethodID java_session_event_quit_meth; jmethodID java_session_event_quit_meth;
jmethodID java_session_event_rumble_meth;
jfieldID java_controller_state_buttons; jfieldID java_controller_state_buttons;
jfieldID java_controller_state_l2_state; jfieldID java_controller_state_l2_state;
jfieldID java_controller_state_r2_state; jfieldID java_controller_state_r2_state;
@ -140,6 +141,20 @@ typedef struct android_chiaki_session_t
jfieldID java_controller_state_left_y; jfieldID java_controller_state_left_y;
jfieldID java_controller_state_right_x; jfieldID java_controller_state_right_x;
jfieldID java_controller_state_right_y; jfieldID java_controller_state_right_y;
jfieldID java_controller_state_touches;
jfieldID java_controller_state_gyro_x;
jfieldID java_controller_state_gyro_y;
jfieldID java_controller_state_gyro_z;
jfieldID java_controller_state_accel_x;
jfieldID java_controller_state_accel_y;
jfieldID java_controller_state_accel_z;
jfieldID java_controller_state_orient_x;
jfieldID java_controller_state_orient_y;
jfieldID java_controller_state_orient_z;
jfieldID java_controller_state_orient_w;
jfieldID java_controller_touch_x;
jfieldID java_controller_touch_y;
jfieldID java_controller_touch_id;
AndroidChiakiVideoDecoder video_decoder; AndroidChiakiVideoDecoder video_decoder;
AndroidChiakiAudioDecoder audio_decoder; AndroidChiakiAudioDecoder audio_decoder;
@ -178,6 +193,14 @@ static void android_chiaki_event_cb(ChiakiEvent *event, void *user)
free(reason_str); free(reason_str);
break; break;
} }
case CHIAKI_EVENT_RUMBLE:
E->CallVoidMethod(env, session->java_session,
session->java_session_event_rumble_meth,
(jint)event->rumble.left,
(jint)event->rumble.right);
break;
default:
break;
} }
(*global_vm)->DetachCurrentThread(global_vm); (*global_vm)->DetachCurrentThread(global_vm);
@ -296,6 +319,7 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
session->java_session_event_connected_meth = E->GetMethodID(env, session->java_session_class, "eventConnected", "()V"); session->java_session_event_connected_meth = E->GetMethodID(env, session->java_session_class, "eventConnected", "()V");
session->java_session_event_login_pin_request_meth = E->GetMethodID(env, session->java_session_class, "eventLoginPinRequest", "(Z)V"); session->java_session_event_login_pin_request_meth = E->GetMethodID(env, session->java_session_class, "eventLoginPinRequest", "(Z)V");
session->java_session_event_quit_meth = E->GetMethodID(env, session->java_session_class, "eventQuit", "(ILjava/lang/String;)V"); session->java_session_event_quit_meth = E->GetMethodID(env, session->java_session_class, "eventQuit", "(ILjava/lang/String;)V");
session->java_session_event_rumble_meth = E->GetMethodID(env, session->java_session_class, "eventRumble", "(II)V");
jclass controller_state_class = E->FindClass(env, BASE_PACKAGE"/ControllerState"); jclass controller_state_class = E->FindClass(env, BASE_PACKAGE"/ControllerState");
session->java_controller_state_buttons = E->GetFieldID(env, controller_state_class, "buttons", "I"); session->java_controller_state_buttons = E->GetFieldID(env, controller_state_class, "buttons", "I");
@ -305,6 +329,22 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
session->java_controller_state_left_y = E->GetFieldID(env, controller_state_class, "leftY", "S"); session->java_controller_state_left_y = E->GetFieldID(env, controller_state_class, "leftY", "S");
session->java_controller_state_right_x = E->GetFieldID(env, controller_state_class, "rightX", "S"); session->java_controller_state_right_x = E->GetFieldID(env, controller_state_class, "rightX", "S");
session->java_controller_state_right_y = E->GetFieldID(env, controller_state_class, "rightY", "S"); session->java_controller_state_right_y = E->GetFieldID(env, controller_state_class, "rightY", "S");
session->java_controller_state_touches = E->GetFieldID(env, controller_state_class, "touches", "[L"BASE_PACKAGE"/ControllerTouch;");
session->java_controller_state_gyro_x = E->GetFieldID(env, controller_state_class, "gyroX", "F");
session->java_controller_state_gyro_y = E->GetFieldID(env, controller_state_class, "gyroY", "F");
session->java_controller_state_gyro_z = E->GetFieldID(env, controller_state_class, "gyroZ", "F");
session->java_controller_state_accel_x = E->GetFieldID(env, controller_state_class, "accelX", "F");
session->java_controller_state_accel_y = E->GetFieldID(env, controller_state_class, "accelY", "F");
session->java_controller_state_accel_z = E->GetFieldID(env, controller_state_class, "accelZ", "F");
session->java_controller_state_orient_x = E->GetFieldID(env, controller_state_class, "orientX", "F");
session->java_controller_state_orient_y = E->GetFieldID(env, controller_state_class, "orientY", "F");
session->java_controller_state_orient_z = E->GetFieldID(env, controller_state_class, "orientZ", "F");
session->java_controller_state_orient_w = E->GetFieldID(env, controller_state_class, "orientW", "F");
jclass controller_touch_class = E->FindClass(env, BASE_PACKAGE"/ControllerTouch");
session->java_controller_touch_x = E->GetFieldID(env, controller_touch_class, "x", "S");
session->java_controller_touch_y = E->GetFieldID(env, controller_touch_class, "y", "S");
session->java_controller_touch_id = E->GetFieldID(env, controller_touch_class, "id", "B");
chiaki_session_set_event_cb(&session->session, android_chiaki_event_cb, session); chiaki_session_set_event_cb(&session->session, android_chiaki_event_cb, session);
chiaki_session_set_video_sample_cb(&session->session, android_chiaki_video_decoder_video_sample, &session->video_decoder); chiaki_session_set_video_sample_cb(&session->session, android_chiaki_video_decoder_video_sample, &session->video_decoder);
@ -373,7 +413,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionSetSurface)(JNIEnv *env, jobject obj, jlon
JNIEXPORT void JNICALL JNI_FCN(sessionSetControllerState)(JNIEnv *env, jobject obj, jlong ptr, jobject controller_state_java) JNIEXPORT void JNICALL JNI_FCN(sessionSetControllerState)(JNIEnv *env, jobject obj, jlong ptr, jobject controller_state_java)
{ {
AndroidChiakiSession *session = (AndroidChiakiSession *)ptr; AndroidChiakiSession *session = (AndroidChiakiSession *)ptr;
ChiakiControllerState controller_state = { 0 }; ChiakiControllerState controller_state;
chiaki_controller_state_set_idle(&controller_state);
controller_state.buttons = (uint32_t)E->GetIntField(env, controller_state_java, session->java_controller_state_buttons); controller_state.buttons = (uint32_t)E->GetIntField(env, controller_state_java, session->java_controller_state_buttons);
controller_state.l2_state = (uint8_t)E->GetByteField(env, controller_state_java, session->java_controller_state_l2_state); controller_state.l2_state = (uint8_t)E->GetByteField(env, controller_state_java, session->java_controller_state_l2_state);
controller_state.r2_state = (uint8_t)E->GetByteField(env, controller_state_java, session->java_controller_state_r2_state); controller_state.r2_state = (uint8_t)E->GetByteField(env, controller_state_java, session->java_controller_state_r2_state);
@ -381,6 +422,34 @@ JNIEXPORT void JNICALL JNI_FCN(sessionSetControllerState)(JNIEnv *env, jobject o
controller_state.left_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_left_y); controller_state.left_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_left_y);
controller_state.right_x = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_x); controller_state.right_x = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_x);
controller_state.right_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_y); controller_state.right_y = (int16_t)E->GetShortField(env, controller_state_java, session->java_controller_state_right_y);
jobjectArray touch_array = E->GetObjectField(env, controller_state_java, session->java_controller_state_touches);
size_t touch_array_len = (size_t)E->GetArrayLength(env, touch_array);
for(size_t i = 0; i < CHIAKI_CONTROLLER_TOUCHES_MAX; i++)
{
if(i < touch_array_len)
{
jobject touch = E->GetObjectArrayElement(env, touch_array, i);
controller_state.touches[i].x = (uint16_t)E->GetShortField(env, touch, session->java_controller_touch_x);
controller_state.touches[i].y = (uint16_t)E->GetShortField(env, touch, session->java_controller_touch_y);
controller_state.touches[i].id = (int8_t)E->GetByteField(env, touch, session->java_controller_touch_id);
}
else
{
controller_state.touches[i].x = 0;
controller_state.touches[i].y = 0;
controller_state.touches[i].id = -1;
}
}
controller_state.gyro_x = E->GetFloatField(env, controller_state_java, session->java_controller_state_gyro_x);
controller_state.gyro_y = E->GetFloatField(env, controller_state_java, session->java_controller_state_gyro_y);
controller_state.gyro_z = E->GetFloatField(env, controller_state_java, session->java_controller_state_gyro_z);
controller_state.accel_x = E->GetFloatField(env, controller_state_java, session->java_controller_state_accel_x);
controller_state.accel_y = E->GetFloatField(env, controller_state_java, session->java_controller_state_accel_y);
controller_state.accel_z = E->GetFloatField(env, controller_state_java, session->java_controller_state_accel_z);
controller_state.orient_x = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_x);
controller_state.orient_y = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_y);
controller_state.orient_z = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_z);
controller_state.orient_w = E->GetFloatField(env, controller_state_java, session->java_controller_state_orient_w);
chiaki_session_set_controller_state(&session->session, &controller_state); chiaki_session_set_controller_state(&session->session, &controller_state);
} }

@ -1 +1 @@
Subproject commit 0ab5b12a5bc3630a3d6c83b20eed2a669ebf7a24 Subproject commit 8740d0fc321a55489dbbf6067298201b7d2e106d

View file

@ -26,29 +26,34 @@ ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *dec
return chiaki_mutex_init(&decoder->codec_mutex, false); return chiaki_mutex_init(&decoder->codec_mutex, false);
} }
static void kill_decoder(AndroidChiakiVideoDecoder *decoder)
{
chiaki_mutex_lock(&decoder->codec_mutex);
decoder->shutdown_output = true;
ssize_t codec_buf_index = AMediaCodec_dequeueInputBuffer(decoder->codec, 1000);
if(codec_buf_index >= 0)
{
CHIAKI_LOGI(decoder->log, "Video Decoder sending EOS buffer");
AMediaCodec_queueInputBuffer(decoder->codec, (size_t)codec_buf_index, 0, 0, decoder->timestamp_cur++, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
AMediaCodec_stop(decoder->codec);
chiaki_mutex_unlock(&decoder->codec_mutex);
chiaki_thread_join(&decoder->output_thread, NULL);
}
else
{
CHIAKI_LOGE(decoder->log, "Failed to get input buffer for shutting down Video Decoder!");
AMediaCodec_stop(decoder->codec);
chiaki_mutex_unlock(&decoder->codec_mutex);
}
AMediaCodec_delete(decoder->codec);
decoder->codec = NULL;
decoder->shutdown_output = false;
}
void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder) void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder)
{ {
if(decoder->codec) if(decoder->codec)
{ kill_decoder(decoder);
chiaki_mutex_lock(&decoder->codec_mutex);
decoder->shutdown_output = true;
ssize_t codec_buf_index = AMediaCodec_dequeueInputBuffer(decoder->codec, -1);
if(codec_buf_index >= 0)
{
CHIAKI_LOGI(decoder->log, "Video Decoder sending EOS buffer");
AMediaCodec_queueInputBuffer(decoder->codec, (size_t)codec_buf_index, 0, 0, decoder->timestamp_cur++, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
AMediaCodec_stop(decoder->codec);
chiaki_mutex_unlock(&decoder->codec_mutex);
chiaki_thread_join(&decoder->output_thread, NULL);
}
else
{
CHIAKI_LOGE(decoder->log, "Failed to get input buffer for shutting down Video Decoder!");
AMediaCodec_stop(decoder->codec);
chiaki_mutex_unlock(&decoder->codec_mutex);
}
AMediaCodec_delete(decoder->codec);
}
chiaki_mutex_fini(&decoder->codec_mutex); chiaki_mutex_fini(&decoder->codec_mutex);
} }
@ -56,6 +61,16 @@ void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder
{ {
chiaki_mutex_lock(&decoder->codec_mutex); chiaki_mutex_lock(&decoder->codec_mutex);
if(!surface)
{
if(decoder->codec)
{
kill_decoder(decoder);
CHIAKI_LOGI(decoder->log, "Decoder shut down after surface was removed");
}
return;
}
if(decoder->codec) if(decoder->codec)
{ {
#if __ANDROID_API__ >= 23 #if __ANDROID_API__ >= 23

View file

@ -3,7 +3,7 @@
package com.metallic.chiaki.common package com.metallic.chiaki.common
import androidx.room.* import androidx.room.*
import androidx.room.ForeignKey.SET_NULL import androidx.room.ForeignKey.Companion.SET_NULL
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.Single import io.reactivex.Single

View file

@ -68,11 +68,26 @@ class Preferences(context: Context)
get() = sharedPreferences.getBoolean(onScreenControlsEnabledKey, true) get() = sharedPreferences.getBoolean(onScreenControlsEnabledKey, true)
set(value) { sharedPreferences.edit().putBoolean(onScreenControlsEnabledKey, value).apply() } set(value) { sharedPreferences.edit().putBoolean(onScreenControlsEnabledKey, value).apply() }
val touchpadOnlyEnabledKey get() = resources.getString(R.string.preferences_touchpad_only_key) val touchpadOnlyEnabledKey get() = resources.getString(R.string.preferences_touchpad_only_enabled_key)
var touchpadOnlyEnabled var touchpadOnlyEnabled
get() = sharedPreferences.getBoolean(touchpadOnlyEnabledKey, false) get() = sharedPreferences.getBoolean(touchpadOnlyEnabledKey, false)
set(value) { sharedPreferences.edit().putBoolean(touchpadOnlyEnabledKey, value).apply() } set(value) { sharedPreferences.edit().putBoolean(touchpadOnlyEnabledKey, value).apply() }
val rumbleEnabledKey get() = resources.getString(R.string.preferences_rumble_enabled_key)
var rumbleEnabled
get() = sharedPreferences.getBoolean(rumbleEnabledKey, true)
set(value) { sharedPreferences.edit().putBoolean(rumbleEnabledKey, value).apply() }
val motionEnabledKey get() = resources.getString(R.string.preferences_motion_enabled_key)
var motionEnabled
get() = sharedPreferences.getBoolean(motionEnabledKey, true)
set(value) { sharedPreferences.edit().putBoolean(motionEnabledKey, value).apply() }
val buttonHapticEnabledKey get() = resources.getString(R.string.preferences_button_haptic_enabled_key)
var buttonHapticEnabled
get() = sharedPreferences.getBoolean(buttonHapticEnabledKey, true)
set(value) { sharedPreferences.edit().putBoolean(buttonHapticEnabledKey, value).apply() }
val logVerboseKey get() = resources.getString(R.string.preferences_log_verbose_key) val logVerboseKey get() = resources.getString(R.string.preferences_log_verbose_key)
var logVerbose var logVerbose
get() = sharedPreferences.getBoolean(logVerboseKey, false) get() = sharedPreferences.getBoolean(logVerboseKey, false)

View file

@ -3,7 +3,7 @@
package com.metallic.chiaki.common package com.metallic.chiaki.common
import androidx.room.* import androidx.room.*
import androidx.room.ColumnInfo.BLOB import androidx.room.ColumnInfo.Companion.BLOB
import com.metallic.chiaki.lib.RegistHost import com.metallic.chiaki.lib.RegistHost
import com.metallic.chiaki.lib.Target import com.metallic.chiaki.lib.Target
import io.reactivex.Completable import io.reactivex.Completable

View file

@ -25,6 +25,8 @@ import io.reactivex.rxkotlin.addTo
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import okio.Buffer import okio.Buffer
import okio.Okio import okio.Okio
import okio.buffer
import okio.source
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -164,7 +166,7 @@ fun importSettingsFromUri(activity: Activity, uri: Uri, disposable: CompositeDis
try try
{ {
val inputStream = activity.contentResolver.openInputStream(uri) ?: throw IOException() val inputStream = activity.contentResolver.openInputStream(uri) ?: throw IOException()
val buffer = Okio.buffer(Okio.source(inputStream)) val buffer = inputStream.source().buffer()
val reader = JsonReader.of(buffer) val reader = JsonReader.of(buffer)
val adapter = moshi().serializedSettingsAdapter() val adapter = moshi().serializedSettingsAdapter()

View file

@ -3,8 +3,7 @@ package com.metallic.chiaki.lib
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
import android.view.Surface import android.view.Surface
import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.parcelize.Parcelize
import kotlinx.android.parcel.Parcelize
import java.lang.Exception import java.lang.Exception
import java.net.InetSocketAddress import java.net.InetSocketAddress
import kotlin.math.abs import kotlin.math.abs
@ -84,14 +83,14 @@ private class ChiakiNative
} }
@JvmStatic external fun errorCodeToString(value: Int): String @JvmStatic external fun errorCodeToString(value: Int): String
@JvmStatic external fun quitReasonToString(value: Int): String @JvmStatic external fun quitReasonToString(value: Int): String
@JvmStatic external fun quitReasonIsStopped(value: Int): Boolean @JvmStatic external fun quitReasonIsError(value: Int): Boolean
@JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int, codec: Codec): ConnectVideoProfile @JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int, codec: Codec): ConnectVideoProfile
@JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean, javaSession: Session) @JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean, javaSession: Session)
@JvmStatic external fun sessionFree(ptr: Long) @JvmStatic external fun sessionFree(ptr: Long)
@JvmStatic external fun sessionStart(ptr: Long): Int @JvmStatic external fun sessionStart(ptr: Long): Int
@JvmStatic external fun sessionStop(ptr: Long): Int @JvmStatic external fun sessionStop(ptr: Long): Int
@JvmStatic external fun sessionJoin(ptr: Long): Int @JvmStatic external fun sessionJoin(ptr: Long): Int
@JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface) @JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface?)
@JvmStatic external fun sessionSetControllerState(ptr: Long, controllerState: ControllerState) @JvmStatic external fun sessionSetControllerState(ptr: Long, controllerState: ControllerState)
@JvmStatic external fun sessionSetLoginPin(ptr: Long, pin: String) @JvmStatic external fun sessionSetLoginPin(ptr: Long, pin: String)
@JvmStatic external fun discoveryServiceCreate(result: CreateResult, options: DiscoveryServiceOptions, javaService: DiscoveryService) @JvmStatic external fun discoveryServiceCreate(result: CreateResult, options: DiscoveryServiceOptions, javaService: DiscoveryService)
@ -150,6 +149,14 @@ class ChiakiLog(val levelMask: Int, val callback: (level: Int, text: String) ->
private fun maxAbs(a: Short, b: Short) = if(abs(a.toInt()) > abs(b.toInt())) a else b private fun maxAbs(a: Short, b: Short) = if(abs(a.toInt()) > abs(b.toInt())) a else b
private val CONTROLLER_TOUCHES_MAX = 2 // must be the same as CHIAKI_CONTROLLER_TOUCHES_MAX
data class ControllerTouch(
var x: UShort = 0U,
var y: UShort = 0U,
var id: Byte = -1 // -1 = up
)
data class ControllerState constructor( data class ControllerState constructor(
var buttons: UInt = 0U, var buttons: UInt = 0U,
var l2State: UByte = 0U, var l2State: UByte = 0U,
@ -157,26 +164,40 @@ data class ControllerState constructor(
var leftX: Short = 0, var leftX: Short = 0,
var leftY: Short = 0, var leftY: Short = 0,
var rightX: Short = 0, var rightX: Short = 0,
var rightY: Short = 0 var rightY: Short = 0,
private var touchIdNext: UByte = 0U,
var touches: Array<ControllerTouch> = arrayOf(ControllerTouch(), ControllerTouch()),
var gyroX: Float = 0.0f,
var gyroY: Float = 0.0f,
var gyroZ: Float = 0.0f,
var accelX: Float = 0.0f,
var accelY: Float = 1.0f,
var accelZ: Float = 0.0f,
var orientX: Float = 0.0f,
var orientY: Float = 0.0f,
var orientZ: Float = 0.0f,
var orientW: Float = 1.0f
){ ){
companion object companion object
{ {
val BUTTON_CROSS = (1 shl 0).toUInt() val BUTTON_CROSS = (1 shl 0).toUInt()
val BUTTON_MOON = (1 shl 1).toUInt() val BUTTON_MOON = (1 shl 1).toUInt()
val BUTTON_BOX = (1 shl 2).toUInt() val BUTTON_BOX = (1 shl 2).toUInt()
val BUTTON_PYRAMID = (1 shl 3).toUInt() val BUTTON_PYRAMID = (1 shl 3).toUInt()
val BUTTON_DPAD_LEFT = (1 shl 4).toUInt() val BUTTON_DPAD_LEFT = (1 shl 4).toUInt()
val BUTTON_DPAD_RIGHT = (1 shl 5).toUInt() val BUTTON_DPAD_RIGHT = (1 shl 5).toUInt()
val BUTTON_DPAD_UP = (1 shl 6).toUInt() val BUTTON_DPAD_UP = (1 shl 6).toUInt()
val BUTTON_DPAD_DOWN = (1 shl 7).toUInt() val BUTTON_DPAD_DOWN = (1 shl 7).toUInt()
val BUTTON_L1 = (1 shl 8).toUInt() val BUTTON_L1 = (1 shl 8).toUInt()
val BUTTON_R1 = (1 shl 9).toUInt() val BUTTON_R1 = (1 shl 9).toUInt()
val BUTTON_L3 = (1 shl 10).toUInt() val BUTTON_L3 = (1 shl 10).toUInt()
val BUTTON_R3 = (1 shl 11).toUInt() val BUTTON_R3 = (1 shl 11).toUInt()
val BUTTON_OPTIONS = (1 shl 12).toUInt() val BUTTON_OPTIONS = (1 shl 12).toUInt()
val BUTTON_SHARE = (1 shl 13).toUInt() val BUTTON_SHARE = (1 shl 13).toUInt()
val BUTTON_TOUCHPAD = (1 shl 14).toUInt() val BUTTON_TOUCHPAD = (1 shl 14).toUInt()
val BUTTON_PS = (1 shl 15).toUInt() val BUTTON_PS = (1 shl 15).toUInt()
val TOUCHPAD_WIDTH: UShort = 1920U
val TOUCHPAD_HEIGHT: UShort = 942U
} }
infix fun or(o: ControllerState) = ControllerState( infix fun or(o: ControllerState) = ControllerState(
@ -186,24 +207,116 @@ data class ControllerState constructor(
leftX = maxAbs(leftX, o.leftX), leftX = maxAbs(leftX, o.leftX),
leftY = maxAbs(leftY, o.leftY), leftY = maxAbs(leftY, o.leftY),
rightX = maxAbs(rightX, o.rightX), rightX = maxAbs(rightX, o.rightX),
rightY = maxAbs(rightY, o.rightY) rightY = maxAbs(rightY, o.rightY),
touches = touches.zip(o.touches) { a, b -> if(a.id >= 0) a else b }.toTypedArray(),
gyroX = gyroX,
gyroY = gyroY,
gyroZ = gyroZ,
accelX = accelX,
accelY = accelY,
accelZ = accelZ,
orientX = orientX,
orientY = orientY,
orientZ = orientZ,
orientW = orientW
) )
override fun equals(other: Any?): Boolean
{
if(this === other) return true
if(javaClass != other?.javaClass) return false
other as ControllerState
if(buttons != other.buttons) return false
if(l2State != other.l2State) return false
if(r2State != other.r2State) return false
if(leftX != other.leftX) return false
if(leftY != other.leftY) return false
if(rightX != other.rightX) return false
if(rightY != other.rightY) return false
if(touchIdNext != other.touchIdNext) return false
if(!touches.contentEquals(other.touches)) return false
if(gyroX != other.gyroX) return false
if(gyroY != other.gyroY) return false
if(gyroZ != other.gyroZ) return false
if(accelX != other.accelX) return false
if(accelY != other.accelY) return false
if(accelZ != other.accelZ) return false
if(orientX != other.orientX) return false
if(orientY != other.orientY) return false
if(orientZ != other.orientZ) return false
if(orientW != other.orientW) return false
return true
}
override fun hashCode(): Int
{
var result = buttons.hashCode()
result = 31 * result + l2State.hashCode()
result = 31 * result + r2State.hashCode()
result = 31 * result + leftX
result = 31 * result + leftY
result = 31 * result + rightX
result = 31 * result + rightY
result = 31 * result + touchIdNext.hashCode()
result = 31 * result + touches.contentHashCode()
result = 31 * result + gyroX.hashCode()
result = 31 * result + gyroY.hashCode()
result = 31 * result + gyroZ.hashCode()
result = 31 * result + accelX.hashCode()
result = 31 * result + accelY.hashCode()
result = 31 * result + accelZ.hashCode()
result = 31 * result + orientX.hashCode()
result = 31 * result + orientY.hashCode()
result = 31 * result + orientZ.hashCode()
result = 31 * result + orientW.hashCode()
return result
}
fun startTouch(x: UShort, y: UShort): UByte? =
touches
.find { it.id < 0 }
?.also {
it.id = touchIdNext.toByte()
it.x = x
it.y = y
touchIdNext = ((touchIdNext + 1U) and 0x7fU).toUByte()
}?.id?.toUByte()
fun stopTouch(id: UByte)
{
touches.find {
it.id >= 0 && it.id == id.toByte()
}?.let {
it.id = -1
}
}
fun setTouchPos(id: UByte, x: UShort, y: UShort): Boolean
= touches.find {
it.id >= 0 && it.id == id.toByte()
}?.let {
val r = it.x != x || it.y != y
it.x = x
it.y = y
r
} ?: false
} }
class QuitReason(val value: Int) class QuitReason(val value: Int)
{ {
override fun toString() = ChiakiNative.quitReasonToString(value) override fun toString() = ChiakiNative.quitReasonToString(value)
/** val isError = ChiakiNative.quitReasonIsError(value)
* whether the reason is CHIAKI_QUIT_REASON_STOPPED
*/
val isStopped = ChiakiNative.quitReasonIsStopped(value)
} }
sealed class Event sealed class Event
object ConnectedEvent: Event() object ConnectedEvent: Event()
data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event() data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event()
data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event() data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event()
data class RumbleEvent(val left: UByte, val right: UByte): Event()
class CreateError(val errorCode: ErrorCode): Exception("Failed to create a native object: $errorCode") class CreateError(val errorCode: ErrorCode): Exception("Failed to create a native object: $errorCode")
@ -259,7 +372,12 @@ class Session(connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean)
event(QuitEvent(QuitReason(reasonValue), reasonString)) event(QuitEvent(QuitReason(reasonValue), reasonString))
} }
fun setSurface(surface: Surface) private fun eventRumble(left: Int, right: Int)
{
event(RumbleEvent(left.toUByte(), right.toUByte()))
}
fun setSurface(surface: Surface?)
{ {
ChiakiNative.sessionSetSurface(nativePtr, surface) ChiakiNative.sessionSetSurface(nativePtr, surface)
} }

View file

@ -3,6 +3,7 @@
package com.metallic.chiaki.main package com.metallic.chiaki.main
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
@ -16,8 +17,8 @@ import com.metallic.chiaki.common.DiscoveredDisplayHost
import com.metallic.chiaki.common.DisplayHost import com.metallic.chiaki.common.DisplayHost
import com.metallic.chiaki.common.ManualDisplayHost import com.metallic.chiaki.common.ManualDisplayHost
import com.metallic.chiaki.common.ext.inflate import com.metallic.chiaki.common.ext.inflate
import com.metallic.chiaki.databinding.ItemDisplayHostBinding
import com.metallic.chiaki.lib.DiscoveryHost import com.metallic.chiaki.lib.DiscoveryHost
import kotlinx.android.synthetic.main.item_display_host.view.*
class DisplayHostDiffCallback(val old: List<DisplayHost>, val new: List<DisplayHost>): DiffUtil.Callback() class DisplayHostDiffCallback(val old: List<DisplayHost>, val new: List<DisplayHost>): DiffUtil.Callback()
{ {
@ -42,10 +43,10 @@ class DisplayHostRecyclerViewAdapter(
diff.dispatchUpdatesTo(this) diff.dispatchUpdatesTo(this)
} }
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) class ViewHolder(val binding: ItemDisplayHostBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
= ViewHolder(parent.inflate(R.layout.item_display_host)) = ViewHolder(ItemDisplayHostBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = hosts.count() override fun getItemCount() = hosts.count()
@ -53,7 +54,7 @@ class DisplayHostRecyclerViewAdapter(
{ {
val context = holder.itemView.context val context = holder.itemView.context
val host = hosts[position] val host = hosts[position]
holder.itemView.also { holder.binding.also {
it.nameTextView.text = host.name it.nameTextView.text = host.name
it.hostTextView.text = context.getString(R.string.display_host_host, host.host) it.hostTextView.text = context.getString(R.string.display_host_host, host.host)
val id = host.id val id = host.id
@ -87,7 +88,7 @@ class DisplayHostRecyclerViewAdapter(
else -> R.drawable.ic_console else -> R.drawable.ic_console
} }
) )
it.setOnClickListener { clickCallback(host) } it.root.setOnClickListener { clickCallback(host) }
val canWakeup = host.registeredHost != null val canWakeup = host.registeredHost != null
val canEditDelete = host is ManualDisplayHost val canEditDelete = host is ManualDisplayHost

View file

@ -17,52 +17,54 @@ import com.metallic.chiaki.R
import com.metallic.chiaki.common.* import com.metallic.chiaki.common.*
import com.metallic.chiaki.common.ext.putRevealExtra import com.metallic.chiaki.common.ext.putRevealExtra
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.databinding.ActivityMainBinding
import com.metallic.chiaki.lib.ConnectInfo import com.metallic.chiaki.lib.ConnectInfo
import com.metallic.chiaki.lib.DiscoveryHost import com.metallic.chiaki.lib.DiscoveryHost
import com.metallic.chiaki.manualconsole.EditManualConsoleActivity import com.metallic.chiaki.manualconsole.EditManualConsoleActivity
import com.metallic.chiaki.regist.RegistActivity import com.metallic.chiaki.regist.RegistActivity
import com.metallic.chiaki.settings.SettingsActivity import com.metallic.chiaki.settings.SettingsActivity
import com.metallic.chiaki.stream.StreamActivity import com.metallic.chiaki.stream.StreamActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() class MainActivity : AppCompatActivity()
{ {
private lateinit var viewModel: MainViewModel private lateinit var viewModel: MainViewModel
private lateinit var binding: ActivityMainBinding
private var discoveryMenuItem: MenuItem? = null private var discoveryMenuItem: MenuItem? = null
override fun onCreate(savedInstanceState: Bundle?) override fun onCreate(savedInstanceState: Bundle?)
{ {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
title = "" title = ""
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
floatingActionButton.setOnClickListener { binding.floatingActionButton.setOnClickListener {
expandFloatingActionButton(!floatingActionButton.isExpanded) expandFloatingActionButton(!binding.floatingActionButton.isExpanded)
} }
floatingActionButtonDialBackground.setOnClickListener { binding.floatingActionButtonDialBackground.setOnClickListener {
expandFloatingActionButton(false) expandFloatingActionButton(false)
} }
addManualButton.setOnClickListener { addManualConsole() } binding.addManualButton.setOnClickListener { addManualConsole() }
addManualLabelButton.setOnClickListener { addManualConsole() } binding.addManualLabelButton.setOnClickListener { addManualConsole() }
registerButton.setOnClickListener { showRegistration() } binding.registerButton.setOnClickListener { showRegistration() }
registerLabelButton.setOnClickListener { showRegistration() } binding.registerLabelButton.setOnClickListener { showRegistration() }
viewModel = ViewModelProvider(this, viewModelFactory { MainViewModel(getDatabase(this), Preferences(this)) }) viewModel = ViewModelProvider(this, viewModelFactory { MainViewModel(getDatabase(this), Preferences(this)) })
.get(MainViewModel::class.java) .get(MainViewModel::class.java)
val recyclerViewAdapter = DisplayHostRecyclerViewAdapter(this::hostTriggered, this::wakeupHost, this::editHost, this::deleteHost) val recyclerViewAdapter = DisplayHostRecyclerViewAdapter(this::hostTriggered, this::wakeupHost, this::editHost, this::deleteHost)
hostsRecyclerView.adapter = recyclerViewAdapter binding.hostsRecyclerView.adapter = recyclerViewAdapter
hostsRecyclerView.layoutManager = LinearLayoutManager(this) binding.hostsRecyclerView.layoutManager = LinearLayoutManager(this)
viewModel.displayHosts.observe(this, Observer { viewModel.displayHosts.observe(this, Observer {
val top = hostsRecyclerView.computeVerticalScrollOffset() == 0 val top = binding.hostsRecyclerView.computeVerticalScrollOffset() == 0
recyclerViewAdapter.hosts = it recyclerViewAdapter.hosts = it
if(top) if(top)
hostsRecyclerView.scrollToPosition(0) binding.hostsRecyclerView.scrollToPosition(0)
updateEmptyInfo() updateEmptyInfo()
}) })
@ -76,19 +78,19 @@ class MainActivity : AppCompatActivity()
{ {
if(viewModel.displayHosts.value?.isEmpty() ?: true) if(viewModel.displayHosts.value?.isEmpty() ?: true)
{ {
emptyInfoLayout.visibility = View.VISIBLE binding.emptyInfoLayout.visibility = View.VISIBLE
val discoveryActive = viewModel.discoveryActive.value ?: false val discoveryActive = viewModel.discoveryActive.value ?: false
emptyInfoImageView.setImageResource(if(discoveryActive) R.drawable.ic_discover_on else R.drawable.ic_discover_off) binding.emptyInfoImageView.setImageResource(if(discoveryActive) R.drawable.ic_discover_on else R.drawable.ic_discover_off)
emptyInfoTextView.setText(if(discoveryActive) R.string.display_hosts_empty_discovery_on_info else R.string.display_hosts_empty_discovery_off_info) binding.emptyInfoTextView.setText(if(discoveryActive) R.string.display_hosts_empty_discovery_on_info else R.string.display_hosts_empty_discovery_off_info)
} }
else else
emptyInfoLayout.visibility = View.GONE binding.emptyInfoLayout.visibility = View.GONE
} }
private fun expandFloatingActionButton(expand: Boolean) private fun expandFloatingActionButton(expand: Boolean)
{ {
floatingActionButton.isExpanded = expand binding.floatingActionButton.isExpanded = expand
floatingActionButton.isActivated = floatingActionButton.isExpanded binding.floatingActionButton.isActivated = binding.floatingActionButton.isExpanded
} }
override fun onStart() override fun onStart()
@ -105,7 +107,7 @@ class MainActivity : AppCompatActivity()
override fun onBackPressed() override fun onBackPressed()
{ {
if(floatingActionButton.isExpanded) if(binding.floatingActionButton.isExpanded)
{ {
expandFloatingActionButton(false) expandFloatingActionButton(false)
return return
@ -151,7 +153,7 @@ class MainActivity : AppCompatActivity()
private fun addManualConsole() private fun addManualConsole()
{ {
Intent(this, EditManualConsoleActivity::class.java).also { Intent(this, EditManualConsoleActivity::class.java).also {
it.putRevealExtra(addManualButton, rootLayout) it.putRevealExtra(binding.addManualButton, binding.rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) startActivity(it, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
} }
} }
@ -159,7 +161,7 @@ class MainActivity : AppCompatActivity()
private fun showRegistration() private fun showRegistration()
{ {
Intent(this, RegistActivity::class.java).also { Intent(this, RegistActivity::class.java).also {
it.putRevealExtra(registerButton, rootLayout) it.putRevealExtra(binding.registerButton, binding.rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) startActivity(it, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
} }
} }

View file

@ -16,10 +16,10 @@ import com.metallic.chiaki.common.RegisteredHost
import com.metallic.chiaki.common.ext.RevealActivity import com.metallic.chiaki.common.ext.RevealActivity
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase import com.metallic.chiaki.common.getDatabase
import com.metallic.chiaki.databinding.ActivityEditManualBinding
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.addTo
import kotlinx.android.synthetic.main.activity_edit_manual.*
class EditManualConsoleActivity: AppCompatActivity(), RevealActivity class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
{ {
@ -28,18 +28,20 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
const val EXTRA_MANUAL_HOST_ID = "manual_host_id" const val EXTRA_MANUAL_HOST_ID = "manual_host_id"
} }
override val revealIntent: Intent get() = intent
override val revealRootLayout: View get() = rootLayout
override val revealWindow: Window get() = window
private lateinit var viewModel: EditManualConsoleViewModel private lateinit var viewModel: EditManualConsoleViewModel
private lateinit var binding: ActivityEditManualBinding
override val revealIntent: Intent get() = intent
override val revealRootLayout: View get() = binding.rootLayout
override val revealWindow: Window get() = window
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) override fun onCreate(savedInstanceState: Bundle?)
{ {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_manual) binding = ActivityEditManualBinding.inflate(layoutInflater)
setContentView(binding.root)
handleReveal() handleReveal()
viewModel = ViewModelProvider(this, viewModelFactory { viewModel = ViewModelProvider(this, viewModelFactory {
@ -52,17 +54,17 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
.get(EditManualConsoleViewModel::class.java) .get(EditManualConsoleViewModel::class.java)
viewModel.existingHost?.observe(this, Observer { viewModel.existingHost?.observe(this, Observer {
hostEditText.setText(it.host) binding.hostEditText.setText(it.host)
}) })
viewModel.selectedRegisteredHost.observe(this, Observer { viewModel.selectedRegisteredHost.observe(this, Observer {
registeredHostTextView.setText(titleForRegisteredHost(it)) binding.registeredHostTextView.setText(titleForRegisteredHost(it))
}) })
viewModel.registeredHosts.observe(this, Observer { hosts -> viewModel.registeredHosts.observe(this, Observer { hosts ->
registeredHostTextView.setAdapter(ArrayAdapter<String>(this, R.layout.dropdown_menu_popup_item, binding.registeredHostTextView.setAdapter(ArrayAdapter<String>(this, R.layout.dropdown_menu_popup_item,
hosts.map { titleForRegisteredHost(it) })) hosts.map { titleForRegisteredHost(it) }))
registeredHostTextView.onItemClickListener = object: AdapterView.OnItemClickListener { binding.registeredHostTextView.onItemClickListener = object: AdapterView.OnItemClickListener {
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long)
{ {
if(position >= hosts.size) if(position >= hosts.size)
@ -73,8 +75,7 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
} }
}) })
binding.saveButton.setOnClickListener { saveHost() }
saveButton.setOnClickListener { saveHost() }
} }
private fun titleForRegisteredHost(registeredHost: RegisteredHost?) = private fun titleForRegisteredHost(registeredHost: RegisteredHost?) =
@ -85,14 +86,14 @@ class EditManualConsoleActivity: AppCompatActivity(), RevealActivity
private fun saveHost() private fun saveHost()
{ {
val host = hostEditText.text.toString().trim() val host = binding.hostEditText.text.toString().trim()
if(host.isEmpty()) if(host.isEmpty())
{ {
hostEditText.error = getString(R.string.entered_host_invalid) binding.hostEditText.error = getString(R.string.entered_host_invalid)
return return
} }
saveButton.isEnabled = false binding.saveButton.isEnabled = false
viewModel.saveHost(host) viewModel.saveHost(host)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {

View file

@ -12,9 +12,9 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.metallic.chiaki.R import com.metallic.chiaki.R
import com.metallic.chiaki.common.ext.RevealActivity import com.metallic.chiaki.common.ext.RevealActivity
import com.metallic.chiaki.databinding.ActivityRegistBinding
import com.metallic.chiaki.lib.RegistInfo import com.metallic.chiaki.lib.RegistInfo
import com.metallic.chiaki.lib.Target import com.metallic.chiaki.lib.Target
import kotlinx.android.synthetic.main.activity_regist.*
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
class RegistActivity: AppCompatActivity(), RevealActivity class RegistActivity: AppCompatActivity(), RevealActivity
@ -30,33 +30,35 @@ class RegistActivity: AppCompatActivity(), RevealActivity
private const val REQUEST_REGIST = 1 private const val REQUEST_REGIST = 1
} }
private lateinit var viewModel: RegistViewModel
private lateinit var binding: ActivityRegistBinding
override val revealWindow: Window get() = window override val revealWindow: Window get() = window
override val revealIntent: Intent get() = intent override val revealIntent: Intent get() = intent
override val revealRootLayout: View get() = rootLayout override val revealRootLayout: View get() = binding.rootLayout
private lateinit var viewModel: RegistViewModel
override fun onCreate(savedInstanceState: Bundle?) override fun onCreate(savedInstanceState: Bundle?)
{ {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_regist) binding = ActivityRegistBinding.inflate(layoutInflater)
setContentView(binding.root)
handleReveal() handleReveal()
viewModel = ViewModelProvider(this).get(RegistViewModel::class.java) viewModel = ViewModelProvider(this).get(RegistViewModel::class.java)
hostEditText.setText(intent.getStringExtra(EXTRA_HOST) ?: "255.255.255.255") binding.hostEditText.setText(intent.getStringExtra(EXTRA_HOST) ?: "255.255.255.255")
broadcastCheckBox.isChecked = intent.getBooleanExtra(EXTRA_BROADCAST, true) binding.broadcastCheckBox.isChecked = intent.getBooleanExtra(EXTRA_BROADCAST, true)
registButton.setOnClickListener { doRegist() } binding.registButton.setOnClickListener { doRegist() }
ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5) { binding.ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5) {
RegistViewModel.ConsoleVersion.PS5 -> R.id.ps5RadioButton RegistViewModel.ConsoleVersion.PS5 -> R.id.ps5RadioButton
RegistViewModel.ConsoleVersion.PS4_GE_8 -> R.id.ps4VersionGE8RadioButton RegistViewModel.ConsoleVersion.PS4_GE_8 -> R.id.ps4VersionGE8RadioButton
RegistViewModel.ConsoleVersion.PS4_GE_7 -> R.id.ps4VersionGE7RadioButton RegistViewModel.ConsoleVersion.PS4_GE_7 -> R.id.ps4VersionGE7RadioButton
RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.id.ps4VersionLT7RadioButton RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.id.ps4VersionLT7RadioButton
}) })
ps4VersionRadioGroup.setOnCheckedChangeListener { _, checkedId -> binding.ps4VersionRadioGroup.setOnCheckedChangeListener { _, checkedId ->
viewModel.ps4Version.value = when(checkedId) viewModel.ps4Version.value = when(checkedId)
{ {
R.id.ps5RadioButton -> RegistViewModel.ConsoleVersion.PS5 R.id.ps5RadioButton -> RegistViewModel.ConsoleVersion.PS5
@ -68,14 +70,14 @@ class RegistActivity: AppCompatActivity(), RevealActivity
} }
viewModel.ps4Version.observe(this, Observer { viewModel.ps4Version.observe(this, Observer {
psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.ConsoleVersion.PS4_LT_7) View.GONE else View.VISIBLE binding.psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.ConsoleVersion.PS4_LT_7) View.GONE else View.VISIBLE
psnIdTextInputLayout.hint = getString(when(it!!) binding.psnIdTextInputLayout.hint = getString(when(it!!)
{ {
RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.hint_regist_psn_online_id RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.hint_regist_psn_online_id
else -> R.string.hint_regist_psn_account_id else -> R.string.hint_regist_psn_account_id
}) })
pinHelpBeforeTextView.setText(if(it.isPS5) R.string.regist_pin_instructions_ps5_before else R.string.regist_pin_instructions_ps4_before) binding.pinHelpBeforeTextView.setText(if(it.isPS5) R.string.regist_pin_instructions_ps5_before else R.string.regist_pin_instructions_ps4_before)
pinHelpNavigationTextView.setText(if(it.isPS5) R.string.regist_pin_instructions_ps5_navigation else R.string.regist_pin_instructions_ps4_navigation) binding.pinHelpNavigationTextView.setText(if(it.isPS5) R.string.regist_pin_instructions_ps5_navigation else R.string.regist_pin_instructions_ps4_navigation)
}) })
} }
@ -83,11 +85,11 @@ class RegistActivity: AppCompatActivity(), RevealActivity
{ {
val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5 val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5
val host = hostEditText.text.toString().trim() val host = binding.hostEditText.text.toString().trim()
val hostValid = host.isNotEmpty() val hostValid = host.isNotEmpty()
val broadcast = broadcastCheckBox.isChecked val broadcast = binding.broadcastCheckBox.isChecked
val psnId = psnIdEditText.text.toString().trim() val psnId = binding.psnIdEditText.text.toString().trim()
val psnOnlineId: String? = if(ps4Version == RegistViewModel.ConsoleVersion.PS4_LT_7) psnId else null val psnOnlineId: String? = if(ps4Version == RegistViewModel.ConsoleVersion.PS4_LT_7) psnId else null
val psnAccountId: ByteArray? = val psnAccountId: ByteArray? =
if(ps4Version != RegistViewModel.ConsoleVersion.PS4_LT_7) if(ps4Version != RegistViewModel.ConsoleVersion.PS4_LT_7)
@ -101,11 +103,11 @@ class RegistActivity: AppCompatActivity(), RevealActivity
} }
val pin = pinEditText.text.toString() val pin = binding.pinEditText.text.toString()
val pinValid = pin.length == PIN_LENGTH val pinValid = pin.length == PIN_LENGTH
hostEditText.error = if(!hostValid) getString(R.string.entered_host_invalid) else null binding.hostEditText.error = if(!hostValid) getString(R.string.entered_host_invalid) else null
psnIdEditText.error = binding.psnIdEditText.error =
if(!psnIdValid) if(!psnIdValid)
getString(when(ps4Version) getString(when(ps4Version)
{ {
@ -114,7 +116,7 @@ class RegistActivity: AppCompatActivity(), RevealActivity
}) })
else else
null null
pinEditText.error = if(!pinValid) getString(R.string.regist_pin_invalid, PIN_LENGTH) else null binding.pinEditText.error = if(!pinValid) getString(R.string.regist_pin_invalid, PIN_LENGTH) else null
if(!hostValid || !psnIdValid || !pinValid) if(!hostValid || !psnIdValid || !pinValid)
return return

View file

@ -16,8 +16,8 @@ import com.metallic.chiaki.R
import com.metallic.chiaki.common.MacAddress import com.metallic.chiaki.common.MacAddress
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase import com.metallic.chiaki.common.getDatabase
import com.metallic.chiaki.databinding.ActivityRegistExecuteBinding
import com.metallic.chiaki.lib.RegistInfo import com.metallic.chiaki.lib.RegistInfo
import kotlinx.android.synthetic.main.activity_regist_execute.*
import kotlin.math.max import kotlin.math.max
class RegistExecuteActivity: AppCompatActivity() class RegistExecuteActivity: AppCompatActivity()
@ -31,55 +31,57 @@ class RegistExecuteActivity: AppCompatActivity()
} }
private lateinit var viewModel: RegistExecuteViewModel private lateinit var viewModel: RegistExecuteViewModel
private lateinit var binding: ActivityRegistExecuteBinding
override fun onCreate(savedInstanceState: Bundle?) override fun onCreate(savedInstanceState: Bundle?)
{ {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_regist_execute) binding = ActivityRegistExecuteBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this, viewModelFactory { RegistExecuteViewModel(getDatabase(this)) }) viewModel = ViewModelProvider(this, viewModelFactory { RegistExecuteViewModel(getDatabase(this)) })
.get(RegistExecuteViewModel::class.java) .get(RegistExecuteViewModel::class.java)
logTextView.setHorizontallyScrolling(true) binding.logTextView.setHorizontallyScrolling(true)
logTextView.movementMethod = ScrollingMovementMethod() binding.logTextView.movementMethod = ScrollingMovementMethod()
viewModel.logText.observe(this, Observer { viewModel.logText.observe(this, Observer {
val textLayout = logTextView.layout ?: return@Observer val textLayout = binding.logTextView.layout ?: return@Observer
val lineCount = textLayout.lineCount val lineCount = textLayout.lineCount
if(lineCount < 1) if(lineCount < 1)
return@Observer return@Observer
logTextView.text = it binding.logTextView.text = it
val scrollY = textLayout.getLineBottom(lineCount - 1) - logTextView.height + logTextView.paddingTop + logTextView.paddingBottom val scrollY = textLayout.getLineBottom(lineCount - 1) - binding.logTextView.height + binding.logTextView.paddingTop + binding.logTextView.paddingBottom
logTextView.scrollTo(0, max(scrollY, 0)) binding.logTextView.scrollTo(0, max(scrollY, 0))
}) })
viewModel.state.observe(this, Observer { viewModel.state.observe(this, Observer {
progressBar.visibility = if(it == RegistExecuteViewModel.State.RUNNING) View.VISIBLE else View.GONE binding.progressBar.visibility = if(it == RegistExecuteViewModel.State.RUNNING) View.VISIBLE else View.GONE
when(it) when(it)
{ {
RegistExecuteViewModel.State.FAILED -> RegistExecuteViewModel.State.FAILED ->
{ {
infoTextView.visibility = View.VISIBLE binding.infoTextView.visibility = View.VISIBLE
infoTextView.setText(R.string.regist_info_failed) binding.infoTextView.setText(R.string.regist_info_failed)
setResult(RESULT_FAILED) setResult(RESULT_FAILED)
} }
RegistExecuteViewModel.State.SUCCESSFUL, RegistExecuteViewModel.State.SUCCESSFUL_DUPLICATE -> RegistExecuteViewModel.State.SUCCESSFUL, RegistExecuteViewModel.State.SUCCESSFUL_DUPLICATE ->
{ {
infoTextView.visibility = View.VISIBLE binding.infoTextView.visibility = View.VISIBLE
infoTextView.setText(R.string.regist_info_success) binding.infoTextView.setText(R.string.regist_info_success)
setResult(RESULT_OK) setResult(RESULT_OK)
if(it == RegistExecuteViewModel.State.SUCCESSFUL_DUPLICATE) if(it == RegistExecuteViewModel.State.SUCCESSFUL_DUPLICATE)
showDuplicateDialog() showDuplicateDialog()
} }
RegistExecuteViewModel.State.STOPPED -> RegistExecuteViewModel.State.STOPPED ->
{ {
infoTextView.visibility = View.GONE binding.infoTextView.visibility = View.GONE
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_CANCELED)
} }
else -> infoTextView.visibility = View.GONE else -> binding.infoTextView.visibility = View.GONE
} }
}) })
shareLogButton.setOnClickListener { binding.shareLogButton.setOnClickListener {
val log = viewModel.logText.value ?: "" val log = viewModel.logText.value ?: ""
Intent(Intent.ACTION_SEND).also { Intent(Intent.ACTION_SEND).also {
it.type = "text/plain" it.type = "text/plain"

View file

@ -1,19 +1,37 @@
package com.metallic.chiaki.session package com.metallic.chiaki.session
import android.util.Log import android.content.Context
import android.view.InputDevice import android.hardware.*
import android.view.KeyEvent import android.view.*
import android.view.MotionEvent import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.common.Preferences
import com.metallic.chiaki.lib.ControllerState import com.metallic.chiaki.lib.ControllerState
class StreamInput(val preferences: Preferences) class StreamInput(val context: Context, val preferences: Preferences)
{ {
var controllerStateChangedCallback: ((ControllerState) -> Unit)? = null var controllerStateChangedCallback: ((ControllerState) -> Unit)? = null
val controllerState: ControllerState get() val controllerState: ControllerState get()
{ {
val controllerState = keyControllerState or motionControllerState val controllerState = sensorControllerState or keyControllerState or motionControllerState
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
@Suppress("DEPRECATION")
when(windowManager.defaultDisplay.rotation)
{
Surface.ROTATION_90 -> {
controllerState.accelX *= -1.0f
controllerState.accelZ *= -1.0f
controllerState.gyroX *= -1.0f
controllerState.gyroZ *= -1.0f
controllerState.orientX *= -1.0f
controllerState.orientZ *= -1.0f
}
else -> {}
}
// prioritize motion controller's l2 and r2 over key // prioritize motion controller's l2 and r2 over key
// (some controllers send only key, others both but key earlier than full press) // (some controllers send only key, others both but key earlier than full press)
@ -25,6 +43,7 @@ class StreamInput(val preferences: Preferences)
return controllerState or touchControllerState return controllerState or touchControllerState
} }
private val sensorControllerState = ControllerState() // from Motion Sensors
private val keyControllerState = ControllerState() // from KeyEvents private val keyControllerState = ControllerState() // from KeyEvents
private val motionControllerState = ControllerState() // from MotionEvents private val motionControllerState = ControllerState() // from MotionEvents
var touchControllerState = ControllerState() var touchControllerState = ControllerState()
@ -36,6 +55,66 @@ class StreamInput(val preferences: Preferences)
private val swapCrossMoon = preferences.swapCrossMoon private val swapCrossMoon = preferences.swapCrossMoon
private val sensorEventListener = object: SensorEventListener {
override fun onSensorChanged(event: SensorEvent)
{
when(event.sensor.type)
{
Sensor.TYPE_ACCELEROMETER -> {
sensorControllerState.accelX = event.values[1] / SensorManager.GRAVITY_EARTH
sensorControllerState.accelY = event.values[2] / SensorManager.GRAVITY_EARTH
sensorControllerState.accelZ = event.values[0] / SensorManager.GRAVITY_EARTH
}
Sensor.TYPE_GYROSCOPE -> {
sensorControllerState.gyroX = event.values[1]
sensorControllerState.gyroY = event.values[2]
sensorControllerState.gyroZ = event.values[0]
}
Sensor.TYPE_ROTATION_VECTOR -> {
val q = floatArrayOf(0f, 0f, 0f, 0f)
SensorManager.getQuaternionFromVector(q, event.values)
sensorControllerState.orientX = q[2]
sensorControllerState.orientY = q[3]
sensorControllerState.orientZ = q[1]
sensorControllerState.orientW = q[0]
}
else -> return
}
controllerStateUpdated()
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
private val motionLifecycleObserver = object: LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume()
{
val samplingPeriodUs = 4000
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
listOfNotNull(
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
).forEach {
sensorManager.registerListener(sensorEventListener, it, samplingPeriodUs)
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause()
{
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensorManager.unregisterListener(sensorEventListener)
}
}
fun observe(lifecycleOwner: LifecycleOwner)
{
if(preferences.motionEnabled)
lifecycleOwner.lifecycle.addObserver(motionLifecycleObserver)
}
private fun controllerStateUpdated() private fun controllerStateUpdated()
{ {
controllerStateChangedCallback?.let { it(controllerState) } controllerStateChangedCallback?.let { it(controllerState) }
@ -98,7 +177,7 @@ class StreamInput(val preferences: Preferences)
{ {
if(event.source and InputDevice.SOURCE_CLASS_JOYSTICK != InputDevice.SOURCE_CLASS_JOYSTICK) if(event.source and InputDevice.SOURCE_CLASS_JOYSTICK != InputDevice.SOURCE_CLASS_JOYSTICK)
return false return false
fun Float.signedAxis() = (this * Short.MAX_VALUE).toShort() fun Float.signedAxis() = (this * Short.MAX_VALUE).toInt().toShort()
fun Float.unsignedAxis() = (this * UByte.MAX_VALUE.toFloat()).toUInt().toUByte() fun Float.unsignedAxis() = (this * UByte.MAX_VALUE.toFloat()).toUInt().toUByte()
motionControllerState.leftX = event.getAxisValue(MotionEvent.AXIS_X).signedAxis() motionControllerState.leftX = event.getAxisValue(MotionEvent.AXIS_X).signedAxis()
motionControllerState.leftY = event.getAxisValue(MotionEvent.AXIS_Y).signedAxis() motionControllerState.leftY = event.getAxisValue(MotionEvent.AXIS_Y).signedAxis()

View file

@ -25,8 +25,11 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
private val _state = MutableLiveData<StreamState>(StreamStateIdle) private val _state = MutableLiveData<StreamState>(StreamStateIdle)
val state: LiveData<StreamState> get() = _state val state: LiveData<StreamState> get() = _state
private val _rumbleState = MutableLiveData<RumbleEvent>(RumbleEvent(0U, 0U))
val rumbleState: LiveData<RumbleEvent> get() = _rumbleState
var surfaceTexture: SurfaceTexture? = null private var surfaceTexture: SurfaceTexture? = null
private var surface: Surface? = null
init init
{ {
@ -59,9 +62,9 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
_state.value = StreamStateConnecting _state.value = StreamStateConnecting
session.eventCallback = this::eventCallback session.eventCallback = this::eventCallback
session.start() session.start()
val surfaceTexture = surfaceTexture val surface = surface
if(surfaceTexture != null) if(surface != null)
session.setSurface(Surface(surfaceTexture)) session.setSurface(surface)
this.session = session this.session = session
} }
catch(e: CreateError) catch(e: CreateError)
@ -86,9 +89,30 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
event.pinIncorrect event.pinIncorrect
) )
) )
is RumbleEvent -> _rumbleState.postValue(event)
} }
} }
fun attachToSurfaceView(surfaceView: SurfaceView)
{
surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) { }
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int)
{
val surface = holder.surface
this@StreamSession.surface = surface
session?.setSurface(surface)
}
override fun surfaceDestroyed(holder: SurfaceHolder)
{
this@StreamSession.surface = null
session?.setSurface(null)
}
})
}
fun attachToTextureView(textureView: TextureView) fun attachToTextureView(textureView: TextureView)
{ {
textureView.surfaceTextureListener = object: TextureView.SurfaceTextureListener { textureView.surfaceTextureListener = object: TextureView.SurfaceTextureListener {
@ -97,6 +121,7 @@ class StreamSession(val connectInfo: ConnectInfo, val logManager: LogManager, va
if(surfaceTexture != null) if(surfaceTexture != null)
return return
surfaceTexture = surface surfaceTexture = surface
this@StreamSession.surface = Surface(surfaceTexture)
session?.setSurface(Surface(surface)) session?.setSurface(Surface(surface))
} }

View file

@ -9,7 +9,7 @@ import androidx.fragment.app.Fragment
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.metallic.chiaki.R import com.metallic.chiaki.R
import kotlinx.android.synthetic.main.activity_settings.* import com.metallic.chiaki.databinding.ActivitySettingsBinding
interface TitleFragment interface TitleFragment
{ {
@ -18,20 +18,23 @@ interface TitleFragment
class SettingsActivity: AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback class SettingsActivity: AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
{ {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) override fun onCreate(savedInstanceState: Bundle?)
{ {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
title = "" title = ""
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
val rootFragment = SettingsFragment() val rootFragment = SettingsFragment()
replaceFragment(rootFragment, false) replaceFragment(rootFragment, false)
supportFragmentManager.addOnBackStackChangedListener { supportFragmentManager.addOnBackStackChangedListener {
val titleFragment = supportFragmentManager.findFragmentById(R.id.settingsFragment) as? TitleFragment ?: return@addOnBackStackChangedListener val titleFragment = supportFragmentManager.findFragmentById(R.id.settingsFragment) as? TitleFragment ?: return@addOnBackStackChangedListener
titleTextView.text = titleFragment.getTitle(resources) binding.titleTextView.text = titleFragment.getTitle(resources)
} }
titleTextView.text = rootFragment.getTitle(resources) binding.titleTextView.text = rootFragment.getTitle(resources)
} }
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference) = when(pref.fragment) override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference) = when(pref.fragment)

View file

@ -16,7 +16,6 @@ import com.metallic.chiaki.common.exportAndShareAllSettings
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase import com.metallic.chiaki.common.getDatabase
import com.metallic.chiaki.common.importSettingsFromUri import com.metallic.chiaki.common.importSettingsFromUri
import com.metallic.chiaki.lib.Codec
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.addTo
@ -26,6 +25,9 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
{ {
preferences.logVerboseKey -> preferences.logVerbose preferences.logVerboseKey -> preferences.logVerbose
preferences.swapCrossMoonKey -> preferences.swapCrossMoon preferences.swapCrossMoonKey -> preferences.swapCrossMoon
preferences.rumbleEnabledKey -> preferences.rumbleEnabled
preferences.motionEnabledKey -> preferences.motionEnabled
preferences.buttonHapticEnabledKey -> preferences.buttonHapticEnabled
else -> defValue else -> defValue
} }
@ -35,6 +37,9 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
{ {
preferences.logVerboseKey -> preferences.logVerbose = value preferences.logVerboseKey -> preferences.logVerbose = value
preferences.swapCrossMoonKey -> preferences.swapCrossMoon = value preferences.swapCrossMoonKey -> preferences.swapCrossMoon = value
preferences.rumbleEnabledKey -> preferences.rumbleEnabled = value
preferences.motionEnabledKey -> preferences.motionEnabled = value
preferences.buttonHapticEnabledKey -> preferences.buttonHapticEnabled = value
} }
} }

View file

@ -2,13 +2,13 @@
package com.metallic.chiaki.settings package com.metallic.chiaki.settings
import android.view.View import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.metallic.chiaki.R import com.metallic.chiaki.R
import com.metallic.chiaki.common.LogFile import com.metallic.chiaki.common.LogFile
import com.metallic.chiaki.common.ext.inflate import com.metallic.chiaki.common.ext.inflate
import kotlinx.android.synthetic.main.item_log_file.view.* import com.metallic.chiaki.databinding.ItemLogFileBinding
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -20,7 +20,7 @@ class SettingsLogsAdapter: RecyclerView.Adapter<SettingsLogsAdapter.ViewHolder>(
private val dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT) private val dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT)
private val timeFormat = SimpleDateFormat("HH:mm:ss:SSS", Locale.getDefault()) private val timeFormat = SimpleDateFormat("HH:mm:ss:SSS", Locale.getDefault())
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) class ViewHolder(val binding: ItemLogFileBinding): RecyclerView.ViewHolder(binding.root)
var logFiles: List<LogFile> = listOf() var logFiles: List<LogFile> = listOf()
set(value) set(value)
@ -29,16 +29,16 @@ class SettingsLogsAdapter: RecyclerView.Adapter<SettingsLogsAdapter.ViewHolder>(
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(parent.inflate(R.layout.item_log_file)) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ViewHolder(ItemLogFileBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = logFiles.size override fun getItemCount() = logFiles.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) override fun onBindViewHolder(holder: ViewHolder, position: Int)
{ {
val view = holder.itemView
val logFile = logFiles[position] val logFile = logFiles[position]
view.nameTextView.text = "${dateFormat.format(logFile.date)} ${timeFormat.format(logFile.date)}" holder.binding.nameTextView.text = "${dateFormat.format(logFile.date)} ${timeFormat.format(logFile.date)}"
view.summaryTextView.text = logFile.filename holder.binding.summaryTextView.text = logFile.filename
view.shareButton.setOnClickListener { shareCallback?.let { it(logFile) } } holder.binding.shareButton.setOnClickListener { shareCallback?.let { it(logFile) } }
} }
} }

View file

@ -21,29 +21,35 @@ import com.metallic.chiaki.common.LogFile
import com.metallic.chiaki.common.LogManager import com.metallic.chiaki.common.LogManager
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.fileProviderAuthority import com.metallic.chiaki.common.fileProviderAuthority
import kotlinx.android.synthetic.main.fragment_settings_logs.* import com.metallic.chiaki.databinding.FragmentSettingsLogsBinding
class SettingsLogsFragment: AppCompatDialogFragment(), TitleFragment class SettingsLogsFragment: AppCompatDialogFragment(), TitleFragment
{ {
private lateinit var viewModel: SettingsLogsViewModel private lateinit var viewModel: SettingsLogsViewModel
private var _binding: FragmentSettingsLogsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_settings_logs, container, false) FragmentSettingsLogsBinding.inflate(inflater, container, false).let {
_binding = it
it.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{ {
val context = context!! val context = requireContext()
viewModel = ViewModelProvider(this, viewModelFactory { SettingsLogsViewModel(LogManager(context)) }) viewModel = ViewModelProvider(this, viewModelFactory { SettingsLogsViewModel(LogManager(context)) })
.get(SettingsLogsViewModel::class.java) .get(SettingsLogsViewModel::class.java)
val adapter = SettingsLogsAdapter() val adapter = SettingsLogsAdapter()
logsRecyclerView.layoutManager = LinearLayoutManager(context) binding.logsRecyclerView.layoutManager = LinearLayoutManager(context)
logsRecyclerView.adapter = adapter binding.logsRecyclerView.adapter = adapter
adapter.shareCallback = this::shareLogFile adapter.shareCallback = this::shareLogFile
viewModel.sessionLogs.observe(viewLifecycleOwner, Observer { viewModel.sessionLogs.observe(viewLifecycleOwner, Observer {
adapter.logFiles = it adapter.logFiles = it
emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE binding.emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE
}) })
val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context) val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context)
@ -55,7 +61,7 @@ class SettingsLogsFragment: AppCompatDialogFragment(), TitleFragment
viewModel.deleteLog(file) viewModel.deleteLog(file)
} }
} }
ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(logsRecyclerView) ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(binding.logsRecyclerView)
} }
override fun getTitle(resources: Resources): String = resources.getString(R.string.preferences_logs_title) override fun getTitle(resources: Resources): String = resources.getString(R.string.preferences_logs_title)

View file

@ -2,17 +2,15 @@
package com.metallic.chiaki.settings package com.metallic.chiaki.settings
import android.view.View import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.metallic.chiaki.R
import com.metallic.chiaki.common.RegisteredHost import com.metallic.chiaki.common.RegisteredHost
import com.metallic.chiaki.common.ext.inflate import com.metallic.chiaki.databinding.ItemRegisteredHostBinding
import kotlinx.android.synthetic.main.item_registered_host.view.*
class SettingsRegisteredHostsAdapter: RecyclerView.Adapter<SettingsRegisteredHostsAdapter.ViewHolder>() class SettingsRegisteredHostsAdapter: RecyclerView.Adapter<SettingsRegisteredHostsAdapter.ViewHolder>()
{ {
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) class ViewHolder(val binding: ItemRegisteredHostBinding): RecyclerView.ViewHolder(binding.root)
var hosts: List<RegisteredHost> = listOf() var hosts: List<RegisteredHost> = listOf()
set(value) set(value)
@ -21,15 +19,15 @@ class SettingsRegisteredHostsAdapter: RecyclerView.Adapter<SettingsRegisteredHos
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(parent.inflate(R.layout.item_registered_host)) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
= ViewHolder(ItemRegisteredHostBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = hosts.size override fun getItemCount() = hosts.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) override fun onBindViewHolder(holder: ViewHolder, position: Int)
{ {
val view = holder.itemView
val host = hosts[position] val host = hosts[position]
view.nameTextView.text = "${host.serverNickname} (${if(host.target.isPS5) "PS5" else "PS4"})" holder.binding.nameTextView.text = "${host.serverNickname} (${if(host.target.isPS5) "PS5" else "PS4"})"
view.summaryTextView.text = host.serverMac.toString() holder.binding.summaryTextView.text = host.serverMac.toString()
} }
} }

View file

@ -20,25 +20,32 @@ import com.metallic.chiaki.R
import com.metallic.chiaki.common.ext.putRevealExtra import com.metallic.chiaki.common.ext.putRevealExtra
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase import com.metallic.chiaki.common.getDatabase
import com.metallic.chiaki.databinding.FragmentSettingsRegisteredHostsBinding
import com.metallic.chiaki.regist.RegistActivity import com.metallic.chiaki.regist.RegistActivity
import kotlinx.android.synthetic.main.fragment_settings_registered_hosts.*
class SettingsRegisteredHostsFragment: AppCompatDialogFragment(), TitleFragment class SettingsRegisteredHostsFragment: AppCompatDialogFragment(), TitleFragment
{ {
private lateinit var viewModel: SettingsRegisteredHostsViewModel private lateinit var viewModel: SettingsRegisteredHostsViewModel
private var _binding: FragmentSettingsRegisteredHostsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_settings_registered_hosts, container, false) FragmentSettingsRegisteredHostsBinding.inflate(inflater, container, false).let {
_binding = it
it.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{ {
viewModel = ViewModelProvider(this, viewModelFactory { SettingsRegisteredHostsViewModel(getDatabase(context!!)) }) val context = requireContext()
viewModel = ViewModelProvider(this, viewModelFactory { SettingsRegisteredHostsViewModel(getDatabase(context)) })
.get(SettingsRegisteredHostsViewModel::class.java) .get(SettingsRegisteredHostsViewModel::class.java)
val adapter = SettingsRegisteredHostsAdapter() val adapter = SettingsRegisteredHostsAdapter()
hostsRecyclerView.layoutManager = LinearLayoutManager(context) binding.hostsRecyclerView.layoutManager = LinearLayoutManager(context)
hostsRecyclerView.adapter = adapter binding.hostsRecyclerView.adapter = adapter
val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context!!) val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context)
{ {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int)
{ {
@ -56,15 +63,15 @@ class SettingsRegisteredHostsFragment: AppCompatDialogFragment(), TitleFragment
.show() .show()
} }
} }
ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(hostsRecyclerView) ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(binding.hostsRecyclerView)
viewModel.registeredHosts.observe(this, Observer { viewModel.registeredHosts.observe(this, Observer {
adapter.hosts = it adapter.hosts = it
emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE binding.emptyInfoGroup.visibility = if(it.isEmpty()) View.VISIBLE else View.GONE
}) })
floatingActionButton.setOnClickListener { binding.floatingActionButton.setOnClickListener {
Intent(context, RegistActivity::class.java).also { Intent(context, RegistActivity::class.java).also {
it.putRevealExtra(floatingActionButton, rootLayout) it.putRevealExtra(binding.floatingActionButton, binding.rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()) startActivity(it, ActivityOptions.makeSceneTransitionAnimation(activity).toBundle())
} }
} }

View file

@ -0,0 +1,68 @@
package com.metallic.chiaki.stream
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
// see ExoPlayer's AspectRatioFrameLayout
class AspectRatioFrameLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null): FrameLayout(context, attrs)
{
companion object
{
private const val MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f
}
var aspectRatio = 0f
set(value)
{
if(field != value)
{
field = value
requestLayout()
}
}
var mode: TransformMode = TransformMode.FIT
set(value)
{
if(field != value)
{
field = value
requestLayout()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if(aspectRatio <= 0)
{
// Aspect ratio not set.
return
}
var width = measuredWidth
var height = measuredHeight
val viewAspectRatio = width.toFloat() / height
val aspectDeformation = aspectRatio / viewAspectRatio - 1
if(Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION)
return
when(mode)
{
TransformMode.ZOOM ->
if(aspectDeformation > 0)
width = (height * aspectRatio).toInt()
else
height = (width / aspectRatio).toInt()
TransformMode.FIT ->
if(aspectDeformation > 0)
height = (width / aspectRatio).toInt()
else
width = (height * aspectRatio).toInt()
TransformMode.STRETCH -> {}
}
super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
}
}

View file

@ -6,12 +6,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.app.AlertDialog import android.app.AlertDialog
import android.graphics.Matrix import android.graphics.Matrix
import android.os.Bundle import android.os.*
import android.os.Handler import android.view.*
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.TextureView
import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone import androidx.core.view.isGone
@ -20,15 +16,18 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.* import androidx.lifecycle.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.metallic.chiaki.R import com.metallic.chiaki.R
import com.metallic.chiaki.common.LogManager
import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.common.Preferences
import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.databinding.ActivityStreamBinding
import com.metallic.chiaki.lib.ConnectInfo import com.metallic.chiaki.lib.ConnectInfo
import com.metallic.chiaki.lib.ConnectVideoProfile import com.metallic.chiaki.lib.ConnectVideoProfile
import com.metallic.chiaki.session.* import com.metallic.chiaki.session.*
import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment import com.metallic.chiaki.touchcontrols.DefaultTouchControlsFragment
import com.metallic.chiaki.touchcontrols.TouchControlsFragment import com.metallic.chiaki.touchcontrols.TouchControlsFragment
import kotlinx.android.synthetic.main.activity_stream.* import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import kotlin.math.min
private sealed class DialogContents private sealed class DialogContents
private object StreamQuitDialog: DialogContents() private object StreamQuitDialog: DialogContents()
@ -44,6 +43,8 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
} }
private lateinit var viewModel: StreamViewModel private lateinit var viewModel: StreamViewModel
private lateinit var binding: ActivityStreamBinding
private val uiVisibilityHandler = Handler() private val uiVisibilityHandler = Handler()
override fun onCreate(savedInstanceState: Bundle?) override fun onCreate(savedInstanceState: Bundle?)
@ -58,64 +59,76 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
} }
viewModel = ViewModelProvider(this, viewModelFactory { viewModel = ViewModelProvider(this, viewModelFactory {
StreamViewModel(Preferences(this), LogManager(this), connectInfo) StreamViewModel(application, connectInfo)
})[StreamViewModel::class.java] })[StreamViewModel::class.java]
setContentView(R.layout.activity_stream) viewModel.input.observe(this)
binding = ActivityStreamBinding.inflate(layoutInflater)
setContentView(binding.root)
window.decorView.setOnSystemUiVisibilityChangeListener(this) window.decorView.setOnSystemUiVisibilityChangeListener(this)
viewModel.onScreenControlsEnabled.observe(this, Observer { viewModel.onScreenControlsEnabled.observe(this, Observer {
if(onScreenControlsSwitch.isChecked != it) if(binding.onScreenControlsSwitch.isChecked != it)
onScreenControlsSwitch.isChecked = it binding.onScreenControlsSwitch.isChecked = it
if(onScreenControlsSwitch.isChecked) if(binding.onScreenControlsSwitch.isChecked)
touchpadOnlySwitch.isChecked = false binding.touchpadOnlySwitch.isChecked = false
}) })
onScreenControlsSwitch.setOnCheckedChangeListener { _, isChecked -> binding.onScreenControlsSwitch.setOnCheckedChangeListener { _, isChecked ->
viewModel.setOnScreenControlsEnabled(isChecked) viewModel.setOnScreenControlsEnabled(isChecked)
showOverlay() showOverlay()
} }
viewModel.touchpadOnlyEnabled.observe(this, Observer { viewModel.touchpadOnlyEnabled.observe(this, Observer {
if(touchpadOnlySwitch.isChecked != it) if(binding.touchpadOnlySwitch.isChecked != it)
touchpadOnlySwitch.isChecked = it binding.touchpadOnlySwitch.isChecked = it
if(touchpadOnlySwitch.isChecked) if(binding.touchpadOnlySwitch.isChecked)
onScreenControlsSwitch.isChecked = false binding.onScreenControlsSwitch.isChecked = false
}) })
touchpadOnlySwitch.setOnCheckedChangeListener { _, isChecked -> binding.touchpadOnlySwitch.setOnCheckedChangeListener { _, isChecked ->
viewModel.setTouchpadOnlyEnabled(isChecked) viewModel.setTouchpadOnlyEnabled(isChecked)
showOverlay() showOverlay()
} }
displayModeToggle.addOnButtonCheckedListener { _, checkedId, _ -> binding.displayModeToggle.addOnButtonCheckedListener { _, _, _ ->
// following 'if' is a workaround until selectionRequired for MaterialButtonToggleGroup adjustStreamViewAspect()
// comes out of alpha.
// See https://stackoverflow.com/questions/56164004/required-single-selection-on-materialbuttontogglegroup
if (displayModeToggle.checkedButtonId == -1)
displayModeToggle.check(checkedId)
adjustTextureViewAspect()
showOverlay() showOverlay()
} }
viewModel.session.attachToTextureView(textureView) //viewModel.session.attachToTextureView(textureView)
viewModel.session.attachToSurfaceView(binding.surfaceView)
viewModel.session.state.observe(this, Observer { this.stateChanged(it) }) viewModel.session.state.observe(this, Observer { this.stateChanged(it) })
textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> adjustStreamViewAspect()
adjustTextureViewAspect()
if(Preferences(this).rumbleEnabled)
{
val vibrator = getSystemService(VIBRATOR_SERVICE) as Vibrator
viewModel.session.rumbleState.observe(this, Observer {
val amplitude = min(255, (it.left.toInt() + it.right.toInt()) / 2)
vibrator.cancel()
if(amplitude == 0)
return@Observer
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
vibrator.vibrate(VibrationEffect.createOneShot(1000, amplitude))
else
vibrator.vibrate(1000)
})
} }
} }
private val controlsDisposable = CompositeDisposable()
override fun onAttachFragment(fragment: Fragment) override fun onAttachFragment(fragment: Fragment)
{ {
super.onAttachFragment(fragment) super.onAttachFragment(fragment)
if(fragment is TouchControlsFragment) if(fragment is TouchControlsFragment)
{ {
fragment.controllerStateCallback = { viewModel.input.touchControllerState = it } fragment.controllerState
.subscribe { viewModel.input.touchControllerState = it }
.addTo(controlsDisposable)
fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled
} if(fragment is TouchpadOnlyFragment)
if(fragment is TouchpadOnlyFragment) fragment.touchpadOnlyEnabled = viewModel.touchpadOnlyEnabled
{
fragment.controllerStateCallback = { viewModel.input.touchControllerState = it }
fragment.touchpadOnlyEnabled = viewModel.touchpadOnlyEnabled
} }
} }
@ -132,6 +145,12 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
viewModel.session.pause() viewModel.session.pause()
} }
override fun onDestroy()
{
super.onDestroy()
controlsDisposable.dispose()
}
private fun reconnect() private fun reconnect()
{ {
viewModel.session.shutdown() viewModel.session.shutdown()
@ -150,14 +169,14 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
private fun showOverlay() private fun showOverlay()
{ {
overlay.isVisible = true binding.overlay.isVisible = true
overlay.animate() binding.overlay.animate()
.alpha(1.0f) .alpha(1.0f)
.setListener(object: AnimatorListenerAdapter() .setListener(object: AnimatorListenerAdapter()
{ {
override fun onAnimationEnd(animation: Animator?) override fun onAnimationEnd(animation: Animator)
{ {
overlay.alpha = 1.0f binding.overlay.alpha = 1.0f
} }
}) })
uiVisibilityHandler.removeCallbacks(hideSystemUIRunnable) uiVisibilityHandler.removeCallbacks(hideSystemUIRunnable)
@ -166,13 +185,13 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
private fun hideOverlay() private fun hideOverlay()
{ {
overlay.animate() binding.overlay.animate()
.alpha(0.0f) .alpha(0.0f)
.setListener(object: AnimatorListenerAdapter() .setListener(object: AnimatorListenerAdapter()
{ {
override fun onAnimationEnd(animation: Animator?) override fun onAnimationEnd(animation: Animator)
{ {
overlay.isGone = true binding.overlay.isGone = true
} }
}) })
} }
@ -205,34 +224,39 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
private fun stateChanged(state: StreamState) private fun stateChanged(state: StreamState)
{ {
progressBar.visibility = if(state == StreamStateConnecting) View.VISIBLE else View.GONE binding.progressBar.visibility = if(state == StreamStateConnecting) View.VISIBLE else View.GONE
when(state) when(state)
{ {
is StreamStateQuit -> is StreamStateQuit ->
{ {
if(!state.reason.isStopped && dialogContents != StreamQuitDialog) if(dialogContents != StreamQuitDialog)
{ {
dialog?.dismiss() if(state.reason.isError)
val reasonStr = state.reasonString {
val dialog = MaterialAlertDialogBuilder(this) dialog?.dismiss()
.setMessage(getString(R.string.alert_message_session_quit, state.reason.toString()) val reasonStr = state.reasonString
+ (if(reasonStr != null) "\n$reasonStr" else "")) val dialog = MaterialAlertDialogBuilder(this)
.setPositiveButton(R.string.action_reconnect) { _, _ -> .setMessage(getString(R.string.alert_message_session_quit, state.reason.toString())
dialog = null + (if(reasonStr != null) "\n$reasonStr" else ""))
reconnect() .setPositiveButton(R.string.action_reconnect) { _, _ ->
} dialog = null
.setOnCancelListener { reconnect()
dialog = null }
finish() .setOnCancelListener {
} dialog = null
.setNegativeButton(R.string.action_quit_session) { _, _ -> finish()
dialog = null }
finish() .setNegativeButton(R.string.action_quit_session) { _, _ ->
} dialog = null
.create() finish()
dialogContents = StreamQuitDialog }
dialog.show() .create()
dialogContents = StreamQuitDialog
dialog.show()
}
else
finish()
} }
} }
@ -287,77 +311,89 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
dialog.show() dialog.show()
} }
} }
else -> {}
} }
} }
private fun adjustTextureViewAspect() private fun adjustTextureViewAspect(textureView: TextureView)
{ {
val displayInfo = DisplayInfo(viewModel.session.connectInfo.videoProfile, textureView) val trans = TextureViewTransform(viewModel.session.connectInfo.videoProfile, textureView)
val resolution = displayInfo.computeResolutionFor(displayModeToggle.checkedButtonId) val resolution = trans.resolutionFor(TransformMode.fromButton(binding.displayModeToggle.checkedButtonId))
Matrix().also { Matrix().also {
textureView.getTransform(it) textureView.getTransform(it)
it.setScale(resolution.width / displayInfo.viewWidth, resolution.height / displayInfo.viewHeight) it.setScale(resolution.width / trans.viewWidth, resolution.height / trans.viewHeight)
it.postTranslate((displayInfo.viewWidth - resolution.width) * 0.5f, (displayInfo.viewHeight - resolution.height) * 0.5f) it.postTranslate((trans.viewWidth - resolution.width) * 0.5f, (trans.viewHeight - resolution.height) * 0.5f)
textureView.setTransform(it) textureView.setTransform(it)
} }
} }
private fun adjustSurfaceViewAspect()
{
val videoProfile = viewModel.session.connectInfo.videoProfile
binding.aspectRatioLayout.aspectRatio = videoProfile.width.toFloat() / videoProfile.height.toFloat()
binding.aspectRatioLayout.mode = TransformMode.fromButton(binding.displayModeToggle.checkedButtonId)
}
private fun adjustStreamViewAspect() = adjustSurfaceViewAspect()
override fun dispatchKeyEvent(event: KeyEvent) = viewModel.input.dispatchKeyEvent(event) || super.dispatchKeyEvent(event) override fun dispatchKeyEvent(event: KeyEvent) = viewModel.input.dispatchKeyEvent(event) || super.dispatchKeyEvent(event)
override fun onGenericMotionEvent(event: MotionEvent) = viewModel.input.onGenericMotionEvent(event) || super.onGenericMotionEvent(event) override fun onGenericMotionEvent(event: MotionEvent) = viewModel.input.onGenericMotionEvent(event) || super.onGenericMotionEvent(event)
} }
enum class TransformMode
class DisplayInfo constructor(val videoProfile: ConnectVideoProfile, val textureView: TextureView)
{ {
val contentWidth : Float get() = videoProfile.width.toFloat() FIT,
val contentHeight : Float get() = videoProfile.height.toFloat() STRETCH,
ZOOM;
companion object
{
fun fromButton(displayModeButtonId: Int)
= when (displayModeButtonId)
{
R.id.display_mode_stretch_button -> STRETCH
R.id.display_mode_zoom_button -> ZOOM
else -> FIT
}
}
}
class TextureViewTransform(private val videoProfile: ConnectVideoProfile, private val textureView: TextureView)
{
private val contentWidth : Float get() = videoProfile.width.toFloat()
private val contentHeight : Float get() = videoProfile.height.toFloat()
val viewWidth : Float get() = textureView.width.toFloat() val viewWidth : Float get() = textureView.width.toFloat()
val viewHeight : Float get() = textureView.height.toFloat() val viewHeight : Float get() = textureView.height.toFloat()
val contentAspect : Float get() = contentHeight / contentWidth private val contentAspect : Float get() = contentHeight / contentWidth
fun computeResolutionFor(displayModeButtonId: Int) : Resolution fun resolutionFor(mode: TransformMode): Resolution
{ = when(mode)
when (displayModeButtonId)
{ {
R.id.display_mode_stretch_button -> return computeStrechedResolution() TransformMode.STRETCH -> strechedResolution
R.id.display_mode_zoom_button -> return computeZoomedResolution() TransformMode.ZOOM -> zoomedResolution
else -> return computeNormalResolution() TransformMode.FIT -> normalResolution
} }
}
private fun computeStrechedResolution(): Resolution private val strechedResolution get() = Resolution(viewWidth, viewHeight)
{
return Resolution(viewWidth, viewHeight)
}
private fun computeZoomedResolution(): Resolution private val zoomedResolution get() =
{ if(viewHeight > viewWidth * contentAspect)
if (viewHeight > viewWidth * contentAspect)
{ {
val zoomFactor = viewHeight / contentHeight val zoomFactor = viewHeight / contentHeight
return Resolution(contentWidth * zoomFactor, viewHeight) Resolution(contentWidth * zoomFactor, viewHeight)
} }
else else
{ {
val zoomFactor = viewWidth / contentWidth val zoomFactor = viewWidth / contentWidth
return Resolution(viewWidth, contentHeight * zoomFactor) Resolution(viewWidth, contentHeight * zoomFactor)
} }
}
private fun computeNormalResolution(): Resolution private val normalResolution get() =
{ if(viewHeight > viewWidth * contentAspect)
if (viewHeight > viewWidth * contentAspect) Resolution(viewWidth, viewWidth * contentAspect)
{
return Resolution(viewWidth, viewWidth * contentAspect)
}
else else
{ Resolution(viewHeight / contentAspect, viewHeight)
return Resolution(viewHeight / contentAspect, viewHeight)
}
}
} }

View file

@ -2,19 +2,22 @@
package com.metallic.chiaki.stream package com.metallic.chiaki.stream
import androidx.lifecycle.LiveData import android.app.Application
import androidx.lifecycle.MutableLiveData import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.*
import com.metallic.chiaki.common.LogManager import com.metallic.chiaki.common.LogManager
import com.metallic.chiaki.session.StreamSession import com.metallic.chiaki.session.StreamSession
import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.common.Preferences
import com.metallic.chiaki.lib.* import com.metallic.chiaki.lib.*
import com.metallic.chiaki.session.StreamInput import com.metallic.chiaki.session.StreamInput
class StreamViewModel(val preferences: Preferences, val logManager: LogManager, val connectInfo: ConnectInfo): ViewModel() class StreamViewModel(val application: Application, val connectInfo: ConnectInfo): ViewModel()
{ {
val preferences = Preferences(application)
val logManager = LogManager(application)
private var _session: StreamSession? = null private var _session: StreamSession? = null
val input = StreamInput(preferences) val input = StreamInput(application, preferences)
val session = StreamSession(connectInfo, logManager, preferences.logVerbose, input) val session = StreamSession(connectInfo, logManager, preferences.logVerbose, input)
private var _onScreenControlsEnabled = MutableLiveData<Boolean>(preferences.onScreenControlsEnabled) private var _onScreenControlsEnabled = MutableLiveData<Boolean>(preferences.onScreenControlsEnabled)

View file

@ -0,0 +1,29 @@
package com.metallic.chiaki.touchcontrols
import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import androidx.appcompat.app.AppCompatActivity
import com.metallic.chiaki.common.Preferences
class ButtonHaptics(val context: Context)
{
private val enabled = Preferences(context).buttonHapticEnabled
fun trigger(harder: Boolean = false)
{
if(!enabled)
return
val vibrator = context.getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
vibrator.vibrate(
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
VibrationEffect.createPredefined(if(harder) VibrationEffect.EFFECT_CLICK else VibrationEffect.EFFECT_TICK)
else
VibrationEffect.createOneShot(10, if(harder) 200 else 100)
)
else
vibrator.vibrate(10)
}
}

View file

@ -6,14 +6,19 @@ import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import com.metallic.chiaki.R import com.metallic.chiaki.R
class ButtonView @JvmOverloads constructor( class ButtonView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) ) : View(context, attrs, defStyleAttr)
{ {
private val haptics = ButtonHaptics(context)
var buttonPressed = false var buttonPressed = false
private set(value) private set(value)
{ {
@ -21,6 +26,8 @@ class ButtonView @JvmOverloads constructor(
field = value field = value
if(diff) if(diff)
{ {
if(value)
haptics.trigger()
invalidate() invalidate()
buttonPressedCallback?.let { it(field) } buttonPressedCallback?.let { it(field) }
} }
@ -46,16 +53,39 @@ class ButtonView @JvmOverloads constructor(
{ {
super.onDraw(canvas) super.onDraw(canvas)
val drawable = if(buttonPressed) drawablePressed else drawableIdle val drawable = if(buttonPressed) drawablePressed else drawableIdle
drawable?.setBounds(0, 0, width, height) drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
drawable?.draw(canvas) drawable?.draw(canvas)
} }
/**
* If this button overlaps with others in the same layout,
* let the one whose center is closest to the touch handle it.
*/
private fun bestFittingTouchView(x: Float, y: Float): View
{
val loc = locationOnScreen + Vector(x, y)
return (parent as? ViewGroup)?.children?.filter {
it is ButtonView
}?.filter {
val pos = it.locationOnScreen
loc.x >= pos.x && loc.x < pos.x + it.width && loc.y >= pos.y && loc.y < pos.y + it.height
}?.sortedBy {
(loc - (it.locationOnScreen + Vector(it.width.toFloat(), it.height.toFloat()) * 0.5f)).lengthSq
}?.firstOrNull() ?: this
}
override fun onTouchEvent(event: MotionEvent): Boolean override fun onTouchEvent(event: MotionEvent): Boolean
{ {
when(event.action) when(event.actionMasked)
{ {
MotionEvent.ACTION_DOWN -> buttonPressed = true MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
MotionEvent.ACTION_UP -> buttonPressed = false if(bestFittingTouchView(event.getX(event.actionIndex), event.getY(event.actionIndex)) != this)
return false
buttonPressed = true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
buttonPressed = false
}
} }
return true return true
} }

View file

@ -16,6 +16,8 @@ class DPadView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) ) : View(context, attrs, defStyleAttr)
{ {
private val haptics = ButtonHaptics(context)
enum class Direction { enum class Direction {
LEFT, LEFT,
RIGHT, RIGHT,
@ -71,7 +73,7 @@ class DPadView @JvmOverloads constructor(
else else
drawable = dpadIdleDrawable drawable = dpadIdleDrawable
drawable?.setBounds(0, 0, width, height) drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
//drawable?.alpha = 127 //drawable?.alpha = 127
drawable?.draw(canvas) drawable?.draw(canvas)
} }
@ -113,6 +115,8 @@ class DPadView @JvmOverloads constructor(
if(state != newState) if(state != newState)
{ {
if(newState != null)
haptics.trigger()
state = newState state = newState
invalidate() invalidate()
stateChangeCallback?.let { it(state) } stateChangeCallback?.let { it(state) }

View file

@ -3,74 +3,97 @@
package com.metallic.chiaki.touchcontrols package com.metallic.chiaki.touchcontrols
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.metallic.chiaki.R import com.metallic.chiaki.databinding.FragmentControlsBinding
import com.metallic.chiaki.lib.ControllerState import com.metallic.chiaki.lib.ControllerState
import kotlinx.android.synthetic.main.fragment_controls.* import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables.combineLatest
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
class TouchControlsFragment : Fragment() abstract class TouchControlsFragment : Fragment()
{ {
private var controllerState = ControllerState() protected var ownControllerState = ControllerState()
private set(value) set(value)
{ {
val diff = field != value val diff = field != value
field = value field = value
if(diff) if(diff)
controllerStateCallback?.let { it(value) } ownControllerStateSubject.onNext(ownControllerState)
} }
var controllerStateCallback: ((ControllerState) -> Unit)? = null protected val ownControllerStateSubject: Subject<ControllerState>
var onScreenControlsEnabled: LiveData<Boolean>? = null = BehaviorSubject.create<ControllerState>().also { it.onNext(ownControllerState) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View // to delay attaching to the touchpadView until it's available
= inflater.inflate(R.layout.fragment_controls, container, false) protected val controllerStateProxy: Subject<Observable<ControllerState>>
= BehaviorSubject.create<Observable<ControllerState>>().also { it.onNext(ownControllerStateSubject) }
val controllerState: Observable<ControllerState> get() =
controllerStateProxy.flatMap { it }
var onScreenControlsEnabled: LiveData<Boolean>? = null
}
class DefaultTouchControlsFragment : TouchControlsFragment()
{
private var _binding: FragmentControlsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
FragmentControlsBinding.inflate(inflater, container, false).let {
_binding = it
controllerStateProxy.onNext(
combineLatest(ownControllerStateSubject, binding.touchpadView.controllerState) { a, b -> a or b }
)
it.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{ {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
dpadView.stateChangeCallback = this::dpadStateChanged binding.dpadView.stateChangeCallback = this::dpadStateChanged
crossButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_CROSS) binding.crossButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_CROSS)
moonButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_MOON) binding.moonButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_MOON)
pyramidButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PYRAMID) binding.pyramidButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PYRAMID)
boxButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_BOX) binding.boxButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_BOX)
l1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_L1) binding.l1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_L1)
r1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_R1) binding.r1ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_R1)
optionsButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_OPTIONS) binding.l3ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_L3)
shareButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_SHARE) binding.r3ButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_R3)
psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS) binding.optionsButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_OPTIONS)
touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD) binding.shareButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_SHARE)
binding.psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS)
l2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { l2State = if(it) 255U else 0U } } binding.l2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { l2State = if(it) 255U else 0U } }
r2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { r2State = if(it) 255U else 0U } } binding.r2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { r2State = if(it) 255U else 0U } }
val quantizeStick = { f: Float -> val quantizeStick = { f: Float ->
(Short.MAX_VALUE * f).toShort() (Short.MAX_VALUE * f).toInt().toShort()
} }
leftAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply { binding.leftAnalogStickView.stateChangedCallback = { ownControllerState = ownControllerState.copy().apply {
leftX = quantizeStick(it.x) leftX = quantizeStick(it.x)
leftY = quantizeStick(it.y) leftY = quantizeStick(it.y)
}} }}
rightAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply { binding.rightAnalogStickView.stateChangedCallback = { ownControllerState = ownControllerState.copy().apply {
rightX = quantizeStick(it.x) rightX = quantizeStick(it.x)
rightY = quantizeStick(it.y) rightY = quantizeStick(it.y)
}} }}
onScreenControlsEnabled?.observe(this, Observer { onScreenControlsEnabled?.observe(viewLifecycleOwner, Observer {
view.visibility = if(it) View.VISIBLE else View.GONE view.visibility = if(it) View.VISIBLE else View.GONE
}) })
} }
private fun dpadStateChanged(direction: DPadView.Direction?) private fun dpadStateChanged(direction: DPadView.Direction?)
{ {
controllerState = controllerState.copy().apply { ownControllerState = ownControllerState.copy().apply {
buttons = ((buttons buttons = ((buttons
and ControllerState.BUTTON_DPAD_LEFT.inv() and ControllerState.BUTTON_DPAD_LEFT.inv()
and ControllerState.BUTTON_DPAD_RIGHT.inv() and ControllerState.BUTTON_DPAD_RIGHT.inv()
@ -92,7 +115,7 @@ class TouchControlsFragment : Fragment()
} }
private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean -> private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean ->
controllerState = controllerState.copy().apply { ownControllerState = ownControllerState.copy().apply {
buttons = buttons =
if(pressed) if(pressed)
buttons or buttonMask buttons or buttonMask

View file

@ -26,7 +26,7 @@ class TouchTracker
if(pointerId == null) if(pointerId == null)
{ {
pointerId = event.getPointerId(event.actionIndex) pointerId = event.getPointerId(event.actionIndex)
currentPosition = Vector(event.x, event.y) currentPosition = Vector(event.getX(event.actionIndex), event.getY(event.actionIndex))
} }
} }

View file

@ -6,49 +6,42 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.metallic.chiaki.R import com.metallic.chiaki.databinding.FragmentTouchpadOnlyBinding
import com.metallic.chiaki.lib.ControllerState import io.reactivex.rxkotlin.Observables.combineLatest
import kotlinx.android.synthetic.main.fragment_controls.*
class TouchpadOnlyFragment : Fragment() class TouchpadOnlyFragment : TouchControlsFragment()
{ {
private var controllerState = ControllerState()
private set(value)
{
val diff = field != value
field = value
if(diff)
controllerStateCallback?.let { it(value) }
}
var controllerStateCallback: ((ControllerState) -> Unit)? = null
var touchpadOnlyEnabled: LiveData<Boolean>? = null var touchpadOnlyEnabled: LiveData<Boolean>? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View private var _binding: FragmentTouchpadOnlyBinding? = null
= inflater.inflate(R.layout.fragment_touchpad_only, container, false) private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
FragmentTouchpadOnlyBinding.inflate(inflater, container, false).let {
_binding = it
controllerStateProxy.onNext(
combineLatest(ownControllerStateSubject, binding.touchpadView.controllerState) { a, b -> a or b }
)
it.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{ {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
touchpadOnlyEnabled?.observe(viewLifecycleOwner, Observer {
touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD)
touchpadOnlyEnabled?.observe(this, Observer {
view.visibility = if(it) View.VISIBLE else View.GONE view.visibility = if(it) View.VISIBLE else View.GONE
}) })
} }
private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean -> private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean ->
controllerState = controllerState.copy().apply { ownControllerState = ownControllerState.copy().apply {
buttons = buttons =
if(pressed) if(pressed)
buttons or buttonMask buttons or buttonMask
else else
buttons and buttonMask.inv() buttons and buttonMask.inv()
} }
} }
} }

View file

@ -0,0 +1,168 @@
// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
package com.metallic.chiaki.touchcontrols
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.metallic.chiaki.R
import com.metallic.chiaki.lib.ControllerState
import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
import kotlin.math.max
class TouchpadView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
{
companion object
{
private const val BUTTON_PRESS_MAX_MOVE_DIST_DP = 32.0f
private const val SHORT_BUTTON_PRESS_DURATION_MS = 200L
private const val BUTTON_HOLD_DELAY_MS = 500L
}
private val haptics = ButtonHaptics(context)
private val drawableIdle: Drawable?
private val drawablePressed: Drawable?
private val state: ControllerState = ControllerState()
inner class Touch(
val stateId: UByte,
private val startX: Float,
private val startY: Float)
{
var lifted = false // will be true but touch still in list when only relevant for short touch
private var maxDist: Float = 0.0f
val moveInsignificant: Boolean get() = maxDist < BUTTON_PRESS_MAX_MOVE_DIST_DP
fun onMove(x: Float, y: Float)
{
val d = (Vector(x, y) - Vector(startX, startY)).length / resources.displayMetrics.density
maxDist = max(d, maxDist)
}
val startButtonHoldRunnable = Runnable {
if(!moveInsignificant || buttonHeld)
return@Runnable
haptics.trigger(true)
state.buttons = state.buttons or ControllerState.BUTTON_TOUCHPAD
buttonHeld = true
triggerStateChanged()
}
}
private val pointerTouches = mutableMapOf<Int, Touch>()
private val stateSubject: Subject<ControllerState>
= BehaviorSubject.create<ControllerState>().also { it.onNext(state) }
val controllerState: Observable<ControllerState> get() = stateSubject
private var shortPressingTouches = listOf<Touch>()
private val shortButtonPressLiftRunnable = Runnable {
state.buttons = state.buttons and ControllerState.BUTTON_TOUCHPAD.inv()
shortPressingTouches.forEach {
state.stopTouch(it.stateId)
}
shortPressingTouches = listOf()
triggerStateChanged()
}
private var buttonHeld = false
init
{
context.theme.obtainStyledAttributes(attrs, R.styleable.TouchpadView, 0, 0).apply {
drawableIdle = getDrawable(R.styleable.TouchpadView_drawableIdle)
drawablePressed = getDrawable(R.styleable.TouchpadView_drawablePressed)
recycle()
}
isClickable = true
}
override fun onDraw(canvas: Canvas)
{
super.onDraw(canvas)
if(pointerTouches.values.find { !it.lifted } == null)
return
val drawable = if(state.buttons and ControllerState.BUTTON_TOUCHPAD != 0U) drawablePressed else drawableIdle
drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
drawable?.draw(canvas)
}
private fun touchX(event: MotionEvent, index: Int): UShort =
maxOf(0U.toUShort(), minOf((ControllerState.TOUCHPAD_WIDTH - 1u).toUShort(),
(ControllerState.TOUCHPAD_WIDTH.toFloat() * event.getX(index) / width.toFloat()).toUInt().toUShort()))
private fun touchY(event: MotionEvent, index: Int): UShort =
maxOf(0U.toUShort(), minOf((ControllerState.TOUCHPAD_HEIGHT - 1u).toUShort(),
(ControllerState.TOUCHPAD_HEIGHT.toFloat() * event.getY(index) / height.toFloat()).toUInt().toUShort()))
override fun onTouchEvent(event: MotionEvent): Boolean
{
when(event.actionMasked)
{
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
state.startTouch(touchX(event, event.actionIndex), touchY(event, event.actionIndex))?.let {
haptics.trigger()
val touch = Touch(it, event.getX(event.actionIndex), event.getY(event.actionIndex))
pointerTouches[event.getPointerId(event.actionIndex)] = touch
if(!buttonHeld)
postDelayed(touch.startButtonHoldRunnable, BUTTON_HOLD_DELAY_MS)
triggerStateChanged()
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
pointerTouches.remove(event.getPointerId(event.actionIndex))?.let {
removeCallbacks(it.startButtonHoldRunnable)
when
{
buttonHeld ->
{
buttonHeld = false
state.buttons = state.buttons and ControllerState.BUTTON_TOUCHPAD.inv()
state.stopTouch(it.stateId)
}
it.moveInsignificant -> triggerShortButtonPress(it)
else -> state.stopTouch(it.stateId)
}
triggerStateChanged()
}
}
MotionEvent.ACTION_MOVE -> {
val changed = pointerTouches.entries.fold(false) { acc, it ->
val index = event.findPointerIndex(it.key)
if(index < 0)
acc
else
{
it.value.onMove(event.getX(event.actionIndex), event.getY(event.actionIndex))
acc || state.setTouchPos(it.value.stateId, touchX(event, index), touchY(event, index))
}
}
if(changed)
triggerStateChanged()
}
}
return true
}
private fun triggerShortButtonPress(touch: Touch)
{
shortPressingTouches = shortPressingTouches + listOf(touch)
removeCallbacks(shortButtonPressLiftRunnable)
state.buttons = state.buttons or ControllerState.BUTTON_TOUCHPAD
postDelayed(shortButtonPressLiftRunnable, SHORT_BUTTON_PRESS_DURATION_MS)
}
private fun triggerStateChanged()
{
invalidate()
stateSubject.onNext(state)
}
}

View file

@ -2,6 +2,7 @@
package com.metallic.chiaki.touchcontrols package com.metallic.chiaki.touchcontrols
import android.view.View
import kotlin.math.sqrt import kotlin.math.sqrt
data class Vector(val x: Float, val y: Float) data class Vector(val x: Float, val y: Float)
@ -18,4 +19,10 @@ data class Vector(val x: Float, val y: Float)
val lengthSq get() = x*x + y*y val lengthSq get() = x*x + y*y
val length get() = sqrt(lengthSq) val length get() = sqrt(lengthSq)
val normalized get() = this / length val normalized get() = this / length
}
val View.locationOnScreen: Vector get() {
val v = intArrayOf(0, 0)
this.getLocationOnScreen(v)
return Vector(v[0].toFloat(), v[1].toFloat())
} }

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM21.7852,8.2563l2.3337,0L24.1189,23.6745L31.89,23.6745L31.89,25.8299L21.7852,25.8299ZM40.6771,8.2563l2.3342,0L43.0113,25.8299l-2.3342,0L40.6771,11.5512l-3.1853,3.1853 -1.6474,-1.6474z" android:pathData="M39.2255,0 L0,39.2255 20.3838,59.6093a27.7366,27.7366 88.4859,0 0,39.2255 0,27.7366 27.7366,88.4859 0,0 0,-39.2255zM25.4129,29.4287l2.7735,0l0,18.1643l9.9818,0l0,2.3342L25.4129,49.9272ZM46.3098,29.4287l2.7735,0l0,18.1643l4.5305,0l0,2.3342l-11.8076,0l0,-2.3342l4.531,0l0,-15.6383l-4.9289,0.9886l0,-2.5259z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_primary" android:fillColor="@color/control_primary"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM21.7852,8.2563l2.3337,0L24.1189,23.6745L31.89,23.6745L31.89,25.8299L21.7852,25.8299ZM40.6771,8.2563l2.3342,0L43.0113,25.8299l-2.3342,0L40.6771,11.5512l-3.1853,3.1853 -1.6474,-1.6474z" android:pathData="M39.2255,0 L0,39.2255 20.3838,59.6093a27.7366,27.7366 88.4859,0 0,39.2255 0,27.7366 27.7366,88.4859 0,0 0,-39.2255zM25.4129,29.4287l2.7735,0l0,18.1643l9.9818,0l0,2.3342L25.4129,49.9272ZM46.3098,29.4287l2.7735,0l0,18.1643l4.5305,0l0,2.3342l-11.8076,0l0,-2.3342l4.531,0l0,-15.6383l-4.9289,0.9886l0,-2.5259z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_pressed" android:fillColor="@color/control_pressed"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM40.6771,8.0367c1.556,0 2.7872,0.3659 3.6933,1.0981 0.9062,0.7322 1.3591,1.7348 1.3591,3.0071 0,0.4027 -0.0139,0.7136 -0.0413,0.9333 -0.0183,0.2105 -0.0867,0.4989 -0.2057,0.8651 -0.1098,0.357 -0.3021,0.7506 -0.5767,1.1808 -0.2746,0.421 -0.6453,0.9242 -1.1121,1.51l-5.6291,7.0435l7.7845,0L45.9491,25.8299l-10.5441,0l0,-2.1554l6.6999,-8.3886c0.4119,-0.5126 0.7142,-0.9612 0.9064,-1.3457 0.1922,-0.3936 0.3017,-0.6911 0.3292,-0.8925 0.0366,-0.2105 0.0548,-0.5123 0.0548,-0.9059 0,-0.6041 -0.2425,-1.0805 -0.7276,-1.4283 -0.4851,-0.3478 -1.1485,-0.5214 -1.9906,-0.5214 -0.8421,0 -1.506,0.1736 -1.9911,0.5214 -0.4851,0.3478 -0.7276,0.8242 -0.7276,1.4283l-2.3337,-0.371c0,-1.1533 0.4529,-2.064 1.3591,-2.7321 0.9062,-0.6682 2.1373,-1.002 3.6933,-1.002zM21.7852,8.2563l2.3337,0L24.1189,23.6745L31.89,23.6745L31.89,25.8299L21.7852,25.8299Z" android:pathData="m27.6526,0a27.7366,27.7366 88.4859,0 0,-19.5285 8.1241,27.7366 27.7366,88.4859 0,0 0,39.2255L28.5078,67.7333 67.7333,28.5078 47.3496,8.1241A27.7366,27.7366 88.4859,0 0,27.6526 0ZM34.9017,15.1846c2.1235,0 3.8172,0.5309 5.0803,1.5927 1.2631,1.0618 1.8945,2.4804 1.8945,4.2561 0,0.8421 -0.1602,1.6432 -0.4806,2.403 -0.3112,0.7506 -0.8832,1.6383 -1.7162,2.6634 -0.2288,0.2654 -0.9563,1.034 -2.1828,2.3063 -1.2265,1.2631 -2.9565,3.0342 -5.1899,5.3134l9.6795,0l0,2.3342l-13.0157,0l0,-2.3342c1.0526,-1.0892 2.4851,-2.549 4.2974,-4.3796 1.8215,-1.8398 2.9655,-3.025 3.4323,-3.5559 0.8879,-0.9977 1.5058,-1.84 1.8536,-2.5265 0.357,-0.6956 0.5354,-1.3777 0.5354,-2.0459 0,-1.0892 -0.3846,-1.9769 -1.1534,-2.6634 -0.7597,-0.6865 -1.7526,-1.0299 -2.9791,-1.0299 -0.8695,-0 -1.7898,0.1511 -2.76,0.4532 -0.9611,0.3021 -1.9908,0.7598 -3.0892,1.373L29.1078,16.5437c1.1167,-0.4485 2.1603,-0.7871 3.1306,-1.016 0.9702,-0.2288 1.8579,-0.3431 2.6634,-0.3431zM14.0053,15.5551l2.7735,0l0,18.1643l9.9813,0l0,2.3342L14.0053,36.0536Z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_primary" android:fillColor="@color/control_primary"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM40.6771,8.0367c1.556,0 2.7872,0.3659 3.6933,1.0981 0.9062,0.7322 1.3591,1.7348 1.3591,3.0071 0,0.4027 -0.0139,0.7136 -0.0413,0.9333 -0.0183,0.2105 -0.0867,0.4989 -0.2057,0.8651 -0.1098,0.357 -0.3021,0.7506 -0.5767,1.1808 -0.2746,0.421 -0.6453,0.9242 -1.1121,1.51l-5.6291,7.0435l7.7845,0L45.9491,25.8299l-10.5441,0l0,-2.1554l6.6999,-8.3886c0.4119,-0.5126 0.7142,-0.9612 0.9064,-1.3457 0.1922,-0.3936 0.3017,-0.6911 0.3292,-0.8925 0.0366,-0.2105 0.0548,-0.5123 0.0548,-0.9059 0,-0.6041 -0.2425,-1.0805 -0.7276,-1.4283 -0.4851,-0.3478 -1.1485,-0.5214 -1.9906,-0.5214 -0.8421,0 -1.506,0.1736 -1.9911,0.5214 -0.4851,0.3478 -0.7276,0.8242 -0.7276,1.4283l-2.3337,-0.371c0,-1.1533 0.4529,-2.064 1.3591,-2.7321 0.9062,-0.6682 2.1373,-1.002 3.6933,-1.002zM21.7852,8.2563l2.3337,0L24.1189,23.6745L31.89,23.6745L31.89,25.8299L21.7852,25.8299Z" android:pathData="m27.6526,0a27.7366,27.7366 88.4859,0 0,-19.5285 8.1241,27.7366 27.7366,88.4859 0,0 0,39.2255L28.5078,67.7333 67.7333,28.5078 47.3496,8.1241A27.7366,27.7366 88.4859,0 0,27.6526 0ZM34.9017,15.1846c2.1235,0 3.8172,0.5309 5.0803,1.5927 1.2631,1.0618 1.8945,2.4804 1.8945,4.2561 0,0.8421 -0.1602,1.6432 -0.4806,2.403 -0.3112,0.7506 -0.8832,1.6383 -1.7162,2.6634 -0.2288,0.2654 -0.9563,1.034 -2.1828,2.3063 -1.2265,1.2631 -2.9565,3.0342 -5.1899,5.3134l9.6795,0l0,2.3342l-13.0157,0l0,-2.3342c1.0526,-1.0892 2.4851,-2.549 4.2974,-4.3796 1.8215,-1.8398 2.9655,-3.025 3.4323,-3.5559 0.8879,-0.9977 1.5058,-1.84 1.8536,-2.5265 0.357,-0.6956 0.5354,-1.3777 0.5354,-2.0459 0,-1.0892 -0.3846,-1.9769 -1.1534,-2.6634 -0.7597,-0.6865 -1.7526,-1.0299 -2.9791,-1.0299 -0.8695,-0 -1.7898,0.1511 -2.76,0.4532 -0.9611,0.3021 -1.9908,0.7598 -3.0892,1.373L29.1078,16.5437c1.1167,-0.4485 2.1603,-0.7871 3.1306,-1.016 0.9702,-0.2288 1.8579,-0.3431 2.6634,-0.3431zM14.0053,15.5551l2.7735,0l0,18.1643l9.9813,0l0,2.3342L14.0053,36.0536Z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_pressed" android:fillColor="@color/control_pressed"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="67.73333"
android:viewportHeight="67.73334">
<path
android:pathData="M0,0 L-41.939,102.093 67.7333,67.7333C67.7333,30.3253 37.4081,0 0,0ZM33.6093,31.3252c2.1052,0 3.7711,0.4807 4.9976,1.4418 1.2265,0.9519 1.8397,2.2423 1.8397,3.8716 0,1.135 -0.3253,2.0964 -0.9751,2.8835 -0.6499,0.778 -1.5739,1.318 -2.773,1.6201 1.3272,0.2837 2.3613,0.8739 3.1027,1.771 0.7506,0.897 1.126,2.0047 1.126,3.3228 0,2.0228 -0.6959,3.5878 -2.0872,4.6953 -1.3913,1.1075 -3.368,1.6614 -5.9309,1.6614 -0.8604,0 -1.7486,-0.0871 -2.6639,-0.261 -0.9062,-0.1648 -1.8441,-0.4163 -2.8143,-0.755l0,-2.6774c0.7689,0.4485 1.6106,0.7871 2.5259,1.016 0.9153,0.2288 1.8719,0.3431 2.8696,0.3431 1.7391,0 3.0621,-0.3434 3.9682,-1.0299 0.9153,-0.6865 1.3725,-1.6837 1.3725,-2.9926 0,-1.2082 -0.4252,-2.1514 -1.2764,-2.8288 -0.8421,-0.6865 -2.0187,-1.0294 -3.529,-1.0294l-2.3885,0l0,-2.2794l2.4986,0c1.3638,0 2.4075,-0.2697 3.1306,-0.8098 0.7231,-0.5492 1.0847,-1.3365 1.0847,-2.3616 0,-1.0526 -0.3755,-1.858 -1.126,-2.4164 -0.7414,-0.5675 -1.808,-0.8511 -3.1993,-0.8511 -0.7597,0 -1.5742,0.0823 -2.4438,0.247 -0.8695,0.1648 -1.8261,0.4211 -2.8696,0.7689l0,-2.4717c1.0526,-0.2929 2.037,-0.5124 2.9523,-0.6589 0.9245,-0.1464 1.794,-0.2196 2.6086,-0.2196zM12.3832,31.6957l2.7735,0l0,18.1648l9.9813,0l0,2.3337L12.3832,52.1942Z"
android:strokeLineJoin="round"
android:strokeWidth="11.0688"
android:fillColor="@color/control_primary"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="67.73333"
android:viewportHeight="67.73334">
<path
android:pathData="M0,0 L-41.939,102.093 67.7333,67.7333C67.7333,30.3253 37.4081,0 0,0ZM33.6093,31.3252c2.1052,0 3.7711,0.4807 4.9976,1.4418 1.2265,0.9519 1.8397,2.2423 1.8397,3.8716 0,1.135 -0.3253,2.0964 -0.9751,2.8835 -0.6499,0.778 -1.5739,1.318 -2.773,1.6201 1.3272,0.2837 2.3613,0.8739 3.1027,1.771 0.7506,0.897 1.126,2.0047 1.126,3.3228 0,2.0228 -0.6959,3.5878 -2.0872,4.6953 -1.3913,1.1075 -3.368,1.6614 -5.9309,1.6614 -0.8604,0 -1.7486,-0.0871 -2.6639,-0.261 -0.9062,-0.1648 -1.8441,-0.4163 -2.8143,-0.755l0,-2.6774c0.7689,0.4485 1.6106,0.7871 2.5259,1.016 0.9153,0.2288 1.8719,0.3431 2.8696,0.3431 1.7391,0 3.0621,-0.3434 3.9682,-1.0299 0.9153,-0.6865 1.3725,-1.6837 1.3725,-2.9926 0,-1.2082 -0.4252,-2.1514 -1.2764,-2.8288 -0.8421,-0.6865 -2.0187,-1.0294 -3.529,-1.0294l-2.3885,0l0,-2.2794l2.4986,0c1.3638,0 2.4075,-0.2697 3.1306,-0.8098 0.7231,-0.5492 1.0847,-1.3365 1.0847,-2.3616 0,-1.0526 -0.3755,-1.858 -1.126,-2.4164 -0.7414,-0.5675 -1.808,-0.8511 -3.1993,-0.8511 -0.7597,0 -1.5742,0.0823 -2.4438,0.247 -0.8695,0.1648 -1.8261,0.4211 -2.8696,0.7689l0,-2.4717c1.0526,-0.2929 2.037,-0.5124 2.9523,-0.6589 0.9245,-0.1464 1.794,-0.2196 2.6086,-0.2196zM12.3832,31.6957l2.7735,0l0,18.1648l9.9813,0l0,2.3337L12.3832,52.1942Z"
android:strokeLineJoin="round"
android:strokeWidth="11.0688"
android:fillColor="@color/control_pressed"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM21.3455,8.2563L27.4417,8.2563c1.5286,0 2.6865,0.352 3.4737,1.0568 0.7963,0.7048 1.1942,1.8814 1.1942,3.529 -0,2.2517 -1.034,3.6703 -3.1027,4.2561L32.7685,25.8299L30.2286,25.8299L26.618,17.4413L23.6797,17.4413L23.6797,25.8299l-2.3342,0zM40.6771,8.2563l2.3342,0L43.0113,25.8299l-2.3342,0L40.6771,11.5512l-3.1853,3.1853 -1.6474,-1.6474zM23.6797,10.4118l0,4.8741l2.3342,0c1.2265,0 2.1601,-0.188 2.8009,-0.5633 0.6407,-0.3753 0.9607,-1.0018 0.9607,-1.8805 0,-0.8695 -0.2014,-1.4923 -0.6041,-1.8676 -0.3936,-0.3753 -0.9699,-0.5628 -1.7296,-0.5628z" android:pathData="M28.5078,0 L8.1241,20.3838a27.7366,27.7366 88.4859,0 0,0 39.2255,27.7366 27.7366,88.4859 0,0 39.2255,0L67.7333,39.2255ZM12.4178,29.4287l6.2606,0c2.3432,0 4.0913,0.4898 5.2446,1.4692 1.1533,0.9794 1.7301,2.4573 1.7301,4.4344 0,1.2906 -0.3018,2.3615 -0.9059,3.2127 -0.595,0.8512 -1.4645,1.442 -2.6086,1.7715 0.595,0.2014 1.1713,0.6313 1.7296,1.2904 0.5675,0.659 1.1352,1.5654 1.7027,2.7187L28.3853,49.9272L25.4062,49.9272l-2.6226,-5.2586C22.1063,43.2956 21.4472,42.385 20.8065,41.9365c-0.6316,-0.4485 -1.4963,-0.6728 -2.5947,-0.6728L15.1913,41.2636l0,8.6636L12.4178,49.9272ZM37.1864,29.4287l2.7735,0l0,18.1643l4.5305,0l0,2.3342l-11.8075,0l0,-2.3342l4.531,0l0,-15.6383l-4.9289,0.9886l0,-2.5259zM15.1913,31.7076l0,7.2766l3.4871,0c1.3364,0 2.3432,-0.3066 3.0205,-0.9198 0.6865,-0.6224 1.0299,-1.5331 1.0299,-2.7321 0,-1.1991 -0.3434,-2.1006 -1.0299,-2.7047 -0.6773,-0.6133 -1.6841,-0.9198 -3.0205,-0.9198z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_primary" android:fillColor="@color/control_primary"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM21.3455,8.2563L27.4417,8.2563c1.5286,0 2.6865,0.352 3.4737,1.0568 0.7963,0.7048 1.1942,1.8814 1.1942,3.529 -0,2.2517 -1.034,3.6703 -3.1027,4.2561L32.7685,25.8299L30.2286,25.8299L26.618,17.4413L23.6797,17.4413L23.6797,25.8299l-2.3342,0zM40.6771,8.2563l2.3342,0L43.0113,25.8299l-2.3342,0L40.6771,11.5512l-3.1853,3.1853 -1.6474,-1.6474zM23.6797,10.4118l0,4.8741l2.3342,0c1.2265,0 2.1601,-0.188 2.8009,-0.5633 0.6407,-0.3753 0.9607,-1.0018 0.9607,-1.8805 0,-0.8695 -0.2014,-1.4923 -0.6041,-1.8676 -0.3936,-0.3753 -0.9699,-0.5628 -1.7296,-0.5628z" android:pathData="M28.5078,0 L8.1241,20.3838a27.7366,27.7366 88.4859,0 0,0 39.2255,27.7366 27.7366,88.4859 0,0 39.2255,0L67.7333,39.2255ZM12.4178,29.4287l6.2606,0c2.3432,0 4.0913,0.4898 5.2446,1.4692 1.1533,0.9794 1.7301,2.4573 1.7301,4.4344 0,1.2906 -0.3018,2.3615 -0.9059,3.2127 -0.595,0.8512 -1.4645,1.442 -2.6086,1.7715 0.595,0.2014 1.1713,0.6313 1.7296,1.2904 0.5675,0.659 1.1352,1.5654 1.7027,2.7187L28.3853,49.9272L25.4062,49.9272l-2.6226,-5.2586C22.1063,43.2956 21.4472,42.385 20.8065,41.9365c-0.6316,-0.4485 -1.4963,-0.6728 -2.5947,-0.6728L15.1913,41.2636l0,8.6636L12.4178,49.9272ZM37.1864,29.4287l2.7735,0l0,18.1643l4.5305,0l0,2.3342l-11.8075,0l0,-2.3342l4.531,0l0,-15.6383l-4.9289,0.9886l0,-2.5259zM15.1913,31.7076l0,7.2766l3.4871,0c1.3364,0 2.3432,-0.3066 3.0205,-0.9198 0.6865,-0.6224 1.0299,-1.5331 1.0299,-2.7321 0,-1.1991 -0.3434,-2.1006 -1.0299,-2.7047 -0.6773,-0.6133 -1.6841,-0.9198 -3.0205,-0.9198z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_pressed" android:fillColor="@color/control_pressed"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM40.6771,8.0367c1.556,0 2.7872,0.3659 3.6933,1.0981 0.9062,0.7322 1.3591,1.7348 1.3591,3.0071 0,0.4027 -0.0139,0.7136 -0.0413,0.9333 -0.0183,0.2105 -0.0867,0.4989 -0.2057,0.8651 -0.1098,0.357 -0.3021,0.7506 -0.5767,1.1808 -0.2746,0.421 -0.6453,0.9242 -1.1121,1.51l-5.6291,7.0435l7.7845,0L45.9491,25.8299l-10.5441,0l0,-2.1554l6.6999,-8.3886c0.4119,-0.5126 0.7142,-0.9612 0.9064,-1.3457 0.1922,-0.3936 0.3017,-0.6911 0.3292,-0.8925 0.0366,-0.2105 0.0548,-0.5123 0.0548,-0.9059 0,-0.6041 -0.2425,-1.0805 -0.7276,-1.4283 -0.4851,-0.3478 -1.1485,-0.5214 -1.9906,-0.5214 -0.8421,0 -1.506,0.1736 -1.9911,0.5214 -0.4851,0.3478 -0.7276,0.8242 -0.7276,1.4283l-2.3337,-0.371c0,-1.1533 0.4529,-2.064 1.3591,-2.7321C37.8899,8.3705 39.1211,8.0367 40.6771,8.0367ZM21.3455,8.2563L27.4417,8.2563c1.5286,0 2.6865,0.352 3.4737,1.0568 0.7963,0.7048 1.1942,1.8814 1.1942,3.529 -0,2.2517 -1.034,3.6703 -3.1027,4.2561L32.7685,25.8299L30.2286,25.8299L26.618,17.4413L23.6797,17.4413L23.6797,25.8299l-2.3342,0zM23.6797,10.4118l0,4.8741l2.3342,0c1.2265,0 2.1601,-0.188 2.8009,-0.5633 0.6407,-0.3753 0.9607,-1.0018 0.9607,-1.8805 0,-0.8695 -0.2014,-1.4923 -0.6041,-1.8676 -0.3936,-0.3753 -0.9699,-0.5628 -1.7296,-0.5628z" android:pathData="M20.3838,8.1241 L0,28.5078 39.2255,67.7333 59.6093,47.3496a27.7366,27.7366 88.4859,0 0,0 -39.2255,27.7366 27.7366,88.4859 0,0 -39.2255,0zM48.9278,15.1846c2.1235,0 3.8167,0.5309 5.0798,1.5927 1.2631,1.0618 1.895,2.4804 1.895,4.2561 0,0.8421 -0.1602,1.6432 -0.4806,2.403 -0.3112,0.7506 -0.8832,1.6383 -1.7162,2.6634 -0.2288,0.2654 -0.9568,1.034 -2.1833,2.3063 -1.2265,1.2631 -2.956,3.0342 -5.1893,5.3134l9.679,0l0,2.3342l-13.0157,0l0,-2.3342c1.0526,-1.0892 2.4851,-2.549 4.2974,-4.3796 1.8215,-1.8398 2.9655,-3.025 3.4323,-3.5559 0.8879,-0.9977 1.5058,-1.84 1.8536,-2.5265 0.357,-0.6956 0.5354,-1.3777 0.5354,-2.0459 0,-1.0892 -0.384,-1.9769 -1.1529,-2.6634 -0.7597,-0.6865 -1.7531,-1.0299 -2.9797,-1.0299 -0.8695,-0 -1.7893,0.1511 -2.7595,0.4532 -0.9611,0.3021 -1.9908,0.7598 -3.0892,1.373L43.1338,16.5437c1.1167,-0.4485 2.1603,-0.7871 3.1306,-1.016 0.9702,-0.2288 1.8579,-0.3431 2.6634,-0.3431zM24.1593,15.5551l6.2611,0c2.3432,0 4.0913,0.4898 5.2446,1.4692 1.1533,0.9794 1.7296,2.4578 1.7296,4.4349 0,1.2906 -0.3018,2.3615 -0.9059,3.2127 -0.595,0.8512 -1.4645,1.4414 -2.6086,1.771 0.595,0.2014 1.1713,0.6313 1.7296,1.2904 0.5675,0.659 1.1352,1.5654 1.7027,2.7187l2.8148,5.6017l-2.9797,0l-2.6221,-5.2586c-0.6773,-1.373 -1.3364,-2.2836 -1.9771,-2.7321 -0.6316,-0.4485 -1.4968,-0.6728 -2.5952,-0.6728l-3.0205,0l0,8.6636L24.1593,36.0536ZM26.9327,17.8346l0,7.2766l3.4876,0c1.3364,0 2.3432,-0.3066 3.0205,-0.9198 0.6865,-0.6224 1.0294,-1.5331 1.0294,-2.7321 0,-1.1991 -0.3429,-2.1006 -1.0294,-2.7047 -0.6773,-0.6133 -1.6841,-0.9198 -3.0205,-0.9198z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_primary" android:fillColor="@color/control_primary"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -1,13 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp" android:width="256dp"
android:height="128dp" android:height="256dp"
android:viewportWidth="67.73333" android:viewportWidth="67.73333"
android:viewportHeight="33.86667"> android:viewportHeight="67.73334">
<path <path
android:pathData="M0,0L0,33.8667L67.7333,33.8667L67.7333,0ZM40.6771,8.0367c1.556,0 2.7872,0.3659 3.6933,1.0981 0.9062,0.7322 1.3591,1.7348 1.3591,3.0071 0,0.4027 -0.0139,0.7136 -0.0413,0.9333 -0.0183,0.2105 -0.0867,0.4989 -0.2057,0.8651 -0.1098,0.357 -0.3021,0.7506 -0.5767,1.1808 -0.2746,0.421 -0.6453,0.9242 -1.1121,1.51l-5.6291,7.0435l7.7845,0L45.9491,25.8299l-10.5441,0l0,-2.1554l6.6999,-8.3886c0.4119,-0.5126 0.7142,-0.9612 0.9064,-1.3457 0.1922,-0.3936 0.3017,-0.6911 0.3292,-0.8925 0.0366,-0.2105 0.0548,-0.5123 0.0548,-0.9059 0,-0.6041 -0.2425,-1.0805 -0.7276,-1.4283 -0.4851,-0.3478 -1.1485,-0.5214 -1.9906,-0.5214 -0.8421,0 -1.506,0.1736 -1.9911,0.5214 -0.4851,0.3478 -0.7276,0.8242 -0.7276,1.4283l-2.3337,-0.371c0,-1.1533 0.4529,-2.064 1.3591,-2.7321C37.8899,8.3705 39.1211,8.0367 40.6771,8.0367ZM21.3455,8.2563L27.4417,8.2563c1.5286,0 2.6865,0.352 3.4737,1.0568 0.7963,0.7048 1.1942,1.8814 1.1942,3.529 -0,2.2517 -1.034,3.6703 -3.1027,4.2561L32.7685,25.8299L30.2286,25.8299L26.618,17.4413L23.6797,17.4413L23.6797,25.8299l-2.3342,0zM23.6797,10.4118l0,4.8741l2.3342,0c1.2265,0 2.1601,-0.188 2.8009,-0.5633 0.6407,-0.3753 0.9607,-1.0018 0.9607,-1.8805 0,-0.8695 -0.2014,-1.4923 -0.6041,-1.8676 -0.3936,-0.3753 -0.9699,-0.5628 -1.7296,-0.5628z" android:pathData="M20.3838,8.1241 L0,28.5078 39.2255,67.7333 59.6093,47.3496a27.7366,27.7366 88.4859,0 0,0 -39.2255,27.7366 27.7366,88.4859 0,0 -39.2255,0zM48.9278,15.1846c2.1235,0 3.8167,0.5309 5.0798,1.5927 1.2631,1.0618 1.895,2.4804 1.895,4.2561 0,0.8421 -0.1602,1.6432 -0.4806,2.403 -0.3112,0.7506 -0.8832,1.6383 -1.7162,2.6634 -0.2288,0.2654 -0.9568,1.034 -2.1833,2.3063 -1.2265,1.2631 -2.956,3.0342 -5.1893,5.3134l9.679,0l0,2.3342l-13.0157,0l0,-2.3342c1.0526,-1.0892 2.4851,-2.549 4.2974,-4.3796 1.8215,-1.8398 2.9655,-3.025 3.4323,-3.5559 0.8879,-0.9977 1.5058,-1.84 1.8536,-2.5265 0.357,-0.6956 0.5354,-1.3777 0.5354,-2.0459 0,-1.0892 -0.384,-1.9769 -1.1529,-2.6634 -0.7597,-0.6865 -1.7531,-1.0299 -2.9797,-1.0299 -0.8695,-0 -1.7893,0.1511 -2.7595,0.4532 -0.9611,0.3021 -1.9908,0.7598 -3.0892,1.373L43.1338,16.5437c1.1167,-0.4485 2.1603,-0.7871 3.1306,-1.016 0.9702,-0.2288 1.8579,-0.3431 2.6634,-0.3431zM24.1593,15.5551l6.2611,0c2.3432,0 4.0913,0.4898 5.2446,1.4692 1.1533,0.9794 1.7296,2.4578 1.7296,4.4349 0,1.2906 -0.3018,2.3615 -0.9059,3.2127 -0.595,0.8512 -1.4645,1.4414 -2.6086,1.771 0.595,0.2014 1.1713,0.6313 1.7296,1.2904 0.5675,0.659 1.1352,1.5654 1.7027,2.7187l2.8148,5.6017l-2.9797,0l-2.6221,-5.2586c-0.6773,-1.373 -1.3364,-2.2836 -1.9771,-2.7321 -0.6316,-0.4485 -1.4968,-0.6728 -2.5952,-0.6728l-3.0205,0l0,8.6636L24.1593,36.0536ZM26.9327,17.8346l0,7.2766l3.4876,0c1.3364,0 2.3432,-0.3066 3.0205,-0.9198 0.6865,-0.6224 1.0294,-1.5331 1.0294,-2.7321 0,-1.1991 -0.3429,-2.1006 -1.0294,-2.7047 -0.6773,-0.6133 -1.6841,-0.9198 -3.0205,-0.9198z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="23.7399" android:strokeWidth="5.54109"
android:fillColor="@color/control_pressed" android:fillColor="@color/control_pressed"
android:strokeColor="#00000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="67.73333"
android:viewportHeight="67.73334">
<path
android:pathData="M67.7333,0C30.3252,0 0,30.3252 0,67.7333l109.6724,34.3597zM50.2982,31.3252c2.1052,0 3.7711,0.4807 4.9976,1.4418 1.2265,0.9519 1.8402,2.2423 1.8402,3.8716 0,1.135 -0.3253,2.0964 -0.9751,2.8835 -0.6499,0.778 -1.5744,1.318 -2.7735,1.6201 1.3272,0.2837 2.3618,0.8739 3.1032,1.771 0.7506,0.897 1.1255,2.0047 1.1255,3.3228 0,2.0228 -0.6954,3.5878 -2.0867,4.6953 -1.3913,1.1075 -3.3685,1.6614 -5.9314,1.6614 -0.8604,0 -1.7481,-0.0871 -2.6634,-0.261 -0.9062,-0.1648 -1.8441,-0.4163 -2.8143,-0.755l0,-2.6774c0.7689,0.4485 1.6106,0.7871 2.5259,1.016 0.9153,0.2288 1.8719,0.3431 2.8696,0.3431 1.7391,0 3.0616,-0.3434 3.9677,-1.0299 0.9153,-0.6865 1.373,-1.6837 1.373,-2.9926 0,-1.2082 -0.4257,-2.1514 -1.2769,-2.8288 -0.8421,-0.6865 -2.0182,-1.0294 -3.5285,-1.0294l-2.389,0l0,-2.2794l2.4991,0c1.3638,0 2.4069,-0.2697 3.13,-0.8098 0.7231,-0.5492 1.0847,-1.3365 1.0847,-2.3616 0,-1.0526 -0.375,-1.858 -1.1255,-2.4164 -0.7414,-0.5675 -1.808,-0.8511 -3.1993,-0.8511 -0.7597,0 -1.5742,0.0823 -2.4438,0.247 -0.8695,0.1648 -1.8261,0.4211 -2.8696,0.7689l0,-2.4717c1.0526,-0.2929 2.0364,-0.5124 2.9518,-0.6589 0.9245,-0.1464 1.794,-0.2196 2.6086,-0.2196zM25.2005,31.6957L31.4611,31.6957c2.3432,0 4.0913,0.4898 5.2446,1.4692 1.1533,0.9794 1.7301,2.4578 1.7301,4.4349 0,1.2906 -0.3018,2.3615 -0.9059,3.2127 -0.595,0.8512 -1.4645,1.4414 -2.6086,1.771 0.595,0.2014 1.1713,0.6319 1.7296,1.2909 0.5675,0.659 1.1352,1.5649 1.7027,2.7182l2.8143,5.6017l-2.9791,0l-2.6226,-5.2581c-0.6773,-1.373 -1.3364,-2.2841 -1.9771,-2.7326 -0.6316,-0.4485 -1.4963,-0.6723 -2.5947,-0.6723l-3.0205,0l0,8.663L25.2005,52.1942ZM27.974,33.9752l0,7.2766l3.4871,0c1.3364,0 2.3432,-0.3066 3.0205,-0.9198 0.6865,-0.6224 1.0299,-1.5331 1.0299,-2.7321 0,-1.1991 -0.3434,-2.1006 -1.0299,-2.7047 -0.6773,-0.6133 -1.6841,-0.9198 -3.0205,-0.9198z"
android:strokeLineJoin="round"
android:strokeWidth="11.0688"
android:fillColor="@color/control_primary"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="67.73333"
android:viewportHeight="67.73334">
<path
android:pathData="M67.7333,0C30.3252,0 0,30.3252 0,67.7333l109.6724,34.3597zM50.2982,31.3252c2.1052,0 3.7711,0.4807 4.9976,1.4418 1.2265,0.9519 1.8402,2.2423 1.8402,3.8716 0,1.135 -0.3253,2.0964 -0.9751,2.8835 -0.6499,0.778 -1.5744,1.318 -2.7735,1.6201 1.3272,0.2837 2.3618,0.8739 3.1032,1.771 0.7506,0.897 1.1255,2.0047 1.1255,3.3228 0,2.0228 -0.6954,3.5878 -2.0867,4.6953 -1.3913,1.1075 -3.3685,1.6614 -5.9314,1.6614 -0.8604,0 -1.7481,-0.0871 -2.6634,-0.261 -0.9062,-0.1648 -1.8441,-0.4163 -2.8143,-0.755l0,-2.6774c0.7689,0.4485 1.6106,0.7871 2.5259,1.016 0.9153,0.2288 1.8719,0.3431 2.8696,0.3431 1.7391,0 3.0616,-0.3434 3.9677,-1.0299 0.9153,-0.6865 1.373,-1.6837 1.373,-2.9926 0,-1.2082 -0.4257,-2.1514 -1.2769,-2.8288 -0.8421,-0.6865 -2.0182,-1.0294 -3.5285,-1.0294l-2.389,0l0,-2.2794l2.4991,0c1.3638,0 2.4069,-0.2697 3.13,-0.8098 0.7231,-0.5492 1.0847,-1.3365 1.0847,-2.3616 0,-1.0526 -0.375,-1.858 -1.1255,-2.4164 -0.7414,-0.5675 -1.808,-0.8511 -3.1993,-0.8511 -0.7597,0 -1.5742,0.0823 -2.4438,0.247 -0.8695,0.1648 -1.8261,0.4211 -2.8696,0.7689l0,-2.4717c1.0526,-0.2929 2.0364,-0.5124 2.9518,-0.6589 0.9245,-0.1464 1.794,-0.2196 2.6086,-0.2196zM25.2005,31.6957L31.4611,31.6957c2.3432,0 4.0913,0.4898 5.2446,1.4692 1.1533,0.9794 1.7301,2.4578 1.7301,4.4349 0,1.2906 -0.3018,2.3615 -0.9059,3.2127 -0.595,0.8512 -1.4645,1.4414 -2.6086,1.771 0.595,0.2014 1.1713,0.6319 1.7296,1.2909 0.5675,0.659 1.1352,1.5649 1.7027,2.7182l2.8143,5.6017l-2.9791,0l-2.6226,-5.2581c-0.6773,-1.373 -1.3364,-2.2841 -1.9771,-2.7326 -0.6316,-0.4485 -1.4963,-0.6723 -2.5947,-0.6723l-3.0205,0l0,8.663L25.2005,52.1942ZM27.974,33.9752l0,7.2766l3.4871,0c1.3364,0 2.3432,-0.3066 3.0205,-0.9198 0.6865,-0.6224 1.0299,-1.5331 1.0299,-2.7321 0,-1.1991 -0.3434,-2.1006 -1.0299,-2.7047 -0.6773,-0.6133 -1.6841,-0.9198 -3.0205,-0.9198z"
android:strokeLineJoin="round"
android:strokeWidth="11.0688"
android:fillColor="@color/control_pressed"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="67.73333"
android:viewportHeight="67.73334">
<path
android:pathData="M16.9333,4.2333C7.5523,4.2333 0,10.8416 0,19.05l0,29.6333c0,8.2084 7.5523,14.8167 16.9333,14.8167l33.8667,0c9.3811,0 16.9333,-6.6082 16.9333,-14.8167L67.7333,19.05C67.7333,10.8416 60.1811,4.2333 50.8,4.2333ZM11.8701,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042A3.7042,3.7042 0,0 1,22.9826 19.05,3.7042 3.7042,0 0,1 26.6867,15.3458ZM41.5034,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042z"
android:strokeLineJoin="round"
android:strokeWidth="22.6959"
android:fillColor="@color/control_primary"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="67.73333"
android:viewportHeight="67.73334">
<path
android:pathData="M16.9333,4.2333C7.5523,4.2333 0,10.8416 0,19.05l0,29.6333c0,8.2084 7.5523,14.8167 16.9333,14.8167l33.8667,0c9.3811,0 16.9333,-6.6082 16.9333,-14.8167L67.7333,19.05C67.7333,10.8416 60.1811,4.2333 50.8,4.2333ZM11.8701,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042A3.7042,3.7042 0,0 1,22.9826 19.05,3.7042 3.7042,0 0,1 26.6867,15.3458ZM41.5034,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042z"
android:strokeLineJoin="round"
android:strokeWidth="22.6959"
android:fillColor="@color/control_pressed"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1920dp"
android:height="942dp"
android:viewportWidth="508"
android:viewportHeight="249.2375">
<path
android:pathData="M32,0L476,0A32,32 0,0 1,508 32L508,217.238A32,32 0,0 1,476 249.238L32,249.238A32,32 0,0 1,-0 217.238L-0,32A32,32 0,0 1,32 0z"
android:strokeLineJoin="round"
android:strokeWidth="1.59637"
android:fillColor="@color/control_primary"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1920dp"
android:height="942dp"
android:viewportWidth="508"
android:viewportHeight="249.2375">
<path
android:pathData="M32,0L476,0A32,32 0,0 1,508 32L508,217.238A32,32 0,0 1,476 249.238L32,249.238A32,32 0,0 1,-0 217.238L-0,32A32,32 0,0 1,32 0z"
android:strokeLineJoin="round"
android:strokeWidth="1.59637"
android:fillColor="@color/control_pressed"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M9,11.24V7.5C9,6.12 10.12,5 11.5,5S14,6.12 14,7.5v3.74c1.21,-0.81 2,-2.18 2,-3.74C16,5.01 13.99,3 11.5,3S7,5.01 7,7.5C7,9.06 7.79,10.43 9,11.24zM18.84,15.87l-4.54,-2.26c-0.17,-0.07 -0.35,-0.11 -0.54,-0.11H13v-6C13,6.67 12.33,6 11.5,6S10,6.67 10,7.5v10.74c-3.6,-0.76 -3.54,-0.75 -3.67,-0.75c-0.31,0 -0.59,0.13 -0.79,0.33l-0.79,0.8l4.94,4.94C9.96,23.83 10.34,24 10.75,24h6.79c0.75,0 1.33,-0.55 1.44,-1.28l0.75,-5.27c0.01,-0.07 0.02,-0.14 0.02,-0.2C19.75,16.63 19.37,16.09 18.84,15.87z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M0,15h2L2,9L0,9v6zM3,17h2L5,7L3,7v10zM22,9v6h2L24,9h-2zM19,17h2L21,7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19L8,19L8,5h8v14z"/>
</vector>

View file

@ -1,16 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mainStreamLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".stream.StreamActivity" tools:context=".stream.StreamActivity"
android:keepScreenOn="true"> android:keepScreenOn="true">
<TextureView <com.metallic.chiaki.stream.AspectRatioFrameLayout
android:id="@+id/textureView" android:id="@+id/aspectRatioLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"
android:layout_gravity="center">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.metallic.chiaki.stream.AspectRatioFrameLayout>
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
@ -20,7 +28,7 @@
<fragment <fragment
android:id="@+id/controlsFragment" android:id="@+id/controlsFragment"
android:name="com.metallic.chiaki.touchcontrols.TouchControlsFragment" android:name="com.metallic.chiaki.touchcontrols.DefaultTouchControlsFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
@ -34,7 +42,6 @@
android:id="@+id/overlay" android:id="@+id/overlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="bottom"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -76,6 +83,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:singleSelection="true" app:singleSelection="true"
app:selectionRequired="true"
app:checkedButton="@id/display_mode_normal_button"> app:checkedButton="@id/display_mode_normal_button">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View file

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:clipChildren="false"> android:clipChildren="false"
tools:ignore="RtlHardcoded,RtlSymmetry">
<com.metallic.chiaki.touchcontrols.ControlsBackgroundView <com.metallic.chiaki.touchcontrols.ControlsBackgroundView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -45,20 +47,34 @@
app:drawableHandle="@drawable/control_analog_stick_handle" app:drawableHandle="@drawable/control_analog_stick_handle"
/> />
<com.metallic.chiaki.touchcontrols.TouchpadView
android:id="@+id/touchpadView"
android:layout_width="0dp"
android:layout_height="0dp"
app:drawableIdle="@drawable/control_touchpad"
app:drawablePressed="@drawable/control_touchpad_pressed"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="300dp"
app:layout_constraintDimensionRatio="1920:942"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"/>
<com.metallic.chiaki.touchcontrols.DPadView <com.metallic.chiaki.touchcontrols.DPadView
android:id="@+id/dpadView" android:id="@+id/dpadView"
android:layout_width="128dp" android:layout_width="160dp"
android:layout_height="128dp" android:layout_height="160dp"
android:layout_marginLeft="32dp" android:padding="16dp"
android:layout_marginBottom="32dp" android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/faceButtonsLayout" android:id="@+id/faceButtonsLayout"
android:layout_width="144dp" android:layout_width="@dimen/control_face_button_size_full"
android:layout_height="144dp" android:layout_height="@dimen/control_face_button_size_full"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -67,9 +83,12 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/crossButtonView" android:id="@+id/crossButtonView"
android:layout_width="@dimen/control_face_button_size" android:layout_width="@dimen/control_face_button_size_full"
android:layout_height="@dimen/control_face_button_size" android:layout_height="@dimen/control_face_button_size_half"
android:padding="8dp" android:paddingLeft="@dimen/control_face_button_padding_to_full"
android:paddingRight="@dimen/control_face_button_padding_to_full"
android:paddingTop="@dimen/control_face_button_padding_to_center"
android:paddingBottom="@dimen/control_face_button_padding_to_outside"
app:drawableIdle="@drawable/control_button_cross" app:drawableIdle="@drawable/control_button_cross"
app:drawablePressed="@drawable/control_button_cross_pressed" app:drawablePressed="@drawable/control_button_cross_pressed"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
@ -78,9 +97,12 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/moonButtonView" android:id="@+id/moonButtonView"
android:layout_width="@dimen/control_face_button_size" android:layout_width="@dimen/control_face_button_size_half"
android:layout_height="@dimen/control_face_button_size" android:layout_height="@dimen/control_face_button_size_full"
android:padding="8dp" android:paddingTop="@dimen/control_face_button_padding_to_full"
android:paddingBottom="@dimen/control_face_button_padding_to_full"
android:paddingLeft="@dimen/control_face_button_padding_to_center"
android:paddingRight="@dimen/control_face_button_padding_to_outside"
app:drawableIdle="@drawable/control_button_moon" app:drawableIdle="@drawable/control_button_moon"
app:drawablePressed="@drawable/control_button_moon_pressed" app:drawablePressed="@drawable/control_button_moon_pressed"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
@ -89,9 +111,12 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/pyramidButtonView" android:id="@+id/pyramidButtonView"
android:layout_width="@dimen/control_face_button_size" android:layout_width="@dimen/control_face_button_size_full"
android:layout_height="@dimen/control_face_button_size" android:layout_height="@dimen/control_face_button_size_half"
android:padding="8dp" android:paddingLeft="@dimen/control_face_button_padding_to_full"
android:paddingRight="@dimen/control_face_button_padding_to_full"
android:paddingBottom="@dimen/control_face_button_padding_to_center"
android:paddingTop="@dimen/control_face_button_padding_to_outside"
app:drawableIdle="@drawable/control_button_pyramid" app:drawableIdle="@drawable/control_button_pyramid"
app:drawablePressed="@drawable/control_button_pyramid_pressed" app:drawablePressed="@drawable/control_button_pyramid_pressed"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
@ -100,24 +125,45 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/boxButtonView" android:id="@+id/boxButtonView"
android:layout_width="@dimen/control_face_button_size" android:layout_width="@dimen/control_face_button_size_half"
android:layout_height="@dimen/control_face_button_size" android:layout_height="@dimen/control_face_button_size_full"
android:padding="8dp" android:paddingTop="@dimen/control_face_button_padding_to_full"
android:paddingBottom="@dimen/control_face_button_padding_to_full"
android:paddingRight="@dimen/control_face_button_padding_to_center"
android:paddingLeft="@dimen/control_face_button_padding_to_outside"
app:drawableIdle="@drawable/control_button_box" app:drawableIdle="@drawable/control_button_box"
app:drawablePressed="@drawable/control_button_box_pressed" app:drawablePressed="@drawable/control_button_box_pressed"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/l3ButtonView"
android:layout_width="64dp"
android:layout_height="64dp"
app:drawableIdle="@drawable/control_button_l3"
app:drawablePressed="@drawable/control_button_l3_pressed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/r3ButtonView"
android:layout_width="64dp"
android:layout_height="64dp"
app:drawableIdle="@drawable/control_button_r3"
app:drawablePressed="@drawable/control_button_r3_pressed"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/psButtonView" android:id="@+id/psButtonView"
android:layout_width="32dp" android:layout_width="48dp"
android:layout_height="32dp" android:layout_height="48dp"
android:padding="8dp" android:padding="8dp"
android:layout_marginBottom="8dp"
app:drawableIdle="@drawable/control_button_home" app:drawableIdle="@drawable/control_button_home"
app:drawablePressed="@drawable/control_button_home_pressed" app:drawablePressed="@drawable/control_button_home_pressed"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
@ -125,25 +171,14 @@
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
<com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/touchpadButtonView"
android:layout_width="32dp"
android:layout_height="32dp"
android:padding="8dp"
android:layout_marginTop="8dp"
app:drawableIdle="@drawable/control_button_touchpad"
app:drawablePressed="@drawable/control_button_touchpad_pressed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/l2ButtonView" android:id="@+id/l2ButtonView"
android:layout_width="64dp" android:layout_width="88dp"
android:layout_height="32dp" android:layout_height="80dp"
android:padding="8dp" android:paddingTop="8dp"
android:layout_marginTop="8dp" android:paddingBottom="8dp"
android:layout_marginLeft="8dp" android:paddingRight="8dp"
android:paddingLeft="16dp"
app:drawableIdle="@drawable/control_button_l2" app:drawableIdle="@drawable/control_button_l2"
app:drawablePressed="@drawable/control_button_l2_pressed" app:drawablePressed="@drawable/control_button_l2_pressed"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
@ -151,23 +186,22 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/l1ButtonView" android:id="@+id/l1ButtonView"
android:layout_width="64dp" android:layout_width="80dp"
android:layout_height="32dp" android:layout_height="80dp"
android:padding="8dp" android:padding="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="32dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="40dp"
app:drawableIdle="@drawable/control_button_l1" app:drawableIdle="@drawable/control_button_l1"
app:drawablePressed="@drawable/control_button_l1_pressed" app:drawablePressed="@drawable/control_button_l1_pressed"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/l2ButtonView"/> app:layout_constraintTop_toTopOf="parent"/>
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/shareButtonView" android:id="@+id/shareButtonView"
android:layout_width="32dp" android:layout_width="48dp"
android:layout_height="32dp" android:layout_height="48dp"
android:padding="8dp" android:padding="8dp"
android:layout_marginTop="8dp" android:layout_marginLeft="32dp"
android:layout_marginLeft="8dp"
app:drawableIdle="@drawable/control_button_share" app:drawableIdle="@drawable/control_button_share"
app:drawablePressed="@drawable/control_button_share_pressed" app:drawablePressed="@drawable/control_button_share_pressed"
app:layout_constraintLeft_toRightOf="@id/l2ButtonView" app:layout_constraintLeft_toRightOf="@id/l2ButtonView"
@ -175,11 +209,12 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/r2ButtonView" android:id="@+id/r2ButtonView"
android:layout_width="64dp" android:layout_width="88dp"
android:layout_height="32dp" android:layout_height="80dp"
android:padding="8dp" android:paddingTop="8dp"
android:layout_marginTop="8dp" android:paddingRight="16dp"
android:layout_marginRight="8dp" android:paddingLeft="8dp"
android:paddingBottom="8dp"
app:drawableIdle="@drawable/control_button_r2" app:drawableIdle="@drawable/control_button_r2"
app:drawablePressed="@drawable/control_button_r2_pressed" app:drawablePressed="@drawable/control_button_r2_pressed"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
@ -187,23 +222,22 @@
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/r1ButtonView" android:id="@+id/r1ButtonView"
android:layout_width="64dp" android:layout_width="80dp"
android:layout_height="32dp" android:layout_height="80dp"
android:padding="8dp" android:padding="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="32dp"
android:layout_marginRight="8dp" android:layout_marginRight="40dp"
app:drawableIdle="@drawable/control_button_r1" app:drawableIdle="@drawable/control_button_r1"
app:drawablePressed="@drawable/control_button_r1_pressed" app:drawablePressed="@drawable/control_button_r1_pressed"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/r2ButtonView"/> app:layout_constraintTop_toTopOf="parent"/>
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.ButtonView
android:id="@+id/optionsButtonView" android:id="@+id/optionsButtonView"
android:layout_width="32dp" android:layout_width="48dp"
android:layout_height="32dp" android:layout_height="48dp"
android:padding="8dp" android:padding="8dp"
android:layout_marginTop="8dp" android:layout_marginRight="32dp"
android:layout_marginRight="8dp"
app:drawableIdle="@drawable/control_button_options" app:drawableIdle="@drawable/control_button_options"
app:drawablePressed="@drawable/control_button_options_pressed" app:drawablePressed="@drawable/control_button_options_pressed"
app:layout_constraintRight_toLeftOf="@id/r2ButtonView" app:layout_constraintRight_toLeftOf="@id/r2ButtonView"

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -12,16 +13,18 @@
tools:layout_editor_absoluteX="0dp" tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="90dp" /> tools:layout_editor_absoluteY="90dp" />
<com.metallic.chiaki.touchcontrols.ButtonView <com.metallic.chiaki.touchcontrols.TouchpadView
android:id="@+id/touchpadButtonView" android:id="@+id/touchpadView"
android:layout_width="32dp" android:layout_width="0dp"
android:layout_height="32dp" android:layout_height="0dp"
android:layout_marginEnd="32dp" app:drawableIdle="@drawable/control_touchpad"
android:layout_marginBottom="8dp" app:drawablePressed="@drawable/control_touchpad_pressed"
android:padding="8dp" app:layout_constraintDimensionRatio="1920:942"
app:drawableIdle="@drawable/control_button_touchpad" app:layout_constraintLeft_toLeftOf="parent"
app:drawablePressed="@drawable/control_button_touchpad_pressed" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> android:layout_marginLeft="80dp"
android:layout_marginRight="80dp"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<attr name="drawableIdle" format="reference" />
<attr name="drawablePressed" format="reference" />
<declare-styleable name="ButtonView"> <declare-styleable name="ButtonView">
<attr name="drawableIdle" format="reference" /> <attr name="drawableIdle" />
<attr name="drawablePressed" format="integer" /> <attr name="drawablePressed" />
</declare-styleable> </declare-styleable>
<declare-styleable name="AnalogStickView"> <declare-styleable name="AnalogStickView">
@ -11,4 +14,9 @@
<attr name="drawableBase" format="reference" /> <attr name="drawableBase" format="reference" />
<attr name="drawableHandle" format="reference" /> <attr name="drawableHandle" format="reference" />
</declare-styleable> </declare-styleable>
<declare-styleable name="TouchpadView">
<attr name="drawableIdle" />
<attr name="drawablePressed" />
</declare-styleable>
</resources> </resources>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="control_face_button_size">48dp</dimen> <dimen name="control_face_button_size_half">88dp</dimen>
<dimen name="control_face_button_size_full">176dp</dimen>
<dimen name="control_face_button_padding_to_center">24dp</dimen>
<dimen name="control_face_button_padding_to_outside">16dp</dimen>
<dimen name="control_face_button_padding_to_full">64dp</dimen>
<dimen name="control_analog_stick_radius">48dp</dimen> <dimen name="control_analog_stick_radius">48dp</dimen>
<dimen name="control_analog_stick_handle_radius">32dp</dimen> <dimen name="control_analog_stick_handle_radius">32dp</dimen>
<dimen name="floating_action_button_speed_dial_anim_offset">48dp</dimen> <dimen name="floating_action_button_speed_dial_anim_offset">48dp</dimen>

View file

@ -88,6 +88,12 @@
<string name="preferences_codec_title_h265">H265 (PS5 only)</string> <string name="preferences_codec_title_h265">H265 (PS5 only)</string>
<string name="preferences_swap_cross_moon_title">Swap Cross/Moon and Box/Pyramid Buttons</string> <string name="preferences_swap_cross_moon_title">Swap Cross/Moon and Box/Pyramid Buttons</string>
<string name="preferences_swap_cross_moon_summary">Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers)</string> <string name="preferences_swap_cross_moon_summary">Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers)</string>
<string name="preferences_rumble_enabled_title">Rumble</string>
<string name="preferences_rumble_enabled_summary">Use phone vibration motor for rumble</string>
<string name="preferences_motion_enabled_title">Motion</string>
<string name="preferences_motion_enabled_summary">Use device\'s motion sensors for controller motion</string>
<string name="preferences_button_haptic_enabled_title">Touch Haptics</string>
<string name="preferences_button_haptic_enabled_summary">Use phone vibration motor for short haptic feedback on button touches</string>
<string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string> <string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string>
<string name="alert_message_delete_manual_host">Are you sure you want to delete the console entry for %s?</string> <string name="alert_message_delete_manual_host">Are you sure you want to delete the console entry for %s?</string>
<string name="action_keep">Keep</string> <string name="action_keep">Keep</string>
@ -101,7 +107,10 @@
<!-- Don't localize these --> <!-- Don't localize these -->
<string name="preferences_discovery_enabled_key">discovery_enabled</string> <string name="preferences_discovery_enabled_key">discovery_enabled</string>
<string name="preferences_on_screen_controls_enabled_key">on_screen_controls_enabled</string> <string name="preferences_on_screen_controls_enabled_key">on_screen_controls_enabled</string>
<string name="preferences_touchpad_only_key">touchpad_only_enabled</string> <string name="preferences_touchpad_only_enabled_key">touchpad_only_enabled</string>
<string name="preferences_rumble_enabled_key">rumble_enabled</string>
<string name="preferences_motion_enabled_key">motion_enabled</string>
<string name="preferences_button_haptic_enabled_key">button_haptic_enabled</string>
<string name="preferences_log_verbose_key">log_verbose</string> <string name="preferences_log_verbose_key">log_verbose</string>
<string name="preferences_import_settings_key">import_settings</string> <string name="preferences_import_settings_key">import_settings</string>
<string name="preferences_export_settings_key">export_settings</string> <string name="preferences_export_settings_key">export_settings</string>

View file

@ -19,6 +19,24 @@
app:summary="@string/preferences_swap_cross_moon_summary" app:summary="@string/preferences_swap_cross_moon_summary"
app:icon="@drawable/ic_gamepad" /> app:icon="@drawable/ic_gamepad" />
<SwitchPreference
app:key="@string/preferences_rumble_enabled_key"
app:title="@string/preferences_rumble_enabled_title"
app:summary="@string/preferences_rumble_enabled_summary"
app:icon="@drawable/ic_rumble" />
<SwitchPreference
app:key="@string/preferences_motion_enabled_key"
app:title="@string/preferences_motion_enabled_title"
app:summary="@string/preferences_motion_enabled_summary"
app:icon="@drawable/ic_motion" />
<SwitchPreference
app:key="@string/preferences_button_haptic_enabled_key"
app:title="@string/preferences_button_haptic_enabled_title"
app:summary="@string/preferences_button_haptic_enabled_summary"
app:icon="@drawable/ic_button_haptic" />
<SwitchPreference <SwitchPreference
app:key="@string/preferences_log_verbose_key" app:key="@string/preferences_log_verbose_key"
app:title="@string/preferences_log_verbose_title" app:title="@string/preferences_log_verbose_title"

View file

@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.61' ext.kotlin_version = '1.8.0'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.android.tools.build:gradle:7.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View file

@ -1,6 +1,6 @@
#Wed Mar 11 18:48:31 CET 2020 #Sun Feb 05 16:25:19 CET 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME

266
assets/chiaki_macos.svg Normal file
View file

@ -0,0 +1,266 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024"
height="1024"
viewBox="0 0 270.93332 270.93334"
version="1.1"
id="svg5"
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
sodipodi:docname="chiaki_macos.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.5946522"
inkscape:cx="616.32665"
inkscape:cy="519.63148"
inkscape:window-width="1920"
inkscape:window-height="978"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true"><sodipodi:guide
position="26.458333,219.96511"
orientation="-1,0"
id="guide1357"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" /><sodipodi:guide
position="50.653599,244.475"
orientation="0,1"
id="guide1359"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" /><sodipodi:guide
position="244.475,210.52655"
orientation="-1,0"
id="guide1361"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" /><sodipodi:guide
position="67.733332,26.458333"
orientation="0,1"
id="guide1363"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" /><sodipodi:guide
position="135.46666,253.78662"
orientation="-1,0"
id="guide4345"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" /></sodipodi:namedview><defs
id="defs2"><linearGradient
inkscape:collect="always"
id="linearGradient4465"><stop
style="stop-color:#f1ff7f;stop-opacity:1;"
offset="0"
id="stop4461" /><stop
style="stop-color:#b2d400;stop-opacity:1;"
offset="1"
id="stop4463" /></linearGradient><filter
x="-0.014563107"
y="-0.014563107"
width="1.0291262"
height="1.0412621"
filterUnits="objectBoundingBox"
id="filter-80aygx4sbj-3"><feOffset
dx="0"
dy="5"
in="SourceAlpha"
result="shadowOffsetOuter1"
id="feOffset424" /><feGaussianBlur
stdDeviation="2.5"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
id="feGaussianBlur426" /><feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"
type="matrix"
in="shadowBlurOuter1"
id="feColorMatrix428" /></filter><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath869"><path
d="m -143.73827,56.831818 a 79.375,79.375 0 0 1 102.042535,2e-6 l -51.021268,60.80478 z"
sodipodi:end="5.4105207"
sodipodi:start="4.0142573"
sodipodi:ry="79.375"
sodipodi:rx="79.375"
sodipodi:cy="117.6366"
sodipodi:cx="-92.717003"
sodipodi:type="arc"
id="path871"
style="fill:#008080;fill-opacity:1;stroke:none;stroke-width:11.1206;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:arc-type="slice" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath964"><circle
style="display:inline;fill:#ff9955;fill-opacity:1;stroke:none;stroke-width:8.46667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle966"
cx="-135.46669"
cy="161.49431"
r="135.46667"
inkscape:label="circle"
transform="scale(-1,1)" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4387"><path
d="m 270.31599,96.939968 c 0,-3.20165 0,-6.403627 -0.0173,-9.605605 -0.0174,-2.697285 -0.0473,-5.393909 -0.12049,-8.089885 -0.15834,-5.876017 -0.50503,-11.802446 -1.55009,-17.613009 -1.05975,-5.894353 -2.79053,-11.380228 -5.51791,-16.735846 -2.68122,-5.26397 -6.18365,-10.080848 -10.3626,-14.256872 -4.17798,-4.176024 -8.99614,-7.676173 -14.26207,-10.355782 -5.36116,-2.728048 -10.85292,-4.458156 -16.75377,-5.517628 -5.81122,-1.043433 -11.73926,-1.38939 -17.61526,-1.54813 -2.69792,-0.07299 -5.39585,-0.103764 -8.09443,-0.120439 -3.2036,-0.01955 -6.40721,-0.01806 -9.61081,-0.01806 L 149.2133,12.945506 h -27.8204 l -36.538684,0.133205 c -3.209827,0 -6.419646,-0.0016 -9.629466,0.01806 -2.704145,0.01657 -5.406981,0.04745 -8.110144,0.120439 -5.889745,0.158737 -11.831203,0.505024 -17.656149,1.549765 -5.908728,1.059145 -11.408663,2.788599 -16.777022,5.514685 -5.277041,2.679934 -10.105685,6.18041 -14.292493,10.35709 -4.186156,4.175697 -7.695131,8.991593 -10.3812753,14.254581 -2.734911,5.35823 -4.4689412,10.84739 -5.5313527,16.74501 -1.0460477,5.808273 -1.3926574,11.732737 -1.55205319,17.606136 -0.0726642,2.696304 -0.10408515,5.392927 -0.12043909,8.089885 -0.0195464,3.202305 -0.18427009,7.179987 -0.18427009,10.381964 v 36.032494 28.11836 l 0.16625869,36.79805 c 0,3.20624 -0.001309,6.41247 0.0180605,9.6187 0.0163323,2.70089 0.0477729,5.40111 0.12078473,8.10101 0.15905935,5.88356 0.50633015,11.81849 1.55336045,17.63691 1.0620848,5.90221 2.7957884,11.39593 5.5284083,16.75842 2.6864707,5.27149 6.1960987,10.09461 10.3825837,14.27651 4.186482,4.18193 9.013815,7.68665 14.289876,10.3702 5.37163,2.73164 10.874184,4.46402 16.786519,5.52481 5.82232,1.04508 11.761815,1.39168 17.649275,1.55042 2.703163,0.073 5.406326,0.10416 8.110465,0.12049 3.20982,0.0198 6.419318,0.0198 9.629138,0.0198 h 36.868929 27.88948 36.79758 c 3.2036,0 6.40721,0 9.61081,-0.0198 2.69858,-0.0174 5.39651,-0.0475 8.09444,-0.12049 5.87828,-0.15908 11.8083,-0.506 17.62211,-1.55173 5.89727,-1.06076 11.38641,-2.79253 16.74462,-5.52253 5.26689,-2.68321 10.08605,-6.18859 14.26436,-10.37117 4.17829,-4.18126 7.68039,-9.00371 10.36162,-14.27421 2.72902,-5.36511 4.45979,-10.86145 5.52024,-16.7676 1.04409,-5.81612 1.39036,-11.74877 1.5491,-17.63003 0.073,-2.70023 0.10416,-5.40045 0.11999,-8.10101 0.0173,-3.20623 0.0173,-6.41246 0.0173,-9.6187 0,0 0,-36.14835 0,-36.79805 v -28.14782 c 0,-0.47982 0,-36.779393 0,-36.779393"
id="path4389"
style="fill:#cfff0d;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.654602"
inkscape:label="clip" /></clipPath><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4465"
id="linearGradient4467"
x1="67.73333"
y1="13.229165"
x2="67.73333"
y2="122.2375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.0000001,0,0,2.0000001,5.0529028e-6,-2.9497097e-5)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4465"
id="linearGradient1573"
gradientUnits="userSpaceOnUse"
x1="67.73333"
y1="13.229165"
x2="67.73333"
y2="122.2375"
gradientTransform="matrix(1.9999999,0,0,1.9999999,-8.9032287e-4,7.677083e-6)" /></defs><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"><path
d="m 417,128.31353 c 0,-4.89098 0,-9.78246 -0.0281,-14.67394 -0.0245,-4.12049 -0.072,-8.23997 -0.184,-12.35846 -0.242,-8.976466 -0.77149,-18.029933 -2.36798,-26.906401 C 412.80098,65.370262 410.157,56.989792 405.99053,48.808322 401.89456,40.766852 396.5441,33.408378 390.16015,27.028902 383.7777,20.649425 376.41725,15.302444 368.37281,11.208959 360.18287,7.0414744 351.79343,4.398484 342.779,2.7799899 333.90156,1.1859957 324.84563,0.65749761 315.8692,0.41499849 311.74773,0.3034989 307.62626,0.25649907 303.50379,0.23099916 298.60983,0.20099927 293.71586,0.20349926 288.8219,0.20349926 L 231.99832,0 h -42.49969 l -55.81808,0.20349926 c -4.90347,0 -9.80693,-0.0025 -14.71039,0.0274999 -4.13097,0.0254999 -8.25994,0.0724997 -12.38941,0.18399933 C 97.583315,0.65749761 88.506882,1.1864957 79.608448,2.7824899 70.582015,4.400484 62.180077,7.0424744 53.979137,11.206959 45.917697,15.300944 38.541252,20.648425 32.145299,27.028902 25.750346,33.407878 20.389886,40.764852 16.286417,48.804822 12.108447,56.990293 9.459467,65.375762 7.836479,74.385229 6.2384908,83.258197 5.7089948,92.308664 5.4654965,101.28113 5.3544974,105.40012 5.3064977,109.5196 5.2814979,113.63959 5.2514981,118.53157 5,124.60805 5,129.49953 v 55.0448 42.95484 l 0.2539981,56.2143 c 0,4.89798 -0.002,9.79596 0.0275,14.69394 0.025,4.12599 0.072999,8.25097 0.1844986,12.37546 0.2429983,8.98797 0.7734943,18.05443 2.3729825,26.9429 1.622488,9.01647 4.2709678,17.40894 8.4454378,25.60091 4.103969,8.05297 9.465429,15.42094 15.860882,21.80942 6.395453,6.38848 13.769898,11.74245 21.829839,15.84194 8.205939,4.17298 16.611876,6.81947 25.64381,8.43997 8.894434,1.59649 17.967867,2.12599 26.961802,2.36849 4.12947,0.1115 8.25894,0.159 12.38991,0.184 C 123.87412,412 128.77708,412 133.68055,412 h 56.32258 42.60518 56.21359 c 4.89396,0 9.78793,0 14.68189,-0.0295 4.12247,-0.025 8.24394,-0.0725 12.36541,-0.184 8.97993,-0.243 18.03887,-0.773 26.9203,-2.37049 9.00893,-1.62049 17.39437,-4.26598 25.57981,-8.43647 8.04594,-4.09898 15.40789,-9.45397 21.79084,-15.84344 6.38295,-6.38748 11.73291,-13.75445 15.82888,-21.80592 4.16897,-8.19597 6.81295,-16.59244 8.43294,-25.61491 1.59499,-8.88497 2.12398,-17.94793 2.36648,-26.9324 0.1115,-4.12499 0.159,-8.24997 0.1835,-12.37546 C 417,293.50943 417,288.61145 417,283.71347 c 0,0 0,-55.2218 0,-56.2143 v -42.99984 c 0,-0.733 0,-56.1858 0,-56.1858"
id="use436"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;filter:url(#filter-80aygx4sbj-3)"
transform="matrix(0.52916669,0,0,0.52916669,23.8125,26.458334)" /><path
d="m 244.475,94.357609 c 0,-2.588144 0,-5.176552 -0.014,-7.76496 -0.014,-2.180426 -0.0382,-4.360318 -0.0974,-6.539686 -0.128,-4.750046 -0.40826,-9.540837 -1.25306,-14.237969 -0.85668,-4.764864 -2.2558,-9.199528 -4.46056,-13.52889 -2.16744,-4.255278 -4.99872,-8.149136 -8.3769,-11.524941 -3.37738,-3.375806 -7.27228,-6.20525 -11.52914,-8.371386 -4.33384,-2.205294 -8.77326,-3.603876 -13.54338,-4.46033 -4.69766,-0.843488 -9.48976,-1.123152 -14.23978,-1.251474 -2.18094,-0.059 -4.3619,-0.08388 -6.54336,-0.09736 -2.58972,-0.0158 -5.17946,-0.0146 -7.76918,-0.0146 l -30.06997,-0.10768 h -22.48942 l -29.537067,0.10768 c -2.594752,0 -5.1895,-0.0013 -7.784248,0.0146 -2.185972,0.0134 -4.370884,0.03836 -6.556062,0.09736 -4.761142,0.12832 -9.564087,0.40825 -14.272841,1.252796 -4.776488,0.85619 -9.222514,2.254242 -13.562178,4.45795 -4.265844,2.1664 -8.169214,4.996108 -11.553737,8.372444 -3.383996,3.375541 -6.220574,7.268605 -8.391994,11.523089 -2.210842,4.331478 -3.612594,8.76879 -4.471424,13.536298 -0.845602,4.69528 -1.125794,9.484485 -1.254646,14.232413 -0.05874,2.179634 -0.08414,4.359524 -0.09736,6.539686 -0.0158,2.588674 -0.14896,5.804144 -0.14896,8.392552 v 29.127869 22.73027 l 0.1344,29.74674 c 0,2.59184 -0.0011,5.18368 0.0146,7.77554 0.0132,2.18334 0.03862,4.36614 0.09764,6.54868 0.12858,4.75614 0.409306,9.5538 1.255702,14.25728 0.858566,4.77122 2.260054,9.21222 4.469044,13.54714 2.171684,4.26136 5.00879,8.16026 8.393052,11.54082 3.384259,3.38058 7.286569,6.21372 11.551621,8.38304 4.34231,2.2082 8.790452,3.60862 13.56985,4.46614 4.706638,0.84482 9.507995,1.125 14.267285,1.25332 2.185178,0.059 4.370356,0.0842 6.556326,0.0974 2.594748,0.016 5.189232,0.016 7.783984,0.016 h 29.804033 22.54524 29.74635 c 2.58972,0 5.17944,0 7.76916,-0.016 2.18148,-0.014 4.36242,-0.0384 6.54338,-0.0974 4.75188,-0.1286 9.54556,-0.40904 14.24532,-1.25438 4.76722,-0.8575 9.20452,-2.25742 13.53598,-4.4643 4.25764,-2.16904 8.15334,-5.00272 11.531,-8.38382 3.37764,-3.38004 6.20866,-7.2784 8.3761,-11.53896 2.20608,-4.33704 3.6052,-8.78016 4.46244,-13.55456 0.84402,-4.70162 1.12394,-9.49744 1.25226,-14.25172 0.059,-2.18282 0.0842,-4.36562 0.097,-6.54868 0.014,-2.59186 0.014,-5.1837 0.014,-7.77554 0,0 0,-29.22154 0,-29.74674 v -22.75408 c 0,-0.38788 0,-29.731651 0,-29.731651"
id="use438"
style="display:inline;fill:url(#linearGradient1573);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.529166" /><path
id="path1446"
style="display:none;fill:url(#linearGradient4467);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.529166"
inkscape:label="bg_cut"
d="m 124.0896,26.458306 -29.537222,0.10748 c -2.59475,0 -5.188758,-0.0015 -7.783504,0.0144 -2.18597,0.0134 -4.37153,0.03918 -6.556706,0.09818 -4.761139,0.12832 -9.564283,0.408092 -14.273031,1.252638 -4.776484,0.856188 -9.222305,2.253902 -13.561965,4.457609 -4.26584,2.166398 -8.169296,4.996284 -11.553817,8.372616 -3.383992,3.375538 -6.219802,7.268332 -8.39122,11.522813 -2.210838,4.331472 -3.613248,8.76862 -4.472078,13.536124 -0.8456,4.695277 -1.125852,9.484801 -1.254704,14.232723 -0.05874,2.179632 -0.08394,4.358977 -0.09716,6.539137 -0.0158,2.588672 -0.14882,5.804882 -0.14882,8.393286 V 108.73021 H 244.47502 V 94.35796 h 0.001 c 0,-2.58814 -4.8e-4,-5.177528 -0.014,-7.765934 -0.014,-2.180424 -0.038,-4.359771 -0.0972,-6.539137 -0.128,-4.75004 -0.40784,-9.540762 -1.25264,-14.237891 -0.85668,-4.76486 -2.25596,-9.199532 -4.46072,-13.52889 -2.16744,-4.255275 -4.99856,-8.149077 -8.37674,-11.524879 -3.37738,-3.375802 -7.27216,-6.205448 -11.52902,-8.371582 -4.33384,-2.205293 -8.77324,-3.604257 -13.54336,-4.460711 -4.69766,-0.843486 -9.48993,-1.122248 -14.23996,-1.25057 -2.18094,-0.059 -4.36179,-0.0847 -6.54327,-0.09818 -2.58971,-0.0158 -5.18035,-0.0144 -7.77006,-0.0144 l -30.06949,-0.10748 z m -97.498969,149.491654 0.0032,0.63976 c 0,2.59184 -0.0012,5.18442 0.0144,7.77627 0.0132,2.18333 0.03916,4.36589 0.09818,6.54843 0.12858,4.75613 0.408308,9.55406 1.254704,14.25754 0.858566,4.7712 2.261024,9.21154 4.470012,13.54646 2.171682,4.26134 5.007994,8.16086 8.392252,11.5414 3.384257,3.38058 7.286703,6.21364 11.551751,8.38296 4.342306,2.2082 8.790839,3.60836 13.570231,4.46588 4.706634,0.84482 9.507544,1.12534 14.266831,1.25366 2.185176,0.059 4.370738,0.084 6.556706,0.0972 2.594746,0.016 5.188754,0.016 7.783504,0.016 h 29.804898 22.54437 29.74702 c 2.58972,0 5.17932,5e-4 7.76903,-0.016 2.18148,-0.014 4.36233,-0.0382 6.54327,-0.0972 4.75188,-0.1286 9.54537,-0.40936 14.24513,-1.2547 4.76722,-0.8575 9.20466,-2.25694 13.53612,-4.4638 4.25764,-2.16904 8.15342,-5.0029 11.53108,-8.384 3.37764,-3.38002 6.20828,-7.27878 8.37572,-11.53934 2.20608,-4.33704 3.60554,-8.78034 4.46278,-13.55472 0.84402,-4.70162 1.12432,-9.49706 1.25264,-14.25134 0.059,-2.1828 0.0834,-4.36536 0.0962,-6.54843 0.014,-2.59185 0.014,-5.18443 0.014,-7.77627 v -0.63976 z" /><g
id="g1005"
inkscape:label="main icon"
clip-path="url(#clipPath4387)"
style="display:inline"
transform="matrix(0.80837807,0,0,0.80837807,25.958398,15.993434)"><rect
inkscape:label="bg"
ry="1.0565645"
y="20.8298"
x="-4.0937502e-07"
height="270.93335"
width="270.93335"
id="rect873"
style="display:none;fill:#cfff0d;fill-opacity:1;stroke:none;stroke-width:8.46431;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><g
id="g1033"
inkscape:label="right (original)"
transform="matrix(-1,0,0,1,262.28035,14.266743)"
style="display:none;stroke:none"><path
inkscape:connector-curvature="0"
id="path1019"
d="M -8.6529992,183.6071 H 107.72321 l 19.09046,28.9079 V 100.45235 H -8.6529992 Z"
style="display:inline;fill:#162d50;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
sodipodi:nodetypes="cccccc"
inkscape:label="face" /><rect
style="fill:#ffaaee;fill-opacity:1;stroke:none;stroke-width:0.185662;stroke-miterlimit:4;stroke-dasharray:none"
id="rect1021"
width="15.497028"
height="31.75"
x="92.382744"
y="131.06844"
inkscape:label="eye" /><path
sodipodi:type="star"
style="fill:#162d50;fill-opacity:1;stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none"
id="path1023"
sodipodi:sides="3"
sodipodi:cx="75.635689"
sodipodi:cy="87.601158"
sodipodi:r1="33.042126"
sodipodi:r2="16.521063"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 104.25101,104.12222 -28.615321,0 -28.615321,0 14.30766,-24.781593 14.307661,-24.781595 14.30766,24.781594 z"
inkscape:transform-center-y="-7.0654063"
transform="matrix(0.70280031,0,0,0.85532099,32.495272,20.355967)"
inkscape:label="ear" /><flowRoot
xml:space="preserve"
id="flowRoot1031"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;fill:#000000;fill-opacity:1;stroke:none"
transform="matrix(0.26458333,0,0,0.26458333,-8.6529992,15.861276)"
inkscape:label="flowRoot4545"><flowRegion
id="flowRegion1027"
style="stroke:none"><rect
id="rect1025"
width="104.28571"
height="78.571426"
x="-374.28571"
y="54"
style="stroke:none" /></flowRegion><flowPara
id="flowPara1029" /></flowRoot></g><use
style="display:none"
inkscape:label="left"
transform="matrix(-1,0,0,1,270.93336,-5.3256301e-6)"
height="100%"
width="100%"
id="use4535"
xlink:href="#g4527"
y="0"
x="0" /><g
style="display:inline;stroke:none"
transform="matrix(-1,0,0,1,262.28035,14.266743)"
inkscape:label="face"
id="g4527"><path
style="display:inline;fill:#162d50;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 356.42773,228.50391 -38.0039,80.11132 -21.9375,46.24219 H -619.27414 V 669.14258 H 439.84766 L 512,778.40039 584.15234,669.14258 H 1613.2487 V 354.85742 H 727.51367 l -21.9375,-46.24219 -38.0039,-80.11132 -38.00586,80.11132 -21.9375,46.24219 H 512 416.37109 l -21.9375,-46.24219 z"
id="path4518"
inkscape:connector-curvature="0"
inkscape:label="shape"
transform="matrix(-0.26458333,0,0,0.26458333,262.28035,6.563066)"
sodipodi:nodetypes="ccccccccccccccccccc" /><rect
inkscape:label="eye right"
y="131.18182"
x="92.153267"
height="31.75"
width="15.875"
id="rect1043"
style="display:inline;fill:#ffaaee;fill-opacity:1;stroke:none;stroke-width:0.187912;stroke-miterlimit:4;stroke-dasharray:none" /><rect
style="display:inline;fill:#ffaaee;fill-opacity:1;stroke:none;stroke-width:0.187912;stroke-miterlimit:4;stroke-dasharray:none"
id="rect1047"
width="15.875"
height="31.75"
x="145.59911"
y="131.18182"
inkscape:label="eye left" /></g><g
inkscape:label="dangerous_microwaves"
clip-path="url(#clipPath869)"
style="fill:none"
transform="translate(187.02207,-8.675551)"
id="g844"><circle
inkscape:label="path830"
r="58.208332"
style="fill:none;fill-opacity:1;stroke:#162d50;stroke-width:8.46667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path830"
cx="-92.71701"
cy="117.6366" /><circle
r="41.241978"
cy="117.6366"
cx="-92.71701"
id="circle840"
style="fill:none;fill-opacity:1;stroke:#162d50;stroke-width:8.46667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></g></g></svg>

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024"
height="1024"
viewBox="0 0 270.93332 270.93334"
version="1.1"
id="svg5"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs2"><linearGradient
id="linearGradient4465"><stop
style="stop-color:#f6ffbc;stop-opacity:1;"
offset="0"
id="stop4461" /><stop
style="stop-color:#b4d700;stop-opacity:1;"
offset="1"
id="stop4463" /></linearGradient><filter
x="-0.014563107"
y="-0.014563107"
width="1.0291262"
height="1.0412621"
filterUnits="objectBoundingBox"
id="filter-80aygx4sbj-3"><feOffset
dx="0"
dy="5"
in="SourceAlpha"
result="shadowOffsetOuter1"
id="feOffset424" /><feGaussianBlur
stdDeviation="2.5"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
id="feGaussianBlur426" /><feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"
type="matrix"
in="shadowBlurOuter1"
id="feColorMatrix428" /></filter><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath964"><circle
style="display:inline;fill:#ff9955;fill-opacity:1;stroke:none;stroke-width:8.46667;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle966"
cx="-135.46669"
cy="161.49431"
r="135.46667"
transform="scale(-1,1)" /></clipPath><linearGradient
xlink:href="#linearGradient4465"
id="linearGradient4467"
x1="67.73333"
y1="13.229165"
x2="67.73333"
y2="122.2375"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#linearGradient4465-9"
id="linearGradient4467-7"
x1="67.73333"
y1="13.229165"
x2="67.73333"
y2="122.2375"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-7.288516e-4,6.5027792e-5)" /><linearGradient
id="linearGradient4465-9"><stop
style="stop-color:#f1ff7f;stop-opacity:1;"
offset="0"
id="stop4461-3" /><stop
style="stop-color:#b2d400;stop-opacity:1;"
offset="1"
id="stop4463-8" /></linearGradient></defs><g
id="layer1"
transform="matrix(2.0000001,0,0,2.0000001,4.2905177e-6,-3.0059484e-5)"><path
d="m 417,128.31353 c 0,-4.89098 0,-9.78246 -0.0281,-14.67394 -0.0245,-4.12049 -0.072,-8.23997 -0.184,-12.35846 -0.242,-8.976466 -0.77149,-18.029933 -2.36798,-26.906401 C 412.80098,65.370262 410.157,56.989792 405.99053,48.808322 401.89456,40.766852 396.5441,33.408378 390.16015,27.028902 383.7777,20.649425 376.41725,15.302444 368.37281,11.208959 360.18287,7.0414744 351.79343,4.398484 342.779,2.7799899 333.90156,1.1859957 324.84563,0.65749761 315.8692,0.41499849 311.74773,0.3034989 307.62626,0.25649907 303.50379,0.23099916 298.60983,0.20099927 293.71586,0.20349926 288.8219,0.20349926 L 231.99832,0 h -42.49969 l -55.81808,0.20349926 c -4.90347,0 -9.80693,-0.0025 -14.71039,0.0274999 -4.13097,0.0254999 -8.25994,0.0724997 -12.38941,0.18399933 C 97.583315,0.65749761 88.506882,1.1864957 79.608448,2.7824899 70.582015,4.400484 62.180077,7.0424744 53.979137,11.206959 45.917697,15.300944 38.541252,20.648425 32.145299,27.028902 25.750346,33.407878 20.389886,40.764852 16.286417,48.804822 12.108447,56.990293 9.459467,65.375762 7.836479,74.385229 6.2384908,83.258197 5.7089948,92.308664 5.4654965,101.28113 5.3544974,105.40012 5.3064977,109.5196 5.2814979,113.63959 5.2514981,118.53157 5,124.60805 5,129.49953 v 55.0448 42.95484 l 0.2539981,56.2143 c 0,4.89798 -0.002,9.79596 0.0275,14.69394 0.025,4.12599 0.072999,8.25097 0.1844986,12.37546 0.2429983,8.98797 0.7734943,18.05443 2.3729825,26.9429 1.622488,9.01647 4.2709678,17.40894 8.4454378,25.60091 4.103969,8.05297 9.465429,15.42094 15.860882,21.80942 6.395453,6.38848 13.769898,11.74245 21.829839,15.84194 8.205939,4.17298 16.611876,6.81947 25.64381,8.43997 8.894434,1.59649 17.967867,2.12599 26.961802,2.36849 4.12947,0.1115 8.25894,0.159 12.38991,0.184 C 123.87412,412 128.77708,412 133.68055,412 h 56.32258 42.60518 56.21359 c 4.89396,0 9.78793,0 14.68189,-0.0295 4.12247,-0.025 8.24394,-0.0725 12.36541,-0.184 8.97993,-0.243 18.03887,-0.773 26.9203,-2.37049 9.00893,-1.62049 17.39437,-4.26598 25.57981,-8.43647 8.04594,-4.09898 15.40789,-9.45397 21.79084,-15.84344 6.38295,-6.38748 11.73291,-13.75445 15.82888,-21.80592 4.16897,-8.19597 6.81295,-16.59244 8.43294,-25.61491 1.59499,-8.88497 2.12398,-17.94793 2.36648,-26.9324 0.1115,-4.12499 0.159,-8.24997 0.1835,-12.37546 C 417,293.50943 417,288.61145 417,283.71347 c 0,0 0,-55.2218 0,-56.2143 v -42.99984 c 0,-0.733 0,-56.1858 0,-56.1858"
id="use436"
style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;filter:url(#filter-80aygx4sbj-3)"
transform="matrix(0.26458333,0,0,0.26458333,11.906247,13.229181)" /><path
id="path1446-0"
style="display:inline;fill:url(#linearGradient4467-7);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583"
d="m 62.044061,13.229232 -14.7686,0.05374 c -1.29738,0 -2.59438,-7.27e-4 -3.89175,0.0072 -1.09299,0.0067 -2.18577,0.01959 -3.27836,0.04909 -2.38057,0.06416 -4.78214,0.204046 -7.13651,0.626319 -2.38824,0.428094 -4.61115,1.126951 -6.78098,2.228804 -2.13292,1.083199 -4.08465,2.498142 -5.77691,4.186308 -1.692,1.687769 -3.1099,3.634166 -4.19561,5.761406 -1.10542,2.165736 -1.80663,4.38431 -2.23604,6.768062 -0.4228,2.347638 -0.56293,4.7424 -0.62735,7.116361 -0.0294,1.089816 -0.042,2.179488 -0.0486,3.269568 -0.008,1.294336 -0.0744,2.902441 -0.0744,4.196643 v 6.872449 H 122.23676 v -7.186125 h 5.2e-4 c 0,-1.29407 -2.4e-4,-2.588764 -0.007,-3.882967 -0.007,-1.090212 -0.019,-2.179885 -0.0486,-3.269568 -0.064,-2.37502 -0.20392,-4.770381 -0.62632,-7.118945 -0.42834,-2.38243 -1.12798,-4.599766 -2.23036,-6.764445 -1.08372,-2.127637 -2.49928,-4.074538 -4.18837,-5.762439 -1.68869,-1.687901 -3.63608,-3.102724 -5.76451,-4.185791 -2.16692,-1.102646 -4.38662,-1.802128 -6.77168,-2.230355 -2.34883,-0.421743 -4.744969,-0.561124 -7.119979,-0.625285 -1.09047,-0.0295 -2.1809,-0.04235 -3.27163,-0.04909 -1.29486,-0.0079 -2.59018,-0.0072 -3.88504,-0.0072 l -15.03474,-0.05374 z m -48.74947,74.745825 0.002,0.319877 c 0,1.295923 -6e-4,2.592212 0.007,3.888135 0.007,1.091667 0.0196,2.182948 0.0491,3.274218 0.0643,2.378065 0.20415,4.777033 0.62735,7.128773 0.42928,2.3856 1.13051,4.60577 2.235,6.77323 1.08584,2.13067 2.504,4.08043 4.19613,5.7707 1.69213,1.69029 3.64335,3.10682 5.77587,4.19148 2.17116,1.1041 4.39542,1.80418 6.78512,2.23294 2.35332,0.42241 4.75377,0.56267 7.13341,0.62683 1.09259,0.0295 2.18537,0.042 3.27836,0.0486 1.29737,0.008 2.59437,0.008 3.89175,0.008 h 14.90244 11.27219 14.87351 c 1.29485,0 2.58965,2.5e-4 3.88451,-0.008 1.09074,-0.007 2.18117,-0.0191 3.27164,-0.0486 2.37594,-0.0643 4.772679,-0.20468 7.122559,-0.62735 2.38361,-0.42875 4.60233,-1.12847 6.76806,-2.2319 2.12882,-1.08452 4.07671,-2.50145 5.76554,-4.192 1.68882,-1.69001 3.10414,-3.63939 4.18786,-5.76967 1.10304,-2.16852 1.80277,-4.39017 2.23139,-6.77736 0.42201,-2.35081 0.56216,-4.748534 0.62632,-7.125673 0.0295,-1.091402 0.0417,-2.182683 0.0481,-3.274218 0.007,-1.295923 0.007,-2.592212 0.007,-3.888135 v -0.319877 z" /><path
id="path4518"
style="display:inline;fill:#162d50;stroke:none;stroke-width:0.106942px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 51.096105,40.852287 -4.064351,8.567436 -2.345593,4.945434 H 13.229682 v 7.69152 11.365198 l 0.06563,14.553117 H 60.017007 L 67.733332,99.65955 75.449657,87.974992 H 122.2375 V 73.421875 62.044791 54.365157 H 90.780505 l -2.345594,-4.945434 -4.064351,-8.567436 -4.064351,8.567436 -2.346111,4.945434 H 67.733332 57.506567 l -2.34611,-4.945434 z" /><rect
y="66.785446"
x="-81.742691"
height="12.833001"
width="6.4165006"
id="rect1043"
style="display:inline;fill:#ffaaee;fill-opacity:1;stroke:none;stroke-width:0.075952;stroke-miterlimit:4;stroke-dasharray:none"
transform="scale(-1,1)" /><rect
style="display:inline;fill:#ffaaee;fill-opacity:1;stroke:none;stroke-width:0.075952;stroke-miterlimit:4;stroke-dasharray:none"
id="rect1047"
width="6.4165006"
height="12.833001"
x="-60.140472"
y="66.785446"
transform="scale(-1,1)" /><path
id="path830"
style="color:#000000;fill:#162d50;-inkscape-stroke:none"
d="m 51.095589,26.798881 c -6.169023,0 -11.826367,2.225125 -16.215031,5.913334 l 2.185913,2.605009 c 3.790164,-3.181711 8.681211,-5.09633 14.029118,-5.09633 5.348221,0 10.239895,1.9148 14.030152,5.096847 l 2.185913,-2.605009 C 62.922884,29.024147 57.264963,26.798881 51.095589,26.798881 Z" /><path
id="circle840"
style="color:#000000;fill:#162d50;-inkscape-stroke:none"
d="m 51.095589,33.656344 c -4.49058,0 -8.610793,1.621713 -11.807548,4.308781 l 2.18643,2.605525 c 2.597877,-2.180752 5.951484,-3.492293 9.621118,-3.492293 3.669777,0 7.024322,1.311388 9.622669,3.492293 l 2.185913,-2.605009 C 59.707311,35.278198 55.58652,33.656344 51.095589,33.656344 Z" /></g></svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="l1.svg"
inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8" id="svg8"
version="1.1" version="1.1"
viewBox="0 0 67.733332 33.866668" viewBox="0 0 67.733332 67.733336"
height="128" height="256"
width="256"> width="256">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="15"
inkscape:window-x="1920"
inkscape:window-height="1048"
inkscape:window-width="1918"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="61.042937"
inkscape:cx="87.179174"
inkscape:zoom="2.8"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#000000"
id="base" />
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -49,28 +25,12 @@
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
style="display:inline" id="g2115"
id="layer1" style="display:inline">
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<rect
y="0"
x="0"
height="33.866665"
width="67.73333"
id="rect1406"
style="display:none;fill:#ffffff;stroke:none;stroke-width:6.28119;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" />
<text
id="text1404"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve" />
<path <path
inkscape:connector-curvature="0" id="path2031"
transform="scale(0.26458333)" style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 0,0 V 128 H 256 V 0 Z m 82.337891,31.205078 h 8.820312 V 89.478516 H 120.5293 V 97.625 H 82.337891 Z m 71.402339,0 h 8.82227 V 97.625 h -8.82227 V 43.658203 l -12.03906,12.039063 -6.22656,-6.226563 z" d="M 148.25391,0 0,148.25391 77.041016,225.29492 a 104.83116,104.83117 0 0 0 148.253904,0 104.83116,104.83117 0 0 0 0,-148.253904 z M 96.048828,111.22656 h 10.482422 v 68.65235 h 37.72656 v 8.82226 H 96.048828 Z m 78.980472,0 h 10.48242 v 68.65235 h 17.12305 v 8.82226 h -44.62696 v -8.82226 h 17.125 v -59.10547 l -18.6289,3.73633 v -9.54688 z"
style="display:inline;fill:#ffffff;stroke:none;stroke-width:23.7399;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" transform="scale(0.26458334)" />
id="rect1518" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="l2.svg"
inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8" id="svg8"
version="1.1" version="1.1"
viewBox="0 0 67.733332 33.866668" viewBox="0 0 67.733332 67.733336"
height="128" height="256"
width="256"> width="256">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="15"
inkscape:window-x="1920"
inkscape:window-height="1048"
inkscape:window-width="1918"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="66.072659"
inkscape:cx="112.07001"
inkscape:zoom="2.8"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#000000"
id="base" />
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -49,28 +25,12 @@
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
style="display:inline" id="g2135"
id="layer1" style="display:inline">
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<rect
y="0"
x="0"
height="33.866665"
width="67.73333"
id="rect1406"
style="display:none;fill:#ffffff;stroke:none;stroke-width:6.28119;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" />
<text
id="text1404"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve" />
<path <path
inkscape:connector-curvature="0" id="path2127"
transform="scale(0.26458333)" style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 0,0 V 128 H 256 V 0 Z m 153.74023,30.375 c 5.88107,0 10.53413,1.382832 13.95899,4.150391 3.42485,2.767558 5.13672,6.556599 5.13672,11.365234 0,1.522157 -0.0525,2.697075 -0.15625,3.527344 -0.0692,0.795671 -0.32762,1.88575 -0.77735,3.269531 -0.41513,1.349186 -1.14185,2.83695 -2.17968,4.462891 -1.03784,1.591347 -2.43881,3.492984 -4.20313,5.707031 l -21.27539,26.621094 h 29.42188 V 97.625 h -39.85157 v -8.146484 l 25.32227,-31.705078 c 1.55675,-1.937291 2.6993,-3.632969 3.42578,-5.085938 0.72648,-1.487564 1.14035,-2.611968 1.24414,-3.373047 0.13843,-0.795674 0.20703,-1.936266 0.20703,-3.423828 0,-2.283238 -0.91649,-4.083848 -2.75,-5.398437 -1.83351,-1.314591 -4.34074,-1.970704 -7.52344,-1.970704 -3.18269,0 -5.69188,0.656113 -7.52539,1.970704 -1.8335,1.314589 -2.75,3.115199 -2.75,5.398437 l -8.82031,-1.402344 c 0,-4.358904 1.71187,-7.800773 5.13672,-10.326172 3.42485,-2.525397 8.07792,-3.787109 13.95898,-3.787109 z m -71.402339,0.830078 h 8.820312 V 89.478516 H 120.5293 V 97.625 H 82.337891 Z" d="m 104.51367,0 a 104.83116,104.83117 0 0 0 -73.808592,30.705078 104.83116,104.83117 0 0 0 0,148.253902 L 107.74609,256 256,107.74609 178.95898,30.705078 A 104.83116,104.83117 0 0 0 104.51367,0 Z m 27.39844,57.390625 c 8.02592,0 14.42713,2.00657 19.20117,6.019531 4.77404,4.012961 7.16016,9.374608 7.16016,16.085938 0,3.182692 -0.6056,6.210686 -1.81641,9.082031 -1.17621,2.836747 -3.33823,6.191824 -6.48633,10.066406 -0.86486,1.003241 -3.61434,3.908169 -8.25,8.716799 -4.63566,4.77404 -11.17418,11.468 -19.61523,20.08203 h 36.58398 v 8.82226 h -49.19336 v -8.82226 c 3.97837,-4.11675 9.39248,-9.63383 16.24219,-16.55274 6.8843,-6.95349 11.20834,-11.432968 12.97266,-13.439448 3.35566,-3.770799 5.69127,-6.954243 7.00586,-9.548828 1.34918,-2.629183 2.02343,-5.207025 2.02343,-7.732422 0,-4.116745 -1.45343,-7.471821 -4.35937,-10.066406 -2.87134,-2.594588 -6.62411,-3.892578 -11.25977,-3.892578 -3.28647,-10e-7 -6.76462,0.571271 -10.43164,1.71289 -3.63242,1.141619 -7.52444,2.871623 -11.67578,5.189453 V 62.527344 c 4.22053,-1.695131 8.16502,-2.974982 11.83203,-3.839844 3.66702,-0.864862 7.0221,-1.296875 10.06641,-1.296875 z m -78.978516,1.400391 h 10.482422 v 68.652344 h 37.724604 v 8.82226 H 52.933594 Z"
style="fill:#ffffff;stroke:none;stroke-width:23.7399;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" transform="scale(0.26458334)" />
id="rect1462" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

36
assets/controls/l3.svg Normal file
View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 67.733332 67.733336"
height="256"
width="256">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
id="layer1">
<path
id="path1554"
style="fill:#ffffff;stroke-width:11.0688;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 0,0 -158.50977,385.86328 256,256 C 256,114.61511 141.38489,0 0,0 Z m 127.02734,118.39453 c 7.95674,0 14.25301,1.8168 18.88868,5.44922 4.63566,3.59783 6.95312,8.47499 6.95312,14.63281 0,4.28972 -1.22934,7.92331 -3.68555,10.89844 -2.4562,2.94053 -5.94857,4.98143 -10.48047,6.12305 5.01622,1.07243 8.92441,3.30309 11.72657,6.69336 2.83674,3.39026 4.25586,7.57698 4.25586,12.55859 0,7.64538 -2.63031,13.56016 -7.88867,17.74609 -5.25838,4.18594 -12.72957,6.2793 -22.41602,6.2793 -3.25188,0 -6.60891,-0.32903 -10.06836,-0.98633 -3.42485,-0.6227 -6.9697,-1.57352 -10.63672,-2.85351 v -10.11914 c 2.90594,1.69512 6.08743,2.97498 9.54688,3.83984 3.45945,0.86486 7.0749,1.29689 10.8457,1.29687 6.57295,10e-6 11.57319,-1.29799 14.99805,-3.89257 3.45945,-2.59459 5.1875,-6.36354 5.1875,-11.31055 0,-4.56647 -1.60693,-8.13141 -4.82422,-10.69141 -3.18269,-2.59458 -7.6298,-3.89062 -13.33789,-3.89062 h -9.02735 v -8.61524 h 9.44336 c 5.15458,0 9.09907,-1.01947 11.83203,-3.06054 2.73297,-2.07567 4.09961,-5.0512 4.09961,-8.92578 0,-3.97837 -1.41911,-7.02255 -4.25586,-9.13282 -2.80215,-2.14486 -6.83343,-3.21679 -12.09179,-3.21679 -2.87135,0 -5.94986,0.31089 -9.23633,0.93359 -3.28648,0.6227 -6.90193,1.59166 -10.8457,2.90625 v -9.3418 c 3.97836,-1.10702 7.69875,-1.93672 11.1582,-2.49023 3.49404,-0.55351 6.78046,-0.83008 9.85937,-0.83008 z m -80.224606,1.40039 h 10.482422 v 68.6543 h 37.72461 v 8.82031 H 46.802734 Z"
transform="scale(0.26458334)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 67.733332 67.733336"
height="256"
width="256">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
id="layer1">
<path
id="path1554"
style="fill:#ffffff;stroke-width:2.92862;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 0,0 C 37.408085,0 67.73333,30.325245 67.73333,67.73333 L -41.939116,102.09299 Z" />
<text
id="text1404"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve"
transform="translate(13.609727,26.364208)"><tspan
x="-3.9868231"
y="25.830172"><tspan>L3</tspan></tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

124
assets/controls/lr12.svg Normal file
View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg8"
version="1.1"
viewBox="0 0 67.733332 67.733336"
height="256"
width="256"
sodipodi:docname="lr12.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1916"
inkscape:window-height="1031"
id="namedview1468"
showgrid="false"
inkscape:zoom="0.79980469"
inkscape:cx="101.38147"
inkscape:cy="95.943219"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g2115"
inkscape:label="l1"
style="display:inline">
<path
id="path2031"
style="fill:#ffffff;stroke-width:1.46608;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 39.225383,0 0,39.225383 20.383723,59.609106 a 27.736579,27.73658 0 0 0 39.225787,4.04e-4 27.736579,27.73658 0 0 0 -4.04e-4,-39.225787 z" />
<text
id="text1404-4"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve"
transform="translate(26.639768,24.096855)"><tspan
x="-3.9868231"
y="25.830172"><tspan>L1</tspan></tspan></text>
</g>
<g
id="g2125"
inkscape:label="r1"
style="display:inline">
<path
id="path2117"
style="fill:#ffffff;stroke-width:1.46608;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 28.507951,0 67.733334,39.225383 47.349611,59.609106 a 27.736579,27.73658 0 0 1 -39.2257874,4.04e-4 27.736579,27.73658 0 0 1 4.04e-4,-39.225787 z" />
<text
id="text2123"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve"
transform="translate(13.644494,24.096798)"><tspan
x="-3.9868231"
y="25.830172"><tspan>R1</tspan></tspan></text>
</g>
<g
id="g2135"
inkscape:label="l2"
style="display:inline">
<path
id="path2127"
style="fill:#ffffff;stroke-width:1.46608;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 28.507951,67.733335 67.733334,28.507952 47.349611,8.1242288 a 27.736579,27.73658 0 0 0 -39.2257874,-4.04e-4 27.736579,27.73658 0 0 0 4.04e-4,39.2257872 z" />
<text
id="text2133"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve"
transform="translate(15.231995,10.223453)"><tspan
x="-3.9868231"
y="25.830172"><tspan>L2</tspan></tspan></text>
</g>
<g
id="g2145"
inkscape:label="r2"
style="display:inline"
transform="matrix(-1,0,0,1,67.733334,0)">
<path
id="path2137"
style="fill:#ffffff;stroke-width:1.46608;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 28.507951,67.733335 67.733334,28.507952 47.349611,8.1242288 a 27.736579,27.73658 0 0 0 -39.2257874,-4.04e-4 27.736579,27.73658 0 0 0 4.04e-4,39.2257872 z" />
<text
id="text2143"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve"
transform="matrix(-1,0,0,1,42.347278,10.223453)"><tspan
x="-3.9868231"
y="25.830172"><tspan>R2</tspan></tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="r1.svg"
inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8" id="svg8"
version="1.1" version="1.1"
viewBox="0 0 67.733332 33.866668" viewBox="0 0 67.733332 67.733336"
height="128" height="256"
width="256"> width="256">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="15"
inkscape:window-x="1920"
inkscape:window-height="1048"
inkscape:window-width="1918"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="56.825844"
inkscape:cx="96.071037"
inkscape:zoom="2.8"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#000000"
id="base" />
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -49,28 +25,12 @@
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
style="display:inline" id="g2125"
id="layer1" style="display:inline">
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<rect
y="0"
x="0"
height="33.866665"
width="67.73333"
id="rect1406"
style="display:none;fill:#ffffff;stroke:none;stroke-width:6.28119;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" />
<text
id="text1404"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve" />
<path <path
inkscape:connector-curvature="0" id="path2117"
transform="scale(0.26458333)" style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 0,0 V 128 H 256 V 0 Z M 80.675781,31.205078 H 103.7168 c 5.77728,0 10.15378,1.330365 13.1289,3.994141 3.00972,2.663775 4.51367,7.110882 4.51368,13.33789 -1e-5,8.510244 -3.90821,13.871891 -11.72657,16.085938 L 123.84961,97.625 H 114.25 L 100.60352,65.919922 H 89.498047 V 97.625 h -8.822266 z m 73.064449,0 h 8.82227 V 97.625 h -8.82227 V 43.658203 l -12.03906,12.039063 -6.22656,-6.226563 z m -64.242183,8.146484 v 18.421876 h 8.822265 c 4.635658,0 8.164328,-0.710533 10.585938,-2.128907 2.42161,-1.418373 3.63086,-3.786351 3.63086,-7.107422 0,-3.286475 -0.76105,-5.640217 -2.2832,-7.058593 -1.48757,-1.418374 -3.66577,-2.126954 -6.53711,-2.126954 z" d="M 107.74609,0 30.705078,77.041016 a 104.83116,104.83117 0 0 0 0,148.253904 104.83116,104.83117 0 0 0 148.253902,0 L 256,148.25391 Z M 46.933594,111.22656 h 23.662109 c 8.856189,0 15.463362,1.85113 19.822266,5.55274 4.358906,3.70161 6.539062,9.28735 6.539062,16.75976 0,4.87783 -1.140593,8.92529 -3.423828,12.14258 -2.248642,3.21729 -5.535064,5.44991 -9.859375,6.69531 2.248643,0.76108 4.426845,2.38615 6.53711,4.87696 2.144856,2.4908 4.290687,5.91648 6.435546,10.27539 L 107.2832,188.70117 H 96.023438 l -9.91211,-19.875 C 83.551336,163.637 81.060286,160.19513 78.638672,158.5 c -2.387019,-1.69513 -5.655301,-2.54297 -9.806641,-2.54297 H 57.416016 v 32.74414 H 46.933594 Z m 93.613286,0 h 10.48242 v 68.65235 h 17.12304 v 8.82226 h -44.62695 v -8.82226 h 17.125 v -59.10547 l -18.62891,3.73633 v -9.54688 z m -83.130864,8.61328 v 27.50196 h 13.179687 c 5.050794,0 8.856023,-1.15874 11.416016,-3.47657 2.594587,-2.35242 3.892578,-5.79429 3.892578,-10.32617 0,-4.53188 -1.297991,-7.93942 -3.892578,-10.22265 -2.559993,-2.31783 -6.365222,-3.47657 -11.416016,-3.47657 z"
style="display:inline;fill:#ffffff;stroke:none;stroke-width:23.7399;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" transform="scale(0.26458334)" />
id="rect1556" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

@ -5,37 +5,13 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="r2.svg"
inkscape:version="1.0beta1 (5c3063637d, 2019-10-15)"
id="svg8" id="svg8"
version="1.1" version="1.1"
viewBox="0 0 67.733332 33.866668" viewBox="0 0 67.733332 67.733336"
height="128" height="256"
width="256"> width="256">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="15"
inkscape:window-x="1920"
inkscape:window-height="1048"
inkscape:window-width="1918"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="56.825844"
inkscape:cx="96.071037"
inkscape:zoom="2.8"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#000000"
id="base" />
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -49,28 +25,13 @@
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
id="g2145"
style="display:inline" style="display:inline"
id="layer1" transform="matrix(-1,0,0,1,67.733334,0)">
inkscape:groupmode="layer"
inkscape:label="Ebene 1">
<rect
y="0"
x="0"
height="33.866665"
width="67.73333"
id="rect1406"
style="display:none;fill:#ffffff;stroke:none;stroke-width:6.28119;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" />
<text
id="text1404"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve" />
<path <path
inkscape:connector-curvature="0" id="path2137"
transform="scale(0.26458333)" style="fill:#ffffff;stroke-width:5.54109;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 0,0 V 128 H 256 V 0 Z m 153.74023,30.375 c 5.88107,0 10.53413,1.382832 13.95899,4.150391 3.42485,2.767558 5.13672,6.556599 5.13672,11.365234 0,1.522157 -0.0525,2.697075 -0.15625,3.527344 -0.0692,0.795671 -0.32762,1.88575 -0.77735,3.269531 -0.41513,1.349186 -1.14185,2.83695 -2.17968,4.462891 -1.03784,1.591347 -2.43881,3.492984 -4.20313,5.707031 l -21.27539,26.621094 h 29.42188 V 97.625 h -39.85157 v -8.146484 l 25.32227,-31.705078 c 1.55675,-1.937291 2.6993,-3.632969 3.42578,-5.085938 0.72648,-1.487564 1.14035,-2.611968 1.24414,-3.373047 0.13843,-0.795674 0.20703,-1.936266 0.20703,-3.423828 0,-2.283238 -0.91649,-4.083848 -2.75,-5.398437 -1.83351,-1.314591 -4.34074,-1.970704 -7.52344,-1.970704 -3.18269,0 -5.69188,0.656113 -7.52539,1.970704 -1.8335,1.314589 -2.75,3.115199 -2.75,5.398437 l -8.82031,-1.402344 c 0,-4.358904 1.71187,-7.800773 5.13672,-10.326172 C 143.2061,31.636712 147.85917,30.375 153.74023,30.375 Z M 80.675781,31.205078 H 103.7168 c 5.77728,0 10.15378,1.330365 13.1289,3.994141 3.00972,2.663775 4.51367,7.110882 4.51368,13.33789 -1e-5,8.510244 -3.90821,13.871891 -11.72657,16.085938 L 123.84961,97.625 H 114.25 L 100.60352,65.919922 H 89.498047 V 97.625 h -8.822266 z m 8.822266,8.146484 v 18.421876 h 8.822265 c 4.635658,0 8.164328,-0.710533 10.585938,-2.128907 2.42161,-1.418373 3.63086,-3.786351 3.63086,-7.107422 0,-3.286475 -0.76105,-5.640217 -2.2832,-7.058593 -1.48757,-1.418374 -3.66577,-2.126954 -6.53711,-2.126954 z" d="M 77.041016,30.705078 0,107.74609 148.25391,256 225.29492,178.95898 a 104.83116,104.83117 0 0 0 0,-148.253902 104.83116,104.83117 0 0 0 -148.253904,0 z M 184.92383,57.390625 c 8.02592,0 14.42518,2.00657 19.19922,6.019531 4.77403,4.012961 7.16211,9.374608 7.16211,16.085938 0,3.182692 -0.6056,6.210686 -1.81641,9.082031 -1.17621,2.836747 -3.33823,6.191824 -6.48633,10.066406 -0.86486,1.003241 -3.61629,3.908169 -8.25195,8.716799 -4.63566,4.77404 -11.17223,11.468 -19.61328,20.08203 h 36.58203 v 8.82226 h -49.19336 v -8.82226 c 3.97837,-4.11675 9.39248,-9.63383 16.24219,-16.55274 6.8843,-6.95349 11.20833,-11.432968 12.97265,-13.439448 3.35567,-3.770799 5.69127,-6.954243 7.00586,-9.548828 1.34919,-2.629183 2.02344,-5.207025 2.02344,-7.732422 0,-4.116745 -1.45148,-7.471821 -4.35742,-10.066406 -2.87134,-2.594588 -6.62606,-3.892578 -11.26172,-3.892578 -3.28648,-10e-7 -6.76267,0.571271 -10.42969,1.71289 -3.63242,1.141619 -7.52444,2.871623 -11.67578,5.189453 V 62.527344 c 4.22053,-1.695131 8.16502,-2.974982 11.83203,-3.839844 3.66702,-0.864862 7.02209,-1.296875 10.06641,-1.296875 z m -93.613283,1.400391 h 23.664063 c 8.85619,0 15.46335,1.851125 19.82227,5.552734 4.3589,3.701611 6.5371,9.289311 6.5371,16.761719 0,4.877823 -1.14059,8.92529 -3.42382,12.142578 -2.24865,3.217287 -5.53507,5.447957 -9.85938,6.693359 2.24864,0.761074 4.42685,2.386144 6.53711,4.876954 2.14486,2.4908 4.29069,5.91648 6.43555,10.27539 l 10.63867,21.17187 h -11.26172 l -9.91016,-19.875 c -2.55999,-5.18917 -5.05104,-8.63104 -7.47265,-10.32617 -2.38702,-1.69513 -5.65726,-2.54297 -9.8086,-2.54297 h -11.41601 v 32.74414 H 91.310547 Z m 10.482423,8.615234 v 27.501953 h 13.18164 c 5.05079,0 8.85602,-1.158732 11.41601,-3.476562 2.5946,-2.352424 3.89063,-5.794292 3.89063,-10.326172 0,-4.531878 -1.29603,-7.93942 -3.89063,-10.222657 -2.55999,-2.31783 -6.36522,-3.476562 -11.41601,-3.476562 z"
style="display:inline;fill:#ffffff;stroke:none;stroke-width:23.7399;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:stroke markers fill" transform="matrix(-0.26458334,0,0,0.26458334,67.733334,0)" />
id="rect1556" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

36
assets/controls/r3.svg Normal file
View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 67.733332 67.733336"
height="256"
width="256">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
id="layer1">
<path
id="path1554"
style="fill:#ffffff;stroke-width:11.0688;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 256,0 C 114.61511,0 0,114.61511 0,256 l 414.50977,129.86328 z m -65.89648,118.39453 c 7.95673,0 14.25301,1.8168 18.88867,5.44922 4.63566,3.59783 6.95508,8.47499 6.95508,14.63281 0,4.28972 -1.22934,7.92331 -3.68555,10.89844 -2.45621,2.94053 -5.95055,4.98143 -10.48242,6.12305 5.0162,1.07243 8.92636,3.30309 11.72851,6.69336 2.83675,3.39026 4.25391,7.57698 4.25391,12.55859 0,7.64538 -2.62836,13.56016 -7.88672,17.74609 -5.25836,4.18594 -12.73151,6.2793 -22.41797,6.2793 -3.25188,0 -6.60695,-0.32903 -10.06641,-0.98633 -3.42484,-0.6227 -6.9697,-1.57352 -10.63671,-2.85351 v -10.11914 c 2.90592,1.69512 6.08742,2.97498 9.54687,3.83984 3.45945,0.86486 7.07492,1.29689 10.8457,1.29687 6.57297,2e-5 11.57124,-1.29799 14.9961,-3.89257 3.45945,-2.59459 5.18945,-6.36354 5.18945,-11.31055 0,-4.56647 -1.60888,-8.13141 -4.82617,-10.69141 -3.18269,-2.59458 -7.62785,-3.89062 -13.33594,-3.89062 h -9.0293 v -8.61524 h 9.44532 c 5.15458,0 9.09711,-1.01947 11.83008,-3.06054 2.73296,-2.07567 4.0996,-5.0512 4.0996,-8.92578 2e-5,-3.97838 -1.41715,-7.02255 -4.2539,-9.13282 -2.80215,-2.14485 -6.83343,-3.21679 -12.0918,-3.21679 -2.87134,0 -5.94985,0.31089 -9.23633,0.93359 -3.28647,0.6227 -6.90193,1.59166 -10.8457,2.90625 v -9.3418 c 3.97837,-1.10702 7.6968,-1.93672 11.15625,-2.49023 3.49404,-0.55349 6.78046,-0.83008 9.85938,-0.83008 z m -94.857426,1.40039 H 118.9082 c 8.85619,0 15.46336,1.85113 19.82227,5.55274 4.35891,3.70161 6.53906,9.28931 6.53906,16.76172 0,4.87782 -1.14059,8.92529 -3.42383,12.14257 -2.24864,3.21729 -5.53506,5.44796 -9.85937,6.69336 2.24864,0.76108 4.42684,2.3881 6.53711,4.87891 2.14485,2.4908 4.29069,5.91453 6.43554,10.27344 l 10.63672,21.17187 h -11.25976 l -9.91211,-19.87305 c -2.55999,-5.18916 -5.05104,-8.63299 -7.47266,-10.32812 -2.38702,-1.69513 -5.6553,-2.54102 -9.80664,-2.54102 h -11.41601 v 32.74219 H 95.246094 Z m 10.482426,8.61524 v 27.50195 h 13.17968 c 5.0508,0 8.85603,-1.15873 11.41602,-3.47656 2.59459,-2.35243 3.89258,-5.79429 3.89258,-10.32617 0,-4.53189 -1.29799,-7.93942 -3.89258,-10.22266 -2.55999,-2.31783 -6.36522,-3.47656 -11.41602,-3.47656 z"
transform="scale(0.26458333)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 67.733332 67.733336"
height="256"
width="256">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
id="layer1">
<path
id="path1554"
style="fill:#ffffff;stroke-width:2.92862;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 67.733333,0 C 30.325248,0 3.2333333e-6,30.325245 3.2333333e-6,67.73333 L 109.67245,102.09299 Z" />
<text
id="text1404"
y="25.830172"
x="-3.9868231"
style="font-style:normal;font-weight:normal;font-size:28.1184px;line-height:1.25;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;inline-size:75.2671;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.70296"
xml:space="preserve"
transform="translate(26.427202,26.364208)"><tspan
x="-3.9868231"
y="25.830172"><tspan>R3</tspan></tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1920"
height="942"
viewBox="0 0 507.99999 249.23751"
version="1.1"
id="svg981"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
sodipodi:docname="touchpad_surface.svg">
<defs
id="defs975" />
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.24748737"
inkscape:cx="876.84187"
inkscape:cy="725.61319"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:window-width="1916"
inkscape:window-height="1031"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata978">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#ffffff;stroke-width:1.59637;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
id="rect1544"
width="508"
height="249.2375"
x="-1.7763568e-15"
y="0"
rx="32"
ry="32" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -15,3 +15,4 @@ endif()
add_executable(chiaki-cli src/main.c) add_executable(chiaki-cli src/main.c)
target_link_libraries(chiaki-cli chiaki-cli-lib) target_link_libraries(chiaki-cli chiaki-cli-lib)
install(TARGETS chiaki-cli)

View file

@ -142,13 +142,19 @@ CHIAKI_EXPORT int chiaki_cli_cmd_discover(ChiakiLog *log, int argc, char *argv[]
return 1; return 1;
} }
((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS4); // TODO: IPv6, PS5, should probably use the service
ChiakiDiscoveryPacket packet; ChiakiDiscoveryPacket packet;
memset(&packet, 0, sizeof(packet)); memset(&packet, 0, sizeof(packet));
packet.cmd = CHIAKI_DISCOVERY_CMD_SRCH; packet.cmd = CHIAKI_DISCOVERY_CMD_SRCH;
packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS4;
chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len); ((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS4);
err = chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len);
if(err != CHIAKI_ERR_SUCCESS)
CHIAKI_LOGE(log, "Failed to send discovery packet for PS4: %s", chiaki_error_string(err));
packet.protocol_version = CHIAKI_DISCOVERY_PROTOCOL_VERSION_PS5;
((struct sockaddr_in *)host_addr)->sin_port = htons(CHIAKI_DISCOVERY_PORT_PS5);
err = chiaki_discovery_send(&discovery, &packet, host_addr, host_addr_len);
if(err != CHIAKI_ERR_SUCCESS)
CHIAKI_LOGE(log, "Failed to send discovery packet for PS5: %s", chiaki_error_string(err));
while(1) while(1)
sleep(1); // TODO: wtf sleep(1); // TODO: wtf

View file

@ -11,10 +11,14 @@ static char doc[] = "Send a PS4 wakeup packet.";
#define ARG_KEY_HOST 'h' #define ARG_KEY_HOST 'h'
#define ARG_KEY_REGISTKEY 'r' #define ARG_KEY_REGISTKEY 'r'
#define ARG_KEY_PS4 '4'
#define ARG_KEY_PS5 '5'
static struct argp_option options[] = { static struct argp_option options[] = {
{ "host", ARG_KEY_HOST, "Host", 0, "Host to send wakeup packet to", 0 }, { "host", ARG_KEY_HOST, "Host", 0, "Host to send wakeup packet to", 0 },
{ "registkey", ARG_KEY_REGISTKEY, "RegistKey", 0, "PS4 registration key", 0 }, { "registkey", ARG_KEY_REGISTKEY, "RegistKey", 0, "Remote Play registration key (plaintext)", 0 },
{ "ps4", ARG_KEY_PS4, NULL, 0, "PlayStation 4", 0 },
{ "ps5", ARG_KEY_PS5, NULL, 0, "PlayStation 5 (default)", 0 },
{ 0 } { 0 }
}; };
@ -22,6 +26,7 @@ typedef struct arguments
{ {
const char *host; const char *host;
const char *registkey; const char *registkey;
bool ps5;
} Arguments; } Arguments;
static int parse_opt(int key, char *arg, struct argp_state *state) static int parse_opt(int key, char *arg, struct argp_state *state)
@ -39,6 +44,12 @@ static int parse_opt(int key, char *arg, struct argp_state *state)
case ARGP_KEY_ARG: case ARGP_KEY_ARG:
argp_usage(state); argp_usage(state);
break; break;
case ARG_KEY_PS4:
arguments->ps5 = false;
break;
case ARG_KEY_PS5:
arguments->ps5 = true;
break;
default: default:
return ARGP_ERR_UNKNOWN; return ARGP_ERR_UNKNOWN;
} }
@ -51,6 +62,7 @@ static struct argp argp = { options, parse_opt, 0, doc, 0, 0, 0 };
CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[]) CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[])
{ {
Arguments arguments = { 0 }; Arguments arguments = { 0 };
arguments.ps5 = true;
error_t argp_r = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &arguments); error_t argp_r = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &arguments);
if(argp_r != 0) if(argp_r != 0)
return 1; return 1;
@ -73,5 +85,5 @@ CHIAKI_EXPORT int chiaki_cli_cmd_wakeup(ChiakiLog *log, int argc, char *argv[])
uint64_t credential = (uint64_t)strtoull(arguments.registkey, NULL, 16); uint64_t credential = (uint64_t)strtoull(arguments.registkey, NULL, 16);
return chiaki_discovery_wakeup(log, NULL, arguments.host, credential, false); return chiaki_discovery_wakeup(log, NULL, arguments.host, credential, arguments.ps5);
} }

View file

@ -49,7 +49,11 @@ function (_ffmpeg_find component headername)
# Try pkg-config first # Try pkg-config first
if(PKG_CONFIG_FOUND) if(PKG_CONFIG_FOUND)
pkg_check_modules(FFMPEG_${component} lib${component} IMPORTED_TARGET) if(CMAKE_VERSION VERSION_LESS "3.6")
pkg_check_modules(FFMPEG_${component} lib${component})
else()
pkg_check_modules(FFMPEG_${component} lib${component} IMPORTED_TARGET)
endif()
if(FFMPEG_${component}_FOUND) if(FFMPEG_${component}_FOUND)
if((TARGET PkgConfig::FFMPEG_${component}) AND (NOT CMAKE_VERSION VERSION_LESS "3.11.0")) if((TARGET PkgConfig::FFMPEG_${component}) AND (NOT CMAKE_VERSION VERSION_LESS "3.11.0"))
if(APPLE) if(APPLE)
@ -69,6 +73,9 @@ function (_ffmpeg_find component headername)
add_library(FFMPEG::${component} ALIAS PkgConfig::FFMPEG_${component}) add_library(FFMPEG::${component} ALIAS PkgConfig::FFMPEG_${component})
else() else()
add_library("FFMPEG::${component}" INTERFACE IMPORTED) add_library("FFMPEG::${component}" INTERFACE IMPORTED)
if(CMAKE_VERSION VERSION_LESS "3.6")
link_directories("${FFMPEG_${component}_LIBRARY_DIRS}")
endif()
set_target_properties("FFMPEG::${component}" PROPERTIES set_target_properties("FFMPEG::${component}" PROPERTIES
INTERFACE_LINK_DIRECTORIES "${FFMPEG_${component}_LIBRARY_DIRS}" INTERFACE_LINK_DIRECTORIES "${FFMPEG_${component}_LIBRARY_DIRS}"
INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIRS}" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIRS}"
@ -224,10 +231,14 @@ foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS)
list(APPEND _ffmpeg_required_vars list(APPEND _ffmpeg_required_vars
"FFMPEG_${_ffmpeg_component}_LIBRARIES") "FFMPEG_${_ffmpeg_component}_LIBRARIES")
else() else()
set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS if(NOT FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS)
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}") set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS
set(FFMPEG_${_ffmpeg_component}_LIBRARIES "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}")
"${FFMPEG_${_ffmpeg_component}_LIBRARY}") endif()
if(NOT FFMPEG_${_ffmpeg_component}_LIBRARIES)
set(FFMPEG_${_ffmpeg_component}_LIBRARIES
"${FFMPEG_${_ffmpeg_component}_LIBRARY}")
endif()
list(APPEND FFMPEG_INCLUDE_DIRS list(APPEND FFMPEG_INCLUDE_DIRS
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}") "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}")
list(APPEND FFMPEG_LIBRARIES list(APPEND FFMPEG_LIBRARIES

View file

@ -1,6 +1,26 @@
find_package(SDL2 NO_MODULE QUIET) find_package(SDL2 NO_MODULE QUIET)
# Adapted from libsdl-org/SDL_ttf: https://github.com/libsdl-org/SDL_ttf/blob/main/cmake/FindPrivateSDL2.cmake#L19-L31
# Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
# Licensed under the zlib license (https://github.com/libsdl-org/SDL_ttf/blob/main/LICENSE.txt)
set(SDL2_VERSION_MAJOR)
set(SDL2_VERSION_MINOR)
set(SDL2_VERSION_PATCH)
set(SDL2_VERSION)
if(SDL2_INCLUDE_DIR)
file(READ "${SDL2_INCLUDE_DIR}/SDL_version.h" _sdl_version_h)
string(REGEX MATCH "#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)" _sdl2_major_re "${_sdl_version_h}")
set(SDL2_VERSION_MAJOR "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)" _sdl2_minor_re "${_sdl_version_h}")
set(SDL2_VERSION_MINOR "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)" _sdl2_patch_re "${_sdl_version_h}")
set(SDL2_VERSION_PATCH "${CMAKE_MATCH_1}")
if(_sdl2_major_re AND _sdl2_minor_re AND _sdl2_patch_re)
set(SDL2_VERSION "${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}")
endif()
endif()
if(SDL2_FOUND AND (NOT TARGET SDL2::SDL2)) if(SDL2_FOUND AND (NOT TARGET SDL2::SDL2))
add_library(SDL2::SDL2 UNKNOWN IMPORTED GLOBAL) add_library(SDL2::SDL2 UNKNOWN IMPORTED GLOBAL)
if(NOT SDL2_LIBDIR) if(NOT SDL2_LIBDIR)

View file

@ -33,8 +33,8 @@ endif()
find_program(MAKE_EXE NAMES gmake make) find_program(MAKE_EXE NAMES gmake make)
ExternalProject_Add(OpenSSL-ExternalProject ExternalProject_Add(OpenSSL-ExternalProject
URL https://www.openssl.org/source/openssl-1.1.1d.tar.gz URL https://www.openssl.org/source/openssl-1.1.1s.tar.gz
URL_HASH SHA256=1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2 URL_HASH SHA256=c5ac01e760ee6ff0dab61d6b2bbd30146724d063eb322180c6f18a6f74e4b6aa
INSTALL_DIR "${OPENSSL_INSTALL_DIR}" INSTALL_DIR "${OPENSSL_INSTALL_DIR}"
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${OPENSSL_BUILD_ENV} CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${OPENSSL_BUILD_ENV}
"<SOURCE_DIR>/Configure" "--prefix=<INSTALL_DIR>" no-shared ${OPENSSL_CONFIG_EXTRA_ARGS} "${OPENSSL_OS_COMPILER}" "<SOURCE_DIR>/Configure" "--prefix=<INSTALL_DIR>" no-shared ${OPENSSL_CONFIG_EXTRA_ARGS} "${OPENSSL_OS_COMPILER}"

View file

@ -1,34 +1,15 @@
# Find DEVKITPRO # Find DEVKITPRO
set(DEVKITPRO "$ENV{DEVKITPRO}" CACHE PATH "Path to DevKitPro") set(DEVKITPRO "$ENV{DEVKITPRO}" CACHE PATH "Path to DevKitPro")
set(PORTLIBS_PREFIX "$ENV{PORTLIBS_PREFIX}" CACHE PATH "Path to portlibs inside DevKitPro") if(NOT DEVKITPRO)
if(NOT DEVKITPRO OR NOT PORTLIBS_PREFIX) message(FATAL_ERROR "Please set DEVKITPRO env before calling cmake. https://devkitpro.org/wiki/Getting_Started")
message(FATAL_ERROR "Please set DEVKITPRO & PORTLIBS_PREFIX env before calling cmake. https://devkitpro.org/wiki/Getting_Started")
endif() endif()
# include devkitpro toolchain # include devkitpro toolchain
include("${DEVKITPRO}/switch.cmake") include("${DEVKITPRO}/cmake/Switch.cmake")
set(NSWITCH TRUE) set(NSWITCH TRUE)
# Enable gcc -g, to use
# /opt/devkitpro/devkitA64/bin/aarch64-none-elf-addr2line -e build_switch/switch/chiaki -f -p -C -a 0xCCB5C
# set(CMAKE_BUILD_TYPE Debug)
# set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available" )
# FIXME rework this file to use the toolchain only
# https://github.com/diasurgical/devilutionX/pull/764
set(ARCH "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec")
# set(CMAKE_C_FLAGS "-O2 -ffunction-sections ${ARCH}")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
# workaroud force -fPIE to avoid
# aarch64-none-elf/bin/ld: read-only segment has dynamic relocations
set(CMAKE_EXE_LINKER_FLAGS "-specs=${DEVKITPRO}/libnx/switch.specs ${ARCH} -fPIE -Wl,-Map,Output.map")
# add portlibs to the list of include dir
include_directories("${PORTLIBS_PREFIX}/include")
# troubleshoot # troubleshoot
message(STATUS "CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") message(STATUS "CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}")
message(STATUS "PKG_CONFIG_EXECUTABLE = ${PKG_CONFIG_EXECUTABLE}") message(STATUS "PKG_CONFIG_EXECUTABLE = ${PKG_CONFIG_EXECUTABLE}")
@ -79,4 +60,3 @@ function(add_nro_target output_name target title author version icon romfs)
endfunction() endfunction()
set(CMAKE_USE_SYSTEM_ENVIRONMENT_PATH OFF) set(CMAKE_USE_SYSTEM_ENVIRONMENT_PATH OFF)
set(CMAKE_PREFIX_PATH "/")

View file

@ -6,9 +6,6 @@ find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Concurrent Multimedia Open
if(APPLE) if(APPLE)
find_package(Qt5 REQUIRED COMPONENTS MacExtras) find_package(Qt5 REQUIRED COMPONENTS MacExtras)
endif() endif()
if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
find_package(SDL2 MODULE REQUIRED)
endif()
if(WIN32) if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN) add_definitions(-DWIN32_LEAN_AND_MEAN)
@ -70,12 +67,12 @@ if(CHIAKI_ENABLE_CLI)
endif() endif()
target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg) target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg)
target_link_libraries(chiaki SDL2::SDL2)
if(APPLE) if(APPLE)
target_link_libraries(chiaki Qt5::MacExtras) target_link_libraries(chiaki Qt5::MacExtras)
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_MACEXTRAS) target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_MACEXTRAS)
endif() endif()
if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
target_link_libraries(chiaki SDL2::SDL2)
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
endif() endif()
if(CHIAKI_ENABLE_SETSU) if(CHIAKI_ENABLE_SETSU)

Binary file not shown.

View file

@ -3,6 +3,8 @@
#ifndef CHIAKI_AVOPENGLWIDGET_H #ifndef CHIAKI_AVOPENGLWIDGET_H
#define CHIAKI_AVOPENGLWIDGET_H #define CHIAKI_AVOPENGLWIDGET_H
#include "transformmode.h"
#include <chiaki/log.h> #include <chiaki/log.h>
#include <QOpenGLWidget> #include <QOpenGLWidget>
@ -74,21 +76,24 @@ class AVOpenGLWidget: public QOpenGLWidget
public: public:
static QSurfaceFormat CreateSurfaceFormat(); static QSurfaceFormat CreateSurfaceFormat();
explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr); explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr, TransformMode transform_mode = TransformMode::Fit);
~AVOpenGLWidget() override; ~AVOpenGLWidget() override;
void SwapFrames(); void SwapFrames();
AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; } AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; }
void SetTransformMode(TransformMode mode) { transform_mode = mode; }
TransformMode GetTransformMode() const { return transform_mode; }
protected: protected:
TransformMode transform_mode;
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
void initializeGL() override; void initializeGL() override;
void paintGL() override; void paintGL() override;
private slots:
void ResetMouseTimeout();
public slots: public slots:
void ResetMouseTimeout();
void HideMouse(); void HideMouse();
}; };

View file

@ -12,8 +12,12 @@
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
#include <SDL.h> #include <SDL.h>
#include <chiaki/orientation.h>
#endif #endif
#define PS_TOUCHPAD_MAX_X 1920
#define PS_TOUCHPAD_MAX_Y 1079
class Controller; class Controller;
class ControllerManager : public QObject class ControllerManager : public QObject
@ -33,7 +37,9 @@ class ControllerManager : public QObject
private slots: private slots:
void UpdateAvailableControllers(); void UpdateAvailableControllers();
void HandleEvents(); void HandleEvents();
void ControllerEvent(int device_id); #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
void ControllerEvent(SDL_Event evt);
#endif
public: public:
static ControllerManager *GetInstance(); static ControllerManager *GetInstance();
@ -57,12 +63,24 @@ class Controller : public QObject
private: private:
Controller(int device_id, ControllerManager *manager); Controller(int device_id, ControllerManager *manager);
void UpdateState(); #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
void UpdateState(SDL_Event event);
bool HandleButtonEvent(SDL_ControllerButtonEvent event);
bool HandleAxisEvent(SDL_ControllerAxisEvent event);
#if SDL_VERSION_ATLEAST(2, 0, 14)
bool HandleSensorEvent(SDL_ControllerSensorEvent event);
bool HandleTouchpadEvent(SDL_ControllerTouchpadEvent event);
#endif
#endif
ControllerManager *manager; ControllerManager *manager;
int id; int id;
ChiakiOrientationTracker orientation_tracker;
ChiakiControllerState state;
bool is_dualsense;
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER #ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
QMap<QPair<Sint64, Sint64>, uint8_t> touch_ids;
SDL_GameController *controller; SDL_GameController *controller;
#endif #endif
@ -73,9 +91,44 @@ class Controller : public QObject
int GetDeviceID(); int GetDeviceID();
QString GetName(); QString GetName();
ChiakiControllerState GetState(); ChiakiControllerState GetState();
void SetRumble(uint8_t left, uint8_t right);
void SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right);
bool IsDualSense();
signals: signals:
void StateChanged(); void StateChanged();
}; };
/* PS5 trigger effect documentation:
https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
Taken from SDL2, licensed under the zlib license,
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
https://github.com/libsdl-org/SDL/blob/release-2.24.1/test/testgamecontroller.c#L263-L289
*/
typedef struct
{
Uint8 ucEnableBits1; /* 0 */
Uint8 ucEnableBits2; /* 1 */
Uint8 ucRumbleRight; /* 2 */
Uint8 ucRumbleLeft; /* 3 */
Uint8 ucHeadphoneVolume; /* 4 */
Uint8 ucSpeakerVolume; /* 5 */
Uint8 ucMicrophoneVolume; /* 6 */
Uint8 ucAudioEnableBits; /* 7 */
Uint8 ucMicLightMode; /* 8 */
Uint8 ucAudioMuteBits; /* 9 */
Uint8 rgucRightTriggerEffect[11]; /* 10 */
Uint8 rgucLeftTriggerEffect[11]; /* 21 */
Uint8 rgucUnknown1[6]; /* 32 */
Uint8 ucLedFlags; /* 38 */
Uint8 rgucUnknown2[2]; /* 39 */
Uint8 ucLedAnim; /* 41 */
Uint8 ucLedBrightness; /* 42 */
Uint8 ucPadLights; /* 43 */
Uint8 ucLedRed; /* 44 */
Uint8 ucLedGreen; /* 45 */
Uint8 ucLedBlue; /* 46 */
} DS5EffectsState_t;
#endif // CHIAKI_CONTROLLERMANAGER_H #endif // CHIAKI_CONTROLLERMANAGER_H

View file

@ -55,6 +55,7 @@ class MainWindow : public QMainWindow
void UpdateDiscoveryEnabled(); void UpdateDiscoveryEnabled();
void ShowSettings(); void ShowSettings();
void Quit();
void UpdateDisplayServers(); void UpdateDisplayServers();
void UpdateServerWidgets(); void UpdateServerWidgets();

View file

@ -63,6 +63,9 @@ class Settings : public QObject
void SetLogVerbose(bool enabled) { settings.setValue("settings/log_verbose", enabled); } void SetLogVerbose(bool enabled) { settings.setValue("settings/log_verbose", enabled); }
uint32_t GetLogLevelMask(); uint32_t GetLogLevelMask();
bool GetDualSenseEnabled() const { return settings.value("settings/dualsense_enabled", false).toBool(); }
void SetDualSenseEnabled(bool enabled) { settings.setValue("settings/dualsense_enabled", enabled); }
ChiakiVideoResolutionPreset GetResolution() const; ChiakiVideoResolutionPreset GetResolution() const;
void SetResolution(ChiakiVideoResolutionPreset resolution); void SetResolution(ChiakiVideoResolutionPreset resolution);

View file

@ -20,6 +20,7 @@ class SettingsDialog : public QDialog
QCheckBox *log_verbose_check_box; QCheckBox *log_verbose_check_box;
QComboBox *disconnect_action_combo_box; QComboBox *disconnect_action_combo_box;
QCheckBox *dualsense_check_box;
QComboBox *resolution_combo_box; QComboBox *resolution_combo_box;
QComboBox *fps_combo_box; QComboBox *fps_combo_box;
@ -37,6 +38,7 @@ class SettingsDialog : public QDialog
private slots: private slots:
void LogVerboseChanged(); void LogVerboseChanged();
void DualSenseChanged();
void DisconnectActionSelected(); void DisconnectActionSelected();
void ResolutionSelected(); void ResolutionSelected();

View file

@ -13,12 +13,14 @@
#if CHIAKI_GUI_ENABLE_SETSU #if CHIAKI_GUI_ENABLE_SETSU
#include <setsu.h> #include <setsu.h>
#include <chiaki/orientation.h>
#endif #endif
#include "exception.h" #include "exception.h"
#include "sessionlog.h" #include "sessionlog.h"
#include "controllermanager.h" #include "controllermanager.h"
#include "settings.h" #include "settings.h"
#include "transformmode.h"
#include <QObject> #include <QObject>
#include <QImage> #include <QImage>
@ -52,9 +54,18 @@ struct StreamSessionConnectInfo
ChiakiConnectVideoProfile video_profile; ChiakiConnectVideoProfile video_profile;
unsigned int audio_buffer_size; unsigned int audio_buffer_size;
bool fullscreen; bool fullscreen;
TransformMode transform_mode;
bool enable_keyboard; bool enable_keyboard;
bool enable_dualsense;
StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen); StreamSessionConnectInfo(
Settings *settings,
ChiakiTarget target,
QString host,
QByteArray regist_key,
QByteArray morning,
bool fullscreen,
TransformMode transform_mode);
}; };
class StreamSession : public QObject class StreamSession : public QObject
@ -74,6 +85,9 @@ class StreamSession : public QObject
Setsu *setsu; Setsu *setsu;
QMap<QPair<QString, SetsuTrackingId>, uint8_t> setsu_ids; QMap<QPair<QString, SetsuTrackingId>, uint8_t> setsu_ids;
ChiakiControllerState setsu_state; ChiakiControllerState setsu_state;
SetsuDevice *setsu_motion_device;
ChiakiOrientationTracker orient_tracker;
bool orient_dirty;
#endif #endif
ChiakiControllerState keyboard_state; ChiakiControllerState keyboard_state;
@ -88,17 +102,23 @@ class StreamSession : public QObject
unsigned int audio_buffer_size; unsigned int audio_buffer_size;
QAudioOutput *audio_output; QAudioOutput *audio_output;
QIODevice *audio_io; QIODevice *audio_io;
SDL_AudioDeviceID haptics_output;
uint8_t *haptics_resampler_buf;
QMap<Qt::Key, int> key_map; QMap<Qt::Key, int> key_map;
void PushAudioFrame(int16_t *buf, size_t samples_count); void PushAudioFrame(int16_t *buf, size_t samples_count);
void Event(ChiakiEvent *event); void PushHapticsFrame(uint8_t *buf, size_t buf_size);
#if CHIAKI_GUI_ENABLE_SETSU #if CHIAKI_GUI_ENABLE_SETSU
void HandleSetsuEvent(SetsuEvent *event); void HandleSetsuEvent(SetsuEvent *event);
#endif #endif
private slots: private slots:
void InitAudio(unsigned int channels, unsigned int rate); void InitAudio(unsigned int channels, unsigned int rate);
void InitHaptics();
void Event(ChiakiEvent *event);
void DisconnectHaptics();
void ConnectHaptics();
public: public:
explicit StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent = nullptr); explicit StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent = nullptr);
@ -120,7 +140,7 @@ class StreamSession : public QObject
#endif #endif
void HandleKeyboardEvent(QKeyEvent *event); void HandleKeyboardEvent(QKeyEvent *event);
void HandleMouseEvent(QMouseEvent *event); bool HandleMouseEvent(QMouseEvent *event);
signals: signals:
void FfmpegFrameAvailable(); void FfmpegFrameAvailable();

View file

@ -22,10 +22,14 @@ class StreamWindow: public QMainWindow
const StreamSessionConnectInfo connect_info; const StreamSessionConnectInfo connect_info;
StreamSession *session; StreamSession *session;
QAction *fullscreen_action;
QAction *stretch_action;
QAction *zoom_action;
AVOpenGLWidget *av_widget; AVOpenGLWidget *av_widget;
void Init(); void Init();
void UpdateVideoTransform(); void UpdateVideoTransform();
void UpdateTransformModeActions();
protected: protected:
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
@ -42,6 +46,9 @@ class StreamWindow: public QMainWindow
void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); void SessionQuit(ChiakiQuitReason reason, const QString &reason_str);
void LoginPINRequested(bool incorrect); void LoginPINRequested(bool incorrect);
void ToggleFullscreen(); void ToggleFullscreen();
void ToggleStretch();
void ToggleZoom();
void Quit();
}; };
#endif // CHIAKI_GUI_STREAMWINDOW_H #endif // CHIAKI_GUI_STREAMWINDOW_H

View file

@ -0,0 +1,12 @@
// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_TRANSFORMMODE_H
#define CHIAKI_TRANSFORMMODE_H
enum class TransformMode {
Fit,
Zoom,
Stretch
};
#endif

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