Added App Icon
This commit is contained in:
@@ -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 **•••**.
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
@@ -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)
|
||||||
@@ -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: $_"
|
||||||
|
}
|
||||||
@@ -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}")
|
||||||
@@ -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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 |
@@ -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)
|
||||||
@@ -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: $_"
|
||||||
|
}
|
||||||
@@ -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}")
|
||||||
@@ -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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user