Compare commits
13 Commits
393f8b5fab
...
main
Author | SHA1 | Date | |
---|---|---|---|
ffb05e3a0a
|
|||
8fda4f79ed
|
|||
f16d8b1dca
|
|||
7566c2908f
|
|||
89ec65f0d2
|
|||
bad6baa9de
|
|||
4369090a53
|
|||
1ef5e4d01a
|
|||
572beb66f7
|
|||
366b6fbca7
|
|||
d80dece8a7
|
|||
d69cf115c8
|
|||
78f7ff2405
|
1712
Cargo.lock
generated
1712
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -39,6 +39,12 @@ tracing = "0.1"
|
|||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
uuid = { version = "1", features = ["v7"] }
|
uuid = { version = "1", features = ["v7"] }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
windows = { version = "0.61", features = [
|
||||||
|
"Win32",
|
||||||
|
"Win32_UI",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
@ -11,8 +11,11 @@ Simple server and GUI for LAN parties.
|
|||||||
```bash
|
```bash
|
||||||
# install Tauri CLI
|
# install Tauri CLI
|
||||||
cargo install tauri-cli
|
cargo install tauri-cli
|
||||||
|
|
||||||
|
# install Deno with a package manager or from https://deno.land/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
#### Frontend
|
#### Frontend
|
||||||
```bash
|
```bash
|
||||||
@ -23,7 +26,7 @@ cargo tauri dev # prefix with RUST_LOG=your_module=debug or similary for more
|
|||||||
cargo tauri build --no-bundle
|
cargo tauri build --no-bundle
|
||||||
|
|
||||||
# Production
|
# Production
|
||||||
cargo tauri build --profile release-lto # also bundles everything into a nice platform-specific installer
|
cargo tauri build -- --profile release-lto # also bundles everything into a nice platform-specific installer
|
||||||
|
|
||||||
# on wayland you probably need to set this env var
|
# on wayland you probably need to set this env var
|
||||||
WEBKIT_DISABLE_DMABUF_RENDERER=1
|
WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||||
@ -38,5 +41,5 @@ deno outdated --update --latest
|
|||||||
./server.sh [options...] # prefix with RUST_LOG=your_module=debug or similary for more verbose output
|
./server.sh [options...] # prefix with RUST_LOG=your_module=debug or similary for more verbose output
|
||||||
|
|
||||||
# Production
|
# Production
|
||||||
cargo build --profile release-lto
|
cargo build --profile release-lto -p lanspread-server
|
||||||
```
|
```
|
||||||
|
@ -10,7 +10,7 @@ use s2n_quic::{Client as QuicClient, Connection, client::Connect, provider::limi
|
|||||||
use tokio::{
|
use tokio::{
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
sync::{
|
sync::{
|
||||||
Mutex,
|
RwLock,
|
||||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
mpsc::{UnboundedReceiver, UnboundedSender},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -162,7 +162,7 @@ async fn download_game_files(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Ctx {
|
struct Ctx {
|
||||||
game_dir: Arc<Mutex<Option<String>>>,
|
game_dir: Arc<RwLock<Option<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
@ -181,7 +181,7 @@ pub async fn run(
|
|||||||
|
|
||||||
// client context
|
// client context
|
||||||
let ctx = Ctx {
|
let ctx = Ctx {
|
||||||
game_dir: Arc::new(Mutex::new(None)),
|
game_dir: Arc::new(RwLock::new(None)),
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -230,7 +230,7 @@ pub async fn run(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ClientCommand::SetGameDir(game_dir) => {
|
ClientCommand::SetGameDir(game_dir) => {
|
||||||
*ctx.game_dir.lock().await = Some(game_dir.clone());
|
*ctx.game_dir.write().await = Some(game_dir.clone());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ClientCommand::DownloadGameFiles {
|
ClientCommand::DownloadGameFiles {
|
||||||
@ -239,7 +239,7 @@ pub async fn run(
|
|||||||
} => {
|
} => {
|
||||||
log::info!("got ClientCommand::DownloadGameFiles");
|
log::info!("got ClientCommand::DownloadGameFiles");
|
||||||
|
|
||||||
let games_folder = { ctx.game_dir.lock().await.clone() };
|
let games_folder = { ctx.game_dir.read().await.clone() };
|
||||||
if let Some(games_folder) = games_folder {
|
if let Some(games_folder) = games_folder {
|
||||||
let tx_notify_ui = tx_notify_ui.clone();
|
let tx_notify_ui = tx_notify_ui.clone();
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
@ -312,7 +312,7 @@ pub async fn run(
|
|||||||
let (mut rx, mut tx) = stream.split();
|
let (mut rx, mut tx) = stream.split();
|
||||||
|
|
||||||
if let Err(e) = tx.send(data).await {
|
if let Err(e) = tx.send(data).await {
|
||||||
log::error!("failed to send request to server {:?}", e);
|
log::error!("failed to send request to server {e:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = BytesMut::new();
|
let mut data = BytesMut::new();
|
||||||
@ -344,7 +344,7 @@ pub async fn run(
|
|||||||
file_descriptions.len()
|
file_descriptions.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
let games_folder = { ctx.game_dir.lock().await.clone() };
|
let games_folder = { ctx.game_dir.read().await.clone() };
|
||||||
|
|
||||||
match games_folder {
|
match games_folder {
|
||||||
Some(games_folder) => {
|
Some(games_folder) => {
|
||||||
|
@ -11,10 +11,7 @@ use gethostname::gethostname;
|
|||||||
use lanspread_compat::eti;
|
use lanspread_compat::eti;
|
||||||
use lanspread_db::db::{Game, GameDB};
|
use lanspread_db::db::{Game, GameDB};
|
||||||
use lanspread_mdns::{
|
use lanspread_mdns::{
|
||||||
DaemonEvent,
|
DaemonEvent, LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, MdnsAdvertiser,
|
||||||
LANSPREAD_INSTANCE_NAME,
|
|
||||||
LANSPREAD_SERVICE_TYPE,
|
|
||||||
MdnsAdvertiser,
|
|
||||||
};
|
};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -53,11 +50,10 @@ fn spawn_mdns_task(server_addr: SocketAddr) -> eyre::Result<()> {
|
|||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Ok(event) = mdns.monitor.recv() {
|
while let Ok(event) = mdns.monitor.recv() {
|
||||||
tracing::debug!("mDNS: {:?}", &event);
|
tracing::trace!("mDNS: {:?}", &event);
|
||||||
if let DaemonEvent::Error(e) = event {
|
if let DaemonEvent::Error(e) = event {
|
||||||
tracing::error!("mDNS: {e}");
|
tracing::error!("mDNS: {e}");
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -171,10 +171,5 @@ fn get_relative_path(base: &Path, deep_path: &Path) -> std::io::Result<PathBuf>
|
|||||||
full_canonical
|
full_canonical
|
||||||
.strip_prefix(&base_canonical)
|
.strip_prefix(&base_canonical)
|
||||||
.map(std::path::Path::to_path_buf)
|
.map(std::path::Path::to_path_buf)
|
||||||
.map_err(|_| {
|
.map_err(|_| std::io::Error::other("Path is not within base directory"))
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
"Path is not within base directory",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -34,3 +34,6 @@ tauri-plugin-shell = { workspace = true }
|
|||||||
tauri-plugin-dialog = { workspace = true }
|
tauri-plugin-dialog = { workspace = true }
|
||||||
tauri-plugin-store = { workspace = true }
|
tauri-plugin-store = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
windows = { workspace = true }
|
||||||
|
BIN
crates/lanspread-tauri-deno-ts/src-tauri/binaries/unrar-aarch64-apple-darwin
Executable file
BIN
crates/lanspread-tauri-deno-ts/src-tauri/binaries/unrar-aarch64-apple-darwin
Executable file
Binary file not shown.
@ -1,6 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs::File,
|
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@ -12,16 +11,16 @@ use lanspread_db::db::{Game, GameDB};
|
|||||||
use lanspread_mdns::{LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, discover_service};
|
use lanspread_mdns::{LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, discover_service};
|
||||||
use tauri::{AppHandle, Emitter as _, Manager};
|
use tauri::{AppHandle, Emitter as _, Manager};
|
||||||
use tauri_plugin_shell::{ShellExt, process::Command};
|
use tauri_plugin_shell::{ShellExt, process::Command};
|
||||||
use tokio::sync::{Mutex, mpsc::UnboundedSender};
|
use tokio::sync::{Mutex, RwLock, mpsc::UnboundedSender};
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
|
|
||||||
struct LanSpreadState {
|
struct LanSpreadState {
|
||||||
server_addr: Mutex<Option<SocketAddr>>,
|
server_addr: RwLock<Option<SocketAddr>>,
|
||||||
client_ctrl: UnboundedSender<ClientCommand>,
|
client_ctrl: UnboundedSender<ClientCommand>,
|
||||||
games: Arc<Mutex<GameDB>>,
|
games: Arc<RwLock<GameDB>>,
|
||||||
games_in_download: Arc<Mutex<HashSet<String>>>,
|
games_in_download: Arc<Mutex<HashSet<String>>>,
|
||||||
games_folder: Arc<Mutex<String>>,
|
games_folder: Arc<RwLock<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -54,14 +53,39 @@ fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[cfg(target_os = "windows")]
|
||||||
fn run_game(id: String, state: tauri::State<LanSpreadState>) {
|
fn run_as_admin(file: &str, params: &str, dir: &str) -> bool {
|
||||||
|
use std::{ffi::OsStr, os::windows::ffi::OsStrExt};
|
||||||
|
|
||||||
|
use windows::{Win32::UI::Shell::ShellExecuteW, core::PCWSTR};
|
||||||
|
|
||||||
|
let file_wide: Vec<u16> = OsStr::new(file).encode_wide().chain(Some(0)).collect();
|
||||||
|
let params_wide: Vec<u16> = OsStr::new(params).encode_wide().chain(Some(0)).collect();
|
||||||
|
let dir_wide: Vec<u16> = OsStr::new(dir).encode_wide().chain(Some(0)).collect();
|
||||||
|
let runas_wide: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect();
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
ShellExecuteW(
|
||||||
|
None,
|
||||||
|
PCWSTR::from_raw(runas_wide.as_ptr()),
|
||||||
|
PCWSTR::from_raw(file_wide.as_ptr()),
|
||||||
|
PCWSTR::from_raw(params_wide.as_ptr()),
|
||||||
|
PCWSTR::from_raw(dir_wide.as_ptr()),
|
||||||
|
windows::Win32::UI::WindowsAndMessaging::SW_HIDE,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
(result.0 as usize) > 32 // Success if greater than 32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn run_game_windows(id: String, state: tauri::State<LanSpreadState>) {
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
const FIRST_START_DONE_FILE: &str = ".softlan_first_start_done";
|
const FIRST_START_DONE_FILE: &str = ".softlan_first_start_done";
|
||||||
|
|
||||||
log::error!("run_game {id}");
|
|
||||||
|
|
||||||
let games_folder =
|
let games_folder =
|
||||||
tauri::async_runtime::block_on(async { state.inner().games_folder.lock().await.clone() });
|
tauri::async_runtime::block_on(async { state.inner().games_folder.read().await.clone() });
|
||||||
|
|
||||||
let games_folder = PathBuf::from(games_folder);
|
let games_folder = PathBuf::from(games_folder);
|
||||||
if !games_folder.exists() {
|
if !games_folder.exists() {
|
||||||
@ -69,34 +93,62 @@ fn run_game(id: String, state: tauri::State<LanSpreadState>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let game_path = games_folder.join(id);
|
let game_path = games_folder.join(id.clone());
|
||||||
|
|
||||||
let game_setup_bin = game_path.join("game_setup.cmd");
|
let game_setup_bin = game_path.join("game_setup.cmd");
|
||||||
let game_start_bin = game_path.join("game_start.cmd");
|
let game_start_bin = game_path.join("game_start.cmd");
|
||||||
|
|
||||||
let first_start_done_file = game_path.join(FIRST_START_DONE_FILE);
|
let first_start_done_file = game_path.join(FIRST_START_DONE_FILE);
|
||||||
if !first_start_done_file.exists() && game_setup_bin.exists() {
|
if !first_start_done_file.exists() && game_setup_bin.exists() {
|
||||||
if let Err(e) = std::process::Command::new(game_setup_bin)
|
let result = run_as_admin(
|
||||||
.current_dir(&game_path)
|
"cmd.exe",
|
||||||
.spawn()
|
&format!(
|
||||||
{
|
r#"/c "{} local {} de playername""#,
|
||||||
log::error!("failed to run game_setup.cmd: {e}");
|
game_setup_bin.display(),
|
||||||
|
&id
|
||||||
|
),
|
||||||
|
&game_path.display().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !result {
|
||||||
|
log::error!("failed to run game_setup.cmd");
|
||||||
return;
|
return;
|
||||||
} else if let Err(e) = File::create(FIRST_START_DONE_FILE) {
|
}
|
||||||
|
|
||||||
|
if let Err(e) = File::create(&first_start_done_file) {
|
||||||
log::error!("failed to create {first_start_done_file:?}: {e}");
|
log::error!("failed to create {first_start_done_file:?}: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if game_start_bin.exists() {
|
if game_start_bin.exists() {
|
||||||
if let Err(e) = std::process::Command::new(game_start_bin)
|
let result = run_as_admin(
|
||||||
.current_dir(&game_path)
|
"cmd.exe",
|
||||||
.spawn()
|
&format!(
|
||||||
{
|
r#"/c "{} local {} de playername""#,
|
||||||
log::error!("failed to run game_start.cmd: {e}");
|
game_start_bin.display(),
|
||||||
|
&id
|
||||||
|
),
|
||||||
|
&game_path.display().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !result {
|
||||||
|
log::error!("failed to run game_start.cmd");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn run_game(id: String, state: tauri::State<LanSpreadState>) {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
run_game_windows(id, state);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
let _ = state;
|
||||||
|
log::error!("run_game not implemented for this platform: id={id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) {
|
fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) {
|
||||||
if let Some(file_name) = path.file_name() {
|
if let Some(file_name) = path.file_name() {
|
||||||
if let Some(file_name) = file_name.to_str() {
|
if let Some(file_name) = file_name.to_str() {
|
||||||
@ -128,7 +180,7 @@ fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
|
|||||||
.state::<LanSpreadState>()
|
.state::<LanSpreadState>()
|
||||||
.inner()
|
.inner()
|
||||||
.games_folder
|
.games_folder
|
||||||
.lock()
|
.write()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
*games_folder = path.clone();
|
*games_folder = path.clone();
|
||||||
@ -153,7 +205,7 @@ fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
|
|||||||
.state::<LanSpreadState>()
|
.state::<LanSpreadState>()
|
||||||
.inner()
|
.inner()
|
||||||
.games
|
.games
|
||||||
.lock()
|
.write()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Reset all games to uninstalled
|
// Reset all games to uninstalled
|
||||||
@ -187,11 +239,7 @@ async fn find_server(app: AppHandle) {
|
|||||||
Ok(server_addr) => {
|
Ok(server_addr) => {
|
||||||
log::info!("Found server at {server_addr}");
|
log::info!("Found server at {server_addr}");
|
||||||
let state: tauri::State<LanSpreadState> = app.state();
|
let state: tauri::State<LanSpreadState> = app.state();
|
||||||
{
|
*state.server_addr.write().await = Some(server_addr);
|
||||||
// mutex scope
|
|
||||||
let mut addr = state.server_addr.lock().await;
|
|
||||||
*addr = Some(server_addr);
|
|
||||||
}
|
|
||||||
state
|
state
|
||||||
.client_ctrl
|
.client_ctrl
|
||||||
.send(ClientCommand::ServerAddr(server_addr))
|
.send(ClientCommand::ServerAddr(server_addr))
|
||||||
@ -214,8 +262,7 @@ async fn update_game_db(games: Vec<Game>, app: AppHandle) {
|
|||||||
let state = app.state::<LanSpreadState>();
|
let state = app.state::<LanSpreadState>();
|
||||||
|
|
||||||
// Store games list
|
// Store games list
|
||||||
let mut state_games = state.games.lock().await;
|
*state.games.write().await = GameDB::from(games.clone());
|
||||||
*state_games = GameDB::from(games.clone());
|
|
||||||
|
|
||||||
// Tell Frontend about new games list
|
// Tell Frontend about new games list
|
||||||
if let Err(e) = app.emit("games-list-updated", Some(games)) {
|
if let Err(e) = app.emit("games-list-updated", Some(games)) {
|
||||||
@ -309,11 +356,11 @@ pub fn run() {
|
|||||||
tokio::sync::mpsc::unbounded_channel::<ClientEvent>();
|
tokio::sync::mpsc::unbounded_channel::<ClientEvent>();
|
||||||
|
|
||||||
let lanspread_state = LanSpreadState {
|
let lanspread_state = LanSpreadState {
|
||||||
server_addr: Mutex::new(None),
|
server_addr: RwLock::new(None),
|
||||||
client_ctrl: tx_client_control,
|
client_ctrl: tx_client_control,
|
||||||
games: Arc::new(Mutex::new(GameDB::empty())),
|
games: Arc::new(RwLock::new(GameDB::empty())),
|
||||||
games_in_download: Arc::new(Mutex::new(HashSet::new())),
|
games_in_download: Arc::new(Mutex::new(HashSet::new())),
|
||||||
games_folder: Arc::new(Mutex::new("".to_string())),
|
games_folder: Arc::new(RwLock::new("".to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@ -399,7 +446,7 @@ pub fn run() {
|
|||||||
.state::<LanSpreadState>()
|
.state::<LanSpreadState>()
|
||||||
.inner()
|
.inner()
|
||||||
.games_folder
|
.games_folder
|
||||||
.lock()
|
.read()
|
||||||
.await
|
.await
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user