Skip to content

Commit a267bbe

Browse files
committed
state improvements and check for running
1 parent 407eece commit a267bbe

File tree

5 files changed

+206
-51
lines changed

5 files changed

+206
-51
lines changed

gui/index.html

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
}
6767

6868
.logo {
69-
width: min(70vw, 600px);
69+
width: min(70vw, 400px);
7070
height: auto;
7171
}
7272

@@ -148,7 +148,7 @@
148148
<div class="spacer"></div>
149149
<div class="game-menu">
150150
<span class="info-label show-offline" style="display: none">Offline mode</span>
151-
<button id="launch-btn" class="btn btn-primary btn-lg requires-available" disabled>Updating...</button>
151+
<button id="launch-btn" class="btn btn-primary btn-lg" disabled>Updating...</button>
152152
<button id="update-btn" class="btn btn-secondary btn-lg" title="Check for updates" disabled>
153153
<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
154154
<path
@@ -177,48 +177,102 @@ <h1>Settings</h1>
177177
<h2>Launch Options</h2>
178178
<input id="launch-options" type="text"></input>
179179
<h2>Install Folder</h2>
180-
<button id="open-folder-btn" class="btn btn-secondary requires-available" disabled>Browse Install Folder</button>
181-
<button id="move-folder-btn" class="btn btn-secondary requires-available" disabled>Move Install Folder</button>
180+
<button id="open-folder-btn" class="btn btn-secondary" disabled>Browse Install Folder</button>
181+
<button id="move-folder-btn" class="btn btn-secondary" disabled>Move Install Folder</button>
182182
</div>
183183
<script>
184-
function refreshAvailable(available) {
185-
if (available && !document.getElementById("update-btn").disabled) {
186-
document.querySelectorAll(".requires-available").forEach(el => el.disabled = false);
187-
} else {
188-
document.querySelectorAll(".requires-available").forEach(el => el.disabled = true);
184+
let launchState = -1;
185+
let errCode = 0;
186+
let errLevel = 0;
187+
let online = true;
188+
let available = true;
189+
190+
function getLaunchBtnText() {
191+
if (launchState === -1) {
192+
return "Updating...";
193+
}
194+
if (launchState === 1) {
195+
return "Launching..."
196+
}
197+
if (launchState === 2) {
198+
return "Running"
189199
}
190-
}
191-
192-
function archiveReady(error) {
193-
const online = error <= 0;
194-
const hasErr = error !== 0;
195-
const errLevel = Math.abs(error);
196-
const available = errLevel <= 1;
197-
document.getElementById("update-btn").disabled = false;
198-
refreshAvailable(available);
199200
if (available) {
200-
document.getElementById("launch-btn").innerText = "Launch";
201+
return "Launch";
201202
} else {
202-
document.getElementById("launch-btn").innerText = "Install failed";
203+
return "Install failed";
203204
}
205+
}
206+
207+
function updateLaunchBtnText() {
208+
document.getElementById("launch-btn").innerText = getLaunchBtnText();
209+
}
210+
211+
function updateLaunchBtnDisabled() {
212+
document.getElementById("launch-btn").disabled = launchState !== 0 || !available;
213+
}
214+
215+
function updateUpdateBtnDisabled() {
216+
document.getElementById("update-btn").disabled = launchState !== 0;
217+
}
218+
219+
function updateOpenFolderBtnDisabled() {
220+
document.getElementById("open-folder-btn").disabled = !available;
221+
}
222+
223+
function updateMoveFolderBtnDisabled() {
224+
document.getElementById("move-folder-btn").disabled = launchState !== 0 || !available;
225+
}
226+
227+
function updateAvailable() {
228+
available = launchState > -1 && errLevel <= 1;
229+
}
230+
231+
function refreshOnline() {
204232
if (online) {
205233
document.querySelector(".show-offline").style.display = "none";
206234
} else {
207235
document.querySelector(".show-offline").style.display = "inline";
208236
}
209237
}
210238

239+
function setErrCode(inErrCode) {
240+
// values
241+
errCode = inErrCode;
242+
errLevel = Math.abs(errCode);
243+
online = errCode <= 0;
244+
updateAvailable();
245+
// effects
246+
updateLaunchBtnText();
247+
updateLaunchBtnDisabled();
248+
updateOpenFolderBtnDisabled();
249+
updateMoveFolderBtnDisabled();
250+
refreshOnline();
251+
}
252+
253+
function setLaunchState(state) {
254+
// values
255+
launchState = state;
256+
updateAvailable();
257+
// effects
258+
updateLaunchBtnText();
259+
updateLaunchBtnDisabled();
260+
updateUpdateBtnDisabled();
261+
updateOpenFolderBtnDisabled();
262+
updateMoveFolderBtnDisabled();
263+
}
264+
265+
function archiveReady(inErrCode) {
266+
setErrCode(inErrCode);
267+
setLaunchState(0);
268+
}
269+
211270
document.getElementById("launch-btn").addEventListener("click", () => {
212271
if (document.getElementById("launch-btn").disabled) {
213272
return;
214273
}
215274
window.pywebview.api.launch_game();
216-
document.getElementById("launch-btn").disabled = true;
217-
document.getElementById("launch-btn").innerText = "Launching...";
218-
setTimeout(() => {
219-
document.getElementById("launch-btn").disabled = false;
220-
document.getElementById("launch-btn").innerText = "Launch";
221-
}, 1000);
275+
setLaunchState(1);
222276
});
223277
document.getElementById("settings-btn").addEventListener("click", () => {
224278
if (document.getElementById("settings-btn").disabled) {
@@ -250,9 +304,7 @@ <h2>Install Folder</h2>
250304
if (document.getElementById("update-btn").disabled) {
251305
return;
252306
}
253-
document.getElementById("update-btn").disabled = true;
254-
document.getElementById("launch-btn").innerText = "Updating...";
255-
refreshAvailable(false);
307+
setLaunchState(-1);
256308
window.pywebview.api.check_for_updates();
257309
});
258310

poetry.lock

Lines changed: 35 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"pyinstaller (>=6.17.0,<7.0.0)",
1717
"pywebview (>=6.1,<7.0) ; sys_platform == \"win32\"",
1818
"pywebview[gtk] (>=6.1,<7.0) ; sys_platform == \"linux\"",
19+
"psutil (>=7.1.3,<8.0.0)",
1920
]
2021

2122

tc2_launcher/gui.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,35 @@
1212
open_install_folder,
1313
set_launch_options,
1414
update_archive,
15+
wait_game_exit,
16+
wait_game_running,
1517
)
1618

1719

20+
def get_window(idx: int = 0):
21+
return webview.windows[idx]
22+
23+
24+
def get_entrypoint():
25+
def exists(path):
26+
path = (Path(__file__).parent / path).resolve()
27+
if path.exists():
28+
return path
29+
30+
queries = ["../gui/index.html", "../Resources/gui/index.html", "./gui/index.html"]
31+
32+
for query in queries:
33+
result = exists(query)
34+
if result:
35+
return result
36+
37+
raise Exception("No index.html found")
38+
39+
1840
class Api:
1941
def launch_game(self):
2042
launch_game()
43+
check_launch_game()
2144

2245
def set_launch_options(self, options):
2346
if options and isinstance(options, str):
@@ -31,11 +54,11 @@ def open_install_folder(self):
3154

3255
def check_for_updates(self):
3356
res = update_archive()
34-
webview.windows[0].evaluate_js(f"archiveReady({res});")
57+
get_window().evaluate_js(f"archiveReady({res});")
3558

3659
def move_install_folder(self):
3760
try:
38-
result = webview.windows[0].create_file_dialog(webview.FileDialog.FOLDER)
61+
result = get_window().create_file_dialog(webview.FileDialog.FOLDER)
3962
if not result:
4063
return
4164
path = result[0]
@@ -46,27 +69,35 @@ def move_install_folder(self):
4669
return
4770

4871

49-
def get_entrypoint():
50-
def exists(path):
51-
path = (Path(__file__).parent / path).resolve()
52-
if path.exists():
53-
return path
72+
def update_and_notify(window):
73+
res = update_archive()
74+
window.evaluate_js(f"archiveReady({res});")
5475

55-
queries = ["../gui/index.html", "../Resources/gui/index.html", "./gui/index.html"]
5676

57-
for query in queries:
58-
result = exists(query)
59-
if result:
60-
return result
77+
def on_game_exit():
78+
window = get_window()
79+
window.evaluate_js("setLaunchState(0);")
80+
update_and_notify(window)
6181

62-
raise Exception("No index.html found")
82+
83+
def check_launch_game():
84+
pid = wait_game_running()
85+
has_pid = pid is not None
86+
res = 2 if has_pid else 0
87+
get_window().evaluate_js(f"setLaunchState({res});")
88+
if has_pid:
89+
wait_game_exit(pid, on_game_exit)
90+
return True
91+
else:
92+
return False
6393

6494

6595
def on_loaded(window):
66-
res = update_archive()
67-
window.evaluate_js(f"archiveReady({res});")
96+
if not check_launch_game():
97+
update_and_notify(window)
6898

6999

100+
debug = not getattr(sys, "frozen", False)
70101
entry_path = get_entrypoint()
71102
entry = str(entry_path)
72103
entry_parent = entry_path.parent
@@ -82,7 +113,7 @@ def start_gui():
82113
window.state.opts = extra_options_str
83114
window.events.loaded += lambda: on_loaded(window)
84115
try:
85-
webview.start(icon=str(entry_parent / "favicon.ico"))
116+
webview.start(icon=str(entry_parent / "favicon.ico"), debug=debug)
86117
except Exception as e:
87118
if os.name == "posix" and sys.platform != "darwin":
88119
subprocess.run(

tc2_launcher/run.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import subprocess
55
import sys
66
import tempfile
7+
import threading
78
import zipfile
89
from pathlib import Path
910
from shutil import move, rmtree
11+
from time import sleep
12+
from timeit import default_timer as timer
1013

14+
import psutil
1115
import requests
1216

1317
TC2_REPO = "mastercomfig/tc2"
@@ -329,18 +333,22 @@ def get_game_dir(dest: Path | None = None) -> Path:
329333
return dest / "game"
330334

331335

336+
def _get_game_exe_name(running_process: bool = False) -> str:
337+
# Determine executable name based on platform
338+
if sys.platform.startswith("win"):
339+
return "tc2_win64.exe"
340+
else:
341+
return "tc2_linux64" if running_process else "tc2.sh"
342+
343+
332344
def _get_game_exe(dest: Path | None) -> Path | None:
333345
if not dest:
334346
dest = default_dest_dir()
335347

336348
game_dir = get_game_dir(dest)
337349

338350
# Determine executable based on platform
339-
exe_path = None
340-
if sys.platform.startswith("win"):
341-
exe_path = game_dir / "tc2_win64.exe"
342-
else:
343-
exe_path = game_dir / "tc2.sh"
351+
exe_path = game_dir / _get_game_exe_name()
344352

345353
if exe_path and exe_path.exists():
346354
return exe_path
@@ -391,6 +399,35 @@ def launch_game(
391399
print(f"Failed to launch game: {e}")
392400

393401

402+
def _wait_game_exit_inner(pid, callback):
403+
try:
404+
p = psutil.Process(pid)
405+
p.wait()
406+
callback()
407+
except Exception:
408+
pass
409+
410+
411+
def wait_game_exit(pid, callback):
412+
wait_game_exit_thread = threading.Thread(
413+
target=_wait_game_exit_inner, args=(pid, callback)
414+
)
415+
wait_game_exit_thread.start()
416+
417+
418+
def wait_game_running() -> int | None:
419+
game_exe_name = _get_game_exe_name(running_process=True)
420+
time_limit = 5
421+
while time_limit > 0:
422+
for p in psutil.process_iter(["pid", "name"]):
423+
if p.info["name"] == game_exe_name:
424+
return p.pid
425+
before = timer()
426+
sleep(0.2)
427+
time_limit -= timer() - before
428+
return None
429+
430+
394431
def get_launch_options(dest: Path | None = None) -> list[str]:
395432
if not dest:
396433
dest = default_dest_dir()

0 commit comments

Comments
 (0)