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