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.
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 **•••**.
+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.
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 **•••**.
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
Set-Location -LiteralPath $projectRoot
& (Join-Path $projectRoot 'create_shortcut.ps1')
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."
}
+8 -1
View File
@@ -585,6 +585,12 @@ def start_local_server(directory: Path) -> 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()
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
Set-Location -LiteralPath $projectRoot
& (Join-Path $projectRoot 'create_shortcut.ps1')
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."
}
+8 -1
View File
@@ -585,6 +585,12 @@ def start_local_server(directory: Path) -> 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()