URI: 
       t__init__.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       t__init__.py (15724B)
       ---
            1 #!/usr/bin/env python
            2 #
            3 # Electrum - lightweight Bitcoin client
            4 # Copyright (C) 2012 thomasv@gitorious
            5 #
            6 # Permission is hereby granted, free of charge, to any person
            7 # obtaining a copy of this software and associated documentation files
            8 # (the "Software"), to deal in the Software without restriction,
            9 # including without limitation the rights to use, copy, modify, merge,
           10 # publish, distribute, sublicense, and/or sell copies of the Software,
           11 # and to permit persons to whom the Software is furnished to do so,
           12 # subject to the following conditions:
           13 #
           14 # The above copyright notice and this permission notice shall be
           15 # included in all copies or substantial portions of the Software.
           16 #
           17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
           21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
           22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
           23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           24 # SOFTWARE.
           25 
           26 import os
           27 import signal
           28 import sys
           29 import traceback
           30 import threading
           31 from typing import Optional, TYPE_CHECKING, List
           32 
           33 
           34 try:
           35     import PyQt5
           36 except Exception:
           37     sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'")
           38 
           39 from PyQt5.QtGui import QGuiApplication
           40 from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QWidget, QMenu,
           41                              QMessageBox)
           42 from PyQt5.QtCore import QObject, pyqtSignal, QTimer
           43 import PyQt5.QtCore as QtCore
           44 
           45 from electrum.i18n import _, set_language
           46 from electrum.plugin import run_hook
           47 from electrum.base_wizard import GoBack
           48 from electrum.util import (UserCancelled, profiler,
           49                            WalletFileException, BitcoinException, get_new_wallet_name)
           50 from electrum.wallet import Wallet, Abstract_Wallet
           51 from electrum.wallet_db import WalletDB
           52 from electrum.logging import Logger
           53 
           54 from .installwizard import InstallWizard, WalletAlreadyOpenInMemory
           55 from .util import get_default_language, read_QIcon, ColorScheme, custom_message_box
           56 from .main_window import ElectrumWindow
           57 from .network_dialog import NetworkDialog
           58 from .stylesheet_patcher import patch_qt_stylesheet
           59 from .lightning_dialog import LightningDialog
           60 from .watchtower_dialog import WatchtowerDialog
           61 
           62 if TYPE_CHECKING:
           63     from electrum.daemon import Daemon
           64     from electrum.simple_config import SimpleConfig
           65     from electrum.plugin import Plugins
           66 
           67 
           68 class OpenFileEventFilter(QObject):
           69     def __init__(self, windows):
           70         self.windows = windows
           71         super(OpenFileEventFilter, self).__init__()
           72 
           73     def eventFilter(self, obj, event):
           74         if event.type() == QtCore.QEvent.FileOpen:
           75             if len(self.windows) >= 1:
           76                 self.windows[0].pay_to_URI(event.url().toString())
           77                 return True
           78         return False
           79 
           80 
           81 class QElectrumApplication(QApplication):
           82     new_window_signal = pyqtSignal(str, object)
           83 
           84 
           85 class QNetworkUpdatedSignalObject(QObject):
           86     network_updated_signal = pyqtSignal(str, object)
           87 
           88 
           89 class ElectrumGui(Logger):
           90 
           91     @profiler
           92     def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'):
           93         set_language(config.get('language', get_default_language()))
           94         Logger.__init__(self)
           95         self.logger.info(f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}")
           96         # Uncomment this call to verify objects are being properly
           97         # GC-ed when windows are closed
           98         #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
           99         #                            ElectrumWindow], interval=5)])
          100         QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
          101         if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
          102             QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
          103         if hasattr(QGuiApplication, 'setDesktopFileName'):
          104             QGuiApplication.setDesktopFileName('electrum.desktop')
          105         self.gui_thread = threading.current_thread()
          106         self.config = config
          107         self.daemon = daemon
          108         self.plugins = plugins
          109         self.windows = []  # type: List[ElectrumWindow]
          110         self.efilter = OpenFileEventFilter(self.windows)
          111         self.app = QElectrumApplication(sys.argv)
          112         self.app.installEventFilter(self.efilter)
          113         self.app.setWindowIcon(read_QIcon("electrum.png"))
          114         # timer
          115         self.timer = QTimer(self.app)
          116         self.timer.setSingleShot(False)
          117         self.timer.setInterval(500)  # msec
          118 
          119         self.network_dialog = None
          120         self.lightning_dialog = None
          121         self.watchtower_dialog = None
          122         self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
          123         self._num_wizards_in_progress = 0
          124         self._num_wizards_lock = threading.Lock()
          125         # init tray
          126         self.dark_icon = self.config.get("dark_icon", False)
          127         self.tray = QSystemTrayIcon(self.tray_icon(), None)
          128         self.tray.setToolTip('Electrum')
          129         self.tray.activated.connect(self.tray_activated)
          130         self.build_tray_menu()
          131         self.tray.show()
          132         self.app.new_window_signal.connect(self.start_new_window)
          133         self.set_dark_theme_if_needed()
          134         run_hook('init_qt', self)
          135 
          136     def set_dark_theme_if_needed(self):
          137         use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
          138         if use_dark_theme:
          139             try:
          140                 import qdarkstyle
          141                 self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
          142             except BaseException as e:
          143                 use_dark_theme = False
          144                 self.logger.warning(f'Error setting dark theme: {repr(e)}')
          145         # Apply any necessary stylesheet patches
          146         patch_qt_stylesheet(use_dark_theme=use_dark_theme)
          147         # Even if we ourselves don't set the dark theme,
          148         # the OS/window manager/etc might set *a dark theme*.
          149         # Hence, try to choose colors accordingly:
          150         ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)
          151 
          152     def build_tray_menu(self):
          153         # Avoid immediate GC of old menu when window closed via its action
          154         if self.tray.contextMenu() is None:
          155             m = QMenu()
          156             self.tray.setContextMenu(m)
          157         else:
          158             m = self.tray.contextMenu()
          159             m.clear()
          160         network = self.daemon.network
          161         m.addAction(_("Network"), self.show_network_dialog)
          162         if network and network.lngossip:
          163             m.addAction(_("Lightning Network"), self.show_lightning_dialog)
          164         if network and network.local_watchtower:
          165             m.addAction(_("Local Watchtower"), self.show_watchtower_dialog)
          166         for window in self.windows:
          167             name = window.wallet.basename()
          168             submenu = m.addMenu(name)
          169             submenu.addAction(_("Show/Hide"), window.show_or_hide)
          170             submenu.addAction(_("Close"), window.close)
          171         m.addAction(_("Dark/Light"), self.toggle_tray_icon)
          172         m.addSeparator()
          173         m.addAction(_("Exit Electrum"), self.close)
          174 
          175     def tray_icon(self):
          176         if self.dark_icon:
          177             return read_QIcon('electrum_dark_icon.png')
          178         else:
          179             return read_QIcon('electrum_light_icon.png')
          180 
          181     def toggle_tray_icon(self):
          182         self.dark_icon = not self.dark_icon
          183         self.config.set_key("dark_icon", self.dark_icon, True)
          184         self.tray.setIcon(self.tray_icon())
          185 
          186     def tray_activated(self, reason):
          187         if reason == QSystemTrayIcon.DoubleClick:
          188             if all([w.is_hidden() for w in self.windows]):
          189                 for w in self.windows:
          190                     w.bring_to_top()
          191             else:
          192                 for w in self.windows:
          193                     w.hide()
          194 
          195     def close(self):
          196         for window in self.windows:
          197             window.close()
          198         if self.network_dialog:
          199             self.network_dialog.close()
          200         if self.lightning_dialog:
          201             self.lightning_dialog.close()
          202         if self.watchtower_dialog:
          203             self.watchtower_dialog.close()
          204         self.app.quit()
          205 
          206     def new_window(self, path, uri=None):
          207         # Use a signal as can be called from daemon thread
          208         self.app.new_window_signal.emit(path, uri)
          209 
          210     def show_lightning_dialog(self):
          211         if not self.daemon.network.has_channel_db():
          212             return
          213         if not self.lightning_dialog:
          214             self.lightning_dialog = LightningDialog(self)
          215         self.lightning_dialog.bring_to_top()
          216 
          217     def show_watchtower_dialog(self):
          218         if not self.watchtower_dialog:
          219             self.watchtower_dialog = WatchtowerDialog(self)
          220         self.watchtower_dialog.bring_to_top()
          221 
          222     def show_network_dialog(self):
          223         if self.network_dialog:
          224             self.network_dialog.on_update()
          225             self.network_dialog.show()
          226             self.network_dialog.raise_()
          227             return
          228         self.network_dialog = NetworkDialog(self.daemon.network, self.config,
          229                                 self.network_updated_signal_obj)
          230         self.network_dialog.show()
          231 
          232     def _create_window_for_wallet(self, wallet):
          233         w = ElectrumWindow(self, wallet)
          234         self.windows.append(w)
          235         self.build_tray_menu()
          236         w.warn_if_testnet()
          237         w.warn_if_watching_only()
          238         return w
          239 
          240     def count_wizards_in_progress(func):
          241         def wrapper(self: 'ElectrumGui', *args, **kwargs):
          242             with self._num_wizards_lock:
          243                 self._num_wizards_in_progress += 1
          244             try:
          245                 return func(self, *args, **kwargs)
          246             finally:
          247                 with self._num_wizards_lock:
          248                     self._num_wizards_in_progress -= 1
          249         return wrapper
          250 
          251     @count_wizards_in_progress
          252     def start_new_window(self, path, uri, *, app_is_starting=False):
          253         '''Raises the window for the wallet if it is open.  Otherwise
          254         opens the wallet and creates a new window for it'''
          255         wallet = None
          256         try:
          257             wallet = self.daemon.load_wallet(path, None)
          258         except BaseException as e:
          259             self.logger.exception('')
          260             custom_message_box(icon=QMessageBox.Warning,
          261                                parent=None,
          262                                title=_('Error'),
          263                                text=_('Cannot load wallet') + ' (1):\n' + repr(e))
          264             # if app is starting, still let wizard to appear
          265             if not app_is_starting:
          266                 return
          267         if not wallet:
          268             try:
          269                 wallet = self._start_wizard_to_select_or_create_wallet(path)
          270             except (WalletFileException, BitcoinException) as e:
          271                 self.logger.exception('')
          272                 custom_message_box(icon=QMessageBox.Warning,
          273                                    parent=None,
          274                                    title=_('Error'),
          275                                    text=_('Cannot load wallet') + ' (2):\n' + repr(e))
          276         if not wallet:
          277             return
          278         # create or raise window
          279         try:
          280             for window in self.windows:
          281                 if window.wallet.storage.path == wallet.storage.path:
          282                     break
          283             else:
          284                 window = self._create_window_for_wallet(wallet)
          285         except BaseException as e:
          286             self.logger.exception('')
          287             custom_message_box(icon=QMessageBox.Warning,
          288                                parent=None,
          289                                title=_('Error'),
          290                                text=_('Cannot create window for wallet') + ':\n' + repr(e))
          291             if app_is_starting:
          292                 wallet_dir = os.path.dirname(path)
          293                 path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir))
          294                 self.start_new_window(path, uri)
          295             return
          296         if uri:
          297             window.pay_to_URI(uri)
          298         window.bring_to_top()
          299         window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
          300 
          301         window.activateWindow()
          302         return window
          303 
          304     def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]:
          305         wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self)
          306         try:
          307             path, storage = wizard.select_storage(path, self.daemon.get_wallet)
          308             # storage is None if file does not exist
          309             if storage is None:
          310                 wizard.path = path  # needed by trustedcoin plugin
          311                 wizard.run('new')
          312                 storage, db = wizard.create_storage(path)
          313             else:
          314                 db = WalletDB(storage.read(), manual_upgrades=False)
          315                 wizard.run_upgrades(storage, db)
          316         except (UserCancelled, GoBack):
          317             return
          318         except WalletAlreadyOpenInMemory as e:
          319             return e.wallet
          320         finally:
          321             wizard.terminate()
          322         # return if wallet creation is not complete
          323         if storage is None or db.get_action():
          324             return
          325         wallet = Wallet(db, storage, config=self.config)
          326         wallet.start_network(self.daemon.network)
          327         self.daemon.add_wallet(wallet)
          328         return wallet
          329 
          330     def close_window(self, window: ElectrumWindow):
          331         if window in self.windows:
          332            self.windows.remove(window)
          333         self.build_tray_menu()
          334         # save wallet path of last open window
          335         if not self.windows:
          336             self.config.save_last_wallet(window.wallet)
          337         run_hook('on_close_window', window)
          338         self.daemon.stop_wallet(window.wallet.storage.path)
          339 
          340     def init_network(self):
          341         # Show network dialog if config does not exist
          342         if self.daemon.network:
          343             if self.config.get('auto_connect') is None:
          344                 wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self)
          345                 wizard.init_network(self.daemon.network)
          346                 wizard.terminate()
          347 
          348     def main(self):
          349         try:
          350             self.init_network()
          351         except UserCancelled:
          352             return
          353         except GoBack:
          354             return
          355         except BaseException as e:
          356             self.logger.exception('')
          357             return
          358         self.timer.start()
          359 
          360         path = self.config.get_wallet_path(use_gui_last_wallet=True)
          361         if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
          362             return
          363         signal.signal(signal.SIGINT, lambda *args: self.app.quit())
          364 
          365         def quit_after_last_window():
          366             # keep daemon running after close
          367             if self.config.get('daemon'):
          368                 return
          369             # check if a wizard is in progress
          370             with self._num_wizards_lock:
          371                 if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
          372                     return
          373                 if self.config.get('persist_daemon'):
          374                     return
          375             self.app.quit()
          376         self.app.setQuitOnLastWindowClosed(False)  # so _we_ can decide whether to quit
          377         self.app.lastWindowClosed.connect(quit_after_last_window)
          378 
          379         def clean_up():
          380             # Shut down the timer cleanly
          381             self.timer.stop()
          382             # clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
          383             event = QtCore.QEvent(QtCore.QEvent.Clipboard)
          384             self.app.sendEvent(self.app.clipboard(), event)
          385             self.tray.hide()
          386         self.app.aboutToQuit.connect(clean_up)
          387 
          388         # main loop
          389         self.app.exec_()
          390         # on some platforms the exec_ call may not return, so use clean_up()
          391 
          392     def stop(self):
          393         self.logger.info('closing GUI')
          394         self.app.quit()