feat: Add toolbar icon configuration and update handling for Agravity
Some checks are pending
Tests & Quality Checks / Test on Python 3.11 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.10 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-2 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-2 (push) Waiting to run
Tests & Quality Checks / Build Artifacts (push) Blocked by required conditions
Tests & Quality Checks / Build Artifacts-1 (push) Blocked by required conditions

This commit is contained in:
claudi 2026-03-12 09:07:14 +01:00
parent eab1009d8c
commit df76cb9b36
8 changed files with 112 additions and 15 deletions

View file

@ -14,5 +14,9 @@
"icon_icns": "resources/icons/app.icns",
"dialog_bmp": "resources/icons/background.bmp",
"banner_bmp": "resources/icons/banner.bmp",
"license_rtf": "resources/license.rtf"
"license_rtf": "resources/license.rtf",
"toolbar_icon_home": "resources/icons/home.ico",
"toolbar_icon_reload": "resources/icons/reload.ico",
"toolbar_icon_open": "resources/icons/open.ico",
"toolbar_icon_openwith": "resources/icons/openwith.ico"
}

View file

@ -16,5 +16,9 @@
"icon_icns": "resources/icons/app.icns",
"dialog_bmp": "resources/icons/background.bmp",
"banner_bmp": "resources/icons/banner.bmp",
"license_rtf": "resources/license.rtf"
"license_rtf": "resources/license.rtf",
"toolbar_icon_home": "resources/icons/home.ico",
"toolbar_icon_reload": "resources/icons/reload.ico",
"toolbar_icon_open": "resources/icons/open.ico",
"toolbar_icon_openwith": "resources/icons/openwith.ico"
}

View file

@ -29,6 +29,10 @@ class BrandConfig:
dialog_bmp: Path
banner_bmp: Path
license_rtf: Path
toolbar_icon_home: str
toolbar_icon_reload: str
toolbar_icon_open: str
toolbar_icon_openwith: str
def windows_installer_name(self, version: str) -> str:
return f"{self.asset_prefix}-{version}-win-x64.msi"
@ -58,6 +62,10 @@ DEFAULT_BRAND_VALUES: dict[str, Any] = {
"dialog_bmp": "resources/icons/background.bmp",
"banner_bmp": "resources/icons/banner.bmp",
"license_rtf": "resources/license.rtf",
"toolbar_icon_home": "resources/icons/home.ico",
"toolbar_icon_reload": "resources/icons/reload.ico",
"toolbar_icon_open": "resources/icons/open.ico",
"toolbar_icon_openwith": "resources/icons/openwith.ico",
}
DEFAULT_BRAND_ID = str(DEFAULT_BRAND_VALUES["brand_id"])
@ -125,6 +133,18 @@ def load_brand_config(
dialog_bmp=resolve_asset("dialog_bmp"),
banner_bmp=resolve_asset("banner_bmp"),
license_rtf=resolve_asset("license_rtf"),
toolbar_icon_home=str(
values.get("toolbar_icon_home", DEFAULT_BRAND_VALUES["toolbar_icon_home"])
),
toolbar_icon_reload=str(
values.get("toolbar_icon_reload", DEFAULT_BRAND_VALUES["toolbar_icon_reload"])
),
toolbar_icon_open=str(
values.get("toolbar_icon_open", DEFAULT_BRAND_VALUES["toolbar_icon_open"])
),
toolbar_icon_openwith=str(
values.get("toolbar_icon_openwith", DEFAULT_BRAND_VALUES["toolbar_icon_openwith"])
),
)
@ -272,6 +292,10 @@ def cli_env(args: argparse.Namespace) -> int:
"WEBDROP_UPDATE_CHANNEL": brand.update_channel,
"WEBDROP_ICON_ICO": str(brand.icon_ico),
"WEBDROP_ICON_ICNS": str(brand.icon_icns),
"WEBDROP_TOOLBAR_ICON_HOME": brand.toolbar_icon_home,
"WEBDROP_TOOLBAR_ICON_RELOAD": brand.toolbar_icon_reload,
"WEBDROP_TOOLBAR_ICON_OPEN": brand.toolbar_icon_open,
"WEBDROP_TOOLBAR_ICON_OPENWITH": brand.toolbar_icon_openwith,
}
for key, value in assignments.items():
print(f'export {key}="{value}"')
@ -324,6 +348,10 @@ def cli_show(args: argparse.Namespace) -> int:
"config_dir_name": brand.config_dir_name,
"msi_upgrade_code": brand.msi_upgrade_code,
"update_channel": brand.update_channel,
"toolbar_icon_home": brand.toolbar_icon_home,
"toolbar_icon_reload": brand.toolbar_icon_reload,
"toolbar_icon_open": brand.toolbar_icon_open,
"toolbar_icon_openwith": brand.toolbar_icon_openwith,
},
indent=2,
)

View file

@ -211,6 +211,10 @@ build_executable() {
echo "BRAND_ID=\"$WEBDROP_BRAND_ID\""
echo "APP_CONFIG_DIR_NAME=\"$WEBDROP_CONFIG_DIR_NAME\""
echo "UPDATE_CHANNEL=\"$WEBDROP_UPDATE_CHANNEL\""
echo "TOOLBAR_ICON_HOME=\"$WEBDROP_TOOLBAR_ICON_HOME\""
echo "TOOLBAR_ICON_RELOAD=\"$WEBDROP_TOOLBAR_ICON_RELOAD\""
echo "TOOLBAR_ICON_OPEN=\"$WEBDROP_TOOLBAR_ICON_OPEN\""
echo "TOOLBAR_ICON_OPENWITH=\"$WEBDROP_TOOLBAR_ICON_OPENWITH\""
} >> "$BUNDLED_ENV_FILE"
# Export env file for spec file to pick up

View file

@ -113,6 +113,10 @@ class WindowsBuilder:
"BRAND_ID": self.brand.brand_id,
"APP_CONFIG_DIR_NAME": self.brand.config_dir_name,
"UPDATE_CHANNEL": self.brand.update_channel,
"TOOLBAR_ICON_HOME": self.brand.toolbar_icon_home,
"TOOLBAR_ICON_RELOAD": self.brand.toolbar_icon_reload,
"TOOLBAR_ICON_OPEN": self.brand.toolbar_icon_open,
"TOOLBAR_ICON_OPENWITH": self.brand.toolbar_icon_openwith,
}
output_env = self.temp_dir / ".env"

View file

@ -106,8 +106,20 @@ These can point at brand-specific files or default shared files:
- `banner_bmp`
- `license_rtf`
Optional toolbar icon overrides:
- `toolbar_icon_home`
- `toolbar_icon_reload`
- `toolbar_icon_open`
- `toolbar_icon_openwith`
If a referenced asset path does not exist, the helper falls back to the default asset defined in `build/scripts/brand_config.py`.
For toolbar icons, the runtime looks for the configured paths in packaged and development layouts. If an icon is missing:
- Home falls back to a standard Qt home icon
- Reload/Open/OpenWith keep their existing icon behavior
### Identity Rules
Treat these values as long-lived product identity once a brand has shipped:
@ -163,6 +175,13 @@ Relevant runtime config keys include:
- `update_channel`
- `update_manifest_name`
Toolbar icon env overrides (useful for packaged branding):
- `TOOLBAR_ICON_HOME`
- `TOOLBAR_ICON_RELOAD`
- `TOOLBAR_ICON_OPEN`
- `TOOLBAR_ICON_OPENWITH`
The current example in `config.example.json` shows the Agravity runtime setup.
When adding a new brand, make sure the runtime config matches the packaging manifest at least for:

View file

@ -3,6 +3,7 @@
import asyncio
import json
import logging
import os
import re
import subprocess
import sys
@ -1386,16 +1387,13 @@ class MainWindow(QMainWindow):
# Separator
toolbar.addSeparator()
if hasattr(sys, "_MEIPASS"):
icons_dir = Path(sys._MEIPASS) / "resources" / "icons" # type: ignore[attr-defined]
else:
icons_dir = Path(__file__).parent.parent.parent.parent / "resources" / "icons"
# Home button
home_icon_path = icons_dir / "home.ico"
home_icon_path = self._resolve_toolbar_icon_path(
os.getenv("TOOLBAR_ICON_HOME", "resources/icons/home.ico")
)
home_icon = (
QIcon(str(home_icon_path))
if home_icon_path.exists()
if home_icon_path is not None
else self.style().standardIcon(self.style().StandardPixmap.SP_DirHomeIcon)
)
home_action = toolbar.addAction(home_icon, "")
@ -1404,15 +1402,19 @@ class MainWindow(QMainWindow):
# Refresh button
refresh_action = self.web_view.pageAction(self.web_view.page().WebAction.Reload)
reload_icon_path = icons_dir / "reload.ico"
if reload_icon_path.exists():
reload_icon_path = self._resolve_toolbar_icon_path(
os.getenv("TOOLBAR_ICON_RELOAD", "resources/icons/reload.ico")
)
if reload_icon_path is not None:
refresh_action.setIcon(QIcon(str(reload_icon_path)))
toolbar.addAction(refresh_action)
# Open-with-default-app drop zone (right of Reload)
self._open_drop_zone = OpenDropZone()
open_icon_path = icons_dir / "open.ico"
if open_icon_path.exists():
open_icon_path = self._resolve_toolbar_icon_path(
os.getenv("TOOLBAR_ICON_OPEN", "resources/icons/open.ico")
)
if open_icon_path is not None:
self._open_drop_zone.set_icon(QIcon(str(open_icon_path)))
self._open_drop_zone.file_opened.connect(self._on_file_opened_via_drop)
self._open_drop_zone.file_open_failed.connect(self._on_file_open_failed_via_drop)
@ -1422,8 +1424,10 @@ class MainWindow(QMainWindow):
# Open-with chooser drop zone (right of Open-with-default-app)
self._open_with_drop_zone = OpenWithDropZone()
open_with_icon_path = icons_dir / "openwith.ico"
if open_with_icon_path.exists():
open_with_icon_path = self._resolve_toolbar_icon_path(
os.getenv("TOOLBAR_ICON_OPENWITH", "resources/icons/openwith.ico")
)
if open_with_icon_path is not None:
self._open_with_drop_zone.set_icon(QIcon(str(open_with_icon_path)))
self._open_with_drop_zone.file_open_with_requested.connect(
self._on_file_open_with_requested
@ -1467,6 +1471,32 @@ class MainWindow(QMainWindow):
dev_tools_action.setToolTip(tr("toolbar.tooltip.dev_tools"))
dev_tools_action.triggered.connect(self._open_developer_tools)
def _resolve_toolbar_icon_path(self, configured_path: str) -> Path | None:
"""Resolve configured toolbar icon path in both dev and packaged layouts."""
icon_path = Path(configured_path)
candidates: list[Path] = []
if icon_path.is_absolute():
candidates.append(icon_path)
else:
if hasattr(sys, "_MEIPASS"):
meipass = Path(sys._MEIPASS) # type: ignore[attr-defined]
candidates.append(meipass / icon_path)
exe_dir = Path(sys.executable).resolve().parent
candidates.append(exe_dir / icon_path)
candidates.append(exe_dir / "_internal" / icon_path)
project_root = Path(__file__).parent.parent.parent.parent
candidates.append(project_root / icon_path)
for candidate in candidates:
if candidate.exists():
return candidate
logger.warning(f"Toolbar icon not found for configured path: {configured_path}")
return None
def _open_log_file(self) -> None:
"""Open the application log file in the system default text editor.

View file

@ -25,6 +25,10 @@ def test_load_agravity_brand_config():
assert brand.display_name == "Agravity Bridge"
assert brand.asset_prefix == "AgravityBridge"
assert brand.exe_name == "AgravityBridge"
assert brand.toolbar_icon_home == "resources/icons/home.ico"
assert brand.toolbar_icon_reload == "resources/icons/reload.ico"
assert brand.toolbar_icon_open == "resources/icons/open.ico"
assert brand.toolbar_icon_openwith == "resources/icons/openwith.ico"
assert brand.windows_installer_name("0.8.4") == "AgravityBridge-0.8.4-win-x64.msi"