Sander van Grieken
4 years ago
4 changed files with 329 additions and 0 deletions
@ -0,0 +1,100 @@ |
|||||
|
import os |
||||
|
import signal |
||||
|
import sys |
||||
|
import traceback |
||||
|
import threading |
||||
|
from typing import Optional, TYPE_CHECKING, List |
||||
|
|
||||
|
try: |
||||
|
import PyQt5 |
||||
|
except Exception: |
||||
|
sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'") |
||||
|
|
||||
|
try: |
||||
|
import PyQt5.QtQml |
||||
|
except Exception: |
||||
|
sys.exit("Error: Could not import PyQt5.QtQml on Linux systems, you may try 'sudo apt-get install python3-pyqt5.qtquick'") |
||||
|
|
||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QUrl |
||||
|
from PyQt5.QtGui import QGuiApplication |
||||
|
from PyQt5.QtQml import qmlRegisterType, QQmlComponent, QQmlApplicationEngine |
||||
|
from PyQt5.QtQuick import QQuickView |
||||
|
import PyQt5.QtCore as QtCore |
||||
|
import PyQt5.QtQml as QtQml |
||||
|
|
||||
|
from electrum.i18n import _, set_language |
||||
|
from electrum.plugin import run_hook |
||||
|
from electrum.base_wizard import GoBack |
||||
|
from electrum.util import (UserCancelled, profiler, |
||||
|
WalletFileException, BitcoinException, get_new_wallet_name) |
||||
|
from electrum.wallet import Wallet, Abstract_Wallet |
||||
|
from electrum.wallet_db import WalletDB |
||||
|
from electrum.logging import Logger |
||||
|
|
||||
|
if TYPE_CHECKING: |
||||
|
from electrum.daemon import Daemon |
||||
|
from electrum.simple_config import SimpleConfig |
||||
|
from electrum.plugin import Plugins |
||||
|
|
||||
|
from .qenetwork import QENetwork, QEDaemon, QEWalletListModel |
||||
|
from .qewallet import * |
||||
|
from .qeqr import QEQR |
||||
|
|
||||
|
class ElectrumQmlApplication(QGuiApplication): |
||||
|
def __init__(self, args, daemon): |
||||
|
super().__init__(args) |
||||
|
|
||||
|
qmlRegisterType(QEWalletListModel, 'QElectrum', 1, 0, 'QEWalletListModel') |
||||
|
qmlRegisterType(QEWallet, 'QElectrum', 1, 0, 'QEWallet') |
||||
|
|
||||
|
self.engine = QQmlApplicationEngine(parent=self) |
||||
|
self.context = self.engine.rootContext() |
||||
|
self.qen = QENetwork(daemon.network) |
||||
|
self.context.setContextProperty('Network', self.qen) |
||||
|
self.qed = QEDaemon(daemon) |
||||
|
self.context.setContextProperty('Daemon', self.qed) |
||||
|
self.qeqr = QEQR() |
||||
|
self.context.setContextProperty('QR', self.qeqr) |
||||
|
self.engine.load(QUrl('electrum/gui/qml/components/main.qml')) |
||||
|
|
||||
|
class ElectrumGui(Logger): |
||||
|
|
||||
|
@profiler |
||||
|
def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'): |
||||
|
# TODO set_language(config.get('language', get_default_language())) |
||||
|
Logger.__init__(self) |
||||
|
self.logger.info(f"Qml GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}") |
||||
|
# Uncomment this call to verify objects are being properly |
||||
|
# GC-ed when windows are closed |
||||
|
#network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, |
||||
|
# ElectrumWindow], interval=5)]) |
||||
|
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) |
||||
|
if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): |
||||
|
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) |
||||
|
# if hasattr(QGuiApplication, 'setDesktopFileName'): |
||||
|
# QGuiApplication.setDesktopFileName('electrum.desktop') |
||||
|
self.gui_thread = threading.current_thread() |
||||
|
self.config = config |
||||
|
self.daemon = daemon |
||||
|
self.plugins = plugins |
||||
|
self.app = ElectrumQmlApplication(sys.argv, self.daemon) |
||||
|
|
||||
|
# TODO when plugin support. run_hook('init_qml', self) |
||||
|
|
||||
|
def close(self): |
||||
|
# for window in self.windows: |
||||
|
# window.close() |
||||
|
# if self.network_dialog: |
||||
|
# self.network_dialog.close() |
||||
|
# if self.lightning_dialog: |
||||
|
# self.lightning_dialog.close() |
||||
|
# if self.watchtower_dialog: |
||||
|
# self.watchtower_dialog.close() |
||||
|
self.app.quit() |
||||
|
|
||||
|
def main(self): |
||||
|
self.app.exec_() |
||||
|
|
||||
|
def stop(self): |
||||
|
self.logger.info('closing GUI') |
||||
|
self.app.quit() |
@ -0,0 +1,128 @@ |
|||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl |
||||
|
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex |
||||
|
|
||||
|
from electrum.util import register_callback |
||||
|
from electrum.logging import get_logger |
||||
|
from electrum.wallet import Wallet, Abstract_Wallet |
||||
|
|
||||
|
from .qewallet import QEWallet |
||||
|
|
||||
|
class QENetwork(QObject): |
||||
|
def __init__(self, network, parent=None): |
||||
|
super().__init__(parent) |
||||
|
self.network = network |
||||
|
register_callback(self.on_network_updated, ['network_updated']) |
||||
|
register_callback(self.on_blockchain_updated, ['blockchain_updated']) |
||||
|
register_callback(self.on_default_server_changed, ['default_server_changed']) |
||||
|
register_callback(self.on_proxy_set, ['proxy_set']) |
||||
|
register_callback(self.on_status, ['status']) |
||||
|
|
||||
|
_logger = get_logger(__name__) |
||||
|
|
||||
|
network_updated = pyqtSignal() |
||||
|
blockchain_updated = pyqtSignal() |
||||
|
default_server_changed = pyqtSignal() |
||||
|
proxy_set = pyqtSignal() |
||||
|
status_updated = pyqtSignal() |
||||
|
|
||||
|
_num_updates = 0 |
||||
|
_server = "" |
||||
|
_height = 0 |
||||
|
_status = "" |
||||
|
|
||||
|
def on_network_updated(self, event, *args): |
||||
|
self._num_updates = self._num_updates + 1 |
||||
|
self.network_updated.emit() |
||||
|
|
||||
|
def on_blockchain_updated(self, event, *args): |
||||
|
self._logger.info('chainupdate: ' + str(event) + str(args)) |
||||
|
self._height = self.network.get_local_height() |
||||
|
self.blockchain_updated.emit() |
||||
|
|
||||
|
def on_default_server_changed(self, event, *args): |
||||
|
netparams = self.network.get_parameters() |
||||
|
self._server = str(netparams.server) |
||||
|
self.default_server_changed.emit() |
||||
|
|
||||
|
def on_proxy_set(self, event, *args): |
||||
|
self._logger.info('proxy set') |
||||
|
self.proxy_set.emit() |
||||
|
|
||||
|
def on_status(self, event, *args): |
||||
|
self._logger.info('status updated') |
||||
|
self._status = self.network.connection_status |
||||
|
self.status_updated.emit() |
||||
|
|
||||
|
@pyqtProperty(int,notify=network_updated) |
||||
|
def updates(self): |
||||
|
return self._num_updates |
||||
|
|
||||
|
@pyqtProperty(int,notify=blockchain_updated) |
||||
|
def height(self): |
||||
|
return self._height |
||||
|
|
||||
|
@pyqtProperty('QString',notify=default_server_changed) |
||||
|
def server(self): |
||||
|
return self._server |
||||
|
|
||||
|
@pyqtProperty('QString',notify=status_updated) |
||||
|
def status(self): |
||||
|
return self._status |
||||
|
|
||||
|
class QEWalletListModel(QAbstractListModel): |
||||
|
def __init__(self, parent=None): |
||||
|
QAbstractListModel.__init__(self, parent) |
||||
|
self.wallets = [] |
||||
|
|
||||
|
def rowCount(self, index): |
||||
|
return len(self.wallets) |
||||
|
|
||||
|
def data(self, index, role): |
||||
|
if role == Qt.DisplayRole: |
||||
|
return self.wallets[index.row()].basename() |
||||
|
|
||||
|
def add_wallet(self, wallet: Abstract_Wallet = None): |
||||
|
if wallet == None: |
||||
|
return |
||||
|
self.beginInsertRows(QModelIndex(), len(self.wallets), len(self.wallets)); |
||||
|
self.wallets.append(wallet); |
||||
|
self.endInsertRows(); |
||||
|
|
||||
|
|
||||
|
class QEDaemon(QObject): |
||||
|
def __init__(self, daemon, parent=None): |
||||
|
super().__init__(parent) |
||||
|
self.daemon = daemon |
||||
|
|
||||
|
_logger = get_logger(__name__) |
||||
|
_wallet = '' |
||||
|
_loaded_wallets = QEWalletListModel() |
||||
|
|
||||
|
wallet_loaded = pyqtSignal() |
||||
|
|
||||
|
@pyqtSlot() |
||||
|
def load_wallet(self, path=None, password=None): |
||||
|
self._logger.info(str(self.daemon.get_wallets())) |
||||
|
if path == None: |
||||
|
path = self.daemon.config.get('recently_open')[0] |
||||
|
wallet = self.daemon.load_wallet(path, password) |
||||
|
if wallet != None: |
||||
|
self._loaded_wallets.add_wallet(wallet) |
||||
|
self._wallet = wallet.basename() |
||||
|
self._current_wallet = QEWallet(wallet) |
||||
|
self.wallet_loaded.emit() |
||||
|
self._logger.info(str(self.daemon.get_wallets())) |
||||
|
else: |
||||
|
self._logger.info('fail open wallet') |
||||
|
|
||||
|
@pyqtProperty('QString',notify=wallet_loaded) |
||||
|
def walletName(self): |
||||
|
return self._wallet |
||||
|
|
||||
|
@pyqtProperty(QEWalletListModel) |
||||
|
def activeWallets(self): |
||||
|
return self._loaded_wallets |
||||
|
|
||||
|
@pyqtProperty(QEWallet,notify=wallet_loaded) |
||||
|
def currentWallet(self): |
||||
|
return self._current_wallet |
@ -0,0 +1,58 @@ |
|||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl |
||||
|
|
||||
|
from electrum.logging import get_logger |
||||
|
|
||||
|
from PIL import Image |
||||
|
from ctypes import * |
||||
|
|
||||
|
class QEQR(QObject): |
||||
|
def __init__(self, text=None, parent=None): |
||||
|
super().__init__(parent) |
||||
|
self._text = text |
||||
|
|
||||
|
_logger = get_logger(__name__) |
||||
|
scan_ready_changed = pyqtSignal() |
||||
|
|
||||
|
_ready = True |
||||
|
|
||||
|
@pyqtSlot('QImage') |
||||
|
def scanImage(self, image=None): |
||||
|
if not self._ready: |
||||
|
self._logger.warning("Already processing an image. Check 'ready' property before calling scanImage") |
||||
|
return |
||||
|
self._ready = False |
||||
|
self.scan_ready_changed.emit() |
||||
|
|
||||
|
pilimage = self.convertToPILImage(image) |
||||
|
parseQR(pilimage) |
||||
|
|
||||
|
self._ready = True |
||||
|
|
||||
|
def logImageStats(self, image): |
||||
|
self._logger.info('width: ' + str(image.width())) |
||||
|
self._logger.info('height: ' + str(image.height())) |
||||
|
self._logger.info('depth: ' + str(image.depth())) |
||||
|
self._logger.info('format: ' + str(image.format())) |
||||
|
|
||||
|
def convertToPILImage(self, image) -> Image: |
||||
|
self.logImageStats(image) |
||||
|
|
||||
|
rawimage = image.constBits() |
||||
|
# assumption: pixels are 32 bits ARGB |
||||
|
numbytes = image.width() * image.height() * 4 |
||||
|
|
||||
|
self._logger.info(type(rawimage)) |
||||
|
buf = bytearray(numbytes) |
||||
|
c_buf = (c_byte * numbytes).from_buffer(buf) |
||||
|
memmove(c_buf, c_void_p(rawimage.__int__()), numbytes) |
||||
|
buf2 = bytes(buf) |
||||
|
|
||||
|
return Image.frombytes('RGBA', (image.width(), image.height()), buf2, 'raw') |
||||
|
|
||||
|
def parseQR(self, image): |
||||
|
pass |
||||
|
|
||||
|
@pyqtProperty(bool, notify=scan_ready_changed) |
||||
|
def ready(self): |
||||
|
return self._ready |
||||
|
|
@ -0,0 +1,43 @@ |
|||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl |
||||
|
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex |
||||
|
|
||||
|
from electrum.util import register_callback |
||||
|
from electrum.logging import get_logger |
||||
|
from electrum.wallet import Wallet, Abstract_Wallet |
||||
|
|
||||
|
class QETransactionsListModel(QAbstractListModel): |
||||
|
def __init__(self, parent=None): |
||||
|
super().__init__(parent) |
||||
|
self.tx_history = [] |
||||
|
|
||||
|
def rowCount(self, index): |
||||
|
return len(self.tx_history) |
||||
|
|
||||
|
def data(self, index, role): |
||||
|
if role == Qt.DisplayRole: |
||||
|
return str(self.tx_history[index.row()]['bc_value']) |
||||
|
|
||||
|
def set_history(self, history): |
||||
|
self.beginInsertRows(QModelIndex(), 0, len(history) - 1) |
||||
|
self.tx_history = history |
||||
|
self.endInsertRows() |
||||
|
|
||||
|
class QEWallet(QObject): |
||||
|
def __init__(self, wallet, parent=None): |
||||
|
super().__init__(parent) |
||||
|
self.wallet = wallet |
||||
|
self.get_history() |
||||
|
|
||||
|
_logger = get_logger(__name__) |
||||
|
|
||||
|
_historyModel = QETransactionsListModel() |
||||
|
|
||||
|
@pyqtProperty(QETransactionsListModel) |
||||
|
def historyModel(self): |
||||
|
return self._historyModel |
||||
|
|
||||
|
def get_history(self): |
||||
|
history = self.wallet.get_detailed_history(show_addresses = True) |
||||
|
txs = history['transactions'] |
||||
|
self._historyModel.set_history(txs) |
||||
|
|
Loading…
Reference in new issue