mirror of
https://github.com/2009scape/2009Scape-mobile.git
synced 2025-12-21 09:01:56 -07:00
Resolve merge conflicrts
This commit is contained in:
commit
4551f731d0
165 changed files with 27205 additions and 3911 deletions
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
open_collective: pojavlauncher
|
||||
patreon: pojavlauncher
|
||||
54
.github/ISSUE_TEMPLATE/bug_report.md
vendored
54
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,54 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] <Short description>"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
If you don't fill in this template, this issue will be marked as invalid and closed.
|
||||
-->
|
||||
|
||||
<details> <summary><b>Read before submit</b></summary>
|
||||
<br>
|
||||
- Make sure there was not duplicated issues.<br>
|
||||
- Make sure you have filled this issue template, or this will get rejected.
|
||||
</details>
|
||||
|
||||
### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Add a log file if you want to see your bug fixed !**
|
||||
|
||||
The log file called *lastlog.txt* is located under "*games/PojavLauncher/.minecraft*" .
|
||||
You may need to activate an option in your file explorer to see hidden files and folders.
|
||||
|
||||
Do NOT paste the whole log text ! Instead, add it as a file on this issue.
|
||||
|
||||
### To Reproduce:
|
||||
Indicate steps to reproduce the buggy behavior:
|
||||
|
||||
1. Start PojavLauncher
|
||||
... *(your set of actions to reproduce the bug)*
|
||||
|
||||
### Expected behavior:
|
||||
I expected ...
|
||||
|
||||
### Screenshots or videos:
|
||||
*Upload here screenshots or videos of the buggy behavior, if possible.*
|
||||
|
||||
**Platform:**
|
||||
- Device Model [e.g. Mi 8 Pro 8/128]
|
||||
- CPU architecture [e.g. aarch64]
|
||||
- Android Version [e.g. 10]
|
||||
- PojavLauncher Version [e.g Latest Release || version 3.3.1.1_rel_20210206 ]
|
||||
|
||||
|
||||
<details> <summary><b>Additional context</b></summary>
|
||||
<br>
|
||||
<pre>
|
||||
Add any other context about the problem here.
|
||||
</pre>
|
||||
</details>
|
||||
70
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
70
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
name: Bug report
|
||||
description: ' Create a report to help us improve '
|
||||
title: "[BUG] <Short description>"
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: >-
|
||||
A clear and concise description of what the bug is.
|
||||
placeholder: 'Example: Application exited with code 1'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: The log file and images/videos
|
||||
description: |
|
||||
The log file called `latestlog.txt` is located under `Android/data/net.kdt.pojavlaunch/files`, `games/PojavLauncher/` or `games/PojavLauncher/.minecraft`. You may need to activate an option in your file explorer to see hidden files and folders. |
|
||||
You can also upload here screenshots or videos of the buggy behavior, if possible.
|
||||
|
||||
Tip: You can attach files by clicking this area to highlight it and then dragging files in or select them on 🖼 option at the toolbar.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Start PojavLauncher
|
||||
... *(your set of actions to reproduce the bug)*
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
placeholder: 'Example: I expect the game to launches'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Platform
|
||||
description: |
|
||||
Examples:
|
||||
- **Device model**: Mi 8 Pro 8/128G
|
||||
- **CPU architecture**: aarch64
|
||||
- **Android version**: 10
|
||||
- **PojavLauncher version**: [Latest Release || version 3.3.1.1_rel_20210206 ]
|
||||
value: |
|
||||
- Device model:
|
||||
- CPU architecture:
|
||||
- Android version:
|
||||
- PojavLauncher version:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[F-REQ]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: Feature request
|
||||
description: 'Ask for new features.'
|
||||
title: "[F-Req] <Short description>"
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: Is this request related to a bug?
|
||||
description: |
|
||||
Give us some details into the problem, and how this affects it.
|
||||
placeholder: 'Example: You should add a version selector because of the text box being too small.'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the request.
|
||||
description: |
|
||||
What do you want to see changed, added, or removed? Make sure to be specific.
|
||||
placeholder: 'Example: A scrollable version picker.'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
113
.github/workflows/android.yml
vendored
113
.github/workflows/android.yml
vendored
|
|
@ -12,32 +12,107 @@ on:
|
|||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
env:
|
||||
GPLAY_KEYSTORE_PASSWORD: ${{ secrets.GPLAY_KEYSTORE_PASSWORD }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: PojavLauncherTeam/gl4es
|
||||
path: gl4es
|
||||
fetch-depth: 0
|
||||
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Build latest GL4ES
|
||||
- name: Get gl4es latest commit hash
|
||||
id: gl4es-sha
|
||||
run: echo "::set-output name=sha::$(echo $(git ls-remote https://github.com/PojavLauncherTeam/gl4es refs/heads/master | grep -io '^\S*' && git ls-remote https://github.com/ptitSeb/gl4es refs/heads/master | grep -io '^\S*'))"
|
||||
shell: bash
|
||||
|
||||
- name: Cache gl4es
|
||||
uses: actions/cache@v2
|
||||
id: gl4es-cache
|
||||
with:
|
||||
path: gl4es/libs
|
||||
key: gl4es-android-shared-nodbg-test1-2-${{ steps.gl4es-sha.outputs.sha }}
|
||||
|
||||
- name: Get gl4es
|
||||
if: steps.gl4es-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'ptitSeb/gl4es'
|
||||
path: 'gl4es'
|
||||
|
||||
- name: Build gl4es
|
||||
if: steps.gl4es-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd gl4es
|
||||
git remote add upstream https://github.com/ptitSeb/gl4es
|
||||
git config --global user.email "github-actions@users.noreply.github.com"
|
||||
git config --global user.name "github-actions"
|
||||
git remote add upstream https://github.com/PojavLauncherTeam/gl4es
|
||||
git fetch upstream
|
||||
git checkout master
|
||||
git merge upstream/main || echo "Merge exit code $?"
|
||||
export ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk-bundle"
|
||||
$ANDROID_NDK_HOME/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_DEBUG=1
|
||||
cp -R libs/* ../app_pojavlauncher/src/main/jniLibs/
|
||||
git merge --allow-unrelated-histories upstream/master || echo "Merge exit code $?"
|
||||
$ANDROID_NDK_HOME/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
|
||||
|
||||
- name: Install gl4es
|
||||
if: steps.gl4es-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cp -R gl4es/libs/* app_pojavlauncher/src/main/jniLibs/
|
||||
mv gl4es ..
|
||||
git add .
|
||||
git commit -am "CI: Update gl4es"
|
||||
git push
|
||||
|
||||
- name: Get vgpu latest commit hash
|
||||
id: vgpu-sha
|
||||
run: echo "::set-output name=sha::$(echo $(git ls-remote https://github.com/PojavLauncherTeam/VGPU refs/heads/main | grep -io '^\S*'))"
|
||||
shell: bash
|
||||
|
||||
- name: Cache vgpu
|
||||
uses: actions/cache@v2
|
||||
id: vgpu-cache
|
||||
with:
|
||||
path: vgpu/libs
|
||||
key: vgpu-android-shared-1-${{ steps.vgpu-sha.outputs.sha }}
|
||||
|
||||
- name: Get vgpu
|
||||
if: steps.vgpu-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'PojavLauncherTeam/VGPU'
|
||||
path: 'vgpu'
|
||||
|
||||
- name: Build vgpu
|
||||
if: steps.vgpu-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd vgpu
|
||||
$ANDROID_NDK_HOME/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
|
||||
|
||||
- name: Install vgpu
|
||||
if: steps.vgpu-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cp -R vgpu/libs/* app_pojavlauncher/src/main/jniLibs/
|
||||
mv vgpu ..
|
||||
git config --global user.email "github-actions@users.noreply.github.com"
|
||||
git config --global user.name "github-actions"
|
||||
git add .
|
||||
git commit -am "CI: Update vgpu"
|
||||
git push
|
||||
|
||||
- name: Get JRE8
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
workflow: build.yml
|
||||
path: app_pojavlauncher/src/main/assets/components/jre
|
||||
workflow_conclusion: success
|
||||
repo: PojavLauncherTeam/android-openjdk-build-multiarch
|
||||
branch: buildjre8
|
||||
name: jre8-pojav
|
||||
|
||||
- name: Build APK with Gradle
|
||||
run: |
|
||||
chmod +x scripts/languagelist_updater.sh
|
||||
|
|
@ -46,33 +121,39 @@ jobs:
|
|||
mkdir -p out
|
||||
chmod +x gradlew
|
||||
./gradlew clean
|
||||
|
||||
# Build JRE JAR files (security manager, etc...)
|
||||
./gradlew :jre_lwjgl3glfw:build
|
||||
# mkdir app_pojavlauncher/src/main/assets/components/internal_libs
|
||||
rm app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
|
||||
cp jre_lwjgl3glfw/build/libs/jre_lwjgl3glfw-3.2.3.jar app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
|
||||
|
||||
./gradlew :app_pojavlauncher:bundleGplay
|
||||
mv app_pojavlauncher/build/outputs/bundle/gplay/app_pojavlauncher-gplay.aab out/app-gplay.aab
|
||||
|
||||
# Build the launcher
|
||||
./gradlew :app_pojavlauncher:assembleDebug
|
||||
mv app_pojavlauncher/build/outputs/apk/debug/app_pojavlauncher-debug.apk out/app-debug.apk
|
||||
# mv app_pojavlauncher/build/intermediates/merged_native_libs/debug/out/lib out/debug_lib
|
||||
|
||||
- name: Build APK without runtime
|
||||
run: |
|
||||
rm -r app_pojavlauncher/src/main/assets/components/jre
|
||||
./gradlew assembleDebug
|
||||
mv app_pojavlauncher/build/outputs/apk/debug/app_pojavlauncher-debug.apk out/app-debug-noruntime.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-debug
|
||||
path: out/app-debug.apk
|
||||
|
||||
- name: Upload onto the Google Play testing track
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.GPLAY_SERVICE_JSON }}
|
||||
packageName: net.kdt.pojavlaunch
|
||||
releaseFiles: out/app-gplay.aab
|
||||
track: internal
|
||||
inAppUpdatePriority: 5
|
||||
- name: Upload APK (without runtime)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-debug-noruntime
|
||||
path: out/app-debug-noruntime.apk
|
||||
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,7 @@
|
|||
/.gradle
|
||||
/build
|
||||
/*/build
|
||||
app_pojavlauncher/src/main/assets/components/jre
|
||||
local.properties
|
||||
.idea/
|
||||
app_pojavlauncher/.cxx/
|
||||
|
|
|
|||
6
LICENSE
6
LICENSE
|
|
@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
|
|||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
PojavLauncher - A Minecraft: Java Edition Launcher for Android and iOS
|
||||
Copyright (C) 2021 Tran Hoang Khanh Duy and contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
|||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
PojavLauncher Copyright (C) 2021 Tran Hoang Khanh Duy
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
|
|
|||
37
README.md
37
README.md
|
|
@ -3,21 +3,23 @@
|
|||
[](https://discord.gg/6RpEJda)
|
||||
[](https://reddit.com/r/PojavLauncher)
|
||||
[](https://play.google.com/store/apps/details?id=net.kdt.pojavlaunch)
|
||||
# PojavLauncher
|
||||
|
||||
# PojavLauncher
|
||||
|
||||
## Note
|
||||
We do not exist on TikTok. No one from the dev team makes TikTok videos.
|
||||
- We do not exist on TikTok. No one from the dev team makes TikTok videos.
|
||||
- The official Twitter for PojavLauncher is [@PLaunchTeam](https://twitter.com/PLaunchTeam). Any others (most notably @PojavLauncher) are fake, please report them to Twitter's moderation team.
|
||||
|
||||
## Navigation
|
||||
- [Introduction](#introduction)
|
||||
- [Building](#building)
|
||||
## Navigation
|
||||
- [Introduction](#introduction)
|
||||
- [Building](#building)
|
||||
- [Current status](#current-status)
|
||||
- [License](#license)
|
||||
- [Contributing](#contributing)
|
||||
- [Credits & Third party components and their licenses](#credits--third-party-components-and-their-licenses)
|
||||
|
||||
## Introduction
|
||||
PojavLauncher is a Minecraft: Java Edition launcher for Android based on [Boardwalk](https://github.com/zhuowei/Boardwalk). This launcher can launch almost all available Minecraft versions (from rd-132211 to latest 1.17 snapshot, including Combat Test versions). Modding via Forge and Fabric are also supported.
|
||||
## Introduction
|
||||
PojavLauncher is a Minecraft: Java Edition launcher for Android and iOS based on [Boardwalk](https://github.com/zhuowei/Boardwalk). This launcher can launch almost all available Minecraft versions (from rd-132211 to 21w08b (1.17) snapshot, including Combat Test versions). Modding via Forge and Fabric are also supported. This repository contains source code for Android. For iOS/iPadOS, check out [PojavLauncher_iOS](https://github.com/PojavLauncherTeam/PojavLauncher_iOS).
|
||||
|
||||
## Building
|
||||
To get started, you can just get prebuilt app from [stable release](https://github.com/PojavLauncherTeam/PojavLauncher/releases) or [automatic builds](https://github.com/PojavLauncherTeam/PojavLauncher/actions). If you want to build after launcher code changes, follow steps below.
|
||||
|
|
@ -27,8 +29,8 @@ Will be moved to **BUILDING.md**
|
|||
- JRE for Android is [here](https://github.com/PojavLauncherTeam/openjdk-multiarch-jdk8u), also the build script [here](https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch).
|
||||
- Follow build instruction on build script [README.md](https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch/blob/buildjre8/README.md).
|
||||
- You can also get [CI auto builds](https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch/actions).
|
||||
- Spliting JRE and put to the launcher:
|
||||
- Get JREs for all of 4 supported architectures (arm, arm64, x86, x86_64) </br>
|
||||
- Either get `jre8-pojav` artifact from auto builds, or do splitting by yourself:</br>
|
||||
- Get JREs for all of 4 supported architectures (arm, arm64, x86, x86_64) </br>
|
||||
- Split JRE into parts:</br>
|
||||
Platform-independent: .jar files, libraries, configs, etc...</br>
|
||||
Platform-dependent: .so files, etc...</br>
|
||||
|
|
@ -79,18 +81,20 @@ cp jre_lwjgl3glfw/build/libs/jre_lwjgl3glfw-3.2.3.jar app_pojavlauncher/src/main
|
|||
|
||||
## Known Issues
|
||||
- Minecraft `21w10a` or newer are currently not yet supported due to the new GLSL usage.
|
||||
- in 1.16 and up spawn eggs banners are white (you can fix this by adding this to your JVM flags
|
||||
`-Dorg.lwjgl.opengl.libname=libgl4es_115.so`, only works on 1.16 and up, do not use under this version)
|
||||
- controller mods aren't working
|
||||
- with big modpacks textures could be messed up
|
||||
- if your using gl4es 1.1.5 on 1.16 and lower texture will bug out when hit a mob
|
||||
- probably more, that's why we have a bug tracker ;)
|
||||
- In 1.16 and up spawn eggs banners are white (you can fix this by switching renderer
|
||||
to `gl4es 1.1.5`, only works on 1.16 and up, do not use under this version)
|
||||
- Controller mods aren't working
|
||||
- Random crashes could happen very often on Android 5.x during game load or join world.
|
||||
- With big modpacks textures could be messed up
|
||||
- If you're using gl4es 1.1.5 on 1.16 and lower texture will bug out when hit a mob
|
||||
- probably more, that's why we have a bug tracker ;)
|
||||
|
||||
## License
|
||||
- PojavLauncher is licensed under [GNU GPLv3](https://github.com/khanhduytran0/PojavLauncher/blob/master/LICENSE).
|
||||
|
||||
## Contributing
|
||||
Contributions are welcome! We welcome any type of contribution, not only code. Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it.
|
||||
Contributions are welcome! We welcome any type of contribution, not only code.
|
||||
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it.
|
||||
|
||||
## Credits & Third party components and their licenses
|
||||
- [Boardwalk](https://github.com/zhuowei/Boardwalk) (JVM Launcher): Unknown License/[Apache License 2.0](https://github.com/zhuowei/Boardwalk/blob/master/LICENSE) or GNU GPLv2.
|
||||
|
|
@ -100,4 +104,5 @@ Contributions are welcome! We welcome any type of contribution, not only code. A
|
|||
- [LWJGL3](https://github.com/PojavLauncherTeam/lwjgl3): [BSD-3 License](https://github.com/LWJGL/lwjgl3/blob/master/LICENSE.md).
|
||||
- [LWJGLX](https://github.com/PojavLauncherTeam/lwjglx) (LWJGL2 API compatibility layer for LWJGL3): unknown license.<br>
|
||||
- [pro-grade](https://github.com/pro-grade/pro-grade) (Java sandboxing security manager): [Apache License 2.0](https://github.com/pro-grade/pro-grade/blob/master/LICENSE.txt).
|
||||
- [xHook](https://github.com/iqiyi/xHook) (Used for exit code trapping): [MIT and BSD-style licenses](https://github.com/iqiyi/xHook/blob/master/LICENSE)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,39 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
def getDate() {
|
||||
return new Date().format('yyyyMMdd')
|
||||
def getDate() { return new Date().format('yyyyMMdd')}
|
||||
|
||||
def getDateSeconds() {
|
||||
if(System.getenv("GITHUB_ACTIONS").equals("true")) {
|
||||
return 9934841+Integer.parseInt(System.getenv("GITHUB_RUN_NUMBER"))
|
||||
}else{
|
||||
return 172005
|
||||
}
|
||||
}
|
||||
|
||||
def getVersionName = {
|
||||
// Get the last version tag, as well as the short head of the last commit
|
||||
ByteArrayOutputStream TAG = new ByteArrayOutputStream()
|
||||
ByteArrayOutputStream BRANCH = new ByteArrayOutputStream()
|
||||
exec {
|
||||
try{
|
||||
commandLine 'git', 'describe', '--tags'
|
||||
ignoreExitValue true
|
||||
standardOutput = TAG
|
||||
}catch(Exception e){}
|
||||
}
|
||||
exec{
|
||||
try{
|
||||
commandLine 'git', 'branch', '--show-current'
|
||||
ignoreExitValue true
|
||||
standardOutput = BRANCH
|
||||
}catch(Exception e){}
|
||||
}
|
||||
|
||||
if(TAG.toString() == ""){
|
||||
return ("LOCAL-" + "${getDate()}")
|
||||
}
|
||||
|
||||
return TAG.toString().trim().replace("-g","-") + "-" + BRANCH.toString().trim();
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -21,22 +53,30 @@ android {
|
|||
keyAlias "androiddebugkey"
|
||||
keyPassword "android"
|
||||
}
|
||||
googlePlayBuild {
|
||||
storeFile file("upload.jks")
|
||||
storePassword System.getenv("GPLAY_KEYSTORE_PASSWORD")
|
||||
keyAlias "upload"
|
||||
keyPassword System.getenv("GPLAY_KEYSTORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.kdt.pojavlaunch"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 156247
|
||||
versionName "3.3.1.1_rel_" + getDate()
|
||||
targetSdkVersion 31
|
||||
versionCode getDateSeconds()
|
||||
versionName getVersionName()
|
||||
multiDexEnabled true //important
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix '.debug'
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.customDebug
|
||||
resValue 'string', 'storageProviderAuthorities', 'net.kdt.pojavlaunch.scoped.gamefolder.debug'
|
||||
}
|
||||
|
||||
release {
|
||||
|
|
@ -46,6 +86,11 @@ android {
|
|||
// defaultConfig already set
|
||||
// multiDexEnabled = true
|
||||
// debuggable = true
|
||||
resValue 'string', 'storageProviderAuthorities', 'net.kdt.pojavlaunch.scoped.gamefolder'
|
||||
}
|
||||
gplay {
|
||||
initWith release
|
||||
signingConfig signingConfigs.googlePlayBuild
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +104,11 @@ android {
|
|||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
buildToolsVersion '30.0.2'
|
||||
}
|
||||
|
||||
|
|
@ -68,14 +118,17 @@ dependencies {
|
|||
// implementation 'com.wu-man:android-bsf-api:3.1.3'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.annotation:annotation:1.2.0'
|
||||
implementation 'androidx.browser:browser:1.3.0'
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
|
||||
implementation 'com.rarepebble:colorpicker:3.0.1'
|
||||
implementation 'com.github.duanhong169:checkerboarddrawable:1.0.2'
|
||||
|
||||
|
||||
// implementation 'com.intuit.sdp:sdp-android:1.0.5'
|
||||
// implementation 'com.intuit.ssp:ssp-android:1.0.5'
|
||||
|
|
|
|||
|
|
@ -2,72 +2,88 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.kdt.pojavlaunch">
|
||||
|
||||
<uses-feature android:glEsVersion="0x00020000"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-feature android:glEsVersion="0x00020000"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:name=".PojavApplication"
|
||||
android:theme="@style/AppTheme"
|
||||
android:name=".PojavApplication"
|
||||
android:theme="@style/AppTheme"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:roundIcon="@drawable/ic_launcher"
|
||||
android:resizeableActivity="true"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="ratio_float"/>
|
||||
android:roundIcon="@drawable/ic_launcher"
|
||||
android:resizeableActivity="true"
|
||||
android:hasFragileUserData="true"
|
||||
android:allowNativeHeapPointerTagging="false">
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="ratio_float"/>
|
||||
|
||||
<activity
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:label="@string/app_short_name"
|
||||
android:name=".PojavLoginActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation">
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:label="@string/app_short_name"
|
||||
android:name=".PojavLoginActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="ms-xal-00000000402b5328"
|
||||
android:host="auth"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/MenuDialog"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".FatalErrorActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/MenuDialog"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".FatalErrorActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
<activity
|
||||
android:theme="@style/MenuDialog"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".ExitActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
<activity
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".PojavLauncherActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
|
||||
<activity
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".JavaGUILauncherActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
|
||||
<activity
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".CustomControlsActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
|
||||
<activity
|
||||
android:launchMode="standard"
|
||||
android:multiprocess="true"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".JavaGUILauncherActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
|
||||
<activity
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".CustomControlsActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
<activity
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".authenticator.microsoft.ui.MicrosoftLoginGUIActivity"/>
|
||||
<activity
|
||||
android:launchMode="standard"
|
||||
android:multiprocess="true"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
|
||||
<provider
|
||||
android:name=".scoped.GameFolderProvider"
|
||||
android:authorities="@string/storageProviderAuthorities"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
• OpenJDK: <a href="https://openjdk.java.net/legal/gplv2+ce.html">GNU GPLv2 License</a>.<br>
|
||||
• LWJGL: <a href="http://legacy.lwjgl.org/license.php.html">LWJGL 2's License</a>.<br><br>
|
||||
• pro-gradle: <a href="https://github.com/pro-grade/pro-grade/blob/master/LICENSE.txt">Apache License 2.0</a>.<br>
|
||||
• xHook: <a href="https://github.com/iqiyi/xHook/blob/master/LICENSE">Mixed licenses</a>.<br>
|
||||
• <a href="https://github.com/PojavLauncherTeam/PojavLauncher#credits--third-party-components-and-their-licenses">More (check at GitHub)</a>.
|
||||
• <a href="https://raw.githubusercontent.com/PojavLauncherTeam/PojavLauncher/v3_openjdk/GPLAY_PRIVACY_POLICY">Privacy policy</a>.
|
||||
• Thanks to <a href="https://mc-heads.net">MCHeads</a> for providing Minecraft avatars.
|
||||
|
|
|
|||
BIN
app_pojavlauncher/src/main/assets/assets-v0.zip
Normal file
BIN
app_pojavlauncher/src/main/assets/assets-v0.zip
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
20210220
|
||||
20210430
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
20210125
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
20210131
|
||||
20210827
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ values-el-rGR
|
|||
values-en-rGB
|
||||
values-en-rUS
|
||||
values-es-rES
|
||||
values-fil-rPH
|
||||
values-fi-rFI
|
||||
values-fr-rFR
|
||||
values-hu-rHU
|
||||
|
|
@ -18,6 +19,8 @@ values-iw-rIL
|
|||
values-ja-rJP
|
||||
values-kk-rKZ
|
||||
values-ko-rKR
|
||||
values-la-rLA
|
||||
values-lt-rLT
|
||||
values-nl-rNL
|
||||
values-no-rNO
|
||||
values-pl-rPL
|
||||
|
|
@ -27,7 +30,9 @@ values-ro-rRO
|
|||
values-ru-rRU
|
||||
values-sr-rSP
|
||||
values-sv-rSE
|
||||
values-th-rTH
|
||||
values-tr-rTR
|
||||
values-tt-rRU
|
||||
values-uk-rUA
|
||||
values-vi-rVN
|
||||
values-zh-rCN
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.kdt;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class DefocusableScrollView extends ScrollView {
|
||||
|
||||
/*
|
||||
What is this class for ?
|
||||
It allows to ignore the focusing from an item such an EditText.
|
||||
Ignoring it will stop the scrollView from refocusing on the view
|
||||
*/
|
||||
|
||||
private boolean keepFocusing = false;
|
||||
|
||||
|
||||
public DefocusableScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public DefocusableScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public DefocusableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public DefocusableScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public void setKeepFocusing(boolean shouldKeepFocusing){
|
||||
keepFocusing = shouldKeepFocusing;
|
||||
}
|
||||
|
||||
public boolean isKeepFocusing(){
|
||||
return keepFocusing;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
|
||||
if(!keepFocusing) return 0;
|
||||
return super.computeScrollDeltaToGetChildRectOnScreen(rect);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@ import android.content.*;
|
|||
import android.graphics.*;
|
||||
import android.util.*;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class MineButton extends androidx.appcompat.widget.AppCompatButton
|
||||
{
|
||||
|
||||
|
|
@ -17,7 +21,7 @@ public class MineButton extends androidx.appcompat.widget.AppCompatButton
|
|||
}
|
||||
|
||||
public void init() {
|
||||
setTypeface(Typeface.createFromAsset(getContext().getAssets(), "font/NotoSans-Bold.ttf"));
|
||||
setTypeface(ResourcesCompat.getFont(getContext(), R.font.noto_sans_bold));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import net.kdt.pojavlaunch.utils.*;
|
|||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class AWTCanvasView extends TextureView implements TextureView.SurfaceTextureListener, Runnable {
|
||||
private int mScaleFactor;
|
||||
private int[] mScales;
|
||||
|
||||
private int mWidth, mHeight;
|
||||
private boolean mIsDestroyed = false;
|
||||
|
||||
|
|
@ -32,6 +35,31 @@ public class AWTCanvasView extends TextureView implements TextureView.SurfaceTex
|
|||
}
|
||||
return difference > 0 ? times.size() / difference : 0.0;
|
||||
}
|
||||
|
||||
/** Computes the scale to better fit the screen */
|
||||
void initScaleFactors(){
|
||||
initScaleFactors(0);
|
||||
}
|
||||
|
||||
void initScaleFactors(int forcedScale){
|
||||
//Could be optimized
|
||||
if(forcedScale < 1) { //Auto scale
|
||||
int minDimension = Math.min(CallbackBridge.physicalHeight, CallbackBridge.physicalWidth);
|
||||
mScaleFactor = Math.max(((3 * minDimension) / 1080) - 1, 1);
|
||||
}else{
|
||||
mScaleFactor = forcedScale;
|
||||
}
|
||||
|
||||
int[] scales = new int[2]; //Left, Top
|
||||
|
||||
scales[0] = (CallbackBridge.physicalWidth/2);
|
||||
scales[0] -= scales[0]/mScaleFactor;
|
||||
|
||||
scales[1] = (CallbackBridge.physicalHeight/2);
|
||||
scales[1] -= scales[1]/mScaleFactor;
|
||||
|
||||
mScales = scales;
|
||||
}
|
||||
|
||||
public AWTCanvasView(Context ctx) {
|
||||
this(ctx, null);
|
||||
|
|
@ -46,6 +74,7 @@ public class AWTCanvasView extends TextureView implements TextureView.SurfaceTex
|
|||
fpsPaint.setTextSize(20);
|
||||
|
||||
setSurfaceTextureListener(this);
|
||||
initScaleFactors();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -91,7 +120,15 @@ public class AWTCanvasView extends TextureView implements TextureView.SurfaceTex
|
|||
int[] rgbArray = JREUtils.renderAWTScreenFrame(/* canvas, mWidth, mHeight */);
|
||||
mDrawing = rgbArray != null;
|
||||
if (rgbArray != null) {
|
||||
|
||||
canvas.save();
|
||||
canvas.scale(mScaleFactor, mScaleFactor);
|
||||
canvas.translate(-mScales[0],-mScales[1]);
|
||||
|
||||
|
||||
canvas.drawBitmap(rgbArray, 0, CallbackBridge.physicalWidth, 0, 0, CallbackBridge.physicalWidth, CallbackBridge.physicalHeight, true, null);
|
||||
canvas.restore();
|
||||
|
||||
}
|
||||
rgbArray = null;
|
||||
// System.gc();
|
||||
|
|
|
|||
|
|
@ -1,222 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
|
||||
import java.net.CookieHandler;
|
||||
import java.util.*;
|
||||
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class AndroidLWJGLKeycode {
|
||||
// Fix double letters on MC 1.9 and above
|
||||
public static boolean isBackspaceAfterChar = true;
|
||||
public static final ArrayMap<Integer, Integer> androidToLwjglMap;
|
||||
public static String[] androidKeyNameArray;
|
||||
|
||||
static {
|
||||
// Mapping Android Keycodes to LWJGL Keycodes
|
||||
androidToLwjglMap = new ArrayMap<Integer, Integer>();
|
||||
|
||||
// 0-9 keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_0, LWJGLGLFWKeycode.GLFW_KEY_0);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_9, LWJGLGLFWKeycode.GLFW_KEY_9);
|
||||
|
||||
// A-Z keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_A, LWJGLGLFWKeycode.GLFW_KEY_A);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_B, LWJGLGLFWKeycode.GLFW_KEY_B);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_C, LWJGLGLFWKeycode.GLFW_KEY_C);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_D, LWJGLGLFWKeycode.GLFW_KEY_D);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_E, LWJGLGLFWKeycode.GLFW_KEY_E);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F, LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_G, LWJGLGLFWKeycode.GLFW_KEY_G);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_H, LWJGLGLFWKeycode.GLFW_KEY_H);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_I, LWJGLGLFWKeycode.GLFW_KEY_I);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_J, LWJGLGLFWKeycode.GLFW_KEY_J);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_K, LWJGLGLFWKeycode.GLFW_KEY_K);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_L, LWJGLGLFWKeycode.GLFW_KEY_L);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_M, LWJGLGLFWKeycode.GLFW_KEY_M);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_N, LWJGLGLFWKeycode.GLFW_KEY_N);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_O, LWJGLGLFWKeycode.GLFW_KEY_O);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_P, LWJGLGLFWKeycode.GLFW_KEY_P);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_Q, LWJGLGLFWKeycode.GLFW_KEY_Q);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_R, LWJGLGLFWKeycode.GLFW_KEY_R);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_S, LWJGLGLFWKeycode.GLFW_KEY_S);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_T, LWJGLGLFWKeycode.GLFW_KEY_T);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_U, LWJGLGLFWKeycode.GLFW_KEY_U);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_V, LWJGLGLFWKeycode.GLFW_KEY_V);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_W, LWJGLGLFWKeycode.GLFW_KEY_W);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_X, LWJGLGLFWKeycode.GLFW_KEY_X);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_Y, LWJGLGLFWKeycode.GLFW_KEY_Y);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_Z, LWJGLGLFWKeycode.GLFW_KEY_Z);
|
||||
|
||||
// Alt keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ALT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ALT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_ALT);
|
||||
|
||||
// Escape key
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_BACK, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_BACKSLASH, LWJGLGLFWKeycode.GLFW_KEY_BACKSLASH);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_BREAK, LWJGLGLFWKeycode.GLFW_KEY_PAUSE);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_CAPS_LOCK, LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
|
||||
// Control keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_CTRL_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_CTRL_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_CONTROL);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DEL, LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE); // Backspace
|
||||
|
||||
// Arrow keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_DOWN, LWJGLGLFWKeycode.GLFW_KEY_DOWN);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_UP, LWJGLGLFWKeycode.GLFW_KEY_UP);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ESCAPE, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
// Fn keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F1, LWJGLGLFWKeycode.GLFW_KEY_F1);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F2, LWJGLGLFWKeycode.GLFW_KEY_F2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F3, LWJGLGLFWKeycode.GLFW_KEY_F3);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F4, LWJGLGLFWKeycode.GLFW_KEY_F4);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F5, LWJGLGLFWKeycode.GLFW_KEY_F5);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F6, LWJGLGLFWKeycode.GLFW_KEY_F6);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F7, LWJGLGLFWKeycode.GLFW_KEY_F7);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F8, LWJGLGLFWKeycode.GLFW_KEY_F8);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F9, LWJGLGLFWKeycode.GLFW_KEY_F9);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F10, LWJGLGLFWKeycode.GLFW_KEY_F10);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F11, LWJGLGLFWKeycode.GLFW_KEY_F11);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F12, LWJGLGLFWKeycode.GLFW_KEY_F12);
|
||||
// FIXME GLFW Function key
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_FUNCTION, LWJGLGLFWKeycode.GLFW_KEY_FUNCTION);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_GRAVE, LWJGLGLFWKeycode.GLFW_KEY_GRAVE_ACCENT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_HOME, LWJGLGLFWKeycode.GLFW_KEY_HOME);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_INSERT, LWJGLGLFWKeycode.GLFW_KEY_INSERT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_KANA, LWJGLGLFWKeycode.GLFW_KEY_K);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_LEFT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_LEFT_BRACKET);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_MINUS, LWJGLGLFWKeycode.GLFW_KEY_MINUS);
|
||||
|
||||
// Num keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUM_LOCK, LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_0, LWJGLGLFWKeycode.GLFW_KEY_0);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_9, LWJGLGLFWKeycode.GLFW_KEY_9);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_ADD, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LWJGLGLFWKeycode.GLFW_KEY_KP_DIVIDE);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_DOT, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LWJGLGLFWKeycode.GLFW_KEY_KP_MULTIPLY);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LWJGLGLFWKeycode.GLFW_KEY_KP_SUBTRACT);
|
||||
|
||||
// Page keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PAGE_DOWN, LWJGLGLFWKeycode.GLFW_KEY_PAGE_DOWN);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PAGE_UP, LWJGLGLFWKeycode.GLFW_KEY_PAGE_UP);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PERIOD, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PLUS, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_POWER, LWJGLGLFWKeycode.GLFW_KEY_POWER);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_RIGHT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_BRACKET);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SEMICOLON, LWJGLGLFWKeycode.GLFW_KEY_SEMICOLON);
|
||||
|
||||
// Shift keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SHIFT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SHIFT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_SHIFT);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SLASH, LWJGLGLFWKeycode.GLFW_KEY_SLASH);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_SLEEP, LWJGLGLFWKeycode.GLFW_KEY_SLEEP);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SPACE, LWJGLGLFWKeycode.GLFW_KEY_SPACE);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_SYSRQ, LWJGLGLFWKeycode.GLFW_KEY_SYSRQ);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_TAB, LWJGLGLFWKeycode.GLFW_KEY_TAB);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_YEN, LWJGLGLFWKeycode.GLFW_KEY_YEN);
|
||||
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_BUTTON_1, LWJGLGLFWKeycode.G
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_AT,LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_POUND,LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
}
|
||||
|
||||
public static String[] generateKeyName() {
|
||||
if (androidKeyNameArray == null) {
|
||||
List<String> keyName = new ArrayList<String>();
|
||||
for (Integer perKey : androidToLwjglMap.keySet()) {
|
||||
keyName.add(KeyEvent.keyCodeToString(perKey.intValue()).replace("KEYCODE_", ""));
|
||||
}
|
||||
androidKeyNameArray = keyName.toArray(new String[0]);
|
||||
}
|
||||
return androidKeyNameArray;
|
||||
}
|
||||
|
||||
public static void execKey(KeyEvent keyEvent, int i, boolean isDown) {
|
||||
CallbackBridge.holdingAlt = keyEvent.isAltPressed();
|
||||
CallbackBridge.holdingCapslock = keyEvent.isCapsLockOn();
|
||||
CallbackBridge.holdingCtrl = keyEvent.isCtrlPressed();
|
||||
CallbackBridge.holdingNumlock = keyEvent.isNumLockOn();
|
||||
CallbackBridge.holdingShift = keyEvent.isShiftPressed();
|
||||
|
||||
try {
|
||||
System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel());
|
||||
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK && LauncherPreferences.PREF_BACK_TO_RIGHT_MOUSE) {
|
||||
BaseMainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, keyEvent.getAction() == KeyEvent.ACTION_DOWN);
|
||||
} else {
|
||||
if(keyEvent.getUnicodeChar() != 0) {
|
||||
char key = (char)keyEvent.getUnicodeChar();
|
||||
BaseMainActivity.sendKeyPress(
|
||||
androidToLwjglMap.get(keyEvent.getKeyCode()),
|
||||
key,
|
||||
0,
|
||||
CallbackBridge.getCurrentMods(),
|
||||
keyEvent.getAction() == KeyEvent.ACTION_DOWN);
|
||||
}else{
|
||||
BaseMainActivity.sendKeyPress(
|
||||
androidToLwjglMap.get(keyEvent.getKeyCode()),
|
||||
CallbackBridge.getCurrentMods(),
|
||||
keyEvent.getAction()==KeyEvent.ACTION_DOWN);
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void execKeyIndex(BaseMainActivity mainActivity, int index) {
|
||||
mainActivity.sendKeyPress(getKeyByIndex(index));
|
||||
|
||||
}
|
||||
|
||||
public static int getKeyByIndex(int index) {
|
||||
return androidToLwjglMap.valueAt(index);
|
||||
}
|
||||
|
||||
public static int getIndexByLWJGLKey(int lwjglKey) {
|
||||
for (int i = 0; i < androidToLwjglMap.size(); i++) {
|
||||
int currKey = androidToLwjglMap.valueAt(i);
|
||||
if (currKey == lwjglKey) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class aims at providing a simple and easy way to deal with the device architecture.
|
||||
*/
|
||||
public class Architecture {
|
||||
public static int UNSUPPORTED_ARCH = -1;
|
||||
public static int ARCH_ARM64 = 0x1;
|
||||
public static int ARCH_ARM = 0x2;
|
||||
public static int ARCH_X86 = 0x4;
|
||||
public static int ARCH_X86_64 = 0x8;
|
||||
|
||||
/**
|
||||
* Tell us if the device supports 64 bits architecture
|
||||
* @return If the device supports 64 bits architecture
|
||||
*/
|
||||
public static boolean is64BitsDevice(){
|
||||
return Build.SUPPORTED_64_BIT_ABIS.length != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell us if the device supports 32 bits architecture
|
||||
* Note, that a 64 bits device won't be reported as supporting 32 bits.
|
||||
* @return If the device supports 32 bits architecture
|
||||
*/
|
||||
public static boolean is32BitsDevice(){
|
||||
return !is64BitsDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the device supported architecture.
|
||||
* Since mips(/64) has been phased out long ago, is isn't checked here.
|
||||
*
|
||||
* @return ARCH_ARM || ARCH_ARM64 || ARCH_X86 || ARCH_86_64
|
||||
*/
|
||||
public static int getDeviceArchitecture(){
|
||||
if(isx86Device()){
|
||||
return is64BitsDevice() ? ARCH_X86_64 : ARCH_X86;
|
||||
}
|
||||
return is64BitsDevice() ? ARCH_ARM64 : ARCH_ARM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell is the device is based on an x86 processor.
|
||||
* It doesn't tell if the device is 64 or 32 bits.
|
||||
* @return Whether or not the device is x86 based.
|
||||
*/
|
||||
public static boolean isx86Device(){
|
||||
//We check the whole range of supported ABIs,
|
||||
//Since asus zenfones can place arm before their native instruction set.
|
||||
String[] ABI = is64BitsDevice() ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS;
|
||||
int comparedArch = is64BitsDevice() ? ARCH_X86_64 : ARCH_X86;
|
||||
for (String str : ABI) {
|
||||
if (archAsInt(str) == comparedArch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell is the device is based on an arm processor.
|
||||
* It doesn't tell if the device is 64 or 32 bits.
|
||||
* @return Whether or not the device is arm based.
|
||||
*/
|
||||
public static boolean isArmDevice(){
|
||||
return !isx86Device();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an architecture from a String to an int.
|
||||
* @param arch The architecture as a String
|
||||
* @return The architecture as an int, can be UNSUPPORTED_ARCH if unknown.
|
||||
*/
|
||||
public static int archAsInt(String arch){
|
||||
arch = arch.toLowerCase().trim().replace(" ", "");
|
||||
if(arch.contains("arm64") || arch.equals("aarch64")) return ARCH_ARM64;
|
||||
if(arch.contains("arm") || arch.equals("aarch32")) return ARCH_ARM;
|
||||
if(arch.contains("x86_64") || arch.contains("amd64")) return ARCH_X86_64;
|
||||
if(arch.contains("x86") || (arch.startsWith("i") && arch.endsWith("86"))) return ARCH_X86;
|
||||
//Shouldn't happen
|
||||
return UNSUPPORTED_ARCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a string an architecture.
|
||||
* @param arch The architecture as an int.
|
||||
* @return "arm64" || "arm" || "x86_64" || "x86" || "UNSUPPORTED_ARCH"
|
||||
*/
|
||||
public static String archAsString(int arch){
|
||||
if(arch == ARCH_ARM64) return "arm64";
|
||||
if(arch == ARCH_ARM) return "arm";
|
||||
if(arch == ARCH_X86_64) return "x86_64";
|
||||
if(arch == ARCH_X86) return "x86";
|
||||
return "UNSUPPORTED_ARCH";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
public class ArchitectureCheck
|
||||
{
|
||||
public static String getArch() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
String archConverted = null;
|
||||
|
||||
if (arch.equals("aarch64") ||
|
||||
arch.endsWith("v8a") ||
|
||||
arch.startsWith("arm64")) {
|
||||
archConverted = "arm64";
|
||||
} else if (arch.startsWith("arm") || arch.endsWith("v7a")) {
|
||||
archConverted = "arm32";
|
||||
} else if (arch.startsWith("x86") || arch.startsWith("amd")) {
|
||||
archConverted = "x86";
|
||||
}
|
||||
|
||||
return archConverted;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,26 +2,37 @@ package net.kdt.pojavlaunch;
|
|||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.*;
|
||||
import android.text.method.*;
|
||||
import android.view.*;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.*;
|
||||
import com.kdt.pickafile.*;
|
||||
import java.io.*;
|
||||
import net.kdt.pojavlaunch.fragments.*;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.tasks.*;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public abstract class BaseLauncherActivity extends BaseActivity {
|
||||
public Button mPlayButton;
|
||||
public ConsoleFragment mConsoleView;
|
||||
public CrashFragment mCrashView;
|
||||
public ProgressBar mLaunchProgress;
|
||||
public Spinner mVersionSelector;
|
||||
public MultiRTConfigDialog mRuntimeConfigDialog;
|
||||
public TextView mLaunchTextStatus, mTextVersion;
|
||||
|
||||
public JMinecraftVersionList mVersionList;
|
||||
|
|
@ -80,14 +91,13 @@ public abstract class BaseLauncherActivity extends BaseActivity {
|
|||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
public static final int RUN_MOD_INSTALLER = 2050;
|
||||
private void installMod(boolean customJavaArgs) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.alerttitle_installmod);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
final AlertDialog dialog;
|
||||
if (customJavaArgs) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.alerttitle_installmod);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
final AlertDialog dialog;
|
||||
final EditText edit = new EditText(this);
|
||||
edit.setSingleLine();
|
||||
edit.setHint("-jar/-cp /path/to/file.jar ...");
|
||||
|
|
@ -102,22 +112,16 @@ public abstract class BaseLauncherActivity extends BaseActivity {
|
|||
});
|
||||
dialog = builder.create();
|
||||
dialog.setView(edit);
|
||||
dialog.show();
|
||||
} else {
|
||||
dialog = builder.create();
|
||||
FileListView flv = new FileListView(dialog,"jar");
|
||||
flv.setFileSelectedListener(new FileSelectedListener(){
|
||||
@Override
|
||||
public void onFileSelected(File file, String path) {
|
||||
Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class);
|
||||
intent.putExtra("modFile", file);
|
||||
startActivity(intent);
|
||||
dialog.dismiss();
|
||||
|
||||
}
|
||||
});
|
||||
dialog.setView(flv);
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jar");
|
||||
if(mimeType == null) mimeType = "*/*";
|
||||
intent.setType(mimeType);
|
||||
startActivityForResult(intent,RUN_MOD_INSTALLER);
|
||||
}
|
||||
dialog.show();
|
||||
|
||||
}
|
||||
|
||||
public void launchGame(View v) {
|
||||
|
|
@ -171,10 +175,12 @@ public abstract class BaseLauncherActivity extends BaseActivity {
|
|||
}
|
||||
}
|
||||
};
|
||||
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener(listRefreshListener);
|
||||
}
|
||||
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener(listRefreshListener);
|
||||
new RefreshVersionListTask(this).execute();
|
||||
System.out.println("call to onResumeFragments");
|
||||
mRuntimeConfigDialog = new MultiRTConfigDialog();
|
||||
mRuntimeConfigDialog.prepare(this);
|
||||
try{
|
||||
final ProgressDialog barrier = new ProgressDialog(this);
|
||||
barrier.setMessage(getString(R.string.global_waiting));
|
||||
|
|
@ -224,7 +230,88 @@ public abstract class BaseLauncherActivity extends BaseActivity {
|
|||
}
|
||||
System.out.println("call to onResumeFragments; E");
|
||||
}
|
||||
|
||||
public static String getFileName(Context ctx, Uri uri) {
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.getPath();
|
||||
int cut = result.lastIndexOf('/');
|
||||
if (cut != -1) {
|
||||
result = result.substring(cut + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode,resultCode,data);
|
||||
if(resultCode == Activity.RESULT_OK) {
|
||||
final ProgressDialog barrier = new ProgressDialog(this);
|
||||
barrier.setMessage(getString(R.string.global_waiting));
|
||||
barrier.setProgressStyle(barrier.STYLE_SPINNER);
|
||||
barrier.setCancelable(false);
|
||||
barrier.show();
|
||||
if (requestCode == MultiRTConfigDialog.MULTIRT_PICK_RUNTIME) {
|
||||
if (data != null) {
|
||||
final Uri uri = data.getData();
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
String name = getFileName(this, uri);
|
||||
MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), name,
|
||||
(resid, stuff) -> BaseLauncherActivity.this.runOnUiThread(
|
||||
() -> barrier.setMessage(BaseLauncherActivity.this.getString(resid, stuff))));
|
||||
MultiRTUtils.postPrepare(BaseLauncherActivity.this, name);
|
||||
} catch (IOException e) {
|
||||
Tools.showError(BaseLauncherActivity.this
|
||||
, e);
|
||||
}
|
||||
BaseLauncherActivity.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
barrier.dismiss();
|
||||
mRuntimeConfigDialog.refresh();
|
||||
mRuntimeConfigDialog.dialog.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
} else if (requestCode == RUN_MOD_INSTALLER) {
|
||||
if (data != null) {
|
||||
final Uri uri = data.getData();
|
||||
barrier.setMessage(BaseLauncherActivity.this.getString(R.string.multirt_progress_caching));
|
||||
Thread t = new Thread(()->{
|
||||
try {
|
||||
final String name = getFileName(this, uri);
|
||||
final File modInstallerFile = new File(getCacheDir(), name);
|
||||
FileOutputStream fos = new FileOutputStream(modInstallerFile);
|
||||
IOUtils.copy(getContentResolver().openInputStream(uri), fos);
|
||||
fos.close();
|
||||
BaseLauncherActivity.this.runOnUiThread(() -> {
|
||||
barrier.dismiss();
|
||||
Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class);
|
||||
intent.putExtra("modFile", modInstallerFile);
|
||||
startActivity(intent);
|
||||
});
|
||||
}catch(IOException e) {
|
||||
Tools.showError(BaseLauncherActivity.this,e);
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Catching touch exception
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,7 +60,10 @@ public class CustomControlsActivity extends BaseActivity
|
|||
load(ctrlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_add:
|
||||
ctrlLayout.addControlButton(new ControlData("New", LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN, 100, 100));
|
||||
ctrlLayout.addControlButton(new ControlData("New"));
|
||||
break;
|
||||
case R.id.menu_ctrl_add_drawer:
|
||||
ctrlLayout.addDrawer(new ControlDrawerData());
|
||||
break;
|
||||
case R.id.menu_ctrl_selectdefault:
|
||||
dialogSelectDefaultCtrl(ctrlLayout);
|
||||
|
|
@ -150,6 +153,7 @@ public class CustomControlsActivity extends BaseActivity
|
|||
if(ctx instanceof MainActivity) {
|
||||
((MainActivity) ctx).leaveCustomControls();
|
||||
}else{
|
||||
((CustomControlsActivity) ctx).isModified = false;
|
||||
((Activity)ctx).onBackPressed();
|
||||
}
|
||||
// setResult(Activity.RESULT_OK, new Intent());
|
||||
|
|
@ -198,13 +202,19 @@ public class CustomControlsActivity extends BaseActivity
|
|||
}
|
||||
|
||||
public static void load(final ControlLayout layout) {
|
||||
/*ControlJsonSelector sel = new ControlJsonSelector(layout.getContext(), R.string.global_load);
|
||||
sel.setFinishCallback((f)->{
|
||||
loadControl(f.getAbsolutePath(),layout);
|
||||
});
|
||||
sel.show();*/
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(layout.getContext());
|
||||
builder.setTitle(R.string.global_load);
|
||||
builder.setPositiveButton(android.R.string.cancel, null);
|
||||
|
||||
final AlertDialog dialog = builder.create();
|
||||
FileListView flv = new FileListView(dialog, "json");
|
||||
flv.listFileAt(Tools.CTRLMAP_PATH);
|
||||
if(Build.VERSION.SDK_INT < 29)flv.listFileAt(Tools.CTRLMAP_PATH);
|
||||
else flv.lockPathAt(Tools.CTRLMAP_PATH);
|
||||
flv.setFileSelectedListener(new FileSelectedListener(){
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EfficientAndroidLWJGLKeycode {
|
||||
|
||||
//This old version of this class was using an ArrayMap, a generic Key -> Value data structure.
|
||||
//The key being the android keycode from a KeyEvent
|
||||
//The value its LWJGL equivalent.
|
||||
private static final int KEYCODE_COUNT = 103;
|
||||
private static final int[] androidKeycodes = new int[KEYCODE_COUNT];
|
||||
private static final short[] LWJGLKeycodes = new short[KEYCODE_COUNT];
|
||||
private static String[] androidKeyNameArray; /* = new String[androidKeycodes.length]; */
|
||||
|
||||
static {
|
||||
|
||||
/* BINARY SEARCH IS PERFORMED ON THE androidKeycodes ARRAY !
|
||||
WHEN ADDING A MAPPING, ADD IT SO THE androidKeycodes ARRAY STAYS SORTED ! */
|
||||
// Mapping Android Keycodes to LWJGL Keycodes
|
||||
add(KeyEvent.KEYCODE_UNKNOWN,LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN);
|
||||
add(KeyEvent.KEYCODE_HOME, LWJGLGLFWKeycode.GLFW_KEY_HOME);
|
||||
// Escape key
|
||||
add(KeyEvent.KEYCODE_BACK, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
// 0-9 keys
|
||||
add(KeyEvent.KEYCODE_0, LWJGLGLFWKeycode.GLFW_KEY_0); //7
|
||||
add(KeyEvent.KEYCODE_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
add(KeyEvent.KEYCODE_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
add(KeyEvent.KEYCODE_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
add(KeyEvent.KEYCODE_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
add(KeyEvent.KEYCODE_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
add(KeyEvent.KEYCODE_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
add(KeyEvent.KEYCODE_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
add(KeyEvent.KEYCODE_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
add(KeyEvent.KEYCODE_9, LWJGLGLFWKeycode.GLFW_KEY_9); //16
|
||||
|
||||
add(KeyEvent.KEYCODE_POUND,LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
|
||||
// Arrow keys
|
||||
add(KeyEvent.KEYCODE_DPAD_UP, LWJGLGLFWKeycode.GLFW_KEY_UP); //19
|
||||
add(KeyEvent.KEYCODE_DPAD_DOWN, LWJGLGLFWKeycode.GLFW_KEY_DOWN);
|
||||
add(KeyEvent.KEYCODE_DPAD_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT);
|
||||
add(KeyEvent.KEYCODE_DPAD_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT); //22
|
||||
|
||||
// A-Z keys
|
||||
add(KeyEvent.KEYCODE_A, LWJGLGLFWKeycode.GLFW_KEY_A); //29
|
||||
add(KeyEvent.KEYCODE_B, LWJGLGLFWKeycode.GLFW_KEY_B);
|
||||
add(KeyEvent.KEYCODE_C, LWJGLGLFWKeycode.GLFW_KEY_C);
|
||||
add(KeyEvent.KEYCODE_D, LWJGLGLFWKeycode.GLFW_KEY_D);
|
||||
add(KeyEvent.KEYCODE_E, LWJGLGLFWKeycode.GLFW_KEY_E);
|
||||
add(KeyEvent.KEYCODE_F, LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
add(KeyEvent.KEYCODE_G, LWJGLGLFWKeycode.GLFW_KEY_G);
|
||||
add(KeyEvent.KEYCODE_H, LWJGLGLFWKeycode.GLFW_KEY_H);
|
||||
add(KeyEvent.KEYCODE_I, LWJGLGLFWKeycode.GLFW_KEY_I);
|
||||
add(KeyEvent.KEYCODE_J, LWJGLGLFWKeycode.GLFW_KEY_J);
|
||||
add(KeyEvent.KEYCODE_K, LWJGLGLFWKeycode.GLFW_KEY_K);
|
||||
add(KeyEvent.KEYCODE_L, LWJGLGLFWKeycode.GLFW_KEY_L);
|
||||
add(KeyEvent.KEYCODE_M, LWJGLGLFWKeycode.GLFW_KEY_M);
|
||||
add(KeyEvent.KEYCODE_N, LWJGLGLFWKeycode.GLFW_KEY_N);
|
||||
add(KeyEvent.KEYCODE_O, LWJGLGLFWKeycode.GLFW_KEY_O);
|
||||
add(KeyEvent.KEYCODE_P, LWJGLGLFWKeycode.GLFW_KEY_P);
|
||||
add(KeyEvent.KEYCODE_Q, LWJGLGLFWKeycode.GLFW_KEY_Q);
|
||||
add(KeyEvent.KEYCODE_R, LWJGLGLFWKeycode.GLFW_KEY_R);
|
||||
add(KeyEvent.KEYCODE_S, LWJGLGLFWKeycode.GLFW_KEY_S);
|
||||
add(KeyEvent.KEYCODE_T, LWJGLGLFWKeycode.GLFW_KEY_T);
|
||||
add(KeyEvent.KEYCODE_U, LWJGLGLFWKeycode.GLFW_KEY_U);
|
||||
add(KeyEvent.KEYCODE_V, LWJGLGLFWKeycode.GLFW_KEY_V);
|
||||
add(KeyEvent.KEYCODE_W, LWJGLGLFWKeycode.GLFW_KEY_W);
|
||||
add(KeyEvent.KEYCODE_X, LWJGLGLFWKeycode.GLFW_KEY_X);
|
||||
add(KeyEvent.KEYCODE_Y, LWJGLGLFWKeycode.GLFW_KEY_Y);
|
||||
add(KeyEvent.KEYCODE_Z, LWJGLGLFWKeycode.GLFW_KEY_Z); //54
|
||||
|
||||
|
||||
add(KeyEvent.KEYCODE_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
add(KeyEvent.KEYCODE_PERIOD, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
|
||||
// Alt keys
|
||||
add(KeyEvent.KEYCODE_ALT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT);
|
||||
add(KeyEvent.KEYCODE_ALT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_ALT);
|
||||
|
||||
// Shift keys
|
||||
add(KeyEvent.KEYCODE_SHIFT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT);
|
||||
add(KeyEvent.KEYCODE_SHIFT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_SHIFT);
|
||||
|
||||
add(KeyEvent.KEYCODE_TAB, LWJGLGLFWKeycode.GLFW_KEY_TAB);
|
||||
add(KeyEvent.KEYCODE_SPACE, LWJGLGLFWKeycode.GLFW_KEY_SPACE);
|
||||
add(KeyEvent.KEYCODE_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER); //66
|
||||
add(KeyEvent.KEYCODE_DEL, LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE); // Backspace
|
||||
add(KeyEvent.KEYCODE_GRAVE, LWJGLGLFWKeycode.GLFW_KEY_GRAVE_ACCENT);
|
||||
add(KeyEvent.KEYCODE_MINUS, LWJGLGLFWKeycode.GLFW_KEY_MINUS);
|
||||
add(KeyEvent.KEYCODE_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
|
||||
add(KeyEvent.KEYCODE_LEFT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_LEFT_BRACKET);
|
||||
add(KeyEvent.KEYCODE_RIGHT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_BRACKET);
|
||||
add(KeyEvent.KEYCODE_BACKSLASH, LWJGLGLFWKeycode.GLFW_KEY_BACKSLASH);
|
||||
add(KeyEvent.KEYCODE_SEMICOLON, LWJGLGLFWKeycode.GLFW_KEY_SEMICOLON); //74
|
||||
|
||||
add(KeyEvent.KEYCODE_SLASH, LWJGLGLFWKeycode.GLFW_KEY_SLASH); //76
|
||||
add(KeyEvent.KEYCODE_AT,LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
|
||||
add(KeyEvent.KEYCODE_PLUS, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
|
||||
// Page keys
|
||||
add(KeyEvent.KEYCODE_PAGE_UP, LWJGLGLFWKeycode.GLFW_KEY_PAGE_UP); //92
|
||||
add(KeyEvent.KEYCODE_PAGE_DOWN, LWJGLGLFWKeycode.GLFW_KEY_PAGE_DOWN);
|
||||
|
||||
add(KeyEvent.KEYCODE_ESCAPE, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
// Control keys
|
||||
add(KeyEvent.KEYCODE_CTRL_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL);
|
||||
add(KeyEvent.KEYCODE_CTRL_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_CONTROL);
|
||||
|
||||
add(KeyEvent.KEYCODE_CAPS_LOCK, LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK);
|
||||
add(KeyEvent.KEYCODE_BREAK, LWJGLGLFWKeycode.GLFW_KEY_PAUSE);
|
||||
add(KeyEvent.KEYCODE_INSERT, LWJGLGLFWKeycode.GLFW_KEY_INSERT);
|
||||
|
||||
// Fn keys
|
||||
add(KeyEvent.KEYCODE_F1, LWJGLGLFWKeycode.GLFW_KEY_F1); //131
|
||||
add(KeyEvent.KEYCODE_F2, LWJGLGLFWKeycode.GLFW_KEY_F2);
|
||||
add(KeyEvent.KEYCODE_F3, LWJGLGLFWKeycode.GLFW_KEY_F3);
|
||||
add(KeyEvent.KEYCODE_F4, LWJGLGLFWKeycode.GLFW_KEY_F4);
|
||||
add(KeyEvent.KEYCODE_F5, LWJGLGLFWKeycode.GLFW_KEY_F5);
|
||||
add(KeyEvent.KEYCODE_F6, LWJGLGLFWKeycode.GLFW_KEY_F6);
|
||||
add(KeyEvent.KEYCODE_F7, LWJGLGLFWKeycode.GLFW_KEY_F7);
|
||||
add(KeyEvent.KEYCODE_F8, LWJGLGLFWKeycode.GLFW_KEY_F8);
|
||||
add(KeyEvent.KEYCODE_F9, LWJGLGLFWKeycode.GLFW_KEY_F9);
|
||||
add(KeyEvent.KEYCODE_F10, LWJGLGLFWKeycode.GLFW_KEY_F10);
|
||||
add(KeyEvent.KEYCODE_F11, LWJGLGLFWKeycode.GLFW_KEY_F11);
|
||||
add(KeyEvent.KEYCODE_F12, LWJGLGLFWKeycode.GLFW_KEY_F12); //142
|
||||
|
||||
// Num keys
|
||||
add(KeyEvent.KEYCODE_NUM_LOCK, LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK); //143
|
||||
add(KeyEvent.KEYCODE_NUMPAD_0, LWJGLGLFWKeycode.GLFW_KEY_0);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_9, LWJGLGLFWKeycode.GLFW_KEY_9);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LWJGLGLFWKeycode.GLFW_KEY_KP_DIVIDE);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LWJGLGLFWKeycode.GLFW_KEY_KP_MULTIPLY);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LWJGLGLFWKeycode.GLFW_KEY_KP_SUBTRACT);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_ADD, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_DOT, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL); //161
|
||||
|
||||
}
|
||||
|
||||
private static short index = 0;
|
||||
|
||||
private static void add(int androidKeycode, short LWJGLKeycode){
|
||||
androidKeycodes[index] = androidKeycode;
|
||||
LWJGLKeycodes[index] = LWJGLKeycode;
|
||||
++index;
|
||||
}
|
||||
|
||||
|
||||
public static boolean containsKey(int keycode){
|
||||
return getIndexByKey(keycode) >= 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String[] generateKeyName() {
|
||||
if (androidKeyNameArray == null) {
|
||||
androidKeyNameArray = new String[androidKeycodes.length];
|
||||
for(int i=0; i < androidKeyNameArray.length; ++i){
|
||||
androidKeyNameArray[i] = KeyEvent.keyCodeToString(androidKeycodes[i]).replace("KEYCODE_", "");
|
||||
}
|
||||
}
|
||||
return androidKeyNameArray;
|
||||
}
|
||||
|
||||
public static void execKey(KeyEvent keyEvent) {
|
||||
execKey(keyEvent, getIndexByKey(keyEvent.getKeyCode()));
|
||||
}
|
||||
|
||||
|
||||
public static void execKey(KeyEvent keyEvent, int valueIndex) {
|
||||
//valueIndex points to where the value is stored in the array.
|
||||
CallbackBridge.holdingAlt = keyEvent.isAltPressed();
|
||||
CallbackBridge.holdingCapslock = keyEvent.isCapsLockOn();
|
||||
CallbackBridge.holdingCtrl = keyEvent.isCtrlPressed();
|
||||
CallbackBridge.holdingNumlock = keyEvent.isNumLockOn();
|
||||
CallbackBridge.holdingShift = keyEvent.isShiftPressed();
|
||||
|
||||
try {
|
||||
System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel());
|
||||
char key = (char)(keyEvent.getUnicodeChar() != 0 ? keyEvent.getUnicodeChar() : '\u0000');
|
||||
BaseMainActivity.sendKeyPress(
|
||||
getValueByIndex(valueIndex),
|
||||
key,
|
||||
0,
|
||||
CallbackBridge.getCurrentMods(),
|
||||
keyEvent.getAction() == KeyEvent.ACTION_DOWN);
|
||||
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void execKeyIndex(int index){
|
||||
//Send a quick key press.
|
||||
BaseMainActivity.sendKeyPress(getValueByIndex(index));
|
||||
}
|
||||
|
||||
public static int getValueByIndex(int index) {
|
||||
return LWJGLKeycodes[index];
|
||||
}
|
||||
|
||||
public static int getIndexByKey(int key){
|
||||
return Arrays.binarySearch(androidKeycodes, key);
|
||||
}
|
||||
|
||||
public static short getValue(int key){
|
||||
return LWJGLKeycodes[Arrays.binarySearch(androidKeycodes, key)];
|
||||
}
|
||||
|
||||
public static int getIndexByValue(int lwjglKey) {
|
||||
//Since the LWJGL keycodes aren't sorted, linear search is used.
|
||||
//You should avoid using this function on performance critical areas
|
||||
for (int i = 0; i < LWJGLKeycodes.length; i++) {
|
||||
if(LWJGLKeycodes[i] == lwjglKey) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class ExitActivity extends AppCompatActivity {
|
||||
public static void showExitMessage(Context ctx, int code) {
|
||||
Intent i = new Intent(ctx,ExitActivity.class);
|
||||
i.putExtra("code",code);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
ctx.startActivity(i);
|
||||
}
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
int code = -1;
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if(extras != null) {
|
||||
code = extras.getInt("code",-1);
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.mcn_exit_title,code))
|
||||
.setPositiveButton(android.R.string.ok,(dialog,which)->{
|
||||
dialog.dismiss();
|
||||
ExitActivity.this.finish();
|
||||
}).setOnCancelListener((z)->{
|
||||
ExitActivity.this.finish();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import androidx.appcompat.app.*;
|
|||
import android.util.*;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class FatalErrorActivity extends BaseActivity
|
||||
public class FatalErrorActivity extends AppCompatActivity
|
||||
{
|
||||
public static void showError(Context ctx, String savePath, boolean storageAllow, /* boolean isFatalErr, */ Throwable th) {
|
||||
Intent ferrorIntent = new Intent(ctx, FatalErrorActivity.class);
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.graphics.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import android.content.*;
|
||||
import com.kdt.mcgui.*;
|
||||
|
||||
public class FontChanger
|
||||
{
|
||||
private static Typeface fNotoSans, fMinecraftTen;
|
||||
|
||||
public static void initFonts(Context ctx) {
|
||||
fNotoSans = Typeface.createFromAsset(ctx.getAssets(), "font/NotoSans-Bold.ttf");
|
||||
fMinecraftTen = Typeface.createFromAsset(ctx.getAssets(), "font/minecraft-ten.ttf");
|
||||
}
|
||||
|
||||
public static void changeFonts(ViewGroup viewTree) {
|
||||
View child;
|
||||
for(int i = 0; i < viewTree.getChildCount(); ++i) {
|
||||
child = viewTree.getChildAt(i);
|
||||
if (child instanceof ViewGroup) {
|
||||
changeFonts((ViewGroup) child);
|
||||
} else if (child instanceof TextView) {
|
||||
changeFont((TextView) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void changeFont(TextView view) {
|
||||
view.setTypeface(view instanceof MineButton ? fMinecraftTen : fNotoSans);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,44 +13,49 @@ public class JMinecraftVersionList {
|
|||
public Version[] versions;
|
||||
|
||||
public static class Version {
|
||||
// Since 1.13, so it's one of ways to check
|
||||
public Arguments arguments;
|
||||
// Since 1.13, so it's one of ways to check
|
||||
public Arguments arguments;
|
||||
|
||||
public AssetIndex assetIndex;
|
||||
|
||||
public String assets;
|
||||
public Map<String, MinecraftClientInfo> downloads;
|
||||
public Map<String, MinecraftClientInfo> downloads;
|
||||
public String id;
|
||||
public String inheritsFrom;
|
||||
public DependentLibrary[] libraries;
|
||||
public String mainClass;
|
||||
public String minecraftArguments;
|
||||
public int minimumLauncherVersion;
|
||||
public DependentLibrary optifineLib;
|
||||
public String inheritsFrom;
|
||||
public JavaVersionInfo javaVersion;
|
||||
public DependentLibrary[] libraries;
|
||||
public String mainClass;
|
||||
public String minecraftArguments;
|
||||
public int minimumLauncherVersion;
|
||||
public DependentLibrary optifineLib;
|
||||
public String releaseTime;
|
||||
public String time;
|
||||
public String type;
|
||||
public String url;
|
||||
public String sha1;
|
||||
public String url;
|
||||
public String sha1;
|
||||
}
|
||||
public static class JavaVersionInfo {
|
||||
public String component;
|
||||
public int majorVersion;
|
||||
}
|
||||
// Since 1.13
|
||||
public static class Arguments {
|
||||
public Object[] game;
|
||||
public Object[] jvm;
|
||||
|
||||
// Since 1.13
|
||||
public static class Arguments {
|
||||
public Object[] game;
|
||||
public Object[] jvm;
|
||||
|
||||
public static class ArgValue {
|
||||
public ArgRules[] rules;
|
||||
public String value;
|
||||
public static class ArgValue {
|
||||
public ArgRules[] rules;
|
||||
public String value;
|
||||
|
||||
// TLauncher styled argument...
|
||||
public String[] values;
|
||||
|
||||
public static class ArgRules {
|
||||
public String action;
|
||||
public String features;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class ArgRules {
|
||||
public String action;
|
||||
public String features;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class AssetIndex {
|
||||
public String id, sha1, url;
|
||||
public long size, totalSize;
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
public class JVersion extends Object
|
||||
{
|
||||
private String version;
|
||||
private JVersion(String version)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
public static JVersion from(String version)
|
||||
{
|
||||
return new JVersion(version);
|
||||
}
|
||||
public boolean isVersionCode()
|
||||
{
|
||||
return !version.contains(".");
|
||||
}
|
||||
public JVersion toVersionCode()
|
||||
{
|
||||
if(!isVersionCode()){
|
||||
version = version.replace(".", "");
|
||||
return this;
|
||||
} else throw new RuntimeException("Can't convert version code to itself");
|
||||
}
|
||||
public JVersion toVersionName()
|
||||
{
|
||||
if(isVersionCode()){
|
||||
StringBuilder charList = new StringBuilder();
|
||||
for(int i=0; i<version.length(); i++){
|
||||
charList.append(version.substring(i, i+1));
|
||||
if(i != version.length() - 1){
|
||||
charList.append(".");
|
||||
}
|
||||
}
|
||||
version = charList.toString();
|
||||
return this;
|
||||
} else throw new RuntimeException("Can't convert version name to itself");
|
||||
}
|
||||
public String getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.*;
|
||||
import android.content.*;
|
||||
import android.graphics.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.View.*;
|
||||
import android.widget.*;
|
||||
import androidx.appcompat.app.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
import static net.kdt.pojavlaunch.utils.MathUtils.map;
|
||||
|
||||
public class JavaGUILauncherActivity extends LoggableActivity implements View.OnTouchListener {
|
||||
private static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
|
||||
|
||||
|
|
@ -33,12 +35,13 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
|
||||
private final Object mDialogLock = new Object();
|
||||
|
||||
private DisplayMetrics displayMetrics;
|
||||
private boolean isLogAllow, mSkipDetectMod;
|
||||
|
||||
private boolean rightOverride = false;
|
||||
private float scaleFactor = 1;
|
||||
private int fingerStillThreshold = 8;
|
||||
private int scaleFactor;
|
||||
private int[] scaleFactors = initScaleFactors();
|
||||
|
||||
private final int fingerStillThreshold = 8;
|
||||
private int initialX;
|
||||
private int initialY;
|
||||
private static boolean triggeredLeftMouseButton = false;
|
||||
|
|
@ -62,17 +65,14 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.install_mod);
|
||||
|
||||
Tools.updateWindowSize(this);
|
||||
|
||||
try {
|
||||
MultiRTUtils.setRuntimeNamed(this,LauncherPreferences.PREF_DEFAULT_RUNTIME);
|
||||
gestureDetector = new GestureDetector(this, new SingleTapConfirm());
|
||||
|
||||
this.displayMetrics = Tools.getDisplayMetrics(this);
|
||||
CallbackBridge.windowWidth = (int) ((float)displayMetrics.widthPixels * scaleFactor);
|
||||
CallbackBridge.windowHeight = (int) ((float)displayMetrics.heightPixels * scaleFactor);
|
||||
System.out.println("WidthHeight: " + CallbackBridge.windowWidth + ":" + CallbackBridge.windowHeight);
|
||||
|
||||
findViewById(R.id.installmod_mouse_pri).setOnTouchListener(this);
|
||||
findViewById(R.id.installmod_mouse_sec).setOnTouchListener(this);
|
||||
|
|
@ -81,24 +81,13 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
touchPad.setFocusable(false);
|
||||
|
||||
this.mousePointer = findViewById(R.id.main_mouse_pointer);
|
||||
this.mousePointer.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ViewGroup.LayoutParams params = mousePointer.getLayoutParams();
|
||||
params.width = (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE);
|
||||
params.height = (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE);
|
||||
}
|
||||
});
|
||||
/*
|
||||
touchPad.setOnHoverListener(new OnHoverListener() {
|
||||
@Override
|
||||
public boolean onHover(View v, MotionEvent event) {
|
||||
AWTInputBridge.sendMousePos((int) event.getX(), (int) event.getY());
|
||||
return false;
|
||||
}
|
||||
this.mousePointer.post(() -> {
|
||||
ViewGroup.LayoutParams params = mousePointer.getLayoutParams();
|
||||
params.width = (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE);
|
||||
params.height = (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE);
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
touchPad.setOnTouchListener(new OnTouchListener(){
|
||||
private float prevX, prevY;
|
||||
@Override
|
||||
|
|
@ -119,32 +108,28 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
float mouseX = mousePointer.getTranslationX();
|
||||
float mouseY = mousePointer.getTranslationY();
|
||||
float mouseX = mousePointer.getX();
|
||||
float mouseY = mousePointer.getY();
|
||||
|
||||
if (gestureDetector.onTouchEvent(event)) {
|
||||
|
||||
AWTInputBridge.sendMousePos((int) (mouseX * scaleFactor), (int) (mouseY *scaleFactor));
|
||||
sendScaledMousePosition(mouseX,mouseY);
|
||||
|
||||
AWTInputBridge.sendMousePress(rightOverride ? AWTInputEvent.BUTTON3_DOWN_MASK : AWTInputEvent.BUTTON1_DOWN_MASK);
|
||||
if (!rightOverride) {
|
||||
CallbackBridge.mouseLeft = true;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_CANCEL: // 3
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
if (!rightOverride) {
|
||||
CallbackBridge.mouseLeft = false;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE: // 2
|
||||
mouseX = Math.max(0, Math.min(displayMetrics.widthPixels, mouseX + x - prevX));
|
||||
mouseY = Math.max(0, Math.min(displayMetrics.heightPixels, mouseY + y - prevY));
|
||||
mouseX = Math.max(0, Math.min(CallbackBridge.physicalWidth, mouseX + x - prevX));
|
||||
mouseY = Math.max(0, Math.min(CallbackBridge.physicalHeight, mouseY + y - prevY));
|
||||
placeMouseAt(mouseX, mouseY);
|
||||
|
||||
AWTInputBridge.sendMousePos((int) (mouseX * scaleFactor), (int) (mouseY *scaleFactor));
|
||||
sendScaledMousePosition(mouseX,mouseY);
|
||||
/*
|
||||
if (!CallbackBridge.isGrabbing()) {
|
||||
CallbackBridge.sendMouseKeycode(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, 0, isLeftMouseDown);
|
||||
|
|
@ -260,13 +245,18 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
}
|
||||
|
||||
public void placeMouseAdd(float x, float y) {
|
||||
this.mousePointer.setTranslationX(mousePointer.getTranslationX() + x);
|
||||
this.mousePointer.setTranslationY(mousePointer.getTranslationY() + y);
|
||||
this.mousePointer.setX(mousePointer.getX() + x);
|
||||
this.mousePointer.setY(mousePointer.getY() + y);
|
||||
}
|
||||
|
||||
public void placeMouseAt(float x, float y) {
|
||||
this.mousePointer.setTranslationX(x);
|
||||
this.mousePointer.setTranslationY(y);
|
||||
this.mousePointer.setX(x);
|
||||
this.mousePointer.setY(y);
|
||||
}
|
||||
|
||||
void sendScaledMousePosition(float x, float y){
|
||||
AWTInputBridge.sendMousePos((int) map(x,0,CallbackBridge.physicalWidth, scaleFactors[0], scaleFactors[2]),
|
||||
(int) map(y,0,CallbackBridge.physicalHeight, scaleFactors[1], scaleFactors[3]));
|
||||
}
|
||||
|
||||
public void forceClose(View v) {
|
||||
|
|
@ -292,8 +282,9 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
}
|
||||
|
||||
public int launchJavaRuntime(File modFile, String javaArgs) {
|
||||
JREUtils.redirectAndPrintJRELog(this, null);
|
||||
JREUtils.redirectAndPrintJRELog(this);
|
||||
try {
|
||||
jreReleaseList = JREUtils.readJREReleaseProperties();
|
||||
List<String> javaArgList = new ArrayList<String>();
|
||||
|
||||
// Enable Caciocavallo
|
||||
|
|
@ -334,12 +325,52 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
|
|||
public void appendToLog(final String text, boolean checkAllow) {
|
||||
logStream.print(text);
|
||||
if (checkAllow && !isLogAllow) return;
|
||||
textLog.post(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
textLog.append(text);
|
||||
contentScroll.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
textLog.post(() -> {
|
||||
textLog.append(text);
|
||||
contentScroll.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
});
|
||||
}
|
||||
|
||||
int[] initScaleFactors(){
|
||||
return initScaleFactors(true);
|
||||
}
|
||||
|
||||
int[] initScaleFactors(boolean autoScale){
|
||||
//Could be optimized
|
||||
|
||||
if(autoScale) { //Auto scale
|
||||
int minDimension = Math.min(CallbackBridge.physicalHeight, CallbackBridge.physicalWidth);
|
||||
scaleFactor = Math.max(((3 * minDimension) / 1080) - 1, 1);
|
||||
}
|
||||
|
||||
int[] scales = new int[4]; //Left, Top, Right, Bottom
|
||||
|
||||
scales[0] = (CallbackBridge.physicalWidth/2);
|
||||
scales[0] -= scales[0]/scaleFactor;
|
||||
|
||||
scales[1] = (CallbackBridge.physicalHeight/2);
|
||||
scales[1] -= scales[1]/scaleFactor;
|
||||
|
||||
scales[2] = (CallbackBridge.physicalWidth/2);
|
||||
scales[2] += scales[2]/scaleFactor;
|
||||
|
||||
scales[3] = (CallbackBridge.physicalHeight/2);
|
||||
scales[3] += scales[3]/scaleFactor;
|
||||
|
||||
return scales;
|
||||
}
|
||||
|
||||
public void scaleDown(View view) {
|
||||
scaleFactor = Math.max(scaleFactor - 1, 1);
|
||||
scaleFactors = initScaleFactors(false);
|
||||
mTextureView.initScaleFactors(scaleFactor);
|
||||
sendScaledMousePosition(mousePointer.getX(),mousePointer.getY());
|
||||
}
|
||||
|
||||
public void scaleUp(View view) {
|
||||
scaleFactor = Math.min(scaleFactor + 1, 6);
|
||||
scaleFactors = initScaleFactors(false);
|
||||
mTextureView.initScaleFactors(scaleFactor);
|
||||
sendScaledMousePosition(mousePointer.getX(),mousePointer.getY());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ package net.kdt.pojavlaunch;
|
|||
public class LWJGLGLFWKeycode
|
||||
{
|
||||
/** The unknown key. */
|
||||
public static final int GLFW_KEY_UNKNOWN = 0; // should be -1
|
||||
public static final short GLFW_KEY_UNKNOWN = 0; // should be -1
|
||||
|
||||
/** Printable keys. */
|
||||
public static final int
|
||||
public static final short
|
||||
GLFW_KEY_SPACE = 32,
|
||||
GLFW_KEY_APOSTROPHE = 39,
|
||||
GLFW_KEY_COMMA = 44,
|
||||
|
|
@ -89,7 +89,7 @@ public class LWJGLGLFWKeycode
|
|||
GLFW_KEY_WORLD_2 = 162;
|
||||
|
||||
/** Function keys. */
|
||||
public static final int
|
||||
public static final short
|
||||
GLFW_KEY_ESCAPE = 256,
|
||||
GLFW_KEY_ENTER = 257,
|
||||
GLFW_KEY_TAB = 258,
|
||||
|
|
@ -182,7 +182,7 @@ public class LWJGLGLFWKeycode
|
|||
|
||||
|
||||
/** Mouse buttons. See <a target="_blank" href="http://www.glfw.org/docs/latest/input.html#input_mouse_button">mouse button input</a> for how these are used. */
|
||||
public static final int
|
||||
public static final short
|
||||
GLFW_MOUSE_BUTTON_1 = 0,
|
||||
GLFW_MOUSE_BUTTON_2 = 1,
|
||||
GLFW_MOUSE_BUTTON_3 = 2,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class LoggableActivity extends BaseActivity {
|
||||
public Map<String, String> jreReleaseList;
|
||||
private boolean filteredSessionID = false;
|
||||
public void appendToLog(String text) {
|
||||
appendToLog(text, true);
|
||||
}
|
||||
|
||||
public void appendlnToLog(String text) {
|
||||
// Filter out Session ID here
|
||||
int index;
|
||||
if (!filteredSessionID && (index = text.indexOf("(Session ID is ")) != -1) {
|
||||
text = text.substring(0, index) + "(Session ID is <censored>)";
|
||||
filteredSessionID = true;
|
||||
}
|
||||
|
||||
appendlnToLog(text, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,134 +3,54 @@ package net.kdt.pojavlaunch;
|
|||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.*;
|
||||
import android.view.*;
|
||||
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.MCOptionUtils;
|
||||
|
||||
import org.lwjgl.glfw.*;
|
||||
import java.io.*;
|
||||
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
|
||||
|
||||
public class MainActivity extends BaseMainActivity {
|
||||
public ControlLayout mControlLayout;
|
||||
|
||||
private View.OnClickListener mClickListener;
|
||||
private View.OnTouchListener mTouchListener;
|
||||
public static ControlLayout mControlLayout;
|
||||
|
||||
private FileObserver fileObserver;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initLayout(R.layout.main_with_customctrl);
|
||||
super.ingameControlsEditorListener = new NavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_ctrl_load:
|
||||
CustomControlsActivity.load(mControlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_add:
|
||||
mControlLayout.addControlButton(new ControlData("New", LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN, 100, 100));
|
||||
break;
|
||||
case R.id.menu_ctrl_selectdefault:
|
||||
CustomControlsActivity.dialogSelectDefaultCtrl(mControlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_save:
|
||||
CustomControlsActivity.save(true,mControlLayout);
|
||||
break;
|
||||
}
|
||||
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
super.ingameControlsEditorListener = menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_ctrl_load:
|
||||
CustomControlsActivity.load(mControlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_add:
|
||||
mControlLayout.addControlButton(new ControlData("New"));
|
||||
break;
|
||||
case R.id.menu_ctrl_add_drawer:
|
||||
mControlLayout.addDrawer(new ControlDrawerData());
|
||||
break;
|
||||
case R.id.menu_ctrl_selectdefault:
|
||||
CustomControlsActivity.dialogSelectDefaultCtrl(mControlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_save:
|
||||
CustomControlsActivity.save(true,mControlLayout);
|
||||
break;
|
||||
}
|
||||
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
};
|
||||
|
||||
mClickListener = new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view instanceof ControlButton) {
|
||||
ControlButton button = (ControlButton) view;
|
||||
switch (button.getProperties().keycode) {
|
||||
case ControlData.SPECIALBTN_KEYBOARD:
|
||||
showKeyboard();
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_TOGGLECTRL:
|
||||
mControlLayout.toggleControlVisible();
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_VIRTUALMOUSE:
|
||||
toggleMouse(button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mTouchListener = new View.OnTouchListener(){
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent e) {
|
||||
boolean isDown;
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: // 0
|
||||
case MotionEvent.ACTION_POINTER_DOWN: // 5
|
||||
isDown = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_CANCEL: // 3
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
isDown = false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (view instanceof ControlButton) {
|
||||
ControlButton button = (ControlButton) view;
|
||||
switch (button.getProperties().keycode) {
|
||||
case ControlData.SPECIALBTN_MOUSEPRI:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSEMID:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSESEC:
|
||||
if (CallbackBridge.isGrabbing()) {
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
|
||||
} else {
|
||||
CallbackBridge.putMouseEventWithCoords(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown ? 1 : 0, CallbackBridge.mouseX, CallbackBridge.mouseY);
|
||||
|
||||
setRightOverride(isDown);
|
||||
}
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_SCROLLDOWN:
|
||||
if(!isDown)CallbackBridge.sendScroll(0, 1d);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_SCROLLUP:
|
||||
if(!isDown)CallbackBridge.sendScroll(0, -1d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
|
||||
fileObserver = new FileObserver(new File(Tools.DIR_GAME_NEW + "/options.txt"), FileObserver.MODIFY) {
|
||||
@Override
|
||||
public void onEvent(int i, @Nullable String s) {
|
||||
//FIXME Make sure the multithreading nature of this event doesn't cause any problems ?
|
||||
MCOptionUtils.load();
|
||||
getMcScale();
|
||||
}
|
||||
|
|
@ -139,7 +59,6 @@ public class MainActivity extends BaseMainActivity {
|
|||
fileObserver = new FileObserver(Tools.DIR_GAME_NEW + "/options.txt", FileObserver.MODIFY) {
|
||||
@Override
|
||||
public void onEvent(int i, @Nullable String s) {
|
||||
//FIXME Make sure the multithreading nature of this event doesn't cause any problems ?
|
||||
MCOptionUtils.load();
|
||||
getMcScale();
|
||||
}
|
||||
|
|
@ -148,19 +67,6 @@ public class MainActivity extends BaseMainActivity {
|
|||
|
||||
fileObserver.startWatching();
|
||||
|
||||
ControlData[] specialButtons = ControlData.getSpecialButtons();
|
||||
specialButtons[0].specialButtonListener
|
||||
= specialButtons[1].specialButtonListener
|
||||
= specialButtons[4].specialButtonListener
|
||||
= mClickListener;
|
||||
|
||||
specialButtons[2].specialButtonListener
|
||||
= specialButtons[3].specialButtonListener
|
||||
= specialButtons[5].specialButtonListener
|
||||
= specialButtons[6].specialButtonListener
|
||||
= specialButtons[7].specialButtonListener
|
||||
= mTouchListener;
|
||||
|
||||
mControlLayout = findViewById(R.id.main_control_layout);
|
||||
mControlLayout.setModifiable(false);
|
||||
try {
|
||||
|
|
@ -186,7 +92,7 @@ public class MainActivity extends BaseMainActivity {
|
|||
|
||||
if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
|
||||
// Reload PREF_DEFAULTCTRL_PATH
|
||||
LauncherPreferences.loadPreferences();
|
||||
LauncherPreferences.loadPreferences(getApplicationContext());
|
||||
try {
|
||||
mControlLayout.loadLayout(LauncherPreferences.PREF_DEFAULTCTRL_PATH);
|
||||
} catch (IOException e) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.content.*;
|
||||
import android.content.res.Configuration;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
public class MinecraftGLView extends TextureView
|
||||
{
|
||||
|
|
@ -19,59 +12,8 @@ public class MinecraftGLView extends TextureView
|
|||
|
||||
public MinecraftGLView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
setOnFocusChangeListener(new OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View view, boolean b) {
|
||||
if(!b) {
|
||||
((InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(view.getWindowToken(),0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||
outAttrs.inputType = EditorInfo.TYPE_NULL;
|
||||
Log.d("TypeableGLView","onCreateInputConnection");
|
||||
return new MinecraftInputConnection(this, false);
|
||||
}
|
||||
@Override
|
||||
public boolean onCheckIsTextEditor() {
|
||||
return false;
|
||||
}
|
||||
public static boolean isHardKB(Context ctx) {
|
||||
return ctx.getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
class MinecraftInputConnection extends BaseInputConnection {
|
||||
private SpannableStringBuilder _editable;
|
||||
BaseMainActivity parent;
|
||||
public MinecraftInputConnection(View targetView, boolean fullEditor) {
|
||||
super(targetView, fullEditor);
|
||||
|
||||
parent = (BaseMainActivity)targetView.getContext();
|
||||
}
|
||||
|
||||
public Editable getEditable() {
|
||||
if (_editable == null) {
|
||||
_editable = (SpannableStringBuilder) Editable.Factory.getInstance()
|
||||
.newEditable("Placeholder");
|
||||
}
|
||||
return _editable;
|
||||
}
|
||||
|
||||
public boolean commitText(CharSequence text, int newCursorPosition) {
|
||||
for(int i = 0; i < text.length(); i++) parent.sendKeyPress(text.charAt(i));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
|
||||
for(int i = 0; i < beforeLength; i++) {
|
||||
parent.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE);
|
||||
}
|
||||
return true;
|
||||
//Fixes freeform and dex mode having transparent glass,
|
||||
//since it forces android to used the background color of the view/layout behind it.
|
||||
setOpaque(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import android.content.pm.*;
|
|||
import android.content.res.*;
|
||||
import android.os.*;
|
||||
import androidx.core.app.*;
|
||||
import androidx.preference.*;
|
||||
|
||||
import android.util.*;
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
|
|
@ -34,7 +34,8 @@ public class PojavApplication extends Application
|
|||
crashStream.append(" - Time: " + DateFormat.getDateTimeInstance().format(new Date()) + "\n");
|
||||
crashStream.append(" - Device: " + Build.PRODUCT + " " + Build.MODEL + "\n");
|
||||
crashStream.append(" - Android version: " + Build.VERSION.RELEASE + "\n");
|
||||
crashStream.append(" - Crash stack trace:\n");
|
||||
crashStream.append(" - Crash stack trace:\n");
|
||||
crashStream.append(" - Launcher version: " + BuildConfig.VERSION_NAME + "\n");
|
||||
crashStream.append(Log.getStackTraceString(th));
|
||||
crashStream.close();
|
||||
} catch (Throwable th2) {
|
||||
|
|
@ -54,28 +55,22 @@ public class PojavApplication extends Application
|
|||
Tools.APP_NAME = getResources().getString(R.string.app_short_name);
|
||||
|
||||
Tools.DIR_DATA = getDir("files", MODE_PRIVATE).getParent();
|
||||
Tools.DIR_HOME_JRE = Tools.DIR_DATA + "/jre_runtime";
|
||||
//Tools.DIR_HOME_JRE = Tools.DIR_DATA + "/jre_runtime".replace("/data/user/0", "/data/data");
|
||||
Tools.DIR_ACCOUNT_OLD = Tools.DIR_DATA + "/Users";
|
||||
Tools.DIR_ACCOUNT_NEW = Tools.DIR_DATA + "/accounts";
|
||||
// Tools.FILE_ACCOUNT_JSON = getFilesDir().getAbsolutePath() + "/account_profiles.json";
|
||||
|
||||
File nativeLibDir = new File(getApplicationInfo().nativeLibraryDir);
|
||||
|
||||
Tools.CURRENT_ARCHITECTURE = nativeLibDir.getName();
|
||||
switch (Tools.CURRENT_ARCHITECTURE) {
|
||||
case "arm": Tools.CURRENT_ARCHITECTURE = "arm/aarch32"; break;
|
||||
case "arm64": Tools.CURRENT_ARCHITECTURE = "arm64/aarch64"; break;
|
||||
case "x86": Tools.CURRENT_ARCHITECTURE = "x86/i*86"; break;
|
||||
case "x86_64": Tools.CURRENT_ARCHITECTURE = "x86_64/amd64"; break;
|
||||
}
|
||||
|
||||
// Special case for Asus x86 devixes
|
||||
if (Build.SUPPORTED_ABIS[0].equals("x86")) {
|
||||
getApplicationInfo().nativeLibraryDir = nativeLibDir.getParent() + "/x86";
|
||||
Tools.CURRENT_ARCHITECTURE = "x86/i*86";
|
||||
}
|
||||
|
||||
FontChanger.initFonts(this);
|
||||
|
||||
Tools.DEVICE_ARCHITECTURE = Architecture.getDeviceArchitecture();
|
||||
//Force x86 lib directory for Asus x86 based zenfones
|
||||
if(Architecture.isx86Device() && Architecture.is32BitsDevice()){
|
||||
String originalJNIDirectory = getApplicationInfo().nativeLibraryDir;
|
||||
getApplicationInfo().nativeLibraryDir = originalJNIDirectory.substring(0,
|
||||
originalJNIDirectory.lastIndexOf("/"))
|
||||
.concat("/x86");
|
||||
}
|
||||
|
||||
|
||||
} catch (Throwable th) {
|
||||
Intent ferrorIntent = new Intent(this, FatalErrorActivity.class);
|
||||
ferrorIntent.putExtra("throwable", th);
|
||||
|
|
|
|||
|
|
@ -311,8 +311,8 @@ public class PojavLauncherActivity extends BaseLauncherActivity
|
|||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
//Try to get the notch so it can be taken into account in settings
|
||||
if (Build.VERSION.SDK_INT >= P){
|
||||
//Get the fucking notch height:
|
||||
try {
|
||||
PREF_NOTCH_SIZE = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout().getBoundingRects().get(0).width();
|
||||
}catch (Exception e){
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import static net.kdt.pojavlaunch.Architecture.archAsString;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
|
|
@ -14,62 +15,58 @@ import android.graphics.Color;
|
|||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.system.Os;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.kdt.pickafile.FileListView;
|
||||
import com.kdt.pickafile.FileSelectedListener;
|
||||
import java.io.BufferedInputStream;
|
||||
|
||||
import net.kdt.pojavlaunch.authenticator.microsoft.MicrosoftAuthTask;
|
||||
import net.kdt.pojavlaunch.authenticator.microsoft.ui.MicrosoftLoginGUIActivity;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.InvalidateTokenTask;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.LoginListener;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.LoginTask;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
|
||||
import net.kdt.pojavlaunch.customcontrols.CustomControls;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.LocaleUtils;
|
||||
import net.kdt.pojavlaunch.value.MinecraftAccount;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import net.kdt.pojavlaunch.authenticator.microsoft.MicrosoftAuthTask;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.InvalidateTokenTask;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.LoginListener;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.LoginTask;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.CustomControls;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.JREUtils;
|
||||
import net.kdt.pojavlaunch.utils.LocaleUtils;
|
||||
import net.kdt.pojavlaunch.value.MinecraftAccount;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public class PojavLoginActivity extends BaseActivity
|
||||
// MineActivity
|
||||
|
|
@ -84,102 +81,96 @@ public class PojavLoginActivity extends BaseActivity
|
|||
private SharedPreferences firstLaunchPrefs;
|
||||
private MinecraftAccount mProfile = null;
|
||||
|
||||
private static boolean isSkipInit = false;
|
||||
private boolean isSkipInit = false;
|
||||
private boolean isStarting = false;
|
||||
|
||||
public static final String PREF_IS_INSTALLED_JAVARUNTIME = "isJavaRuntimeInstalled";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState); // false;
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
isStarting = savedInstanceState.getBoolean("isStarting");
|
||||
isSkipInit = savedInstanceState.getBoolean("isSkipInit");
|
||||
}
|
||||
Tools.updateWindowSize(this);
|
||||
|
||||
firstLaunchPrefs = getSharedPreferences("pojav_extract", MODE_PRIVATE);
|
||||
new InitTask().execute(isSkipInit);
|
||||
new Thread(new InitRunnable()).start();
|
||||
}
|
||||
|
||||
private class InitTask extends AsyncTask<Boolean, String, Integer>{
|
||||
private AlertDialog startAle;
|
||||
private ProgressBar progress;
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean("isStarting",isStarting);
|
||||
outState.putBoolean("isSkipInit",isSkipInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
LinearLayout startScr = new LinearLayout(PojavLoginActivity.this);
|
||||
LayoutInflater.from(PojavLoginActivity.this).inflate(R.layout.start_screen, startScr);
|
||||
|
||||
FontChanger.changeFonts(startScr);
|
||||
|
||||
progress = (ProgressBar) startScr.findViewById(R.id.startscreenProgress);
|
||||
startupTextView = (TextView) startScr.findViewById(R.id.startscreen_text);
|
||||
|
||||
|
||||
AlertDialog.Builder startDlg = new AlertDialog.Builder(PojavLoginActivity.this, R.style.AppTheme);
|
||||
startDlg.setView(startScr);
|
||||
startDlg.setCancelable(false);
|
||||
|
||||
startAle = startDlg.create();
|
||||
startAle.show();
|
||||
startAle.getWindow().setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
}
|
||||
|
||||
public class InitRunnable implements Runnable{
|
||||
private int revokeCount = -1;
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Boolean[] params) {
|
||||
// If trigger a quick restart
|
||||
if (params[0]) return 0;
|
||||
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {}
|
||||
private int proceedState = 0;
|
||||
private ProgressBar progress;
|
||||
public InitRunnable() {
|
||||
}
|
||||
public void initLocalUi() {
|
||||
LinearLayout startScr = new LinearLayout(PojavLoginActivity.this);
|
||||
LayoutInflater.from(PojavLoginActivity.this).inflate(R.layout.start_screen,startScr);
|
||||
PojavLoginActivity.this.setContentView(startScr);
|
||||
|
||||
publishProgress("visible");
|
||||
progress = (ProgressBar) findViewById(R.id.startscreenProgress);
|
||||
if(isStarting) progress.setVisibility(View.VISIBLE);
|
||||
startupTextView = (TextView) findViewById(R.id.startscreen_text);
|
||||
}
|
||||
|
||||
while (Build.VERSION.SDK_INT >= 23 && !isStorageAllowed()){
|
||||
try {
|
||||
revokeCount++;
|
||||
if (revokeCount >= 3) {
|
||||
Toast.makeText(PojavLoginActivity.this, R.string.toast_permission_denied, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return 0;
|
||||
public int _start() {
|
||||
Log.i("UITest","START initialization");
|
||||
if(!isStarting) {
|
||||
//try { Thread.sleep(2000); } catch (InterruptedException e) { }
|
||||
runOnUiThread(() -> progress.setVisibility(View.VISIBLE));
|
||||
while (Build.VERSION.SDK_INT >= 23 && Build.VERSION.SDK_INT < 29 && !isStorageAllowed()) { //Do not ask for storage at all on Android 10+
|
||||
try {
|
||||
revokeCount++;
|
||||
if (revokeCount >= 3) {
|
||||
Toast.makeText(PojavLoginActivity.this, R.string.toast_permission_denied, Toast.LENGTH_LONG).show();
|
||||
return 2;
|
||||
}
|
||||
requestStoragePermission();
|
||||
|
||||
synchronized (mLockStoragePerm) {
|
||||
mLockStoragePerm.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
|
||||
requestStoragePermission();
|
||||
|
||||
synchronized (mLockStoragePerm) {
|
||||
mLockStoragePerm.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
isStarting = true;
|
||||
}
|
||||
|
||||
try {
|
||||
initMain();
|
||||
} catch (Throwable th) {
|
||||
Tools.showError(PojavLoginActivity.this, th, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... obj)
|
||||
{
|
||||
if (obj[0].equals("visible")) {
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
public void proceed() {
|
||||
isStarting = false;
|
||||
switch(proceedState) {
|
||||
case 2:
|
||||
finish();
|
||||
break;
|
||||
case 0:
|
||||
uiInit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer obj) {
|
||||
startAle.dismiss();
|
||||
if (obj == 0) uiInit();
|
||||
public void run() {
|
||||
if(!isSkipInit) {
|
||||
PojavLoginActivity.this.runOnUiThread(this::initLocalUi);
|
||||
proceedState = _start();
|
||||
}
|
||||
PojavLoginActivity.this.runOnUiThread(this::proceed);
|
||||
}
|
||||
}
|
||||
|
||||
private void uiInit() {
|
||||
setContentView(R.layout.launcher_login_v3);
|
||||
|
||||
|
|
@ -189,7 +180,7 @@ public class PojavLoginActivity extends BaseActivity
|
|||
SpannableString defaultLangChar = new SpannableString(defaultLang);
|
||||
defaultLangChar.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, defaultLang.length(), 0);
|
||||
|
||||
final ArrayAdapter<DisplayableLocale> langAdapter = new ArrayAdapter<DisplayableLocale>(this, android.R.layout.simple_spinner_item);
|
||||
final ArrayAdapter<DisplayableLocale> langAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
|
||||
langAdapter.add(new DisplayableLocale(LocaleUtils.DEFAULT_LOCALE, defaultLangChar));
|
||||
langAdapter.add(new DisplayableLocale(Locale.ENGLISH));
|
||||
|
||||
|
|
@ -255,14 +246,10 @@ public class PojavLoginActivity extends BaseActivity
|
|||
|
||||
sRemember = findViewById(R.id.login_switch_remember);
|
||||
sOffline = findViewById(R.id.login_switch_offline);
|
||||
sOffline.setOnCheckedChangeListener(new OnCheckedChangeListener(){
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton p1, boolean p2) {
|
||||
// May delete later
|
||||
edit3.setEnabled(!p2);
|
||||
}
|
||||
});
|
||||
sOffline.setOnCheckedChangeListener((p1, p2) -> {
|
||||
// May delete later
|
||||
edit3.setEnabled(!p2);
|
||||
});
|
||||
|
||||
isSkipInit = true;
|
||||
}
|
||||
|
|
@ -277,17 +264,6 @@ public class PojavLoginActivity extends BaseActivity
|
|||
PojavProfile.setCurrentProfile(this, null);
|
||||
}
|
||||
|
||||
private boolean isJavaRuntimeInstalled(AssetManager am) {
|
||||
boolean prefValue = firstLaunchPrefs.getBoolean(PREF_IS_INSTALLED_JAVARUNTIME, false);
|
||||
try {
|
||||
return prefValue && (
|
||||
am.open("components/jre/bin-" + Tools.CURRENT_ARCHITECTURE.split("/")[0] + ".tar.xz") == null ||
|
||||
Tools.read(new FileInputStream(Tools.DIR_HOME_JRE+"/version")).equals(Tools.read(am.open("components/jre/version"))));
|
||||
} catch(IOException e) {
|
||||
Log.e("JVMCtl","failed to read file",e);
|
||||
return prefValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void unpackComponent(AssetManager am, String component) throws IOException {
|
||||
File versionFile = new File(Tools.DIR_GAME_HOME + "/" + component + "/version");
|
||||
|
|
@ -353,7 +329,7 @@ public class PojavLoginActivity extends BaseActivity
|
|||
}
|
||||
|
||||
mkdirs(Tools.CTRLMAP_PATH);
|
||||
|
||||
|
||||
try {
|
||||
new CustomControls(this).save(Tools.CTRLDEF_FILE);
|
||||
|
||||
|
|
@ -367,206 +343,88 @@ public class PojavLoginActivity extends BaseActivity
|
|||
|
||||
unpackComponent(am, "caciocavallo");
|
||||
unpackComponent(am, "lwjgl3");
|
||||
if (!isJavaRuntimeInstalled(am)) {
|
||||
if(!installRuntimeAutomatically(am)) {
|
||||
File jreTarFile = selectJreTarFile();
|
||||
uncompressTarXZ(jreTarFile, new File(Tools.DIR_HOME_JRE));
|
||||
} else {
|
||||
Tools.copyAssetFile(this, "components/jre/version", Tools.DIR_HOME_JRE + "/","version", true);
|
||||
if(!installRuntimeAutomatically(am,MultiRTUtils.getRuntimes().size() > 0)) {
|
||||
MultiRTConfigDialog.openRuntimeSelector(this, MultiRTConfigDialog.MULTIRT_PICK_RUNTIME_STARTUP);
|
||||
synchronized (mLockSelectJRE) {
|
||||
mLockSelectJRE.wait();
|
||||
}
|
||||
firstLaunchPrefs.edit().putBoolean(PREF_IS_INSTALLED_JAVARUNTIME, true).commit();
|
||||
}
|
||||
|
||||
JREUtils.relocateLibPath(this);
|
||||
|
||||
File ftIn = new File(Tools.DIR_HOME_JRE, Tools.DIRNAME_HOME_JRE + "/libfreetype.so.6");
|
||||
File ftOut = new File(Tools.DIR_HOME_JRE, Tools.DIRNAME_HOME_JRE + "/libfreetype.so");
|
||||
if (ftIn.exists() && (!ftOut.exists() || ftIn.length() != ftOut.length())) {
|
||||
ftIn.renameTo(ftOut);
|
||||
}
|
||||
|
||||
// Refresh libraries
|
||||
copyDummyNativeLib("libawt_xawt.so");
|
||||
// copyDummyNativeLib("libfontconfig.so");
|
||||
if(Build.VERSION.SDK_INT > 28) runOnUiThread(this::showStorageDialog);
|
||||
LauncherPreferences.loadPreferences(getApplicationContext());
|
||||
}
|
||||
catch(Throwable e){
|
||||
Tools.showError(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean installRuntimeAutomatically(AssetManager am) {
|
||||
try {
|
||||
am.open("components/jre/version");
|
||||
} catch (IOException e) {
|
||||
Log.e("JREAuto", "JRE was not included on this APK.", e);
|
||||
return false;
|
||||
private void showStorageDialog() {
|
||||
if(!firstLaunchPrefs.getBoolean("storageDialogShown",false)) {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(this);
|
||||
bldr.setTitle(R.string.storage_warning_title);
|
||||
Spanned sp = Html.fromHtml(getString(R.string.storage_warning_text,BuildConfig.APPLICATION_ID));
|
||||
bldr.setMessage(sp);
|
||||
bldr.setCancelable(false);
|
||||
bldr.setPositiveButton(android.R.string.ok, (dialog, which)->{
|
||||
firstLaunchPrefs.edit().putBoolean("storageDialogShown",true).apply();
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.show();
|
||||
}
|
||||
|
||||
File rtUniversal = new File(Tools.DIR_HOME_JRE+"/universal.tar.xz");
|
||||
File rtPlatformDependent = new File(Tools.DIR_HOME_JRE+"/cust-bin.tar.xz");
|
||||
if(!new File(Tools.DIR_HOME_JRE).exists()) new File(Tools.DIR_HOME_JRE).mkdirs(); else {
|
||||
//SANITY: remove the existing files
|
||||
for (File f : new File(Tools.DIR_HOME_JRE).listFiles()) {
|
||||
if (f.isDirectory()){
|
||||
try {
|
||||
FileUtils.deleteDirectory(f);
|
||||
} catch(IOException e1) {
|
||||
Log.e("JREAuto","da fuq is wrong wit ur device? n2",e1);
|
||||
}
|
||||
} else{
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
InputStream is;
|
||||
FileOutputStream os;
|
||||
try {
|
||||
is = am.open("components/jre/universal.tar.xz");
|
||||
os = new FileOutputStream(rtUniversal);
|
||||
IOUtils.copy(is,os);
|
||||
is.close();
|
||||
os.close();
|
||||
uncompressTarXZ(rtUniversal, new File(Tools.DIR_HOME_JRE));
|
||||
} catch (IOException e){
|
||||
Log.e("JREAuto","Failed to unpack universal. Custom embedded-less build?",e);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
is = am.open("components/jre/bin-" + Tools.CURRENT_ARCHITECTURE.split("/")[0] + ".tar.xz");
|
||||
os = new FileOutputStream(rtPlatformDependent);
|
||||
IOUtils.copy(is, os);
|
||||
is.close();
|
||||
os.close();
|
||||
uncompressTarXZ(rtPlatformDependent, new File(Tools.DIR_HOME_JRE));
|
||||
} catch (IOException e) {
|
||||
// Something's very wrong, or user's using an unsupported arch (MIPS phone? ARMv6 phone?),
|
||||
// in both cases, redirecting to manual install, and removing the universal stuff
|
||||
for (File f : new File(Tools.DIR_HOME_JRE).listFiles()) {
|
||||
if (f.isDirectory()){
|
||||
try {
|
||||
FileUtils.deleteDirectory(f);
|
||||
} catch(IOException e1) {
|
||||
Log.e("JREAuto","da fuq is wrong wit ur device?",e1);
|
||||
}
|
||||
} else{
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void copyDummyNativeLib(String name) throws Throwable {
|
||||
File fileLib = new File(Tools.DIR_HOME_JRE, Tools.DIRNAME_HOME_JRE + "/" + name);
|
||||
fileLib.delete();
|
||||
FileInputStream is = new FileInputStream(new File(getApplicationInfo().nativeLibraryDir, name));
|
||||
FileOutputStream os = new FileOutputStream(fileLib);
|
||||
IOUtils.copy(is, os);
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
|
||||
private File selectJreTarFile() throws InterruptedException {
|
||||
final StringBuilder selectedFile = new StringBuilder();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(PojavLoginActivity.this);
|
||||
builder.setTitle(getString(R.string.alerttitle_install_jre, Tools.CURRENT_ARCHITECTURE));
|
||||
builder.setCancelable(false);
|
||||
|
||||
final AlertDialog dialog = builder.create();
|
||||
FileListView flv = new FileListView(dialog, "tar.xz");
|
||||
flv.setFileSelectedListener(new FileSelectedListener(){
|
||||
|
||||
@Override
|
||||
public void onFileSelected(File file, String path) {
|
||||
selectedFile.append(path);
|
||||
dialog.dismiss();
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if(resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == MultiRTConfigDialog.MULTIRT_PICK_RUNTIME_STARTUP) {
|
||||
if (data != null) {
|
||||
final Uri uri = data.getData();
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), BaseLauncherActivity.getFileName(this, uri),
|
||||
(resid, stuff) -> PojavLoginActivity.this.runOnUiThread(
|
||||
() -> {
|
||||
if (startupTextView != null)
|
||||
startupTextView.setText(PojavLoginActivity.this.getString(resid, stuff));
|
||||
}));
|
||||
synchronized (mLockSelectJRE) {
|
||||
mLockSelectJRE.notifyAll();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Tools.showError(PojavLoginActivity.this
|
||||
, e);
|
||||
}
|
||||
});
|
||||
dialog.setView(flv);
|
||||
dialog.show();
|
||||
t.start();
|
||||
}
|
||||
}else if(requestCode == MicrosoftLoginGUIActivity.AUTHENTICATE_MICROSOFT_REQUEST) {
|
||||
//Log.i("MicroLoginWrap","Got microsoft login result:" + data);
|
||||
performMicroLogin(data);
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (mLockSelectJRE) {
|
||||
mLockSelectJRE.wait();
|
||||
}
|
||||
|
||||
return new File(selectedFile.toString());
|
||||
}
|
||||
private boolean installRuntimeAutomatically(AssetManager am, boolean otherRuntimesAvailable) {
|
||||
/* Check if JRE is included */
|
||||
String rt_version = null;
|
||||
String current_rt_version = MultiRTUtils.__internal__readBinpackVersion("Internal");
|
||||
try {
|
||||
rt_version = Tools.read(am.open("components/jre/version"));
|
||||
} catch (IOException e) {
|
||||
Log.e("JREAuto", "JRE was not included on this APK.", e);
|
||||
}
|
||||
if(current_rt_version == null && otherRuntimesAvailable) return true; //Assume user maintains his own runtime
|
||||
if(rt_version == null) return false;
|
||||
if(!rt_version.equals(current_rt_version)) { //If we already have an integrated one installed, check if it's up-to-date
|
||||
try {
|
||||
MultiRTUtils.installRuntimeNamedBinpack(am.open("components/jre/universal.tar.xz"), am.open("components/jre/bin-" + archAsString(Tools.DEVICE_ARCHITECTURE) + ".tar.xz"), "Internal", rt_version,
|
||||
(resid, vararg) -> runOnUiThread(()->{if(startupTextView!=null)startupTextView.setText(getString(resid,vararg));}));
|
||||
MultiRTUtils.postPrepare(PojavLoginActivity.this,"Internal");
|
||||
return true;
|
||||
}catch (IOException e) {
|
||||
Log.e("JREAuto", "Internal JRE unpack failed", e);
|
||||
return false;
|
||||
}
|
||||
}else return true; // we have at least one runtime, and it's compartible, good to go
|
||||
}
|
||||
|
||||
private void uncompressTarXZ(final File tarFile, final File dest) throws IOException {
|
||||
|
||||
dest.mkdirs();
|
||||
TarArchiveInputStream tarIn = null;
|
||||
|
||||
tarIn = new TarArchiveInputStream(
|
||||
new XZCompressorInputStream(
|
||||
new BufferedInputStream(
|
||||
new FileInputStream(tarFile)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
|
||||
// tarIn is a TarArchiveInputStream
|
||||
while (tarEntry != null) {
|
||||
/*
|
||||
* Unpacking very small files in short time cause
|
||||
* application to ANR or out of memory, so delay
|
||||
* a little if size is below than 20kb (20480 bytes)
|
||||
*/
|
||||
if (tarEntry.getSize() <= 20480) {
|
||||
try {
|
||||
// 40 small files per second
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
final String tarEntryName = tarEntry.getName();
|
||||
runOnUiThread(new Runnable(){
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
@Override
|
||||
public void run() {
|
||||
startupTextView.setText(getString(R.string.global_unpacking, tarEntryName));
|
||||
}
|
||||
});
|
||||
// publishProgress(null, "Unpacking " + tarEntry.getName());
|
||||
File destPath = new File(dest, tarEntry.getName());
|
||||
if (tarEntry.isSymbolicLink()) {
|
||||
destPath.getParentFile().mkdirs();
|
||||
try {
|
||||
// android.system.Os
|
||||
// Libcore one support all Android versions
|
||||
Os.symlink(tarEntry.getName(), tarEntry.getLinkName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else if (tarEntry.isDirectory()) {
|
||||
destPath.mkdirs();
|
||||
destPath.setExecutable(true);
|
||||
} else if (!destPath.exists() || destPath.length() != tarEntry.getSize()) {
|
||||
destPath.getParentFile().mkdirs();
|
||||
destPath.createNewFile();
|
||||
|
||||
FileOutputStream os = new FileOutputStream(destPath);
|
||||
IOUtils.copy(tarIn, os);
|
||||
os.close();
|
||||
|
||||
}
|
||||
tarEntry = tarIn.getNextTarEntry();
|
||||
}
|
||||
tarIn.close();
|
||||
}
|
||||
|
||||
private static boolean mkdirs(String path)
|
||||
{
|
||||
File file = new File(path);
|
||||
|
|
@ -578,18 +436,15 @@ public class PojavLoginActivity extends BaseActivity
|
|||
|
||||
|
||||
public void loginMicrosoft(View view) {
|
||||
CustomTabs.openTab(this,
|
||||
"https://login.live.com/oauth20_authorize.srf" +
|
||||
"?client_id=00000000402b5328" +
|
||||
"&response_type=code" +
|
||||
"&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL" +
|
||||
"&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf");
|
||||
Intent i = new Intent(this,MicrosoftLoginGUIActivity.class);
|
||||
startActivityForResult(i,MicrosoftLoginGUIActivity.AUTHENTICATE_MICROSOFT_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
}
|
||||
public void performMicroLogin(Intent intent) {
|
||||
Uri data = intent.getData();
|
||||
//Log.i("MicroAuth", data.toString());
|
||||
if (data != null && data.getScheme().equals("ms-xal-00000000402b5328") && data.getHost().equals("auth")) {
|
||||
|
|
@ -603,22 +458,21 @@ public class PojavLoginActivity extends BaseActivity
|
|||
} else {
|
||||
String code = data.getQueryParameter("code");
|
||||
new MicrosoftAuthTask(this, new RefreshListener(){
|
||||
@Override
|
||||
public void onFailed(Throwable e) {
|
||||
Tools.showError(PojavLoginActivity.this, e);
|
||||
}
|
||||
@Override
|
||||
public void onFailed(Throwable e) {
|
||||
Tools.showError(PojavLoginActivity.this, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(MinecraftAccount b) {
|
||||
mProfile = b;
|
||||
playProfile(false);
|
||||
}
|
||||
}).execute("false", code);
|
||||
@Override
|
||||
public void onSuccess(MinecraftAccount b) {
|
||||
mProfile = b;
|
||||
playProfile(false);
|
||||
}
|
||||
}).execute("false", code);
|
||||
// Toast.makeText(this, "Logged in to Microsoft account, but NYI", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private View getViewFromList(int pos, ListView listView) {
|
||||
final int firstItemPos = listView.getFirstVisiblePosition();
|
||||
final int lastItemPos = firstItemPos + listView.getChildCount() - 1;
|
||||
|
|
@ -645,21 +499,21 @@ public class PojavLoginActivity extends BaseActivity
|
|||
LinearLayout accountListLayout = accountDialog.findViewById(R.id.accountListLayout);
|
||||
LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
|
||||
for (int accountIndex = 0; accountIndex < accountArr.length; accountIndex++) {
|
||||
String s = accountArr[accountIndex];
|
||||
View child = inflater.inflate(R.layout.simple_account_list_item, null);
|
||||
View child = inflater.inflate(R.layout.simple_account_list_item, accountListLayout,false);
|
||||
TextView accountName = child.findViewById(R.id.accountitem_text_name);
|
||||
ImageButton removeButton = child.findViewById(R.id.accountitem_button_remove);
|
||||
ImageView imageView = child.findViewById(R.id.account_head);
|
||||
|
||||
String accNameStr = s.substring(0, s.length() - 5);
|
||||
String skinFaceBase64 = MinecraftAccount.load(accNameStr).skinFaceBase64;
|
||||
if (skinFaceBase64 != null) {
|
||||
byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT);
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length);
|
||||
|
||||
accountName.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(getResources(),
|
||||
bitmap), null, null, null);
|
||||
|
||||
imageView.setImageDrawable(new BitmapDrawable(getResources(),
|
||||
bitmap));
|
||||
}
|
||||
accountName.setText(accNameStr);
|
||||
|
||||
|
|
@ -700,9 +554,7 @@ public class PojavLoginActivity extends BaseActivity
|
|||
}
|
||||
});
|
||||
|
||||
// Tiny trick to avoid 'const' field
|
||||
final int accountIndex_final = accountIndex;
|
||||
|
||||
removeButton.setOnClickListener(new View.OnClickListener() {
|
||||
final String selectedAccName = accountName.getText().toString();
|
||||
@Override
|
||||
|
|
@ -710,21 +562,17 @@ public class PojavLoginActivity extends BaseActivity
|
|||
AlertDialog.Builder builder2 = new AlertDialog.Builder(PojavLoginActivity.this);
|
||||
builder2.setTitle(selectedAccName);
|
||||
builder2.setMessage(R.string.warning_remove_account);
|
||||
builder2.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2) {
|
||||
new InvalidateTokenTask(PojavLoginActivity.this).execute(selectedAccName);
|
||||
accountListLayout.removeViewsInLayout(accountIndex_final, 1);
|
||||
|
||||
if (accountListLayout.getChildCount() == 0) {
|
||||
accountDialog.dismiss(); //No need to keep it, since there is no account
|
||||
return;
|
||||
}
|
||||
//Refreshes the layout with the same settings so it take the missing child into account.
|
||||
accountListLayout.setLayoutParams(accountListLayout.getLayoutParams());
|
||||
builder2.setPositiveButton(android.R.string.ok, (p1, p2) -> {
|
||||
new InvalidateTokenTask(PojavLoginActivity.this).execute(selectedAccName);
|
||||
accountListLayout.removeViewsInLayout(accountIndex_final, 1);
|
||||
|
||||
if (accountListLayout.getChildCount() == 0) {
|
||||
accountDialog.dismiss(); //No need to keep it, since there is no account
|
||||
return;
|
||||
}
|
||||
//Refreshes the layout with the same settings so it take the missing child into account.
|
||||
accountListLayout.setLayoutParams(accountListLayout.getLayoutParams());
|
||||
|
||||
});
|
||||
builder2.setNegativeButton(android.R.string.cancel, null);
|
||||
builder2.show();
|
||||
|
|
@ -740,13 +588,15 @@ public class PojavLoginActivity extends BaseActivity
|
|||
new File(Tools.DIR_ACCOUNT_OLD).mkdir();
|
||||
|
||||
String text = edit2.getText().toString();
|
||||
if(text.isEmpty()){
|
||||
edit2.setError(getResources().getString(R.string.global_error_field_empty));
|
||||
} else if(text.length() <= 2){
|
||||
edit2.setError(getResources().getString(R.string.login_error_short_username));
|
||||
} else if(new File(Tools.DIR_ACCOUNT_NEW + "/" + text + ".json").exists()){
|
||||
edit2.setError(getResources().getString(R.string.login_error_exist_username));
|
||||
} else{
|
||||
if (text.isEmpty()) {
|
||||
edit2.setError(getString(R.string.global_error_field_empty));
|
||||
} else if (text.length() < 3 || text.length() > 16 || !text.matches("\\w+")) {
|
||||
edit2.setError(getString(R.string.login_error_invalid_username));
|
||||
} else if (new File(Tools.DIR_ACCOUNT_NEW + "/" + text + ".json").exists()) {
|
||||
edit2.setError(getString(R.string.login_error_exist_username));
|
||||
} else if (!edit3.getText().toString().isEmpty()) {
|
||||
edit3.setError(getString(R.string.login_error_offline_password));
|
||||
} else {
|
||||
MinecraftAccount builder = new MinecraftAccount();
|
||||
builder.isMicrosoft = false;
|
||||
builder.username = text;
|
||||
|
|
@ -816,7 +666,7 @@ public class PojavLoginActivity extends BaseActivity
|
|||
|
||||
public static String strArrToString(String[] strArr)
|
||||
{
|
||||
String[] strArrEdit = strArr;
|
||||
String[] strArrEdit = strArr.clone();
|
||||
strArrEdit[0] = "";
|
||||
|
||||
String str = Arrays.toString(strArrEdit);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class PojavMigrator
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean migrateGameDir() throws IOException {
|
||||
File oldGameDir = new File(Tools.DIR_GAME_OLD);
|
||||
|
||||
|
|
@ -49,25 +49,7 @@ public class PojavMigrator
|
|||
}
|
||||
return moved;
|
||||
}
|
||||
/*
|
||||
public static boolean migrateBugFix20201217() throws IOException, InterruptedException {
|
||||
File bugGameDir = new File(Tools.DIR_GAME_NEW + "/.minecraft");
|
||||
File oldGameDir = new File(Tools.DIR_GAME_OLD);
|
||||
boolean moved = bugGameDir.exists() && bugGameDir.isDirectory();
|
||||
|
||||
if (oldGameDir.exists() && oldGameDir.isDirectory() && moved) {
|
||||
command("rm -rf " + oldGameDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
command("mv " + bugGameDir.getAbsolutePath() + " " + Tools.DIR_GAME_OLD);
|
||||
command("rm -rf " + Tools.DIR_GAME_HOME + "/*");
|
||||
command("mv " + Tools.DIR_GAME_OLD + " " + Tools.DIR_GAME_HOME + "/");
|
||||
}
|
||||
|
||||
return moved;
|
||||
}
|
||||
*/
|
||||
private static void command(String cmd) throws IOException, InterruptedException {
|
||||
Process p = Runtime.getRuntime().exec(cmd);
|
||||
int exitCode = p.waitFor();
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.io.*;
|
||||
import android.app.*;
|
||||
|
||||
public class ShellProcessOperation
|
||||
{
|
||||
private OnPrintListener listener;
|
||||
private Process process;
|
||||
|
||||
public ShellProcessOperation(OnPrintListener listener) throws IOException {
|
||||
this.listener = listener;
|
||||
process = Runtime.getRuntime().exec("/system/bin/sh");
|
||||
}
|
||||
|
||||
public ShellProcessOperation(OnPrintListener listener, String command) throws IOException {
|
||||
this.listener = listener;
|
||||
process = Runtime.getRuntime().exec(
|
||||
command
|
||||
); //"/system/bin/sh -c \"" + command + "\"");
|
||||
}
|
||||
|
||||
public void writeToProcess(String[] cmdArr) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String cmd : cmdArr) {sb.append(cmd + " ");}
|
||||
writeToProcess(sb.toString());
|
||||
}
|
||||
|
||||
public void writeToProcess(String cmd) throws IOException {
|
||||
// listener.onPrintLine(" > " + cmd + "\n");
|
||||
|
||||
DataOutputStream os = new DataOutputStream(process.getOutputStream());
|
||||
os.writeBytes(cmd + "\n");
|
||||
os.flush();
|
||||
}
|
||||
|
||||
public void initInputStream(Activity ctx) {
|
||||
ctx.runOnUiThread(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printStream(process.getInputStream());
|
||||
printStream(process.getErrorStream());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int exitCode() {
|
||||
return process.exitValue();
|
||||
}
|
||||
|
||||
public int waitFor() throws InterruptedException {
|
||||
return process.waitFor();
|
||||
}
|
||||
|
||||
public int exit() throws InterruptedException, IOException {
|
||||
writeToProcess("exit");
|
||||
return waitFor();
|
||||
}
|
||||
|
||||
private void printStream(final InputStream stream) {
|
||||
new Thread(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try {
|
||||
BufferedReader buffStream = new BufferedReader(new InputStreamReader(stream));
|
||||
String line = null;
|
||||
while ((line = buffStream.readLine()) != null) {
|
||||
listener.onPrintLine(line + "\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
listener.onPrintLine("PrintStream error: " + e.getMessage() + "\n");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static interface OnPrintListener {
|
||||
public void onPrintLine(String text);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +33,7 @@ import static android.os.Build.VERSION_CODES.P;
|
|||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_IGNORE_NOTCH;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
|
||||
|
||||
public final class Tools
|
||||
{
|
||||
public final class Tools {
|
||||
public static final boolean ENABLE_DEV_FEATURES = BuildConfig.DEBUG;
|
||||
|
||||
public static String APP_NAME = "null";
|
||||
|
|
@ -42,53 +41,81 @@ public final class Tools
|
|||
public static final Gson GLOBAL_GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static final String URL_HOME = "https://pojavlauncherteam.github.io/PojavLauncher";
|
||||
public static String DIR_DATA = "/data/data/" + BuildConfig.APPLICATION_ID;
|
||||
public static String CURRENT_ARCHITECTURE;
|
||||
|
||||
public static String DIR_DATA; //Initialized later to get context
|
||||
public static String MULTIRT_HOME;
|
||||
public static String LOCAL_RENDERER = null;
|
||||
public static int DEVICE_ARCHITECTURE;
|
||||
|
||||
// New since 3.3.1
|
||||
public static String DIR_ACCOUNT_NEW;
|
||||
public static String DIR_ACCOUNT_OLD;
|
||||
public static final String DIR_GAME_HOME = Environment.getExternalStorageDirectory().getAbsolutePath() + "/games/PojavLauncher";
|
||||
public static final String DIR_GAME_NEW = DIR_GAME_HOME + "/.minecraft";
|
||||
public static final String DIR_GAME_OLD = Environment.getExternalStorageDirectory().getAbsolutePath() + "/games/.minecraft";
|
||||
public static String DIR_GAME_HOME = Environment.getExternalStorageDirectory().getAbsolutePath() + "/games/PojavLauncher";
|
||||
public static String DIR_GAME_NEW;
|
||||
public static String DIR_GAME_OLD = Environment.getExternalStorageDirectory().getAbsolutePath() + "/games/.minecraft";
|
||||
|
||||
// New since 3.0.0
|
||||
public static String DIR_HOME_JRE;
|
||||
public static String DIRNAME_HOME_JRE = "lib";
|
||||
|
||||
// New since 2.4.2
|
||||
public static final String DIR_HOME_VERSION = DIR_GAME_NEW + "/versions";
|
||||
public static final String DIR_HOME_LIBRARY = DIR_GAME_NEW + "/libraries";
|
||||
public static String DIR_HOME_VERSION;
|
||||
public static String DIR_HOME_LIBRARY;
|
||||
|
||||
public static final String DIR_HOME_CRASH = DIR_GAME_NEW + "/crash-reports";
|
||||
public static String DIR_HOME_CRASH;
|
||||
|
||||
public static final String ASSETS_PATH = DIR_GAME_NEW + "/assets";
|
||||
public static final String OBSOLETE_RESOURCES_PATH= DIR_GAME_NEW + "/resources";
|
||||
public static final String CTRLMAP_PATH = DIR_GAME_HOME + "/controlmap";
|
||||
public static final String CTRLDEF_FILE = DIR_GAME_HOME + "/controlmap/default.json";
|
||||
public static String ASSETS_PATH;
|
||||
public static String OBSOLETE_RESOURCES_PATH;
|
||||
public static String CTRLMAP_PATH;
|
||||
public static String CTRLDEF_FILE;
|
||||
|
||||
public static final String LIBNAME_OPTIFINE = "optifine:OptiFine";
|
||||
|
||||
/**
|
||||
* Since some constant requires the use of the Context object
|
||||
* You can call this function to initialize them.
|
||||
* Any value (in)directly dependant on DIR_DATA should be set only here.
|
||||
*/
|
||||
public static void initContextConstants(Context ctx){
|
||||
DIR_DATA = ctx.getFilesDir().getParent();
|
||||
MULTIRT_HOME = DIR_DATA+"/runtimes";
|
||||
if(Build.VERSION.SDK_INT >= 29) {
|
||||
DIR_GAME_HOME = ctx.getExternalFilesDir(null).getAbsolutePath();
|
||||
}else{
|
||||
DIR_GAME_HOME = new File(Environment.getExternalStorageDirectory(),"games/PojavLauncher").getAbsolutePath();
|
||||
}
|
||||
DIR_GAME_NEW = DIR_GAME_HOME + "/.minecraft";
|
||||
DIR_HOME_VERSION = DIR_GAME_NEW + "/versions";
|
||||
DIR_HOME_LIBRARY = DIR_GAME_NEW + "/libraries";
|
||||
DIR_HOME_CRASH = DIR_GAME_NEW + "/crash-reports";
|
||||
ASSETS_PATH = DIR_GAME_NEW + "/assets";
|
||||
OBSOLETE_RESOURCES_PATH= DIR_GAME_NEW + "/resources";
|
||||
CTRLMAP_PATH = DIR_GAME_HOME + "/controlmap";
|
||||
CTRLDEF_FILE = DIR_GAME_HOME + "/controlmap/default.json";
|
||||
}
|
||||
|
||||
|
||||
public static void launchMinecraft(final LoggableActivity ctx, MinecraftAccount profile, String versionName) throws Throwable {
|
||||
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
|
||||
((ActivityManager)ctx.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(mi);
|
||||
if(LauncherPreferences.PREF_RAM_ALLOCATION > (mi.availMem/1048576L)) {
|
||||
ctx.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
androidx.appcompat.app.AlertDialog.Builder b = new androidx.appcompat.app.AlertDialog.Builder(ctx)
|
||||
.setMessage(ctx.getString(R.string.memory_warning_msg,(mi.availMem/1048576L),LauncherPreferences.PREF_RAM_ALLOCATION))
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {}
|
||||
});
|
||||
b.show();
|
||||
}
|
||||
Object memoryErrorLock = new Object();
|
||||
ctx.runOnUiThread(() -> {
|
||||
androidx.appcompat.app.AlertDialog.Builder b = new androidx.appcompat.app.AlertDialog.Builder(ctx)
|
||||
.setMessage(ctx.getString(R.string.memory_warning_msg,(mi.availMem/1048576L),LauncherPreferences.PREF_RAM_ALLOCATION))
|
||||
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {synchronized(memoryErrorLock){memoryErrorLock.notifyAll();}})
|
||||
.setOnCancelListener((i) -> {synchronized(memoryErrorLock){memoryErrorLock.notifyAll();}});
|
||||
b.show();
|
||||
});
|
||||
synchronized (memoryErrorLock) {
|
||||
memoryErrorLock.wait();
|
||||
}
|
||||
}
|
||||
|
||||
JMinecraftVersionList.Version versionInfo = Tools.getVersionInfo(null,versionName);
|
||||
PerVersionConfig.update();
|
||||
PerVersionConfig.VersionConfig pvcConfig = PerVersionConfig.configMap.get(versionName);
|
||||
|
||||
String gamedirPath;
|
||||
if(pvcConfig != null && pvcConfig.gamePath != null && !pvcConfig.gamePath.isEmpty()) gamedirPath = pvcConfig.gamePath;
|
||||
else gamedirPath = Tools.DIR_GAME_NEW;
|
||||
|
|
@ -101,7 +128,13 @@ public final class Tools
|
|||
String launchClassPath = generateLaunchClassPath(versionInfo,versionName);
|
||||
|
||||
List<String> javaArgList = new ArrayList<String>();
|
||||
|
||||
|
||||
// Only Java 8 supports headful AWT for now
|
||||
if (ctx.jreReleaseList.get("JAVA_VERSION").equals("1.8.0")) {
|
||||
getCacioJavaArgs(javaArgList, false);
|
||||
}
|
||||
|
||||
/*
|
||||
int mcReleaseDate = Integer.parseInt(versionInfo.releaseTime.substring(0, 10).replace("-", ""));
|
||||
// 13w17a: 20130425
|
||||
// 13w18a: 20130502
|
||||
|
|
@ -112,7 +145,8 @@ public final class Tools
|
|||
getCacioJavaArgs(javaArgList,false); // true
|
||||
ctx.appendlnToLog("Headless version detected! ("+mcReleaseDate+")");
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
javaArgList.add("-cp");
|
||||
javaArgList.add(getLWJGL3ClassPath() + ":" + launchClassPath);
|
||||
|
||||
|
|
@ -146,61 +180,6 @@ public final class Tools
|
|||
javaArgList.add(cacioClasspath.toString());
|
||||
}
|
||||
|
||||
public static void getJavaArgs(Context ctx, List<String> javaArgList) {
|
||||
List<String> overrideableArgList = new ArrayList<String>();
|
||||
|
||||
overrideableArgList.add("-Djava.home=" + Tools.DIR_HOME_JRE);
|
||||
overrideableArgList.add("-Djava.io.tmpdir=" + ctx.getCacheDir().getAbsolutePath());
|
||||
|
||||
overrideableArgList.add("-Duser.home=" + new File(Tools.DIR_GAME_NEW).getParent());
|
||||
overrideableArgList.add("-Duser.language=" + System.getProperty("user.language"));
|
||||
// overrideableArgList.add("-Duser.timezone=GMT");
|
||||
|
||||
overrideableArgList.add("-Dos.name=Linux");
|
||||
overrideableArgList.add("-Dos.version=Android-" + Build.VERSION.RELEASE);
|
||||
|
||||
overrideableArgList.add("-Dpojav.path.minecraft=" + Tools.DIR_GAME_NEW);
|
||||
overrideableArgList.add("-Dpojav.path.private.account=" + Tools.DIR_ACCOUNT_NEW);
|
||||
|
||||
// javaArgList.add("-Dorg.lwjgl.libname=liblwjgl3.so");
|
||||
// javaArgList.add("-Dorg.lwjgl.system.jemalloc.libname=libjemalloc.so");
|
||||
|
||||
overrideableArgList.add("-Dorg.lwjgl.opengl.libname=libgl4es_114.so");
|
||||
// overrideableArgList.add("-Dorg.lwjgl.opengl.libname=libgl4es_115.so");
|
||||
|
||||
// javaArgList.add("-Dorg.lwjgl.opengl.libname=libRegal.so");
|
||||
|
||||
// Enable LWJGL3 debug
|
||||
// overrideableArgList.add("-Dorg.lwjgl.util.Debug=true");
|
||||
// overrideableArgList.add("-Dorg.lwjgl.util.DebugFunctions=true");
|
||||
// overrideableArgList.add("-Dorg.lwjgl.util.DebugLoader=true");
|
||||
|
||||
// GLFW Stub width height
|
||||
overrideableArgList.add("-Dglfwstub.windowWidth=" + CallbackBridge.windowWidth);
|
||||
overrideableArgList.add("-Dglfwstub.windowHeight=" + CallbackBridge.windowHeight);
|
||||
overrideableArgList.add("-Dglfwstub.initEgl=false");
|
||||
|
||||
overrideableArgList.add("-Dnet.minecraft.clientmodname=" + Tools.APP_NAME);
|
||||
|
||||
// Disable FML Early Loading Screen to get Forge 1.14+ works
|
||||
overrideableArgList.add("-Dfml.earlyprogresswindow=false");
|
||||
|
||||
// Override args
|
||||
for (String argOverride : LauncherPreferences.PREF_CUSTOM_JAVA_ARGS.split(" ")) {
|
||||
for (int i = 0; i < overrideableArgList.size(); i++) {
|
||||
String arg = overrideableArgList.get(i);
|
||||
if (arg.startsWith("-D") && argOverride.startsWith(arg.substring(0, arg.indexOf('=') + 1))) {
|
||||
overrideableArgList.set(i, argOverride);
|
||||
break;
|
||||
} else if (i+1 == overrideableArgList.size()) {
|
||||
javaArgList.add(argOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
javaArgList.addAll(overrideableArgList);
|
||||
}
|
||||
|
||||
public static String[] getMinecraftArgs(MinecraftAccount profile, JMinecraftVersionList.Version versionInfo, String strGameDir) {
|
||||
String username = profile.username;
|
||||
String versionName = versionInfo.id;
|
||||
|
|
@ -213,7 +192,7 @@ public final class Tools
|
|||
File gameDir = new File(strGameDir);
|
||||
gameDir.mkdirs();
|
||||
|
||||
Map<String, String> varArgMap = new ArrayMap<String, String>();
|
||||
Map<String, String> varArgMap = new ArrayMap<>();
|
||||
varArgMap.put("auth_access_token", profile.accessToken);
|
||||
varArgMap.put("auth_player_name", username);
|
||||
varArgMap.put("auth_uuid", profile.profileId);
|
||||
|
|
@ -249,6 +228,14 @@ public final class Tools
|
|||
}
|
||||
}
|
||||
}
|
||||
minecraftArgs.add("--width");
|
||||
minecraftArgs.add(Integer.toString(CallbackBridge.windowWidth));
|
||||
minecraftArgs.add("--height");
|
||||
minecraftArgs.add(Integer.toString(CallbackBridge.windowHeight));
|
||||
minecraftArgs.add("--fullscreenWidth");
|
||||
minecraftArgs.add(Integer.toString(CallbackBridge.windowWidth));
|
||||
minecraftArgs.add("--fullscreenHeight");
|
||||
minecraftArgs.add(Integer.toString(CallbackBridge.windowHeight));
|
||||
|
||||
String[] argsFromJson = JSONUtils.insertJSONValueList(
|
||||
splitAndFilterEmpty(
|
||||
|
|
@ -395,10 +382,13 @@ public final class Tools
|
|||
}
|
||||
|
||||
public static float dpToPx(float dp) {
|
||||
// 921600 = 1280 * 720, default scale
|
||||
// TODO better way to scaling
|
||||
float scaledDp = dp; // / DisplayMetrics.DENSITY_XHIGH * currentDisplayMetrics.densityDpi;
|
||||
return (scaledDp * currentDisplayMetrics.density);
|
||||
//Better hope for the currentDisplayMetrics to be good
|
||||
return dp * currentDisplayMetrics.density;
|
||||
}
|
||||
|
||||
public static float pxToDp(float px){
|
||||
//Better hope for the currentDisplayMetrics to be good
|
||||
return px / currentDisplayMetrics.density;
|
||||
}
|
||||
|
||||
public static void copyAssetFile(Context ctx, String fileName, String output, boolean overwrite) throws IOException {
|
||||
|
|
@ -416,31 +406,7 @@ public final class Tools
|
|||
write(file2.getAbsolutePath(), loadFromAssetToByte(ctx, fileName));
|
||||
}
|
||||
}
|
||||
/*
|
||||
public static void extractAssetFolder(Activity ctx, String path, String output) throws Exception {
|
||||
extractAssetFolder(ctx, path, output, false);
|
||||
}
|
||||
|
||||
public static void extractAssetFolder(Activity ctx, String path, String output, boolean overwrite) throws Exception {
|
||||
AssetManager assetManager = ctx.getAssets();
|
||||
String assets[] = null;
|
||||
try {
|
||||
assets = assetManager.list(path);
|
||||
if (assets.length == 0) {
|
||||
Tools.copyAssetFile(ctx, path, output, overwrite);
|
||||
} else {
|
||||
File dir = new File(output, path);
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
for (String sub : assets) {
|
||||
extractAssetFolder(ctx, path + "/" + sub, output, overwrite);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showError(ctx, e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
public static void showError(Context ctx, Throwable e) {
|
||||
showError(ctx, e, false);
|
||||
}
|
||||
|
|
@ -465,45 +431,27 @@ public final class Tools
|
|||
AlertDialog.Builder builder = new AlertDialog.Builder((Context) ctx)
|
||||
.setTitle(titleId)
|
||||
.setMessage(errMsg)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2)
|
||||
{
|
||||
if(exitIfOk) {
|
||||
if (ctx instanceof BaseMainActivity) {
|
||||
BaseMainActivity.fullyExit();
|
||||
} else if (ctx instanceof Activity) {
|
||||
((Activity) ctx).finish();
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok, (DialogInterface.OnClickListener) (p1, p2) -> {
|
||||
if(exitIfOk) {
|
||||
if (ctx instanceof BaseMainActivity) {
|
||||
BaseMainActivity.fullyExit();
|
||||
} else if (ctx instanceof Activity) {
|
||||
((Activity) ctx).finish();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(showMore ? R.string.error_show_less : R.string.error_show_more, new DialogInterface.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2)
|
||||
{
|
||||
showError(ctx, titleId, e, exitIfOk, !showMore);
|
||||
}
|
||||
})
|
||||
.setNeutralButton(android.R.string.copy, new DialogInterface.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2)
|
||||
{
|
||||
android.content.ClipboardManager mgr = (android.content.ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
mgr.setPrimaryClip(ClipData.newPlainText("error", Log.getStackTraceString(e)));
|
||||
if(exitIfOk) {
|
||||
if (ctx instanceof BaseMainActivity) {
|
||||
BaseMainActivity.fullyExit();
|
||||
} else {
|
||||
((Activity) ctx).finish();
|
||||
}
|
||||
.setNegativeButton(showMore ? R.string.error_show_less : R.string.error_show_more, (DialogInterface.OnClickListener) (p1, p2) -> showError(ctx, titleId, e, exitIfOk, !showMore))
|
||||
.setNeutralButton(android.R.string.copy, (DialogInterface.OnClickListener) (p1, p2) -> {
|
||||
ClipboardManager mgr = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
mgr.setPrimaryClip(ClipData.newPlainText("error", Log.getStackTraceString(e)));
|
||||
if(exitIfOk) {
|
||||
if (ctx instanceof BaseMainActivity) {
|
||||
BaseMainActivity.fullyExit();
|
||||
} else {
|
||||
((Activity) ctx).finish();
|
||||
}
|
||||
}
|
||||
})
|
||||
//.setNegativeButton("Report (not available)", null)
|
||||
.setCancelable(!exitIfOk);
|
||||
try {
|
||||
builder.show();
|
||||
|
|
@ -521,18 +469,11 @@ public final class Tools
|
|||
}
|
||||
|
||||
public static void dialogOnUiThread(final Activity ctx, final CharSequence title, final CharSequence message) {
|
||||
ctx.runOnUiThread(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
new AlertDialog.Builder(ctx)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
ctx.runOnUiThread(() -> new AlertDialog.Builder(ctx)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show());
|
||||
}
|
||||
|
||||
public static void moveInside(String from, String to) {
|
||||
|
|
@ -898,4 +839,23 @@ public final class Tools
|
|||
}
|
||||
}
|
||||
|
||||
public static int getTotalDeviceMemory(Context ctx){
|
||||
ActivityManager actManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
|
||||
actManager.getMemoryInfo(memInfo);
|
||||
return (int) (memInfo.totalMem / 1048576L);
|
||||
}
|
||||
|
||||
public static int getFreeDeviceMemory(Context ctx){
|
||||
ActivityManager actManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
|
||||
actManager.getMemoryInfo(memInfo);
|
||||
return (int) (memInfo.availMem / 1048576L);
|
||||
}
|
||||
|
||||
public static int getDisplayFriendlyRes(int displaySideRes, float scaling){
|
||||
displaySideRes *= scaling;
|
||||
if(displaySideRes % 2 != 0) displaySideRes ++;
|
||||
return displaySideRes;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,206 +108,5 @@ public class MicrosoftAuthTask extends AsyncTask<String, Void, Object> {
|
|||
listener.onFailed((Throwable) result);
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://github.com/MiniDigger/MiniLauncher/blob/master/launcher/src/main/java/me/minidigger/minecraftlauncher/launcher/gui/MsaFragmentController.java
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Ammar Ahmad
|
||||
Copyright (c) 2018 Martin Benndorf
|
||||
Copyright (c) 2018 Mark Vainomaa
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
/*
|
||||
private final class XSTSXUI {
|
||||
private String uhs;
|
||||
}
|
||||
|
||||
private final class XSTSDisplayClaims {
|
||||
private XSTSXUI[] xui;
|
||||
}
|
||||
|
||||
private final class GlobalToken {
|
||||
// MSA AccessToken
|
||||
private String access_token;
|
||||
|
||||
// XBL, XSTS Token
|
||||
private String Token;
|
||||
|
||||
// XSTS
|
||||
private XSTSDisplayClaims DisplayClaims;
|
||||
|
||||
// Minecraft side
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
private String acquireAccessToken(String authcode) throws IOException, URISyntaxException{
|
||||
URI uri = new URI(authTokenUrl);
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("client_id", "00000000402b5328");
|
||||
data.put("code", authcode);
|
||||
data.put("grant_type", "authorization_code");
|
||||
data.put("redirect_url", "https://login.live.com/oauth20_desktop.srf");
|
||||
data.put("scope", "service::user.auth.xboxlive.com::MBI_SSL");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(ofFormData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).access_token;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private String acquireXBLToken(String accessToken) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(xblAuthUrl);
|
||||
|
||||
Map<Object, Object> dataProp = new ArrayMap<>();
|
||||
dataProp.put("AuthMethod", "RPS");
|
||||
dataProp.put("SiteName", "user.auth.xboxlive.com");
|
||||
dataProp.put("RpsTicket", accessToken);
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("Properties", dataProp);
|
||||
data.put("RelyingParty", "http://auth.xboxlive.com");
|
||||
data.put("TokenType", "JWT");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(ofJSONData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).Token;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private String[] acquireXsts(String xblToken) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(xstsAuthUrl);
|
||||
|
||||
Map<Object, Object> dataProp = new ArrayMap<>();
|
||||
dataProp.put("SandboxId", "RETAIL");
|
||||
dataProp.put("UserTokens", Arrays.asList(xblToken));
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("Properties", dataProp);
|
||||
data.put("RelyingParty", "rp://api.minecraftservices.com/");
|
||||
data.put("TokenType", "JWT");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(ofJSONData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = (String) resp.body();
|
||||
GlobalToken token = Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class);
|
||||
String xblXsts = token.Token;
|
||||
String uhs = token.DisplayClaims.xui[0].uhs;
|
||||
|
||||
return new String[]{uhs, xblXsts};
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private String acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(mcLoginUrl);
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(ofJSONData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = (String) resp.body();
|
||||
return Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class).access_token;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private MinecraftAccount checkMcProfile(String mcAccessToken) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(mcProfileUrl);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Authorization", "Bearer " + mcAccessToken)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.GET().build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = (String) resp.body();
|
||||
GlobalToken token = Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class);
|
||||
String uuid = token.id;
|
||||
String uuidDashes = uuid.replaceFirst(
|
||||
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
|
||||
);
|
||||
|
||||
MinecraftAccount acc = new MinecraftAccount();
|
||||
acc.isMicrosoft = true;
|
||||
acc.username = token.name;
|
||||
acc.accessToken = mcAccessToken;
|
||||
acc.profileId = uuidDashes;
|
||||
return acc;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpRequest.BodyPublisher ofJSONData(Map<Object, Object> data) {
|
||||
return HttpRequest.BodyPublishers.ofString(Tools.GLOBAL_GSON.toJson(data));
|
||||
}
|
||||
|
||||
public static HttpRequest.BodyPublisher ofFormData(Map<Object, Object> data) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<Object, Object> entry : data.entrySet()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append("&");
|
||||
}
|
||||
try {
|
||||
builder.append(URLEncoder.encode(entry.getKey().toString(), "UTF-8"));
|
||||
builder.append("=");
|
||||
builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return HttpRequest.BodyPublishers.ofString(builder.toString());
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,7 +286,8 @@ public class Msa {
|
|||
if (errStr.contains("NOT_FOUND") &&
|
||||
errStr.contains("The server has not found anything matching the request URI"))
|
||||
{
|
||||
otherErrStr = "Can't login a demo account!";
|
||||
// TODO localize this
|
||||
otherErrStr = "It seems that this Microsoft Account does not own the game. Make sure that you have bought/migrated to your Microsoft account.";
|
||||
}
|
||||
|
||||
throw new RuntimeException(otherErrStr + "\n\nMSA Error: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + ", error stream:\n" + errStr);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package net.kdt.pojavlaunch.authenticator.microsoft.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class MicrosoftLoginGUIActivity extends AppCompatActivity {
|
||||
public static final int AUTHENTICATE_MICROSOFT_REQUEST = 60;
|
||||
WebView webView;
|
||||
ProgressDialog waitDialog;
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
waitDialog = new ProgressDialog(this);
|
||||
waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
waitDialog.setIndeterminate(true);
|
||||
waitDialog.setMessage(getString(R.string.global_waiting));
|
||||
webView = new WebView(this);
|
||||
webView.setWebViewClient(new WebViewTrackClient());
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
webView.loadUrl("https://login.live.com/oauth20_authorize.srf" +
|
||||
"?client_id=00000000402b5328" +
|
||||
"&response_type=code" +
|
||||
"&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL" +
|
||||
"&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf");
|
||||
setContentView(webView);
|
||||
}
|
||||
class WebViewTrackClient extends WebViewClient {
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if(url.startsWith("ms-xal-00000000402b5328")) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.parse(url));
|
||||
if(waitDialog.isShowing()) waitDialog.dismiss();
|
||||
setResult(Activity.RESULT_OK,data);
|
||||
finish();
|
||||
return true;
|
||||
}else{
|
||||
return super.shouldOverrideUrlLoading(view, url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
//super.onPageStarted(view, url, favicon);
|
||||
if(!waitDialog.isShowing()) waitDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
waitDialog.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
import android.content.*;
|
||||
import android.graphics.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.View.*;
|
||||
import android.widget.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.handleview.*;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener
|
||||
{
|
||||
private Paint mRectPaint;
|
||||
|
||||
private GestureDetector mGestureDetector;
|
||||
private ControlData mProperties;
|
||||
private SelectionEndHandleView mHandleView;
|
||||
|
||||
private boolean mModifiable = false;
|
||||
private boolean mCanTriggerLongClick = true;
|
||||
|
||||
private boolean mChecked = false;
|
||||
|
||||
private float mScaleAt;
|
||||
|
||||
public ControlButton(ControlLayout layout, ControlData properties) {
|
||||
super(layout.getContext());
|
||||
setPadding(4, 4, 4, 4);
|
||||
setWillNotDraw(false);
|
||||
|
||||
mScaleAt = layout.mLayout.scaledAt;
|
||||
|
||||
mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
|
||||
|
||||
if (!LauncherPreferences.PREF_BUTTON_FLAT) {
|
||||
setBackgroundResource(R.drawable.control_button);
|
||||
} else {
|
||||
setBackgroundResource(R.drawable.control_button_black);
|
||||
}
|
||||
setOnLongClickListener(this);
|
||||
|
||||
setProperties(properties);
|
||||
setModified(false);
|
||||
|
||||
mHandleView = new SelectionEndHandleView(this);
|
||||
|
||||
final TypedValue value = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(R.attr.colorAccent, value, true);
|
||||
|
||||
mRectPaint = new Paint();
|
||||
mRectPaint.setColor(value.data);
|
||||
mRectPaint.setAlpha(128);
|
||||
}
|
||||
|
||||
public HandleView getHandleView() {
|
||||
return mHandleView;
|
||||
}
|
||||
|
||||
public ControlData getProperties() {
|
||||
return mProperties;
|
||||
}
|
||||
|
||||
public void setProperties(ControlData properties) {
|
||||
setProperties(properties, true);
|
||||
}
|
||||
|
||||
public void setProperties(ControlData properties, boolean changePos) {
|
||||
mProperties = properties;
|
||||
mProperties.transparency = mProperties.hidden ? 100 : mProperties.transparency;
|
||||
properties.update();
|
||||
|
||||
// com.android.internal.R.string.delete
|
||||
// android.R.string.
|
||||
setText(properties.name);
|
||||
if (changePos) {
|
||||
setTranslationX(moveX = properties.x);
|
||||
setTranslationY(moveY = properties.y);
|
||||
}
|
||||
if (!LauncherPreferences.PREF_BUTTON_FLAT) {
|
||||
setBackgroundResource(mProperties.isRound ? R.drawable.control_button_round : R.drawable.control_button);
|
||||
} else {
|
||||
setBackgroundResource(mProperties.isRound ? R.drawable.control_button_round_black : R.drawable.control_button_black);
|
||||
}
|
||||
if (properties.specialButtonListener == null) {
|
||||
// A non-special button or inside custom controls screen so skip listener
|
||||
} else if (properties.specialButtonListener instanceof View.OnClickListener) {
|
||||
setOnClickListener((View.OnClickListener) properties.specialButtonListener);
|
||||
// setOnLongClickListener(null);
|
||||
// setOnTouchListener(null);
|
||||
} else if (properties.specialButtonListener instanceof View.OnTouchListener) {
|
||||
// setOnLongClickListener(null);
|
||||
setOnTouchListener((View.OnTouchListener) properties.specialButtonListener);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Field " + ControlData.class.getName() + ".specialButtonListener must be View.OnClickListener or View.OnTouchListener, but was " +
|
||||
properties.specialButtonListener.getClass().getName());
|
||||
}
|
||||
|
||||
setLayoutParams(new FrameLayout.LayoutParams((int) properties.width, (int) properties.height));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
super.setLayoutParams(params);
|
||||
|
||||
mProperties.width = params.width;
|
||||
mProperties.height = params.height;
|
||||
|
||||
// Re-calculate position
|
||||
mProperties.update();
|
||||
setTranslationX(mProperties.x);
|
||||
setTranslationY(mProperties.y);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTranslationX(float x) {
|
||||
super.setTranslationX(x);
|
||||
|
||||
if (!mProperties.isDynamicBtn) {
|
||||
mProperties.x = x;
|
||||
mProperties.dynamicX = Float.toString(x / CallbackBridge.physicalWidth) + " * ${screen_width}";
|
||||
setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTranslationY(float y) {
|
||||
super.setTranslationY(y);
|
||||
|
||||
if (!mProperties.isDynamicBtn) {
|
||||
mProperties.y = y;
|
||||
mProperties.dynamicY = Float.toString(y / CallbackBridge.physicalHeight) + " * ${screen_height}";
|
||||
setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProperties() {
|
||||
setProperties(mProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (mChecked) {
|
||||
canvas.drawRect(0, getHeight() - 10 * mScaleAt, getWidth(), getHeight(), mRectPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View p1) {
|
||||
if (mCanTriggerLongClick && mModifiable) {
|
||||
if (mHandleView.isShowing()) {
|
||||
mHandleView.hide();
|
||||
} else {
|
||||
if (getParent() != null) {
|
||||
((ControlLayout) getParent()).hideAllHandleViews();
|
||||
}
|
||||
|
||||
try {
|
||||
mHandleView.show();
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mCanTriggerLongClick;
|
||||
}
|
||||
|
||||
private void setHolding(boolean isDown) {
|
||||
if (mProperties.holdAlt) {
|
||||
CallbackBridge.holdingAlt = isDown;
|
||||
MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT,0,isDown);
|
||||
System.out.println("holdingAlt="+CallbackBridge.holdingAlt);
|
||||
} if (mProperties.keycode == LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK) {
|
||||
CallbackBridge.holdingCapslock = isDown;
|
||||
//MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK,0,isDown);
|
||||
System.out.println("holdingCapslock="+CallbackBridge.holdingCapslock);
|
||||
} if (mProperties.holdCtrl) {
|
||||
CallbackBridge.holdingCtrl = isDown;
|
||||
MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL,0,isDown);
|
||||
System.out.println("holdingCtrl="+CallbackBridge.holdingCtrl);
|
||||
} if (mProperties.keycode == LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK) {
|
||||
CallbackBridge.holdingNumlock = isDown;
|
||||
//MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK,0,isDown);
|
||||
System.out.println("holdingNumlock="+CallbackBridge.holdingNumlock);
|
||||
} if (mProperties.holdShift) {
|
||||
CallbackBridge.holdingShift = isDown;
|
||||
MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT,0,isDown);
|
||||
System.out.println("holdingShift="+CallbackBridge.holdingShift);
|
||||
}
|
||||
}
|
||||
|
||||
private float moveX, moveY;
|
||||
private float downX, downY;
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!mModifiable) {
|
||||
mCanTriggerLongClick = false;
|
||||
if(event.getAction() == MotionEvent.ACTION_MOVE && CallbackBridge.isGrabbing() && mProperties.passThruEnabled) {
|
||||
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
|
||||
if(v != null) {
|
||||
v.dispatchTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (mProperties.keycode >= 0) {
|
||||
if (!mProperties.isToggle) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: // 0
|
||||
case MotionEvent.ACTION_POINTER_DOWN: // 5
|
||||
setHolding(true);
|
||||
MainActivity.sendKeyPress(mProperties.keycode, CallbackBridge.getCurrentMods(), true);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_CANCEL: // 3
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
setHolding(false);
|
||||
MainActivity.sendKeyPress(mProperties.keycode, CallbackBridge.getCurrentMods(), false);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (mGestureDetector.onTouchEvent(event)) {
|
||||
mChecked = !mChecked;
|
||||
invalidate();
|
||||
setHolding(mChecked);
|
||||
MainActivity.sendKeyPress(mProperties.keycode, CallbackBridge.getCurrentMods(), mChecked);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (mGestureDetector.onTouchEvent(event)) {
|
||||
mCanTriggerLongClick = true;
|
||||
onLongClick(this);
|
||||
}
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mCanTriggerLongClick = true;
|
||||
downX = event.getX();
|
||||
downY = event.getY();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mCanTriggerLongClick = false;
|
||||
moveX += event.getX() - downX;
|
||||
moveY += event.getY() - downY;
|
||||
|
||||
if (!mProperties.isDynamicBtn) {
|
||||
setTranslationX(moveX);
|
||||
setTranslationY(moveY);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void setModifiable(boolean z) {
|
||||
mModifiable = z;
|
||||
}
|
||||
|
||||
private void setModified(boolean modified) {
|
||||
if (getParent() != null) {
|
||||
((ControlLayout) getParent()).setModified(modified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,18 +3,17 @@ package net.kdt.pojavlaunch.customcontrols;
|
|||
import android.util.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.objecthunter.exp4j.*;
|
||||
import net.objecthunter.exp4j.function.Function;
|
||||
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class ControlData implements Cloneable
|
||||
{
|
||||
/*
|
||||
public static int pixelOf2dp = (int) Tools.dpToPx(2);
|
||||
public static int pixelOf30dp = (int) Tools.dpToPx(30);
|
||||
public static int pixelOf50dp = Tools.dpToPx(50);;
|
||||
public static int pixelOf80dp;
|
||||
*/
|
||||
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
|
||||
|
||||
public class ControlData {
|
||||
|
||||
public static final int SPECIALBTN_KEYBOARD = -1;
|
||||
public static final int SPECIALBTN_TOGGLECTRL = -2;
|
||||
public static final int SPECIALBTN_MOUSEPRI = -3;
|
||||
|
|
@ -38,22 +37,21 @@ public class ControlData implements Cloneable
|
|||
* bigger device or vice versa.
|
||||
*/
|
||||
public String dynamicX, dynamicY;
|
||||
public boolean isDynamicBtn, isToggle, passThruEnabled, isRound;
|
||||
public boolean isDynamicBtn, isToggle, passThruEnabled;
|
||||
|
||||
public static ControlData[] getSpecialButtons(){
|
||||
if (SPECIAL_BUTTONS == null) {
|
||||
ControlData[] specialButtons = new ControlData[]{
|
||||
new ControlData("Keyboard", SPECIALBTN_KEYBOARD, "${margin} * 3 + ${width} * 2", "${margin}", false),
|
||||
new ControlData("GUI", SPECIALBTN_TOGGLECTRL, "${margin}", "${bottom} - ${margin}"),
|
||||
new ControlData("PRI", SPECIALBTN_MOUSEPRI, "${margin}", "${screen_height} - ${margin} * 3 - ${height} * 3"),
|
||||
new ControlData("SEC", SPECIALBTN_MOUSESEC, "${margin} * 3 + ${width} * 2", "${screen_height} - ${margin} * 3 - ${height} * 3"),
|
||||
new ControlData("Mouse", SPECIALBTN_VIRTUALMOUSE, "${right}", "${margin}", false),
|
||||
SPECIAL_BUTTONS = new ControlData[]{
|
||||
new ControlData("Keyboard", new int[]{SPECIALBTN_KEYBOARD}, "${margin} * 3 + ${width} * 2", "${margin}", false),
|
||||
new ControlData("GUI", new int[]{SPECIALBTN_TOGGLECTRL}, "${margin}", "${bottom} - ${margin}"),
|
||||
new ControlData("PRI", new int[]{SPECIALBTN_MOUSEPRI}, "${margin}", "${screen_height} - ${margin} * 3 - ${height} * 3"),
|
||||
new ControlData("SEC", new int[]{SPECIALBTN_MOUSESEC}, "${margin} * 3 + ${width} * 2", "${screen_height} - ${margin} * 3 - ${height} * 3"),
|
||||
new ControlData("Mouse", new int[]{SPECIALBTN_VIRTUALMOUSE}, "${right}", "${margin}", false),
|
||||
|
||||
new ControlData("MID", SPECIALBTN_MOUSEMID, "${margin}", "${margin}"),
|
||||
new ControlData("SCROLLUP", SPECIALBTN_SCROLLUP, "${margin}", "${margin}"),
|
||||
new ControlData("SCROLLDOWN", SPECIALBTN_SCROLLDOWN, "${margin}", "${margin}")
|
||||
new ControlData("MID", new int[]{SPECIALBTN_MOUSEMID}, "${margin}", "${margin}"),
|
||||
new ControlData("SCROLLUP", new int[]{SPECIALBTN_SCROLLUP}, "${margin}", "${margin}"),
|
||||
new ControlData("SCROLLDOWN", new int[]{SPECIALBTN_SCROLLDOWN}, "${margin}", "${margin}")
|
||||
};
|
||||
SPECIAL_BUTTONS = specialButtons;
|
||||
}
|
||||
|
||||
return SPECIAL_BUTTONS;
|
||||
|
|
@ -72,118 +70,170 @@ public class ControlData implements Cloneable
|
|||
}
|
||||
|
||||
public String name;
|
||||
public float x;
|
||||
public float y;
|
||||
public float width;
|
||||
public float height;
|
||||
public int keycode;
|
||||
public int transparency;
|
||||
@Deprecated
|
||||
public boolean hidden;
|
||||
public boolean holdCtrl;
|
||||
public boolean holdAlt;
|
||||
public boolean holdShift;
|
||||
public Object specialButtonListener;
|
||||
private float width; //Dp instead of Px now
|
||||
private float height; //Dp instead of Px now
|
||||
public int[] keycodes; //Should store up to 4 keys
|
||||
public float opacity; //Alpha value from 0 to 1;
|
||||
public int bgColor;
|
||||
public int strokeColor;
|
||||
public int strokeWidth; //0-100%
|
||||
public float cornerRadius; //0-100%
|
||||
public boolean isSwipeable;
|
||||
|
||||
public ControlData() {
|
||||
this("", LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN, 0, 0);
|
||||
this("button");
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode) {
|
||||
this(name, keycode, 0, 0);
|
||||
public ControlData(String name){
|
||||
this(name, new int[] {});
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode, float x, float y) {
|
||||
this(name, keycode, x, y, Tools.dpToPx(50), Tools.dpToPx(50));
|
||||
public ControlData(String name, int[] keycodes) {
|
||||
this(name, keycodes, Tools.currentDisplayMetrics.widthPixels/2, Tools.currentDisplayMetrics.heightPixels/2);
|
||||
}
|
||||
|
||||
public ControlData(android.content.Context ctx, int resId, int keycode, float x, float y, boolean isSquare) {
|
||||
this(ctx.getResources().getString(resId), keycode, x, y, isSquare);
|
||||
public ControlData(String name, int[] keycodes, float x, float y) {
|
||||
this(name, keycodes, x, y, 50, 50);
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode, float x, float y, boolean isSquare) {
|
||||
this(name, keycode, x, y, isSquare ? Tools.dpToPx(50) : Tools.dpToPx(80), isSquare ? Tools.dpToPx(50) : Tools.dpToPx(30));
|
||||
public ControlData(android.content.Context ctx, int resId, int[] keycodes, float x, float y, boolean isSquare) {
|
||||
this(ctx.getResources().getString(resId), keycodes, x, y, isSquare);
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode, float x, float y, float width, float height) {
|
||||
this(name, keycode, Float.toString(x), Float.toString(y), width, height, false);
|
||||
public ControlData(String name, int[] keycodes, float x, float y, boolean isSquare) {
|
||||
this(name, keycodes, x, y, isSquare ? 50 : 80, isSquare ? 50 : 30);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, float x, float y, float width, float height) {
|
||||
this(name, keycodes, Float.toString(x), Float.toString(y), width, height, false);
|
||||
this.isDynamicBtn = false;
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode, String dynamicX, String dynamicY) {
|
||||
this(name, keycode, dynamicX, dynamicY, Tools.dpToPx(50), Tools.dpToPx(50), false);
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY) {
|
||||
this(name, keycodes, dynamicX, dynamicY, 50, 50, false);
|
||||
}
|
||||
|
||||
public ControlData(android.content.Context ctx, int resId, int keycode, String dynamicX, String dynamicY, boolean isSquare) {
|
||||
this(ctx.getResources().getString(resId), keycode, dynamicX, dynamicY, isSquare);
|
||||
public ControlData(android.content.Context ctx, int resId, int[] keycodes, String dynamicX, String dynamicY, boolean isSquare) {
|
||||
this(ctx.getResources().getString(resId), keycodes, dynamicX, dynamicY, isSquare);
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode, String dynamicX, String dynamicY, boolean isSquare) {
|
||||
this(name, keycode, dynamicX, dynamicY, isSquare ? Tools.dpToPx(50) : Tools.dpToPx(80), isSquare ? Tools.dpToPx(50) : Tools.dpToPx(30), false);
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, boolean isSquare) {
|
||||
this(name, keycodes, dynamicX, dynamicY, isSquare ? 50 : 80, isSquare ? 50 : 30, false);
|
||||
}
|
||||
|
||||
public ControlData(String name, int keycode, String dynamicX, String dynamicY, float width, float height, boolean isToggle) {
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle){
|
||||
this(name, keycodes, dynamicX, dynamicY, width, height, isToggle, 1,0x4D000000, 0xFFFFFFFF,0,0);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle, float opacity, int bgColor, int strokeColor, int strokeWidth, float cornerRadius) {
|
||||
this.name = name;
|
||||
this.keycode = keycode;
|
||||
this.keycodes = inflateKeycodeArray(keycodes);
|
||||
this.dynamicX = dynamicX;
|
||||
this.dynamicY = dynamicY;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.isDynamicBtn = true;
|
||||
this.isDynamicBtn = false;
|
||||
this.isToggle = isToggle;
|
||||
update();
|
||||
}
|
||||
|
||||
public void execute(BaseMainActivity act, boolean isDown) {
|
||||
act.sendKeyPress(keycode, 0, isDown);
|
||||
this.opacity = opacity;
|
||||
this.bgColor = bgColor;
|
||||
this.strokeColor = strokeColor;
|
||||
this.strokeWidth = strokeWidth;
|
||||
this.cornerRadius = cornerRadius;
|
||||
}
|
||||
|
||||
public ControlData clone() {
|
||||
if (this instanceof ControlData) {
|
||||
return new ControlData(name, keycode, ((ControlData) this).dynamicX, ((ControlData) this).dynamicY, width, height, isToggle);
|
||||
} else {
|
||||
return new ControlData(name, keycode, x, y, width, height);
|
||||
//Deep copy constructor
|
||||
public ControlData(ControlData controlData){
|
||||
this(
|
||||
controlData.name,
|
||||
controlData.keycodes,
|
||||
controlData.dynamicX,
|
||||
controlData.dynamicY,
|
||||
controlData.width,
|
||||
controlData.height,
|
||||
controlData.isToggle,
|
||||
controlData.opacity,
|
||||
controlData.bgColor,
|
||||
controlData.strokeColor,
|
||||
controlData.strokeWidth,
|
||||
controlData.cornerRadius
|
||||
);
|
||||
}
|
||||
|
||||
public void execute(boolean isDown) {
|
||||
for(int keycode : keycodes){
|
||||
BaseMainActivity.sendKeyPress(keycode, 0, isDown);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float insertDynamicPos(String dynamicPos) {
|
||||
// Values in the map below may be always changed
|
||||
Map<String, String> keyValueMap = new ArrayMap<>();
|
||||
keyValueMap.put("top", "0");
|
||||
keyValueMap.put("left", "0");
|
||||
keyValueMap.put("right", Float.toString(CallbackBridge.physicalWidth - width));
|
||||
keyValueMap.put("bottom", Float.toString(CallbackBridge.physicalHeight - height));
|
||||
keyValueMap.put("width", Float.toString(width));
|
||||
keyValueMap.put("height", Float.toString(height));
|
||||
keyValueMap.put("right", Float.toString(CallbackBridge.physicalWidth - getWidth()));
|
||||
keyValueMap.put("bottom", Float.toString(CallbackBridge.physicalHeight - getHeight()));
|
||||
keyValueMap.put("width", Float.toString(getWidth()));
|
||||
keyValueMap.put("height", Float.toString(getHeight()));
|
||||
keyValueMap.put("screen_width", Integer.toString(CallbackBridge.physicalWidth));
|
||||
keyValueMap.put("screen_height", Integer.toString(CallbackBridge.physicalHeight));
|
||||
keyValueMap.put("margin", Integer.toString((int) Tools.dpToPx(2)));
|
||||
|
||||
keyValueMap.put("preferred_scale", Float.toString(LauncherPreferences.PREF_BUTTONSIZE));
|
||||
|
||||
// Insert value to ${variable}
|
||||
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, keyValueMap);
|
||||
|
||||
// Calculate, because the dynamic position contains some math equations
|
||||
return calculate(insertedPos);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (keycode < 0 && SPECIAL_BUTTONS != null) {
|
||||
for (ControlData data : getSpecialButtons()) {
|
||||
if (keycode == data.keycode) {
|
||||
specialButtonListener = data.specialButtonListener;
|
||||
}
|
||||
}
|
||||
} if (dynamicX == null) {
|
||||
dynamicX = Float.toString(x);
|
||||
} if (dynamicY == null) {
|
||||
dynamicY = Float.toString(y);
|
||||
}
|
||||
|
||||
x = insertDynamicPos(dynamicX);
|
||||
y = insertDynamicPos(dynamicY);
|
||||
}
|
||||
|
||||
private static float calculate(String math) {
|
||||
return (float) new ExpressionBuilder(math).build().evaluate();
|
||||
return (float) new ExpressionBuilder(math)
|
||||
.function(new Function("dp", 1) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return Tools.pxToDp((float) args[0]);
|
||||
}
|
||||
})
|
||||
.function(new Function("px", 1) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return Tools.dpToPx((float) args[0]);
|
||||
}
|
||||
})
|
||||
.build().evaluate();
|
||||
}
|
||||
|
||||
private static int[] inflateKeycodeArray(int[] keycodes){
|
||||
int[] inflatedArray = new int[]{GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN};
|
||||
System.arraycopy(keycodes, 0, inflatedArray, 0, keycodes.length);
|
||||
return inflatedArray;
|
||||
}
|
||||
|
||||
|
||||
public boolean containsKeycode(int keycodeToCheck){
|
||||
for(int keycode : keycodes)
|
||||
if(keycodeToCheck == keycode)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Getters || setters (with conversion for ease of use)
|
||||
public float getWidth() {
|
||||
return Tools.dpToPx(width);
|
||||
}
|
||||
|
||||
public float getHeight(){
|
||||
return Tools.dpToPx(height);
|
||||
}
|
||||
|
||||
|
||||
public void setWidth(float widthInPx){
|
||||
width = Tools.pxToDp(widthInPx);
|
||||
}
|
||||
|
||||
public void setHeight(float heightInPx){
|
||||
height = Tools.pxToDp(heightInPx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.DOWN;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.LEFT;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.RIGHT;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.UP;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ControlDrawerData {
|
||||
|
||||
public ArrayList<ControlData> buttonProperties;
|
||||
public ControlData properties;
|
||||
public Orientation orientation;
|
||||
|
||||
public enum Orientation {
|
||||
DOWN,
|
||||
LEFT,
|
||||
UP,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
public static Orientation[] getOrientations(){
|
||||
return new Orientation[]{DOWN,LEFT,UP,RIGHT};
|
||||
}
|
||||
|
||||
public static int orientationToInt(Orientation orientation){
|
||||
switch (orientation){
|
||||
case DOWN:
|
||||
return 0;
|
||||
case LEFT:
|
||||
return 1;
|
||||
case UP:
|
||||
return 2;
|
||||
case RIGHT:
|
||||
return 3;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Orientation intToOrientation(int by){
|
||||
switch (by){
|
||||
case 0:
|
||||
return Orientation.DOWN;
|
||||
case 1:
|
||||
return Orientation.LEFT;
|
||||
case 2:
|
||||
return Orientation.UP;
|
||||
case 3:
|
||||
return RIGHT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ControlDrawerData(){
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
public ControlDrawerData(ArrayList<ControlData> buttonProperties){
|
||||
this(buttonProperties, new ControlData("Drawer", new int[] {}, Tools.currentDisplayMetrics.widthPixels/2, Tools.currentDisplayMetrics.heightPixels/2));
|
||||
}
|
||||
|
||||
public ControlDrawerData(ArrayList<ControlData> buttonProperties, ControlData properties){
|
||||
this(buttonProperties, properties, Orientation.LEFT);
|
||||
}
|
||||
|
||||
|
||||
public ControlDrawerData(ArrayList<ControlData> buttonProperties, ControlData properties, Orientation orientation){
|
||||
this.buttonProperties = buttonProperties;
|
||||
this.properties = properties;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public ControlDrawerData(ControlDrawerData drawerData){
|
||||
buttonProperties = new ArrayList<>(drawerData.buttonProperties.size());
|
||||
for(ControlData controlData : drawerData.buttonProperties){
|
||||
buttonProperties.add(new ControlData(controlData));
|
||||
}
|
||||
properties = new ControlData(drawerData.properties);
|
||||
orientation = drawerData.orientation;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,11 +6,15 @@ import android.widget.*;
|
|||
import com.google.gson.*;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.handleview.HandleView;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class ControlLayout extends FrameLayout
|
||||
{
|
||||
|
|
@ -28,59 +32,59 @@ public class ControlLayout extends FrameLayout
|
|||
}
|
||||
|
||||
public void hideAllHandleViews() {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View view = getChildAt(i);
|
||||
if (view instanceof ControlButton) {
|
||||
((ControlButton) view).getHandleView().hide();
|
||||
}
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
HandleView hv = button.getHandleView();
|
||||
if(hv != null) hv.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadLayout(String jsonPath) throws IOException, JsonSyntaxException {
|
||||
loadLayout(Tools.GLOBAL_GSON.fromJson(Tools.read(jsonPath), CustomControls.class));
|
||||
CustomControls layout = LayoutConverter.loadAndConvertIfNecessary(jsonPath);
|
||||
if(layout != null) {
|
||||
loadLayout(layout);
|
||||
}else{
|
||||
throw new IOException("Unsupported control layout version");
|
||||
}
|
||||
}
|
||||
|
||||
public void loadLayout(CustomControls controlLayout) {
|
||||
if (mModifiable) {
|
||||
if (mModifiable)
|
||||
hideAllHandleViews();
|
||||
}
|
||||
/*if (getChildAt(0) instanceof MinecraftGLView) {
|
||||
View viewGL = getChildAt(0);
|
||||
View viewTouchpad = getChildAt(1);
|
||||
removeAllViews();
|
||||
addView(viewGL);
|
||||
addView(viewTouchpad);
|
||||
} else {
|
||||
removeAllViews();*/
|
||||
removeAllButtons();
|
||||
//}
|
||||
if (mLayout != null) {
|
||||
mLayout.mControlDataList = null;
|
||||
mLayout = null;
|
||||
}
|
||||
|
||||
removeAllButtons();
|
||||
if(mLayout != null) {
|
||||
mLayout.mControlDataList = null;
|
||||
mLayout = null;
|
||||
}
|
||||
|
||||
System.gc();
|
||||
mapTable.clear();
|
||||
|
||||
// Cleanup buttons only when input layout is null
|
||||
if (controlLayout == null) return;
|
||||
|
||||
mLayout = controlLayout;
|
||||
|
||||
|
||||
//CONTROL BUTTON
|
||||
for (ControlData button : controlLayout.mControlDataList) {
|
||||
button.isHideable = button.keycode != ControlData.SPECIALBTN_TOGGLECTRL && button.keycode != ControlData.SPECIALBTN_VIRTUALMOUSE;
|
||||
button.width = button.width / controlLayout.scaledAt * LauncherPreferences.PREF_BUTTONSIZE;
|
||||
button.height = button.height / controlLayout.scaledAt * LauncherPreferences.PREF_BUTTONSIZE;
|
||||
if (!button.isDynamicBtn) {
|
||||
button.dynamicX = Float.toString(button.x / CallbackBridge.physicalWidth) + " * ${screen_width}";
|
||||
button.dynamicY = Float.toString(button.y / CallbackBridge.physicalHeight) + " * ${screen_height}";
|
||||
}
|
||||
button.update();
|
||||
addControlView(button);
|
||||
}
|
||||
|
||||
//CONTROL DRAWER
|
||||
for(ControlDrawerData drawerData : controlLayout.mDrawerDataList){
|
||||
ControlDrawer drawer = addDrawerView(drawerData);
|
||||
if(mModifiable) drawer.areButtonsVisible = true;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
mLayout.scaledAt = LauncherPreferences.PREF_BUTTONSIZE;
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
} // loadLayout
|
||||
|
||||
//CONTROL BUTTON
|
||||
public void addControlButton(ControlData controlButton) {
|
||||
mLayout.mControlDataList.add(controlButton);
|
||||
addControlView(controlButton);
|
||||
|
|
@ -90,7 +94,7 @@ public class ControlLayout extends FrameLayout
|
|||
final ControlButton view = new ControlButton(this, controlButton);
|
||||
view.setModifiable(mModifiable);
|
||||
if (!mModifiable) {
|
||||
view.setAlpha(1f - view.getProperties().transparency / 100f);
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}
|
||||
|
|
@ -98,22 +102,67 @@ public class ControlLayout extends FrameLayout
|
|||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
// CONTROL DRAWER
|
||||
public void addDrawer(ControlDrawerData drawerData){
|
||||
mLayout.mDrawerDataList.add(drawerData);
|
||||
addDrawerView();
|
||||
}
|
||||
|
||||
private void addDrawerView(){
|
||||
addDrawerView(null);
|
||||
}
|
||||
|
||||
private ControlDrawer addDrawerView(ControlDrawerData drawerData){
|
||||
|
||||
final ControlDrawer view = new ControlDrawer(this,drawerData == null ? mLayout.mDrawerDataList.get(mLayout.mDrawerDataList.size()-1) : drawerData);
|
||||
view.setModifiable(mModifiable);
|
||||
if (!mModifiable) {
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}
|
||||
addView(view);
|
||||
//CONTROL SUB BUTTON
|
||||
for (ControlData subButton : view.getDrawerData().buttonProperties) {
|
||||
addSubView(view, subButton);
|
||||
}
|
||||
|
||||
setModified(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
//CONTROL SUB-BUTTON
|
||||
public void addSubButton(ControlDrawer drawer, ControlData controlButton){
|
||||
//Yep there isn't much here
|
||||
drawer.getDrawerData().buttonProperties.add(controlButton);
|
||||
addSubView(drawer, drawer.getDrawerData().buttonProperties.get(drawer.getDrawerData().buttonProperties.size()-1 ));
|
||||
}
|
||||
|
||||
public void addSubView(ControlDrawer drawer, ControlData controlButton){
|
||||
final ControlSubButton view = new ControlSubButton(this, controlButton, drawer);
|
||||
view.setModifiable(mModifiable);
|
||||
if (!mModifiable) {
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}
|
||||
drawer.addButton(view);
|
||||
addView(view);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
private void removeAllButtons() {
|
||||
List<View> viewList = new ArrayList<>();
|
||||
View v;
|
||||
for(int i = 0; i < getChildCount(); i++) {
|
||||
v = getChildAt(i);
|
||||
if(v instanceof ControlButton) viewList.add(v);
|
||||
for(View v : getButtonChildren()){
|
||||
removeView(v);
|
||||
}
|
||||
v = null;
|
||||
for(View v2 : viewList) {
|
||||
removeView(v2);
|
||||
}
|
||||
viewList = null;
|
||||
|
||||
System.gc();
|
||||
//i wanna be sure that all the removed Views will be removed after a reload
|
||||
//because if frames will slowly go down after many control changes it will be warm and bad
|
||||
}
|
||||
|
||||
public void removeControlButton(ControlButton controlButton) {
|
||||
mLayout.mControlDataList.remove(controlButton.getProperties());
|
||||
controlButton.setVisibility(View.GONE);
|
||||
|
|
@ -122,6 +171,29 @@ public class ControlLayout extends FrameLayout
|
|||
setModified(true);
|
||||
}
|
||||
|
||||
public void removeControlDrawer(ControlDrawer controlDrawer){
|
||||
for(ControlSubButton subButton : controlDrawer.buttons){
|
||||
subButton.setVisibility(GONE);
|
||||
removeView(subButton);
|
||||
}
|
||||
mLayout.mDrawerDataList.remove(controlDrawer.getDrawerData());
|
||||
controlDrawer.setVisibility(GONE);
|
||||
removeView(controlDrawer);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
public void removeControlSubButton(ControlSubButton subButton){
|
||||
subButton.parentDrawer.drawerData.buttonProperties.remove(subButton.getProperties());
|
||||
subButton.parentDrawer.buttons.remove(subButton);
|
||||
|
||||
subButton.parentDrawer.syncButtons();
|
||||
|
||||
subButton.setVisibility(GONE);
|
||||
removeView(subButton);
|
||||
|
||||
}
|
||||
|
||||
public void saveLayout(String path) throws Exception {
|
||||
mLayout.save(path);
|
||||
setModified(false);
|
||||
|
|
@ -130,34 +202,95 @@ public class ControlLayout extends FrameLayout
|
|||
public void setActivity(CustomControlsActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
public void toggleControlVisible() {
|
||||
if (mModifiable) return; // Not using on custom controls activity
|
||||
|
||||
|
||||
public void toggleControlVisible(){
|
||||
mControlVisible = !mControlVisible;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View view = getChildAt(i);
|
||||
if (view instanceof ControlButton && ((ControlButton) view).getProperties().isHideable) {
|
||||
((ControlButton) view).setVisibility(mControlVisible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
setControlVisible(mControlVisible);
|
||||
}
|
||||
|
||||
public float getLayoutScale(){
|
||||
return mLayout.scaledAt;
|
||||
}
|
||||
|
||||
public void setControlVisible(boolean isVisible) {
|
||||
if (mModifiable) return; // Not using on custom controls activity
|
||||
|
||||
mControlVisible = isVisible;
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
button.setVisible(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
public void setModifiable(boolean z) {
|
||||
mModifiable = z;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View v = getChildAt(i);
|
||||
if (v instanceof ControlButton) {
|
||||
ControlButton cv = ((ControlButton) v);
|
||||
cv.setModifiable(z);
|
||||
if (!z) {
|
||||
cv.setAlpha(1f - cv.getProperties().transparency / 100f);
|
||||
}
|
||||
}
|
||||
public void setModifiable(boolean isModifiable) {
|
||||
mModifiable = isModifiable;
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
button.setModifiable(isModifiable);
|
||||
if (!isModifiable)
|
||||
button.setAlpha(button.getProperties().opacity);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setModified(boolean z) {
|
||||
if (mActivity != null) mActivity.isModified = z;
|
||||
public boolean getModifiable(){
|
||||
return mModifiable;
|
||||
}
|
||||
|
||||
public void setModified(boolean isModified) {
|
||||
if (mActivity != null) mActivity.isModified = isModified;
|
||||
|
||||
}
|
||||
|
||||
public ArrayList<ControlButton> getButtonChildren(){
|
||||
ArrayList<ControlButton> children = new ArrayList<>();
|
||||
for(int i=0; i<getChildCount(); ++i){
|
||||
View v = getChildAt(i);
|
||||
if(v instanceof ControlButton)
|
||||
children.add(((ControlButton) v));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
HashMap<View, ControlButton> mapTable = new HashMap<>();
|
||||
//While this is called onTouch, this should only be called from a ControlButton.
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
ControlButton lastControlButton = mapTable.get(v);
|
||||
|
||||
//Check if the action is cancelling, reset the lastControl button associated to the view
|
||||
if(ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL){
|
||||
if(lastControlButton != null) lastControlButton.sendKeyPresses(false);
|
||||
mapTable.put(v, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ev.getActionMasked() != MotionEvent.ACTION_MOVE) return false;
|
||||
|
||||
//Optimization pass to avoid looking at all children again
|
||||
if(lastControlButton != null){
|
||||
if( ev.getRawX() > lastControlButton.getX() && ev.getRawX() < lastControlButton.getX() + lastControlButton.getWidth() &&
|
||||
ev.getRawY() > lastControlButton.getY() && ev.getRawY() < lastControlButton.getY() + lastControlButton.getHeight()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Release last keys
|
||||
if (lastControlButton != null) lastControlButton.sendKeyPresses(false);
|
||||
mapTable.put(v, null);
|
||||
|
||||
//Look for another SWIPEABLE button
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
if(!button.getProperties().isSwipeable) continue;
|
||||
|
||||
if( ev.getRawX() > button.getX() && ev.getRawX() < button.getX() + button.getWidth() &&
|
||||
ev.getRawY() > button.getY() && ev.getRawY() < button.getY() + button.getHeight()){
|
||||
|
||||
//Press the new key
|
||||
if(!button.equals(lastControlButton)){
|
||||
button.sendKeyPresses(true);
|
||||
|
||||
mapTable.put(v, button);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,64 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
import android.content.*;
|
||||
import com.google.gson.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class CustomControls
|
||||
{
|
||||
public class CustomControls {
|
||||
public int version = -1;
|
||||
public float scaledAt;
|
||||
public List<ControlData> mControlDataList;
|
||||
public List<ControlDrawerData> mDrawerDataList;
|
||||
public CustomControls() {
|
||||
this(new ArrayList<ControlData>());
|
||||
this(new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
public CustomControls(List<ControlData> mControlDataList) {
|
||||
|
||||
|
||||
|
||||
public CustomControls(List<ControlData> mControlDataList, List<ControlDrawerData> mDrawerDataList) {
|
||||
this.mControlDataList = mControlDataList;
|
||||
this.mDrawerDataList = mDrawerDataList;
|
||||
this.scaledAt = 100f;
|
||||
}
|
||||
|
||||
// Generate default control
|
||||
public CustomControls(Context ctx) {
|
||||
this();
|
||||
this.mControlDataList.add(ControlData.getSpecialButtons()[0].clone()); // Keyboard
|
||||
this.mControlDataList.add(ControlData.getSpecialButtons()[1].clone()); // GUI
|
||||
this.mControlDataList.add(ControlData.getSpecialButtons()[2].clone()); // Primary Mouse mControlDataList
|
||||
this.mControlDataList.add(ControlData.getSpecialButtons()[3].clone()); // Secondary Mouse mControlDataList
|
||||
this.mControlDataList.add(ControlData.getSpecialButtons()[4].clone()); // Virtual mouse toggle
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[0])); // Keyboard
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[1])); // GUI
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[2])); // Primary Mouse mControlDataList
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[3])); // Secondary Mouse mControlDataList
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[4])); // Virtual mouse toggle
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_debug, LWJGLGLFWKeycode.GLFW_KEY_F3, "${margin}", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_chat, LWJGLGLFWKeycode.GLFW_KEY_T, "${margin} * 2 + ${width}", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_listplayers, LWJGLGLFWKeycode.GLFW_KEY_TAB, "${margin} * 4 + ${width} * 3", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_thirdperson, LWJGLGLFWKeycode.GLFW_KEY_F5, "${margin}", "${height} + ${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_debug, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F3}, "${margin}", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_chat, new int[]{LWJGLGLFWKeycode.GLFW_KEY_T}, "${margin} * 2 + ${width}", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_listplayers, new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB}, "${margin} * 4 + ${width} * 3", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_thirdperson, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F5}, "${margin}", "${height} + ${margin}", false));
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_up, LWJGLGLFWKeycode.GLFW_KEY_W, "${margin} * 2 + ${width}", "${bottom} - ${margin} * 3 - ${height} * 2", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_left, LWJGLGLFWKeycode.GLFW_KEY_A, "${margin}", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_down, LWJGLGLFWKeycode.GLFW_KEY_S, "${margin} * 2 + ${width}", "${bottom} - ${margin}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_right, LWJGLGLFWKeycode.GLFW_KEY_D, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_up, new int[]{LWJGLGLFWKeycode.GLFW_KEY_W}, "${margin} * 2 + ${width}", "${bottom} - ${margin} * 3 - ${height} * 2", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_left, new int[]{LWJGLGLFWKeycode.GLFW_KEY_A}, "${margin}", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_down, new int[]{LWJGLGLFWKeycode.GLFW_KEY_S}, "${margin} * 2 + ${width}", "${bottom} - ${margin}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_right, new int[]{LWJGLGLFWKeycode.GLFW_KEY_D}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_inventory, LWJGLGLFWKeycode.GLFW_KEY_E, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_inventory, new int[]{LWJGLGLFWKeycode.GLFW_KEY_E}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin}", true));
|
||||
|
||||
ControlData shiftData = new ControlData(ctx, R.string.control_shift, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT, "${margin} * 2 + ${width}", "${screen_height} - ${margin} * 2 - ${height} * 2", true);
|
||||
ControlData shiftData = new ControlData(ctx, R.string.control_shift, new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT}, "${margin} * 2 + ${width}", "${screen_height} - ${margin} * 2 - ${height} * 2", true);
|
||||
shiftData.isToggle = true;
|
||||
this.mControlDataList.add(shiftData);
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_jump, LWJGLGLFWKeycode.GLFW_KEY_SPACE, "${right} - ${margin} * 2 - ${width}", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_jump, new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE}, "${right} - ${margin} * 2 - ${width}", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
|
||||
//The default controls are conform to the V2
|
||||
version = 3;
|
||||
}
|
||||
|
||||
public ControlData findControlData(int keycode) {
|
||||
for (ControlData data : mControlDataList) {
|
||||
if (data.keycode == keycode) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void save(String path) throws Exception {
|
||||
public void save(String path) throws IOException {
|
||||
//Current version is the V2.3 so the version as to be marked as 3 !
|
||||
version = 3;
|
||||
|
||||
Tools.write(path, Tools.GLOBAL_GSON.toJson(this));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LayoutConverter {
|
||||
public static boolean convertLookType = false; //false = flat; true = classic
|
||||
public static CustomControls loadAndConvertIfNecessary(String jsonPath) throws IOException, JsonSyntaxException {
|
||||
String jsonLayoutData = Tools.read(jsonPath);
|
||||
try {
|
||||
JSONObject layoutJobj = new JSONObject(jsonLayoutData);
|
||||
|
||||
if(!layoutJobj.has("version")) { //v1 layout
|
||||
CustomControls layout = LayoutConverter.convertV1Layout(layoutJobj);
|
||||
layout.save(jsonPath);
|
||||
return layout;
|
||||
}else if (layoutJobj.getInt("version") == 2) {
|
||||
CustomControls layout = LayoutConverter.convertV2Layout(layoutJobj);
|
||||
layout.save(jsonPath);
|
||||
return layout;
|
||||
}else if (layoutJobj.getInt("version") == 3) {
|
||||
return Tools.GLOBAL_GSON.fromJson(jsonLayoutData, CustomControls.class);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}catch (JSONException e) {
|
||||
throw new JsonSyntaxException("Failed to load",e);
|
||||
}
|
||||
}
|
||||
public static CustomControls convertV2Layout(JSONObject oldLayoutJson) throws JSONException {
|
||||
CustomControls layout = Tools.GLOBAL_GSON.fromJson(oldLayoutJson.toString(), CustomControls.class);
|
||||
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
|
||||
layout.mControlDataList = new ArrayList<>(layoutMainArray.length());
|
||||
for(int i = 0; i < layoutMainArray.length(); i++) {
|
||||
JSONObject button = layoutMainArray.getJSONObject(i);
|
||||
ControlData n_button = Tools.GLOBAL_GSON.fromJson(button.toString(), ControlData.class);
|
||||
if((n_button.dynamicX == null || n_button.dynamicX.isEmpty())&&button.has("x")) {
|
||||
double buttonC = button.getDouble("x");
|
||||
double ratio = buttonC/CallbackBridge.physicalWidth;
|
||||
n_button.dynamicX = ratio + " * ${screen_width}";
|
||||
}
|
||||
if((n_button.dynamicY == null || n_button.dynamicY.isEmpty())&&button.has("y")) {
|
||||
double buttonC = button.getDouble("y");
|
||||
double ratio = buttonC/CallbackBridge.physicalHeight;
|
||||
n_button.dynamicY = ratio + " * ${screen_height}";
|
||||
}
|
||||
layout.mControlDataList.add(n_button);
|
||||
}
|
||||
JSONArray layoutDrawerArray = oldLayoutJson.getJSONArray("mDrawerDataList");
|
||||
layout.mDrawerDataList = new ArrayList<>();
|
||||
for(int i = 0; i < layoutDrawerArray.length(); i++) {
|
||||
JSONObject button = layoutDrawerArray.getJSONObject(i);
|
||||
JSONObject buttonProperties = button.getJSONObject("properties");
|
||||
ControlDrawerData n_button = Tools.GLOBAL_GSON.fromJson(button.toString(), ControlDrawerData.class);
|
||||
if((n_button.properties.dynamicX == null || n_button.properties.dynamicX.isEmpty())&&buttonProperties.has("x")) {
|
||||
double buttonC = buttonProperties.getDouble("x");
|
||||
double ratio = buttonC/CallbackBridge.physicalWidth;
|
||||
n_button.properties.dynamicX = ratio + " * ${screen_width}";
|
||||
}
|
||||
if((n_button.properties.dynamicY == null || n_button.properties.dynamicY.isEmpty())&&buttonProperties.has("y")) {
|
||||
double buttonC = buttonProperties.getDouble("y");
|
||||
double ratio = buttonC/CallbackBridge.physicalHeight;
|
||||
n_button.properties.dynamicY = ratio + " * ${screen_height}";
|
||||
}
|
||||
layout.mDrawerDataList.add(n_button);
|
||||
}
|
||||
layout.version = 3;
|
||||
return layout;
|
||||
}
|
||||
public static CustomControls convertV1Layout(JSONObject oldLayoutJson) throws JSONException {
|
||||
CustomControls empty = new CustomControls();
|
||||
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
|
||||
for(int i = 0; i < layoutMainArray.length(); i++) {
|
||||
JSONObject button = layoutMainArray.getJSONObject(i);
|
||||
ControlData n_button = new ControlData();
|
||||
int[] keycodes = new int[] {LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN};
|
||||
n_button.isDynamicBtn = button.getBoolean("isDynamicBtn");
|
||||
n_button.dynamicX = button.getString("dynamicX");
|
||||
n_button.dynamicY = button.getString("dynamicY");
|
||||
if((n_button.dynamicX == null || n_button.dynamicX.isEmpty())&&button.has("x")) {
|
||||
double buttonC = button.getDouble("x");
|
||||
double ratio = buttonC/CallbackBridge.physicalWidth;
|
||||
n_button.dynamicX = ratio + " * ${screen_width}";
|
||||
}
|
||||
if((n_button.dynamicY == null || n_button.dynamicY.isEmpty())&&button.has("y")) {
|
||||
double buttonC = button.getDouble("y");
|
||||
double ratio = buttonC/CallbackBridge.physicalHeight;
|
||||
n_button.dynamicY = ratio + " * ${screen_height}";
|
||||
}
|
||||
n_button.name = button.getString("name");
|
||||
n_button.opacity = ((float)((button.getInt("transparency")-100)*-1))/100f;
|
||||
n_button.passThruEnabled = button.getBoolean("passThruEnabled");
|
||||
n_button.isToggle = button.getBoolean("isToggle");
|
||||
n_button.setHeight(button.getInt("height"));
|
||||
n_button.setWidth(button.getInt("width"));
|
||||
if(convertLookType) {
|
||||
n_button.strokeColor = 0xdd7f7f7f;
|
||||
n_button.bgColor = 0x807f7f7f;
|
||||
n_button.strokeWidth = 10;
|
||||
}else{
|
||||
n_button.bgColor = 0x4d000000;
|
||||
n_button.strokeWidth = 0;
|
||||
}
|
||||
if(button.getBoolean("isRound")) { n_button.cornerRadius = 35f; }
|
||||
int next_idx = 0;
|
||||
if(button.getBoolean("holdShift")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT; next_idx++; }
|
||||
if(button.getBoolean("holdCtrl")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL; next_idx++; }
|
||||
if(button.getBoolean("holdAlt")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT; next_idx++; }
|
||||
keycodes[next_idx] = button.getInt("keycode");
|
||||
n_button.keycodes = keycodes;
|
||||
empty.mControlDataList.add(n_button);
|
||||
}
|
||||
empty.scaledAt = (float)oldLayoutJson.getDouble("scaledAt");
|
||||
empty.version = 3;
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
|
||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.BaseMainActivity;
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
/**
|
||||
* This class is intended for sending characters used in chat via the virtual keyboard
|
||||
*/
|
||||
public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText {
|
||||
public TouchCharInput(@NonNull Context context) {
|
||||
super(context);
|
||||
setup();
|
||||
}
|
||||
public TouchCharInput(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setup();
|
||||
}
|
||||
public TouchCharInput(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setup();
|
||||
}
|
||||
|
||||
|
||||
private boolean isDoingInternalChanges = false;
|
||||
|
||||
/**
|
||||
* We take the new chars, and send them to the game.
|
||||
* If less chars are present, remove some.
|
||||
* The text is always cleaned up.
|
||||
*/
|
||||
@Override
|
||||
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
|
||||
super.onTextChanged(text, start, lengthBefore, lengthAfter);
|
||||
if(isDoingInternalChanges){
|
||||
isDoingInternalChanges = false;
|
||||
return;
|
||||
}
|
||||
if(lengthAfter < lengthBefore){
|
||||
for(int i=0; i< lengthBefore-lengthAfter; ++i){
|
||||
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
|
||||
}
|
||||
}else{
|
||||
for(int i=lengthBefore, index=lengthBefore+start; i < lengthAfter; ++i){
|
||||
//I didn't know F25 existed before that. I just need a full fat keycode for mc 1.13+
|
||||
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_F25, text.charAt(index), 0, 0, true);
|
||||
index ++;
|
||||
}
|
||||
}
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When we change from app to app, the keyboard gets disabled.
|
||||
* So, we disable the object
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts the back key to disable focus
|
||||
* Does not affect the rest of the activity.
|
||||
*/
|
||||
@Override
|
||||
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
|
||||
disable();
|
||||
}
|
||||
return super.onKeyPreIme(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int index) {
|
||||
super.setSelection(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle on and off the soft keyboard, depending of the state
|
||||
*
|
||||
* @return if the keyboard is set to be shown.
|
||||
*/
|
||||
public boolean switchKeyboardState(){
|
||||
//If an hard keyboard is present, never trigger the soft one
|
||||
if(hasFocus()
|
||||
|| (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY
|
||||
&& getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES)){
|
||||
clear();
|
||||
disable();
|
||||
return false;
|
||||
}else{
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
enable();
|
||||
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the EditText from any leftover inputs
|
||||
* It does not affect the in-game input
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void clear(){
|
||||
isDoingInternalChanges = true;
|
||||
//Braille space, doesn't trigger keyboard auto-complete
|
||||
setText("\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800");
|
||||
setSelection(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the enter key.
|
||||
*/
|
||||
private void sendEnter(){
|
||||
BaseMainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regain ability to exist, take focus and have some text being input
|
||||
*/
|
||||
public void enable(){
|
||||
setEnabled(true);
|
||||
setFocusable(true);
|
||||
setVisibility(VISIBLE);
|
||||
requestFocus();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lose ability to exist, take focus and have some text being input
|
||||
*/
|
||||
public void disable(){
|
||||
clear();
|
||||
setVisibility(GONE);
|
||||
clearFocus();
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This function deals with anything that has to be executed when the constructor is called
|
||||
*/
|
||||
private void setup(){
|
||||
setOnEditorActionListener((textView, i, keyEvent) -> {
|
||||
sendEnter();
|
||||
clear();
|
||||
disable();
|
||||
return false;
|
||||
});
|
||||
clear();
|
||||
disable();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.buttons;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.*;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.View.*;
|
||||
import android.widget.*;
|
||||
|
||||
|
||||
|
||||
import androidx.core.math.MathUtils;
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
import net.kdt.pojavlaunch.customcontrols.handleview.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
import static net.kdt.pojavlaunch.BaseMainActivity.sendMouseButton;
|
||||
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener
|
||||
{
|
||||
private final Paint mRectPaint = new Paint();;
|
||||
|
||||
protected GestureDetector mGestureDetector;
|
||||
protected ControlData mProperties;
|
||||
protected SelectionEndHandleView mHandleView;
|
||||
|
||||
protected boolean mModifiable = false;
|
||||
protected boolean mCanTriggerLongClick = true;
|
||||
|
||||
protected boolean isToggled = false;
|
||||
protected boolean isPointerOutOfBounds = false;
|
||||
|
||||
public ControlButton(ControlLayout layout, ControlData properties) {
|
||||
super(layout.getContext());
|
||||
setPadding(4, 4, 4, 4);
|
||||
|
||||
setOnLongClickListener(this);
|
||||
|
||||
//When a button is created, the width/height has yet to be processed to fit the scaling.
|
||||
setProperties(preProcessProperties(properties, layout));
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
public HandleView getHandleView() {
|
||||
return mHandleView;
|
||||
}
|
||||
|
||||
public ControlData getProperties() {
|
||||
return mProperties;
|
||||
}
|
||||
|
||||
public void setProperties(ControlData properties) {
|
||||
setProperties(properties, true);
|
||||
}
|
||||
|
||||
public ControlData preProcessProperties(ControlData properties, ControlLayout layout){
|
||||
//When a button is created, properties have to be modified to fit the screen.
|
||||
//Size
|
||||
properties.setWidth(properties.getWidth() / layout.getLayoutScale() * PREF_BUTTONSIZE);
|
||||
properties.setHeight(properties.getHeight() / layout.getLayoutScale() * PREF_BUTTONSIZE);
|
||||
|
||||
//Visibility
|
||||
properties.isHideable = !properties.containsKeycode(ControlData.SPECIALBTN_TOGGLECTRL) && !properties.containsKeycode(ControlData.SPECIALBTN_VIRTUALMOUSE);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(ControlData properties, boolean changePos) {
|
||||
mProperties = properties;
|
||||
|
||||
if(mProperties.isToggle){
|
||||
//For the toggle layer
|
||||
final TypedValue value = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(R.attr.colorAccent, value, true);
|
||||
mRectPaint.setColor(value.data);
|
||||
mRectPaint.setAlpha(128);
|
||||
}else{
|
||||
mRectPaint.setColor(Color.WHITE);
|
||||
mRectPaint.setAlpha(60);
|
||||
}
|
||||
|
||||
setText(properties.name);
|
||||
|
||||
if (changePos) {
|
||||
setX(properties.insertDynamicPos(mProperties.dynamicX));
|
||||
setY(properties.insertDynamicPos(mProperties.dynamicY));
|
||||
}
|
||||
|
||||
setLayoutParams(new FrameLayout.LayoutParams((int) properties.getWidth(), (int) properties.getHeight() ));
|
||||
}
|
||||
|
||||
public void setBackground(){
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setColor(mProperties.bgColor);
|
||||
gd.setStroke(computeStrokeWidth(mProperties.strokeWidth), mProperties.strokeColor);
|
||||
gd.setCornerRadius(computeCornerRadius(mProperties.cornerRadius));
|
||||
|
||||
setBackground(gd);
|
||||
}
|
||||
|
||||
public void setModifiable(boolean isModifiable) {
|
||||
mModifiable = isModifiable;
|
||||
}
|
||||
|
||||
private void setModified(boolean modified) {
|
||||
if (getParent() != null)
|
||||
((ControlLayout) getParent()).setModified(modified);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
super.setLayoutParams(params);
|
||||
|
||||
mProperties.setWidth(params.width);
|
||||
mProperties.setHeight(params.height);
|
||||
setBackground();
|
||||
|
||||
// Re-calculate position
|
||||
if(!mProperties.isDynamicBtn){
|
||||
setX(getX());
|
||||
setY(getY());
|
||||
}else {
|
||||
setX(mProperties.insertDynamicPos(mProperties.dynamicX));
|
||||
setY(mProperties.insertDynamicPos(mProperties.dynamicY));
|
||||
}
|
||||
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
public void setVisible(boolean isVisible){
|
||||
if(mProperties.isHideable)
|
||||
setVisibility(isVisible ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(float x) {
|
||||
super.setX(x);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(float y) {
|
||||
super.setY(y);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the dynamic equation on the x axis.
|
||||
* @param dynamicX The equation to compute the position from
|
||||
*/
|
||||
public void setDynamicX(String dynamicX){
|
||||
mProperties.dynamicX = dynamicX;
|
||||
setX(mProperties.insertDynamicPos(dynamicX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the dynamic equation on the y axis.
|
||||
* @param dynamicY The equation to compute the position from
|
||||
*/
|
||||
public void setDynamicY(String dynamicY){
|
||||
mProperties.dynamicY = dynamicY;
|
||||
setY(mProperties.insertDynamicPos(dynamicY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a dynamic equation from an absolute position, used to scale properly across devices
|
||||
* @param x The absolute position on the horizontal axis
|
||||
* @return The equation as a String
|
||||
*/
|
||||
public String generateDynamicX(float x){
|
||||
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
|
||||
return (x + mProperties.getWidth()) / CallbackBridge.physicalWidth + " * ${screen_width} - ${width}";
|
||||
}else{
|
||||
return x / CallbackBridge.physicalWidth + " * ${screen_width}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a dynamic equation from an absolute position, used to scale properly across devices
|
||||
* @param y The absolute position on the vertical axis
|
||||
* @return The equation as a String
|
||||
*/
|
||||
public String generateDynamicY(float y){
|
||||
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
|
||||
return (y + mProperties.getHeight()) / CallbackBridge.physicalHeight + " * ${screen_height} - ${height}";
|
||||
}else{
|
||||
return y / CallbackBridge.physicalHeight + " * ${screen_height}";
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProperties() {
|
||||
setProperties(mProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (isToggled || (!mProperties.isToggle && isActivated()))
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), mProperties.cornerRadius, mProperties.cornerRadius, mRectPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (mCanTriggerLongClick && mModifiable) {
|
||||
//Instantiate on need only
|
||||
if(mHandleView == null) mHandleView = new SelectionEndHandleView(this);
|
||||
|
||||
if (mHandleView.isShowing()) {
|
||||
mHandleView.hide();
|
||||
} else {
|
||||
if (getParent() != null) {
|
||||
((ControlLayout) getParent()).hideAllHandleViews();
|
||||
}
|
||||
|
||||
try {
|
||||
mHandleView.show(this);
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mCanTriggerLongClick;
|
||||
}
|
||||
|
||||
protected float downX, downY;
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable){
|
||||
mCanTriggerLongClick = false;
|
||||
|
||||
switch (event.getActionMasked()){
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
//Send the event to be taken as a mouse action
|
||||
if(mProperties.passThruEnabled && CallbackBridge.isGrabbing()){
|
||||
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
|
||||
if (v != null) v.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
//If out of bounds
|
||||
if(event.getX() < getLeft() || event.getX() > getRight() ||
|
||||
event.getY() < getTop() || event.getY() > getBottom()){
|
||||
if(mProperties.isSwipeable && !isPointerOutOfBounds){
|
||||
//Remove keys
|
||||
if(!triggerToggle()) {
|
||||
sendKeyPresses(false);
|
||||
}
|
||||
}
|
||||
isPointerOutOfBounds = true;
|
||||
((ControlLayout) getParent()).onTouch(this, event);
|
||||
break;
|
||||
}
|
||||
|
||||
//Else if we now are in bounds
|
||||
if(isPointerOutOfBounds) {
|
||||
((ControlLayout) getParent()).onTouch(this, event);
|
||||
//RE-press the button
|
||||
if(mProperties.isSwipeable && !mProperties.isToggle){
|
||||
sendKeyPresses(true);
|
||||
}
|
||||
}
|
||||
isPointerOutOfBounds = false;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN: // 0
|
||||
case MotionEvent.ACTION_POINTER_DOWN: // 5
|
||||
if(!mProperties.isToggle){
|
||||
sendKeyPresses(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_CANCEL: // 3
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
if(mProperties.passThruEnabled){
|
||||
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
|
||||
if (v != null) v.dispatchTouchEvent(event);
|
||||
}
|
||||
if(isPointerOutOfBounds) ((ControlLayout) getParent()).onTouch(this, event);
|
||||
isPointerOutOfBounds = false;
|
||||
|
||||
if(!triggerToggle()) {
|
||||
sendKeyPresses(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the button can be modified/moved */
|
||||
//Instantiate the gesture detector only when needed
|
||||
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
|
||||
|
||||
if (mGestureDetector.onTouchEvent(event)) {
|
||||
mCanTriggerLongClick = true;
|
||||
onLongClick(this);
|
||||
}
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mCanTriggerLongClick = true;
|
||||
downX = event.getRawX() - getX();
|
||||
downY = event.getRawY() - getY();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mCanTriggerLongClick = false;
|
||||
|
||||
if (!mProperties.isDynamicBtn) {
|
||||
snapAndAlign(
|
||||
MathUtils.clamp(event.getRawX() - downX, 0, CallbackBridge.physicalWidth - getWidth()),
|
||||
MathUtils.clamp(event.getRawY() - downY, 0, CallbackBridge.physicalHeight - getHeight())
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe a series of checks to determine if the ControlButton is available to be snapped on.
|
||||
*
|
||||
* @param button The button to check
|
||||
* @return whether or not the button
|
||||
*/
|
||||
protected boolean canSnap(ControlButton button){
|
||||
float MIN_DISTANCE = Tools.dpToPx(8);
|
||||
|
||||
if(button == this) return false;
|
||||
if(com.google.android.material.math.MathUtils.dist(
|
||||
button.getX() + button.getWidth()/2f,
|
||||
button.getY() + button.getHeight()/2f,
|
||||
getX() + getWidth()/2f,
|
||||
getY() + getHeight()/2f) > Math.max(button.getWidth()/2f + getWidth()/2f, button.getHeight()/2f + getHeight()/2f) + MIN_DISTANCE) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to snap, then align to neighboring buttons, given the provided coordinates.
|
||||
* The new position is automatically applied to the View,
|
||||
* regardless of if the View snapped or not.
|
||||
*
|
||||
* The new position is always dynamic, thus replacing previous dynamic positions
|
||||
*
|
||||
* @param x Coordinate on the x axis
|
||||
* @param y Coordinate on the y axis
|
||||
*/
|
||||
protected void snapAndAlign(float x, float y){
|
||||
float MIN_DISTANCE = Tools.dpToPx(8);
|
||||
String dynamicX = generateDynamicX(x);
|
||||
String dynamicY = generateDynamicY(y);
|
||||
|
||||
setX(x);
|
||||
setY(y);
|
||||
|
||||
for(ControlButton button : ((ControlLayout) getParent()).getButtonChildren()){
|
||||
//Step 1: Filter unwanted buttons
|
||||
if(!canSnap(button)) continue;
|
||||
|
||||
//Step 2: Get Coordinates
|
||||
float button_top = button.getY();
|
||||
float button_bottom = button_top + button.getHeight();
|
||||
float button_left = button.getX();
|
||||
float button_right = button_left + button.getWidth();
|
||||
|
||||
float top = getY();
|
||||
float bottom = getY() + getHeight();
|
||||
float left = getX();
|
||||
float right = getX() + getWidth();
|
||||
|
||||
//Step 3: For each axis, we try to snap to the nearest
|
||||
if(Math.abs(top - button_bottom) < MIN_DISTANCE){ // Bottom snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " + ${margin}" ;
|
||||
}else if(Math.abs(button_top - bottom) < MIN_DISTANCE){ //Top snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button) + " - ${height} - ${margin}";
|
||||
}
|
||||
if(!dynamicY.equals(generateDynamicY(getY()))){ //If we snapped
|
||||
if(Math.abs(button_left - left) < MIN_DISTANCE){ //Left align snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button);
|
||||
}else if(Math.abs(button_right - right) < MIN_DISTANCE){ //Right align snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " - ${width}";
|
||||
}
|
||||
}
|
||||
|
||||
if(Math.abs(button_left - right) < MIN_DISTANCE){ //Left snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button) + " - ${width} - ${margin}";
|
||||
}else if(Math.abs(left - button_right) < MIN_DISTANCE){ //Right snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " + ${margin}";
|
||||
}
|
||||
if(!dynamicX.equals(generateDynamicX(getX()))){ //If we snapped
|
||||
if(Math.abs(button_top - top) < MIN_DISTANCE){ //Top align snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button);
|
||||
}else if(Math.abs(button_bottom - bottom) < MIN_DISTANCE){ //Bottom align snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " - ${height}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setDynamicX(dynamicX);
|
||||
setDynamicY(dynamicY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a pre-conversion of an equation using values from a button,
|
||||
* so the variables can be used for another button
|
||||
*
|
||||
* Internal use only.
|
||||
* @param equation The dynamic position as a String
|
||||
* @param button The button to get the values from.
|
||||
* @return The pre-processed equation as a String.
|
||||
*/
|
||||
private static String applySize(String equation, ControlButton button){
|
||||
return equation
|
||||
.replace("${right}", "(${screen_width} - ${width})")
|
||||
.replace("${bottom}","(${screen_height} - ${height})")
|
||||
.replace("${height}", "(px(" + Tools.pxToDp(button.getProperties().getHeight()) + ") /" + PREF_BUTTONSIZE + " * ${preferred_scale})")
|
||||
.replace("${width}", "(px(" + Tools.pxToDp(button.getProperties().getWidth()) + ") / " + PREF_BUTTONSIZE + " * ${preferred_scale})");
|
||||
}
|
||||
|
||||
public int computeStrokeWidth(float widthInPercent){
|
||||
float maxSize = Math.max(mProperties.getWidth(), mProperties.getHeight());
|
||||
return (int)((maxSize/2) * (widthInPercent/100));
|
||||
}
|
||||
|
||||
public float computeCornerRadius(float radiusInPercent){
|
||||
float minSize = Math.min(mProperties.getWidth(), mProperties.getHeight());
|
||||
return (minSize/2) * (radiusInPercent/100);
|
||||
}
|
||||
|
||||
public boolean triggerToggle(){
|
||||
//returns true a the toggle system is triggered
|
||||
if(mProperties.isToggle){
|
||||
isToggled = !isToggled;
|
||||
invalidate();
|
||||
sendKeyPresses(isToggled);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sendKeyPresses(boolean isDown){
|
||||
setActivated(isDown);
|
||||
for(int keycode : mProperties.keycodes){
|
||||
if(keycode >= GLFW_KEY_UNKNOWN){
|
||||
MainActivity.sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
|
||||
CallbackBridge.setModifiers(keycode, isDown);
|
||||
}else{
|
||||
sendSpecialKey(keycode, isDown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSpecialKey(int keycode, boolean isDown){
|
||||
switch (keycode) {
|
||||
case ControlData.SPECIALBTN_KEYBOARD:
|
||||
if(isDown)BaseMainActivity.switchKeyboardState();
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_TOGGLECTRL:
|
||||
if(isDown)MainActivity.mControlLayout.toggleControlVisible();
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_VIRTUALMOUSE:
|
||||
if(isDown)BaseMainActivity.toggleMouse(getContext());
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSEPRI:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSEMID:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSESEC:
|
||||
if (CallbackBridge.isGrabbing()) {
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
|
||||
} else {
|
||||
CallbackBridge.putMouseEventWithCoords(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown , CallbackBridge.mouseX, CallbackBridge.mouseY);
|
||||
|
||||
BaseMainActivity.setRightOverride(isDown);
|
||||
}
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_SCROLLDOWN:
|
||||
if (!isDown) CallbackBridge.sendScroll(0, 1d);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_SCROLLUP:
|
||||
if (!isDown) CallbackBridge.sendScroll(0, -1d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.buttons;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class ControlDrawer extends ControlButton {
|
||||
|
||||
|
||||
public ArrayList<ControlSubButton> buttons;
|
||||
public ControlDrawerData drawerData;
|
||||
public ControlLayout mLayout;
|
||||
public boolean areButtonsVisible;
|
||||
|
||||
|
||||
public ControlDrawer(ControlLayout layout, ControlDrawerData drawerData) {
|
||||
super(layout, drawerData.properties);
|
||||
|
||||
buttons = new ArrayList<>(drawerData.buttonProperties.size());
|
||||
mLayout = layout;
|
||||
this.drawerData = drawerData;
|
||||
areButtonsVisible = layout.getModifiable();
|
||||
}
|
||||
|
||||
|
||||
public void addButton(ControlData properties){
|
||||
addButton(new ControlSubButton(mLayout, properties, this));
|
||||
}
|
||||
|
||||
public void addButton(ControlSubButton button){
|
||||
buttons.add(button);
|
||||
setControlButtonVisibility(button, mModifiable || areButtonsVisible);
|
||||
syncButtons();
|
||||
}
|
||||
|
||||
private void setControlButtonVisibility(ControlButton button, boolean isVisible){
|
||||
button.setVisible(isVisible);
|
||||
}
|
||||
|
||||
private void switchButtonVisibility(){
|
||||
areButtonsVisible = !areButtonsVisible;
|
||||
for(ControlButton button : buttons){
|
||||
button.setVisible(areButtonsVisible);
|
||||
}
|
||||
}
|
||||
|
||||
//Syncing stuff
|
||||
private void alignButtons(){
|
||||
|
||||
if(buttons == null) return;
|
||||
for(int i=0; i < buttons.size(); ++i){
|
||||
switch (drawerData.orientation){
|
||||
case RIGHT:
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX() + (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1) ));
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY()));
|
||||
break;
|
||||
|
||||
case LEFT:
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX() - (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1)));
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY()));
|
||||
break;
|
||||
|
||||
case UP:
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY() - (drawerData.properties.getHeight() + Tools.dpToPx(2))*(i+1)));
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX()));
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY() + (drawerData.properties.getHeight() + Tools.dpToPx(2))*(i+1)));
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX()));
|
||||
break;
|
||||
}
|
||||
buttons.get(i).updateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void resizeButtons(){
|
||||
if (buttons == null) return;
|
||||
for(ControlSubButton subButton : buttons){
|
||||
subButton.mProperties.setWidth(mProperties.getWidth());
|
||||
subButton.mProperties.setHeight(mProperties.getHeight());
|
||||
|
||||
subButton.updateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void syncButtons(){
|
||||
alignButtons();
|
||||
resizeButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the button passed as a parameter belongs to this drawer.
|
||||
*
|
||||
* @param button The button to look for
|
||||
* @return Whether the button is in the buttons list of the drawer.
|
||||
*/
|
||||
public boolean containsChild(ControlButton button){
|
||||
for(ControlButton childButton : buttons){
|
||||
if (childButton == button) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlData preProcessProperties(ControlData properties, ControlLayout layout) {
|
||||
ControlData data = super.preProcessProperties(properties, layout);
|
||||
data.isHideable = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean isVisible) {
|
||||
//TODO replicate changes to his children ?
|
||||
setVisibility(isVisible ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable){
|
||||
switch (event.getActionMasked()){
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
switchButtonVisibility();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canSnap(ControlButton button) {
|
||||
return super.canSnap(button) && !containsChild(button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(float x) {
|
||||
super.setX(x);
|
||||
alignButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(float y) {
|
||||
super.setY(y);
|
||||
alignButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
super.setLayoutParams(params);
|
||||
syncButtons();
|
||||
}
|
||||
|
||||
//Getters
|
||||
public ControlDrawerData getDrawerData() {
|
||||
return drawerData;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.buttons;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.kdt.pojavlaunch.SingleTapConfirm;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
|
||||
public class ControlSubButton extends ControlButton {
|
||||
|
||||
public ControlDrawer parentDrawer;
|
||||
|
||||
public ControlSubButton(ControlLayout layout, ControlData properties, ControlDrawer parentDrawer) {
|
||||
super(layout, properties);
|
||||
this.parentDrawer = parentDrawer;
|
||||
|
||||
//Delayed to let the button inflate first
|
||||
if(!layout.getModifiable())
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> setVisibility(parentDrawer.areButtonsVisible ? VISIBLE : GONE), 0);
|
||||
|
||||
filterProperties();
|
||||
}
|
||||
|
||||
private void filterProperties(){
|
||||
mProperties.setHeight(parentDrawer.getProperties().getHeight());
|
||||
mProperties.setWidth(parentDrawer.getProperties().getWidth());
|
||||
mProperties.isDynamicBtn = false;
|
||||
|
||||
setProperties(mProperties, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean isVisible) {
|
||||
setVisibility(isVisible ? (parentDrawer.areButtonsVisible ? VISIBLE : GONE) : (!mProperties.isHideable && parentDrawer.getVisibility() == GONE) ? VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
if(parentDrawer != null){
|
||||
params.width = (int)parentDrawer.mProperties.getWidth();
|
||||
params.height = (int)parentDrawer.mProperties.getHeight();
|
||||
}
|
||||
super.setLayoutParams(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable){
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
|
||||
|
||||
if (mGestureDetector.onTouchEvent(event)) {
|
||||
mCanTriggerLongClick = true;
|
||||
onLongClick(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.math.MathUtils;
|
||||
|
||||
import net.kdt.pojavlaunch.BaseMainActivity;
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
import net.kdt.pojavlaunch.MainActivity;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_EAST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NONE;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH_EAST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH_WEST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_EAST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_WEST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_WEST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.isJoystickEvent;
|
||||
|
||||
public class Gamepad {
|
||||
|
||||
private final BaseMainActivity gameActivity;
|
||||
private final ImageView pointerView;
|
||||
|
||||
private final GamepadDpad gamepadDpad = new GamepadDpad();
|
||||
|
||||
private final GamepadJoystick leftJoystick;
|
||||
private int currentJoystickDirection = DIRECTION_NONE;
|
||||
|
||||
private final GamepadJoystick rightJoystick;
|
||||
private float lastHorizontalValue = 0.0f;
|
||||
private float lastVerticalValue = 0.0f;
|
||||
|
||||
private final double mouseMaxAcceleration = 2f;
|
||||
private double acceleration = 0.0f;
|
||||
|
||||
private double mouseMagnitude;
|
||||
private double mouseAngle;
|
||||
private double mouseSensitivity = 19;
|
||||
|
||||
private final GamepadMap gameMap = GamepadMap.getDefaultGameMap();
|
||||
private final GamepadMap menuMap = GamepadMap.getDefaultMenuMap();
|
||||
private GamepadMap currentMap = gameMap;
|
||||
|
||||
private boolean lastGrabbingState = true;
|
||||
private final boolean hasDigitalTriggers;
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable switchStateRunnable;
|
||||
|
||||
public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){
|
||||
//Toast.makeText(gameActivity.getApplicationContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show();
|
||||
|
||||
leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
|
||||
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice);
|
||||
hasDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
|
||||
|
||||
this.gameActivity = gameActivity;
|
||||
pointerView = this.gameActivity.findViewById(R.id.console_pointer);
|
||||
pointerView.getDrawable().setFilterBitmap(false);
|
||||
notifyGUISizeChange(gameActivity.getMcScale());
|
||||
|
||||
Runnable handlerRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
updateGrabbingState();
|
||||
tick();
|
||||
|
||||
handler.postDelayed(this, 16);
|
||||
}
|
||||
};
|
||||
|
||||
handler.postDelayed(handlerRunnable, 16);
|
||||
|
||||
//Initialize runnables to be used by the input system, avoiding generating one each time is better memory.
|
||||
switchStateRunnable = () -> {
|
||||
currentMap.resetPressedState();
|
||||
if(lastGrabbingState){
|
||||
currentMap = gameMap;
|
||||
pointerView.setVisibility(View.INVISIBLE);
|
||||
mouseSensitivity = 22 / gameActivity.sensitivityFactor; //sensitivity in menus is resolution dependent.
|
||||
return;
|
||||
}
|
||||
|
||||
currentMap = menuMap;
|
||||
sendDirectionalKeycode(currentJoystickDirection, false, gameMap); // removing what we were doing
|
||||
|
||||
gameActivity.mouse_x = CallbackBridge.windowWidth/2;
|
||||
gameActivity.mouse_y = CallbackBridge.windowHeight/2;
|
||||
CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y);
|
||||
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
|
||||
pointerView.setVisibility(View.VISIBLE);
|
||||
mouseSensitivity = 14; //sensitivity in game doesn't need to be resolution dependent
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void tick(){
|
||||
//update mouse position
|
||||
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
|
||||
|
||||
acceleration = (mouseMagnitude - currentJoystick.getDeadzone())/(1 - currentJoystick.getDeadzone());
|
||||
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
|
||||
if(acceleration > 1) acceleration = 1;
|
||||
|
||||
CallbackBridge.mouseX += Math.cos(mouseAngle) * acceleration * mouseSensitivity;
|
||||
CallbackBridge.mouseY -= Math.sin(mouseAngle) * acceleration * mouseSensitivity;
|
||||
|
||||
if(!lastGrabbingState){
|
||||
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
|
||||
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
|
||||
placePointerView((int) (CallbackBridge.mouseX /gameActivity.scaleFactor), (int) (CallbackBridge.mouseY/gameActivity.scaleFactor));
|
||||
}
|
||||
|
||||
gameActivity.mouse_x = CallbackBridge.mouseX;
|
||||
gameActivity.mouse_y = CallbackBridge.mouseY;
|
||||
|
||||
//Send the mouse to the game
|
||||
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateGrabbingState() {
|
||||
boolean lastGrabbingValue = lastGrabbingState;
|
||||
lastGrabbingState = CallbackBridge.isGrabbing();
|
||||
if(lastGrabbingValue != lastGrabbingState){
|
||||
gameActivity.runOnUiThread(switchStateRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(KeyEvent event){
|
||||
sendButton(event);
|
||||
}
|
||||
|
||||
public void update(MotionEvent event){
|
||||
updateDirectionalJoystick(event);
|
||||
updateMouseJoystick(event);
|
||||
updateAnalogTriggers(event);
|
||||
sendButton(gamepadDpad.convertEvent(event));
|
||||
}
|
||||
|
||||
private void updateMouseJoystick(MotionEvent event){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? rightJoystick : leftJoystick;
|
||||
lastHorizontalValue = currentJoystick.getHorizontalAxis(event);
|
||||
lastVerticalValue = currentJoystick.getVerticalAxis(event);
|
||||
|
||||
mouseMagnitude = currentJoystick.getMagnitude(event);
|
||||
mouseAngle = currentJoystick.getAngleRadian(event);
|
||||
}
|
||||
|
||||
private void updateDirectionalJoystick(MotionEvent event){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
|
||||
|
||||
int lastJoystickDirection = currentJoystickDirection;
|
||||
currentJoystickDirection = currentJoystick.getHeightDirection(event);
|
||||
|
||||
if(currentJoystickDirection != lastJoystickDirection){
|
||||
sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap());
|
||||
sendDirectionalKeycode(currentJoystickDirection, true, getCurrentMap());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAnalogTriggers(MotionEvent event){
|
||||
if(!hasDigitalTriggers){
|
||||
getCurrentMap().TRIGGER_LEFT.update((event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5));
|
||||
getCurrentMap().TRIGGER_RIGHT.update((event.getAxisValue(MotionEvent.AXIS_RTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_GAS) > 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyGUISizeChange(int newSize){
|
||||
//Change the pointer size to match UI
|
||||
int size = (int) ((22 * newSize) / gameActivity.scaleFactor);
|
||||
gameActivity.runOnUiThread(() -> pointerView.setLayoutParams(new FrameLayout.LayoutParams(size, size)));
|
||||
}
|
||||
|
||||
private GamepadMap getCurrentMap(){
|
||||
return currentMap;
|
||||
}
|
||||
|
||||
private static void sendDirectionalKeycode(int direction, boolean isDown, GamepadMap map){
|
||||
switch (direction){
|
||||
case DIRECTION_NORTH:
|
||||
sendInput(map.DIRECTION_FORWARD, isDown);
|
||||
break;
|
||||
case DIRECTION_NORTH_EAST:
|
||||
sendInput(map.DIRECTION_FORWARD, isDown);
|
||||
sendInput(map.DIRECTION_RIGHT, isDown);
|
||||
break;
|
||||
case DIRECTION_EAST:
|
||||
sendInput(map.DIRECTION_RIGHT, isDown);
|
||||
break;
|
||||
case DIRECTION_SOUTH_EAST:
|
||||
sendInput(map.DIRECTION_RIGHT, isDown);
|
||||
sendInput(map.DIRECTION_BACKWARD, isDown);
|
||||
break;
|
||||
case DIRECTION_SOUTH:
|
||||
sendInput(map.DIRECTION_BACKWARD, isDown);
|
||||
break;
|
||||
case DIRECTION_SOUTH_WEST:
|
||||
sendInput(map.DIRECTION_BACKWARD, isDown);
|
||||
sendInput(map.DIRECTION_LEFT, isDown);
|
||||
break;
|
||||
case DIRECTION_WEST:
|
||||
sendInput(map.DIRECTION_LEFT, isDown);
|
||||
break;
|
||||
case DIRECTION_NORTH_WEST:
|
||||
sendInput(map.DIRECTION_FORWARD, isDown);
|
||||
sendInput(map.DIRECTION_LEFT, isDown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void placePointerView(int x, int y){
|
||||
pointerView.setX(x - pointerView.getWidth()/2);
|
||||
pointerView.setY(y - pointerView.getHeight()/2);
|
||||
}
|
||||
|
||||
|
||||
public void sendButton(KeyEvent event){
|
||||
int keycode = event.getKeyCode();
|
||||
switch (keycode){
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
getCurrentMap().BUTTON_A.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
getCurrentMap().BUTTON_B.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
getCurrentMap().BUTTON_X.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
getCurrentMap().BUTTON_Y.update(event);
|
||||
break;
|
||||
|
||||
//Shoulders
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
getCurrentMap().SHOULDER_LEFT.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
getCurrentMap().SHOULDER_RIGHT.update(event);
|
||||
break;
|
||||
|
||||
//Triggers
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
getCurrentMap().TRIGGER_LEFT.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
getCurrentMap().TRIGGER_RIGHT.update(event);
|
||||
break;
|
||||
|
||||
//L3 || R3
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
getCurrentMap().THUMBSTICK_LEFT.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
getCurrentMap().THUMBSTICK_RIGHT.update(event);
|
||||
break;
|
||||
|
||||
//DPAD
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
getCurrentMap().DPAD_UP.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
getCurrentMap().DPAD_DOWN.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
getCurrentMap().DPAD_LEFT.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
getCurrentMap().DPAD_RIGHT.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
getCurrentMap().DPAD_RIGHT.update(false);
|
||||
getCurrentMap().DPAD_LEFT.update(false);
|
||||
getCurrentMap().DPAD_UP.update(false);
|
||||
getCurrentMap().DPAD_DOWN.update(false);
|
||||
break;
|
||||
|
||||
//Start/select
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
getCurrentMap().BUTTON_START.update(event);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
getCurrentMap().BUTTON_SELECT.update(event);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_SPACE, CallbackBridge.getCurrentMods(), event.getAction() == KeyEvent.ACTION_DOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendInput(int[] keycodes, boolean isDown){
|
||||
for(int keycode : keycodes){
|
||||
switch (keycode){
|
||||
case GamepadMap.MOUSE_SCROLL_DOWN:
|
||||
if(isDown) CallbackBridge.sendScroll(0, -1);
|
||||
break;
|
||||
case GamepadMap.MOUSE_SCROLL_UP:
|
||||
if(isDown) CallbackBridge.sendScroll(0, 1);
|
||||
break;
|
||||
|
||||
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT:
|
||||
MainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
|
||||
break;
|
||||
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT:
|
||||
MainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
MainActivity.sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
|
||||
break;
|
||||
}
|
||||
CallbackBridge.setModifiers(keycode, isDown);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean isGamepadEvent(MotionEvent event){
|
||||
return isJoystickEvent(event);
|
||||
}
|
||||
|
||||
public static boolean isGamepadEvent(KeyEvent event){
|
||||
return ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|
||||
|| GamepadDpad.isDpadEvent(event) );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
public class GamepadButton {
|
||||
|
||||
/*
|
||||
Just a simple button, that auto deal with the great habit from android to just SPAAAMS input events
|
||||
*/
|
||||
public int[] keycodes;
|
||||
public boolean isToggleable = false;
|
||||
private boolean isDown = false;
|
||||
private boolean isToggled = false;
|
||||
|
||||
public void update(KeyEvent event){
|
||||
boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN);
|
||||
update(isKeyDown);
|
||||
}
|
||||
|
||||
public void update(boolean isKeyDown){
|
||||
if(isKeyDown != isDown){
|
||||
isDown = isKeyDown;
|
||||
if(isToggleable){
|
||||
if(isKeyDown){
|
||||
isToggled = !isToggled;
|
||||
Gamepad.sendInput(keycodes, isToggled);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Gamepad.sendInput(keycodes, isDown);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetButtonState(){
|
||||
if(isDown || isToggled){
|
||||
Gamepad.sendInput(keycodes, false);
|
||||
}
|
||||
isDown = false;
|
||||
isToggled = false;
|
||||
}
|
||||
|
||||
public boolean isDown(){
|
||||
return isToggleable ? isToggled : isDown;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static android.view.InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC;
|
||||
import static android.view.InputDevice.SOURCE_DPAD;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
|
||||
|
||||
/*
|
||||
Reflection is used to avoid memory churning, and only has an negative impact at start
|
||||
*/
|
||||
|
||||
public class GamepadDpad {
|
||||
|
||||
|
||||
private int lastKeycode = KEYCODE_DPAD_CENTER;
|
||||
private KeyEvent dummyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, lastKeycode);
|
||||
private Field eventCodeField;
|
||||
private Field eventActionField;
|
||||
|
||||
{
|
||||
try {
|
||||
eventCodeField = dummyEvent.getClass().getDeclaredField("mKeyCode");
|
||||
eventCodeField.setAccessible(true);
|
||||
|
||||
eventActionField = dummyEvent.getClass().getDeclaredField("mAction");
|
||||
eventActionField.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public KeyEvent convertEvent(MotionEvent event){
|
||||
// Use the hat axis value to find the D-pad direction
|
||||
float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X);
|
||||
float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
|
||||
int action = KeyEvent.ACTION_DOWN;
|
||||
|
||||
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
|
||||
// LEFT and RIGHT direction accordingly.
|
||||
if (Float.compare(xaxis, -1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_LEFT;
|
||||
} else if (Float.compare(xaxis, 1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_RIGHT;
|
||||
}
|
||||
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
|
||||
// UP and DOWN direction accordingly.
|
||||
else if (Float.compare(yaxis, -1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_UP;
|
||||
} else if (Float.compare(yaxis, 1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_DOWN;
|
||||
}else {
|
||||
//No keycode change
|
||||
action = KeyEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
setDummyEventKeycode(lastKeycode);
|
||||
setDummyEventAction(action);
|
||||
dummyEvent.setSource(SOURCE_DPAD);
|
||||
return dummyEvent;
|
||||
|
||||
}
|
||||
|
||||
private void setDummyEventKeycode(int fakeKeycode){
|
||||
try {
|
||||
eventCodeField.setInt(dummyEvent, fakeKeycode);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDummyEventAction(int action){
|
||||
try {
|
||||
eventActionField.setInt(dummyEvent, action);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDpadEvent(MotionEvent event) {
|
||||
// Check that input comes from a device with directional pads.
|
||||
// And... also the joystick since it declares sometimes as a joystick.
|
||||
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK;
|
||||
}
|
||||
|
||||
public static boolean isDpadEvent(KeyEvent event){
|
||||
return ((event.getSource() & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) && (event.getDevice().getKeyboardType() == KEYBOARD_TYPE_NON_ALPHABETIC);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
|
||||
import com.google.android.material.math.MathUtils;
|
||||
|
||||
public class GamepadJoystick {
|
||||
|
||||
//Directions
|
||||
public static final int DIRECTION_NONE = -1; //GamepadJoystick at the center
|
||||
|
||||
public static final int DIRECTION_EAST = 0;
|
||||
public static final int DIRECTION_NORTH_EAST = 1;
|
||||
public static final int DIRECTION_NORTH = 2;
|
||||
public static final int DIRECTION_NORTH_WEST = 3;
|
||||
public static final int DIRECTION_WEST = 4;
|
||||
public static final int DIRECTION_SOUTH_WEST = 5;
|
||||
public static final int DIRECTION_SOUTH = 6;
|
||||
public static final int DIRECTION_SOUTH_EAST = 7;
|
||||
|
||||
private float deadzone;
|
||||
|
||||
private final int verticalAxis;
|
||||
private final int horizontalAxis;
|
||||
|
||||
public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){
|
||||
this.verticalAxis = verticalAxis;
|
||||
this.horizontalAxis = horizontalAxis;
|
||||
|
||||
//Some controllers aren't recognized as such by android, so we fallback to a default value of 0.2
|
||||
//And some others don't report their MotionRange. This was the case with the xbox one series S controller.
|
||||
|
||||
//try { deadzone = Math.max(device.getMotionRange(verticalAxis).getFlat(), device.getMotionRange(horizontalAxis).getFlat()) * 1.9f; }
|
||||
//catch (NullPointerException e){ deadzone = 0.2f; }
|
||||
deadzone = 0.2f;
|
||||
}
|
||||
|
||||
public double getAngleRadian(MotionEvent event){
|
||||
//From -PI to PI
|
||||
return -Math.atan2(getVerticalAxis(event), getHorizontalAxis(event));
|
||||
}
|
||||
|
||||
|
||||
public double getAngleDegree(MotionEvent event){
|
||||
//From 0 to 360 degrees
|
||||
double result = Math.toDegrees(getAngleRadian(event));
|
||||
if(result < 0) result += 360;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public double getMagnitude(MotionEvent event){
|
||||
float x = Math.abs(event.getAxisValue(horizontalAxis));
|
||||
float y = Math.abs(event.getAxisValue(verticalAxis));
|
||||
|
||||
return MathUtils.dist(0,0, x, y);
|
||||
}
|
||||
|
||||
public float getVerticalAxis(MotionEvent event){
|
||||
return applyDeadzone(event, verticalAxis);
|
||||
}
|
||||
|
||||
public float getHorizontalAxis(MotionEvent event){
|
||||
return applyDeadzone(event, horizontalAxis);
|
||||
}
|
||||
|
||||
private float applyDeadzone(MotionEvent event, int axis){
|
||||
//This piece of code also modifies the value
|
||||
//to make it seem like there was no deadzone in the first place
|
||||
|
||||
double magnitude = getMagnitude(event);
|
||||
if (magnitude < deadzone) return 0;
|
||||
|
||||
return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) );
|
||||
}
|
||||
|
||||
public static boolean isJoystickEvent(MotionEvent event){
|
||||
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|
||||
&& event.getAction() == MotionEvent.ACTION_MOVE;
|
||||
}
|
||||
|
||||
|
||||
public int getHeightDirection(MotionEvent event){
|
||||
if(getMagnitude(event) <= deadzone) return DIRECTION_NONE;
|
||||
return ((int) ((getAngleDegree(event)+22.5)/45)) % 8;
|
||||
}
|
||||
|
||||
|
||||
public float getDeadzone() {
|
||||
return deadzone;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
|
||||
public class GamepadMap {
|
||||
|
||||
public static final int MOUSE_SCROLL_DOWN = -1;
|
||||
public static final int MOUSE_SCROLL_UP = -2;
|
||||
|
||||
/*
|
||||
This class is just here to store the mapping
|
||||
can be modified to create re-mappable controls I guess
|
||||
|
||||
Be warned, you should define ALL keys if you want to avoid a non defined exception
|
||||
*/
|
||||
|
||||
public GamepadButton BUTTON_A = new GamepadButton();
|
||||
public GamepadButton BUTTON_B = new GamepadButton();
|
||||
public GamepadButton BUTTON_X = new GamepadButton();
|
||||
public GamepadButton BUTTON_Y = new GamepadButton();
|
||||
|
||||
public GamepadButton BUTTON_START = new GamepadButton();
|
||||
public GamepadButton BUTTON_SELECT = new GamepadButton();
|
||||
|
||||
public GamepadButton TRIGGER_RIGHT = new GamepadButton(); //R2
|
||||
public GamepadButton TRIGGER_LEFT = new GamepadButton(); //L2
|
||||
|
||||
public GamepadButton SHOULDER_RIGHT = new GamepadButton(); //R1
|
||||
public GamepadButton SHOULDER_LEFT = new GamepadButton(); //L1
|
||||
|
||||
public int[] DIRECTION_FORWARD;
|
||||
public int[] DIRECTION_BACKWARD;
|
||||
public int[] DIRECTION_RIGHT;
|
||||
public int[] DIRECTION_LEFT;
|
||||
|
||||
public GamepadButton THUMBSTICK_RIGHT = new GamepadButton(); //R3
|
||||
public GamepadButton THUMBSTICK_LEFT = new GamepadButton(); //L3
|
||||
|
||||
public GamepadButton DPAD_UP = new GamepadButton();
|
||||
public GamepadButton DPAD_RIGHT = new GamepadButton();
|
||||
public GamepadButton DPAD_DOWN = new GamepadButton();
|
||||
public GamepadButton DPAD_LEFT = new GamepadButton();
|
||||
|
||||
|
||||
/*
|
||||
* Sets all buttons to a not pressed state, sending an input if needed
|
||||
*/
|
||||
public void resetPressedState(){
|
||||
BUTTON_A.resetButtonState();
|
||||
BUTTON_B.resetButtonState();
|
||||
BUTTON_X.resetButtonState();
|
||||
BUTTON_Y.resetButtonState();
|
||||
|
||||
BUTTON_START.resetButtonState();
|
||||
BUTTON_SELECT.resetButtonState();
|
||||
|
||||
TRIGGER_LEFT.resetButtonState();
|
||||
TRIGGER_RIGHT.resetButtonState();
|
||||
|
||||
SHOULDER_LEFT.resetButtonState();
|
||||
SHOULDER_RIGHT.resetButtonState();
|
||||
|
||||
THUMBSTICK_LEFT.resetButtonState();
|
||||
THUMBSTICK_RIGHT.resetButtonState();
|
||||
|
||||
DPAD_UP.resetButtonState();
|
||||
DPAD_RIGHT.resetButtonState();
|
||||
DPAD_DOWN.resetButtonState();
|
||||
DPAD_LEFT.resetButtonState();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a pre-done mapping used when the mouse is grabbed by the game.
|
||||
*/
|
||||
public static GamepadMap getDefaultGameMap(){
|
||||
GamepadMap gameMap = new GamepadMap();
|
||||
|
||||
gameMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE};
|
||||
gameMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_Q};
|
||||
gameMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_E};
|
||||
gameMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_F};
|
||||
|
||||
gameMap.DIRECTION_FORWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_W};
|
||||
gameMap.DIRECTION_BACKWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_S};
|
||||
gameMap.DIRECTION_RIGHT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_D};
|
||||
gameMap.DIRECTION_LEFT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_A};
|
||||
|
||||
gameMap.DPAD_UP.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
|
||||
gameMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
|
||||
gameMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
|
||||
gameMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
|
||||
|
||||
gameMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
|
||||
gameMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
|
||||
|
||||
gameMap.TRIGGER_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
|
||||
gameMap.TRIGGER_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
|
||||
|
||||
gameMap.THUMBSTICK_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL};
|
||||
gameMap.THUMBSTICK_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
|
||||
gameMap.THUMBSTICK_RIGHT.isToggleable = true;
|
||||
|
||||
gameMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
|
||||
gameMap.BUTTON_SELECT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB};
|
||||
|
||||
return gameMap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a pre-done mapping used when the mouse is NOT grabbed by the game.
|
||||
*/
|
||||
public static GamepadMap getDefaultMenuMap(){
|
||||
GamepadMap menuMap = new GamepadMap();
|
||||
|
||||
menuMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
|
||||
menuMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
|
||||
menuMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
|
||||
menuMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT, LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT}; //Oops, doesn't work since left shift isn't properly applied.
|
||||
|
||||
menuMap.DIRECTION_FORWARD = new int[]{GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP};
|
||||
menuMap.DIRECTION_BACKWARD = new int[]{GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN};
|
||||
menuMap.DIRECTION_RIGHT = new int[]{};
|
||||
menuMap.DIRECTION_LEFT = new int[]{};
|
||||
|
||||
menuMap.DPAD_UP.keycodes = new int[]{};
|
||||
menuMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
|
||||
menuMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
|
||||
menuMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
|
||||
|
||||
menuMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
|
||||
menuMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
|
||||
|
||||
menuMap.TRIGGER_LEFT.keycodes = new int[]{};
|
||||
menuMap.TRIGGER_RIGHT.keycodes = new int[]{};
|
||||
|
||||
menuMap.THUMBSTICK_LEFT.keycodes = new int[]{};
|
||||
menuMap.THUMBSTICK_RIGHT.keycodes = new int[]{};
|
||||
|
||||
menuMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
|
||||
menuMap.BUTTON_SELECT.keycodes = new int[]{};
|
||||
|
||||
return menuMap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns all GamepadButtons, does not include directional keys
|
||||
*/
|
||||
public GamepadButton[] getButtons(){
|
||||
return new GamepadButton[]{ BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y,
|
||||
BUTTON_SELECT, BUTTON_START,
|
||||
TRIGGER_LEFT, TRIGGER_RIGHT,
|
||||
SHOULDER_LEFT, SHOULDER_RIGHT,
|
||||
THUMBSTICK_LEFT, THUMBSTICK_RIGHT,
|
||||
DPAD_UP, DPAD_RIGHT, DPAD_DOWN, DPAD_LEFT};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an pre-initialized GamepadMap with only empty keycodes
|
||||
*/
|
||||
public static GamepadMap getEmptyMap(){
|
||||
GamepadMap emptyMap = new GamepadMap();
|
||||
for(GamepadButton button : emptyMap.getButtons())
|
||||
button.keycodes = new int[]{};
|
||||
|
||||
emptyMap.DIRECTION_LEFT = new int[]{};
|
||||
emptyMap.DIRECTION_FORWARD = new int[]{};
|
||||
emptyMap.DIRECTION_RIGHT = new int[]{};
|
||||
emptyMap.DIRECTION_BACKWARD = new int[]{};
|
||||
|
||||
return emptyMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.content.*;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.*;
|
||||
import android.view.ViewGroup.*;
|
||||
import android.widget.*;
|
||||
|
|
@ -28,14 +29,25 @@ import net.kdt.pojavlaunch.*;
|
|||
|
||||
import android.view.View.OnClickListener;
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
|
||||
|
||||
import androidx.appcompat.app.*;
|
||||
|
||||
import com.rarepebble.colorpicker.ColorPickerView;
|
||||
|
||||
public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
|
||||
|
||||
private TextView mEditTextView;
|
||||
private TextView mDeleteTextView;
|
||||
|
||||
public ActionPopupWindow(HandleView handleView) {
|
||||
private TextView mCloneTextView;
|
||||
|
||||
private ControlButton editedButton;
|
||||
|
||||
public ActionPopupWindow(HandleView handleView, ControlButton button){
|
||||
super(handleView);
|
||||
this.editedButton = button;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -68,6 +80,12 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
|
|||
mContentView.addView(mDeleteTextView);
|
||||
mDeleteTextView.setText(R.string.global_remove);
|
||||
mDeleteTextView.setOnClickListener(this);
|
||||
|
||||
mCloneTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
|
||||
mCloneTextView.setLayoutParams(wrapContent);
|
||||
mContentView.addView(mCloneTextView);
|
||||
mCloneTextView.setText(R.string.global_clone);
|
||||
mCloneTextView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -77,178 +95,63 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
|
|||
|
||||
@Override
|
||||
public void onClick(final View view) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(view.getContext());
|
||||
alert.setCancelable(false);
|
||||
if (view == mEditTextView) {
|
||||
alert.setTitle(view.getResources().getString(R.string.customctrl_edit, mHandleView.mView.getText()));
|
||||
alert.setView(R.layout.control_setting);
|
||||
alert.setPositiveButton(android.R.string.ok, null);
|
||||
alert.setNegativeButton(android.R.string.cancel, null);
|
||||
final AlertDialog dialog = alert.create();
|
||||
final ControlData properties = mHandleView.mView.getProperties();
|
||||
|
||||
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
if(editedButton instanceof ControlSubButton){
|
||||
new EditControlSubButtonPopup((ControlSubButton) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
final EditText editName = dialog.findViewById(R.id.controlsetting_edit_name);
|
||||
editName.setText(properties.name);
|
||||
if(editedButton instanceof ControlDrawer){
|
||||
new EditControlDrawerPopup((ControlDrawer) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
final Spinner spinnerKeycode = dialog.findViewById(R.id.controlsetting_spinner_lwjglkeycode);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(view.getContext(), android.R.layout.simple_spinner_item);
|
||||
if(editedButton instanceof ControlButton){
|
||||
new EditControlButtonPopup((ControlButton) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] oldSpecialArr = ControlData.buildSpecialButtonArray();
|
||||
final String[] specialArr = new String[oldSpecialArr.length];
|
||||
for (int i = 0; i < specialArr.length; i++) {
|
||||
specialArr[i] = "SPECIAL_" + oldSpecialArr[specialArr.length - i - 1];
|
||||
}
|
||||
|
||||
adapter.addAll(specialArr);
|
||||
adapter.addAll(AndroidLWJGLKeycode.generateKeyName());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
spinnerKeycode.setAdapter(adapter);
|
||||
if (properties.keycode < 0) {
|
||||
spinnerKeycode.setSelection(properties.keycode + specialArr.length);
|
||||
} else {
|
||||
spinnerKeycode.setSelection(AndroidLWJGLKeycode.getIndexByLWJGLKey(properties.keycode) + specialArr.length);
|
||||
}
|
||||
|
||||
final CheckBox checkToggle = dialog.findViewById(R.id.controlsetting_checkbox_toggle);
|
||||
checkToggle.setChecked(properties.isToggle);
|
||||
final CheckBox checkPassthru = dialog.findViewById(R.id.controlsetting_checkbox_passthru);
|
||||
checkPassthru.setChecked(properties.passThruEnabled);
|
||||
final CheckBox checkRound = dialog.findViewById(R.id.controlsetting_checkbox_round);
|
||||
checkRound.setChecked(properties.isRound);
|
||||
final EditText editWidth = dialog.findViewById(R.id.controlsetting_edit_width);
|
||||
final EditText editHeight = dialog.findViewById(R.id.controlsetting_edit_height);
|
||||
editWidth.setText(Float.toString(properties.width));
|
||||
editHeight.setText(Float.toString(properties.height));
|
||||
|
||||
final EditText editDynamicX = dialog.findViewById(R.id.controlsetting_edit_dynamicpos_x);
|
||||
final EditText editDynamicY = dialog.findViewById(R.id.controlsetting_edit_dynamicpos_y);
|
||||
editDynamicX.setEnabled(properties.isDynamicBtn);
|
||||
editDynamicY.setEnabled(properties.isDynamicBtn);
|
||||
|
||||
final SeekBar seekTransparency = dialog.findViewById(R.id.controlsetting_seek_transparency);
|
||||
seekTransparency.setMax(100);
|
||||
seekTransparency.setProgress(properties.hidden ? 100 : properties.transparency);
|
||||
|
||||
final CheckBox checkDynamicPos = dialog.findViewById(R.id.controlsetting_checkbox_dynamicpos);
|
||||
checkDynamicPos.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener(){
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton btn, boolean checked) {
|
||||
editDynamicX.setEnabled(checked);
|
||||
editDynamicY.setEnabled(checked);
|
||||
}
|
||||
});
|
||||
checkDynamicPos.setChecked(properties.isDynamicBtn);
|
||||
|
||||
editDynamicX.setHint(Float.toString(properties.x));
|
||||
editDynamicX.setText(properties.dynamicX);
|
||||
|
||||
editDynamicY.setHint(Float.toString(properties.y));
|
||||
editDynamicY.setText(properties.dynamicY);
|
||||
|
||||
final CheckBox checkHoldAlt = dialog.findViewById(R.id.controlsetting_checkbox_keycombine_alt);
|
||||
checkHoldAlt.setChecked(properties.holdAlt);
|
||||
|
||||
final CheckBox checkHoldControl = dialog.findViewById(R.id.controlsetting_checkbox_keycombine_control);
|
||||
checkHoldControl.setChecked(properties.holdCtrl);
|
||||
|
||||
final CheckBox checkHoldShift = dialog.findViewById(R.id.controlsetting_checkbox_keycombine_shift);
|
||||
checkHoldShift.setChecked(properties.holdShift);
|
||||
|
||||
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view2) {
|
||||
if (editName.getText().toString().isEmpty()) {
|
||||
editName.setError(view.getResources().getString(R.string.global_error_field_empty));
|
||||
} else {
|
||||
/*
|
||||
String errorAt = null;
|
||||
try {
|
||||
errorAt = "DynamicX";
|
||||
properties.insertDynamicPos(editDynamicX.getText().toString());
|
||||
errorAt = "DynamicY";
|
||||
properties.insertDynamicPos(editDynamicY.getText().toString());
|
||||
} catch (Throwable th) {
|
||||
Error e = new Error(errorAt, th);
|
||||
e.setStackTrace(null);
|
||||
Tools.showError(view.getContext(), e);
|
||||
return;
|
||||
}
|
||||
errorAt = null;
|
||||
*/
|
||||
|
||||
if (properties.isDynamicBtn) {
|
||||
int errorAt = 0;
|
||||
try {
|
||||
properties.insertDynamicPos(editDynamicX.getText().toString());
|
||||
errorAt = 1;
|
||||
properties.insertDynamicPos(editDynamicY.getText().toString());
|
||||
} catch (Throwable th) {
|
||||
(errorAt == 0 ? editDynamicX : editDynamicY)
|
||||
.setError(th.getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (spinnerKeycode.getSelectedItemPosition() < specialArr.length) {
|
||||
properties.keycode = spinnerKeycode.getSelectedItemPosition() - specialArr.length;
|
||||
} else {
|
||||
properties.keycode = AndroidLWJGLKeycode.getKeyByIndex(spinnerKeycode.getSelectedItemPosition() - specialArr.length);
|
||||
}
|
||||
properties.name = editName.getText().toString();
|
||||
|
||||
properties.transparency = seekTransparency.getProgress();
|
||||
|
||||
properties.hidden = false;
|
||||
properties.isToggle = checkToggle.isChecked();
|
||||
properties.passThruEnabled = checkPassthru.isChecked();
|
||||
properties.isRound = checkRound.isChecked();
|
||||
properties.isDynamicBtn = checkDynamicPos.isChecked();
|
||||
properties.width = Float.parseFloat(editWidth.getText().toString());
|
||||
properties.height = Float.parseFloat(editHeight.getText().toString());
|
||||
properties.dynamicX = editDynamicX.getText().toString();
|
||||
properties.dynamicY = editDynamicY.getText().toString();
|
||||
|
||||
properties.holdAlt = checkHoldAlt.isChecked();
|
||||
properties.holdCtrl = checkHoldControl.isChecked();
|
||||
properties.holdShift = checkHoldShift.isChecked();
|
||||
|
||||
if (properties.dynamicX.isEmpty()) {
|
||||
properties.dynamicX = Float.toString(properties.x);
|
||||
} if (properties.dynamicY.isEmpty()) {
|
||||
properties.dynamicY = Float.toString(properties.y);
|
||||
}
|
||||
|
||||
mHandleView.mView.updateProperties();
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
} else if (view == mDeleteTextView) {
|
||||
alert.setMessage(view.getContext().getString(R.string.customctrl_remove, mHandleView.mView.getText()) + "?");
|
||||
alert.setPositiveButton(R.string.global_remove, new DialogInterface.OnClickListener(){
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(view.getContext());
|
||||
alertBuilder.setCancelable(false);
|
||||
alertBuilder.setMessage(view.getContext().getString(R.string.customctrl_remove, mHandleView.mView.getText()) + "?");
|
||||
|
||||
alertBuilder.setPositiveButton(R.string.global_remove, (p1, p2) -> {
|
||||
ControlLayout layout = ((ControlLayout) mHandleView.mView.getParent());
|
||||
|
||||
if(editedButton instanceof ControlSubButton){
|
||||
layout.removeControlSubButton((ControlSubButton) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
if(editedButton instanceof ControlDrawer){
|
||||
layout.removeControlDrawer((ControlDrawer) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
if(editedButton instanceof ControlButton){
|
||||
layout.removeControlButton((ControlButton) editedButton);
|
||||
}
|
||||
|
||||
layout.removeControlButton(mHandleView.mView);
|
||||
});
|
||||
alertBuilder.setNegativeButton(android.R.string.cancel, null);
|
||||
alertBuilder.show();
|
||||
}else if(view == mCloneTextView) {
|
||||
if(editedButton instanceof ControlDrawer){
|
||||
ControlDrawerData cloneData = new ControlDrawerData(((ControlDrawer)editedButton).getDrawerData());
|
||||
cloneData.properties.dynamicX = "0.5 * ${screen_width}";
|
||||
cloneData.properties.dynamicY = "0.5 * ${screen_height}";
|
||||
((ControlLayout) mHandleView.mView.getParent()).addDrawer(cloneData);
|
||||
}else{
|
||||
ControlData cloneData = new ControlData(editedButton.getProperties());
|
||||
cloneData.dynamicX = "0.5 * ${screen_width}";
|
||||
cloneData.dynamicY = "0.5 * ${screen_height}";
|
||||
((ControlLayout) mHandleView.mView.getParent()).addControlButton(cloneData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2)
|
||||
{
|
||||
ControlLayout layout = ((ControlLayout) mHandleView.mView.getParent());
|
||||
layout.removeControlButton(mHandleView.mView);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, null);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
hide();
|
||||
|
|
@ -257,32 +160,37 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
|
|||
@Override
|
||||
protected int getTextOffset() {
|
||||
return 0;
|
||||
// return (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVerticalLocalPosition(int line) {
|
||||
// return mTextView.getLayout().getLineTop(line) - mContentView.getMeasuredHeight();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int clipVertically(int positionY) {
|
||||
/*
|
||||
if (positionY < 0) {
|
||||
final int offset = getTextOffset();
|
||||
final Layout layout = mTextView.getLayout();
|
||||
final int line = layout.getLineForOffset(offset);
|
||||
positionY += layout.getLineBottom(line) - layout.getLineTop(line);
|
||||
positionY += mContentView.getMeasuredHeight();
|
||||
|
||||
// Assumes insertion and selection handles share the same height
|
||||
final Drawable handle = mHandleView.getContext().getDrawable(
|
||||
mTextView.mTextSelectHandleRes);
|
||||
positionY += handle.getIntrinsicHeight();
|
||||
}
|
||||
*/
|
||||
|
||||
return positionY;
|
||||
}
|
||||
|
||||
|
||||
public static void showColorPicker(Context ctx,String title, boolean showAlpha, ImageView v){
|
||||
int startColor = ((ColorDrawable)v.getDrawable()).getColor();
|
||||
|
||||
ColorPickerView picker = new ColorPickerView(ctx);
|
||||
picker.setColor(startColor);
|
||||
picker.showAlpha(showAlpha);
|
||||
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
|
||||
dialog.setTitle(title);
|
||||
dialog.setView(picker);
|
||||
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||
dialog.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> v.setImageDrawable(new ColorDrawable(picker.getColor())));
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static void setPercentageText(TextView textView, int progress){
|
||||
textView.setText(progress + " %");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,331 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
|
||||
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
|
||||
|
||||
import static net.kdt.pojavlaunch.customcontrols.handleview.ActionPopupWindow.setPercentageText;
|
||||
|
||||
public class EditControlButtonPopup {
|
||||
|
||||
protected Dialog dialog;
|
||||
protected View v;
|
||||
protected AlertDialog.Builder builder;
|
||||
|
||||
protected EditText editName;
|
||||
protected Spinner[] spinnersKeycode;
|
||||
|
||||
protected CheckBox checkToggle;
|
||||
protected CheckBox checkPassThrough;
|
||||
protected CheckBox checkBoxSwipeable;
|
||||
protected CheckBox checkDynamicPosition;
|
||||
|
||||
protected EditText editWidth;
|
||||
protected EditText editHeight;
|
||||
protected EditText editDynamicX;
|
||||
protected EditText editDynamicY;
|
||||
|
||||
protected SeekBar seekBarOpacity;
|
||||
protected SeekBar seekBarCornerRadius;
|
||||
protected SeekBar seekBarStrokeWidth;
|
||||
|
||||
protected ImageButton buttonBackgroundColor;
|
||||
protected ImageButton buttonStrokeColor;
|
||||
|
||||
protected TextView textOpacity;
|
||||
protected TextView textCornerRadius;
|
||||
protected TextView textStrokeWidth;
|
||||
protected TextView textStrokeColor;
|
||||
|
||||
protected final ControlButton button;
|
||||
protected final ControlData properties;
|
||||
|
||||
protected ArrayAdapter<String> adapter;
|
||||
protected String[] specialArr;
|
||||
|
||||
|
||||
public EditControlButtonPopup(ControlButton button){
|
||||
this.button = button;
|
||||
this.properties = button.getProperties();
|
||||
|
||||
initializeEditDialog(button.getContext());
|
||||
|
||||
//Create the finalized dialog
|
||||
dialog = builder.create();
|
||||
dialog.setOnShowListener(dialogInterface -> setEditDialogValues());
|
||||
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
protected void initializeEditDialog(Context ctx){
|
||||
//Create the editing dialog
|
||||
LayoutInflater layoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
v = layoutInflater.inflate(R.layout.control_button_setting,null);
|
||||
|
||||
builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(ctx.getResources().getString(R.string.customctrl_edit, properties.name));
|
||||
builder.setView(v);
|
||||
|
||||
//Linking a lot of stuff
|
||||
editName = v.findViewById(R.id.editName_editText);
|
||||
|
||||
spinnersKeycode = new Spinner[]{
|
||||
v.findViewById(R.id.editMapping_spinner_1),
|
||||
v.findViewById(R.id.editMapping_spinner_2),
|
||||
v.findViewById(R.id.editMapping_spinner_3),
|
||||
v.findViewById(R.id.editMapping_spinner_4)
|
||||
};
|
||||
|
||||
checkToggle = v.findViewById(R.id.checkboxToggle);
|
||||
checkPassThrough = v.findViewById(R.id.checkboxPassThrough);
|
||||
checkBoxSwipeable = v.findViewById(R.id.checkboxSwipeable);
|
||||
|
||||
editWidth = v.findViewById(R.id.editSize_editTextX);
|
||||
editHeight = v.findViewById(R.id.editSize_editTextY);
|
||||
|
||||
editDynamicX = v.findViewById(R.id.editDynamicPositionX_editText);
|
||||
editDynamicY = v.findViewById(R.id.editDynamicPositionY_editText);
|
||||
|
||||
seekBarOpacity = v.findViewById(R.id.editButtonOpacity_seekbar);
|
||||
seekBarCornerRadius = v.findViewById(R.id.editCornerRadius_seekbar);
|
||||
seekBarStrokeWidth = v.findViewById(R.id.editStrokeWidth_seekbar);
|
||||
|
||||
SeekBar.OnSeekBarChangeListener changeListener = new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||
if(seekBar.equals(seekBarCornerRadius)) {
|
||||
setPercentageText(textCornerRadius, i);
|
||||
return;
|
||||
}
|
||||
if(seekBar.equals(seekBarOpacity)) {
|
||||
setPercentageText(textOpacity, i);
|
||||
return;
|
||||
}
|
||||
if(seekBar.equals(seekBarStrokeWidth)) {
|
||||
setPercentageText(textStrokeWidth, i);
|
||||
textStrokeColor.setVisibility(i == 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {/*STUB*/}
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {/*STUB*/}
|
||||
};
|
||||
|
||||
//Add listeners, too bad I don't need all the methods
|
||||
seekBarOpacity.setOnSeekBarChangeListener(changeListener);
|
||||
seekBarCornerRadius.setOnSeekBarChangeListener(changeListener);
|
||||
seekBarStrokeWidth.setOnSeekBarChangeListener(changeListener);
|
||||
|
||||
buttonBackgroundColor = v.findViewById(R.id.editBackgroundColor_imageButton);
|
||||
buttonStrokeColor = v.findViewById(R.id.editStrokeColor_imageButton);
|
||||
|
||||
textOpacity = v.findViewById(R.id.editButtonOpacity_textView_percent);
|
||||
textCornerRadius = v.findViewById(R.id.editCornerRadius_textView_percent);
|
||||
textStrokeWidth = v.findViewById(R.id.editStrokeWidth_textView_percent);
|
||||
textStrokeColor = v.findViewById(R.id.editStrokeColor_textView);
|
||||
|
||||
checkDynamicPosition = v.findViewById(R.id.checkboxDynamicPosition);
|
||||
checkDynamicPosition.setOnCheckedChangeListener((btn, checked) -> {
|
||||
editDynamicX.setEnabled(checked);
|
||||
editDynamicY.setEnabled(checked);
|
||||
});
|
||||
|
||||
|
||||
//Initialize adapter for keycodes
|
||||
adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
|
||||
String[] oldSpecialArr = ControlData.buildSpecialButtonArray();
|
||||
specialArr = new String[oldSpecialArr.length];
|
||||
for (int i = 0; i < specialArr.length; i++) {
|
||||
specialArr[i] = "SPECIAL_" + oldSpecialArr[specialArr.length - i - 1];
|
||||
}
|
||||
adapter.addAll(specialArr);
|
||||
adapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
|
||||
for (Spinner spinner : spinnersKeycode) {
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
//Set color imageButton behavior
|
||||
buttonBackgroundColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit background color", true, buttonBackgroundColor));
|
||||
buttonStrokeColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit stroke color", false, buttonStrokeColor));
|
||||
|
||||
|
||||
//Set dialog buttons behavior
|
||||
setupDialogButtons();
|
||||
|
||||
hideUselessViews();
|
||||
|
||||
defineDynamicCheckChange();
|
||||
|
||||
setupCheckerboards();
|
||||
}
|
||||
|
||||
protected void setupDialogButtons(){
|
||||
//Set dialog buttons behavior
|
||||
builder.setPositiveButton(android.R.string.ok, (dialogInterface1, i) -> {
|
||||
if(!hasPropertiesErrors(dialog.getContext())){
|
||||
saveProperties();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
protected void hideUselessViews(){
|
||||
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
|
||||
editDynamicX.setVisibility(View.GONE);
|
||||
editDynamicY.setVisibility(View.GONE);
|
||||
|
||||
//Hide the color choice if the width is 0.
|
||||
textStrokeColor.setVisibility(properties.strokeWidth == 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
protected void defineDynamicCheckChange(){
|
||||
checkDynamicPosition.setOnCheckedChangeListener((compoundButton, b) -> {
|
||||
int visibility = b ? View.VISIBLE : View.GONE;
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(visibility);
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(visibility);
|
||||
editDynamicX.setVisibility(visibility);
|
||||
editDynamicY.setVisibility(visibility);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void setupCheckerboards(){
|
||||
CheckerboardDrawable drawable = new CheckerboardDrawable.Builder()
|
||||
.colorEven(Color.LTGRAY)
|
||||
.colorOdd(Color.WHITE)
|
||||
.size((int) Tools.dpToPx(20))
|
||||
.build();
|
||||
|
||||
buttonBackgroundColor.setBackground(drawable);
|
||||
buttonStrokeColor.setBackground(drawable);
|
||||
}
|
||||
|
||||
protected void setEditDialogValues(){
|
||||
|
||||
editName.setText(properties.name);
|
||||
|
||||
checkToggle.setChecked(properties.isToggle);
|
||||
checkPassThrough.setChecked(properties.passThruEnabled);
|
||||
checkBoxSwipeable.setChecked(properties.isSwipeable);
|
||||
|
||||
editWidth.setText(Float.toString(properties.getWidth()));
|
||||
editHeight.setText(Float.toString(properties.getHeight()));
|
||||
|
||||
editDynamicX.setEnabled(properties.isDynamicBtn);
|
||||
editDynamicY.setEnabled(properties.isDynamicBtn);
|
||||
editDynamicX.setText(properties.dynamicX);
|
||||
|
||||
editDynamicY.setText(properties.dynamicY);
|
||||
|
||||
seekBarOpacity.setProgress((int) (properties.opacity*100));
|
||||
seekBarStrokeWidth.setProgress(properties.strokeWidth);
|
||||
seekBarCornerRadius.setProgress((int)properties.cornerRadius);
|
||||
|
||||
buttonBackgroundColor.setImageDrawable(new ColorDrawable(properties.bgColor));
|
||||
buttonStrokeColor.setImageDrawable(new ColorDrawable(properties.strokeColor));
|
||||
|
||||
setPercentageText(textCornerRadius,seekBarCornerRadius.getProgress());
|
||||
setPercentageText(textOpacity,seekBarOpacity.getProgress());
|
||||
setPercentageText(textStrokeWidth,seekBarStrokeWidth.getProgress());
|
||||
|
||||
checkDynamicPosition.setChecked(properties.isDynamicBtn);
|
||||
|
||||
for(int i=0; i< properties.keycodes.length; i++){
|
||||
if (properties.keycodes[i] < 0) {
|
||||
spinnersKeycode[i].setSelection(properties.keycodes[i] + specialArr.length);
|
||||
} else {
|
||||
spinnersKeycode[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(properties.keycodes[i]) + specialArr.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean hasPropertiesErrors(Context ctx){
|
||||
if (editName.getText().toString().isEmpty()) {
|
||||
editName.setError(ctx.getResources().getString(R.string.global_error_field_empty));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (properties.isDynamicBtn) {
|
||||
|
||||
int errorAt = 0;
|
||||
try {
|
||||
properties.insertDynamicPos(editDynamicX.getText().toString());
|
||||
errorAt = 1;
|
||||
properties.insertDynamicPos(editDynamicY.getText().toString());
|
||||
} catch (Throwable th) {
|
||||
(errorAt == 0 ? editDynamicX : editDynamicY).setError(th.getMessage());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void saveProperties(){
|
||||
//This method assumes there are no error.
|
||||
properties.name = editName.getText().toString();
|
||||
|
||||
//Keycodes
|
||||
for(int i=0; i<spinnersKeycode.length; ++i){
|
||||
if (spinnersKeycode[i].getSelectedItemPosition() < specialArr.length) {
|
||||
properties.keycodes[i] = spinnersKeycode[i].getSelectedItemPosition() - specialArr.length;
|
||||
} else {
|
||||
properties.keycodes[i] = EfficientAndroidLWJGLKeycode.getValueByIndex(spinnersKeycode[i].getSelectedItemPosition() - specialArr.length);
|
||||
}
|
||||
}
|
||||
|
||||
properties.opacity = seekBarOpacity.getProgress()/100f;
|
||||
properties.strokeWidth = seekBarStrokeWidth.getProgress();
|
||||
properties.cornerRadius = seekBarCornerRadius.getProgress();
|
||||
|
||||
properties.bgColor = ((ColorDrawable)buttonBackgroundColor.getDrawable()).getColor();
|
||||
properties.strokeColor = ((ColorDrawable) buttonStrokeColor.getDrawable()).getColor();
|
||||
|
||||
properties.isToggle = checkToggle.isChecked();
|
||||
properties.passThruEnabled = checkPassThrough.isChecked();
|
||||
properties.isSwipeable = checkBoxSwipeable.isChecked();
|
||||
|
||||
properties.setWidth(Float.parseFloat(editWidth.getText().toString()));
|
||||
properties.setHeight(Float.parseFloat(editHeight.getText().toString()));
|
||||
|
||||
properties.isDynamicBtn = checkDynamicPosition.isChecked();
|
||||
if(!editDynamicX.getText().toString().isEmpty()) properties.dynamicX = editDynamicX.getText().toString();
|
||||
if(!editDynamicY.getText().toString().isEmpty()) properties.dynamicY = editDynamicY.getText().toString();
|
||||
|
||||
button.updateProperties();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
|
||||
public class EditControlDrawerPopup extends EditControlButtonPopup{
|
||||
private Spinner spinnerOrientation;
|
||||
|
||||
|
||||
private ControlDrawer drawer;
|
||||
private ControlDrawerData drawerData;
|
||||
|
||||
public EditControlDrawerPopup(ControlDrawer editedButton) {
|
||||
super(editedButton);
|
||||
drawer = editedButton;
|
||||
drawerData = editedButton.getDrawerData();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void hideUselessViews() {
|
||||
(v.findViewById(R.id.editMapping_textView)).setVisibility(View.GONE);
|
||||
checkPassThrough.setVisibility(View.GONE);
|
||||
checkToggle.setVisibility(View.GONE);
|
||||
checkBoxSwipeable.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeEditDialog(Context ctx) {
|
||||
super.initializeEditDialog(ctx);
|
||||
|
||||
spinnerOrientation = v.findViewById(R.id.editOrientation_spinner);
|
||||
|
||||
ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
|
||||
adapter.addAll(ControlDrawerData.getOrientations());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
|
||||
spinnerOrientation.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEditDialogValues() {
|
||||
super.setEditDialogValues();
|
||||
|
||||
spinnerOrientation.setSelection(ControlDrawerData.orientationToInt(drawerData.orientation));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupDialogButtons() {
|
||||
super.setupDialogButtons();
|
||||
|
||||
builder.setNeutralButton(v.getResources().getString(R.string.customctrl_addsubbutton), (dialogInterface, i) -> {
|
||||
ControlLayout layout = (ControlLayout) drawer.getParent();
|
||||
ControlData controlData = new ControlData(drawerData.properties);
|
||||
controlData.name = "new";
|
||||
layout.addSubButton(drawer, controlData);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveProperties() {
|
||||
drawerData.orientation = ControlDrawerData.intToOrientation(spinnerOrientation.getSelectedItemPosition());
|
||||
super.saveProperties();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
|
||||
public class EditControlSubButtonPopup extends EditControlButtonPopup{
|
||||
|
||||
|
||||
public EditControlSubButtonPopup(ControlButton button){
|
||||
super(button);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hideUselessViews() {
|
||||
(v.findViewById(R.id.editSize_textView)).setVisibility(View.GONE);
|
||||
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
|
||||
|
||||
checkDynamicPosition.setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
|
||||
editDynamicX.setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
|
||||
editDynamicY.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,13 +22,12 @@ package net.kdt.pojavlaunch.customcontrols.handleview;
|
|||
import android.graphics.*;
|
||||
import android.graphics.drawable.*;
|
||||
import android.os.*;
|
||||
import android.text.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import android.content.*;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
|
||||
|
||||
public abstract class HandleView extends View implements ViewPositionListener, View.OnLongClickListener
|
||||
{
|
||||
|
|
@ -100,6 +99,7 @@ public abstract class HandleView extends View implements ViewPositionListener, V
|
|||
mIdealVerticalOffset = 0.7f * handleHeight;
|
||||
}
|
||||
|
||||
|
||||
protected void updateDrawable() {
|
||||
// final int offset = getCurrentCursorOffset();
|
||||
final boolean isRtlCharAtOffset = true; // mView.getLayout().isRtlCharAt(offset);
|
||||
|
|
@ -189,9 +189,9 @@ public abstract class HandleView extends View implements ViewPositionListener, V
|
|||
getPositionListener().removeSubscriber(this);
|
||||
}
|
||||
|
||||
void showActionPopupWindow(int delay) {
|
||||
void showActionPopupWindow(int delay, ControlButton button) {
|
||||
if (mActionPopupWindow == null) {
|
||||
mActionPopupWindow = new ActionPopupWindow(this);
|
||||
mActionPopupWindow = new ActionPopupWindow(this, button);
|
||||
}
|
||||
if (mActionPopupShower == null) {
|
||||
mActionPopupShower = new Runnable() {
|
||||
|
|
@ -314,13 +314,6 @@ public abstract class HandleView extends View implements ViewPositionListener, V
|
|||
protected int getCursorOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Addition
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
showActionPopupWindow(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Addition
|
||||
private float mDownX, mDownY;
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.graphics.drawable.*;
|
||||
import android.text.*;
|
||||
import android.view.*;
|
||||
import android.os.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
|
||||
|
||||
public class SelectionEndHandleView extends HandleView
|
||||
{
|
||||
|
|
@ -50,11 +50,9 @@ public class SelectionEndHandleView extends HandleView
|
|||
return 0; // mView.getSelectionEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
public void show(ControlButton button){
|
||||
super.show();
|
||||
|
||||
showActionPopupWindow(0);
|
||||
showActionPopupWindow(0, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -68,10 +66,11 @@ public class SelectionEndHandleView extends HandleView
|
|||
// updatePosition((int) x, (int) y, false, false);
|
||||
positionAtCursorOffset(0, false);
|
||||
}
|
||||
/*
|
||||
public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
|
||||
mActionPopupWindow = actionPopupWindow;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
//TODO stub
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import android.os.Bundle;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -17,28 +21,68 @@ import android.webkit.WebViewClient;
|
|||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class LauncherFragment extends Fragment
|
||||
{
|
||||
private WebView webNews;
|
||||
private View view;
|
||||
private Thread validUrlSelectorThread;
|
||||
private String validChangelog = "/changelog.html";
|
||||
private Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
private boolean interruptLoad = false;
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
view = inflater.inflate(R.layout.lmaintab_news, container, false);
|
||||
return view;
|
||||
}
|
||||
public void selectValidUrl() {
|
||||
String lang = LauncherPreferences.PREF_LANGUAGE;
|
||||
if(lang.equals("default")) lang = Locale.getDefault().getLanguage();
|
||||
final String localizedUrl = "/changelog-"+lang+".html";
|
||||
if(!tryUrl(Tools.URL_HOME+localizedUrl)) return;
|
||||
else {
|
||||
mainHandler.post(()->{
|
||||
interruptLoad = true;
|
||||
validChangelog = localizedUrl;
|
||||
webNews.loadUrl(Tools.URL_HOME+validChangelog);
|
||||
});
|
||||
}
|
||||
}
|
||||
public boolean tryUrl(String url) {
|
||||
Log.i("ChangelogLocale","Trying localized url: "+url);
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.connect();
|
||||
Log.i("ChangelogLocale","Code: "+conn.getResponseCode());
|
||||
return ("" + conn.getResponseCode()).startsWith("2");
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onActivityCreated(Bundle p1)
|
||||
{
|
||||
super.onActivityCreated(p1);
|
||||
|
||||
mainHandler = new Handler(Looper.myLooper());
|
||||
webNews = (WebView) getView().findViewById(R.id.lmaintabnewsNewsView);
|
||||
webNews.setWebViewClient(new WebViewClient(){
|
||||
|
||||
// API < 23
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
Log.i("WebNews",failingUrl + ": "+description);
|
||||
if(webNews != null){
|
||||
if(validUrlSelectorThread.isAlive()) validUrlSelectorThread.interrupt();
|
||||
removeWebView();
|
||||
//Change the background to match the other pages.
|
||||
//We change it only when the webView is removed to avoid huge overdraw.
|
||||
|
|
@ -48,7 +92,7 @@ public class LauncherFragment extends Fragment
|
|||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if(!url.equals(Tools.URL_HOME + "/changelog.html")){
|
||||
if(!url.equals(Tools.URL_HOME + validChangelog)){
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(i);
|
||||
return true;
|
||||
|
|
@ -59,7 +103,9 @@ public class LauncherFragment extends Fragment
|
|||
@RequiresApi(23) //API 23+
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
Log.i("WebNews",error.getDescription()+"");
|
||||
if(webNews != null){
|
||||
if(validUrlSelectorThread.isAlive()) validUrlSelectorThread.interrupt();
|
||||
removeWebView();
|
||||
LauncherFragment.this.view.setBackgroundColor(Color.parseColor("#44000000"));
|
||||
}
|
||||
|
|
@ -68,7 +114,7 @@ public class LauncherFragment extends Fragment
|
|||
@RequiresApi(23)
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
if(!request.getUrl().toString().equals(Tools.URL_HOME + "/changelog.html")){
|
||||
if(!request.getUrl().toString().equals(Tools.URL_HOME + validChangelog)){
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, request.getUrl());
|
||||
startActivity(i);
|
||||
return true;
|
||||
|
|
@ -78,7 +124,9 @@ public class LauncherFragment extends Fragment
|
|||
});
|
||||
webNews.clearCache(true);
|
||||
webNews.getSettings().setJavaScriptEnabled(true);
|
||||
webNews.loadUrl(Tools.URL_HOME + "/changelog.html");
|
||||
validUrlSelectorThread = new Thread(this::selectValidUrl);
|
||||
validUrlSelectorThread.start();
|
||||
if(!interruptLoad)webNews.loadUrl(Tools.URL_HOME + validChangelog);
|
||||
}
|
||||
|
||||
private void removeWebView() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.kdt.pojavlaunch.BaseLauncherActivity;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class MultiRTConfigDialog {
|
||||
public static final int MULTIRT_PICK_RUNTIME = 2048;
|
||||
public static final int MULTIRT_PICK_RUNTIME_STARTUP = 2049;
|
||||
public AlertDialog dialog;
|
||||
public RecyclerView dialogView;
|
||||
public void prepare(BaseLauncherActivity ctx) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(R.string.multirt_config_title);
|
||||
dialogView = new RecyclerView(ctx);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(ctx);
|
||||
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
dialogView.setLayoutManager(linearLayoutManager);
|
||||
dialogView.setAdapter(new RTRecyclerViewAdapter(this));
|
||||
builder.setView(dialogView);
|
||||
builder.setPositiveButton(R.string.multirt_config_add, (dialog, which) -> openRuntimeSelector(ctx,MULTIRT_PICK_RUNTIME));
|
||||
builder.setNegativeButton(R.string.mcn_exit_call, (dialog, which) -> dialog.cancel());
|
||||
dialog = builder.create();
|
||||
}
|
||||
public void refresh() {
|
||||
RecyclerView.Adapter adapter = dialogView.getAdapter();
|
||||
if(adapter != null)dialogView.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
public static void openRuntimeSelector(Activity ctx, int code) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("xz");
|
||||
if(mimeType == null) mimeType = "*/*";
|
||||
intent.setType(mimeType);
|
||||
ctx.startActivityForResult(intent,code);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.Os;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.utils.JREUtils;
|
||||
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MultiRTUtils {
|
||||
public static HashMap<String,Runtime> cache = new HashMap<>();
|
||||
public static class Runtime {
|
||||
public Runtime(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public String name;
|
||||
public String versionString;
|
||||
public String arch;
|
||||
public int javaVersion;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Runtime runtime = (Runtime) o;
|
||||
return name.equals(runtime.name);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
}
|
||||
public static interface ProgressReporterThingy {
|
||||
void reportStringProgress(int resid, Object ... stuff);
|
||||
}
|
||||
private static final File runtimeFolder = new File(Tools.MULTIRT_HOME);
|
||||
private static final String JAVA_VERSION_str = "JAVA_VERSION=\"";
|
||||
private static final String OS_ARCH_str = "OS_ARCH=\"";
|
||||
public static List<Runtime> getRuntimes() {
|
||||
if(!runtimeFolder.exists()) runtimeFolder.mkdirs();
|
||||
ArrayList<Runtime> ret = new ArrayList<>();
|
||||
System.out.println("Fetch runtime list");
|
||||
for(File f : runtimeFolder.listFiles()) {
|
||||
ret.add(read(f.getName()));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
public static String getNearestJREName(int majorVersion) {
|
||||
List<Runtime> runtimes = getRuntimes();
|
||||
int diff_factor = Integer.MAX_VALUE;
|
||||
String result = null;
|
||||
for(Runtime r : runtimes) {
|
||||
if(r.javaVersion >= majorVersion) { // lower - not useful
|
||||
int currentFactor = r.javaVersion - majorVersion;
|
||||
if(diff_factor > currentFactor) {
|
||||
result = r.name;
|
||||
diff_factor = currentFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static void installRuntimeNamed(InputStream runtimeInputStream, String name, ProgressReporterThingy thingy) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
File tmp = new File(dest,"temporary");
|
||||
if(dest.exists()) FileUtils.deleteDirectory(dest);
|
||||
dest.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(tmp);
|
||||
thingy.reportStringProgress(R.string.multirt_progress_caching);
|
||||
IOUtils.copy(runtimeInputStream,fos);
|
||||
fos.close();
|
||||
runtimeInputStream.close();
|
||||
uncompressTarXZ(tmp,dest,thingy);
|
||||
tmp.delete();
|
||||
read(name);
|
||||
}
|
||||
private static void __installRuntimeNamed__NoRM(InputStream runtimeInputStream, File dest, ProgressReporterThingy thingy) throws IOException {
|
||||
File tmp = new File(dest,"temporary");
|
||||
FileOutputStream fos = new FileOutputStream(tmp);
|
||||
thingy.reportStringProgress(R.string.multirt_progress_caching);
|
||||
IOUtils.copy(runtimeInputStream,fos);
|
||||
fos.close();
|
||||
runtimeInputStream.close();
|
||||
uncompressTarXZ(tmp,dest,thingy);
|
||||
tmp.delete();
|
||||
}
|
||||
public static void postPrepare(Context ctx, String name) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if(!dest.exists()) return;
|
||||
Runtime r = read(name);
|
||||
String libFolder = "lib";
|
||||
if(new File(dest,libFolder+"/"+r.arch).exists()) libFolder = libFolder+"/"+r.arch;
|
||||
File ftIn = new File(dest, libFolder+ "/libfreetype.so.6");
|
||||
File ftOut = new File(dest, libFolder + "/libfreetype.so");
|
||||
if (ftIn.exists() && (!ftOut.exists() || ftIn.length() != ftOut.length())) {
|
||||
ftIn.renameTo(ftOut);
|
||||
}
|
||||
|
||||
// Refresh libraries
|
||||
copyDummyNativeLib(ctx,"libawt_xawt.so",dest,libFolder);
|
||||
}
|
||||
private static void copyDummyNativeLib(Context ctx, String name, File dest, String libFolder) throws IOException {
|
||||
|
||||
File fileLib = new File(dest, "/"+libFolder + "/" + name);
|
||||
fileLib.delete();
|
||||
FileInputStream is = new FileInputStream(new File(ctx.getApplicationInfo().nativeLibraryDir, name));
|
||||
FileOutputStream os = new FileOutputStream(fileLib);
|
||||
IOUtils.copy(is, os);
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
public static Runtime installRuntimeNamedBinpack(InputStream universalFileInputStream, InputStream platformBinsInputStream, String name, String binpackVersion, ProgressReporterThingy thingy) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if(dest.exists()) FileUtils.deleteDirectory(dest);
|
||||
dest.mkdirs();
|
||||
__installRuntimeNamed__NoRM(universalFileInputStream,dest,thingy);
|
||||
__installRuntimeNamed__NoRM(platformBinsInputStream,dest,thingy);
|
||||
File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version");
|
||||
FileOutputStream fos = new FileOutputStream(binpack_verfile);
|
||||
fos.write(binpackVersion.getBytes());
|
||||
fos.close();
|
||||
cache.remove(name); // Force reread
|
||||
return read(name);
|
||||
}
|
||||
public static String __internal__readBinpackVersion(String name) {
|
||||
File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version");
|
||||
try {
|
||||
if (binpack_verfile.exists()) {
|
||||
return Tools.read(binpack_verfile.getAbsolutePath());
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static void removeRuntimeNamed(String name) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if(dest.exists()) {
|
||||
FileUtils.deleteDirectory(dest);
|
||||
cache.remove(name);
|
||||
}
|
||||
}
|
||||
public static void setRuntimeNamed(Context ctx, String name) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if((!dest.exists()) || MultiRTUtils.forceReread(name).versionString == null) throw new RuntimeException("Selected runtime is broken!");
|
||||
Tools.DIR_HOME_JRE = dest.getAbsolutePath();
|
||||
JREUtils.relocateLibPath(ctx);
|
||||
}
|
||||
public static Runtime forceReread(String name) {
|
||||
cache.remove(name);
|
||||
return read(name);
|
||||
}
|
||||
public static Runtime read(String name) {
|
||||
if(cache.containsKey(name)) return cache.get(name);
|
||||
Runtime retur;
|
||||
File release = new File(runtimeFolder,"/"+name+"/release");
|
||||
if(!release.exists()) {
|
||||
return new Runtime(name);
|
||||
}
|
||||
try {
|
||||
String content = Tools.read(release.getAbsolutePath());
|
||||
int _JAVA_VERSION_index = content.indexOf(JAVA_VERSION_str);
|
||||
int _OS_ARCH_index = content.indexOf(OS_ARCH_str);
|
||||
if(_JAVA_VERSION_index != -1 && _OS_ARCH_index != -1) {
|
||||
_JAVA_VERSION_index += JAVA_VERSION_str.length();
|
||||
_OS_ARCH_index += OS_ARCH_str.length();
|
||||
String javaVersion = content.substring(_JAVA_VERSION_index,content.indexOf('"',_JAVA_VERSION_index));
|
||||
String[] javaVersionSplit = javaVersion.split("\\.");
|
||||
int javaVersionInt;
|
||||
if (javaVersionSplit[0].equals("1")) {
|
||||
javaVersionInt = Integer.parseInt(javaVersionSplit[1]);
|
||||
} else {
|
||||
javaVersionInt = Integer.parseInt(javaVersionSplit[0]);
|
||||
}
|
||||
Runtime r = new Runtime(name);
|
||||
r.arch = content.substring(_OS_ARCH_index,content.indexOf('"',_OS_ARCH_index));
|
||||
r.javaVersion = javaVersionInt;
|
||||
r.versionString = javaVersion;
|
||||
retur = r;
|
||||
}else{
|
||||
retur = new Runtime(name);
|
||||
}
|
||||
}catch(IOException e) {
|
||||
retur = new Runtime(name);
|
||||
}
|
||||
cache.put(name,retur);
|
||||
return retur;
|
||||
}
|
||||
private static void uncompressTarXZ(final File tarFile, final File dest, final ProgressReporterThingy thingy) throws IOException {
|
||||
dest.mkdirs();
|
||||
TarArchiveInputStream tarIn = null;
|
||||
|
||||
tarIn = new TarArchiveInputStream(
|
||||
new XZCompressorInputStream(
|
||||
new BufferedInputStream(
|
||||
new FileInputStream(tarFile)
|
||||
)
|
||||
)
|
||||
);
|
||||
TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
|
||||
// tarIn is a TarArchiveInputStream
|
||||
while (tarEntry != null) {
|
||||
/*
|
||||
* Unpacking very small files in short time cause
|
||||
* application to ANR or out of memory, so delay
|
||||
* a little if size is below than 20kb (20480 bytes)
|
||||
*/
|
||||
if (tarEntry.getSize() <= 20480) {
|
||||
try {
|
||||
// 40 small files per second
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
final String tarEntryName = tarEntry.getName();
|
||||
// publishProgress(null, "Unpacking " + tarEntry.getName());
|
||||
thingy.reportStringProgress(R.string.global_unpacking,tarEntryName);
|
||||
File destPath = new File(dest, tarEntry.getName());
|
||||
if (tarEntry.isSymbolicLink()) {
|
||||
destPath.getParentFile().mkdirs();
|
||||
try {
|
||||
// android.system.Os
|
||||
// Libcore one support all Android versions
|
||||
Os.symlink(tarEntry.getName(), tarEntry.getLinkName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else if (tarEntry.isDirectory()) {
|
||||
destPath.mkdirs();
|
||||
destPath.setExecutable(true);
|
||||
} else if (!destPath.exists() || destPath.length() != tarEntry.getSize()) {
|
||||
destPath.getParentFile().mkdirs();
|
||||
destPath.createNewFile();
|
||||
|
||||
FileOutputStream os = new FileOutputStream(destPath);
|
||||
IOUtils.copy(tarIn, os);
|
||||
os.close();
|
||||
|
||||
}
|
||||
tarEntry = tarIn.getNextTarEntry();
|
||||
}
|
||||
tarIn.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class RTRecyclerViewAdapter extends RecyclerView.Adapter<RTRecyclerViewAdapter.RTViewHolder> {
|
||||
MultiRTConfigDialog dialog;
|
||||
public RTRecyclerViewAdapter(MultiRTConfigDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
@NonNull
|
||||
@Override
|
||||
public RTViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View recyclableView = LayoutInflater.from(parent.getContext()).inflate(R.layout.multirt_recyclable_view,parent,false);
|
||||
return new RTViewHolder(recyclableView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RTViewHolder holder, int position) {
|
||||
final List<MultiRTUtils.Runtime> runtimes = MultiRTUtils.getRuntimes();
|
||||
holder.bindRuntime(runtimes.get(position),position);
|
||||
}
|
||||
public boolean isDefaultRuntime(MultiRTUtils.Runtime rt) {
|
||||
return LauncherPreferences.PREF_DEFAULT_RUNTIME.equals(rt.name);
|
||||
}
|
||||
public void setDefault(MultiRTUtils.Runtime rt){
|
||||
LauncherPreferences.PREF_DEFAULT_RUNTIME = rt.name;
|
||||
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultRuntime",LauncherPreferences.PREF_DEFAULT_RUNTIME).apply();
|
||||
RTRecyclerViewAdapter.this.notifyDataSetChanged();
|
||||
}
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return MultiRTUtils.getRuntimes().size();
|
||||
}
|
||||
public class RTViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
|
||||
final TextView javaVersionView;
|
||||
final TextView fullJavaVersionView;
|
||||
final ColorStateList defaultColors;
|
||||
final Button setDefaultButton;
|
||||
final Context ctx;
|
||||
MultiRTUtils.Runtime currentRuntime;
|
||||
int currentPosition;
|
||||
public RTViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
javaVersionView = itemView.findViewById(R.id.multirt_view_java_version);
|
||||
fullJavaVersionView = itemView.findViewById(R.id.multirt_view_java_version_full);
|
||||
itemView.findViewById(R.id.multirt_view_removebtn).setOnClickListener(this);
|
||||
setDefaultButton = itemView.findViewById(R.id.multirt_view_setdefaultbtn);
|
||||
setDefaultButton.setOnClickListener(this);
|
||||
defaultColors = fullJavaVersionView.getTextColors();
|
||||
ctx = itemView.getContext();
|
||||
}
|
||||
public void bindRuntime(MultiRTUtils.Runtime rt, int pos) {
|
||||
currentRuntime = rt;
|
||||
currentPosition = pos;
|
||||
if(rt.versionString != null) {
|
||||
javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion));
|
||||
fullJavaVersionView.setText(rt.versionString);
|
||||
fullJavaVersionView.setTextColor(defaultColors);
|
||||
setDefaultButton.setVisibility(View.VISIBLE);
|
||||
boolean default_ = isDefaultRuntime(rt);
|
||||
setDefaultButton.setEnabled(!default_);
|
||||
setDefaultButton.setText(default_?R.string.multirt_config_setdefault_already:R.string.multirt_config_setdefault);
|
||||
}else{
|
||||
javaVersionView.setText(rt.name);
|
||||
fullJavaVersionView.setText(R.string.multirt_runtime_corrupt);
|
||||
fullJavaVersionView.setTextColor(Color.RED);
|
||||
setDefaultButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(v.getId() == R.id.multirt_view_removebtn) {
|
||||
if (currentRuntime != null) {
|
||||
if(MultiRTUtils.getRuntimes().size() < 2) {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(ctx);
|
||||
bldr.setTitle(R.string.global_error);
|
||||
bldr.setMessage(R.string.multirt_config_removeerror_last);
|
||||
bldr.setPositiveButton(android.R.string.ok,(adapter, which)->adapter.dismiss());
|
||||
bldr.show();
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressDialog barrier = new ProgressDialog(ctx);
|
||||
barrier.setMessage(ctx.getString(R.string.global_waiting));
|
||||
barrier.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
barrier.setCancelable(false);
|
||||
barrier.show();
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
MultiRTUtils.removeRuntimeNamed(currentRuntime.name);
|
||||
} catch (IOException e) {
|
||||
Tools.showError(itemView.getContext(), e);
|
||||
}
|
||||
v.post(() -> {
|
||||
if(isDefaultRuntime(currentRuntime)) setDefault(MultiRTUtils.getRuntimes().get(0));
|
||||
barrier.dismiss();
|
||||
RTRecyclerViewAdapter.this.notifyDataSetChanged();
|
||||
dialog.dialog.show();
|
||||
});
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
}else if(v.getId() == R.id.multirt_view_setdefaultbtn) {
|
||||
if(currentRuntime != null) {
|
||||
setDefault(currentRuntime);
|
||||
RTRecyclerViewAdapter.this.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RTSpinnerAdapter implements SpinnerAdapter {
|
||||
final Context ctx;
|
||||
List<MultiRTUtils.Runtime> runtimes;
|
||||
public RTSpinnerAdapter(@NonNull Context context, List<MultiRTUtils.Runtime> runtimes) {
|
||||
this.runtimes = runtimes;
|
||||
MultiRTUtils.Runtime runtime = new MultiRTUtils.Runtime("<Default>");
|
||||
runtime.versionString = "";
|
||||
this.runtimes.add(runtime);
|
||||
ctx = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return runtimes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return runtimes.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return runtimes.get(position).name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View v = convertView!=null?
|
||||
convertView:
|
||||
LayoutInflater.from(ctx).inflate(R.layout.multirt_recyclable_view,parent,false);
|
||||
|
||||
MultiRTUtils.Runtime rt = runtimes.get(position);
|
||||
|
||||
final TextView javaVersionView = v.findViewById(R.id.multirt_view_java_version);
|
||||
final TextView fullJavaVersionView = v.findViewById(R.id.multirt_view_java_version_full);
|
||||
v.findViewById(R.id.multirt_view_removebtn).setVisibility(View.GONE);
|
||||
v.findViewById(R.id.multirt_view_setdefaultbtn).setVisibility(View.GONE);
|
||||
|
||||
if(rt.versionString != null) {
|
||||
javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion));
|
||||
fullJavaVersionView.setText(rt.versionString);
|
||||
}else{
|
||||
javaVersionView.setText(rt.name);
|
||||
fullJavaVersionView.setText(R.string.multirt_runtime_corrupt);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return runtimes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
return getView(position,convertView,parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -44,9 +44,7 @@ public class CustomSeekBarPreference extends SeekBarPreference {
|
|||
public void setMin(int min) {
|
||||
//Note: since the max (setMax is a final function) is not taken into account properly, setting the min over the max may produce funky results
|
||||
super.setMin(min);
|
||||
if (min != mMin) {
|
||||
mMin = min;
|
||||
}
|
||||
if (min != mMin) mMin = min;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -92,7 +90,21 @@ public class CustomSeekBarPreference extends SeekBarPreference {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a suffix to be appended on the TextView associated to the value
|
||||
* @param suffix The suffix to append as a String
|
||||
*/
|
||||
public void setSuffix(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to set both min and max at the same time.
|
||||
* @param min The minimum value
|
||||
* @param max The maximum value
|
||||
*/
|
||||
public void setRange(int min, int max){
|
||||
setMin(min);
|
||||
setMax(max);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,76 @@
|
|||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.*;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.fragments.LauncherFragment;
|
||||
|
||||
import android.content.*;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import static net.kdt.pojavlaunch.Architecture.is32BitsDevice;
|
||||
import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
|
||||
|
||||
public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
view.setBackgroundColor(Color.parseColor("#44000000"));
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
addPreferencesFromResource(R.xml.pref_main);
|
||||
|
||||
//Disable notch checking behavior on android 8.1 and below.
|
||||
findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE != 0);
|
||||
|
||||
// Disable freeform mode in Android 6.0 and below.
|
||||
findPreference("freeform").setVisible(Build.VERSION.SDK_INT >= 24);
|
||||
//Disable notch checking behavior on android 8.0 and below.
|
||||
findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= 29);
|
||||
|
||||
CustomSeekBarPreference seek2 = (CustomSeekBarPreference) findPreference("timeLongPressTrigger");
|
||||
seek2.setMin(100);
|
||||
seek2.setMax(1000);
|
||||
CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger");
|
||||
seek2.setRange(100, 1000);
|
||||
seek2.setValue(LauncherPreferences.PREF_LONGPRESS_TRIGGER);
|
||||
seek2.setSuffix(" ms");
|
||||
|
||||
CustomSeekBarPreference seek3 = (CustomSeekBarPreference) findPreference("buttonscale");
|
||||
seek3.setMin(80);
|
||||
seek3.setMax(250);
|
||||
CustomSeekBarPreference seek3 = findPreference("buttonscale");
|
||||
seek3.setRange(80, 250);
|
||||
seek3.setValue((int) LauncherPreferences.PREF_BUTTONSIZE);
|
||||
seek3.setSuffix(" %");
|
||||
|
||||
CustomSeekBarPreference seek4 = (CustomSeekBarPreference) findPreference("mousescale");
|
||||
seek4.setMin(25);
|
||||
seek4.setMax(300);
|
||||
CustomSeekBarPreference seek4 = findPreference("mousescale");
|
||||
seek4.setRange(25, 300);
|
||||
seek4.setValue((int) LauncherPreferences.PREF_MOUSESCALE);
|
||||
seek4.setSuffix(" %");
|
||||
|
||||
CustomSeekBarPreference seek5 = (CustomSeekBarPreference) findPreference("resolutionRatio");
|
||||
CustomSeekBarPreference seek5 = findPreference("resolutionRatio");
|
||||
seek5.setMin(25);
|
||||
seek5.setSuffix(" %");
|
||||
|
||||
CustomSeekBarPreference seek6 = (CustomSeekBarPreference) findPreference("mousespeed");
|
||||
seek6.setMin(25);
|
||||
seek6.setValue((int)(LauncherPreferences.PREF_MOUSESPEED*100f));
|
||||
seek6.setMax(300);
|
||||
CustomSeekBarPreference seek6 = findPreference("mousespeed");
|
||||
seek6.setRange(25, 300);
|
||||
seek6.setValue((int)(LauncherPreferences.PREF_MOUSESPEED*100f));
|
||||
seek6.setSuffix(" %");
|
||||
|
||||
CustomSeekBarPreference seek7 = (CustomSeekBarPreference) findPreference("allocation");
|
||||
|
||||
int maxRAM;
|
||||
int deviceRam = getTotalDeviceMemory(getContext());
|
||||
|
||||
|
||||
CustomSeekBarPreference seek7 = findPreference("allocation");
|
||||
seek7.setMin(256);
|
||||
if(Tools.CURRENT_ARCHITECTURE.contains("32")) seek7.setMax(800);
|
||||
else seek7.setMax(4096);
|
||||
|
||||
if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam);
|
||||
else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe
|
||||
|
||||
seek7.setMax(maxRAM);
|
||||
seek7.setValue(LauncherPreferences.PREF_RAM_ALLOCATION);
|
||||
seek7.setSuffix(" MB");
|
||||
|
||||
|
|
@ -80,6 +99,6 @@ public class LauncherPreferenceFragment extends PreferenceFragmentCompat impleme
|
|||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences p, String s) {
|
||||
LauncherPreferences.loadPreferences();
|
||||
LauncherPreferences.loadPreferences(getContext());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,63 +2,61 @@ package net.kdt.pojavlaunch.prefs;
|
|||
|
||||
import android.content.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.utils.JREUtils;
|
||||
|
||||
public class LauncherPreferences
|
||||
{
|
||||
public static SharedPreferences DEFAULT_PREF;
|
||||
public static SharedPreferences DEFAULT_PREF;
|
||||
public static String PREF_RENDERER = "opengles2";
|
||||
|
||||
public static boolean PREF_VERTYPE_RELEASE = true;
|
||||
public static boolean PREF_VERTYPE_SNAPSHOT = false;
|
||||
public static boolean PREF_VERTYPE_OLDALPHA = false;
|
||||
public static boolean PREF_VERTYPE_OLDBETA = false;
|
||||
public static boolean PREF_FREEFORM = false;
|
||||
public static boolean PREF_HIDE_SIDEBAR = false;
|
||||
public static boolean PREF_IGNORE_NOTCH = false;
|
||||
public static boolean PREF_BACK_TO_RIGHT_MOUSE = false;
|
||||
public static boolean PREF_BUTTON_FLAT = false;
|
||||
public static int PREF_NOTCH_SIZE = 0;
|
||||
public static float PREF_BUTTONSIZE = 100f;
|
||||
public static float PREF_MOUSESCALE = 100f;
|
||||
public static int PREF_LONGPRESS_TRIGGER = 500;
|
||||
public static int PREF_LONGPRESS_TRIGGER = 300;
|
||||
public static String PREF_DEFAULTCTRL_PATH = Tools.CTRLDEF_FILE;
|
||||
public static String PREF_CUSTOM_JAVA_ARGS;
|
||||
public static String PREF_CUSTOM_OPENGL_LIBNAME = "libgl4es_114.so";
|
||||
public static String PREF_LANGUAGE = "default";
|
||||
public static String PREF_VERSION_REPOS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
public static boolean PREF_CHECK_LIBRARY_SHA = true;
|
||||
public static boolean PREF_DISABLE_GESTURES = false;
|
||||
public static float PREF_MOUSESPEED = 1f;
|
||||
public static int PREF_RAM_ALLOCATION=300;
|
||||
public static void loadPreferences() {
|
||||
public static boolean PREF_CHECK_LIBRARY_SHA = true;
|
||||
public static boolean PREF_DISABLE_GESTURES = false;
|
||||
public static float PREF_MOUSESPEED = 1f;
|
||||
public static int PREF_RAM_ALLOCATION;
|
||||
public static String PREF_DEFAULT_RUNTIME;
|
||||
public static void loadPreferences(Context ctx) {
|
||||
//Required for the data folder.
|
||||
Tools.initContextConstants(ctx);
|
||||
|
||||
PREF_RENDERER = DEFAULT_PREF.getString("renderer", "opengles2");
|
||||
|
||||
PREF_BUTTONSIZE = DEFAULT_PREF.getInt("buttonscale", 100);
|
||||
PREF_MOUSESCALE = DEFAULT_PREF.getInt("mousescale", 100);
|
||||
PREF_MOUSESPEED = ((float)DEFAULT_PREF.getInt("mousespeed",100))/100f;
|
||||
PREF_FREEFORM = DEFAULT_PREF.getBoolean("freeform", false);
|
||||
PREF_HIDE_SIDEBAR = DEFAULT_PREF.getBoolean("hideSidebar", false);
|
||||
PREF_IGNORE_NOTCH = DEFAULT_PREF.getBoolean("ignoreNotch", false);
|
||||
PREF_BACK_TO_RIGHT_MOUSE = DEFAULT_PREF.getBoolean("backToRightMouse", false);
|
||||
PREF_BUTTON_FLAT = DEFAULT_PREF.getBoolean("flatButtonStyle", false);
|
||||
PREF_VERTYPE_RELEASE = DEFAULT_PREF.getBoolean("vertype_release", true);
|
||||
PREF_VERTYPE_SNAPSHOT = DEFAULT_PREF.getBoolean("vertype_snapshot", false);
|
||||
PREF_VERTYPE_OLDALPHA = DEFAULT_PREF.getBoolean("vertype_oldalpha", false);
|
||||
PREF_VERTYPE_OLDBETA = DEFAULT_PREF.getBoolean("vertype_oldbeta", false);
|
||||
PREF_LONGPRESS_TRIGGER = DEFAULT_PREF.getInt("timeLongPressTrigger", 500);
|
||||
PREF_LONGPRESS_TRIGGER = DEFAULT_PREF.getInt("timeLongPressTrigger", 300);
|
||||
PREF_DEFAULTCTRL_PATH = DEFAULT_PREF.getString("defaultCtrl", Tools.CTRLDEF_FILE);
|
||||
PREF_LANGUAGE = DEFAULT_PREF.getString("language", "default");
|
||||
PREF_CHECK_LIBRARY_SHA = DEFAULT_PREF.getBoolean("checkLibraries",true);
|
||||
PREF_DISABLE_GESTURES = DEFAULT_PREF.getBoolean("disableGestures",false);
|
||||
PREF_RAM_ALLOCATION = DEFAULT_PREF.getInt("allocation",300);
|
||||
// Get double of max Android heap to set default heap size
|
||||
int androidHeap = (int) (Runtime.getRuntime().maxMemory() / 1024l / 512l);
|
||||
int doubleAndroidHeap = androidHeap * 2;
|
||||
PREF_CUSTOM_JAVA_ARGS = DEFAULT_PREF.getString("javaArgs", "");
|
||||
PREF_DISABLE_GESTURES = DEFAULT_PREF.getBoolean("disableGestures",false);
|
||||
PREF_RAM_ALLOCATION = DEFAULT_PREF.getInt("allocation", findBestRAMAllocation(ctx));
|
||||
PREF_CUSTOM_JAVA_ARGS = DEFAULT_PREF.getString("javaArgs", "");
|
||||
/*
|
||||
if (PREF_CUSTOM_JAVA_ARGS.isEmpty()) {
|
||||
String DEFAULT_JAVA_ARGS =
|
||||
String DEFAULT_JAVA_ARGS = "";
|
||||
"-Xms" + (androidHeap > 800 ? 800 : androidHeap) + "m " +
|
||||
// (32bit) More than 800mb may make JVM not allocateable and crash
|
||||
"-Xmx" + (doubleAndroidHeap > 800 ? 800 : doubleAndroidHeap) + "m"; /* "m " +
|
||||
|
||||
"-Xmx" + (doubleAndroidHeap > 800 ? 800 : doubleAndroidHeap) + "m" +
|
||||
"-XX:+UseG1GC " +
|
||||
"-XX:+ParallelRefProcEnabled " +
|
||||
"-XX:MaxGCPauseMillis=200 " +
|
||||
|
|
@ -76,17 +74,51 @@ public class LauncherPreferences
|
|||
"-XX:SurvivorRatio=32 " +
|
||||
"-XX:+PerfDisableSharedMem " +
|
||||
"-XX:MaxTenuringThreshold=1";
|
||||
*/
|
||||
|
||||
PREF_CUSTOM_JAVA_ARGS = DEFAULT_JAVA_ARGS;
|
||||
DEFAULT_PREF.edit().putString("javaArgs", DEFAULT_JAVA_ARGS).commit();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
if (PREF_RENDERER.equals("2") || PREF_RENDERER.equals("3")) {
|
||||
PREF_RENDERER = "opengles" + PREF_RENDERER;
|
||||
}
|
||||
String argLwjglLibname = "-Dorg.lwjgl.opengl.libname=";
|
||||
for (String arg : PREF_CUSTOM_JAVA_ARGS.split(" ")) {
|
||||
for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) {
|
||||
if (arg.startsWith(argLwjglLibname)) {
|
||||
PREF_CUSTOM_OPENGL_LIBNAME = arg.substring(argLwjglLibname.length());
|
||||
// purge arg
|
||||
DEFAULT_PREF.edit().putString("javaArgs",
|
||||
PREF_CUSTOM_JAVA_ARGS.replace(arg, "")).commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(DEFAULT_PREF.contains("defaultRuntime")) {
|
||||
PREF_DEFAULT_RUNTIME = DEFAULT_PREF.getString("defaultRuntime","");
|
||||
}else{
|
||||
if(MultiRTUtils.getRuntimes().size() < 1) {
|
||||
PREF_DEFAULT_RUNTIME = "";
|
||||
return;
|
||||
}
|
||||
PREF_DEFAULT_RUNTIME = MultiRTUtils.getRuntimes().get(0).name;
|
||||
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultRuntime",LauncherPreferences.PREF_DEFAULT_RUNTIME).apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions aims at finding the best default RAM amount,
|
||||
* according to the RAM amount of the physical device.
|
||||
* Put not enough RAM ? Minecraft will lag and crash.
|
||||
* Put too much RAM ?
|
||||
* The GC will lag, android won't be able to breathe properly.
|
||||
* @param ctx Context needed to get the total memory of the device.
|
||||
* @return The best default value found.
|
||||
*/
|
||||
private static int findBestRAMAllocation(Context ctx){
|
||||
int deviceRam = Tools.getTotalDeviceMemory(ctx);
|
||||
if (deviceRam < 1024) return 300;
|
||||
if (deviceRam < 1536) return 450;
|
||||
if (deviceRam < 2048) return 600;
|
||||
if (deviceRam < 3064) return 936;
|
||||
if (deviceRam < 4096) return 1148;
|
||||
if (deviceRam < 6144) return 1536;
|
||||
return 2048; //Default RAM allocation for 64 bits
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter;
|
||||
import net.kdt.pojavlaunch.tasks.RefreshVersionListTask;
|
||||
import net.kdt.pojavlaunch.value.PerVersionConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PerVersionConfigDialog{
|
||||
final Context ctx;
|
||||
final AlertDialog dialog;
|
||||
final View v;
|
||||
List<MultiRTUtils.Runtime> runtimes;
|
||||
final Spinner javaVMSpinner;
|
||||
final Spinner rendererSpinner;
|
||||
final EditText customDirText;
|
||||
final EditText jvmArgsEditText;
|
||||
final List<String> renderNames;
|
||||
String selectedGameVersion = null;
|
||||
public PerVersionConfigDialog(Context _ctx) {
|
||||
ctx = _ctx;
|
||||
v = LayoutInflater.from(ctx).inflate(R.layout.pvc_popup,null);
|
||||
javaVMSpinner = v.findViewById(R.id.pvc_javaVm);
|
||||
rendererSpinner = v.findViewById(R.id.pvc_renderer);
|
||||
{
|
||||
List<String> renderList = new ArrayList<>();
|
||||
Collections.addAll(renderList, ctx.getResources().getStringArray(R.array.renderer));
|
||||
renderList.add("Default");
|
||||
renderNames = Arrays.asList(ctx.getResources().getStringArray(R.array.renderer_values));
|
||||
rendererSpinner.setAdapter(new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_dropdown_item,renderList));
|
||||
}
|
||||
customDirText = v.findViewById(R.id.pvc_customDir);
|
||||
jvmArgsEditText = v.findViewById(R.id.pvc_jvmArgs);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setView(v);
|
||||
builder.setTitle(R.string.pvc_title);
|
||||
builder.setNegativeButton(android.R.string.cancel,(dialogInterface,i)->dialogInterface.dismiss());
|
||||
builder.setPositiveButton(android.R.string.ok,this::save);
|
||||
dialog = builder.create();
|
||||
}
|
||||
public void refreshRuntimes() {
|
||||
if(runtimes!=null)runtimes.clear();
|
||||
runtimes = MultiRTUtils.getRuntimes();
|
||||
//runtimes.add(new MultiRTUtils.Runtime("<Default>"));
|
||||
}
|
||||
private void save(DialogInterface i, int which) {
|
||||
if(selectedGameVersion == null) {
|
||||
i.dismiss();
|
||||
return;
|
||||
}
|
||||
PerVersionConfig.VersionConfig conf1 = PerVersionConfig.configMap.get(selectedGameVersion);
|
||||
if(conf1==null){
|
||||
conf1=new PerVersionConfig.VersionConfig();
|
||||
}
|
||||
conf1.jvmArgs=jvmArgsEditText.getText().toString();
|
||||
conf1.gamePath=customDirText.getText().toString();
|
||||
|
||||
if(rendererSpinner.getSelectedItemPosition() == renderNames.size()) conf1.renderer = null;
|
||||
else conf1.renderer = renderNames.get(rendererSpinner.getSelectedItemPosition());
|
||||
|
||||
String runtime=((MultiRTUtils.Runtime)javaVMSpinner.getSelectedItem()).name;;
|
||||
if(!runtime.equals("<Default>"))conf1.selectedRuntime=runtime;
|
||||
else conf1.selectedRuntime=null;
|
||||
|
||||
PerVersionConfig.configMap.put(selectedGameVersion,conf1);
|
||||
try{
|
||||
PerVersionConfig.update();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public boolean openConfig(String selectedVersion) {
|
||||
selectedGameVersion = selectedVersion;
|
||||
try{
|
||||
PerVersionConfig.update();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
PerVersionConfig.VersionConfig conf=PerVersionConfig.configMap.get(selectedGameVersion);
|
||||
refreshRuntimes();
|
||||
javaVMSpinner.setAdapter(new RTSpinnerAdapter(ctx,runtimes));
|
||||
{
|
||||
int jvm_index = runtimes.indexOf(new MultiRTUtils.Runtime("<Default>"));
|
||||
int rnd_index = rendererSpinner.getAdapter().getCount()-1;
|
||||
if (conf != null) {
|
||||
customDirText.setText(conf.gamePath);
|
||||
jvmArgsEditText.setText(conf.jvmArgs);
|
||||
if (conf.selectedRuntime != null) {
|
||||
int nindex = runtimes.indexOf(new MultiRTUtils.Runtime(conf.selectedRuntime));
|
||||
if (nindex != -1) jvm_index = nindex;
|
||||
}
|
||||
if(conf.renderer != null) {
|
||||
int nindex = renderNames.indexOf(conf.renderer);
|
||||
if (nindex != -1) rnd_index = nindex;
|
||||
}
|
||||
}
|
||||
javaVMSpinner.setSelection(jvm_index);
|
||||
rendererSpinner.setSelection(rnd_index);
|
||||
}
|
||||
dialog.show();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import net.kdt.pojavlaunch.BaseLauncherActivity;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog;
|
||||
|
||||
public class RuntimeManagerPreference extends Preference{
|
||||
public RuntimeManagerPreference(Context ctx) {
|
||||
this(ctx, null);
|
||||
}
|
||||
|
||||
public RuntimeManagerPreference(Context ctx, AttributeSet attrs) {
|
||||
super(ctx, attrs);
|
||||
setPersistent(false);
|
||||
}
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
((BaseLauncherActivity)this.getContext()).mRuntimeConfigDialog.dialog.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
import android.content.*;
|
||||
import androidx.appcompat.app.*;
|
||||
import androidx.preference.*;
|
||||
import android.util.*;
|
||||
import android.widget.*;
|
||||
import java.io.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
public class UninstallJREDialogPreference extends Preference implements DialogInterface.OnClickListener
|
||||
{
|
||||
private AlertDialog mDialog;
|
||||
public UninstallJREDialogPreference(Context ctx) {
|
||||
this(ctx, null);
|
||||
}
|
||||
|
||||
public UninstallJREDialogPreference(Context ctx, AttributeSet attrs) {
|
||||
super(ctx, attrs);
|
||||
setPersistent(false);
|
||||
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||
dialog.setMessage(R.string.mcl_setting_title_uninstalljre);
|
||||
dialog.setPositiveButton(android.R.string.ok, this);
|
||||
dialog.setNegativeButton(android.R.string.cancel, this);
|
||||
mDialog = dialog.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
try {
|
||||
FileUtils.deleteDirectory(new File(Tools.DIR_HOME_JRE));
|
||||
|
||||
getContext().getSharedPreferences("pojav_extract", Context.MODE_PRIVATE)
|
||||
.edit().putBoolean(PojavLoginActivity.PREF_IS_INSTALLED_JAVARUNTIME, false).commit();
|
||||
|
||||
Toast.makeText(getContext(), R.string.toast_uninstalljre_done, Toast.LENGTH_SHORT).show();
|
||||
} catch (IOException e) {
|
||||
Tools.showError(getContext(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package net.kdt.pojavlaunch.scoped;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class GameFolderProvider extends DocumentsProvider {
|
||||
static File baseDir = new File(Tools.DIR_GAME_HOME);
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath());
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath());
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH);
|
||||
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
|
||||
row.add(Root.COLUMN_MIME_TYPES, "*/*");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir.getFreeSpace());
|
||||
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result,documentId,null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
for(File f : new File(parentDocumentId).listFiles()) {
|
||||
includeFile(result,null,f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
|
||||
return ParcelFileDescriptor.open(new File(documentId),ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
File f = new File(documentId);
|
||||
return new AssetFileDescriptor(ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY), 0, f.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
if (!new File(documentId).delete()) throw new FileNotFoundException("Can't remove "+documentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
return getMimeType(new File(documentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = new File(rootId);
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(parent);
|
||||
|
||||
final int MAX_SEARCH_RESULTS = 50;
|
||||
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||
final File file = pending.removeFirst();
|
||||
boolean isInsideGameDir;
|
||||
try {
|
||||
isInsideGameDir = file.getCanonicalPath().startsWith(baseDir.getCanonicalPath());
|
||||
} catch (IOException e) {
|
||||
isInsideGameDir = true;
|
||||
}
|
||||
if (isInsideGameDir) {
|
||||
if (file.isDirectory()) {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
private void includeFile(MatrixCursor result, String docId, File file)
|
||||
throws FileNotFoundException {
|
||||
if (docId == null) {
|
||||
docId = file.getAbsolutePath();
|
||||
} else {
|
||||
file = new File(docId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (file.isDirectory()) {
|
||||
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
|
||||
}
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getMimeType(file);
|
||||
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -2,18 +2,26 @@ package net.kdt.pojavlaunch.tasks;
|
|||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
import com.google.gson.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
|
||||
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable>
|
||||
{
|
||||
private BaseLauncherActivity mActivity;
|
||||
|
|
@ -80,6 +88,40 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
}
|
||||
|
||||
verInfo = Tools.getVersionInfo(mActivity,p1[0]);
|
||||
|
||||
//Now we have the reliable information to check if our runtime settings are good enough
|
||||
if(verInfo.javaVersion != null) { //1.17+
|
||||
PerVersionConfig.update();
|
||||
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(p1[0]);
|
||||
if(cfg == null) {
|
||||
cfg = new PerVersionConfig.VersionConfig();
|
||||
PerVersionConfig.configMap.put(p1[0],cfg);
|
||||
}
|
||||
MultiRTUtils.Runtime r = cfg.selectedRuntime != null?MultiRTUtils.read(cfg.selectedRuntime):MultiRTUtils.read(LauncherPreferences.PREF_DEFAULT_RUNTIME);
|
||||
if(r.javaVersion < verInfo.javaVersion.majorVersion) {
|
||||
String appropriateRuntime = MultiRTUtils.getNearestJREName(verInfo.javaVersion.majorVersion);
|
||||
if(appropriateRuntime != null) {
|
||||
cfg.selectedRuntime = appropriateRuntime;
|
||||
PerVersionConfig.update();
|
||||
}else{
|
||||
mActivity.runOnUiThread(()->{
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity);
|
||||
bldr.setTitle(R.string.global_error);
|
||||
bldr.setMessage(R.string.multirt_nocompartiblert);
|
||||
bldr.setPositiveButton(android.R.string.ok,(dialog, which)->{
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.show();
|
||||
});
|
||||
throw new SilentException();
|
||||
}
|
||||
} //if else, we are satisfied
|
||||
}
|
||||
{ //run the checks to detect if we have a *brand new* engine
|
||||
int mcReleaseDate = Integer.parseInt(verInfo.releaseTime.substring(0, 10).replace("-", ""));
|
||||
if(mcReleaseDate > 20210225 && verInfo.javaVersion != null && verInfo.javaVersion.majorVersion > 15)
|
||||
V117CompatUtil.runCheck(p1[0],mActivity);
|
||||
}
|
||||
try {
|
||||
assets = downloadIndex(verInfo.assets, new File(Tools.ASSETS_PATH, "indexes/" + verInfo.assets + ".json"));
|
||||
} catch (IOException e) {
|
||||
|
|
@ -94,7 +136,7 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
for (final DependentLibrary libItem : verInfo.libraries) {
|
||||
|
||||
if (
|
||||
libItem.name.startsWith("net.java.jinput") ||
|
||||
// libItem.name.startsWith("net.java.jinput") ||
|
||||
libItem.name.startsWith("org.lwjgl")
|
||||
) { // Black list
|
||||
publishProgress("1", "Ignored " + libItem.name);
|
||||
|
|
@ -201,7 +243,7 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
}
|
||||
}
|
||||
private int addProgress = 0;
|
||||
|
||||
public static class SilentException extends Exception{}
|
||||
public void zeroProgress() {
|
||||
addProgress = 0;
|
||||
}
|
||||
|
|
@ -278,8 +320,7 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
if (addedProg != -1) {
|
||||
addProgress = addProgress + addedProg;
|
||||
mActivity.mLaunchProgress.setProgress(addProgress);
|
||||
|
||||
mActivity.mLaunchTextStatus.setText(p1[1]);
|
||||
if(p1[1] != null) mActivity.mLaunchTextStatus.setText(p1[1]);
|
||||
}
|
||||
|
||||
if (p1.length < 3) {
|
||||
|
|
@ -295,7 +336,7 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
mActivity.mLaunchProgress.setMax(100);
|
||||
mActivity.mLaunchProgress.setProgress(0);
|
||||
mActivity.statusIsLaunching(false);
|
||||
if(p1 != null) {
|
||||
if(p1 != null && !(p1 instanceof SilentException)) {
|
||||
p1.printStackTrace();
|
||||
Tools.showError(mActivity, p1);
|
||||
}
|
||||
|
|
@ -303,35 +344,15 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
mActivity.mCrashView.setLastCrash("");
|
||||
|
||||
try {
|
||||
/*
|
||||
List<String> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
|
||||
jvmArgs.add("-Xms128M");
|
||||
jvmArgs.add("-Xmx1G");
|
||||
*/
|
||||
Intent mainIntent = new Intent(mActivity, MainActivity.class /* MainActivity.class */);
|
||||
// mainIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
|
||||
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
mainIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
if (LauncherPreferences.PREF_FREEFORM) {
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||
|
||||
ActivityOptions options = (ActivityOptions) ActivityOptions.class.getMethod("makeBasic").invoke(null);
|
||||
Rect freeformRect = new Rect(0, 0, dm.widthPixels / 2, dm.heightPixels / 2);
|
||||
options.getClass().getDeclaredMethod("setLaunchBounds", Rect.class).invoke(options, freeformRect);
|
||||
mActivity.startActivity(mainIntent, options.toBundle());
|
||||
} else {
|
||||
mActivity.startActivity(mainIntent);
|
||||
}
|
||||
mActivity.startActivity(mainIntent);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Tools.showError(mActivity, e);
|
||||
}
|
||||
|
||||
/*
|
||||
FloatingIntent maini = new FloatingIntent(PojavLauncherActivity.this, MainActivity.class);
|
||||
maini.startFloatingActivity();
|
||||
*/
|
||||
}
|
||||
|
||||
mActivity.mTask = null;
|
||||
|
|
@ -339,7 +360,7 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
|
||||
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
|
||||
|
||||
public JAssets downloadIndex(String versionName, File output) throws Throwable {
|
||||
public JAssets downloadIndex(String versionName, File output) throws IOException {
|
||||
if (!output.exists()) {
|
||||
output.getParentFile().mkdirs();
|
||||
DownloadUtils.downloadFile(verInfo.assetIndex != null ? verInfo.assetIndex.url : "https://s3.amazonaws.com/Minecraft.Download/indexes/" + versionName + ".json", output);
|
||||
|
|
@ -348,46 +369,97 @@ public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable
|
|||
return Tools.GLOBAL_GSON.fromJson(Tools.read(output.getAbsolutePath()), JAssets.class);
|
||||
}
|
||||
|
||||
public void downloadAsset(JAssetInfo asset, File objectsDir) throws IOException, Throwable {
|
||||
public void downloadAsset(JAssetInfo asset, File objectsDir, AtomicInteger downloadCounter) throws IOException {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(objectsDir, assetPath);
|
||||
if (!outFile.exists()) {
|
||||
DownloadUtils.downloadFile(MINECRAFT_RES + assetPath, outFile);
|
||||
}
|
||||
Tools.downloadFileMonitored(MINECRAFT_RES + assetPath, outFile.getAbsolutePath(), new Tools.DownloaderFeedback() {
|
||||
int prevCurr;
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
downloadCounter.addAndGet(curr - prevCurr);
|
||||
prevCurr = curr;
|
||||
}
|
||||
});
|
||||
}
|
||||
public void downloadAssetMapped(JAssetInfo asset, String assetName, File resDir) throws Throwable {
|
||||
public void downloadAssetMapped(JAssetInfo asset, String assetName, File resDir, AtomicInteger downloadCounter) throws IOException {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(resDir,"/"+assetName);
|
||||
if (!outFile.exists()) {
|
||||
DownloadUtils.downloadFile(MINECRAFT_RES + assetPath, outFile);
|
||||
}
|
||||
Tools.downloadFileMonitored(MINECRAFT_RES + assetPath, outFile.getAbsolutePath(), new Tools.DownloaderFeedback() {
|
||||
int prevCurr;
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
downloadCounter.addAndGet(curr - prevCurr);
|
||||
prevCurr = curr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void downloadAssets(JAssets assets, String assetsVersion, File outputDir) throws IOException, Throwable {
|
||||
public void downloadAssets(final JAssets assets, String assetsVersion, final File outputDir) throws IOException {
|
||||
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
|
||||
final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 500, TimeUnit.MILLISECONDS, workQueue);
|
||||
mActivity.mIsAssetsProcessing = true;
|
||||
File hasDownloadedFile = new File(outputDir, "downloaded/" + assetsVersion + ".downloaded");
|
||||
if (!hasDownloadedFile.exists()) {
|
||||
System.out.println("Assets begin time: " + System.currentTimeMillis());
|
||||
Map<String, JAssetInfo> assetsObjects = assets.objects;
|
||||
mActivity.mLaunchProgress.setMax(assetsObjects.size());
|
||||
zeroProgress();
|
||||
int assetsSizeBytes=0;
|
||||
AtomicInteger downloadedSize = new AtomicInteger(0);
|
||||
AtomicBoolean localInterrupt = new AtomicBoolean(false);
|
||||
File objectsDir = new File(outputDir, "objects");
|
||||
int downloadedSs = 0;
|
||||
for (JAssetInfo asset : assetsObjects.values()) {
|
||||
if (!mActivity.mIsAssetsProcessing) {
|
||||
return;
|
||||
zeroProgress();
|
||||
for(String assetKey : assetsObjects.keySet()) {
|
||||
if(!mActivity.mIsAssetsProcessing) break;
|
||||
JAssetInfo asset = assetsObjects.get(assetKey);
|
||||
assetsSizeBytes+=asset.size;
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = assets.map_to_resources?new File(objectsDir,"/"+assetKey):new File(objectsDir, assetPath);
|
||||
boolean skip = outFile.exists();// skip if the file exists
|
||||
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA) //if sha checking is enabled
|
||||
if(skip) skip = Tools.compareSHA1(outFile, asset.hash); //check hash
|
||||
if(skip) {
|
||||
downloadedSize.addAndGet(asset.size);
|
||||
}else{
|
||||
if(outFile.exists()) publishProgress("0",mActivity.getString(R.string.dl_library_sha_fail,assetKey));
|
||||
executor.execute(()->{
|
||||
try {
|
||||
if (!assets.map_to_resources) {
|
||||
downloadAsset(asset, objectsDir, downloadedSize);
|
||||
} else {
|
||||
downloadAssetMapped(asset, assetKey, outputDir, downloadedSize);
|
||||
}
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
localInterrupt.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!assets.map_to_resources) downloadAsset(asset, objectsDir);
|
||||
else downloadAssetMapped(asset,(assetsObjects.keySet().toArray(new String[0])[downloadedSs]),outputDir);
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, assetsObjects.keySet().toArray(new String[0])[downloadedSs]));
|
||||
downloadedSs++;
|
||||
}
|
||||
hasDownloadedFile.getParentFile().mkdirs();
|
||||
hasDownloadedFile.createNewFile();
|
||||
mActivity.mLaunchProgress.setMax(assetsSizeBytes);
|
||||
executor.shutdown();
|
||||
try {
|
||||
int prevDLSize=0;
|
||||
System.out.println("Queue size: "+workQueue.size());
|
||||
while ((!executor.awaitTermination(250, TimeUnit.MILLISECONDS))&&(!localInterrupt.get())&&mActivity.mIsAssetsProcessing) {
|
||||
int DLSize = downloadedSize.get();
|
||||
publishProgress(Integer.toString(DLSize-prevDLSize),null,"");
|
||||
publishDownloadProgress("assets", DLSize, assetsSizeBytes);
|
||||
prevDLSize = downloadedSize.get();
|
||||
}
|
||||
if(mActivity.mIsAssetsProcessing) {
|
||||
System.out.println("Unskipped download done!");
|
||||
if(!hasDownloadedFile.getParentFile().exists())hasDownloadedFile.getParentFile().mkdirs();
|
||||
hasDownloadedFile.createNewFile();
|
||||
}else{
|
||||
System.out.println("Skipped!");
|
||||
}
|
||||
executor.shutdownNow();
|
||||
while (!executor.awaitTermination(250, TimeUnit.MILLISECONDS)) {}
|
||||
System.out.println("Fully shut down!");
|
||||
}catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("Assets end time: " + System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private JMinecraftVersionList.Version findVersion(String version) {
|
||||
if (mActivity.mVersionList != null) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import android.widget.AdapterView.*;
|
|||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.kdt.pojavlaunch.value.PerVersionConfig;
|
||||
|
|
@ -69,53 +71,9 @@ public class RefreshVersionListTask extends AsyncTask<Void, Void, ArrayList<Stri
|
|||
} else {
|
||||
mActivity.mVersionSelector.setSelection(selectAt(mActivity.mAvailableVersions, mActivity.mProfile.selectedVersion));
|
||||
}
|
||||
View.OnLongClickListener listener = new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
||||
final View v = LayoutInflater.from(view.getContext()).inflate(R.layout.pvc_popup,null);
|
||||
try {
|
||||
PerVersionConfig.update();
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
PerVersionConfig.VersionConfig conf = PerVersionConfig.configMap.get(mActivity.mProfile.selectedVersion);
|
||||
if(conf != null) {
|
||||
((EditText)v.findViewById(R.id.pvc_customDir)).setText(conf.gamePath);
|
||||
((EditText)v.findViewById(R.id.pvc_jvmArgs)).setText(conf.jvmArgs);
|
||||
}
|
||||
builder.setView(v);
|
||||
builder.setTitle("Per-version settings");
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.dismiss();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
PerVersionConfig.VersionConfig conf = PerVersionConfig.configMap.get(mActivity.mProfile.selectedVersion);
|
||||
if(conf == null) {
|
||||
conf = new PerVersionConfig.VersionConfig();
|
||||
}
|
||||
conf.jvmArgs = ((EditText)v.findViewById(R.id.pvc_jvmArgs)).getText().toString();
|
||||
conf.gamePath = ((EditText)v.findViewById(R.id.pvc_customDir)).getText().toString();
|
||||
PerVersionConfig.configMap.put(mActivity.mProfile.selectedVersion,conf);
|
||||
try {
|
||||
PerVersionConfig.update();
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
mActivity.mVersionSelector.setOnLongClickListener(listener);
|
||||
PerVersionConfigDialog dialog = new PerVersionConfigDialog(this.mActivity);
|
||||
mActivity.mVersionSelector.setOnLongClickListener((v)->dialog.openConfig(mActivity.mProfile.selectedVersion));
|
||||
mActivity.mVersionSelector.setOnItemSelectedListener(new OnItemSelectedListener(){
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> p1, View p2, int p3, long p4)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import static net.kdt.pojavlaunch.Architecture.ARCH_X86;
|
||||
import static net.kdt.pojavlaunch.Architecture.archAsString;
|
||||
import static net.kdt.pojavlaunch.Architecture.is64BitsDevice;
|
||||
import static net.kdt.pojavlaunch.Tools.LOCAL_RENDERER;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLExt;
|
||||
import android.opengl.GLES10;
|
||||
import android.os.Build;
|
||||
import android.system.*;
|
||||
import android.util.*;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.oracle.dalvik.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
|
@ -23,30 +24,39 @@ import javax.microedition.khronos.egl.EGLConfig;
|
|||
import javax.microedition.khronos.egl.EGLContext;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
|
||||
public class JREUtils
|
||||
{
|
||||
public class JREUtils {
|
||||
private JREUtils() {}
|
||||
|
||||
public static String JRE_ARCHITECTURE;
|
||||
|
||||
|
||||
public static String LD_LIBRARY_PATH;
|
||||
private static String nativeLibDir;
|
||||
|
||||
public static void checkJavaArchitecture(LoggableActivity act, String jreArch) throws Exception {
|
||||
String[] argName = Tools.CURRENT_ARCHITECTURE.split("/");
|
||||
act.appendlnToLog("Architecture: " + Tools.CURRENT_ARCHITECTURE);
|
||||
if (!(jreArch.contains(argName[0]) || jreArch.contains(argName[1]))) {
|
||||
// x86 check workaround
|
||||
if (jreArch.startsWith("i") && jreArch.endsWith("86") && Tools.CURRENT_ARCHITECTURE.contains("x86") && !Tools.CURRENT_ARCHITECTURE.contains("64")) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Checks if the java architecture is correct for the device architecture.
|
||||
* @param act An Activity with logging capabilities
|
||||
* @param jreArch The java architecture to compare as a String.
|
||||
*/
|
||||
public static void checkJavaArchitecture(LoggableActivity act, String jreArch) {
|
||||
act.appendlnToLog("Architecture: " + archAsString(Tools.DEVICE_ARCHITECTURE));
|
||||
if(Tools.DEVICE_ARCHITECTURE == Architecture.archAsInt(jreArch)) return;
|
||||
|
||||
act.appendlnToLog("Architecture " + archAsString(Tools.DEVICE_ARCHITECTURE) + " is incompatible with Java Runtime " + jreArch);
|
||||
throw new RuntimeException(act.getString(R.string.mcn_check_fail_incompatiblearch, archAsString(Tools.DEVICE_ARCHITECTURE), jreArch));
|
||||
|
||||
act.appendlnToLog("Architecture " + Tools.CURRENT_ARCHITECTURE + " is incompatible with Java Runtime " + jreArch);
|
||||
throw new RuntimeException(act.getString(R.string.mcn_check_fail_incompatiblearch, Tools.CURRENT_ARCHITECTURE, jreArch));
|
||||
}
|
||||
}
|
||||
|
||||
public static String findInLdLibPath(String libName) {
|
||||
if(Os.getenv("LD_LIBRARY_PATH")==null) {
|
||||
try {
|
||||
if (LD_LIBRARY_PATH != null) {
|
||||
Os.setenv("LD_LIBRARY_PATH", LD_LIBRARY_PATH, true);
|
||||
}else{
|
||||
return libName;
|
||||
}
|
||||
}catch (ErrnoException e) {
|
||||
e.printStackTrace();
|
||||
return libName;
|
||||
}
|
||||
}
|
||||
for (String libPath : Os.getenv("LD_LIBRARY_PATH").split(":")) {
|
||||
File f = new File(libPath, libName);
|
||||
if (f.exists() && f.isFile()) {
|
||||
|
|
@ -68,9 +78,11 @@ public class JREUtils
|
|||
return ret;
|
||||
}
|
||||
public static void initJavaRuntime() {
|
||||
|
||||
dlopen(findInLdLibPath("libjli.so"));
|
||||
dlopen(findInLdLibPath("libjvm.so"));
|
||||
if(!dlopen("libjvm.so")){
|
||||
Log.w("DynamicLoader","Failed to load with no path, trying with full path");
|
||||
dlopen(jvmLibraryPath+"/libjvm.so");
|
||||
}
|
||||
dlopen(findInLdLibPath("libverify.so"));
|
||||
dlopen(findInLdLibPath("libjava.so"));
|
||||
// dlopen(findInLdLibPath("libjsig.so"));
|
||||
|
|
@ -84,14 +96,7 @@ public class JREUtils
|
|||
dlopen(f.getAbsolutePath());
|
||||
}
|
||||
dlopen(nativeLibDir + "/libopenal.so");
|
||||
|
||||
if (LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME.equals("libgl4es_114.so")) {
|
||||
LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME = nativeLibDir + "/libgl4es_114.so";
|
||||
}
|
||||
if (!dlopen(LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME) && !dlopen(findInLdLibPath(LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME))) {
|
||||
System.err.println("Failed to load custom OpenGL library " + LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME + ". Fallbacking to GL4ES.");
|
||||
dlopen(nativeLibDir + "/libgl4es_114.so");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Map<String, String> readJREReleaseProperties() throws IOException {
|
||||
|
|
@ -107,9 +112,8 @@ public class JREUtils
|
|||
jreReleaseReader.close();
|
||||
return jreReleaseMap;
|
||||
}
|
||||
|
||||
private static boolean checkAccessTokenLeak = true;
|
||||
public static void redirectAndPrintJRELog(final LoggableActivity act, final String accessToken) {
|
||||
public static String jvmLibraryPath;
|
||||
public static void redirectAndPrintJRELog(final LoggableActivity act) {
|
||||
Log.v("jrelog","Log starts here");
|
||||
JREUtils.logToActivity(act);
|
||||
Thread t = new Thread(new Runnable(){
|
||||
|
|
@ -141,16 +145,6 @@ public class JREUtils
|
|||
int len;
|
||||
while ((len = p.getInputStream().read(buf)) != -1) {
|
||||
String currStr = new String(buf, 0, len);
|
||||
|
||||
// Avoid leaking access token to log by replace it.
|
||||
// Also, Minecraft will just print it once.
|
||||
if (checkAccessTokenLeak) {
|
||||
if (accessToken != null && accessToken.length() > 5 && currStr.contains(accessToken)) {
|
||||
checkAccessTokenLeak = false;
|
||||
currStr = currStr.replace(accessToken, "ACCESS_TOKEN_HIDDEN");
|
||||
}
|
||||
}
|
||||
|
||||
act.appendToLog(currStr);
|
||||
}
|
||||
|
||||
|
|
@ -175,13 +169,10 @@ public class JREUtils
|
|||
Log.i("jrelog-logcat","Logcat thread started");
|
||||
}
|
||||
|
||||
public static void relocateLibPath(Context ctx) throws Exception {
|
||||
if (JRE_ARCHITECTURE == null) {
|
||||
Map<String, String> jreReleaseList = JREUtils.readJREReleaseProperties();
|
||||
JRE_ARCHITECTURE = jreReleaseList.get("OS_ARCH");
|
||||
if (JRE_ARCHITECTURE.startsWith("i") && JRE_ARCHITECTURE.endsWith("86") && Tools.CURRENT_ARCHITECTURE.contains("x86") && !Tools.CURRENT_ARCHITECTURE.contains("64")) {
|
||||
JRE_ARCHITECTURE = "i386/i486/i586";
|
||||
}
|
||||
public static void relocateLibPath(final Context ctx) throws IOException {
|
||||
String JRE_ARCHITECTURE = readJREReleaseProperties().get("OS_ARCH");
|
||||
if (Architecture.archAsInt(JRE_ARCHITECTURE) == ARCH_X86){
|
||||
JRE_ARCHITECTURE = "i386/i486/i586";
|
||||
}
|
||||
|
||||
nativeLibDir = ctx.getApplicationInfo().nativeLibraryDir;
|
||||
|
|
@ -193,12 +184,8 @@ public class JREUtils
|
|||
}
|
||||
}
|
||||
|
||||
String libName = Tools.CURRENT_ARCHITECTURE.contains("64") ? "lib64" : "lib";
|
||||
String libName = is64BitsDevice() ? "lib64" : "lib";
|
||||
StringBuilder ldLibraryPath = new StringBuilder();
|
||||
File serverFile = new File(Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/server/libjvm.so");
|
||||
// To make libjli.so ignore re-execute
|
||||
ldLibraryPath.append(
|
||||
Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/" + (serverFile.exists() ? "server" : "client") + ":");
|
||||
ldLibraryPath.append(
|
||||
Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/jli:" +
|
||||
Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + ":"
|
||||
|
|
@ -207,14 +194,12 @@ public class JREUtils
|
|||
"/system/" + libName + ":" +
|
||||
"/vendor/" + libName + ":" +
|
||||
"/vendor/" + libName + "/hw:" +
|
||||
|
||||
nativeLibDir
|
||||
);
|
||||
|
||||
LD_LIBRARY_PATH = ldLibraryPath.toString();
|
||||
}
|
||||
|
||||
public static void setJavaEnvironment(LoggableActivity ctx, @Nullable ShellProcessOperation shell) throws Throwable {
|
||||
public static void setJavaEnvironment(LoggableActivity ctx) throws Throwable {
|
||||
Map<String, String> envMap = new ArrayMap<>();
|
||||
envMap.put("JAVA_HOME", Tools.DIR_HOME_JRE);
|
||||
envMap.put("HOME", Tools.DIR_GAME_NEW);
|
||||
|
|
@ -225,13 +210,22 @@ public class JREUtils
|
|||
envMap.put("LIBGL_NORMALIZE", "1");
|
||||
|
||||
envMap.put("MESA_GLSL_CACHE_DIR", ctx.getCacheDir().getAbsolutePath());
|
||||
envMap.put("MESA_GL_VERSION_OVERRIDE", "4.6");
|
||||
envMap.put("MESA_GLSL_VERSION_OVERRIDE", "460");
|
||||
envMap.put("force_glsl_extensions_warn", "true");
|
||||
envMap.put("allow_higher_compat_version", "true");
|
||||
envMap.put("allow_glsl_extension_directive_midshader", "true");
|
||||
envMap.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||
|
||||
envMap.put("LD_LIBRARY_PATH", LD_LIBRARY_PATH);
|
||||
envMap.put("PATH", Tools.DIR_HOME_JRE + "/bin:" + Os.getenv("PATH"));
|
||||
|
||||
envMap.put("REGAL_GL_VENDOR", "Android");
|
||||
envMap.put("REGAL_GL_RENDERER", "Regal");
|
||||
envMap.put("REGAL_GL_VERSION", "4.5");
|
||||
|
||||
if(LOCAL_RENDERER != null) {
|
||||
envMap.put("POJAV_RENDERER", LOCAL_RENDERER);
|
||||
}
|
||||
envMap.put("AWTSTUB_WIDTH", Integer.toString(CallbackBridge.windowWidth > 0 ? CallbackBridge.windowWidth : CallbackBridge.physicalWidth));
|
||||
envMap.put("AWTSTUB_HEIGHT", Integer.toString(CallbackBridge.windowHeight > 0 ? CallbackBridge.windowHeight : CallbackBridge.physicalHeight));
|
||||
|
||||
|
|
@ -246,14 +240,15 @@ public class JREUtils
|
|||
}
|
||||
reader.close();
|
||||
}
|
||||
if(!envMap.containsKey("LIBGL_ES")) {
|
||||
if(!envMap.containsKey("LIBGL_ES") && LOCAL_RENDERER != null) {
|
||||
int glesMajor = getDetectedVersion();
|
||||
Log.i("glesDetect","GLES version detected: "+glesMajor);
|
||||
|
||||
if (glesMajor < 3) {
|
||||
//fallback to 2 since it's the minimum for the entire app
|
||||
envMap.put("LIBGL_ES","2");
|
||||
} else if (LauncherPreferences.PREF_RENDERER.startsWith("opengles")) {
|
||||
envMap.put("LIBGL_ES", LauncherPreferences.PREF_RENDERER.replace("opengles", ""));
|
||||
} else if (LOCAL_RENDERER.startsWith("opengles")) {
|
||||
envMap.put("LIBGL_ES", LOCAL_RENDERER.replace("opengles", "").replace("_5", ""));
|
||||
} else {
|
||||
// TODO if can: other backends such as Vulkan.
|
||||
// Sure, they should provide GLES 3 support.
|
||||
|
|
@ -261,48 +256,21 @@ public class JREUtils
|
|||
}
|
||||
}
|
||||
for (Map.Entry<String, String> env : envMap.entrySet()) {
|
||||
try {
|
||||
if (shell == null) {
|
||||
Os.setenv(env.getKey(), env.getValue(), true);
|
||||
} else {
|
||||
shell.writeToProcess("export " + env.getKey() + "=" + env.getValue());
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
ctx.appendlnToLog(Log.getStackTraceString(th));
|
||||
}
|
||||
ctx.appendlnToLog("Added custom env: " + env.getKey() + "=" + env.getValue());
|
||||
Os.setenv(env.getKey(), env.getValue(), true);
|
||||
}
|
||||
|
||||
if (shell == null) {
|
||||
setLdLibraryPath(LD_LIBRARY_PATH);
|
||||
}
|
||||
|
||||
|
||||
File serverFile = new File(Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/server/libjvm.so");
|
||||
jvmLibraryPath = Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/" + (serverFile.exists() ? "server" : "client");
|
||||
Log.d("DynamicLoader","Base LD_LIBRARY_PATH: "+LD_LIBRARY_PATH);
|
||||
Log.d("DynamicLoader","Internal LD_LIBRARY_PATH: "+jvmLibraryPath+":"+LD_LIBRARY_PATH);
|
||||
setLdLibraryPath(jvmLibraryPath+":"+LD_LIBRARY_PATH);
|
||||
|
||||
// return ldLibraryPath;
|
||||
}
|
||||
|
||||
public static int launchJavaVM(final LoggableActivity ctx, final List<String> args) throws Throwable {
|
||||
public static int launchJavaVM(final LoggableActivity ctx,final List<String> JVMArgs) throws Throwable {
|
||||
JREUtils.relocateLibPath(ctx);
|
||||
// ctx.appendlnToLog("LD_LIBRARY_PATH = " + JREUtils.LD_LIBRARY_PATH);
|
||||
|
||||
List<String> javaArgList = new ArrayList<String>();
|
||||
javaArgList.add(Tools.DIR_HOME_JRE + "/bin/java");
|
||||
Tools.getJavaArgs(ctx, javaArgList);
|
||||
purgeArg(javaArgList,"-Xms");
|
||||
purgeArg(javaArgList,"-Xmx");
|
||||
/*if(Tools.CURRENT_ARCHITECTURE.contains("32") && ((mi.availMem / 1048576L)-50) > 300) {
|
||||
javaArgList.add("-Xms300M");
|
||||
javaArgList.add("-Xmx300M");
|
||||
}else {*/
|
||||
javaArgList.add("-Xms" + LauncherPreferences.PREF_RAM_ALLOCATION + "M");
|
||||
javaArgList.add("-Xmx" + LauncherPreferences.PREF_RAM_ALLOCATION + "M");
|
||||
//}
|
||||
ctx.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Toast.makeText(ctx, ctx.getString(R.string.autoram_info_msg,LauncherPreferences.PREF_RAM_ALLOCATION), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
System.out.println(javaArgList);
|
||||
javaArgList.addAll(args);
|
||||
|
||||
// For debugging only!
|
||||
/*
|
||||
StringBuilder sbJavaArgs = new StringBuilder();
|
||||
|
|
@ -312,31 +280,160 @@ public class JREUtils
|
|||
ctx.appendlnToLog("Executing JVM: \"" + sbJavaArgs.toString() + "\"");
|
||||
*/
|
||||
|
||||
setJavaEnvironment(ctx, null);
|
||||
setJavaEnvironment(ctx);
|
||||
|
||||
final String graphicsLib = loadGraphicsLibrary();
|
||||
List<String> userArgs = getJavaArgs(ctx);
|
||||
|
||||
//Remove arguments that can interfere with the good working of the launcher
|
||||
purgeArg(userArgs,"-Xms");
|
||||
purgeArg(userArgs,"-Xmx");
|
||||
purgeArg(userArgs,"-d32");
|
||||
purgeArg(userArgs,"-d64");
|
||||
purgeArg(userArgs, "-Dorg.lwjgl.opengl.libname");
|
||||
|
||||
//Add automatically generated args
|
||||
userArgs.add("-Xms" + LauncherPreferences.PREF_RAM_ALLOCATION + "M");
|
||||
userArgs.add("-Xmx" + LauncherPreferences.PREF_RAM_ALLOCATION + "M");
|
||||
if(LOCAL_RENDERER != null) userArgs.add("-Dorg.lwjgl.opengl.libname=" + graphicsLib);
|
||||
|
||||
userArgs.addAll(JVMArgs);
|
||||
ctx.runOnUiThread(() -> Toast.makeText(ctx, ctx.getString(R.string.autoram_info_msg,LauncherPreferences.PREF_RAM_ALLOCATION), Toast.LENGTH_SHORT).show());
|
||||
System.out.println(JVMArgs);
|
||||
|
||||
initJavaRuntime();
|
||||
setupExitTrap(ctx.getApplication());
|
||||
chdir(Tools.DIR_GAME_NEW);
|
||||
|
||||
final int exitCode = VMLauncher.launchJVM(javaArgList.toArray(new String[0]));
|
||||
final int exitCode = VMLauncher.launchJVM(userArgs.toArray(new String[0]));
|
||||
ctx.appendlnToLog("Java Exit code: " + exitCode);
|
||||
if (exitCode != 0) {
|
||||
ctx.runOnUiThread(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
|
||||
dialog.setMessage(ctx.getString(R.string.mcn_exit_title, exitCode));
|
||||
dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
|
||||
ctx.runOnUiThread(() -> {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
|
||||
dialog.setMessage(ctx.getString(R.string.mcn_exit_title, exitCode));
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2){
|
||||
BaseMainActivity.fullyExit();
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
dialog.setPositiveButton(android.R.string.ok, (p1, p2) -> BaseMainActivity.fullyExit());
|
||||
dialog.show();
|
||||
});
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives an argument list filled with both the user args
|
||||
* and the auto-generated ones (eg. the window resolution).
|
||||
* @param ctx The application context
|
||||
* @return A list filled with args.
|
||||
*/
|
||||
public static List<String> getJavaArgs(Context ctx) {
|
||||
List<String> userArguments = parseJavaArguments(LauncherPreferences.PREF_CUSTOM_JAVA_ARGS);
|
||||
String[] overridableArguments = new String[]{
|
||||
"-Djava.home=" + Tools.DIR_HOME_JRE,
|
||||
"-Djava.io.tmpdir=" + ctx.getCacheDir().getAbsolutePath(),
|
||||
"-Duser.home=" + new File(Tools.DIR_GAME_NEW).getParent(),
|
||||
"-Duser.language=" + System.getProperty("user.language"),
|
||||
"-Dos.name=Linux",
|
||||
"-Dos.version=Android-" + Build.VERSION.RELEASE,
|
||||
"-Dpojav.path.minecraft=" + Tools.DIR_GAME_NEW,
|
||||
"-Dpojav.path.private.account=" + Tools.DIR_ACCOUNT_NEW,
|
||||
|
||||
//LWJGL 3 DEBUG FLAGS
|
||||
//"-Dorg.lwjgl.util.Debug=true",
|
||||
//"-Dorg.lwjgl.util.DebugFunctions=true",
|
||||
//"-Dorg.lwjgl.util.DebugLoader=true",
|
||||
// GLFW Stub width height
|
||||
"-Dglfwstub.windowWidth=" + CallbackBridge.windowWidth,
|
||||
"-Dglfwstub.windowHeight=" + CallbackBridge.windowHeight,
|
||||
"-Dglfwstub.initEgl=false",
|
||||
|
||||
"-Dnet.minecraft.clientmodname=" + Tools.APP_NAME,
|
||||
"-Dfml.earlyprogresswindow=false" //Forge 1.14+ workaround
|
||||
};
|
||||
|
||||
|
||||
for (String userArgument : userArguments) {
|
||||
for(int i=0; i < overridableArguments.length; ++i){
|
||||
String overridableArgument = overridableArguments[i];
|
||||
//Only java properties are considered overridable for now
|
||||
if(userArgument.startsWith("-D") && userArgument.startsWith(overridableArgument.substring(0, overridableArgument.indexOf("=")))){
|
||||
overridableArguments[i] = ""; //Remove the argument since it is overridden
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Add all the arguments
|
||||
userArguments.addAll(Arrays.asList(overridableArguments));
|
||||
return userArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and separate java arguments in a user friendly fashion
|
||||
* It supports multi line and absence of spaces between arguments
|
||||
* The function also supports auto-removal of improper arguments, although it may miss some.
|
||||
*
|
||||
* @param args The un-parsed argument list.
|
||||
* @return Parsed args as an ArrayList
|
||||
*/
|
||||
public static ArrayList<String> parseJavaArguments(String args){
|
||||
ArrayList<String> parsedArguments = new ArrayList<>(0);
|
||||
args = args.trim().replace(" ", "");
|
||||
//For each prefixes, we separate args.
|
||||
for(String prefix : new String[]{"-XX:-","-XX:+", "-XX:","-"}){
|
||||
while (true){
|
||||
int start = args.indexOf(prefix);
|
||||
if(start == -1) break;
|
||||
//Get the end of the current argument
|
||||
int end = args.indexOf("-", start + prefix.length());
|
||||
if(end == -1) end = args.length();
|
||||
//Extract it
|
||||
String parsedSubString = args.substring(start, end);
|
||||
args = args.replace(parsedSubString, "");
|
||||
|
||||
//Check if two args aren't bundled together by mistake
|
||||
if(parsedSubString.indexOf('=') == parsedSubString.lastIndexOf('='))
|
||||
parsedArguments.add(parsedSubString);
|
||||
else Log.w("JAVA ARGS PARSER", "Removed improper arguments: " + parsedSubString);
|
||||
}
|
||||
}
|
||||
return parsedArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the render library in accordance to the settings.
|
||||
* It will fallback if it fails to load the library.
|
||||
* @return The name of the loaded library
|
||||
*/
|
||||
public static String loadGraphicsLibrary(){
|
||||
if(LOCAL_RENDERER == null) return null;
|
||||
String renderLibrary;
|
||||
switch (LOCAL_RENDERER){
|
||||
case "opengles2": renderLibrary = "libgl4es_114.so"; break;
|
||||
case "opengles2_5": renderLibrary = "libgl4es_115.so"; break;
|
||||
case "opengles3": renderLibrary = "libgl4es_115.so"; break;
|
||||
case "vulkan_zink": renderLibrary = "libOSMesa_8.so"; break;
|
||||
case "opengles3_vgpu" : renderLibrary = "libvgpu.so"; break;
|
||||
default:
|
||||
Log.w("RENDER_LIBRARY", "No renderer selected, defaulting to opengles2");
|
||||
renderLibrary = "libgl4es_114.so";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dlopen(renderLibrary) && !dlopen(findInLdLibPath(renderLibrary))) {
|
||||
Log.e("RENDER_LIBRARY","Failed to load renderer " + renderLibrary + ". Falling back to GL4ES 1.1.4");
|
||||
LOCAL_RENDERER = "opengles2";
|
||||
renderLibrary = "libgl4es_114.so";
|
||||
dlopen(nativeLibDir + "/libgl4es_114.so");
|
||||
}
|
||||
return renderLibrary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the argument from the list, if it exists
|
||||
* If the argument exists multiple times, they will all be removed.
|
||||
* @param argList The argument list to purge
|
||||
* @param argStart The argument to purge from the list.
|
||||
*/
|
||||
private static void purgeArg(List<String> argList, String argStart) {
|
||||
for(int i = 0; i < argList.size(); i++) {
|
||||
final String arg = argList.get(i);
|
||||
|
|
@ -422,13 +519,14 @@ public class JREUtils
|
|||
public static native boolean dlopen(String libPath);
|
||||
public static native void setLdLibraryPath(String ldLibraryPath);
|
||||
public static native void setupBridgeWindow(Object surface);
|
||||
|
||||
public static native void setupExitTrap(Context context);
|
||||
// Obtain AWT screen pixels to render on Android SurfaceView
|
||||
public static native int[] renderAWTScreenFrame(/* Object canvas, int width, int height */);
|
||||
|
||||
static {
|
||||
System.loadLibrary("pojavexec");
|
||||
System.loadLibrary("pojavexec_awt");
|
||||
dlopen("libxhook.so");
|
||||
System.loadLibrary("istdio");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class LocaleUtils {
|
|||
public static Context setLocale(Context context) {
|
||||
if (LauncherPreferences.DEFAULT_PREF == null) {
|
||||
LauncherPreferences.DEFAULT_PREF = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
LauncherPreferences.loadPreferences();
|
||||
LauncherPreferences.loadPreferences(context);
|
||||
}
|
||||
|
||||
Locale locale;
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import android.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class MCOptionUtils
|
||||
{
|
||||
private static List<String> mLineList;
|
||||
private static final HashMap<String,String> parameterMap = new HashMap<>();
|
||||
|
||||
public static void load() {
|
||||
if (mLineList == null) {
|
||||
mLineList = new ArrayList<String>();
|
||||
} else {
|
||||
mLineList.clear();
|
||||
}
|
||||
|
||||
parameterMap.clear();
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_NEW + "/options.txt"));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
mLineList.add(line);
|
||||
int firstColonIndex = line.indexOf(':');
|
||||
if(firstColonIndex < 0) {
|
||||
Log.w(Tools.APP_NAME, "No colon on line \""+line+"\", skipping");
|
||||
continue;
|
||||
}
|
||||
parameterMap.put(line.substring(0,firstColonIndex), line.substring(firstColonIndex+1));
|
||||
}
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
|
|
@ -29,39 +29,20 @@ public class MCOptionUtils
|
|||
}
|
||||
|
||||
public static void set(String key, String value) {
|
||||
for (int i = 0; i < mLineList.size(); i++) {
|
||||
String line = mLineList.get(i);
|
||||
if (line.startsWith(key + ":")) {
|
||||
mLineList.set(i, key + ":" + value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mLineList.add(key + ":" + value);
|
||||
parameterMap.put(key,value);
|
||||
}
|
||||
|
||||
public static String get(String key){
|
||||
if (mLineList == null){
|
||||
load();
|
||||
}
|
||||
for (int i = 0; i < mLineList.size(); i++) {
|
||||
String line = mLineList.get(i);
|
||||
if (line.startsWith(key + ":")) {
|
||||
String value = mLineList.get(i);
|
||||
return value.substring(value.indexOf(":")+1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return parameterMap.get(key);
|
||||
}
|
||||
|
||||
public static void save() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < mLineList.size(); i++) {
|
||||
result.append(mLineList.get(i));
|
||||
if (i + 1 < mLineList.size()) {
|
||||
result.append("\n");
|
||||
}
|
||||
}
|
||||
for(String key : parameterMap.keySet())
|
||||
result.append(key)
|
||||
.append(':')
|
||||
.append(parameterMap.get(key))
|
||||
.append('\n');
|
||||
|
||||
try {
|
||||
Tools.write(Tools.DIR_GAME_NEW + "/options.txt", result.toString());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
public class MathUtils {
|
||||
|
||||
//Ported from https://www.arduino.cc/reference/en/language/functions/math/map/
|
||||
public static float map(float x, float in_min, float in_max, float out_min, float out_max) {
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.tasks.MinecraftDownloaderTask;
|
||||
import net.kdt.pojavlaunch.value.PerVersionConfig;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class V117CompatUtil {
|
||||
/*
|
||||
/*
|
||||
New rendering engine was added in snapshot 21w10a
|
||||
21a08b (FP (GL2) engine): 20210225
|
||||
21w10a (non-FP engine): 20210310
|
||||
|
||||
boolean skipResDialog = false;
|
||||
if(mcReleaseDate > 20210225) skipResDialog = true;
|
||||
PerVersionConfig.update(); //Prepare the PVC
|
||||
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(p1[0]); //Get the version config...
|
||||
if (cfg == null) {
|
||||
cfg = new PerVersionConfig.VersionConfig();//or create a new one!
|
||||
PerVersionConfig.configMap.put(p1[0], cfg);//and put it into the base
|
||||
}
|
||||
MCOptionUtils.load();
|
||||
|
||||
if(!skipResDialog) {
|
||||
AtomicBoolean proceed = new AtomicBoolean(false);
|
||||
Object lock = new Object();
|
||||
mActivity.runOnUiThread(() -> {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity);
|
||||
bldr.setTitle(R.string.global_warinng);
|
||||
bldr.setMessage(R.string.compat_117_message);
|
||||
bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
proceed.set(true);
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setCancelable(false);
|
||||
bldr.show();
|
||||
});
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
if(proceed.get()) {
|
||||
File resourcepacksDir = new File(cfg.gamePath==null? Tools.DIR_GAME_NEW:cfg.gamePath,"resourcepacks");
|
||||
if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(new File(resourcepacksDir,"assets-v0.zip"));
|
||||
InputStream is = this.mActivity.getAssets().open("assets-v0.zip");
|
||||
IOUtils.copy(is,fos);
|
||||
is.close();fos.close();
|
||||
String resourcepacks = MCOptionUtils.get("resourcePacks");
|
||||
if(resourcepacks == null || !resourcepacks.contains("assets-v0.zip")) {
|
||||
List<String> resPacksArray = resourcepacks == null ? new ArrayList(): Arrays.asList()
|
||||
}
|
||||
}else throw new MinecraftDownloaderTask.SilentException();
|
||||
}
|
||||
*/
|
||||
private static List<String> getTexturePackList(String param) {
|
||||
if (param == null) {
|
||||
Log.i("V117CompatDebug","null, defaulting to empty");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Log.i("V117CompatDebug",param);
|
||||
if("[]".equals(param)) return new ArrayList<>();
|
||||
Log.i("V117CompatDebug","ph2");
|
||||
if(param == null) return new ArrayList<>();
|
||||
Log.i("V117CompatDebug","ph3");
|
||||
String rawList = param.substring(1,param.length()-1);
|
||||
Log.i("V117CompatDebug",rawList);
|
||||
return new ArrayList<>(Arrays.asList(rawList.split(",")));
|
||||
}
|
||||
private static String regenPackList(List<String> packs) {
|
||||
if(packs.size()==0) return "[]";
|
||||
String ret = "["+packs.get(0);
|
||||
for(int i = 1; i < packs.size(); i++) {
|
||||
ret += ","+packs.get(i);
|
||||
}
|
||||
ret += "]";
|
||||
return ret;
|
||||
}
|
||||
public static void runCheck(String version, Activity ctx) throws Exception{
|
||||
|
||||
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(version);
|
||||
MCOptionUtils.load();
|
||||
|
||||
List<String> packList =getTexturePackList(MCOptionUtils.get("resourcePacks"));
|
||||
String renderer = cfg != null && cfg.renderer != null?cfg.renderer:LauncherPreferences.PREF_RENDERER;
|
||||
|
||||
if(renderer.equals("vulkan_zink")) return; //don't install for zink users;
|
||||
if(packList.contains("\"assets-v0.zip\"") && renderer.equals("opengles3")) return;
|
||||
|
||||
Object lock = new Object();
|
||||
AtomicInteger proceed = new AtomicInteger(0);
|
||||
ctx.runOnUiThread(() -> {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(ctx);
|
||||
bldr.setTitle(R.string.global_warinng);
|
||||
bldr.setMessage(R.string.compat_117_message);
|
||||
bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
proceed.set(1);
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setNeutralButton(R.string.compat_11x_playanyway, (dialog, which) -> {
|
||||
proceed.set(2);
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setCancelable(false);
|
||||
bldr.show();
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
switch(proceed.get()) {
|
||||
case 1:
|
||||
if (cfg == null) {
|
||||
cfg = new PerVersionConfig.VersionConfig();
|
||||
PerVersionConfig.configMap.put(version, cfg);
|
||||
}
|
||||
cfg.renderer = "opengles3";
|
||||
String path = Tools.DIR_GAME_NEW;
|
||||
if(cfg.gamePath != null && !cfg.gamePath.isEmpty()) path = cfg.gamePath;
|
||||
copyResourcePack(path,ctx.getAssets());
|
||||
if(!packList.contains("\"assets-v0.zip\"")) packList.add(0,"\"assets-v0.zip\"");
|
||||
MCOptionUtils.set("resourcePacks",regenPackList(packList));
|
||||
MCOptionUtils.save();
|
||||
PerVersionConfig.update();
|
||||
break;
|
||||
case 0:
|
||||
throw new MinecraftDownloaderTask.SilentException();
|
||||
}
|
||||
}
|
||||
public static void copyResourcePack(String gameDir, AssetManager am) throws IOException {
|
||||
File resourcepacksDir = new File(gameDir,"resourcepacks");
|
||||
if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(new File(resourcepacksDir,"assets-v0.zip"));
|
||||
InputStream is = am.open("assets-v0.zip");
|
||||
IOUtils.copy(is,fos);
|
||||
is.close();fos.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -27,5 +27,7 @@ public class PerVersionConfig {
|
|||
public static class VersionConfig {
|
||||
public String jvmArgs;
|
||||
public String gamePath;
|
||||
public String selectedRuntime;
|
||||
public String renderer;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ public class CallbackBridge {
|
|||
public static volatile int windowWidth, windowHeight;
|
||||
public static volatile int physicalWidth, physicalHeight;
|
||||
public static int mouseX, mouseY;
|
||||
public static boolean mouseLeft;
|
||||
public static StringBuilder DEBUG_STRING = new StringBuilder();
|
||||
|
||||
// volatile private static boolean isGrabbing = false;
|
||||
|
|
@ -29,30 +28,30 @@ public class CallbackBridge {
|
|||
}
|
||||
@Override
|
||||
public void run() {
|
||||
putMouseEventWithCoords(button, 1, x, y);
|
||||
putMouseEventWithCoords(button, true, x, y);
|
||||
try { Thread.sleep(40); } catch (InterruptedException e) {}
|
||||
putMouseEventWithCoords(button, 0, x, y);
|
||||
putMouseEventWithCoords(button, false, x, y);
|
||||
}
|
||||
}
|
||||
public static void putMouseEventWithCoords(int button, int x, int y /* , int dz, long nanos */) {
|
||||
new Thread(new PusherRunnable(button,x,y)).run();
|
||||
}
|
||||
|
||||
public static void putMouseEventWithCoords(int button, int state, int x, int y /* , int dz, long nanos */) {
|
||||
public static void putMouseEventWithCoords(int button, boolean isDown, int x, int y /* , int dz, long nanos */) {
|
||||
sendCursorPos(x, y);
|
||||
sendMouseKeycode(button, CallbackBridge.getCurrentMods(), state == 1);
|
||||
sendMouseKeycode(button, CallbackBridge.getCurrentMods(), isDown);
|
||||
}
|
||||
|
||||
private static boolean threadAttached;
|
||||
public static void sendCursorPos(int x, int y) {
|
||||
public static void sendCursorPos(float x, float y) {
|
||||
if (!threadAttached) {
|
||||
threadAttached = CallbackBridge.nativeAttachThreadToOther(true, BaseMainActivity.isInputStackCall);
|
||||
}
|
||||
|
||||
DEBUG_STRING.append("CursorPos=" + x + ", " + y + "\n");
|
||||
mouseX = x;
|
||||
mouseY = y;
|
||||
nativeSendCursorPos(x, y);
|
||||
DEBUG_STRING.append("CursorPos=").append(x).append(", ").append(y).append("\n");
|
||||
mouseX = (int) x;
|
||||
mouseY = (int) y;
|
||||
nativeSendCursorPos(mouseX, mouseY);
|
||||
}
|
||||
|
||||
public static void sendPrepareGrabInitialPos() {
|
||||
|
|
@ -61,7 +60,7 @@ public class CallbackBridge {
|
|||
}
|
||||
|
||||
public static void sendKeycode(int keycode, char keychar, int scancode, int modifiers, boolean isDown) {
|
||||
DEBUG_STRING.append("KeyCode=" + keycode + ", Char=" + keychar);
|
||||
DEBUG_STRING.append("KeyCode=").append(keycode).append(", Char=").append(keychar);
|
||||
// TODO CHECK: This may cause input issue, not receive input!
|
||||
/*
|
||||
if (!nativeSendCharMods((int) keychar, modifiers) || !nativeSendChar(keychar)) {
|
||||
|
|
@ -71,7 +70,7 @@ public class CallbackBridge {
|
|||
|
||||
//nativeSendKeycode(keycode, keychar, scancode, isDown ? 1 : 0, modifiers);
|
||||
if(keycode != 0) nativeSendKey(keycode,scancode,isDown ? 1 : 0, modifiers);
|
||||
else nativeSendKey(32,scancode,isDown ? 1 : 0, modifiers);
|
||||
//else nativeSendKey(32,scancode,isDown ? 1 : 0, modifiers);
|
||||
if(isDown && keychar != '\u0000') {
|
||||
nativeSendCharMods(keychar,modifiers);
|
||||
nativeSendChar(keychar);
|
||||
|
|
@ -81,7 +80,7 @@ public class CallbackBridge {
|
|||
}
|
||||
|
||||
public static void sendMouseKeycode(int button, int modifiers, boolean isDown) {
|
||||
DEBUG_STRING.append("MouseKey=" + button + ", down=" + isDown + "\n");
|
||||
DEBUG_STRING.append("MouseKey=").append(button).append(", down=").append(isDown).append("\n");
|
||||
// if (isGrabbing()) DEBUG_STRING.append("MouseGrabStrace: " + android.util.Log.getStackTraceString(new Throwable()) + "\n");
|
||||
nativeSendMouseButton(button, isDown ? 1 : 0, modifiers);
|
||||
}
|
||||
|
|
@ -92,7 +91,7 @@ public class CallbackBridge {
|
|||
}
|
||||
|
||||
public static void sendScroll(double xoffset, double yoffset) {
|
||||
DEBUG_STRING.append("ScrollX=" + xoffset + ",ScrollY=" + yoffset);
|
||||
DEBUG_STRING.append("ScrollX=").append(xoffset).append(",ScrollY=").append(yoffset);
|
||||
nativeSendScroll(xoffset, yoffset);
|
||||
}
|
||||
|
||||
|
|
@ -166,6 +165,30 @@ public class CallbackBridge {
|
|||
return currMods;
|
||||
}
|
||||
|
||||
public static void setModifiers(int keyCode, boolean isDown){
|
||||
switch (keyCode){
|
||||
case LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT:
|
||||
CallbackBridge.holdingShift = isDown;
|
||||
return;
|
||||
|
||||
case LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL:
|
||||
CallbackBridge.holdingCtrl = isDown;
|
||||
return;
|
||||
|
||||
case LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT:
|
||||
CallbackBridge.holdingAlt = isDown;
|
||||
return;
|
||||
|
||||
case LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK:
|
||||
CallbackBridge.holdingCapslock = isDown;
|
||||
return;
|
||||
|
||||
case LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK:
|
||||
CallbackBridge.holdingNumlock = isDown;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static native boolean nativeAttachThreadToOther(boolean isAndroid, boolean isUsePushPoll);
|
||||
|
||||
private static native boolean nativeSendChar(char codepoint);
|
||||
|
|
@ -179,8 +202,6 @@ public class CallbackBridge {
|
|||
private static native void nativeSendScreenSize(int width, int height);
|
||||
|
||||
public static native boolean nativeIsGrabbing();
|
||||
public static native void nativePutControllerAxes(FloatBuffer axBuf);
|
||||
public static native void nativePutControllerButtons(ByteBuffer axBuf);
|
||||
static {
|
||||
System.loadLibrary("pojavexec");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,25 @@ HERE_PATH := $(LOCAL_PATH)
|
|||
# include $(HERE_PATH)/crash_dump/libbacktrace/Android.mk
|
||||
# include $(HERE_PATH)/crash_dump/debuggerd/Android.mk
|
||||
|
||||
|
||||
LOCAL_PATH := $(HERE_PATH)
|
||||
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := xhook
|
||||
LOCAL_SRC_FILES := xhook/xhook.c \
|
||||
xhook/xh_core.c \
|
||||
xhook/xh_elf.c \
|
||||
xhook/xh_jni.c \
|
||||
xhook/xh_log.c \
|
||||
xhook/xh_util.c \
|
||||
xhook/xh_version.c
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/xhook
|
||||
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
|
||||
LOCAL_CONLYFLAGS := -std=c11
|
||||
LOCAL_LDLIBS := -llog
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
# Link GLESv2 for test
|
||||
LOCAL_LDLIBS := -ldl -llog -landroid -lEGL
|
||||
|
|
@ -22,8 +40,10 @@ include $(BUILD_SHARED_LIBRARY)
|
|||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := istdio
|
||||
LOCAL_SHARED_LIBRARIES := xhook
|
||||
LOCAL_SRC_FILES := \
|
||||
stdio_is.c
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/xhook
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
|
|
|||
2102
app_pojavlauncher/src/main/jni/GL/gl.h
Normal file
2102
app_pojavlauncher/src/main/jni/GL/gl.h
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue