qt.py (4293B)
1 from functools import partial 2 import zlib 3 import json 4 from io import BytesIO 5 import sys 6 import platform 7 from typing import TYPE_CHECKING 8 9 from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) 10 11 from electrum.plugin import BasePlugin, hook 12 from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon 13 from electrum.i18n import _ 14 from electrum.logging import get_logger 15 16 if TYPE_CHECKING: 17 from electrum.gui.qt.transaction_dialog import TxDialog 18 19 20 _logger = get_logger(__name__) 21 22 23 try: 24 import amodem.audio 25 import amodem.main 26 import amodem.config 27 _logger.info('Audio MODEM is available.') 28 amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) 29 amodem.log.setLevel(amodem.logging.INFO) 30 except ImportError: 31 amodem = None 32 _logger.info('Audio MODEM is not found.') 33 34 35 class Plugin(BasePlugin): 36 37 def __init__(self, parent, config, name): 38 BasePlugin.__init__(self, parent, config, name) 39 if self.is_available(): 40 self.modem_config = amodem.config.slowest() 41 self.library_name = { 42 'Linux': 'libportaudio.so' 43 }[platform.system()] 44 45 def is_available(self): 46 return amodem is not None 47 48 def requires_settings(self): 49 return True 50 51 def settings_widget(self, window): 52 return EnterButton(_('Settings'), partial(self.settings_dialog, window)) 53 54 def settings_dialog(self, window): 55 d = WindowModalDialog(window, _("Audio Modem Settings")) 56 57 layout = QGridLayout(d) 58 layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0) 59 60 bitrates = list(sorted(amodem.config.bitrates.keys())) 61 62 def _index_changed(index): 63 bitrate = bitrates[index] 64 self.modem_config = amodem.config.bitrates[bitrate] 65 66 combo = QComboBox() 67 combo.addItems([str(x) for x in bitrates]) 68 combo.currentIndexChanged.connect(_index_changed) 69 layout.addWidget(combo, 0, 1) 70 71 ok_button = QPushButton(_("OK")) 72 ok_button.clicked.connect(d.accept) 73 layout.addWidget(ok_button, 1, 1) 74 75 return bool(d.exec_()) 76 77 @hook 78 def transaction_dialog(self, dialog: 'TxDialog'): 79 b = QPushButton() 80 b.setIcon(read_QIcon("speaker.png")) 81 82 def handler(): 83 blob = dialog.tx.serialize() 84 self._send(parent=dialog, blob=blob) 85 b.clicked.connect(handler) 86 dialog.sharing_buttons.insert(-1, b) 87 88 @hook 89 def scan_text_edit(self, parent): 90 parent.addButton('microphone.png', partial(self._recv, parent), 91 _("Read from microphone")) 92 93 @hook 94 def show_text_edit(self, parent): 95 def handler(): 96 blob = str(parent.toPlainText()) 97 self._send(parent=parent, blob=blob) 98 parent.addButton('speaker.png', handler, _("Send to speaker")) 99 100 def _audio_interface(self): 101 interface = amodem.audio.Interface(config=self.modem_config) 102 return interface.load(self.library_name) 103 104 def _send(self, parent, blob): 105 def sender_thread(): 106 with self._audio_interface() as interface: 107 src = BytesIO(blob) 108 dst = interface.player() 109 amodem.main.send(config=self.modem_config, src=src, dst=dst) 110 111 _logger.info(f'Sending: {repr(blob)}') 112 blob = zlib.compress(blob.encode('ascii')) 113 114 kbps = self.modem_config.modem_bps / 1e3 115 msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps) 116 WaitingDialog(parent, msg, sender_thread) 117 118 def _recv(self, parent): 119 def receiver_thread(): 120 with self._audio_interface() as interface: 121 src = interface.recorder() 122 dst = BytesIO() 123 amodem.main.recv(config=self.modem_config, src=src, dst=dst) 124 return dst.getvalue() 125 126 def on_finished(blob): 127 if blob: 128 blob = zlib.decompress(blob).decode('ascii') 129 _logger.info(f'Received: {repr(blob)}') 130 parent.setText(blob) 131 132 kbps = self.modem_config.modem_bps / 1e3 133 msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps) 134 WaitingDialog(parent, msg, receiver_thread, on_finished)