Adds the PyQt6 Windows client (Homepage, Scan & results, About & updates, Settings), ClamAV scanning and quarantine, browser-download watch with an exclusive scan gate, Bolt loopback/PKCE sign-in with DPAPI key storage, Gitea release feed with checksum verification, opt-in diagnostics, and Windows/portable build tooling. Co-Authored-By: Claude Code <noreply@anthropic.com>
114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
"""Generate the small raster assets used by the Qt stylesheet and Windows icon.
|
|
|
|
Run with the project venv from the repository root:
|
|
QT_QPA_PLATFORM=offscreen .venv/bin/python scripts/generate_assets.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import struct
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
|
|
from PyQt6.QtCore import QBuffer, QByteArray, QIODevice, QPointF, QRectF, Qt # noqa: E402
|
|
from PyQt6.QtGui import QColor, QImage, QPainter, QPainterPath, QPen, QPixmap # noqa: E402
|
|
from PyQt6.QtWidgets import QApplication # noqa: E402
|
|
|
|
from boltclam.ui import brand # noqa: E402
|
|
|
|
RESOURCES = ROOT / "resources"
|
|
|
|
|
|
def _png_bytes(pixmap: QPixmap) -> bytes:
|
|
data = QByteArray()
|
|
buffer = QBuffer(data)
|
|
buffer.open(QIODevice.OpenModeFlag.WriteOnly)
|
|
pixmap.save(buffer, "PNG")
|
|
buffer.close()
|
|
return bytes(data)
|
|
|
|
|
|
def _check() -> None:
|
|
size = 18
|
|
pixmap = QPixmap(size, size)
|
|
pixmap.fill(Qt.GlobalColor.transparent)
|
|
painter = QPainter(pixmap)
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
pen = QPen(QColor("#201704"), 2.4, Qt.PenStyle.SolidLine,
|
|
Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin)
|
|
painter.setPen(pen)
|
|
path = QPainterPath(QPointF(4.2, 9.3))
|
|
path.lineTo(7.4, 12.5)
|
|
path.lineTo(13.8, 5.4)
|
|
painter.drawPath(path)
|
|
painter.end()
|
|
pixmap.save(str(RESOURCES / "check.png"), "PNG")
|
|
|
|
|
|
def _radio() -> None:
|
|
size = 17
|
|
pixmap = QPixmap(size, size)
|
|
pixmap.fill(Qt.GlobalColor.transparent)
|
|
painter = QPainter(pixmap)
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
painter.setPen(Qt.PenStyle.NoPen)
|
|
painter.setBrush(QColor("#201704"))
|
|
painter.drawEllipse(QRectF(5.0, 5.0, 7.0, 7.0))
|
|
painter.end()
|
|
pixmap.save(str(RESOURCES / "radio.png"), "PNG")
|
|
|
|
|
|
def _chevron() -> None:
|
|
size = 14
|
|
pixmap = QPixmap(size, size)
|
|
pixmap.fill(Qt.GlobalColor.transparent)
|
|
painter = QPainter(pixmap)
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
painter.setPen(QPen(QColor("#8BA7B2"), 1.7, Qt.PenStyle.SolidLine,
|
|
Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin))
|
|
path = QPainterPath(QPointF(3.2, 5.2))
|
|
path.lineTo(7.0, 9.0)
|
|
path.lineTo(10.8, 5.2)
|
|
painter.drawPath(path)
|
|
painter.end()
|
|
pixmap.save(str(RESOURCES / "chevron_down.png"), "PNG")
|
|
|
|
|
|
def _ico() -> None:
|
|
sizes = (16, 24, 32, 48, 64, 128, 256)
|
|
images: list[tuple[int, bytes]] = []
|
|
for size in sizes:
|
|
pixmap = brand.pixmap(size)
|
|
images.append((size, _png_bytes(pixmap)))
|
|
header = struct.pack("<HHH", 0, 1, len(images))
|
|
offset = 6 + 16 * len(images)
|
|
entries = b""
|
|
payload = b""
|
|
for size, data in images:
|
|
width = 0 if size >= 256 else size
|
|
height = 0 if size >= 256 else size
|
|
entries += struct.pack("<BBBBHHII", width, height, 0, 0, 1, 32,
|
|
len(data), offset)
|
|
offset += len(data)
|
|
payload += data
|
|
(RESOURCES / "boltclam.ico").write_bytes(header + entries + payload)
|
|
brand.pixmap(256).save(str(RESOURCES / "boltclam.png"), "PNG")
|
|
|
|
|
|
def main() -> int:
|
|
app = QApplication.instance() or QApplication(sys.argv)
|
|
RESOURCES.mkdir(parents=True, exist_ok=True)
|
|
_check()
|
|
_radio()
|
|
_chevron()
|
|
_ico()
|
|
print("assets written to", RESOURCES)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|