-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
113 lines (90 loc) · 3.21 KB
/
Copy pathmain.py
File metadata and controls
113 lines (90 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"""
MADLIONS 60% Hall Effect Configurator — entry point.
Wires the bridge to a pywebview window that loads the web UI. Run: python main.py
"""
from __future__ import annotations
import os
import socket
import sys
import threading
import webview
from bridge import Api
from tray import TrayController
def _base_dir():
"""Resolve the app's base directory, whether run from source or PyInstaller-frozen."""
if getattr(sys, "frozen", False):
return getattr(sys, "_MEIPASS", os.path.dirname(sys.executable))
return os.path.dirname(os.path.abspath(__file__))
_UI_DIR = os.path.join(_base_dir(), "ui")
# Single-instance lock: the first instance binds this loopback port and listens; a second
# launch fails to bind, pokes the running instance (which restores its window) and exits.
_LOCK_PORT = 49613
def _acquire_single_instance(on_activate):
"""Return the held lock socket if we're the only instance, else None (another is running)."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.bind(("127.0.0.1", _LOCK_PORT)) # no SO_REUSEADDR: a 2nd bind must fail
except OSError:
try:
with socket.create_connection(("127.0.0.1", _LOCK_PORT), timeout=1):
pass # poke the running instance to surface its window
except OSError:
pass
sock.close()
return None
sock.listen(5)
def _serve():
while True:
try:
conn, _ = sock.accept()
conn.close()
on_activate()
except OSError:
break
threading.Thread(target=_serve, daemon=True).start()
return sock
def main():
# Enforce a single instance: a second launch restores the existing window and exits,
# so the user can't end up with several copies open or minimized to the tray.
holder = {}
lock = _acquire_single_instance(lambda: _activate(holder.get("window")))
if lock is None:
return
api = Api()
window = webview.create_window(
"MADLIONS 60 — Configurator",
os.path.join(_UI_DIR, "index.html"),
js_api=api,
width=1240,
height=760,
min_size=(960, 640),
background_color="#0a0a16",
)
# Closing the window minimizes to the system tray (like Discord/Steam) so the
# host-rendered animation keeps streaming to the keyboard. The app fully quits only
# via the tray "Quit" item.
tray = TrayController(window, on_quit=api.shutdown)
def _on_closing():
# Returning False cancels the close. If the tray isn't available (or the user chose
# Quit), allow the real close so they're never trapped without a way to exit.
if tray.quitting or not tray.ready:
return True
tray.hide_to_tray()
return False
window.events.closing += _on_closing
holder["window"] = window
tray.start()
webview.start()
tray.stop()
api.shutdown()
def _activate(window):
"""Bring the running instance's window back to the foreground (from tray/minimized)."""
if window is None:
return
try:
window.show()
window.restore()
except Exception:
pass
if __name__ == "__main__":
main()