From cad3646b49f6c047ff3b914a8edff02da71ee23d Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 5 Feb 2025 22:19:16 +0100 Subject: [PATCH 1/4] Add config option for custom app icon. Takes an absolute path to an image file. Falls back to default icons when custom icon cannot be loaded. This resolves the following issues: https://github.com/qutebrowser/qutebrowser/issues/7714 https://github.com/qutebrowser/qutebrowser/issues/4009 It can be used by adding this line to the config.py: `c.app.icon = '/Users/luke/Pictures/browser.png'` --- .gitignore | 2 ++ qutebrowser/app.py | 35 +++++++++++++++---- qutebrowser/browser/webengine/notification.py | 17 +++++++-- qutebrowser/config/configdata.yml | 12 +++++++ qutebrowser/utils/resources.py | 15 ++++++++ 5 files changed, 72 insertions(+), 9 deletions(-) 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..9748461ba 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -168,15 +168,36 @@ 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 != None: + # Use the custom icon if possible 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..1d6187545 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -970,8 +970,21 @@ 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(): + if icon_custom == 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 f3432e5fd..a570b02c4 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -4094,3 +4094,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() From b163aef7f126b26548558e76cff8fc7296bd8531 Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 5 Feb 2025 22:24:44 +0100 Subject: [PATCH 2/4] Add missing type annotation --- qutebrowser/browser/webengine/notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 1d6187545..5bd462439 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -973,7 +973,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): icon_custom = config.val.app.icon icon_default = 'icons/qutebrowser-64x64.png' - def get_icon_data(): + def get_icon_data() -> bytes: if icon_custom == None: return resources.read_file_binary(icon_default) From 408fd930832b6200e43d13cf5199c02905e88332 Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 5 Feb 2025 22:29:10 +0100 Subject: [PATCH 3/4] Fix linting --- qutebrowser/app.py | 3 +-- qutebrowser/browser/webengine/notification.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 9748461ba..cd92ff9d9 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -168,7 +168,6 @@ def init(*, args: argparse.Namespace) -> None: def _init_icon(): """Initialize the icon of qutebrowser.""" - fallback_icon = QIcon() def load_default_icons(): @@ -182,7 +181,7 @@ def _init_icon(): fallback_icon.addPixmap(pixmap) icon_custom = config.val.app.icon - if icon_custom != None: + if icon_custom is not None: # Use the custom icon if possible pixmap = QPixmap() try: diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 5bd462439..43b81f3fd 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -974,7 +974,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): icon_default = 'icons/qutebrowser-64x64.png' def get_icon_data() -> bytes: - if icon_custom == None: + if icon_custom is None: return resources.read_file_binary(icon_default) try: @@ -982,8 +982,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): 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" From e24cb30e1e8b78e0d5923c9e891cba9622325861 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 6 Feb 2025 08:51:59 +0100 Subject: [PATCH 4/4] Remove comment --- qutebrowser/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index cd92ff9d9..c402c07e3 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -182,7 +182,6 @@ def _init_icon(): icon_custom = config.val.app.icon if icon_custom is not None: - # Use the custom icon if possible pixmap = QPixmap() try: pixmap.loadFromData(resources.read_absolute_file_binary(icon_custom))