From 04fc3d4135f82b7c6aaea6437b243a43a39f381c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 1 Nov 2014 12:40:46 +0200 Subject: [PATCH] Add audio modem integration for transaction sending & receiving http://www.flaticon.com/free-icon/speaker-outline_54951 Speaker icon made by Catalin Fertu from www.flaticon.com is licensed under CC BY 3.0 http://www.flaticon.com/free-icon/mic_10032 Microphone icon made by Elegant Themes from www.flaticon.com is licensed under CC BY 3.0 --- gui/qt/qrtextedit.py | 3 + icons.qrc | 2 + icons/microphone.png | Bin 0 -> 199 bytes icons/speaker.png | Bin 0 -> 392 bytes plugins/audio_modem.py | 123 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 icons/microphone.png create mode 100644 icons/speaker.png create mode 100644 plugins/audio_modem.py diff --git a/gui/qt/qrtextedit.py b/gui/qt/qrtextedit.py index 8b7106e3..994733ab 100644 --- 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 index 94fd43e3..e2197dd0 100644 --- a/icons.qrc +++ b/icons.qrc @@ -28,6 +28,8 @@ icons/network.png icons/dark_background.png icons/qrcode.png + icons/microphone.png + icons/speaker.png icons/trustedcoin.png diff --git a/icons/microphone.png b/icons/microphone.png new file mode 100644 index 0000000000000000000000000000000000000000..f9a271c55810fd14f8915beaebd826b0e52a3ffa GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6`aE46Ln;{OUf7t)>?pu`q5b1j z1x3?MjY8rIER&d?-){K5!8v(1?{aoOF}=^nt~Wclu6bmuwo9D%u>U{y2FI|_n+j8W zC#6hkUBxm<>q^t<$rrjDHirBoc5!lIL8@MUQTpt6Hc|`>jI5q6 zjv*Qo=UzUqeI!tX^~3q!K_X%%(kG9Fc}DiBX-|5_|A2qP(L?_iq@^Y&uAZX%lX;4! zs_LfDx+zmcwO409)HMES^z2IHp5psEx8Jk%clpsh!%6t{iY-^Xc>dlvq^tGfAk(*k z{+pNBRy?%vw>};dT`PA!A}g~&@lAsK;z;YmGHwRn-sXvxOXvi7wQDXtpxvgNq^dM; zOA^C!&82Q$E6tT=SsZk0v{JixV7I)0ApcLr^bJ#^Q6A>ar(MdUfQkVzopr00*d;g8%>k literal 0 HcmV?d00001 diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py new file mode 100644 index 00000000..146cb135 --- /dev/null +++ b/plugins/audio_modem.py @@ -0,0 +1,123 @@ +from electrum.plugins import BasePlugin, hook +from electrum_gui.qt.util import WaitingDialog +from electrum.util import print_msg + +from PyQt4.QtGui import * +from PyQt4.QtCore import * + +import subprocess +import traceback +import zlib +import json + +try: + subprocess.check_call(['amodem-cli', '--help']) + print_msg('Audio MODEM is enabled.') + amodem_available = True +except subprocess.CalledProcessError: + print_msg('Audio MODEM is not found.') + amodem_available = False + + +def send(parent, blob): + print_msg('Sending:', repr(blob)) + blob = zlib.compress(blob) + def sender_thread(): + try: + args = ['amodem-cli', 'send', '-vv'] + p = subprocess.Popen(args, stdin=subprocess.PIPE) + p.stdin.write(blob) + p.stdin.close() + p.wait() + except Exception: + traceback.print_exc() + p.kill() + + return WaitingDialog( + parent=parent, message='Sending transaction to Audio MODEM...', + run_task=sender_thread) + + +def recv(parent): + def receiver_thread(): + import subprocess + try: + args = ['amodem-cli', 'recv', '-vv'] + p = subprocess.Popen(args, stdout=subprocess.PIPE) + return p.stdout.read() + except Exception: + traceback.print_exc() + p.kill() + + def on_success(blob): + blob = zlib.decompress(blob) + print_msg('Received:', repr(blob)) + parent.setText(blob) + + return WaitingDialog( + parent=parent, message='Receiving transaction from Audio MODEM...', + run_task=receiver_thread, on_success=on_success) + + +class Plugin(BasePlugin): + + 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_available + + is_enabled = is_available + + @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 = 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 = 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 = send(parent=parent, blob=blob) + self.sender.start() + button = add_button(parent=parent, icon_name=':icons/speaker.png') + button.clicked.connect(handler) + + +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