%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/ |
| Current File : //lib/calibre/calibre/gui2/notify.py |
#!/usr/bin/env python3
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys
from contextlib import suppress
from functools import lru_cache
from calibre.constants import DEBUG, __appname__, get_osx_version, islinux, ismacos
class Notifier:
DEFAULT_TIMEOUT = 5000
def get_msg_parms(self, timeout, body, summary):
if summary is None:
summary = 'calibre'
if timeout == 0:
timeout = self.DEFAULT_TIMEOUT
return timeout, body, summary
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
raise NotImplementedError('implement in subclass')
@lru_cache(maxsize=2)
def icon(data=False):
return I('lt.png', data=data)
class DBUSNotifier(Notifier):
def __init__(self):
self.initialized = False
def initialize(self):
from jeepney.io.blocking import open_dbus_connection
if self.initialized:
return
self.initialized = True
self.ok = False
try:
self.connection = open_dbus_connection(bus='SESSION')
except Exception:
return
with suppress(Exception):
self.ok = self.initialize_fdo()
if self.ok:
self.notify = self.fdo_notify
return
if DEBUG:
print('Failed to connect to FDO Notifications service', file=sys.stderr)
with suppress(Exception):
self.ok = self.initialize_portal()
if self.ok:
self.notify = self.portal_notify
else:
print('Failed to connect to Portal Notifications service', file=sys.stderr)
def initialize_fdo(self):
from jeepney import DBusAddress, MessageType, new_method_call
self.address = DBusAddress(
'/org/freedesktop/Notifications',
bus_name='org.freedesktop.Notifications',
interface='org.freedesktop.Notifications')
msg = new_method_call(self.address, 'GetCapabilities')
reply = self.connection.send_and_get_reply(msg)
return bool(reply and reply.header.message_type is MessageType.method_return)
def initialize_portal(self):
from jeepney import DBusAddress, MessageType, Properties
self.address = DBusAddress(
'/org/freedesktop/portal/desktop',
bus_name='org.freedesktop.portal.Desktop',
interface='org.freedesktop.portal.Notification')
p = Properties(self.address)
msg = p.get('version')
reply = self.connection.send_and_get_reply(msg)
return bool(reply and reply.header.message_type is MessageType.method_return and reply.body[0][1] >= 1)
def fdo_notify(self, body, summary=None, replaces_id=None, timeout=0):
from jeepney import new_method_call
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
msg = new_method_call(
self.address, 'Notify', 'susssasa{sv}i',
(__appname__,
replaces_id or 0,
icon(),
summary,
body,
[], {}, # Actions, hints
timeout,
))
try:
self.connection.send(msg)
except Exception:
import traceback
traceback.print_exc()
def portal_notify(self, body, summary=None, replaces_id=None, timeout=0):
from jeepney import new_method_call
_, body, summary = self.get_msg_parms(timeout, body, summary)
# Note: This backend does not natively support the notion of timeouts
#
# While the effect may be emulated by manually withdrawing the notifi-
# cation from the Calibre side, this resulted in a less than optimal
# User Experience. Based on this, KG decided it to be better to not
# support timeouts at all for this backend.
#
# See discussion at https://github.com/kovidgoyal/calibre/pull/1268.
# For the icon: This should instead just send the themable icon name
#
# Doing that however, requires Calibre to first be converted to use
# its AppID everywhere and then we still need a fallback for portable
# installations.
msg = new_method_call(
self.address, 'AddNotification', 'sa{sv}', (
str(replaces_id or 0),
{
"title": ('s', summary),
"body": ('s', body),
"icon": (
'(sv)',
(
"bytes",
('ay', icon(data=True))
)
),
}))
try:
self.connection.send(msg)
except Exception:
import traceback
traceback.print_exc()
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
self.initialize()
if not self.ok:
if DEBUG:
print('Failed to connect to any notification service', file=sys.stderr)
return
self.notify(body, summary, replaces_id, timeout)
class QtNotifier(Notifier):
def __init__(self, systray=None):
self.systray = systray
self.ok = self.systray is not None and self.systray.supportsMessages()
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
from qt.core import QSystemTrayIcon
if self.systray is not None:
try:
hide = False
try:
if not isinstance(body, str):
body = body.decode('utf-8')
if ismacos and not self.systray.isVisible():
self.systray.show()
hide = True
self.systray.showMessage(summary, body, QSystemTrayIcon.MessageIcon.Information,
timeout)
finally:
if hide:
self.systray.hide()
except Exception:
pass
class DummyNotifier(Notifier):
ok = True
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
pass
class AppleNotifier(Notifier):
def __init__(self):
from calibre_extensions import cocoa
self.cocoa = cocoa
self.ok = True
def notify(self, body, summary):
if summary:
title, informative_text = summary, body
else:
title, informative_text = body, None
self.cocoa.send_notification(None, title, informative_text)
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
if self.ok:
try:
self.notify(body, summary)
except Exception:
import traceback
traceback.print_exc()
def get_notifier(systray=None):
ans = None
if islinux:
ans = DBUSNotifier()
elif ismacos:
if get_osx_version() >= (10, 8, 0):
ans = AppleNotifier()
if not ans.ok:
ans = DummyNotifier()
else:
# We dont use Qt's systray based notifier as it uses Growl and is
# broken with different versions of Growl
ans = DummyNotifier()
if ans is None:
ans = QtNotifier(systray)
if not ans.ok:
ans = None
return ans
def hello():
n = get_notifier()
n('hello')
if __name__ == '__main__':
hello()