mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
feat: VRServer gracefully shutdown (#632)
Co-authored-by: ImUrX <urielfontan2002@gmail.com>
This commit is contained in:
@@ -1,125 +1,26 @@
|
||||
#![cfg_attr(all(not(debug_assertions), windows), windows_subsystem = "windows")]
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::Write;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::panic;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Stdio};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use clap::Parser;
|
||||
use const_format::concatcp;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use shadow_rs::shadow;
|
||||
use tauri::api::process::Command;
|
||||
use tauri::api::process::{Command, CommandChild};
|
||||
use tauri::Manager;
|
||||
use tauri::RunEvent;
|
||||
|
||||
#[cfg(windows)]
|
||||
use tauri::WindowEvent;
|
||||
use tempfile::Builder;
|
||||
|
||||
#[cfg(windows)]
|
||||
/// For Commands on Windows so they dont create terminals
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
/// It's an i32 because we check it through exit codes of the process
|
||||
const MINIMUM_JAVA_VERSION: i32 = 17;
|
||||
const JAVA_BIN: &str = if cfg!(windows) { "java.exe" } else { "java" };
|
||||
static POSSIBLE_TITLES: &[&str] = &[
|
||||
"Panicking situation",
|
||||
"looking for spatula",
|
||||
"never gonna give you up",
|
||||
"never gonna let you down",
|
||||
"uwu sowwy",
|
||||
];
|
||||
shadow!(build);
|
||||
// Tauri has a way to return the package.json version, but it's not a constant...
|
||||
const VERSION: &str = if build::TAG.is_empty() {
|
||||
build::SHORT_COMMIT
|
||||
} else {
|
||||
build::TAG
|
||||
use crate::util::{
|
||||
get_launch_path, show_error, valid_java_paths, Cli, JAVA_BIN, MINIMUM_JAVA_VERSION,
|
||||
};
|
||||
const MODIFIED: &str = if build::GIT_CLEAN { "" } else { "-dirty" };
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(
|
||||
version = concatcp!(VERSION, MODIFIED),
|
||||
about
|
||||
)]
|
||||
struct Cli {
|
||||
#[clap(short, long)]
|
||||
display_console: bool,
|
||||
#[clap(long)]
|
||||
launch_from_path: Option<PathBuf>,
|
||||
#[clap(flatten)]
|
||||
verbose: clap_verbosity_flag::Verbosity,
|
||||
}
|
||||
|
||||
fn is_valid_path(path: &Path) -> bool {
|
||||
path.join("slimevr.jar").exists()
|
||||
}
|
||||
|
||||
fn get_launch_path(cli: Cli) -> Option<PathBuf> {
|
||||
let paths = [
|
||||
cli.launch_from_path,
|
||||
// AppImage passes the fakeroot in `APPDIR` env var.
|
||||
env::var_os("APPDIR").map(|x| PathBuf::from(x)),
|
||||
env::current_dir().ok(),
|
||||
// getcwd in Mac can't be trusted, so let's get the executable's path
|
||||
env::current_exe()
|
||||
.map(|mut f| {
|
||||
f.pop();
|
||||
f
|
||||
})
|
||||
.ok(),
|
||||
Some(PathBuf::from(env!("CARGO_MANIFEST_DIR"))),
|
||||
// For flatpak container
|
||||
Some(PathBuf::from("/app/share/slimevr/")),
|
||||
Some(PathBuf::from("/usr/share/slimevr/")),
|
||||
];
|
||||
|
||||
paths
|
||||
.into_iter()
|
||||
.filter_map(|x| x)
|
||||
.find(|x| is_valid_path(x))
|
||||
}
|
||||
|
||||
fn spawn_java(java: &OsStr, java_version: &OsStr) -> std::io::Result<Child> {
|
||||
let mut cmd = std::process::Command::new(java);
|
||||
|
||||
#[cfg(windows)]
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
|
||||
cmd.arg("-jar")
|
||||
.arg(java_version)
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.spawn()
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn show_error(text: &str) -> bool {
|
||||
use tauri::api::dialog::{
|
||||
blocking::MessageDialogBuilder, MessageDialogButtons, MessageDialogKind,
|
||||
};
|
||||
|
||||
MessageDialogBuilder::new(
|
||||
format!(
|
||||
"SlimeVR GUI crashed - {}",
|
||||
POSSIBLE_TITLES.choose(&mut thread_rng()).unwrap()
|
||||
),
|
||||
text,
|
||||
)
|
||||
.buttons(MessageDialogButtons::Ok)
|
||||
.kind(MessageDialogKind::Error)
|
||||
.show()
|
||||
}
|
||||
|
||||
#[cfg(mobile)]
|
||||
fn show_error(text: &str) -> bool {
|
||||
// needs to do native stuff on mobile
|
||||
false
|
||||
}
|
||||
mod util;
|
||||
|
||||
fn main() {
|
||||
// Make an error dialog box when panicking
|
||||
@@ -142,6 +43,7 @@ fn main() {
|
||||
// and then check for WebView2's existence
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use crate::util::webview2_exists;
|
||||
use win32job::{ExtendedLimitInfo, Job};
|
||||
|
||||
let mut info = ExtendedLimitInfo::new();
|
||||
@@ -173,6 +75,8 @@ fn main() {
|
||||
}
|
||||
|
||||
// Spawn server process
|
||||
let exit_flag = Arc::new(AtomicBool::new(false));
|
||||
let mut backend: Option<CommandChild> = None;
|
||||
let run_path = get_launch_path(cli);
|
||||
|
||||
let stdout_recv = if let Some(p) = run_path {
|
||||
@@ -190,20 +94,22 @@ fn main() {
|
||||
};
|
||||
|
||||
log::info!("Using Java binary: {:?}", java_bin);
|
||||
let (recv, _child) = Command::new(java_bin.to_str().unwrap())
|
||||
let (recv, child) = Command::new(java_bin.to_str().unwrap())
|
||||
.current_dir(p)
|
||||
.args(["-Xmx512M", "-jar", "slimevr.jar", "--no-gui"])
|
||||
.spawn()
|
||||
.expect("Unable to start the server jar");
|
||||
backend = Some(child);
|
||||
Some(recv)
|
||||
} else {
|
||||
log::warn!("No server found. We will not start the server.");
|
||||
None
|
||||
};
|
||||
|
||||
let run_result = tauri::Builder::default()
|
||||
let exit_flag_terminated = exit_flag.clone();
|
||||
let build_result = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.setup(|app| {
|
||||
.setup(move |app| {
|
||||
if let Some(mut recv) = stdout_recv {
|
||||
let app_handle = app.app_handle();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
@@ -215,6 +121,7 @@ fn main() {
|
||||
CommandEvent::Stdout(s) => ("stdout", s),
|
||||
CommandEvent::Error(s) => ("error", s),
|
||||
CommandEvent::Terminated(s) => {
|
||||
exit_flag_terminated.store(true, Ordering::Relaxed);
|
||||
("terminated", format!("{s:?}"))
|
||||
}
|
||||
_ => ("other", "".to_string()),
|
||||
@@ -237,8 +144,29 @@ fn main() {
|
||||
WindowEvent::Resized(_) => std::thread::sleep(std::time::Duration::from_nanos(1)),
|
||||
_ => (),
|
||||
})
|
||||
.run(tauri::generate_context!());
|
||||
match run_result {
|
||||
.build(tauri::generate_context!());
|
||||
match build_result {
|
||||
Ok(app) => {
|
||||
app.run(move |_app_handle, event| match event {
|
||||
RunEvent::ExitRequested { .. } => {
|
||||
let Some(ref mut child) = backend else { return };
|
||||
let write_result = child.write(b"exit\n");
|
||||
match write_result {
|
||||
Ok(()) => log::info!("send exit to backend"),
|
||||
Err(_) => log::info!("fail to send exit to backend"),
|
||||
}
|
||||
let ten_seconds = Duration::from_secs(10);
|
||||
let start_time = Instant::now();
|
||||
while start_time.elapsed() < ten_seconds {
|
||||
if exit_flag.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
#[cfg(windows)]
|
||||
// Often triggered when the user doesn't have webview2 installed
|
||||
Err(tauri::Error::Runtime(tauri_runtime::Error::CreateWebview(error))) => {
|
||||
@@ -256,110 +184,10 @@ fn main() {
|
||||
if confirm {
|
||||
open::that("https://docs.slimevr.dev/common-issues.html#webview2-is-missing--slimevr-gui-crashes-immediately--panicked-at--webview2error").unwrap();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => run_result.expect("error while running tauri application"),
|
||||
Err(error) => {
|
||||
log::error!("tauri build error {}", error);
|
||||
show_error(&error.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Check if WebView2 exists
|
||||
fn webview2_exists() -> bool {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
// First on the machine itself
|
||||
let machine: Option<String> = RegKey::predef(HKEY_LOCAL_MACHINE)
|
||||
.open_subkey(r"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}")
|
||||
.map(|r| r.get_value("pv").ok()).ok().flatten();
|
||||
let mut exists = false;
|
||||
if let Some(version) = machine {
|
||||
exists = version.split('.').any(|x| x != "0");
|
||||
}
|
||||
// Then in the current user
|
||||
if !exists {
|
||||
let user: Option<String> = RegKey::predef(HKEY_CURRENT_USER)
|
||||
.open_subkey(
|
||||
r"Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||||
)
|
||||
.map(|r| r.get_value("pv").ok())
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some(version) = user {
|
||||
exists = version.split('.').any(|x| x != "0");
|
||||
}
|
||||
}
|
||||
exists
|
||||
}
|
||||
|
||||
fn valid_java_paths() -> Vec<(OsString, i32)> {
|
||||
let mut file = Builder::new()
|
||||
.suffix(".jar")
|
||||
.tempfile()
|
||||
.expect("Couldn't generate .jar file");
|
||||
file.write_all(include_bytes!("JavaVersion.jar"))
|
||||
.expect("Couldn't write to .jar file");
|
||||
let java_version = file.into_temp_path();
|
||||
|
||||
// Check if main Java is a supported version
|
||||
let main_java = if let Ok(java_home) = std::env::var("JAVA_HOME") {
|
||||
PathBuf::from(java_home)
|
||||
.join("bin")
|
||||
.join(JAVA_BIN)
|
||||
.into_os_string()
|
||||
} else {
|
||||
JAVA_BIN.into()
|
||||
};
|
||||
if let Some(main_child) = spawn_java(&main_java, java_version.as_os_str())
|
||||
.expect("Couldn't spawn the main Java binary")
|
||||
.wait()
|
||||
.expect("Couldn't execute the main Java binary")
|
||||
.code()
|
||||
{
|
||||
if main_child >= MINIMUM_JAVA_VERSION {
|
||||
return vec![(main_java, main_child)];
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise check if anything else is a supported version
|
||||
let mut childs = vec![];
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
// macOS JVMs are saved on multiple possible places,
|
||||
// /Library/Java/JavaVirtualMachines are the ones installed by an admin
|
||||
// /Users/$USER/Library/Java/JavaVirtualMachines are the ones installed locally by the user
|
||||
let libs = glob::glob(concatcp!("/Library/Java/JavaVirtualMachines/*/Contents/Home/bin/", JAVA_BIN))
|
||||
.unwrap()
|
||||
.filter_map(|res| res.ok());
|
||||
} else if #[cfg(unix)] {
|
||||
// Linux JVMs are saved on /usr/lib/jvm from what I found out,
|
||||
// there is usually a default dir and a default-runtime dir also which are linked
|
||||
// to the current default runtime and the current default JDK (I think it's JDK)
|
||||
let libs = glob::glob(concatcp!("/usr/lib/jvm/*/bin/", JAVA_BIN))
|
||||
.unwrap()
|
||||
.filter_map(|res| res.ok());
|
||||
} else {
|
||||
let libs = which::which_all(JAVA_BIN).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for java in libs {
|
||||
let res = spawn_java(java.as_os_str(), java_version.as_os_str());
|
||||
|
||||
match res {
|
||||
Ok(child) => childs.push((java.into_os_string(), child)),
|
||||
Err(e) => println!("Error on trying to spawn a Java executable: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
childs
|
||||
.into_iter()
|
||||
.filter_map(|(p, mut c)| {
|
||||
c.wait()
|
||||
.expect("Failed on executing a Java executable")
|
||||
.code()
|
||||
.map(|code| (p, code))
|
||||
.filter(|(_p, code)| *code >= MINIMUM_JAVA_VERSION)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
221
gui/src-tauri/src/util.rs
Normal file
221
gui/src-tauri/src/util.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::{
|
||||
env,
|
||||
ffi::{OsStr, OsString},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::{Child, Stdio},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use const_format::concatcp;
|
||||
use shadow_rs::shadow;
|
||||
use tempfile::Builder;
|
||||
|
||||
#[cfg(windows)]
|
||||
/// For Commands on Windows so they dont create terminals
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
/// It's an i32 because we check it through exit codes of the process
|
||||
pub const MINIMUM_JAVA_VERSION: i32 = 17;
|
||||
pub const JAVA_BIN: &str = if cfg!(windows) { "java.exe" } else { "java" };
|
||||
pub static POSSIBLE_TITLES: &[&str] = &[
|
||||
"Panicking situation",
|
||||
"looking for spatula",
|
||||
"never gonna give you up",
|
||||
"never gonna let you down",
|
||||
"uwu sowwy",
|
||||
];
|
||||
|
||||
shadow!(build);
|
||||
// Tauri has a way to return the package.json version, but it's not a constant...
|
||||
const VERSION: &str = if build::TAG.is_empty() {
|
||||
build::SHORT_COMMIT
|
||||
} else {
|
||||
build::TAG
|
||||
};
|
||||
const MODIFIED: &str = if build::GIT_CLEAN { "" } else { "-dirty" };
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(
|
||||
version = concatcp!(VERSION, MODIFIED),
|
||||
about
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[clap(short, long)]
|
||||
display_console: bool,
|
||||
#[clap(long)]
|
||||
launch_from_path: Option<PathBuf>,
|
||||
#[clap(flatten)]
|
||||
verbose: clap_verbosity_flag::Verbosity,
|
||||
}
|
||||
|
||||
pub fn is_valid_path(path: &Path) -> bool {
|
||||
path.join("slimevr.jar").exists()
|
||||
}
|
||||
|
||||
pub fn get_launch_path(cli: Cli) -> Option<PathBuf> {
|
||||
let paths = [
|
||||
cli.launch_from_path,
|
||||
// AppImage passes the fakeroot in `APPDIR` env var.
|
||||
env::var_os("APPDIR").map(|x| PathBuf::from(x)),
|
||||
env::current_dir().ok(),
|
||||
// getcwd in Mac can't be trusted, so let's get the executable's path
|
||||
env::current_exe()
|
||||
.map(|mut f| {
|
||||
f.pop();
|
||||
f
|
||||
})
|
||||
.ok(),
|
||||
Some(PathBuf::from(env!("CARGO_MANIFEST_DIR"))),
|
||||
// For flatpak container
|
||||
Some(PathBuf::from("/app/share/slimevr/")),
|
||||
Some(PathBuf::from("/usr/share/slimevr/")),
|
||||
];
|
||||
|
||||
paths
|
||||
.into_iter()
|
||||
.filter_map(|x| x)
|
||||
.find(|x| is_valid_path(x))
|
||||
}
|
||||
|
||||
pub fn spawn_java(java: &OsStr, java_version: &OsStr) -> std::io::Result<Child> {
|
||||
let mut cmd = std::process::Command::new(java);
|
||||
|
||||
#[cfg(windows)]
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
|
||||
cmd.arg("-jar")
|
||||
.arg(java_version)
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.spawn()
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub fn show_error(text: &str) -> bool {
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use tauri::api::dialog::{
|
||||
blocking::MessageDialogBuilder, MessageDialogButtons, MessageDialogKind,
|
||||
};
|
||||
|
||||
MessageDialogBuilder::new(
|
||||
format!(
|
||||
"SlimeVR GUI crashed - {}",
|
||||
POSSIBLE_TITLES.choose(&mut thread_rng()).unwrap()
|
||||
),
|
||||
text,
|
||||
)
|
||||
.buttons(MessageDialogButtons::Ok)
|
||||
.kind(MessageDialogKind::Error)
|
||||
.show()
|
||||
}
|
||||
|
||||
#[cfg(mobile)]
|
||||
pub fn show_error(text: &str) -> bool {
|
||||
// needs to do native stuff on mobile
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Check if WebView2 exists
|
||||
pub fn webview2_exists() -> bool {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
// First on the machine itself
|
||||
let machine: Option<String> = RegKey::predef(HKEY_LOCAL_MACHINE)
|
||||
.open_subkey(r"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}")
|
||||
.map(|r| r.get_value("pv").ok()).ok().flatten();
|
||||
let mut exists = false;
|
||||
if let Some(version) = machine {
|
||||
exists = version.split('.').any(|x| x != "0");
|
||||
}
|
||||
// Then in the current user
|
||||
if !exists {
|
||||
let user: Option<String> = RegKey::predef(HKEY_CURRENT_USER)
|
||||
.open_subkey(
|
||||
r"Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||||
)
|
||||
.map(|r| r.get_value("pv").ok())
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some(version) = user {
|
||||
exists = version.split('.').any(|x| x != "0");
|
||||
}
|
||||
}
|
||||
exists
|
||||
}
|
||||
|
||||
pub fn valid_java_paths() -> Vec<(OsString, i32)> {
|
||||
let mut file = Builder::new()
|
||||
.suffix(".jar")
|
||||
.tempfile()
|
||||
.expect("Couldn't generate .jar file");
|
||||
file.write_all(include_bytes!("JavaVersion.jar"))
|
||||
.expect("Couldn't write to .jar file");
|
||||
let java_version = file.into_temp_path();
|
||||
|
||||
// Check if main Java is a supported version
|
||||
let main_java = if let Ok(java_home) = std::env::var("JAVA_HOME") {
|
||||
PathBuf::from(java_home)
|
||||
.join("bin")
|
||||
.join(JAVA_BIN)
|
||||
.into_os_string()
|
||||
} else {
|
||||
JAVA_BIN.into()
|
||||
};
|
||||
if let Some(main_child) = spawn_java(&main_java, java_version.as_os_str())
|
||||
.expect("Couldn't spawn the main Java binary")
|
||||
.wait()
|
||||
.expect("Couldn't execute the main Java binary")
|
||||
.code()
|
||||
{
|
||||
if main_child >= MINIMUM_JAVA_VERSION {
|
||||
return vec![(main_java, main_child)];
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise check if anything else is a supported version
|
||||
let mut childs = vec![];
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
// macOS JVMs are saved on multiple possible places,
|
||||
// /Library/Java/JavaVirtualMachines are the ones installed by an admin
|
||||
// /Users/$USER/Library/Java/JavaVirtualMachines are the ones installed locally by the user
|
||||
let libs = glob::glob(concatcp!("/Library/Java/JavaVirtualMachines/*/Contents/Home/bin/", JAVA_BIN))
|
||||
.unwrap()
|
||||
.filter_map(|res| res.ok());
|
||||
} else if #[cfg(unix)] {
|
||||
// Linux JVMs are saved on /usr/lib/jvm from what I found out,
|
||||
// there is usually a default dir and a default-runtime dir also which are linked
|
||||
// to the current default runtime and the current default JDK (I think it's JDK)
|
||||
let libs = glob::glob(concatcp!("/usr/lib/jvm/*/bin/", JAVA_BIN))
|
||||
.unwrap()
|
||||
.filter_map(|res| res.ok());
|
||||
} else {
|
||||
let libs = which::which_all(JAVA_BIN).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for java in libs {
|
||||
let res = spawn_java(java.as_os_str(), java_version.as_os_str());
|
||||
|
||||
match res {
|
||||
Ok(child) => childs.push((java.into_os_string(), child)),
|
||||
Err(e) => println!("Error on trying to spawn a Java executable: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
childs
|
||||
.into_iter()
|
||||
.filter_map(|(p, mut c)| {
|
||||
c.wait()
|
||||
.expect("Failed on executing a Java executable")
|
||||
.code()
|
||||
.map(|code| (p, code))
|
||||
.filter(|(_p, code)| *code >= MINIMUM_JAVA_VERSION)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -168,3 +168,5 @@ configure<com.diffplug.gradle.spotless.SpotlessExtension> {
|
||||
eclipse().configFile("spotless.xml")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByName("run", JavaExec::class) { standardInput = System.`in` }
|
||||
|
||||
@@ -15,12 +15,14 @@ import java.io.IOException
|
||||
import java.lang.System
|
||||
import java.net.ServerSocket
|
||||
import javax.swing.JOptionPane
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
val VERSION =
|
||||
(GIT_VERSION_TAG.ifEmpty { GIT_COMMIT_HASH }) +
|
||||
if (GIT_CLEAN) "" else "-dirty"
|
||||
var vrServer: VRServer? = null
|
||||
lateinit var vrServer: VRServer
|
||||
private set
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
System.setProperty("awt.useSystemAAFontSettings", "on")
|
||||
@@ -63,6 +65,7 @@ fun main(args: Array<String>) {
|
||||
"SlimeVR: Java Runtime Mismatch",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
LogManager.closeLogger()
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -84,27 +87,27 @@ fun main(args: Array<String>) {
|
||||
"SlimeVR: Ports are busy",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
LogManager.closeLogger()
|
||||
return
|
||||
}
|
||||
try {
|
||||
vrServer = VRServer()
|
||||
vrServer!!.start()
|
||||
vrServer.start()
|
||||
Keybinding(vrServer)
|
||||
val scanner = thread {
|
||||
while (true) {
|
||||
if (readln() == "exit") {
|
||||
vrServer.interrupt()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
vrServer.join()
|
||||
scanner.join()
|
||||
LogManager.closeLogger()
|
||||
exitProcess(0)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
try {
|
||||
Thread.sleep(2000L)
|
||||
} catch (e2: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
exitProcess(1) // Exit in case error happened on init and window
|
||||
// not appeared, but some thread
|
||||
// started
|
||||
} finally {
|
||||
try {
|
||||
Thread.sleep(2000L)
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,10 @@ public class VRServer extends Thread {
|
||||
// final long time = System.currentTimeMillis() - start;
|
||||
try {
|
||||
Thread.sleep(1); // 1000Hz
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException error) {
|
||||
LogManager.info("VRServer thread interrupted");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,13 @@ public class LogManager {
|
||||
log.log(level, message, t);
|
||||
}
|
||||
|
||||
public static void closeLogger() {
|
||||
for (Handler handler : global.getHandlers()) {
|
||||
handler.close();
|
||||
removeHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
boolean hasConsoleHandler = false;
|
||||
for (Handler h : global.getHandlers()) {
|
||||
|
||||
Reference in New Issue
Block a user