diff --git a/README.md b/README.md index 37343e8..3c53bf5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Install Python 3.11+ with the Python launcher, then run: ``` The script creates a virtual environment and installs dependencies automatically. +On first launch, it also adds a **SUB Control** desktop shortcut. Select a COM port and **Connect**, or choose **Demo Mode** to try it without hardware. Connection settings are under **•••**. diff --git a/SUB Control/README.md b/SUB Control/README.md index 37343e8..3c53bf5 100644 --- a/SUB Control/README.md +++ b/SUB Control/README.md @@ -13,6 +13,7 @@ Install Python 3.11+ with the Python launcher, then run: ``` The script creates a virtual environment and installs dependencies automatically. +On first launch, it also adds a **SUB Control** desktop shortcut. Select a COM port and **Connect**, or choose **Demo Mode** to try it without hardware. Connection settings are under **•••**. diff --git a/SUB Control/assets/sub-control-icon.png b/SUB Control/assets/sub-control-icon.png new file mode 100644 index 0000000..724136b Binary files /dev/null and b/SUB Control/assets/sub-control-icon.png differ diff --git a/SUB Control/assets/sub-control.ico b/SUB Control/assets/sub-control.ico new file mode 100644 index 0000000..763f7c4 Binary files /dev/null and b/SUB Control/assets/sub-control.ico differ diff --git a/SUB Control/build_icon.py b/SUB Control/build_icon.py new file mode 100644 index 0000000..55c79fc --- /dev/null +++ b/SUB Control/build_icon.py @@ -0,0 +1,14 @@ +"""Package the original PNG in a Windows ICO without altering its pixels.""" +from pathlib import Path +import struct + +root = Path(__file__).resolve().parent +png = (root / "assets" / "sub-control-icon.png").read_bytes() +if png[:8] != b"\x89PNG\r\n\x1a\n": + raise ValueError("Icon source must be PNG") +width, height = struct.unpack(">II", png[16:24]) +if not (1 <= width <= 256 and 1 <= height <= 256): + raise ValueError("Windows icon source must be at most 256 x 256") +header = struct.pack(" int: def main(): + if os.name == "nt": + # Keep the running window separate from generic Python taskbar groups. + import ctypes + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("SUBControl.Desktop") + from desktop_shortcut import ensure_desktop_shortcut + ensure_desktop_shortcut() api = ControlApi() api._migrate_legacy_model() project_root = Path(__file__).resolve().parent @@ -602,7 +608,8 @@ def main(): window.events.maximized += lambda: setattr(api, "maximized", True) window.events.restored += lambda: setattr(api, "maximized", False) try: - webview.start(debug=True) + icon_path = project_root / "assets" / "sub-control.ico" + webview.start(debug=True, icon=str(icon_path) if icon_path.is_file() else None) finally: api.close() diff --git a/assets/sub-control-icon.png b/assets/sub-control-icon.png new file mode 100644 index 0000000..724136b Binary files /dev/null and b/assets/sub-control-icon.png differ diff --git a/assets/sub-control.ico b/assets/sub-control.ico new file mode 100644 index 0000000..763f7c4 Binary files /dev/null and b/assets/sub-control.ico differ diff --git a/build_icon.py b/build_icon.py new file mode 100644 index 0000000..55c79fc --- /dev/null +++ b/build_icon.py @@ -0,0 +1,14 @@ +"""Package the original PNG in a Windows ICO without altering its pixels.""" +from pathlib import Path +import struct + +root = Path(__file__).resolve().parent +png = (root / "assets" / "sub-control-icon.png").read_bytes() +if png[:8] != b"\x89PNG\r\n\x1a\n": + raise ValueError("Icon source must be PNG") +width, height = struct.unpack(">II", png[16:24]) +if not (1 <= width <= 256 and 1 <= height <= 256): + raise ValueError("Windows icon source must be at most 256 x 256") +header = struct.pack(" int: def main(): + if os.name == "nt": + # Keep the running window separate from generic Python taskbar groups. + import ctypes + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("SUBControl.Desktop") + from desktop_shortcut import ensure_desktop_shortcut + ensure_desktop_shortcut() api = ControlApi() api._migrate_legacy_model() project_root = Path(__file__).resolve().parent @@ -602,7 +608,8 @@ def main(): window.events.maximized += lambda: setattr(api, "maximized", True) window.events.restored += lambda: setattr(api, "maximized", False) try: - webview.start(debug=True) + icon_path = project_root / "assets" / "sub-control.ico" + webview.start(debug=True, icon=str(icon_path) if icon_path.is_file() else None) finally: api.close()