Added App Icon

This commit is contained in:
2026-08-28 17:40:43 +02:00
parent 73c20a0ff2
commit 7c85397af0
16 changed files with 178 additions and 2 deletions
+1
View File
@@ -13,6 +13,7 @@ Install Python 3.11+ with the Python launcher, then run:
``` ```
The script creates a virtual environment and installs dependencies automatically. 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. Select a COM port and **Connect**, or choose **Demo Mode** to try it without hardware.
Connection settings are under **•••**. Connection settings are under **•••**.
+1
View File
@@ -13,6 +13,7 @@ Install Python 3.11+ with the Python launcher, then run:
``` ```
The script creates a virtual environment and installs dependencies automatically. 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. Select a COM port and **Connect**, or choose **Demo Mode** to try it without hardware.
Connection settings are under **•••**. Connection settings are under **•••**.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+14
View File
@@ -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("<HHH", 0, 1, 1)
entry = struct.pack("<BBBBHHII", width % 256, height % 256, 0, 0, 1, 32, len(png), 22)
(root / "assets" / "sub-control.ico").write_bytes(header + entry + png)
+43
View File
@@ -0,0 +1,43 @@
param([switch]$UpdateIcon)
$ErrorActionPreference = 'Stop'
$shortcutProject = $PSScriptRoot
$shortcutMarker = Join-Path $shortcutProject 'runtime\desktop-shortcut-created.txt'
if ((Test-Path -LiteralPath $shortcutMarker) -and -not $UpdateIcon) { return }
try {
# Resolves the real Desktop, including a redirected/OneDrive Desktop.
$shortcutDesktop = [Environment]::GetFolderPath('DesktopDirectory')
if (-not $shortcutDesktop) { throw 'Windows Desktop folder is unavailable.' }
$shortcutPath = Join-Path $shortcutDesktop 'SUB Control.lnk'
# Never replace a shortcut the user already has.
if (-not (Test-Path -LiteralPath $shortcutPath)) {
$shortcutShell = New-Object -ComObject WScript.Shell
$shortcutLink = $shortcutShell.CreateShortcut($shortcutPath)
$shortcutLink.TargetPath = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
$shortcutLink.Arguments = '-NoProfile -WindowStyle Hidden -File "' + (Join-Path $shortcutProject 'run.ps1') + '"'
$shortcutLink.WorkingDirectory = $shortcutProject
$shortcutLink.Description = 'Launch SUB Control'
$shortcutLink.WindowStyle = 7
$shortcutIcon = Join-Path $shortcutProject 'assets\sub-control.ico'
if (Test-Path -LiteralPath $shortcutIcon) { $shortcutLink.IconLocation = "$shortcutIcon,0" }
$shortcutLink.Save()
} elseif ($UpdateIcon) {
$shortcutShell = New-Object -ComObject WScript.Shell
$shortcutLink = $shortcutShell.CreateShortcut($shortcutPath)
$expectedScript = '"' + (Join-Path $shortcutProject 'run.ps1') + '"'
if ($shortcutLink.Arguments.Contains($expectedScript)) {
$shortcutLink.IconLocation = (Join-Path $shortcutProject 'assets\sub-control.ico') + ',0'
$shortcutLink.Save()
} else {
throw 'Existing shortcut belongs to another installation; left unchanged.'
}
}
New-Item -ItemType Directory -Path (Split-Path -Parent $shortcutMarker) -Force | Out-Null
Set-Content -LiteralPath $shortcutMarker -Value $shortcutPath
} catch {
# Desktop permissions must not prevent the controller from starting.
Write-Warning "Could not create the SUB Control desktop shortcut: $_"
}
+21
View File
@@ -0,0 +1,21 @@
"""Best-effort Windows first-run shortcut, including direct Python launches."""
import os
from pathlib import Path
import subprocess
def ensure_desktop_shortcut():
if os.name != "nt":
return
root = Path(__file__).resolve().parent
if (root / "runtime" / "desktop-shortcut-created.txt").exists():
return
powershell = Path(os.environ["SystemRoot"]) / "System32/WindowsPowerShell/v1.0/powershell.exe"
try:
subprocess.run(
[str(powershell), "-NoProfile", "-NonInteractive", "-File", str(root / "create_shortcut.ps1")],
cwd=root, timeout=10, check=True, capture_output=True,
creationflags=subprocess.CREATE_NO_WINDOW,
)
except (OSError, subprocess.SubprocessError) as error:
print(f"Desktop shortcut setup skipped: {error}")
+2
View File
@@ -3,6 +3,8 @@ $ErrorActionPreference = "Stop"
$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location -LiteralPath $projectRoot Set-Location -LiteralPath $projectRoot
& (Join-Path $projectRoot 'create_shortcut.ps1')
if (-not (Get-Command py -ErrorAction SilentlyContinue)) { if (-not (Get-Command py -ErrorAction SilentlyContinue)) {
throw "Python 3.11 or newer is required. Install Python from python.org, then run this script again." throw "Python 3.11 or newer is required. Install Python from python.org, then run this script again."
} }
+8 -1
View File
@@ -585,6 +585,12 @@ def start_local_server(directory: Path) -> int:
def main(): 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 = ControlApi()
api._migrate_legacy_model() api._migrate_legacy_model()
project_root = Path(__file__).resolve().parent project_root = Path(__file__).resolve().parent
@@ -602,7 +608,8 @@ def main():
window.events.maximized += lambda: setattr(api, "maximized", True) window.events.maximized += lambda: setattr(api, "maximized", True)
window.events.restored += lambda: setattr(api, "maximized", False) window.events.restored += lambda: setattr(api, "maximized", False)
try: 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: finally:
api.close() api.close()
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+14
View File
@@ -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("<HHH", 0, 1, 1)
entry = struct.pack("<BBBBHHII", width % 256, height % 256, 0, 0, 1, 32, len(png), 22)
(root / "assets" / "sub-control.ico").write_bytes(header + entry + png)
+43
View File
@@ -0,0 +1,43 @@
param([switch]$UpdateIcon)
$ErrorActionPreference = 'Stop'
$shortcutProject = $PSScriptRoot
$shortcutMarker = Join-Path $shortcutProject 'runtime\desktop-shortcut-created.txt'
if ((Test-Path -LiteralPath $shortcutMarker) -and -not $UpdateIcon) { return }
try {
# Resolves the real Desktop, including a redirected/OneDrive Desktop.
$shortcutDesktop = [Environment]::GetFolderPath('DesktopDirectory')
if (-not $shortcutDesktop) { throw 'Windows Desktop folder is unavailable.' }
$shortcutPath = Join-Path $shortcutDesktop 'SUB Control.lnk'
# Never replace a shortcut the user already has.
if (-not (Test-Path -LiteralPath $shortcutPath)) {
$shortcutShell = New-Object -ComObject WScript.Shell
$shortcutLink = $shortcutShell.CreateShortcut($shortcutPath)
$shortcutLink.TargetPath = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
$shortcutLink.Arguments = '-NoProfile -WindowStyle Hidden -File "' + (Join-Path $shortcutProject 'run.ps1') + '"'
$shortcutLink.WorkingDirectory = $shortcutProject
$shortcutLink.Description = 'Launch SUB Control'
$shortcutLink.WindowStyle = 7
$shortcutIcon = Join-Path $shortcutProject 'assets\sub-control.ico'
if (Test-Path -LiteralPath $shortcutIcon) { $shortcutLink.IconLocation = "$shortcutIcon,0" }
$shortcutLink.Save()
} elseif ($UpdateIcon) {
$shortcutShell = New-Object -ComObject WScript.Shell
$shortcutLink = $shortcutShell.CreateShortcut($shortcutPath)
$expectedScript = '"' + (Join-Path $shortcutProject 'run.ps1') + '"'
if ($shortcutLink.Arguments.Contains($expectedScript)) {
$shortcutLink.IconLocation = (Join-Path $shortcutProject 'assets\sub-control.ico') + ',0'
$shortcutLink.Save()
} else {
throw 'Existing shortcut belongs to another installation; left unchanged.'
}
}
New-Item -ItemType Directory -Path (Split-Path -Parent $shortcutMarker) -Force | Out-Null
Set-Content -LiteralPath $shortcutMarker -Value $shortcutPath
} catch {
# Desktop permissions must not prevent the controller from starting.
Write-Warning "Could not create the SUB Control desktop shortcut: $_"
}
+21
View File
@@ -0,0 +1,21 @@
"""Best-effort Windows first-run shortcut, including direct Python launches."""
import os
from pathlib import Path
import subprocess
def ensure_desktop_shortcut():
if os.name != "nt":
return
root = Path(__file__).resolve().parent
if (root / "runtime" / "desktop-shortcut-created.txt").exists():
return
powershell = Path(os.environ["SystemRoot"]) / "System32/WindowsPowerShell/v1.0/powershell.exe"
try:
subprocess.run(
[str(powershell), "-NoProfile", "-NonInteractive", "-File", str(root / "create_shortcut.ps1")],
cwd=root, timeout=10, check=True, capture_output=True,
creationflags=subprocess.CREATE_NO_WINDOW,
)
except (OSError, subprocess.SubprocessError) as error:
print(f"Desktop shortcut setup skipped: {error}")
+2
View File
@@ -3,6 +3,8 @@ $ErrorActionPreference = "Stop"
$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location -LiteralPath $projectRoot Set-Location -LiteralPath $projectRoot
& (Join-Path $projectRoot 'create_shortcut.ps1')
if (-not (Get-Command py -ErrorAction SilentlyContinue)) { if (-not (Get-Command py -ErrorAction SilentlyContinue)) {
throw "Python 3.11 or newer is required. Install Python from python.org, then run this script again." throw "Python 3.11 or newer is required. Install Python from python.org, then run this script again."
} }
+8 -1
View File
@@ -585,6 +585,12 @@ def start_local_server(directory: Path) -> int:
def main(): 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 = ControlApi()
api._migrate_legacy_model() api._migrate_legacy_model()
project_root = Path(__file__).resolve().parent project_root = Path(__file__).resolve().parent
@@ -602,7 +608,8 @@ def main():
window.events.maximized += lambda: setattr(api, "maximized", True) window.events.maximized += lambda: setattr(api, "maximized", True)
window.events.restored += lambda: setattr(api, "maximized", False) window.events.restored += lambda: setattr(api, "maximized", False)
try: 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: finally:
api.close() api.close()