diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..fcadb2cf9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/Cargo.lock b/Cargo.lock index 3a2916ac5..a15ad7a84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -407,6 +407,16 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap-verbosity-flag" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e437250f23cdd03daf3de763093aa27873a026ef9a156800da9ddd617c51d4" +dependencies = [ + "clap", + "log", +] + [[package]] name = "clap_derive" version = "3.1.7" @@ -462,9 +472,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.3" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" dependencies = [ "bytes", "memchr", @@ -2849,7 +2859,7 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" name = "slimevr_ui" version = "0.1.0" dependencies = [ - "clap", + "clap-verbosity-flag", "log", "pretty_env_logger", "serde", @@ -3116,6 +3126,7 @@ dependencies = [ "attohttpc", "bincode", "cfg_aliases", + "clap", "dirs-next", "either", "embed_plist", @@ -3354,9 +3365,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] diff --git a/Cargo.toml b/Cargo.toml index 98baea7aa..732102494 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,10 @@ [workspace] resolver = "2" members = ["src-tauri"] + + +# This lets us get the webview dev tools even in release mode, so that we can see the +# console. Just hit F12. +[profile.release.package.wry] +debug = true +debug-assertions = true diff --git a/package-lock.json b/package-lock.json index 4431b63ba..d02389316 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "resolve-url-loader": "^4.0.0", "sass-loader": "^12.3.0", "semver": "^7.3.5", - "solarxr-protocol": "file:../SlimeVR-Server/solarxr-protocol", + "solarxr-protocol": "SlimeVR/SolarXR-Protocol", "source-map-loader": "^3.0.0", "style-loader": "^3.3.1", "tailwindcss": "^3.0.2", @@ -89,8 +89,23 @@ "tailwindcss": "^3.0.23" } }, + "../protocol": { + "name": "solarxr-protocol", + "version": "1.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "flatbuffers": "^2.0.6", + "slimevr-protocol": "file:" + }, + "devDependencies": { + "@mgit-at/typescript-flatbuffers-codegen": "^0.1.3", + "typescript": "^4.6.3" + } + }, "../SlimeVR-Server/solarxr-protocol": { "version": "1.0.0", + "extraneous": true, "license": "ISC", "dependencies": { "flatbuffers": "^2.0.6", @@ -13814,8 +13829,13 @@ } }, "node_modules/solarxr-protocol": { - "resolved": "../SlimeVR-Server/solarxr-protocol", - "link": true + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/SlimeVR/SolarXR-Protocol.git#88d9f7060cdf4c28210fa0138359adf5b768e9fc", + "license": "ISC", + "dependencies": { + "flatbuffers": "^2.0.6", + "solarxr-protocol": "github:SlimeVR/SolarXR-Protocol#fix-import-from-npm" + } }, "node_modules/source-list-map": { "version": "2.0.1", @@ -23963,12 +23983,11 @@ } }, "solarxr-protocol": { - "version": "file:../SlimeVR-Server/solarxr-protocol", + "version": "git+ssh://git@github.com/SlimeVR/SolarXR-Protocol.git#88d9f7060cdf4c28210fa0138359adf5b768e9fc", + "from": "solarxr-protocol@SlimeVR/SolarXR-Protocol", "requires": { - "@mgit-at/typescript-flatbuffers-codegen": "^0.1.3", "flatbuffers": "^2.0.6", - "slimevr-protocol": "file:", - "typescript": "^4.6.3" + "solarxr-protocol": "github:SlimeVR/SolarXR-Protocol#fix-import-from-npm" } }, "source-list-map": { diff --git a/package.json b/package.json index 9c85d478b..7bbb5b694 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "resolve-url-loader": "^4.0.0", "sass-loader": "^12.3.0", "semver": "^7.3.5", - "solarxr-protocol": "file:../SlimeVR-Server/solarxr-protocol", + "solarxr-protocol": "SlimeVR/SolarXR-Protocol", "source-map-loader": "^3.0.0", "style-loader": "^3.3.1", "tailwindcss": "^3.0.2", diff --git a/public/favicon.ico b/public/favicon.ico index a11777cc4..26c8da020 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/logo192.png b/public/logo192.png index fc44b0a37..dba08afb2 100644 Binary files a/public/logo192.png and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png index a4e47a654..5fc7ab551 100644 Binary files a/public/logo512.png and b/public/logo512.png differ diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore index 2e36dd877..c12370459 100644 --- a/src-tauri/.gitignore +++ b/src-tauri/.gitignore @@ -1,4 +1,4 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -WixTools +# Generated by Cargo +# will have compiled files and executables +/target/ +WixTools diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a8ffd5625..203b5099d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -10,17 +10,6 @@ rust-version = "1.59" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[build-dependencies] -tauri-build = { version = "1.0.0-rc.5", features = [] } - -[dependencies] -serde_json = "1" -serde = { version = "1", features = ["derive"] } -tauri = { version = "1.0.0-rc.6", features = ["api-all"] } -pretty_env_logger = "0.4" -log = "0.4" -clap = { version = "3", features = ["derive"] } - [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 @@ -29,5 +18,16 @@ default = ["custom-protocol"] # DO NOT remove this custom-protocol = ["tauri/custom-protocol"] +[build-dependencies] +tauri-build = { version = "1.0.0-rc.5", features = [] } + +[dependencies] +serde_json = "1" +serde = { version = "1", features = ["derive"] } +tauri = { version = "1.0.0-rc.6", features = ["api-all", "cli"] } +pretty_env_logger = "0.4" +log = "0.4" +clap-verbosity-flag = "1" + [target.'cfg(windows)'.dependencies] win32job = "1" diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 2ba80a8bf..d860e1e6a 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ -fn main() { - tauri_build::build() -} +fn main() { + tauri_build::build() +} diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index a68d9acc7..685a8c082 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3636fad25..cce40154d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,60 +1,89 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -use clap::Parser; -use std::path::PathBuf; -use std::process::{Command}; - -// the payload type must implement `Serialize` and `Clone`. - -#[derive(Parser)] -#[clap(version, about)] -struct Args { - #[clap(short, long)] - display_console: bool, -} - -fn main() { - // Set up loggers and global handlers - { - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "info") - } - pretty_env_logger::init(); - } - - let args = Args::parse(); - - // Ensure child processes die when spawned on windows - #[cfg(target_os = "windows")] - { - use win32job::{ExtendedLimitInfo, Job}; - - let mut info = ExtendedLimitInfo::new(); - info.limit_kill_on_job_close(); - let job = Job::create_with_limit_info(&mut info).expect("Failed to create Job"); - job.assign_current_process() - .expect("Failed to assign current process to Job"); - - // We don't do anything with the job anymore, but we shouldn't drop it because that would - // terminate our process tree. So we intentionally leak it instead. - std::mem::forget(job); - } - - // Spawn server process - let runfile_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("run.bat"); - if runfile_path.exists() { - Command::new("cmd") - .args(["/C", runfile_path.to_str().unwrap()]) - .spawn() - .expect("sh command failed to start"); - } else { - log::warn!("No run.bat found, SKIP"); - } - - tauri::Builder::default() - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use clap::Parser; +use clap_verbosity_flag::{InfoLevel, Verbosity}; +use std::path::PathBuf; +use tauri::api::clap; +use tauri::api::process::Command; +use tauri::Manager; + +#[derive(Parser)] +#[clap(version, about)] +struct Cli { + #[clap(short, long)] + display_console: bool, + #[clap(long)] + launch_from_path: Option, + #[clap(flatten)] + verbosity: Verbosity, +} + +fn main() { + let cli = Cli::parse(); + + // Set up loggers and global handlers + { + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "info") + } + pretty_env_logger::init(); + } + + // Ensure child processes die when spawned on windows + #[cfg(target_os = "windows")] + { + use win32job::{ExtendedLimitInfo, Job}; + + let mut info = ExtendedLimitInfo::new(); + info.limit_kill_on_job_close(); + let job = Job::create_with_limit_info(&mut info).expect("Failed to create Job"); + job.assign_current_process() + .expect("Failed to assign current process to Job"); + + // We don't do anything with the job anymore, but we shouldn't drop it because that would + // terminate our process tree. So we intentionally leak it instead. + std::mem::forget(job); + } + + // Spawn server process + let runfile_path = cli + .launch_from_path + .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("run.bat")); + let stdout_recv = if !runfile_path.exists() { + log::warn!("runfile doesn't exist. We will not start the server."); + None + } else { + let (recv, _child) = Command::new(runfile_path.to_str().unwrap()) + .spawn() + .expect("sh command failed to start"); + Some(recv) + }; + + tauri::Builder::default() + .setup(|app| { + if let Some(mut recv) = stdout_recv { + let app_handle = app.app_handle(); + tauri::async_runtime::spawn(async move { + use tauri::api::process::CommandEvent; + + while let Some(cmd_event) = recv.recv().await { + let emit_me = match cmd_event { + CommandEvent::Stderr(s) => ("stderr", s), + CommandEvent::Stdout(s) => ("stdout", s), + CommandEvent::Error(s) => ("error", s), + _ => continue, + }; + app_handle + .emit_all("server-stdio", emit_me) + .expect("Failed to emit"); + } + }); + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9ce4a450c..5b5a4c6dc 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -60,6 +60,7 @@ ], "security": { "csp": null - } + }, + "cli": {} } } diff --git a/src/App.tsx b/src/App.tsx index 0bd1ef093..9bac92138 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,9 @@ import { Settings } from './components/settings/Settings'; import { MainLayoutRoute } from './components/MainLayout'; import { SettingsLayoutRoute } from './components/settings/SettingsLayout'; +import { emit, listen } from '@tauri-apps/api/event' +import type { Event } from '@tauri-apps/api/event' + function Layout() { const { sendDataFeedPacket } = useWebsocketAPI(); @@ -33,7 +36,7 @@ function Layout() { config.minimumTimeSinceLast = 100; config.syntheticTrackersMask = trackerData - const startDataFeed = new StartDataFeedT() + const startDataFeed = new StartDataFeedT() startDataFeed.dataFeeds = [config] sendDataFeedPacket(DataFeedMessage.StartDataFeed, startDataFeed) }, []) @@ -64,6 +67,20 @@ function Layout() { function App() { const websocketAPI = useProvideWebsocketApi(); + + listen("server-stdio", (event: Event<[string, string]>) => { + let [event_type, s] = event.payload; + if ("stderr" == event_type) { + // This strange invocation is what lets us lose the line information in the console + // See more here: https://stackoverflow.com/a/48994308 + setTimeout(console.error.bind(console, s)); + } else if ("stdout" == event_type) { + setTimeout(console.log.bind(console, s)); + } else if ("error" == event_type) { + console.error("Error: %s", s) + } + }); + return ( diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index e080531da..624c875ec 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -68,4 +68,4 @@ declare module '*.module.scss' { declare module '*.module.sass' { const classes: { readonly [key: string]: string }; export default classes; -} +} diff --git a/tsconfig.json b/tsconfig.json index 16fff78a0..a273b0cfc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,26 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": [ - "src" - ] -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +}