# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2020 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
"""Different ways of showing notifications to the user.
Our notification implementation consists of two different parts:
- NotificationBridgePresenter, the object we set as notification presenter on
QWebEngineProfiles on startup.
- Adapters (subclassing from AbstractNotificationAdapter) which get called by the bridge
and contain the code to show notifications using different means (e.g. a systray icon
or DBus).
Adapters are initialized lazily when the bridge gets the first notification. This makes
sure we don't block while e.g. talking to DBus during startup, but only when needed.
If an adapter raises Error during __init__, the bridge assumes that it's unavailable and
tries the next one in a list of candidates.
Useful test pages:
- https://tests.peter.sh/notification-generator/
- https://www.bennish.net/web-notifications.html
- https://web-push-book.gauntface.com/demos/notification-examples/
- tests/end2end/data/javascript/notifications.html
"""
import os
import signal
import html
import dataclasses
import itertools
import functools
import subprocess
from typing import Any, List, Dict, Optional, Iterator, TYPE_CHECKING
from PyQt5.QtCore import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot,
pyqtSignal, QTimer, QProcess, QUrl)
from PyQt5.QtGui import QImage, QIcon, QPixmap
from PyQt5.QtDBus import (QDBusConnection, QDBusInterface, QDBus, QDBusServiceWatcher,
QDBusArgument, QDBusMessage, QDBusError)
from PyQt5.QtWidgets import QSystemTrayIcon
if TYPE_CHECKING:
# putting these behind TYPE_CHECKING also means this module is importable
# on installs that don't have these
from PyQt5.QtWebEngineCore import QWebEngineNotification
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
from qutebrowser.config import config
from qutebrowser.misc import objects
from qutebrowser.utils import qtutils, log, utils, debug, message
bridge: Optional['NotificationBridgePresenter'] = None
def init() -> None:
"""Initialize the DBus notification presenter, if applicable.
If the user doesn't want a notification presenter or it's not supported,
this method does nothing.
Always succeeds, but might log an error.
"""
if config.val.content.notifications.presenter == "qt":
# In theory, we could somehow postpone the install if the user switches to "qt"
# at a later point in time. However, doing so is probably too complex compared
# to its usefulness.
return
if not qtutils.version_check('5.14'):
return
global bridge
bridge = NotificationBridgePresenter()
class Error(Exception):
"""Raised when something goes wrong with notifications."""
class AbstractNotificationAdapter(QObject):
"""An adapter taking notifications and displaying them.
This can happen via different mechanisms, e.g. a system tray icon or DBus.
"""
# A short name for the adapter, shown in errors. Should be the same as the
# associated content.notification.presenter setting.
NAME: str
# Emitted by the adapter when the notification with the given ID was closed or
# clicked by the user.
close_id = pyqtSignal(int)
click_id = pyqtSignal(int)