Moved notifications to plugins (including sound)

This commit is contained in:
Dmitri Bogomolov 2017-03-10 01:00:54 +02:00
parent 24a9dc3b37
commit ef8f40ccc4
Signed by untrusted user: g1itch
GPG Key ID: 720A756F18DEED13
5 changed files with 160 additions and 109 deletions

View File

@ -254,7 +254,9 @@ if __name__ == "__main__":
install_requires=installRequires, install_requires=installRequires,
extras_require={ extras_require={
'qrcode': ['qrcode'], 'qrcode': ['qrcode'],
'pyopencl': ['pyopencl'] 'pyopencl': ['pyopencl'],
'notify2': ['pygobject', 'notify2'],
'sound:platform_system=="Windows"': ['winsound']
}, },
classifiers=[ classifiers=[
"License :: OSI Approved :: MIT License" "License :: OSI Approved :: MIT License"
@ -278,9 +280,16 @@ if __name__ == "__main__":
'popMenuYourIdentities.qrcode = ' 'popMenuYourIdentities.qrcode = '
'pybitmessage.plugins.qrcodeui [qrcode]' 'pybitmessage.plugins.qrcodeui [qrcode]'
], ],
# 'console_scripts': [ 'notification.message': [
# 'pybitmessage = pybitmessage.bitmessagemain:main' 'notify2 = pybitmessage.plugins.notification_notify2'
# ] '[notify2]'
],
'notification.sound': [
'fallback = pybitmessage.plugins.sound_playfile [sound]'
],
# 'console_scripts': [
# 'pybitmessage = pybitmessage.bitmessagemain:main'
# ]
}, },
scripts=['src/pybitmessage'], scripts=['src/pybitmessage'],
cmdclass={'install': InstallCmd} cmdclass={'install': InstallCmd}

View File

@ -4,8 +4,6 @@ try:
import gi import gi
gi.require_version('MessagingMenu', '1.0') gi.require_version('MessagingMenu', '1.0')
from gi.repository import MessagingMenu from gi.repository import MessagingMenu
gi.require_version('Notify', '0.7')
from gi.repository import Notify
withMessagingMenu = True withMessagingMenu = True
except (ImportError, ValueError): except (ImportError, ValueError):
MessagingMenu = None MessagingMenu = None
@ -62,9 +60,8 @@ import platform
import textwrap import textwrap
import debug import debug
import random import random
import subprocess
import string import string
import datetime from datetime import datetime, timedelta
from helper_sql import * from helper_sql import *
import helper_search import helper_search
import l10n import l10n
@ -153,7 +150,7 @@ class MyForm(settingsmixin.SMainWindow):
SOUND_CONNECTION_GREEN = 5 SOUND_CONNECTION_GREEN = 5
# the last time that a message arrival sound was played # the last time that a message arrival sound was played
lastSoundTime = datetime.datetime.now() - datetime.timedelta(days=1) lastSoundTime = datetime.now() - timedelta(days=1)
# the maximum frequency of message sounds in seconds # the maximum frequency of message sounds in seconds
maxSoundFrequencySec = 60 maxSoundFrequencySec = 60
@ -1368,39 +1365,41 @@ class MyForm(settingsmixin.SMainWindow):
# returns true if the given sound category is a connection sound # returns true if the given sound category is a connection sound
# rather than a received message sound # rather than a received message sound
def isConnectionSound(self, category): def isConnectionSound(self, category):
if (category is self.SOUND_CONNECTED or return category in (
category is self.SOUND_DISCONNECTED or self.SOUND_CONNECTED,
category is self.SOUND_CONNECTION_GREEN): self.SOUND_DISCONNECTED,
return True self.SOUND_CONNECTION_GREEN
return False )
# play a sound # play a sound
def playSound(self, category, label): def playSound(self, category, label):
# filename of the sound to be played # filename of the sound to be played
soundFilename = None soundFilename = None
# whether to play a sound or not def _choose_ext(basename):
play = True for ext in ('.wav', '.mp3', '.oga'):
if os.path.isfile(basename + ext):
return ext
# if the address had a known label in the address book # if the address had a known label in the address book
if label is not None: if label:
# Does a sound file exist for this particular contact? # Does a sound file exist for this particular contact?
if (os.path.isfile(state.appdata + 'sounds/' + label + '.wav') or soundFilename = state.appdata + 'sounds/' + label
os.path.isfile(state.appdata + 'sounds/' + label + '.mp3')): ext = _choose_ext(soundFilename)
soundFilename = state.appdata + 'sounds/' + label if not ext:
soundFilename = None
# Avoid making sounds more frequently than the threshold.
# This suppresses playing sounds repeatedly when there
# are many new messages
if (soundFilename is None and
not self.isConnectionSound(category)):
# elapsed time since the last sound was played
dt = datetime.datetime.now() - self.lastSoundTime
# suppress sounds which are more frequent than the threshold
if dt.total_seconds() < self.maxSoundFrequencySec:
play = False
if soundFilename is None: if soundFilename is None:
# Avoid making sounds more frequently than the threshold.
# This suppresses playing sounds repeatedly when there
# are many new messages
if not self.isConnectionSound(category):
# elapsed time since the last sound was played
dt = datetime.now() - self.lastSoundTime
# suppress sounds which are more frequent than the threshold
if dt.total_seconds() < self.maxSoundFrequencySec:
return
# the sound is for an address which exists in the address book # the sound is for an address which exists in the address book
if category is self.SOUND_KNOWN: if category is self.SOUND_KNOWN:
soundFilename = state.appdata + 'sounds/known' soundFilename = state.appdata + 'sounds/known'
@ -1417,73 +1416,53 @@ class MyForm(settingsmixin.SMainWindow):
elif category is self.SOUND_CONNECTION_GREEN: elif category is self.SOUND_CONNECTION_GREEN:
soundFilename = state.appdata + 'sounds/green' soundFilename = state.appdata + 'sounds/green'
if soundFilename is not None and play is True: if soundFilename is None:
if not self.isConnectionSound(category): return
# record the last time that a received message sound was played
self.lastSoundTime = datetime.datetime.now()
# if not wav then try mp3 format if not self.isConnectionSound(category):
if not os.path.isfile(soundFilename + '.wav'): # record the last time that a received message sound was played
soundFilename = soundFilename + '.mp3' self.lastSoundTime = datetime.now()
else:
soundFilename = soundFilename + '.wav'
if os.path.isfile(soundFilename): try: # try already known format
if 'linux' in sys.platform: soundFilename += ext
# Note: QSound was a nice idea but it didn't work except (TypeError, NameError):
if '.mp3' in soundFilename: ext = _choose_ext(soundFilename)
gst_available=False if not ext:
try: return
subprocess.call(["gst123", soundFilename],
stdin=subprocess.PIPE, soundFilename += ext
stdout=subprocess.PIPE)
gst_available=True self._player(soundFilename)
except:
logger.warning("WARNING: gst123 must be installed in order to play mp3 sounds")
if not gst_available:
try:
subprocess.call(["mpg123", soundFilename],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
gst_available=True
except:
logger.warning("WARNING: mpg123 must be installed in order to play mp3 sounds")
else:
try:
subprocess.call(["aplay", soundFilename],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
except:
logger.warning("WARNING: aplay must be installed in order to play WAV sounds")
elif sys.platform[0:3] == 'win':
# use winsound on Windows
import winsound
winsound.PlaySound(soundFilename, winsound.SND_FILENAME)
# initialise the message notifier # initialise the message notifier
def notifierInit(self): def notifierInit(self):
if withMessagingMenu: def _simple_notify(
Notify.init('pybitmessage') title, subtitle, category, label=None, icon=None):
# shows a notification
def notifierShow(self, title, subtitle, fromCategory, label):
self.playSound(fromCategory, label)
if withMessagingMenu:
n = Notify.Notification.new(
title, subtitle, 'notification-message-email')
try:
n.show()
except:
# n.show() has been known to throw this exception:
# gi._glib.GError: GDBus.Error:org.freedesktop.Notifications.
# MaxNotificationsExceeded: Exceeded maximum number of
# notifications
pass
return
else:
self.tray.showMessage(title, subtitle, 1, 2000) self.tray.showMessage(title, subtitle, 1, 2000)
self._notifier = _simple_notify
self._player = QtGui.QSound.play
if not get_plugins:
return
for plugin in get_plugins('notification.message'):
self._notifier = plugin
break
if not QtGui.QSound.isAvailable():
for plugin in get_plugins('notification.sound'):
self._player = plugin
break
else:
logger.warning("No sound player plugin found!")
def notifierShow(
self, title, subtitle, category, label=None, icon=None):
self.playSound(category, label)
self._notifier(
unicode(title), unicode(subtitle), category, label, icon)
# tree # tree
def treeWidgetKeyPressEvent(self, event): def treeWidgetKeyPressEvent(self, event):
return self.handleKeyPress(event, self.getCurrentTreeWidget()) return self.handleKeyPress(event, self.getCurrentTreeWidget())
@ -1663,15 +1642,18 @@ class MyForm(settingsmixin.SMainWindow):
def setStatusIcon(self, color): def setStatusIcon(self, color):
# print 'setting status icon color' # print 'setting status icon color'
_notifications_enabled = not BMConfigParser().getboolean(
'bitmessagesettings', 'hidetrayconnectionnotifications')
if color == 'red': if color == 'red':
self.pushButtonStatusIcon.setIcon( self.pushButtonStatusIcon.setIcon(
QIcon(":/newPrefix/images/redicon.png")) QIcon(":/newPrefix/images/redicon.png"))
shared.statusIconColor = 'red' shared.statusIconColor = 'red'
# if the connection is lost then show a notification # if the connection is lost then show a notification
if self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'): if self.connected and _notifications_enabled:
self.notifierShow('Bitmessage', unicode(_translate( self.notifierShow(
"MainWindow", "Connection lost").toUtf8(),'utf-8'), 'Bitmessage',
self.SOUND_DISCONNECTED, None) _translate("MainWindow", "Connection lost"),
self.SOUND_DISCONNECTED)
if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \ if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \
BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none": BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none":
self.statusBar().showMessage(_translate( self.statusBar().showMessage(_translate(
@ -1689,10 +1671,11 @@ class MyForm(settingsmixin.SMainWindow):
":/newPrefix/images/yellowicon.png")) ":/newPrefix/images/yellowicon.png"))
shared.statusIconColor = 'yellow' shared.statusIconColor = 'yellow'
# if a new connection has been established then show a notification # if a new connection has been established then show a notification
if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'): if not self.connected and _notifications_enabled:
self.notifierShow('Bitmessage', unicode(_translate( self.notifierShow(
"MainWindow", "Connected").toUtf8(),'utf-8'), 'Bitmessage',
self.SOUND_CONNECTED, None) _translate("MainWindow", "Connected"),
self.SOUND_CONNECTED)
self.connected = True self.connected = True
if self.actionStatus is not None: if self.actionStatus is not None:
@ -1705,10 +1688,11 @@ class MyForm(settingsmixin.SMainWindow):
self.pushButtonStatusIcon.setIcon( self.pushButtonStatusIcon.setIcon(
QIcon(":/newPrefix/images/greenicon.png")) QIcon(":/newPrefix/images/greenicon.png"))
shared.statusIconColor = 'green' shared.statusIconColor = 'green'
if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'): if not self.connected and _notifications_enabled:
self.notifierShow('Bitmessage', unicode(_translate( self.notifierShow(
"MainWindow", "Connected").toUtf8(),'utf-8'), 'Bitmessage',
self.SOUND_CONNECTION_GREEN, None) _translate("MainWindow", "Connected"),
self.SOUND_CONNECTION_GREEN)
self.connected = True self.connected = True
if self.actionStatus is not None: if self.actionStatus is not None:
@ -2253,8 +2237,14 @@ class MyForm(settingsmixin.SMainWindow):
else: else:
acct = ret acct = ret
self.propagateUnreadCount(acct.address) self.propagateUnreadCount(acct.address)
if BMConfigParser().getboolean('bitmessagesettings', 'showtraynotifications'): if BMConfigParser().getboolean(
self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(acct.fromLabel, 'utf-8'), self.SOUND_UNKNOWN, None) 'bitmessagesettings', 'showtraynotifications'):
self.notifierShow(
_translate("MainWindow", "New Message"),
_translate("MainWindow", "From %1").arg(
unicode(acct.fromLabel, 'utf-8')),
self.SOUND_UNKNOWN
)
if self.getCurrentAccount() is not None and ((self.getCurrentFolder(treeWidget) != "inbox" and self.getCurrentFolder(treeWidget) is not None) or self.getCurrentAccount(treeWidget) != acct.address): if self.getCurrentAccount() is not None and ((self.getCurrentFolder(treeWidget) != "inbox" and self.getCurrentFolder(treeWidget) is not None) or self.getCurrentAccount(treeWidget) != acct.address):
# Ubuntu should notify of new message irespective of whether it's in current message list or not # Ubuntu should notify of new message irespective of whether it's in current message list or not
self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel) self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel)

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
import gi
gi.require_version('Notify', '0.7')
from gi.repository import Notify
Notify.init('pybitmessage')
def connect_plugin(title, subtitle, category, label, icon):
if not icon:
icon = 'mail-message-new' if category == 2 else 'pybitmessage'
Notify.Notification.new(title, subtitle, icon).show()

View File

@ -3,10 +3,12 @@
import pkg_resources import pkg_resources
def get_plugins(group, point, name=None): def get_plugins(group, point='', name=None):
for plugin in pkg_resources.iter_entry_points(group): for plugin in pkg_resources.iter_entry_points(group):
if plugin.name.startswith(point): if plugin.name == name or plugin.name.startswith(point):
try: try:
yield plugin.load().connect_plugin yield plugin.load().connect_plugin
except (AttributeError, pkg_resources.DistributionNotFound): except (AttributeError,
pkg_resources.DistributionNotFound,
pkg_resources.UnknownExtra):
continue continue

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
try:
import winsound
def connect_plugin(sound_file):
winsound.PlaySound(sound_file, winsound.SND_FILENAME)
except ImportError:
import os
import subprocess
play_cmd = {}
def connect_plugin(sound_file):
global play_cmd
ext = os.path.splitext(sound_file)[-1]
try:
subprocess.call([play_cmd[ext], sound_file])
return
except (KeyError, AttributeError):
pass
programs = ['gst123']
if ext == '.wav':
programs.append('aplay')
elif ext == '.mp3':
programs += ['mpg123', 'mpg321', 'mpg321-mpg123']
for cmd in programs:
try:
subprocess.call([cmd, sound_file])
except OSError:
pass # log here!
else:
play_cmd[ext] = cmd
break