diff --git a/.gitignore b/.gitignore index ccfc12ccb..1209dc655 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ TODO /misc/nsis/include /misc/nsis/plugins /wheels +/wheels +.DS_Store diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 66bd485fc..c402c07e3 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -169,14 +169,33 @@ def init(*, args: argparse.Namespace) -> None: def _init_icon(): """Initialize the icon of qutebrowser.""" fallback_icon = QIcon() - for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]: - filename = 'icons/qutebrowser-{size}x{size}.png'.format(size=size) + + def load_default_icons(): + for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]: + filename = 'icons/qutebrowser-{size}x{size}.png'.format(size=size) + pixmap = QPixmap() + pixmap.loadFromData(resources.read_file_binary(filename)) + if pixmap.isNull(): + log.init.warning("Failed to load: {}".format(filename)) + else: + fallback_icon.addPixmap(pixmap) + + icon_custom = config.val.app.icon + if icon_custom is not None: pixmap = QPixmap() - pixmap.loadFromData(resources.read_file_binary(filename)) - if pixmap.isNull(): - log.init.warning("Failed to load {}".format(filename)) - else: - fallback_icon.addPixmap(pixmap) + try: + pixmap.loadFromData(resources.read_absolute_file_binary(icon_custom)) + if pixmap.isNull(): + log.init.warning("Failed to load custom icon: {}. Falling back to default".format(icon_custom)) + load_default_icons() + else: + fallback_icon.addPixmap(pixmap) + except FileNotFoundError: + log.init.warning("Custom icon not found: {}. Falling back to default".format(icon_custom)) + load_default_icons() + else: + load_default_icons() + icon = QIcon.fromTheme('qutebrowser', fallback_icon) if icon.isNull(): log.init.warning("Failed to load icon") diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 9037ff214..43b81f3fd 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -970,8 +970,20 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): hints["x-kde-origin-name"] = origin_url_str if icon.isNull(): - filename = 'icons/qutebrowser-64x64.png' - icon = QImage.fromData(resources.read_file_binary(filename)) + icon_custom = config.val.app.icon + icon_default = 'icons/qutebrowser-64x64.png' + + def get_icon_data() -> bytes: + if icon_custom is None: + return resources.read_file_binary(icon_default) + + try: + return resources.read_absolute_file_binary(icon_custom) + except FileNotFoundError: + log.init.warning("Custom icon not found: {}. Falling back to default".format(icon_custom)) + return resources.read_file_binary(icon_default) + + icon = QImage.fromData(get_icon_data()) key = self._quirks.icon_key or "image-data" data = self._convert_image(icon) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index b221a70dc..9c9896593 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -4111,3 +4111,15 @@ logging.level.console: desc: >- Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used. + +## app + +app.icon: + type: + name: String + none_ok: true + default: null + restart: true + desc: >- + The absolute path to a custom icon for the qutebrowser app. + Note that this only sets the icon for the app while it is running. diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index 35fd62f75..ef9c22510 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -121,3 +121,18 @@ def read_file_binary(filename: str) -> bytes: path = _path(filename) with _keyerror_workaround(): return path.read_bytes() + + +def read_absolute_file_binary(filepath: str) -> bytes: + """Get the contents of an binary file at an absolute path. + This file may exist outside of qutebrowser-owned directories. + + Args: + filepath: The absolute filepath to open as string. + + Return: + The file contents as a bytes object. + """ + path = pathlib.Path(filepath) + with _keyerror_workaround(): + return path.read_bytes()