204 lines
8.0 KiB
Python
204 lines
8.0 KiB
Python
import re
|
|
import queue
|
|
import time
|
|
import os
|
|
|
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl, QLocale, qInstallMessageHandler, QTimer
|
|
from PyQt5.QtGui import QGuiApplication, QFontDatabase
|
|
from PyQt5.QtQml import qmlRegisterType, qmlRegisterUncreatableType, QQmlApplicationEngine
|
|
|
|
from electrum.logging import Logger, get_logger
|
|
from electrum import version
|
|
|
|
from .qeconfig import QEConfig
|
|
from .qedaemon import QEDaemon, QEWalletListModel
|
|
from .qenetwork import QENetwork
|
|
from .qewallet import QEWallet
|
|
from .qeqr import QEQRParser, QEQRImageProvider
|
|
from .qewalletdb import QEWalletDB
|
|
from .qebitcoin import QEBitcoin
|
|
from .qefx import QEFX
|
|
from .qetxfinalizer import QETxFinalizer
|
|
from .qeinvoice import QEInvoice
|
|
from .qetypes import QEAmount
|
|
from .qeaddressdetails import QEAddressDetails
|
|
from .qetxdetails import QETxDetails
|
|
from .qechannelopener import QEChannelOpener
|
|
from .qelnpaymentdetails import QELnPaymentDetails
|
|
|
|
notification = None
|
|
|
|
class QEAppController(QObject):
|
|
userNotify = pyqtSignal(str)
|
|
|
|
def __init__(self, qedaemon):
|
|
super().__init__()
|
|
self.logger = get_logger(__name__)
|
|
|
|
self._qedaemon = qedaemon
|
|
|
|
# set up notification queue and notification_timer
|
|
self.user_notification_queue = queue.Queue()
|
|
self.user_notification_last_time = 0
|
|
|
|
self.notification_timer = QTimer(self)
|
|
self.notification_timer.setSingleShot(False)
|
|
self.notification_timer.setInterval(500) # msec
|
|
self.notification_timer.timeout.connect(self.on_notification_timer)
|
|
|
|
self._qedaemon.walletLoaded.connect(self.on_wallet_loaded)
|
|
|
|
self.userNotify.connect(self.notifyAndroid)
|
|
|
|
def on_wallet_loaded(self):
|
|
qewallet = self._qedaemon.currentWallet
|
|
# attach to the wallet user notification events
|
|
# connect only once
|
|
try:
|
|
qewallet.userNotify.disconnect(self.on_wallet_usernotify)
|
|
except:
|
|
pass
|
|
qewallet.userNotify.connect(self.on_wallet_usernotify)
|
|
|
|
def on_wallet_usernotify(self, wallet, message):
|
|
self.logger.debug(message)
|
|
self.user_notification_queue.put(message)
|
|
if not self.notification_timer.isActive():
|
|
self.logger.debug('starting app notification timer')
|
|
self.notification_timer.start()
|
|
|
|
def on_notification_timer(self):
|
|
if self.user_notification_queue.qsize() == 0:
|
|
self.logger.debug('queue empty, stopping app notification timer')
|
|
self.notification_timer.stop()
|
|
return
|
|
now = time.time()
|
|
rate_limit = 20 # seconds
|
|
if self.user_notification_last_time + rate_limit > now:
|
|
return
|
|
self.user_notification_last_time = now
|
|
self.logger.info("Notifying GUI about new user notifications")
|
|
try:
|
|
self.userNotify.emit(self.user_notification_queue.get_nowait())
|
|
except queue.Empty:
|
|
pass
|
|
|
|
def notifyAndroid(self, message):
|
|
try:
|
|
# TODO: lazy load not in UI thread please
|
|
global notification
|
|
if not notification:
|
|
from plyer import notification
|
|
icon = (os.path.dirname(os.path.realpath(__file__))
|
|
+ '/../icons/electrum.png')
|
|
notification.notify('Electrum', message, app_icon=icon, app_name='Electrum')
|
|
except ImportError:
|
|
self.logger.error('Notification: needs plyer; `sudo python3 -m pip install plyer`')
|
|
|
|
@pyqtSlot(str, str)
|
|
def doShare(self, data, title):
|
|
#if platform != 'android':
|
|
#return
|
|
try:
|
|
from jnius import autoclass, cast
|
|
except ImportError:
|
|
self.logger.error('Share: needs jnius. Platform not Android?')
|
|
return
|
|
|
|
JS = autoclass('java.lang.String')
|
|
Intent = autoclass('android.content.Intent')
|
|
sendIntent = Intent()
|
|
sendIntent.setAction(Intent.ACTION_SEND)
|
|
sendIntent.setType("text/plain")
|
|
sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data))
|
|
pythonActivity = autoclass('org.kivy.android.PythonActivity')
|
|
currentActivity = cast('android.app.Activity', pythonActivity.mActivity)
|
|
it = Intent.createChooser(sendIntent, cast('java.lang.CharSequence', JS(title)))
|
|
currentActivity.startActivity(it)
|
|
|
|
@pyqtSlot('QString')
|
|
def textToClipboard(self, text):
|
|
QGuiApplication.clipboard().setText(text)
|
|
|
|
@pyqtSlot(result='QString')
|
|
def clipboardToText(self):
|
|
return QGuiApplication.clipboard().text()
|
|
|
|
class ElectrumQmlApplication(QGuiApplication):
|
|
|
|
_valid = True
|
|
|
|
def __init__(self, args, config, daemon):
|
|
super().__init__(args)
|
|
|
|
self.logger = get_logger(__name__)
|
|
|
|
ElectrumQmlApplication._daemon = daemon
|
|
|
|
qmlRegisterType(QEWalletListModel, 'org.electrum', 1, 0, 'WalletListModel')
|
|
qmlRegisterType(QEWallet, 'org.electrum', 1, 0, 'Wallet')
|
|
qmlRegisterType(QEWalletDB, 'org.electrum', 1, 0, 'WalletDB')
|
|
qmlRegisterType(QEBitcoin, 'org.electrum', 1, 0, 'Bitcoin')
|
|
qmlRegisterType(QEQRParser, 'org.electrum', 1, 0, 'QRParser')
|
|
qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX')
|
|
qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer')
|
|
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
|
|
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
|
|
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
|
|
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
|
|
qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails')
|
|
|
|
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
|
|
|
self.engine = QQmlApplicationEngine(parent=self)
|
|
self.engine.addImportPath('./qml')
|
|
|
|
screensize = self.primaryScreen().size()
|
|
|
|
self.qr_ip = QEQRImageProvider((7/8)*min(screensize.width(), screensize.height()))
|
|
self.engine.addImageProvider('qrgen', self.qr_ip)
|
|
|
|
# add a monospace font as we can't rely on device having one
|
|
self.fixedFont = 'PT Mono'
|
|
not_loaded = QFontDatabase.addApplicationFont('electrum/gui/qml/fonts/PTMono-Regular.ttf') < 0
|
|
not_loaded = QFontDatabase.addApplicationFont('electrum/gui/qml/fonts/PTMono-Bold.ttf') < 0 and not_loaded
|
|
if not_loaded:
|
|
self.logger.warning('Could not load font PT Mono')
|
|
self.fixedFont = 'Monospace' # hope for the best
|
|
|
|
self.context = self.engine.rootContext()
|
|
self._qeconfig = QEConfig(config)
|
|
self._qenetwork = QENetwork(daemon.network)
|
|
self._qedaemon = QEDaemon(daemon)
|
|
self._appController = QEAppController(self._qedaemon)
|
|
self._maxAmount = QEAmount(is_max=True)
|
|
self.context.setContextProperty('AppController', self._appController)
|
|
self.context.setContextProperty('Config', self._qeconfig)
|
|
self.context.setContextProperty('Network', self._qenetwork)
|
|
self.context.setContextProperty('Daemon', self._qedaemon)
|
|
self.context.setContextProperty('FixedFont', self.fixedFont)
|
|
self.context.setContextProperty('MAX', self._maxAmount)
|
|
self.context.setContextProperty('BUILD', {
|
|
'electrum_version': version.ELECTRUM_VERSION,
|
|
'apk_version': version.APK_VERSION,
|
|
'protocol_version': version.PROTOCOL_VERSION
|
|
})
|
|
|
|
qInstallMessageHandler(self.message_handler)
|
|
|
|
# get notified whether root QML document loads or not
|
|
self.engine.objectCreated.connect(self.objectCreated)
|
|
|
|
# slot is called after loading root QML. If object is None, it has failed.
|
|
@pyqtSlot('QObject*', 'QUrl')
|
|
def objectCreated(self, object, url):
|
|
if object is None:
|
|
self._valid = False
|
|
self.engine.objectCreated.disconnect(self.objectCreated)
|
|
|
|
def message_handler(self, line, funct, file):
|
|
# filter out common harmless messages
|
|
if re.search('file:///.*TypeError: Cannot read property.*null$', file):
|
|
return
|
|
self.logger.warning(file)
|