Skip to content

Commit ce20952

Browse files
committed
show connecting
1 parent d1f3e71 commit ce20952

9 files changed

Lines changed: 210 additions & 7 deletions

File tree

src-tauri/src/byond.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::presence::{ConnectionParams, PresenceManager};
1212
use std::process::Command;
1313
#[cfg(target_os = "windows")]
1414
use std::sync::Arc;
15+
#[cfg(target_os = "windows")]
16+
use tauri::Emitter;
1517
#[derive(Debug, Serialize, Deserialize)]
1618
pub struct ByondVersionInfo {
1719
pub version: String,
@@ -202,10 +204,13 @@ pub async fn connect_to_server(
202204

203205
#[cfg(target_os = "windows")]
204206
{
205-
// Get the control server port to include in the connection URL
206-
let control_port = app
207-
.try_state::<ControlServer>()
208-
.map(|s| s.port.to_string());
207+
if let Some(control_server) = app.try_state::<ControlServer>() {
208+
control_server.reset_connected_flag();
209+
}
210+
211+
app.emit("game-connecting", &server_name).ok();
212+
213+
let control_port = app.try_state::<ControlServer>().map(|s| s.port.to_string());
209214

210215
// Build query parameters
211216
let mut query_params = Vec::new();
@@ -260,7 +265,14 @@ pub async fn connect_to_server(
260265
#[cfg(not(target_os = "windows"))]
261266
{
262267
// Suppress unused warnings
263-
let _ = (dreamseeker_path, host, port, server_name, access_type, access_token);
268+
let _ = (
269+
dreamseeker_path,
270+
host,
271+
port,
272+
server_name,
273+
access_type,
274+
access_token,
275+
);
264276
Err("BYOND is only natively supported on Windows".to_string())
265277
}
266278
}

src-tauri/src/control_server.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::sync::atomic::{AtomicBool, Ordering};
12
use std::sync::Arc;
23
use std::thread;
4+
use tauri::Emitter;
35
#[cfg(feature = "steam")]
46
use tauri::Manager;
57
use tiny_http::{Response, Server};
@@ -44,6 +46,9 @@ fn preflight_response() -> Response<std::io::Empty> {
4446

4547
pub struct ControlServer {
4648
pub port: u16,
49+
50+
#[allow(dead_code)]
51+
pub game_connected: Arc<AtomicBool>,
4752
}
4853

4954
impl ControlServer {
@@ -62,17 +67,29 @@ impl ControlServer {
6267

6368
tracing::info!("Control server started on port {}", port);
6469

70+
let game_connected = Arc::new(AtomicBool::new(false));
71+
let game_connected_clone = Arc::clone(&game_connected);
72+
6573
thread::spawn(move || {
66-
Self::run_server(server, app_handle, presence_manager);
74+
Self::run_server(server, app_handle, presence_manager, game_connected_clone);
6775
});
6876

69-
Ok(Self { port })
77+
Ok(Self {
78+
port,
79+
game_connected,
80+
})
81+
}
82+
83+
#[allow(dead_code)]
84+
pub fn reset_connected_flag(&self) {
85+
self.game_connected.store(false, Ordering::SeqCst);
7086
}
7187

7288
fn run_server(
7389
server: Server,
7490
app_handle: tauri::AppHandle,
7591
presence_manager: Arc<PresenceManager>,
92+
game_connected: Arc<AtomicBool>,
7693
) {
7794
for request in server.incoming_requests() {
7895
// Handle CORS preflight requests
@@ -94,6 +111,13 @@ impl ControlServer {
94111

95112
tracing::debug!("Control server received request: {}", url.path());
96113

114+
if !game_connected.swap(true, Ordering::SeqCst) {
115+
tracing::info!("Game connected to control server");
116+
if let Some(session) = presence_manager.get_game_session() {
117+
app_handle.emit("game-connected", &session.server_name).ok();
118+
}
119+
}
120+
97121
match url.path() {
98122
"/restart" => {
99123
Self::handle_restart(request, &app_handle, &presence_manager);

src-tauri/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ fn get_control_server_port(control_server: tauri::State<'_, control_server::Cont
3939
control_server.port
4040
}
4141

42+
#[tauri::command]
43+
fn kill_game(presence_manager: tauri::State<'_, std::sync::Arc<presence::PresenceManager>>) -> bool {
44+
presence_manager.kill_game_process()
45+
}
46+
4247
#[cfg_attr(mobile, tauri::mobile_entry_point)]
4348
pub fn run() {
4449
tracing_subscriber::fmt::init();
@@ -72,6 +77,7 @@ pub fn run() {
7277
get_settings,
7378
set_auth_mode,
7479
get_control_server_port,
80+
kill_game,
7581
]);
7682
}
7783

@@ -92,6 +98,7 @@ pub fn run() {
9298
get_settings,
9399
set_auth_mode,
94100
get_control_server_port,
101+
kill_game,
95102
get_steam_user_info,
96103
get_steam_auth_ticket,
97104
cancel_steam_auth_ticket,

src/App.css

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,47 @@ body {
807807
.settings-button {
808808
padding: 8px 16px;
809809
}
810+
811+
/* Game Connection Modal */
812+
.game-connection-overlay {
813+
position: fixed;
814+
inset: 0;
815+
background: rgba(0, 0, 0, 0.9);
816+
display: flex;
817+
align-items: center;
818+
justify-content: center;
819+
z-index: 950;
820+
}
821+
822+
.game-connection-modal {
823+
display: flex;
824+
flex-direction: column;
825+
align-items: center;
826+
gap: 32px;
827+
padding: 48px 64px;
828+
text-align: center;
829+
}
830+
831+
.game-connection-status {
832+
display: flex;
833+
flex-direction: column;
834+
align-items: center;
835+
gap: 24px;
836+
}
837+
838+
.game-connection-status h2 {
839+
color: var(--primary);
840+
margin: 0;
841+
text-transform: uppercase;
842+
letter-spacing: 2px;
843+
font-size: 20px;
844+
}
845+
846+
.game-connection-spinner {
847+
width: 60px;
848+
height: 60px;
849+
border: 4px solid var(--primary-faint);
850+
border-top-color: var(--primary);
851+
border-radius: 50%;
852+
animation: spin 1s linear infinite;
853+
}

src/App.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
AccountInfo,
77
AuthModal,
88
ErrorNotifications,
9+
GameConnectionModal,
910
RelayDropdown,
1011
ServerItem,
1112
SettingsModal,
@@ -16,6 +17,7 @@ import {
1617
ErrorProvider,
1718
useAuth,
1819
useError,
20+
useGameConnection,
1921
useRelays,
2022
useServers,
2123
useSettings,
@@ -68,6 +70,13 @@ function AppContent() {
6870
closeSettings,
6971
} = useSettings();
7072

73+
const {
74+
gameConnectionState,
75+
connectedServerName,
76+
closeGameConnectionModal,
77+
showGameConnectionModal,
78+
} = useGameConnection();
79+
7180
const [pendingAutoConnect, setPendingAutoConnect] = useState<string | null>(
7281
null,
7382
);
@@ -279,6 +288,12 @@ function AppContent() {
279288
onAuthModeChange={handleAuthModeChange}
280289
onClose={closeSettings}
281290
/>
291+
<GameConnectionModal
292+
visible={showGameConnectionModal}
293+
state={gameConnectionState}
294+
serverName={connectedServerName}
295+
onClose={closeGameConnectionModal}
296+
/>
282297

283298
<div className="launcher">
284299
<Titlebar />
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { invoke } from "@tauri-apps/api/core";
2+
import { useState } from "react";
3+
4+
export type GameConnectionState = "idle" | "connecting" | "connected";
5+
6+
interface GameConnectionModalProps {
7+
visible: boolean;
8+
state: GameConnectionState;
9+
serverName: string | null;
10+
onClose: () => void;
11+
}
12+
13+
export function GameConnectionModal({
14+
visible,
15+
state,
16+
serverName,
17+
onClose,
18+
}: GameConnectionModalProps) {
19+
const [closing, setClosing] = useState(false);
20+
21+
if (!visible) return null;
22+
23+
const handleCloseGame = async () => {
24+
setClosing(true);
25+
try {
26+
await invoke("kill_game");
27+
onClose();
28+
} catch (err) {
29+
console.error("Failed to close game:", err);
30+
} finally {
31+
setClosing(false);
32+
}
33+
};
34+
35+
const statusText =
36+
state === "connecting"
37+
? `Connecting to ${serverName}...`
38+
: `Connected to ${serverName}`;
39+
40+
return (
41+
<div className="game-connection-overlay">
42+
<div className="game-connection-modal">
43+
<div className="game-connection-status">
44+
{state === "connecting" && <div className="game-connection-spinner" />}
45+
<h2>{statusText}</h2>
46+
</div>
47+
<button
48+
type="button"
49+
className="button"
50+
onClick={handleCloseGame}
51+
disabled={closing}
52+
>
53+
{closing ? "Closing..." : "Close Game"}
54+
</button>
55+
</div>
56+
</div>
57+
);
58+
}

src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export { AccountInfo } from "./AccountInfo";
22
export type { AuthModalState } from "./AuthModal";
33
export { AuthModal } from "./AuthModal";
44
export { ErrorNotifications } from "./ErrorNotifications";
5+
export type { GameConnectionState } from "./GameConnectionModal";
6+
export { GameConnectionModal } from "./GameConnectionModal";
57
export { Modal, ModalCloseButton, ModalContent, ModalSpinner } from "./Modal";
68
export { RelayDropdown } from "./RelayDropdown";
79
export { ServerItem } from "./ServerItem";

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { useAuth } from "./useAuth";
22
export { ErrorProvider, useError } from "./useError";
3+
export { useGameConnection } from "./useGameConnection";
34
export { useRelays } from "./useRelays";
45
export { useServers } from "./useServers";
56
export { useSettings } from "./useSettings";

src/hooks/useGameConnection.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { listen } from "@tauri-apps/api/event";
2+
import { useCallback, useEffect, useState } from "react";
3+
import type { GameConnectionState } from "../components";
4+
5+
export function useGameConnection() {
6+
const [gameConnectionState, setGameConnectionState] =
7+
useState<GameConnectionState>("idle");
8+
const [connectedServerName, setConnectedServerName] = useState<string | null>(
9+
null
10+
);
11+
12+
useEffect(() => {
13+
const unlistenConnecting = listen<string>("game-connecting", (event) => {
14+
setGameConnectionState("connecting");
15+
setConnectedServerName(event.payload);
16+
});
17+
18+
const unlistenConnected = listen<string>("game-connected", (event) => {
19+
setGameConnectionState("connected");
20+
setConnectedServerName(event.payload);
21+
});
22+
23+
return () => {
24+
unlistenConnecting.then((unlisten) => unlisten());
25+
unlistenConnected.then((unlisten) => unlisten());
26+
};
27+
}, []);
28+
29+
const closeGameConnectionModal = useCallback(() => {
30+
setGameConnectionState("idle");
31+
setConnectedServerName(null);
32+
}, []);
33+
34+
return {
35+
gameConnectionState,
36+
connectedServerName,
37+
closeGameConnectionModal,
38+
showGameConnectionModal: gameConnectionState !== "idle",
39+
};
40+
}

0 commit comments

Comments
 (0)