commit 699b22565c815b0368f71b3998d9c3a8a87aaf64
parent 5262d6ae731ccbd0b19f3132982f032474a1471f
Author: ThomasV <thomasv@gitorious>
Date: Wed, 7 Jan 2015 02:50:22 +0100
Merge branch 'romanz-amodem-plugin'
Diffstat:
5 files changed, 176 insertions(+), 0 deletions(-)
diff --git a/gui/qt/qrtextedit.py b/gui/qt/qrtextedit.py
@@ -1,4 +1,5 @@
from electrum.i18n import _
+from electrum.plugins import run_hook
from PyQt4.QtGui import *
from PyQt4.QtCore import *
@@ -25,6 +26,7 @@ class ShowQRTextEdit(QRTextEdit):
super(ShowQRTextEdit, self).__init__(text)
self.setReadOnly(1)
self.button.clicked.connect(self.qr_show)
+ run_hook('show_text_edit', self)
def qr_show(self):
from qrcodewidget import QRDialog
@@ -49,6 +51,7 @@ class ScanQRTextEdit(QRTextEdit):
if win:
assert hasattr(win,"config"), "You must pass a window with access to the config to ScanQRTextEdit constructor."
self.button.clicked.connect(self.qr_input)
+ run_hook('scan_text_edit', self)
def qr_input(self):
diff --git a/icons.qrc b/icons.qrc
@@ -28,6 +28,8 @@
<file>icons/network.png</file>
<file>icons/dark_background.png</file>
<file>icons/qrcode.png</file>
+ <file>icons/microphone.png</file>
+ <file>icons/speaker.png</file>
<file>icons/trustedcoin.png</file>
</qresource>
</RCC>
diff --git a/icons/microphone.png b/icons/microphone.png
Binary files differ.
diff --git a/icons/speaker.png b/icons/speaker.png
Binary files differ.
diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py
@@ -0,0 +1,171 @@
+from electrum.plugins import BasePlugin, hook
+from electrum_gui.qt.util import WaitingDialog, EnterButton
+from electrum.util import print_msg
+from electrum.i18n import _
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+
+import traceback
+import zlib
+import json
+from io import BytesIO
+import sys
+import platform
+
+try:
+ import amodem.audio
+ import amodem.recv
+ import amodem.send
+ import amodem.config
+ print_msg('Audio MODEM is available.')
+ amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))
+ amodem.log.setLevel(amodem.logging.INFO)
+except ImportError:
+ amodem = None
+ print_msg('Audio MODEM is not found.')
+
+
+class Plugin(BasePlugin):
+
+ def __init__(self, config, name):
+ BasePlugin.__init__(self, config, name)
+ if self.is_available():
+ self.modem_config = amodem.config.slowest()
+ self.library_name = {
+ 'Linux': 'libportaudio.so'
+ }[platform.system()]
+
+ def fullname(self):
+ return 'Audio MODEM'
+
+ def description(self):
+ return ('Provides support for air-gapped transaction signing.\n\n'
+ 'Requires http://github.com/romanz/amodem/')
+
+ def is_available(self):
+ return amodem is not None
+
+ def requires_settings(self):
+ return True
+
+ def settings_widget(self, window):
+ return EnterButton(_('Settings'), self.settings_dialog)
+
+ def settings_dialog(self):
+ d = QDialog()
+ d.setWindowTitle("Settings")
+
+ layout = QGridLayout(d)
+ layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0)
+
+ bitrates = list(sorted(amodem.config.bitrates.keys()))
+
+ def _index_changed(index):
+ bitrate = bitrates[index]
+ self.modem_config = amodem.config.bitrates[bitrate]
+
+ combo = QComboBox()
+ combo.addItems(map(str, bitrates))
+ combo.currentIndexChanged.connect(_index_changed)
+ layout.addWidget(combo, 0, 1)
+
+ ok_button = QPushButton(_("OK"))
+ ok_button.clicked.connect(d.accept)
+ layout.addWidget(ok_button, 1, 1)
+
+ return bool(d.exec_())
+
+ @hook
+ def transaction_dialog(self, dialog):
+ b = QPushButton()
+ b.setIcon(QIcon(":icons/speaker.png"))
+
+ def handler():
+ blob = json.dumps(dialog.tx.as_dict())
+ self.sender = self._send(parent=dialog, blob=blob)
+ self.sender.start()
+ b.clicked.connect(handler)
+ dialog.buttons.insertWidget(1, b)
+
+ @hook
+ def scan_text_edit(self, parent):
+ def handler():
+ self.receiver = self._recv(parent=parent)
+ self.receiver.start()
+ button = add_button(parent=parent, icon_name=':icons/microphone.png')
+ button.clicked.connect(handler)
+
+ @hook
+ def show_text_edit(self, parent):
+ def handler():
+ blob = str(parent.toPlainText())
+ self.sender = self._send(parent=parent, blob=blob)
+ self.sender.start()
+ button = add_button(parent=parent, icon_name=':icons/speaker.png')
+ button.clicked.connect(handler)
+
+ def _audio_interface(self):
+ return amodem.audio.Interface(
+ config=self.modem_config,
+ name=self.library_name
+ )
+
+ def _send(self, parent, blob):
+ def sender_thread():
+ try:
+ with self._audio_interface() as interface:
+ src = BytesIO(blob)
+ dst = interface.player()
+ amodem.send.main(config=self.modem_config, src=src, dst=dst)
+ except Exception:
+ traceback.print_exc()
+
+ print_msg('Sending:', repr(blob))
+ blob = zlib.compress(blob)
+
+ kbps = self.modem_config.modem_bps / 1e3
+ msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps)
+ return WaitingDialog(parent=parent, message=msg, run_task=sender_thread)
+
+ def _recv(self, parent):
+ def receiver_thread():
+ try:
+ with self._audio_interface() as interface:
+ src = interface.recorder()
+ dst = BytesIO()
+ amodem.recv.main(config=self.modem_config, src=src, dst=dst)
+ return dst.getvalue()
+ except Exception:
+ traceback.print_exc()
+
+ def on_success(blob):
+ if blob:
+ blob = zlib.decompress(blob)
+ print_msg('Received:', repr(blob))
+ parent.setText(blob)
+
+ kbps = self.modem_config.modem_bps / 1e3
+ msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps)
+ return WaitingDialog(parent=parent, message=msg,
+ run_task=receiver_thread, on_success=on_success)
+
+
+def add_button(parent, icon_name):
+ audio_button = QToolButton(parent)
+ audio_button.setIcon(QIcon(icon_name))
+ audio_button.setStyleSheet("QToolButton { border: none; padding: 0px; }")
+ audio_button.setVisible(True)
+
+ parent_resizeEvent = parent.resizeEvent
+
+ def resizeEvent(e):
+ result = parent_resizeEvent(e)
+ qr_button = parent.button
+ left = qr_button.geometry().left() - audio_button.sizeHint().width()
+ top = qr_button.geometry().top()
+ audio_button.move(left, top)
+ return result
+
+ parent.resizeEvent = resizeEvent
+ return audio_button