mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
3 Commits
v18.1.0
...
tauri-andr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9be83cf108 | ||
|
|
d7436e3972 | ||
|
|
bebc34d035 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -36,6 +36,8 @@ build/
|
||||
|
||||
# Rust build artifacts
|
||||
/target
|
||||
/.tauri
|
||||
/tauri.settings.gradle
|
||||
|
||||
# direnv has been claimed for Nix usage
|
||||
.direnv/
|
||||
@@ -43,6 +45,8 @@ build/
|
||||
|
||||
# Ignore Android local properties
|
||||
local.properties
|
||||
keystore.properties
|
||||
/.android
|
||||
|
||||
# Ignore temporary config
|
||||
vrconfig.yml.tmp
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -4081,7 +4081,6 @@ name = "slimevr"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"clap",
|
||||
"clap-verbosity-flag",
|
||||
"color-eyre",
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
plugins {
|
||||
id("org.ajoberstar.grgit")
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.6.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProject.properties["kotlinVersion"]}")
|
||||
}
|
||||
}
|
||||
|
||||
subprojects.filter { it.name.contains("tauri") }.forEach {
|
||||
it.repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
23
buildSrc/build.gradle.kts
Normal file
23
buildSrc/build.gradle.kts
Normal file
@@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("pluginsForCoolKids") {
|
||||
id = "rust"
|
||||
implementationClass = "RustPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
implementation("com.android.tools.build:gradle:8.6.1")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import java.io.File
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
open class BuildTask : DefaultTask() {
|
||||
@Input
|
||||
var rootDirRel: String? = null
|
||||
@Input
|
||||
var target: String? = null
|
||||
@Input
|
||||
var release: Boolean? = null
|
||||
|
||||
@TaskAction
|
||||
fun assemble() {
|
||||
val executable = """pnpm""";
|
||||
try {
|
||||
runTauriCli(executable)
|
||||
} catch (e: Exception) {
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
runTauriCli("$executable.cmd")
|
||||
} else if (Os.isFamily(Os.FAMILY_UNIX)){
|
||||
runTauriCli("/nix/store/r2n3dbbp0djly6wjxx43sbffaq3abjpy-pnpm-10.15.0/bin/pnpm")
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runTauriCli(executable: String) {
|
||||
val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
|
||||
val target = target ?: throw GradleException("target cannot be null")
|
||||
val release = release ?: throw GradleException("release cannot be null")
|
||||
val args = listOf("tauri", "android", "android-studio-script")
|
||||
|
||||
project.exec {
|
||||
workingDir(File(project.projectDir, rootDirRel))
|
||||
executable(executable)
|
||||
args(args)
|
||||
if (project.logger.isEnabled(LogLevel.DEBUG)) {
|
||||
args("-vv")
|
||||
} else if (project.logger.isEnabled(LogLevel.INFO)) {
|
||||
args("-v")
|
||||
}
|
||||
if (release) {
|
||||
args("--release")
|
||||
}
|
||||
args(listOf("--target", target))
|
||||
}.assertNormalExitValue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.get
|
||||
|
||||
const val TASK_GROUP = "rust"
|
||||
|
||||
open class Config {
|
||||
lateinit var rootDirRel: String
|
||||
}
|
||||
|
||||
open class RustPlugin : Plugin<Project> {
|
||||
private lateinit var config: Config
|
||||
|
||||
override fun apply(project: Project) = with(project) {
|
||||
config = extensions.create("rust", Config::class.java)
|
||||
|
||||
val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64");
|
||||
val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList
|
||||
|
||||
val defaultArchList = listOf("arm64", "arm", "x86", "x86_64");
|
||||
val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList
|
||||
|
||||
val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
@Suppress("UnstableApiUsage")
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList
|
||||
}
|
||||
}
|
||||
defaultArchList.forEachIndexed { index, arch ->
|
||||
create(arch) {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters.add(defaultAbiList[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
for (profile in listOf("debug", "release")) {
|
||||
val profileCapitalized = profile.replaceFirstChar { it.uppercase() }
|
||||
val buildTask = tasks.maybeCreate(
|
||||
"rustBuildUniversal$profileCapitalized",
|
||||
DefaultTask::class.java
|
||||
).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in $profile mode for all targets"
|
||||
}
|
||||
|
||||
tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask)
|
||||
|
||||
for (targetPair in targetsList.withIndex()) {
|
||||
val targetName = targetPair.value
|
||||
val targetArch = archList[targetPair.index]
|
||||
val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }
|
||||
val targetBuildTask = project.tasks.maybeCreate(
|
||||
"rustBuild$targetArchCapitalized$profileCapitalized",
|
||||
BuildTask::class.java
|
||||
).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in $profile mode for $targetArch"
|
||||
rootDirRel = config.rootDirRel
|
||||
target = targetName
|
||||
release = profile == "release"
|
||||
}
|
||||
|
||||
buildTask.dependsOn(targetBuildTask)
|
||||
tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn(
|
||||
targetBuildTask
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
flake.nix
41
flake.nix
@@ -57,11 +57,36 @@
|
||||
_module.args.pkgs = import self.inputs.nixpkgs {
|
||||
inherit system;
|
||||
overlays = [nixgl.overlay];
|
||||
# Allow android SDK
|
||||
# config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||
# "android-sdk-cmdline-tools"
|
||||
# "android-sdk-platform-tools"
|
||||
# "platform-tools"
|
||||
# "android-sdk-tools"
|
||||
# "android-sdk-emulator"
|
||||
# "android-sdk-system-image-32-google_apis_playstore-arm64-v8a-system-image-32-google_apis_playstore-x86_64"
|
||||
# "system-image-32-google_apis_playstore-arm64-v8a"
|
||||
# "system-image-32-google_apis_playstore-x86_64"
|
||||
# "android-sdk-system-image-34-google_apis_playstore-arm64-v8a-system-image-34-google_apis_playstore-x86_64"
|
||||
# "system-image-34-google_apis_playstore-arm64-v8a"
|
||||
# "system-image-34-google_apis_playstore-x86_64"
|
||||
# "emulator"
|
||||
# "tools"
|
||||
# "android-sdk-build-tools"
|
||||
# "build-tools"
|
||||
# "android-sdk-platforms"
|
||||
# "platforms"
|
||||
# "cmake"
|
||||
# "android-sdk-ndk"
|
||||
# "ndk"
|
||||
# "android-sdk-extras-google-gcm"
|
||||
# "extras-google-gcm"
|
||||
# "cmdline-tools"
|
||||
# ];
|
||||
};
|
||||
|
||||
devenv.shells.default = let
|
||||
fenixpkgs = inputs'.fenix.packages;
|
||||
rust_toolchain = lib.importTOML ./rust-toolchain.toml;
|
||||
in {
|
||||
name = "slimevr";
|
||||
|
||||
@@ -76,6 +101,7 @@
|
||||
(with pkgs; [
|
||||
pkgs.nixgl.nixGLIntel
|
||||
cacert
|
||||
stow
|
||||
])
|
||||
++ lib.optionals pkgs.stdenv.isLinux (with pkgs; [
|
||||
atk
|
||||
@@ -118,6 +144,12 @@
|
||||
};
|
||||
languages.kotlin.enable = true;
|
||||
|
||||
# android = {
|
||||
# enable = true;
|
||||
# googleAPIs.enable = false;
|
||||
# googleTVAddOns.enable = false;
|
||||
# };
|
||||
|
||||
languages.javascript = {
|
||||
enable = true;
|
||||
corepack.enable = true;
|
||||
@@ -127,11 +159,10 @@
|
||||
|
||||
languages.rust = {
|
||||
enable = true;
|
||||
toolchain = fenixpkgs.fromToolchainName {
|
||||
name = rust_toolchain.toolchain.channel;
|
||||
sha256 = "sha256-yMuSb5eQPO/bHv+Bcf/US8LVMbf/G/0MSfiPwBhiPpk=";
|
||||
toolchainPackage = fenixpkgs.fromToolchainFile {
|
||||
file = ./rust-toolchain.toml;
|
||||
sha256 = "sha256-+9FmLhAOezBZCOziO0Qct1NOrfpjNsXxc/8I0c7BdKE=";
|
||||
};
|
||||
components = rust_toolchain.toolchain.components;
|
||||
};
|
||||
|
||||
env = {
|
||||
|
||||
@@ -3,14 +3,26 @@ org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAME
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
|
||||
-Dfile.encoding=UTF-8
|
||||
|
||||
kotlin.code.style=official
|
||||
# https://github.com/Kotlin/kotlinx-atomicfu#atomicfu-compiler-plugin
|
||||
kotlinx.atomicfu.enableJvmIrTransformation=true
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
org.gradle.unsafe.configuration-cache=false
|
||||
|
||||
kotlinVersion=2.0.20
|
||||
|
||||
@@ -12,6 +12,13 @@ default-run = "slimevr"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "slimevr_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||
@@ -22,7 +29,6 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0", features = [] }
|
||||
cfg_aliases = "0.2"
|
||||
shadow-rs = "0.35"
|
||||
|
||||
[dependencies]
|
||||
@@ -49,7 +55,6 @@ shadow-rs = { version = "0.35", default-features = false }
|
||||
const_format = "0.2.30"
|
||||
cfg-if = "1"
|
||||
color-eyre = "0.6"
|
||||
rfd = { version = "0.15", features = ["gtk3"], default-features = false }
|
||||
dirs-next = "2.0.0"
|
||||
discord-sdk = "0.3.6"
|
||||
tokio = { version = "1.37.0", features = ["time"] }
|
||||
@@ -62,3 +67,6 @@ winreg = "0.52"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libloading = "0.8"
|
||||
|
||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
rfd = { version = "0.15", features = ["gtk3"], default-features = false }
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
// Bypass for Nix script having libudev-zero and Tauri not liking it
|
||||
if let Some(path) = option_env!("SLIMEVR_RUST_LD_LIBRARY_PATH") {
|
||||
@@ -7,9 +5,5 @@ fn main() -> shadow_rs::SdResult<()> {
|
||||
}
|
||||
|
||||
tauri_build::build();
|
||||
cfg_aliases! {
|
||||
mobile: { any(target_os = "ios", target_os = "android") },
|
||||
desktop: { not(any(target_os = "ios", target_os = "android")) }
|
||||
}
|
||||
shadow_rs::new()
|
||||
}
|
||||
|
||||
10
gui/src-tauri/gen/.stowrc
Normal file
10
gui/src-tauri/gen/.stowrc
Normal file
@@ -0,0 +1,10 @@
|
||||
--ignore='^gui$'
|
||||
--ignore='^\..+'
|
||||
--ignore='^node_modules$'
|
||||
--ignore='^build$'
|
||||
--ignore='^(local|keystore).properties$'
|
||||
--ignore='^target$'
|
||||
--ignore='^assets'
|
||||
--ignore='.*\.(toml|yaml|nix|lock|json|md)$'
|
||||
-d ../../../../
|
||||
-t android
|
||||
1
gui/src-tauri/gen/android/app
Symbolic link
1
gui/src-tauri/gen/android/app
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../server/android/
|
||||
1
gui/src-tauri/gen/android/build.gradle.kts
Symbolic link
1
gui/src-tauri/gen/android/build.gradle.kts
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/build.gradle.kts
|
||||
1
gui/src-tauri/gen/android/buildSrc
Symbolic link
1
gui/src-tauri/gen/android/buildSrc
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/buildSrc
|
||||
1
gui/src-tauri/gen/android/gradle
Symbolic link
1
gui/src-tauri/gen/android/gradle
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/gradle
|
||||
1
gui/src-tauri/gen/android/gradle.properties
Symbolic link
1
gui/src-tauri/gen/android/gradle.properties
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/gradle.properties
|
||||
1
gui/src-tauri/gen/android/gradlew
vendored
Symbolic link
1
gui/src-tauri/gen/android/gradlew
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/gradlew
|
||||
1
gui/src-tauri/gen/android/gradlew.bat
vendored
Symbolic link
1
gui/src-tauri/gen/android/gradlew.bat
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/gradlew.bat
|
||||
1
gui/src-tauri/gen/android/server
Symbolic link
1
gui/src-tauri/gen/android/server
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/server
|
||||
1
gui/src-tauri/gen/android/settings.gradle.kts
Symbolic link
1
gui/src-tauri/gen/android/settings.gradle.kts
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/settings.gradle.kts
|
||||
1
gui/src-tauri/gen/android/solarxr-protocol
Symbolic link
1
gui/src-tauri/gen/android/solarxr-protocol
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/solarxr-protocol
|
||||
1
gui/src-tauri/gen/android/tauri.settings.gradle
Symbolic link
1
gui/src-tauri/gen/android/tauri.settings.gradle
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../SlimeVR-Server/tauri.settings.gradle
|
||||
6
gui/src-tauri/src/cross.rs
Normal file
6
gui/src-tauri/src/cross.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub struct TrayAvailable(pub bool);
|
||||
|
||||
#[tauri::command]
|
||||
pub fn is_tray_available(tray_available: tauri::State<TrayAvailable>) -> bool {
|
||||
tray_available.0
|
||||
}
|
||||
45
gui/src-tauri/src/lib.rs
Normal file
45
gui/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use tauri::Manager;
|
||||
|
||||
mod cross;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
log_panics::init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.target(tauri_plugin_log::Target::new(
|
||||
tauri_plugin_log::TargetKind::LogDir {
|
||||
file_name: Some("slimevr".to_string()),
|
||||
},
|
||||
))
|
||||
.max_file_size(30_000 /* bytes */)
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepSome(3))
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_store::Builder::default().build())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.invoke_handler(tauri::generate_handler![cross::is_tray_available,])
|
||||
.setup(move |app| {
|
||||
log::info!("SlimeVR started!");
|
||||
|
||||
let _ = tauri::WebviewWindowBuilder::new(
|
||||
app,
|
||||
"main",
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
.build()?;
|
||||
|
||||
app.manage(cross::TrayAvailable(false));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use crate::util::{
|
||||
get_launch_path, show_error, valid_java_paths, Cli, JAVA_BIN, MINIMUM_JAVA_VERSION,
|
||||
};
|
||||
|
||||
mod cross;
|
||||
mod presence;
|
||||
mod state;
|
||||
mod tray;
|
||||
@@ -106,6 +107,7 @@ fn main() -> Result<()> {
|
||||
setup_webview2()?;
|
||||
|
||||
// Check for environment variables that can affect the server, and if so, warn in log and GUI
|
||||
#[cfg(desktop)]
|
||||
check_environment_variables();
|
||||
|
||||
// Spawn server process
|
||||
@@ -189,6 +191,7 @@ fn setup_webview2() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn check_environment_variables() {
|
||||
use itertools::Itertools;
|
||||
const ENVS_TO_CHECK: &[&str] = &["_JAVA_OPTIONS", "JAVA_TOOL_OPTIONS"];
|
||||
@@ -264,7 +267,7 @@ fn setup_tauri(
|
||||
open_logs_folder,
|
||||
tray::update_translations,
|
||||
tray::update_tray_text,
|
||||
tray::is_tray_available,
|
||||
cross::is_tray_available,
|
||||
presence::discord_client_exists,
|
||||
presence::update_presence,
|
||||
presence::clear_presence,
|
||||
@@ -299,7 +302,7 @@ fn setup_tauri(
|
||||
tray::create_tray(handle)?;
|
||||
presence::create_presence(handle)?;
|
||||
} else {
|
||||
app.manage(tray::TrayAvailable(false));
|
||||
app.manage(cross::TrayAvailable(false));
|
||||
}
|
||||
|
||||
app.manage(Mutex::new(window_state));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
use crate::cross::TrayAvailable;
|
||||
use tauri::{
|
||||
include_image,
|
||||
menu::{Menu, MenuBuilder, MenuItemBuilder, MenuItemKind},
|
||||
@@ -8,7 +9,6 @@ use tauri::{
|
||||
};
|
||||
|
||||
pub struct TrayMenu<R: Runtime>(Menu<R>);
|
||||
pub struct TrayAvailable(pub bool);
|
||||
|
||||
pub struct TrayTranslations {
|
||||
store: Mutex<HashMap<String, String>>,
|
||||
@@ -22,11 +22,6 @@ impl TrayTranslations {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn is_tray_available(tray_available: State<TrayAvailable>) -> bool {
|
||||
tray_available.0
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_translations<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
|
||||
6
gui/src-tauri/tauri.android.conf.json
Normal file
6
gui/src-tauri/tauri.android.conf.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm run start --host"
|
||||
},
|
||||
"identifier": "dev.slimevr.android"
|
||||
}
|
||||
@@ -2,3 +2,4 @@
|
||||
channel = "1.82"
|
||||
profile = "default"
|
||||
components = ["rustc", "cargo", "clippy", "rustfmt", "rust-analyzer", "rust-src"]
|
||||
targets = ["x86_64-linux-android", "aarch64-linux-android", "armv7-linux-androideabi", "i686-linux-android"]
|
||||
|
||||
7
server/android/.gitignore
vendored
7
server/android/.gitignore
vendored
@@ -1,2 +1,9 @@
|
||||
/build
|
||||
/src/main/resources/web-gui
|
||||
|
||||
/src/main/java/dev/slimevr/android/generated
|
||||
/src/main/jniLibs/**/*.so
|
||||
/src/main/assets/tauri.conf.json
|
||||
/tauri.build.gradle.kts
|
||||
/proguard-tauri.pro
|
||||
/tauri.properties
|
||||
|
||||
@@ -7,14 +7,24 @@
|
||||
*/
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
kotlin("android")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.github.gmazzo.buildconfig")
|
||||
|
||||
id("com.android.application") version "8.6.1"
|
||||
id("com.android.application")
|
||||
id("org.ajoberstar.grgit")
|
||||
id("rust")
|
||||
}
|
||||
|
||||
val tauriProperties = Properties().apply {
|
||||
val propFile = file("tauri.properties")
|
||||
if (propFile.exists()) {
|
||||
propFile.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -28,17 +38,6 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyGuiAssets") {
|
||||
from(rootProject.layout.projectDirectory.dir("gui/dist"))
|
||||
into(layout.projectDirectory.dir("src/main/resources/web-gui"))
|
||||
if (inputs.sourceFiles.isEmpty) {
|
||||
throw GradleException("You need to run \"pnpm run build\" on the gui folder first!")
|
||||
}
|
||||
}
|
||||
tasks.preBuild {
|
||||
dependsOn(":server:android:copyGuiAssets")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
@@ -61,6 +60,14 @@ repositories {
|
||||
google()
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = if (projectDir.absolutePath.contains("gen/android")) {
|
||||
"../../../../../"
|
||||
} else {
|
||||
"../../gui/src-tauri"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":server:core"))
|
||||
|
||||
@@ -68,17 +75,15 @@ dependencies {
|
||||
implementation("org.apache.commons:commons-lang3:3.15.0")
|
||||
|
||||
// Android stuff
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.webkit:webkit:1.14.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("androidx.activity:activity-ktx:1.10.1")
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||
// For hosting web GUI
|
||||
implementation("io.ktor:ktor-server-core:2.3.12")
|
||||
implementation("io.ktor:ktor-server-netty:2.3.10")
|
||||
implementation("io.ktor:ktor-server-caching-headers:2.3.12")
|
||||
|
||||
// Serial
|
||||
implementation("com.github.mik3y:usb-serial-for-android:3.7.0")
|
||||
@@ -90,34 +95,18 @@ extra.apply {
|
||||
set("gitVersionName", grgit.describe(mapOf("tags" to true, "always" to true)))
|
||||
}
|
||||
android {
|
||||
// The app's namespace. Used primarily to access app resources.
|
||||
|
||||
namespace = "dev.slimevr.android"
|
||||
|
||||
/* compileSdk specifies the Android API level Gradle should use to
|
||||
compile your app. This means your app can use the API features included in
|
||||
this API level and lower. */
|
||||
|
||||
compileSdk = 35
|
||||
|
||||
/* The defaultConfig block encapsulates default settings and entries for all
|
||||
build variants and can override some attributes in main/AndroidManifest.xml
|
||||
dynamically from the build system. You can configure product flavors to override
|
||||
these values for different versions of your app. */
|
||||
|
||||
packaging {
|
||||
resources.excludes.add("META-INF/*")
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
|
||||
// Uniquely identifies the package for publishing.
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "dev.slimevr.server.android"
|
||||
|
||||
// Defines the minimum API level required to run the app.
|
||||
minSdk = 26
|
||||
|
||||
// Specifies the API level used to test the app.
|
||||
targetSdk = 35
|
||||
|
||||
// adds an offset of the version code as we might do apk releases in the middle of actual
|
||||
@@ -128,8 +117,27 @@ android {
|
||||
|
||||
// Defines a user-friendly version name for your app.
|
||||
versionName = extra["gitVersionName"] as? String ?: "v0.0.0"
|
||||
setProperty("archivesBaseName", "app")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
ndk {
|
||||
abiFilters += listOf("x86", "x86_64", "arm64-v8a", "armeabi-v7a")
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
||||
keyPassword = keystoreProperties["password"] as String?
|
||||
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
|
||||
storePassword = keystoreProperties["password"] as String?
|
||||
}
|
||||
}
|
||||
|
||||
/* The buildTypes block is where you can configure multiple build types.
|
||||
@@ -139,15 +147,25 @@ android {
|
||||
build type applies ProGuard settings and is not signed by default. */
|
||||
|
||||
buildTypes {
|
||||
|
||||
/* By default, Android Studio configures the release build type to enable code
|
||||
shrinking, using minifyEnabled, and specifies the default ProGuard rules file. */
|
||||
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packaging {
|
||||
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true // Enables code shrinking for the release build type.
|
||||
isMinifyEnabled = true
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android.txt"),
|
||||
"proguard-rules.pro",
|
||||
*fileTree(".") { include("**/*.pro") }
|
||||
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
.toList().toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -159,4 +177,13 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
apply(from = "tauri.build.gradle.kts")
|
||||
} catch (e: Exception) {
|
||||
println("Couldn't enable tauri stuff")
|
||||
}
|
||||
|
||||
@@ -28,6 +28,15 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name=".ForegroundService"
|
||||
|
||||
@@ -9,17 +9,7 @@ import dev.slimevr.Keybinding
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.android.serial.AndroidSerialHandler
|
||||
import io.eiren.util.logging.LogManager
|
||||
import io.ktor.http.CacheControl
|
||||
import io.ktor.http.CacheControl.Visibility
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.http.content.CachingOptions
|
||||
import io.ktor.server.http.content.staticResources
|
||||
import io.ktor.server.netty.Netty
|
||||
import io.ktor.server.plugins.cachingheaders.CachingHeaders
|
||||
import io.ktor.server.routing.routing
|
||||
import java.io.File
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -29,18 +19,6 @@ val vrServerInitialized: Boolean
|
||||
get() = ::vrServer.isInitialized
|
||||
|
||||
fun main(activity: AppCompatActivity) {
|
||||
// Host the web GUI server
|
||||
embeddedServer(Netty, port = 34536) {
|
||||
routing {
|
||||
install(CachingHeaders) {
|
||||
options { _, _ ->
|
||||
CachingOptions(CacheControl.NoStore(Visibility.Public), ZonedDateTime.now())
|
||||
}
|
||||
}
|
||||
staticResources("/", "web-gui", "index.html")
|
||||
}
|
||||
}.start(wait = false)
|
||||
|
||||
thread(start = true, name = "Main VRServer Thread") {
|
||||
try {
|
||||
LogManager.initialize(activity.filesDir)
|
||||
|
||||
@@ -3,9 +3,7 @@ package dev.slimevr.android
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import io.eiren.util.logging.LogManager
|
||||
|
||||
class AndroidJsObject {
|
||||
@@ -13,10 +11,10 @@ class AndroidJsObject {
|
||||
fun isThere(): Boolean = true
|
||||
}
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
class MainActivity : TauriActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Initialize logger (doesn't re-initialize if already run)
|
||||
try {
|
||||
@@ -33,35 +31,6 @@ class MainActivity : AppCompatActivity() {
|
||||
LogManager.info("[MainActivity] VRServer is already running, skipping initialization.")
|
||||
}
|
||||
|
||||
// Load the web GUI web page
|
||||
LogManager.info("[MainActivity] Initializing GUI WebView...")
|
||||
val guiWebView = findViewById<WebView>(R.id.guiWebView)
|
||||
|
||||
// ## Configure for web GUI ##
|
||||
// Enable debug mode
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
|
||||
// Set required features
|
||||
guiWebView.settings.javaScriptEnabled = true
|
||||
guiWebView.settings.domStorageEnabled = true
|
||||
|
||||
// TODO: Let code know it is in android, should be gone when we start using tauri
|
||||
guiWebView.addJavascriptInterface(AndroidJsObject(), "__ANDROID__")
|
||||
|
||||
// Try fixing zoom usability
|
||||
guiWebView.settings.setSupportZoom(true)
|
||||
guiWebView.settings.useWideViewPort = true
|
||||
guiWebView.settings.loadWithOverviewMode = true
|
||||
guiWebView.invokeZoomPicker()
|
||||
|
||||
// Disable cache! This is all local anyway
|
||||
guiWebView.settings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
guiWebView.clearCache(true)
|
||||
|
||||
// Load GUI page
|
||||
guiWebView.loadUrl("http://127.0.0.1:34536/")
|
||||
LogManager.info("[MainActivity] GUI WebView has been initialized and loaded.")
|
||||
|
||||
// Start a foreground service to notify the user the SlimeVR Server is running
|
||||
// This also helps prevent Android from ejecting the process unexpectedly
|
||||
val serviceIntent = Intent(this, ForegroundService::class.java)
|
||||
|
||||
5
server/android/src/main/res/xml/file_paths.xml
Normal file
5
server/android/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -40,3 +40,8 @@ project(":server").projectDir = File("server")
|
||||
include(":server:core")
|
||||
include(":server:desktop")
|
||||
include(":server:android")
|
||||
try {
|
||||
apply(from = "tauri.settings.gradle")
|
||||
} catch(e: Exception) {
|
||||
println("Couldn't enable tauri stuff")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user