From ef8f40ccc4abb677e96243ab096d0c818c80e8f3 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 10 Mar 2017 01:00:54 +0200 Subject: [PATCH 01/11] Moved notifications to plugins (including sound) --- setup.py | 17 ++- src/bitmessageqt/__init__.py | 194 +++++++++++++--------------- src/plugins/notification_notify2.py | 13 ++ src/plugins/plugin.py | 8 +- src/plugins/sound_playfile.py | 37 ++++++ 5 files changed, 160 insertions(+), 109 deletions(-) create mode 100644 src/plugins/notification_notify2.py create mode 100644 src/plugins/sound_playfile.py diff --git a/setup.py b/setup.py index 9357284b..c7f7fab5 100644 --- a/setup.py +++ b/setup.py @@ -254,7 +254,9 @@ if __name__ == "__main__": install_requires=installRequires, extras_require={ 'qrcode': ['qrcode'], - 'pyopencl': ['pyopencl'] + 'pyopencl': ['pyopencl'], + 'notify2': ['pygobject', 'notify2'], + 'sound:platform_system=="Windows"': ['winsound'] }, classifiers=[ "License :: OSI Approved :: MIT License" @@ -278,9 +280,16 @@ if __name__ == "__main__": 'popMenuYourIdentities.qrcode = ' 'pybitmessage.plugins.qrcodeui [qrcode]' ], - # 'console_scripts': [ - # 'pybitmessage = pybitmessage.bitmessagemain:main' - # ] + 'notification.message': [ + 'notify2 = pybitmessage.plugins.notification_notify2' + '[notify2]' + ], + 'notification.sound': [ + 'fallback = pybitmessage.plugins.sound_playfile [sound]' + ], + # 'console_scripts': [ + # 'pybitmessage = pybitmessage.bitmessagemain:main' + # ] }, scripts=['src/pybitmessage'], cmdclass={'install': InstallCmd} diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 014831bf..44dcb8ec 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -4,8 +4,6 @@ try: import gi gi.require_version('MessagingMenu', '1.0') from gi.repository import MessagingMenu - gi.require_version('Notify', '0.7') - from gi.repository import Notify withMessagingMenu = True except (ImportError, ValueError): MessagingMenu = None @@ -62,9 +60,8 @@ import platform import textwrap import debug import random -import subprocess import string -import datetime +from datetime import datetime, timedelta from helper_sql import * import helper_search import l10n @@ -153,7 +150,7 @@ class MyForm(settingsmixin.SMainWindow): SOUND_CONNECTION_GREEN = 5 # 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 maxSoundFrequencySec = 60 @@ -1368,39 +1365,41 @@ class MyForm(settingsmixin.SMainWindow): # returns true if the given sound category is a connection sound # rather than a received message sound def isConnectionSound(self, category): - if (category is self.SOUND_CONNECTED or - category is self.SOUND_DISCONNECTED or - category is self.SOUND_CONNECTION_GREEN): - return True - return False + return category in ( + self.SOUND_CONNECTED, + self.SOUND_DISCONNECTED, + self.SOUND_CONNECTION_GREEN + ) # play a sound def playSound(self, category, label): # filename of the sound to be played soundFilename = None - # whether to play a sound or not - play = True + def _choose_ext(basename): + 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 label is not None: + if label: # Does a sound file exist for this particular contact? - if (os.path.isfile(state.appdata + 'sounds/' + label + '.wav') or - os.path.isfile(state.appdata + 'sounds/' + label + '.mp3')): - soundFilename = state.appdata + 'sounds/' + label - - # 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 + soundFilename = state.appdata + 'sounds/' + label + ext = _choose_ext(soundFilename) + if not ext: + soundFilename = 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 if category is self.SOUND_KNOWN: soundFilename = state.appdata + 'sounds/known' @@ -1415,75 +1414,55 @@ class MyForm(settingsmixin.SMainWindow): soundFilename = state.appdata + 'sounds/disconnected' # sound when the connection status becomes 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 not self.isConnectionSound(category): - # record the last time that a received message sound was played - self.lastSoundTime = datetime.datetime.now() + if soundFilename is None: + return - # if not wav then try mp3 format - if not os.path.isfile(soundFilename + '.wav'): - soundFilename = soundFilename + '.mp3' - else: - soundFilename = soundFilename + '.wav' + if not self.isConnectionSound(category): + # record the last time that a received message sound was played + self.lastSoundTime = datetime.now() - if os.path.isfile(soundFilename): - if 'linux' in sys.platform: - # Note: QSound was a nice idea but it didn't work - if '.mp3' in soundFilename: - gst_available=False - try: - subprocess.call(["gst123", soundFilename], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - gst_available=True - 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) + try: # try already known format + soundFilename += ext + except (TypeError, NameError): + ext = _choose_ext(soundFilename) + if not ext: + return + + soundFilename += ext + + self._player(soundFilename) # initialise the message notifier def notifierInit(self): - if withMessagingMenu: - Notify.init('pybitmessage') - - # 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: + def _simple_notify( + title, subtitle, category, label=None, icon=None): 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 def treeWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentTreeWidget()) @@ -1663,15 +1642,18 @@ class MyForm(settingsmixin.SMainWindow): def setStatusIcon(self, color): # print 'setting status icon color' + _notifications_enabled = not BMConfigParser().getboolean( + 'bitmessagesettings', 'hidetrayconnectionnotifications') if color == 'red': self.pushButtonStatusIcon.setIcon( QIcon(":/newPrefix/images/redicon.png")) shared.statusIconColor = 'red' # if the connection is lost then show a notification - if self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'): - self.notifierShow('Bitmessage', unicode(_translate( - "MainWindow", "Connection lost").toUtf8(),'utf-8'), - self.SOUND_DISCONNECTED, None) + if self.connected and _notifications_enabled: + self.notifierShow( + 'Bitmessage', + _translate("MainWindow", "Connection lost"), + self.SOUND_DISCONNECTED) if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \ BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none": self.statusBar().showMessage(_translate( @@ -1689,10 +1671,11 @@ class MyForm(settingsmixin.SMainWindow): ":/newPrefix/images/yellowicon.png")) shared.statusIconColor = 'yellow' # if a new connection has been established then show a notification - if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'): - self.notifierShow('Bitmessage', unicode(_translate( - "MainWindow", "Connected").toUtf8(),'utf-8'), - self.SOUND_CONNECTED, None) + if not self.connected and _notifications_enabled: + self.notifierShow( + 'Bitmessage', + _translate("MainWindow", "Connected"), + self.SOUND_CONNECTED) self.connected = True if self.actionStatus is not None: @@ -1705,10 +1688,11 @@ class MyForm(settingsmixin.SMainWindow): self.pushButtonStatusIcon.setIcon( QIcon(":/newPrefix/images/greenicon.png")) shared.statusIconColor = 'green' - if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'): - self.notifierShow('Bitmessage', unicode(_translate( - "MainWindow", "Connected").toUtf8(),'utf-8'), - self.SOUND_CONNECTION_GREEN, None) + if not self.connected and _notifications_enabled: + self.notifierShow( + 'Bitmessage', + _translate("MainWindow", "Connected"), + self.SOUND_CONNECTION_GREEN) self.connected = True if self.actionStatus is not None: @@ -2253,8 +2237,14 @@ class MyForm(settingsmixin.SMainWindow): else: acct = ret self.propagateUnreadCount(acct.address) - if BMConfigParser().getboolean('bitmessagesettings', 'showtraynotifications'): - 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) + if BMConfigParser().getboolean( + '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): # Ubuntu should notify of new message irespective of whether it's in current message list or not self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel) diff --git a/src/plugins/notification_notify2.py b/src/plugins/notification_notify2.py new file mode 100644 index 00000000..90f09df3 --- /dev/null +++ b/src/plugins/notification_notify2.py @@ -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() diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 288be48a..395f0e9d 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -3,10 +3,12 @@ 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): - if plugin.name.startswith(point): + if plugin.name == name or plugin.name.startswith(point): try: yield plugin.load().connect_plugin - except (AttributeError, pkg_resources.DistributionNotFound): + except (AttributeError, + pkg_resources.DistributionNotFound, + pkg_resources.UnknownExtra): continue diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py new file mode 100644 index 00000000..03a164af --- /dev/null +++ b/src/plugins/sound_playfile.py @@ -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 From be716bf228685f11742dcf15a0d9189dddd3d6a8 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 10 Mar 2017 16:45:46 +0200 Subject: [PATCH 02/11] Improved and documented plugin module --- src/bitmessageqt/__init__.py | 16 +++++++++------- src/plugins/plugin.py | 29 +++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 44dcb8ec..39202d17 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -86,7 +86,7 @@ import throttle from version import softwareVersion try: - from plugins.plugin import get_plugins + from plugins.plugin import get_plugin, get_plugins except ImportError: get_plugins = False @@ -1441,19 +1441,21 @@ class MyForm(settingsmixin.SMainWindow): self.tray.showMessage(title, subtitle, 1, 2000) self._notifier = _simple_notify + # does nothing if isAvailable returns false self._player = QtGui.QSound.play if not get_plugins: return - for plugin in get_plugins('notification.message'): - self._notifier = plugin - break + _plugin = get_plugin('notification.message') + if _plugin: + self._notifier = _plugin if not QtGui.QSound.isAvailable(): - for plugin in get_plugins('notification.sound'): - self._player = plugin - break + _plugin = get_plugin( + 'notification.sound', 'file', fallback='file.fallback') + if _plugin: + self._player = _plugin else: logger.warning("No sound player plugin found!") diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 395f0e9d..ba14b836 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -3,12 +3,33 @@ import pkg_resources -def get_plugins(group, point='', name=None): - for plugin in pkg_resources.iter_entry_points(group): - if plugin.name == name or plugin.name.startswith(point): +def get_plugins(group, point='', name=None, fallback=None): + """ + Iterate through plugins (`connect_plugin` attribute of entry point) + which name starts with `point` or equals to `name`. + If `fallback` kwarg specified, plugin with that name yield last. + """ + for ep in pkg_resources.iter_entry_points(group): + if name and ep.name == name or ep.name.startswith(point): try: - yield plugin.load().connect_plugin + plugin = ep.load().connect_plugin + if ep.name == fallback: + _fallback = plugin + else: + yield plugin except (AttributeError, + ImportError, + ValueError, pkg_resources.DistributionNotFound, pkg_resources.UnknownExtra): continue + try: + yield _fallback + except NameError: + pass + + +def get_plugin(*args, **kwargs): + """Returns first available plugin `from get_plugins()` if any.""" + for plugin in get_plugins(*args, **kwargs): + return plugin From 91eb75b1407ad18cb4919c9786d6d99f32b8caac Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 10 Mar 2017 18:34:31 +0200 Subject: [PATCH 03/11] gst-play-1.0 is another player program which bundled with gstreamer 1.0 --- src/plugins/sound_playfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py index 03a164af..65d5dda9 100644 --- a/src/plugins/sound_playfile.py +++ b/src/plugins/sound_playfile.py @@ -22,7 +22,7 @@ except ImportError: except (KeyError, AttributeError): pass - programs = ['gst123'] + programs = ['gst123', 'gst-play-1.0'] if ext == '.wav': programs.append('aplay') elif ext == '.mp3': From 84a903f116e99403023de753b1db980312446b8f Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sat, 11 Mar 2017 13:33:51 +0200 Subject: [PATCH 04/11] Redirected output of the player programs to /dev/null --- src/plugins/sound_playfile.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py index 65d5dda9..c8216d07 100644 --- a/src/plugins/sound_playfile.py +++ b/src/plugins/sound_playfile.py @@ -12,13 +12,17 @@ except ImportError: play_cmd = {} + def _subprocess(*args): + FNULL = open(os.devnull, 'wb') + subprocess.call( + args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) + def connect_plugin(sound_file): global play_cmd ext = os.path.splitext(sound_file)[-1] try: - subprocess.call([play_cmd[ext], sound_file]) - return + return _subprocess(play_cmd[ext], sound_file) except (KeyError, AttributeError): pass @@ -29,7 +33,7 @@ except ImportError: programs += ['mpg123', 'mpg321', 'mpg321-mpg123'] for cmd in programs: try: - subprocess.call([cmd, sound_file]) + _subprocess(cmd, sound_file) except OSError: pass # log here! else: From 289a6c5bfa9218e0bec83bbc970160d56525ff5b Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sat, 11 Mar 2017 13:40:33 +0200 Subject: [PATCH 05/11] Added support for sound notification plugins which use the desktop sound theme, with pycanberra for example. Plugin name should start with 'theme' in that case, whereas the name of plugins playing the sound file starts with 'file'. --- setup.py | 3 ++- src/bitmessageqt/__init__.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index c7f7fab5..eb79c3b7 100644 --- a/setup.py +++ b/setup.py @@ -285,7 +285,8 @@ if __name__ == "__main__": '[notify2]' ], 'notification.sound': [ - 'fallback = pybitmessage.plugins.sound_playfile [sound]' + 'file.fallback = pybitmessage.plugins.sound_playfile' + '[sound]' ], # 'console_scripts': [ # 'pybitmessage = pybitmessage.bitmessagemain:main' diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 39202d17..fd0bcdc1 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1387,6 +1387,7 @@ class MyForm(settingsmixin.SMainWindow): soundFilename = state.appdata + 'sounds/' + label ext = _choose_ext(soundFilename) if not ext: + category = self.SOUND_KNOWN soundFilename = None if soundFilename is None: @@ -1417,6 +1418,7 @@ class MyForm(settingsmixin.SMainWindow): soundFilename = state.appdata + 'sounds/green' if soundFilename is None: + logger.warning("Probably wrong category number in playSound()") return if not self.isConnectionSound(category): @@ -1428,7 +1430,10 @@ class MyForm(settingsmixin.SMainWindow): except (TypeError, NameError): ext = _choose_ext(soundFilename) if not ext: - return + try: # if no user sound file found try to play from theme + return self._theme_player(category, label) + except TypeError: + return soundFilename += ext @@ -1450,6 +1455,10 @@ class MyForm(settingsmixin.SMainWindow): _plugin = get_plugin('notification.message') if _plugin: self._notifier = _plugin + else: + logger.warning("No notification.message plugin found") + + self._theme_player = get_plugin('notification.sound', 'theme') if not QtGui.QSound.isAvailable(): _plugin = get_plugin( @@ -1457,7 +1466,7 @@ class MyForm(settingsmixin.SMainWindow): if _plugin: self._player = _plugin else: - logger.warning("No sound player plugin found!") + logger.warning("No notification.sound plugin found") def notifierShow( self, title, subtitle, category, label=None, icon=None): From cd8171887195edd089826be33e19dc68e836867d Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 15 Mar 2017 15:56:47 +0200 Subject: [PATCH 06/11] Moved sound category constants to the separate module `sound` for importing from the sound theme plugins. --- src/bitmessageqt/__init__.py | 42 +++++++++++------------------------- src/bitmessageqt/sound.py | 19 ++++++++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 src/bitmessageqt/sound.py diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index fd0bcdc1..8d126812 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -84,6 +84,7 @@ import state from statusbar import BMStatusBar import throttle from version import softwareVersion +import sound try: from plugins.plugin import get_plugin, get_plugins @@ -141,14 +142,6 @@ def change_translation(newlocale): class MyForm(settingsmixin.SMainWindow): - # sound type constants - SOUND_NONE = 0 - SOUND_KNOWN = 1 - SOUND_UNKNOWN = 2 - SOUND_CONNECTED = 3 - SOUND_DISCONNECTED = 4 - SOUND_CONNECTION_GREEN = 5 - # the last time that a message arrival sound was played lastSoundTime = datetime.now() - timedelta(days=1) @@ -1362,15 +1355,6 @@ class MyForm(settingsmixin.SMainWindow): # update the menu entries self.ubuntuMessagingMenuUnread(drawAttention) - # returns true if the given sound category is a connection sound - # rather than a received message sound - def isConnectionSound(self, category): - return category in ( - self.SOUND_CONNECTED, - self.SOUND_DISCONNECTED, - self.SOUND_CONNECTION_GREEN - ) - # play a sound def playSound(self, category, label): # filename of the sound to be played @@ -1387,14 +1371,14 @@ class MyForm(settingsmixin.SMainWindow): soundFilename = state.appdata + 'sounds/' + label ext = _choose_ext(soundFilename) if not ext: - category = self.SOUND_KNOWN + category = sound.SOUND_KNOWN soundFilename = 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): + if not sound.is_connection_sound(category): # elapsed time since the last sound was played dt = datetime.now() - self.lastSoundTime # suppress sounds which are more frequent than the threshold @@ -1402,26 +1386,26 @@ class MyForm(settingsmixin.SMainWindow): return # the sound is for an address which exists in the address book - if category is self.SOUND_KNOWN: + if category is sound.SOUND_KNOWN: soundFilename = state.appdata + 'sounds/known' # the sound is for an unknown address - elif category is self.SOUND_UNKNOWN: + elif category is sound.SOUND_UNKNOWN: soundFilename = state.appdata + 'sounds/unknown' # initial connection sound - elif category is self.SOUND_CONNECTED: + elif category is sound.SOUND_CONNECTED: soundFilename = state.appdata + 'sounds/connected' # disconnected sound - elif category is self.SOUND_DISCONNECTED: + elif category is sound.SOUND_DISCONNECTED: soundFilename = state.appdata + 'sounds/disconnected' # sound when the connection status becomes green - elif category is self.SOUND_CONNECTION_GREEN: + elif category is sound.SOUND_CONNECTION_GREEN: soundFilename = state.appdata + 'sounds/green' if soundFilename is None: logger.warning("Probably wrong category number in playSound()") return - if not self.isConnectionSound(category): + if not sound.is_connection_sound(category): # record the last time that a received message sound was played self.lastSoundTime = datetime.now() @@ -1664,7 +1648,7 @@ class MyForm(settingsmixin.SMainWindow): self.notifierShow( 'Bitmessage', _translate("MainWindow", "Connection lost"), - self.SOUND_DISCONNECTED) + sound.SOUND_DISCONNECTED) if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \ BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none": self.statusBar().showMessage(_translate( @@ -1686,7 +1670,7 @@ class MyForm(settingsmixin.SMainWindow): self.notifierShow( 'Bitmessage', _translate("MainWindow", "Connected"), - self.SOUND_CONNECTED) + sound.SOUND_CONNECTED) self.connected = True if self.actionStatus is not None: @@ -1703,7 +1687,7 @@ class MyForm(settingsmixin.SMainWindow): self.notifierShow( 'Bitmessage', _translate("MainWindow", "Connected"), - self.SOUND_CONNECTION_GREEN) + sound.SOUND_CONNECTION_GREEN) self.connected = True if self.actionStatus is not None: @@ -2254,7 +2238,7 @@ class MyForm(settingsmixin.SMainWindow): _translate("MainWindow", "New Message"), _translate("MainWindow", "From %1").arg( unicode(acct.fromLabel, 'utf-8')), - self.SOUND_UNKNOWN + sound.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): # Ubuntu should notify of new message irespective of whether it's in current message list or not diff --git a/src/bitmessageqt/sound.py b/src/bitmessageqt/sound.py new file mode 100644 index 00000000..4b6aaf00 --- /dev/null +++ b/src/bitmessageqt/sound.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# sound type constants +SOUND_NONE = 0 +SOUND_KNOWN = 1 +SOUND_UNKNOWN = 2 +SOUND_CONNECTED = 3 +SOUND_DISCONNECTED = 4 +SOUND_CONNECTION_GREEN = 5 + + +# returns true if the given sound category is a connection sound +# rather than a received message sound +def is_connection_sound(category): + return category in ( + SOUND_CONNECTED, + SOUND_DISCONNECTED, + SOUND_CONNECTION_GREEN + ) From c8a47b988fb3c464d8d6e86b8032f09211314910 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 23 Mar 2017 19:04:56 +0200 Subject: [PATCH 07/11] Moved "Ubuntu" MessagingMenu code into indicator_libmessaging plugin --- src/bitmessageqt/__init__.py | 222 +++++--------------------- src/plugins/indicator_libmessaging.py | 71 ++++++++ 2 files changed, 109 insertions(+), 184 deletions(-) create mode 100644 src/plugins/indicator_libmessaging.py diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 8d126812..942ed3de 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1,19 +1,10 @@ from debug import logger -withMessagingMenu = False -try: - import gi - gi.require_version('MessagingMenu', '1.0') - from gi.repository import MessagingMenu - withMessagingMenu = True -except (ImportError, ValueError): - MessagingMenu = None try: from PyQt4 import QtCore, QtGui from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4.QtNetwork import QLocalSocket, QLocalServer - except Exception as err: logmsg = 'PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).' logger.critical(logmsg, exc_info=True) @@ -30,7 +21,7 @@ import shared from bitmessageui import * from bmconfigparser import BMConfigParser import defaults -from namecoin import namecoinConnection, ensureNamecoinOptions +from namecoin import namecoinConnection from newaddressdialog import * from newaddresswizard import * from messageview import MessageView @@ -51,12 +42,10 @@ from iconglossary import * from connect import * import locale import sys -from time import strftime, localtime, gmtime import time import os import hashlib from pyelliptic.openssl import OpenSSL -import platform import textwrap import debug import random @@ -66,15 +55,13 @@ from helper_sql import * import helper_search import l10n import openclpow -import types -from utils import * -from collections import OrderedDict +from utils import str_broadcast_subscribers, avatarize from account import * -from class_objectHashHolder import objectHashHolder -from class_singleWorker import singleWorker from dialogs import AddAddressDialog from helper_generic import powQueueSize -from inventory import Inventory, PendingDownloadQueue, PendingUpload, PendingUploadDeadlineException +from inventory import ( + Inventory, PendingDownloadQueue, PendingUpload, + PendingUploadDeadlineException) import knownnodes import paths from proofofwork import getPowType @@ -140,6 +127,7 @@ def change_translation(newlocale): except: logger.error("Failed to set locale to %s", lang, exc_info=True) + class MyForm(settingsmixin.SMainWindow): # the last time that a message arrival sound was played @@ -148,8 +136,6 @@ class MyForm(settingsmixin.SMainWindow): # the maximum frequency of message sounds in seconds maxSoundFrequencySec = 60 - str_chan = '[chan]' - REPLY_TYPE_SENDER = 0 REPLY_TYPE_CHAN = 1 @@ -858,14 +844,6 @@ class MyForm(settingsmixin.SMainWindow): self.raise_() self.activateWindow() - # pointer to the application - # app = None - # The most recent message - newMessageItem = None - - # The most recent broadcast - newBroadcastItem = None - # show the application window def appIndicatorShow(self): if self.actionShow is None: @@ -895,32 +873,19 @@ class MyForm(settingsmixin.SMainWindow): self.appIndicatorShowOrHideWindow()""" # Show the program window and select inbox tab - def appIndicatorInbox(self, mm_app, source_id): + def appIndicatorInbox(self, item=None): self.appIndicatorShow() # select inbox self.ui.tabWidget.setCurrentIndex(0) - selectedItem = None - if source_id == 'Subscriptions': - # select unread broadcast - if self.newBroadcastItem is not None: - selectedItem = self.newBroadcastItem - self.newBroadcastItem = None - else: - # select unread message - if self.newMessageItem is not None: - selectedItem = self.newMessageItem - self.newMessageItem = None - # make it the current item - if selectedItem is not None: - try: - self.ui.tableWidgetInbox.setCurrentItem(selectedItem) - except Exception: - self.ui.tableWidgetInbox.setCurrentCell(0, 0) + self.ui.treeWidgetYourIdentities.setCurrentItem( + self.ui.treeWidgetYourIdentities.topLevelItem(0).child(0) + ) + + if item: + self.ui.tableWidgetInbox.setCurrentItem(item) self.tableWidgetInboxItemClicked() else: - # just select the first item self.ui.tableWidgetInbox.setCurrentCell(0, 0) - self.tableWidgetInboxItemClicked() # Show the program window and select send tab def appIndicatorSend(self): @@ -1218,142 +1183,21 @@ class MyForm(settingsmixin.SMainWindow): self.tray.setContextMenu(m) self.tray.show() - # Ubuntu Messaging menu object - mmapp = None - - # is the operating system Ubuntu? - def isUbuntu(self): - for entry in platform.uname(): - if "Ubuntu" in entry: - return True - return False - - # When an unread inbox row is selected on then clear the messaging menu - def ubuntuMessagingMenuClear(self, inventoryHash): - # if this isn't ubuntu then don't do anything - if not self.isUbuntu(): - return - - # has messageing menu been installed - if not withMessagingMenu: - return - - # if there are no items on the messaging menu then - # the subsequent query can be avoided - if not (self.mmapp.has_source("Subscriptions") or self.mmapp.has_source("Messages")): - return - - queryreturn = sqlQuery( - '''SELECT toaddress, read FROM inbox WHERE msgid=?''', inventoryHash) - for row in queryreturn: - toAddress, read = row - if not read: - if toAddress == str_broadcast_subscribers: - if self.mmapp.has_source("Subscriptions"): - self.mmapp.remove_source("Subscriptions") - else: - if self.mmapp.has_source("Messages"): - self.mmapp.remove_source("Messages") - # returns the number of unread messages and subscriptions def getUnread(self): - unreadMessages = 0 - unreadSubscriptions = 0 + counters = [0, 0] - queryreturn = sqlQuery( - '''SELECT msgid, toaddress, read FROM inbox where folder='inbox' ''') - for row in queryreturn: - msgid, toAddress, read = row - - try: - if toAddress == str_broadcast_subscribers: - toLabel = str_broadcast_subscribers - else: - toLabel = BMConfigParser().get(toAddress, 'label') - except: - toLabel = '' - if toLabel == '': - toLabel = toAddress + queryreturn = sqlQuery(''' + SELECT msgid, toaddress, read FROM inbox where folder='inbox' + ''') + for msgid, toAddress, read in queryreturn: if not read: - if toLabel == str_broadcast_subscribers: - # increment the unread subscriptions - unreadSubscriptions = unreadSubscriptions + 1 - else: - # increment the unread messages - unreadMessages = unreadMessages + 1 - return unreadMessages, unreadSubscriptions + # increment the unread subscriptions if True (1) + # else messages (0) + counters[toAddress == str_broadcast_subscribers] += 1 - # show the number of unread messages and subscriptions on the messaging - # menu - def ubuntuMessagingMenuUnread(self, drawAttention): - unreadMessages, unreadSubscriptions = self.getUnread() - # unread messages - if unreadMessages > 0: - self.mmapp.append_source( - "Messages", None, "Messages (" + str(unreadMessages) + ")") - if drawAttention: - self.mmapp.draw_attention("Messages") - - # unread subscriptions - if unreadSubscriptions > 0: - self.mmapp.append_source("Subscriptions", None, "Subscriptions (" + str( - unreadSubscriptions) + ")") - if drawAttention: - self.mmapp.draw_attention("Subscriptions") - - # initialise the Ubuntu messaging menu - def ubuntuMessagingMenuInit(self): - global withMessagingMenu - - # if this isn't ubuntu then don't do anything - if not self.isUbuntu(): - return - - # has messageing menu been installed - if not withMessagingMenu: - logger.warning('WARNING: MessagingMenu is not available. Is libmessaging-menu-dev installed?') - return - - # create the menu server - if withMessagingMenu: - try: - self.mmapp = MessagingMenu.App( - desktop_id='pybitmessage.desktop') - self.mmapp.register() - self.mmapp.connect('activate-source', self.appIndicatorInbox) - self.ubuntuMessagingMenuUnread(True) - except Exception: - withMessagingMenu = False - logger.warning('WARNING: messaging menu disabled') - - # update the Ubuntu messaging menu - def ubuntuMessagingMenuUpdate(self, drawAttention, newItem, toLabel): - # if this isn't ubuntu then don't do anything - if not self.isUbuntu(): - return - - # has messageing menu been installed - if not withMessagingMenu: - logger.warning('WARNING: messaging menu disabled or libmessaging-menu-dev not installed') - return - - # remember this item to that the messaging menu can find it - if toLabel == str_broadcast_subscribers: - self.newBroadcastItem = newItem - else: - self.newMessageItem = newItem - - # Remove previous messages and subscriptions entries, then recreate them - # There might be a better way to do it than this - if self.mmapp.has_source("Messages"): - self.mmapp.remove_source("Messages") - - if self.mmapp.has_source("Subscriptions"): - self.mmapp.remove_source("Subscriptions") - - # update the menu entries - self.ubuntuMessagingMenuUnread(drawAttention) + return counters # play a sound def playSound(self, category, label): @@ -1423,6 +1267,17 @@ class MyForm(settingsmixin.SMainWindow): self._player(soundFilename) + # Try init the distro specific appindicator, + # for example the Ubuntu MessagingMenu + def indicatorInit(self): + def _noop_update(*args, **kwargs): + pass + + try: + self.indicatorUpdate = get_plugin('indicator')(self) + except (NameError, TypeError): + self.indicatorUpdate = _noop_update + # initialise the message notifier def notifierInit(self): def _simple_notify( @@ -2241,8 +2096,10 @@ class MyForm(settingsmixin.SMainWindow): sound.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): - # Ubuntu should notify of new message irespective of whether it's in current message list or not - self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel) + # Ubuntu should notify of new message irespective of + # whether it's in current message list or not + self.indicatorUpdate(True, to_label=acct.toLabel) + # cannot find item to pass here ): if hasattr(acct, "feedback") and acct.feedback != GatewayAccount.ALL_OK: if acct.feedback == GatewayAccount.REGISTRATION_DENIED: self.dialog = EmailGatewayRegistrationDialog(self, _translate("EmailGatewayRegistrationDialog", "Registration failed:"), @@ -2836,9 +2693,6 @@ class MyForm(settingsmixin.SMainWindow): shutdown.doCleanShutdown() self.statusBar().showMessage(_translate("MainWindow", "Stopping notifications... %1%").arg(str(90))) self.tray.hide() - # unregister the messaging system - if self.mmapp is not None: - self.mmapp.unregister() self.statusBar().showMessage(_translate("MainWindow", "Shutdown imminent... %1%").arg(str(100))) shared.thisapp.cleanup() @@ -4510,7 +4364,7 @@ def run(): myapp = MyForm() myapp.appIndicatorInit(app) - myapp.ubuntuMessagingMenuInit() + myapp.indicatorInit() myapp.notifierInit() myapp._firstrun = BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'dontconnect') diff --git a/src/plugins/indicator_libmessaging.py b/src/plugins/indicator_libmessaging.py new file mode 100644 index 00000000..96ab1516 --- /dev/null +++ b/src/plugins/indicator_libmessaging.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import gi +gi.require_version('MessagingMenu', '1.0') +from gi.repository import MessagingMenu + +from pybitmessage.bitmessageqt.utils import str_broadcast_subscribers +from pybitmessage.tr import _translate + + +class IndicatorLibmessaging(object): + def __init__(self, form): + try: + self.app = MessagingMenu.App(desktop_id='pybitmessage.desktop') + self.app.register() + self.app.connect('activate-source', self.activate) + except: + self.app = None + return + + self._menu = { + 'send': str(_translate('MainWindow', 'Send')), + 'messages': str(_translate('MainWindow', 'Messages')), + 'subscriptions': str(_translate('MainWindow', 'Subscriptions')) + } + + self.new_message_item = self.new_broadcast_item = None + self.form = form + self.show_unread() + + def __del__(self): + if self.app: + self.app.unregister() + + def activate(self, app, source): + self.form.appIndicatorInbox( + self.new_message_item if source == 'messages' + else self.new_broadcast_item + ) + + # show the number of unread messages and subscriptions + # on the messaging menu + def show_unread(self, draw_attention=False): + for source, count in zip( + ('messages', 'subscriptions'), + self.form.getUnread() + ): + if count > 0: + if self.app.has_source(source): + self.app.set_source_count(source, count) + else: + self.app.append_source_with_count( + source, None, self._menu[source], count) + if draw_attention: + self.app.draw_attention(source) + + # update the Ubuntu messaging menu + def __call__(self, draw_attention, item=None, to_label=None): + if not self.app: + return + # remember this item to that the activate() can find it + if item: + if to_label == str_broadcast_subscribers: + self.new_broadcast_item = item + else: + self.new_message_item = item + + self.show_unread(draw_attention) + + +connect_plugin = IndicatorLibmessaging From b77eb0c7e57a41917284a35d68caae0d987fadeb Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 24 Mar 2017 14:31:27 +0200 Subject: [PATCH 08/11] Namespace 'bitmessage' for plugins entry points --- setup.py | 6 +++--- src/plugins/plugin.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index eb79c3b7..06d49ba5 100644 --- a/setup.py +++ b/setup.py @@ -276,15 +276,15 @@ if __name__ == "__main__": ext_modules=[bitmsghash], zip_safe=False, entry_points={ - 'gui.menu': [ + 'bitmessage.gui.menu': [ 'popMenuYourIdentities.qrcode = ' 'pybitmessage.plugins.qrcodeui [qrcode]' ], - 'notification.message': [ + 'bitmessage.notification.message': [ 'notify2 = pybitmessage.plugins.notification_notify2' '[notify2]' ], - 'notification.sound': [ + 'bitmessage.notification.sound': [ 'file.fallback = pybitmessage.plugins.sound_playfile' '[sound]' ], diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index ba14b836..6601adaf 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -9,7 +9,7 @@ def get_plugins(group, point='', name=None, fallback=None): which name starts with `point` or equals to `name`. If `fallback` kwarg specified, plugin with that name yield last. """ - for ep in pkg_resources.iter_entry_points(group): + for ep in pkg_resources.iter_entry_points('bitmessage.' + group): if name and ep.name == name or ep.name.startswith(point): try: plugin = ep.load().connect_plugin From de531949e05bc3f59b2bedf83ddde7547ba4246b Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 24 Mar 2017 13:51:46 +0200 Subject: [PATCH 09/11] setup.py changes needed for indicator_libmessaging: - entry point 'indicator' and new extra 'gir' which requires only pygobject - desktop entry - icons are renamed and placed into separate dirs for standard sizes, because data_files keyword not supports file renaming --- setup.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 06d49ba5..cf6bd9d2 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ import os import sys +import shutil try: from setuptools import setup, Extension from setuptools.command.install import install @@ -167,6 +168,7 @@ def prereqToPackages(): print packageName[package].get('description') + def compilerToPackages(): if not detectOS() in compiling: return @@ -202,6 +204,14 @@ class InstallCmd(install): except (EOFError, NameError): pass + # prepare icons directories + os.makedirs('desktop/icons/scalable') + shutil.copyfile( + 'desktop/can-icon.svg', 'desktop/icons/scalable/pybitmessage.svg') + os.makedirs('desktop/icons/24x24') + shutil.copyfile( + 'desktop/icon24.png', 'desktop/icons/24x24/pybitmessage.png') + return install.run(self) @@ -253,9 +263,10 @@ if __name__ == "__main__": #keywords='', install_requires=installRequires, extras_require={ + 'gir': ['pygobject'], 'qrcode': ['qrcode'], 'pyopencl': ['pyopencl'], - 'notify2': ['pygobject', 'notify2'], + 'notify2': ['notify2'], 'sound:platform_system=="Windows"': ['winsound'] }, classifiers=[ @@ -273,6 +284,14 @@ if __name__ == "__main__": 'translations/*.ts', 'translations/*.qm', 'images/*.png', 'images/*.ico', 'images/*.icns' ]}, + data_files=[ + ('share/applications/', + ['desktop/pybitmessage.desktop']), + ('share/icons/hicolor/scalable/apps/', + ['desktop/icons/scalable/pybitmessage.svg']), + ('share/icons/hicolor/24x24/apps/', + ['desktop/icons/24x24/pybitmessage.png']) + ], ext_modules=[bitmsghash], zip_safe=False, entry_points={ @@ -282,12 +301,16 @@ if __name__ == "__main__": ], 'bitmessage.notification.message': [ 'notify2 = pybitmessage.plugins.notification_notify2' - '[notify2]' + '[gir, notify2]' ], 'bitmessage.notification.sound': [ 'file.fallback = pybitmessage.plugins.sound_playfile' '[sound]' ], + 'bitmessage.indicator': [ + 'libmessaging =' + 'pybitmessage.plugins.indicator_libmessaging [gir]' + ], # 'console_scripts': [ # 'pybitmessage = pybitmessage.bitmessagemain:main' # ] From 1f47a4060e9bb51d255ab6e3c583e9723d33a295 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 4 Sep 2017 16:06:48 +0300 Subject: [PATCH 10/11] Added "Set notification sound..." context menu on addressbook entry. --- src/bitmessageqt/__init__.py | 57 +++++++++++++++++++++++++++++++++--- src/bitmessageqt/sound.py | 2 ++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 942ed3de..bb90bb47 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -325,6 +325,10 @@ class MyForm(settingsmixin.SMainWindow): _translate( "MainWindow", "Set avatar..."), self.on_action_AddressBookSetAvatar) + self.actionAddressBookSetSound = \ + self.ui.addressBookContextMenuToolbar.addAction( + _translate("MainWindow", "Set notification sound..."), + self.on_action_AddressBookSetSound) self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction( _translate( "MainWindow", "Add New Address"), self.on_action_AddressBookNew) @@ -1205,9 +1209,9 @@ class MyForm(settingsmixin.SMainWindow): soundFilename = None def _choose_ext(basename): - for ext in ('.wav', '.mp3', '.oga'): - if os.path.isfile(basename + ext): - return ext + for ext in sound.extensions: + if os.path.isfile(os.extsep.join([basename, ext])): + return os.extsep + ext # if the address had a known label in the address book if label: @@ -3175,6 +3179,7 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuAddressBook.addAction(self.actionAddressBookClipboard) self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe) self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar) + self.popMenuAddressBook.addAction(self.actionAddressBookSetSound) self.popMenuAddressBook.addSeparator() self.popMenuAddressBook.addAction(self.actionAddressBookNew) normal = True @@ -3578,7 +3583,51 @@ class MyForm(settingsmixin.SMainWindow): return False return True - + + def on_action_AddressBookSetSound(self): + widget = self.ui.tableWidgetAddressBook + self.setAddressSound(widget.item(widget.currentRow(), 0).text()) + + def setAddressSound(self, addr): + filters = [unicode(_translate( + "MainWindow", "Sound files (%s)" % + ' '.join(['*%s%s' % (os.extsep, ext) for ext in sound.extensions]) + ))] + sourcefile = unicode(QtGui.QFileDialog.getOpenFileName( + self, _translate("MainWindow", "Set notification sound..."), + filter=';;'.join(filters) + )) + + if not sourcefile: + return + + destdir = os.path.join(state.appdata, 'sounds') + destfile = unicode(addr) + os.path.splitext(sourcefile)[-1] + destination = os.path.join(destdir, destfile) + + if sourcefile == destination: + return + + pattern = destfile.lower() + for item in os.listdir(destdir): + if item.lower() == pattern: + overwrite = QtGui.QMessageBox.question( + self, _translate("MainWindow", "Message"), + _translate( + "MainWindow", + "You have already set a notification sound" + " for this address book entry." + " Do you really want to overwrite it?"), + QtGui.QMessageBox.Yes, QtGui.QMessageBox.No + ) == QtGui.QMessageBox.Yes + if overwrite: + QtCore.QFile.remove(os.path.join(destdir, item)) + break + + if not QtCore.QFile.copy(sourcefile, destination): + logger.error( + 'couldn\'t copy %s to %s', sourcefile, destination) + def on_context_menuYourIdentities(self, point): currentItem = self.getCurrentItem() self.popMenuYourIdentities = QtGui.QMenu(self) diff --git a/src/bitmessageqt/sound.py b/src/bitmessageqt/sound.py index 4b6aaf00..9c86a9a4 100644 --- a/src/bitmessageqt/sound.py +++ b/src/bitmessageqt/sound.py @@ -17,3 +17,5 @@ def is_connection_sound(category): SOUND_DISCONNECTED, SOUND_CONNECTION_GREEN ) + +extensions = ('wav', 'mp3', 'oga') From 53c3eeb8f77c2d175707d95a5c948df40679a5e8 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 11 Sep 2017 17:44:17 +0300 Subject: [PATCH 11/11] Sound plugins using pycanberra and gst-python --- setup.py | 3 +++ src/plugins/sound_canberra.py | 21 +++++++++++++++++++++ src/plugins/sound_gstreamer.py | 14 ++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/plugins/sound_canberra.py create mode 100644 src/plugins/sound_gstreamer.py diff --git a/setup.py b/setup.py index cf6bd9d2..dc803931 100644 --- a/setup.py +++ b/setup.py @@ -304,6 +304,9 @@ if __name__ == "__main__": '[gir, notify2]' ], 'bitmessage.notification.sound': [ + 'theme.canberra = pybitmessage.plugins.sound_canberra', + 'file.gstreamer = pybitmessage.plugins.sound_gstreamer' + '[gir]', 'file.fallback = pybitmessage.plugins.sound_playfile' '[sound]' ], diff --git a/src/plugins/sound_canberra.py b/src/plugins/sound_canberra.py new file mode 100644 index 00000000..094901ed --- /dev/null +++ b/src/plugins/sound_canberra.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pybitmessage.bitmessageqt import sound + +import pycanberra + +_canberra = pycanberra.Canberra() + +_theme = { + sound.SOUND_UNKNOWN: 'message-new-email', + sound.SOUND_CONNECTED: 'network-connectivity-established', + sound.SOUND_DISCONNECTED: 'network-connectivity-lost', + sound.SOUND_CONNECTION_GREEN: 'network-connectivity-established' +} + + +def connect_plugin(category, label=None): + try: + _canberra.play(0, pycanberra.CA_PROP_EVENT_ID, _theme[category], None) + except (KeyError, pycanberra.CanberraException): + pass diff --git a/src/plugins/sound_gstreamer.py b/src/plugins/sound_gstreamer.py new file mode 100644 index 00000000..062da3f9 --- /dev/null +++ b/src/plugins/sound_gstreamer.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst # noqa: E402 + +Gst.init(None) +_player = Gst.ElementFactory.make("playbin", "player") + + +def connect_plugin(sound_file): + _player.set_state(Gst.State.NULL) + _player.set_property("uri", "file://" + sound_file) + _player.set_state(Gst.State.PLAYING)