use bitcoinrpc for "make local"
[bitcoin:spesmilo.git] / main.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # Spesmilo -- Python Bitcoin user interface
4 # Copyright © 2011 Luke Dashjr
5 #
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.
9 #
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.
14 #
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/>.
17
18 import socket
19
20 from PySide.QtCore import *
21 from PySide.QtGui import *
22 from PySide.QtNetwork import *
23 import core_interface
24 import cashier
25 import send
26 from settings import SpesmiloSettings, SettingsDialog, icon, quietPopen
27
28 def receive_uri(w, uri):
29     import send
30     sw = send.SendDialog(w.core, w.parent(), uri=uri)
31     sw.setFocus()
32
33 def _startup(rootwindow, *args, **kwargs):
34     if SpesmiloSettings.useInternalCore():
35         import os
36         user, passwd, port = SpesmiloSettings.getInternalCoreAuth()
37         cmd = ('bitcoind', '-rpcuser=%s' % (user,), '-rpcpassword=%s' % (passwd,), '-rpcallowip=127.0.0.1', '-rpcport=%d' % (port,))
38         if len(args):
39             options = args[0]
40             cmd += options.bitcoind
41         quietPopen(cmd)
42         import ipc
43         ipcsrv = ipc.ipc_handler('Bitcoin')
44         ipcsrv.have_uri.connect(lambda x: receive_uri(rootwindow, x), Qt.QueuedConnection)
45         ipcsrv.start()
46         rootwindow.ipcsrv = ipcsrv
47     rootwindow.start(*args, **kwargs)
48
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)
58         
59         button_layout = QHBoxLayout()
60         button_layout.addStretch()
61         cfgbtn = QPushButton(self.tr('&Configure'))
62         cfgbtn.clicked.connect(self.config)
63         self.cfgbtn = cfgbtn
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)
69
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)
74         self.show()
75
76     def config(self):
77         self.hide()
78         rootwindow = self.parent()
79         rootwindow.config()
80
81     def stop(self):
82         self.hide()
83         self.parent().stop()
84
85     def closeEvent(self, event):
86         self.hide()
87         event.ignore()
88
89 class TrayIcon(QSystemTrayIcon):
90     def __init__(self, core, parent):
91         super(TrayIcon, self).__init__(parent)
92         self.core = core
93         self.current_window = None
94         self.create_menu()
95         self.setIcon(self.parent().bitcoin_icon)
96         self.activated.connect(self.toggle_window)
97         self.show()
98     
99     def create_menu(self):
100         tray_menu = QMenu()
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)
114
115     def delete_window(self):
116         if self.current_window is not None:
117             self.current_window.deleteLater()
118
119     def create_connecting(self):
120         self.delete_window()
121         self.current_window = ConnectingDialog(self.parent())
122
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)
128         self.delete_window()
129         self.current_window = cashier.Cashier(self.core, qApp.clipboard(),
130                                               self.parent(),
131                                               tray=self)
132         self.show_cashier()
133
134     def show_cashier(self):
135         self.current_window.show()
136
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()
142                 else:
143                     self.current_window.show()
144
145     def show_send(self):
146         send.SendDialog(self.core, self.parent())
147
148     def quit(self):
149         self.parent().stop()
150
151 class RootWindow(QMainWindow):
152     CLIENT_NONE = 0
153     CLIENT_CONNECTING = 1
154     CLIENT_DOWNLOADING = 2
155     CLIENT_RUNNING = 3
156
157     def __init__(self):
158         super(RootWindow, self).__init__()
159         self.app = qApp
160         
161         self.state = self.CLIENT_NONE
162     
163     def setup(self, options, args):
164         icon._default = icon(options.icon, *icon._defaultSearch)
165         self.bitcoin_icon = icon()
166         self.caption = options.caption
167         self.core = None
168
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'):
175             self.tray.hide()
176             del self.tray
177         self.tray = TrayIcon(self.core, self)
178
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)
183         self.refresh_state()
184
185     def config(self):
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())
191
192     def refresh_state(self):
193         is_init = False
194         if self.state == self.CLIENT_NONE:
195             self.state = self.CLIENT_CONNECTING
196             try:
197                 is_init = self.core.is_initialised()
198             except:
199                 pass
200             # show initialising dialog
201             self.tray.create_connecting()
202         elif self.state == self.CLIENT_CONNECTING:
203             try:
204                 is_init = self.core.is_initialised()
205             except Exception, e:
206                 import traceback
207                 traceback.print_exc()
208                 error = QMessageBox(QMessageBox.Critical, 
209                                     self.tr('Error connecting'),
210                                     self.tr(str(e)))
211                 error.exec_()
212                 self.config()
213         if is_init:
214             # some voodoo here checking whether we have blocks
215             self.state = self.CLIENT_RUNNING
216             self.tray.create_cashier()
217
218     def closeEvent(self, event):
219         super(RootWindow, self).closeEvent(event)
220         self.stop()
221
222     def stop(self, doQuit = True):
223         try:
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
227                 while True:
228                     try:
229                         self.core.stop()
230                     except core_interface.JSONRPCException:
231                         pass
232                     except IOError:
233                         break
234                     except socket.error:
235                         break
236                     except:
237                         raise
238                     else:
239                         break
240         # Re-proprogate exception & trigger app exit so we can break out
241         except:
242             raise
243         finally:
244             self.core = None
245             if hasattr(self, 'ipcsrv'):
246                 try:
247                     self.ipcsrv.stop()
248                 except:
249                     pass
250             if doQuit:
251                 qApp.quit()
252
253 def _RunCLI():
254     import code, threading
255     try:
256         raise None
257     except:
258         frame = sys.exc_info()[2].tb_frame.f_back
259     namespace = frame.f_globals.copy()
260     namespace.update(frame.f_locals)
261
262     def CLI():
263         code.interact(banner=None, local=namespace)
264     threading.Timer(0, CLI).start()
265
266 if __name__ == '__main__':
267     def vararg_callback(option, opt_str, value, parser):
268         assert value is None
269         value = []
270
271         for arg in parser.rargs:
272             if arg == '--':
273                 break
274             value.append(arg)
275
276         del parser.rargs[:len(value) + 1]
277         setattr(parser.values, option.dest, tuple(value))
278
279     import optparse
280     import os
281     import sys
282
283     if hasattr(sys, 'frozen') and sys.frozen == "windows_exe":
284         sys.stderr = sys.stdout
285
286     app = QApplication(sys.argv)
287     font = app.font()
288     if not QFontMetrics(font).inFont(0xe9d9):
289         font.setFamily(font.family() + ', tonal, Tonal (Luxi Mono)')
290         app.setFont(font)
291     SpesmiloSettings.loadTranslator()
292
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)'))
309
310     args = app.arguments()
311     # Workaround PySide/Windows bug
312     if sys.argv[0] in args:
313         i = args.index(sys.argv[0])
314         if i:
315             args[0:i] = ()
316     # Workaround KDE bug
317     if '-icon' in args:
318         args[args.index('-icon')] = '--icon'
319     (options, args) = argp.parse_args(args)
320     args[0:1] = ()
321
322     if args or options.send:
323         if SpesmiloSettings.useInternalCore():
324             try:
325                 import ipc
326                 ipc.ipc_send('Bitcoin', args[0] if args else 'bitcoin:')
327                 exit(0)
328             except Exception:
329                 pass
330         rootwindow = send.SendDialog(autostart=False)
331     else:
332         app.setQuitOnLastWindowClosed(False)
333         rootwindow = RootWindow()
334         rootwindow.setup(options, args)
335
336     if SpesmiloSettings.isConfigured():
337         _startup(rootwindow, options, args)
338     else:
339         sd = SettingsDialog(rootwindow)
340         sd.accepted.connect(lambda: _startup(rootwindow, options, args))
341         sd.rejected.connect(lambda: qApp.quit())
342     if options.debug:
343         SpesmiloSettings.debugMode = True
344         _RunCLI()
345     sys.exit(app.exec_())