Moved notifications to plugins (including sound)
This commit is contained in:
parent
24a9dc3b37
commit
ef8f40ccc4
17
setup.py
17
setup.py
|
@ -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}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
13
src/plugins/notification_notify2.py
Normal file
13
src/plugins/notification_notify2.py
Normal 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()
|
|
@ -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
|
||||||
|
|
37
src/plugins/sound_playfile.py
Normal file
37
src/plugins/sound_playfile.py
Normal 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
|
Loading…
Reference in New Issue
Block a user