2 # -*- coding: utf-8 -*-
3 # Spesmilo -- Python Bitcoin user interface
4 # Copyright © 2011 Luke Dashjr
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, version 3 only.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from PySide.QtCore import *
21 from PySide.QtGui import *
22 from PySide.QtNetwork import *
26 from settings import SpesmiloSettings, SettingsDialog, icon, quietPopen
28 def receive_uri(w, uri):
30 sw = send.SendDialog(w.core, w.parent(), uri=uri)
33 def _startup(rootwindow, *args, **kwargs):
34 if SpesmiloSettings.useInternalCore():
36 user, passwd, port = SpesmiloSettings.getInternalCoreAuth()
37 cmd = ('bitcoind', '-rpcuser=%s' % (user,), '-rpcpassword=%s' % (passwd,), '-rpcallowip=127.0.0.1', '-rpcport=%d' % (port,))
40 cmd += options.bitcoind
43 ipcsrv = ipc.ipc_handler('Bitcoin')
44 ipcsrv.have_uri.connect(lambda x: receive_uri(rootwindow, x), Qt.QueuedConnection)
46 rootwindow.ipcsrv = ipcsrv
47 rootwindow.start(*args, **kwargs)
49 class ConnectingDialog(QDialog):
50 def __init__(self, parent):
51 super(ConnectingDialog, self).__init__(parent)
52 main_layout = QVBoxLayout(self)
53 main_layout.addWidget(QLabel(self.tr('Connecting...')))
54 progressbar = QProgressBar()
55 progressbar.setMinimum(0)
56 progressbar.setMaximum(0)
57 main_layout.addWidget(progressbar)
59 button_layout = QHBoxLayout()
60 button_layout.addStretch()
61 cfgbtn = QPushButton(self.tr('&Configure'))
62 cfgbtn.clicked.connect(self.config)
64 button_layout.addWidget(cfgbtn)
65 abortbtn = QPushButton(self.tr('&Abort'))
66 abortbtn.clicked.connect(self.stop)
67 button_layout.addWidget(abortbtn)
68 main_layout.addLayout(button_layout)
70 if parent is not None:
71 self.setWindowIcon(parent.bitcoin_icon)
72 self.setWindowTitle(self.tr('Connecting...'))
73 self.setAttribute(Qt.WA_DeleteOnClose, False)
78 rootwindow = self.parent()
85 def closeEvent(self, event):
89 class TrayIcon(QSystemTrayIcon):
90 def __init__(self, core, parent):
91 super(TrayIcon, self).__init__(parent)
93 self.current_window = None
95 self.setIcon(self.parent().bitcoin_icon)
96 self.activated.connect(self.toggle_window)
99 def create_menu(self):
101 self.cash_act = QAction(self.tr('&Cashier'), self)
102 self.cash_act.triggered.connect(self.show_cashier)
103 self.cash_act.setDisabled(True)
104 tray_menu.addAction(self.cash_act)
105 self.send_act = QAction(self.tr('&Send funds'), self)
106 self.send_act.triggered.connect(self.show_send)
107 self.send_act.setDisabled(True)
108 tray_menu.addAction(self.send_act)
109 tray_menu.addSeparator()
110 quit_act = QAction(self.tr('&Quit'), self)
111 quit_act.triggered.connect(self.quit)
112 tray_menu.addAction(quit_act)
113 self.setContextMenu(tray_menu)
115 def delete_window(self):
116 if self.current_window is not None:
117 self.current_window.deleteLater()
119 def create_connecting(self):
121 self.current_window = ConnectingDialog(self.parent())
123 def create_cashier(self):
124 if hasattr(self.current_window, 'cfgbtn'):
125 self.current_window.cfgbtn.setDisabled(True)
126 self.cash_act.setDisabled(False)
127 self.send_act.setDisabled(False)
129 self.current_window = cashier.Cashier(self.core, qApp.clipboard(),
134 def show_cashier(self):
135 self.current_window.show()
137 def toggle_window(self, reason):
138 if reason == self.Trigger:
139 if self.current_window is not None:
140 if self.current_window.isVisible():
141 self.current_window.hide()
143 self.current_window.show()
146 send.SendDialog(self.core, self.parent())
151 class RootWindow(QMainWindow):
153 CLIENT_CONNECTING = 1
154 CLIENT_DOWNLOADING = 2
158 super(RootWindow, self).__init__()
161 self.state = self.CLIENT_NONE
163 def setup(self, options, args):
164 icon._default = icon(options.icon, *icon._defaultSearch)
165 self.bitcoin_icon = icon()
166 self.caption = options.caption
169 def start(self, options = None, args = None):
170 self.state = self.CLIENT_NONE
171 self.uri = SpesmiloSettings.getEffectiveURI()
172 self.core = core_interface.CoreInterface(self.uri)
173 self.core.tr = lambda s: self.app.translate('CoreInterface', s)
174 if hasattr(self, 'tray'):
177 self.tray = TrayIcon(self.core, self)
179 refresh_state_timer = QTimer(self)
180 self.refresh_state_timer = refresh_state_timer
181 refresh_state_timer.timeout.connect(self.refresh_state)
182 refresh_state_timer.start(1000)
186 self.stop(doQuit=False)
187 sd = SettingsDialog(self)
188 self.tray.current_window = sd
189 sd.accepted.connect(lambda: _startup(self))
190 sd.rejected.connect(lambda: qApp.quit())
192 def refresh_state(self):
194 if self.state == self.CLIENT_NONE:
195 self.state = self.CLIENT_CONNECTING
197 is_init = self.core.is_initialised()
200 # show initialising dialog
201 self.tray.create_connecting()
202 elif self.state == self.CLIENT_CONNECTING:
204 is_init = self.core.is_initialised()
207 traceback.print_exc()
208 error = QMessageBox(QMessageBox.Critical,
209 self.tr('Error connecting'),
214 # some voodoo here checking whether we have blocks
215 self.state = self.CLIENT_RUNNING
216 self.tray.create_cashier()
218 def closeEvent(self, event):
219 super(RootWindow, self).closeEvent(event)
222 def stop(self, doQuit = True):
224 self.refresh_state_timer.stop()
225 if SpesmiloSettings.useInternalCore() and self.core:
226 # Keep looping until connected so we can issue the stop command
230 except core_interface.JSONRPCException:
240 # Re-proprogate exception & trigger app exit so we can break out
245 if hasattr(self, 'ipcsrv'):
254 import code, threading
258 frame = sys.exc_info()[2].tb_frame.f_back
259 namespace = frame.f_globals.copy()
260 namespace.update(frame.f_locals)
263 code.interact(banner=None, local=namespace)
264 threading.Timer(0, CLI).start()
266 if __name__ == '__main__':
267 def vararg_callback(option, opt_str, value, parser):
271 for arg in parser.rargs:
276 del parser.rargs[:len(value) + 1]
277 setattr(parser.values, option.dest, tuple(value))
283 if hasattr(sys, 'frozen') and sys.frozen == "windows_exe":
284 sys.stderr = sys.stdout
286 app = QApplication(sys.argv)
288 if not QFontMetrics(font).inFont(0xe9d9):
289 font.setFamily(font.family() + ', tonal, Tonal (Luxi Mono)')
291 SpesmiloSettings.loadTranslator()
293 argp = optparse.OptionParser(usage=app.tr('Usage: %prog [options] [URI]'))
294 #argp.add_option('URI', nargs='?', help=app.tr('a bitcoin: URI to open a send dialog to'))
295 argp.add_option('--caption', dest='caption', nargs=1, default=None,
296 help=app.tr('Use this caption for the cashier window'))
297 #argp.add_option('--cashier', dest='cashier', action='store_true', default=False,
298 # help=app.tr('Opens a view of your transactions'))
299 #argp.add_option('--config', dest='config', nargs=1,
300 # help=app.tr('Use an alternative config'))
301 argp.add_option('--debug', dest='debug', action='store_true', default=False,
302 help=app.tr('Opens an interactive Python prompt, and enables infinite in-RAM logging'))
303 argp.add_option('--icon', dest='icon', nargs=1, default=None,
304 help=app.tr('Use this window icon'))
305 argp.add_option('--send', dest='send', action='store_true', default=False,
306 help=app.tr('Opens a dialog to send funds'))
307 argp.add_option('--bitcoind', dest='bitcoind', action='callback', callback=vararg_callback, default=(),
308 help=app.tr('Pass remaining arguments to bitcoind (internal core only)'))
310 args = app.arguments()
311 # Workaround PySide/Windows bug
312 if sys.argv[0] in args:
313 i = args.index(sys.argv[0])
318 args[args.index('-icon')] = '--icon'
319 (options, args) = argp.parse_args(args)
322 if args or options.send:
323 if SpesmiloSettings.useInternalCore():
326 ipc.ipc_send('Bitcoin', args[0] if args else 'bitcoin:')
330 rootwindow = send.SendDialog(autostart=False)
332 app.setQuitOnLastWindowClosed(False)
333 rootwindow = RootWindow()
334 rootwindow.setup(options, args)
336 if SpesmiloSettings.isConfigured():
337 _startup(rootwindow, options, args)
339 sd = SettingsDialog(rootwindow)
340 sd.accepted.connect(lambda: _startup(rootwindow, options, args))
341 sd.rejected.connect(lambda: qApp.quit())
343 SpesmiloSettings.debugMode = True
345 sys.exit(app.exec_())