electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 2d13107897f8e7acb9c7f5a4c1f54780934784db
parent d929c4d2ddc4dd910c8249a456a850255e86aa28
Author: ThomasV <thomasv@gitorious>
Date:   Sun, 18 Nov 2012 11:34:52 +0100

make a regular gui module for android

Diffstat:
Melectrum | 136++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Delectrum4a.py | 986-------------------------------------------------------------------------------
Mlib/gui.py | 18+-----------------
Alib/gui_android.py | 970+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/gui_qt.py | 17+----------------
Mlib/interface.py | 22++++++++++++++++++++++
Mlib/simple_config.py | 48+++++++++++++++++++-----------------------------
Mlib/util.py | 10+++++-----
Mlib/wallet.py | 18+++++++++++-------
9 files changed, 1102 insertions(+), 1123 deletions(-)

diff --git a/electrum b/electrum @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import sys +import sys, os import optparse try: @@ -121,10 +121,7 @@ def prompt_password(prompt, confirm=True): password = None return password - - -if __name__ == '__main__': - +def parse_args(): usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands)) parser = optparse.OptionParser(prog=usage) parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text") @@ -139,12 +136,20 @@ if __name__ == '__main__': parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h") parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information") - options, args = parser.parse_args() + return parser.parse_args() + +if __name__ == '__main__': + + options, args = parse_args() set_verbosity(options.verbose) # config is an object passed to the various constructors (wallet, interface, gui) - config = SimpleConfig(options) + if 'ANDROID_DATA' in os.environ: + config = SimpleConfig({'wallet_path':"/sdcard/electrum.dat", 'blockchain_headers_path':'/sdcard', 'gui':'android'}) + else: + config = SimpleConfig(eval(str(options))) + wallet = Wallet(config) if len(args)==0: @@ -181,6 +186,11 @@ if __name__ == '__main__': import lib.gui_text as gui except ImportError: import electrum.gui_text as gui + elif pref_gui == 'android': + try: + import lib.gui_android as gui + except ImportError: + import electrum.gui_android as gui else: sys.exit("Error: Unknown GUI: " + pref_gui ) @@ -201,9 +211,9 @@ if __name__ == '__main__': found = config.wallet_file_exists if not found: found = gui.restore_or_create() - except SystemExit, e: + except SystemExit(e): exit(e) - except BaseException, e: + except BaseException(e): import traceback traceback.print_exc(file=sys.stdout) #gui.show_message(e.message) @@ -222,8 +232,8 @@ if __name__ == '__main__': cmd = 'help' if not config.wallet_file_exists and cmd not in ['help','create','restore']: - print "Error: Wallet file not found." - print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option" + print("Error: Wallet file not found.") + print("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option") sys.exit(0) if cmd in ['create', 'restore']: @@ -267,30 +277,30 @@ if __name__ == '__main__': verifier = WalletVerifier(interface, config) wallet.set_verifier(verifier) - print "Recovering wallet..." + print("Recovering wallet...") WalletSynchronizer(wallet, config).start() wallet.up_to_date_event.clear() wallet.up_to_date = False wallet.update() if wallet.is_found(): - print "Recovery successful" + print("Recovery successful") else: - print "Warning: Found no history for this wallet" + print("Warning: Found no history for this wallet") else: wallet.synchronize() wallet.fill_addressbook() wallet.save() - print "Wallet saved in '%s'"%wallet.config.path + print("Wallet saved in '%s'"%wallet.config.path) else: wallet.new_seed(None) wallet.init_mpk( wallet.seed ) wallet.synchronize() # there is no wallet thread wallet.save() - print "Your wallet generation seed is: " + wallet.seed - print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet." - print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:" - print "\""+' '.join(mnemonic_encode(wallet.seed))+"\"" - print "Wallet saved in '%s'"%wallet.config.path + print("Your wallet generation seed is: " + wallet.seed) + print("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.") + print("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:") + print("\""+' '.join(mnemonic_encode(wallet.seed))+"\"") + print("Wallet saved in '%s'"%wallet.config.path) if password: wallet.update_password(wallet.seed, None, password) @@ -328,9 +338,9 @@ if __name__ == '__main__': # important warning if cmd=='addresses' and options.show_keys: - print "WARNING: ALL your private keys are secret." - print "Exposing a single private key can compromise your entire wallet!" - print "In particular, DO NOT use 'redeem private key' services proposed by third parties." + print("WARNING: ALL your private keys are secret.") + print("Exposing a single private key can compromise your entire wallet!") + print("In particular, DO NOT use 'redeem private key' services proposed by third parties.") # commands needing password if cmd in protected_commands or ( cmd=='addresses' and options.show_keys): @@ -351,23 +361,23 @@ if __name__ == '__main__': try: wallet.import_key(keypair,password) wallet.save() - print "Keypair imported" - except BaseException, e: + print("Keypair imported") + except BaseException(e): print_error("Error: Keypair import failed: " + str(e)) if cmd == 'help': cmd2 = firstarg if cmd2 not in known_commands: parser.print_help() - print "Type 'electrum help <command>' to see the help for a specific command" - print "Type 'electrum --help' to see the list of options" - print "List of commands:", ', '.join(known_commands) + print("Type 'electrum help <command>' to see the help for a specific command") + print("Type 'electrum --help' to see the list of options") + print("List of commands:", ', '.join(known_commands)) else: - print known_commands[cmd2] + print(known_commands[cmd2]) elif cmd == 'seed': seed = wallet.pw_decode( wallet.seed, password) - print seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"' + print(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"') elif cmd == 'deseed': if not wallet.seed: @@ -376,7 +386,7 @@ if __name__ == '__main__': print_error("Error: This wallet is encrypted") else: ns = wallet.path + '.seed' - print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns) + print("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)) if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']: f = open(ns,'w') f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n") @@ -384,13 +394,13 @@ if __name__ == '__main__': wallet.seed = '' for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = '' wallet.save() - print "Done." + print("Done.") else: print_error("Action canceled.") elif cmd == 'reseed': if wallet.seed: - print "Warning: This wallet already has a seed", wallet.seed + print("Warning: This wallet already has a seed", wallet.seed) else: ns = wallet.path + '.seed' try: @@ -414,13 +424,13 @@ if __name__ == '__main__': wallet.init_mpk(seed) if mpk == wallet.master_public_key: wallet.save() - print "Done: " + wallet.path + print("Done: " + wallet.path) else: print_error("Error: Master public key does not match") elif cmd == 'validateaddress': addr = args[1] - print wallet.is_valid(addr) + print(wallet.is_valid(addr)) elif cmd == 'balance': try: @@ -430,36 +440,36 @@ if __name__ == '__main__': if addrs == []: c, u = wallet.get_balance() if u: - print Decimal( c ) / 100000000 , Decimal( u ) / 100000000 + print(Decimal( c ) / 100000000 , Decimal( u ) / 100000000) else: - print Decimal( c ) / 100000000 + print(Decimal( c ) / 100000000) else: for addr in addrs: c, u = wallet.get_addr_balance(addr) if u: - print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)) + print("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))) else: - print "%s %s" % (addr, str(Decimal(c)/100000000)) + print("%s %s" % (addr, str(Decimal(c)/100000000))) elif cmd in [ 'contacts']: for addr in wallet.addressbook: - print addr, " ", wallet.labels.get(addr) + print(addr, " ", wallet.labels.get(addr)) elif cmd == 'eval': - print eval(args[1]) + print(eval(args[1])) wallet.save() elif cmd == 'get': key = args[1] - print wallet.config.get(key) + print(wallet.config.get(key)) elif cmd == 'set': key, value = args[1:3] if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']: wallet.config.set_key(key, value, True) - print True + print(True) else: - print False + print(False) elif cmd in [ 'addresses']: for addr in wallet.all_addresses(): @@ -481,7 +491,7 @@ if __name__ == '__main__': m_addr = "%34s"%addr if options.show_keys: m_addr += ':' + str(wallet.get_private_key_base58(addr, password)) - print flags, m_addr, b, label + print(flags, m_addr, b, label) if cmd == 'history': import datetime @@ -496,8 +506,8 @@ if __name__ == '__main__': if not label: label = tx_hash else: label = label + ' '*(64 - len(label) ) - print "%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance) - print "# balance: ", format_satoshis(balance) + print("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance)) + print("# balance: ", format_satoshis(balance)) elif cmd == 'label': try: @@ -529,7 +539,7 @@ if __name__ == '__main__': for k, v in wallet.labels.items(): if v == to_address: to_address = k - print "alias", to_address + print("alias", to_address) break if change_addr and v == change_addr: change_addr = k @@ -543,9 +553,9 @@ if __name__ == '__main__': if tx and cmd=='payto': r, h = wallet.sendtx( tx ) - print h + print(h) else: - print tx + print(tx) if is_temporary: wallet.imported_keys.pop(from_addr) @@ -555,7 +565,7 @@ if __name__ == '__main__': elif cmd == 'sendtx': tx = args[1] r, h = wallet.sendtx( tx ) - print h + print(h) elif cmd == 'password': try: @@ -569,13 +579,13 @@ if __name__ == '__main__': elif cmd == 'signmessage': if len(args) < 3: print_error("Error: Invalid usage of signmessage.") - print known_commands[cmd] + print(known_commands[cmd]) sys.exit(1) address = args[1] message = ' '.join(args[2:]) if len(args) > 3: - print "Warning: Message was reconstructed from several arguments:", repr(message) - print wallet.sign_message(address, message, password) + print("Warning: Message was reconstructed from several arguments:", repr(message)) + print(wallet.sign_message(address, message, password)) elif cmd == 'verifymessage': try: @@ -584,30 +594,30 @@ if __name__ == '__main__': message = ' '.join(args[3:]) except: print_error("Error: Not all parameters were given, displaying help instead.") - print known_commands[cmd] + print(known_commands[cmd]) sys.exit(1) if len(args) > 4: - print "Warning: Message was reconstructed from several arguments:", repr(message) + print("Warning: Message was reconstructed from several arguments:", repr(message)) try: wallet.verify_message(address, signature, message) - print True + print(True) except BaseException as e: - print "Verification error: {0}".format(e) - print False + print_error("Verification error: {0}".format(e)) + print(False) elif cmd == 'freeze': addr = args[1] - print wallet.freeze(addr) + print(wallet.freeze(addr)) elif cmd == 'unfreeze': addr = args[1] - print wallet.unfreeze(addr) + print(wallet.unfreeze(addr)) elif cmd == 'prioritize': addr = args[1] - print wallet.prioritize(addr) + print(wallet.prioritize(addr)) elif cmd == 'unprioritize': addr = args[1] - print wallet.unprioritize(addr) + print(wallet.unprioritize(addr)) diff --git a/electrum4a.py b/electrum4a.py @@ -1,986 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2011 thomasv@gitorious -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - - - -import android - -from interface import WalletSynchronizer -from wallet import Wallet, format_satoshis -import mnemonic -from decimal import Decimal -import datetime, re - - - -def modal_dialog(title, msg = None): - droid.dialogCreateAlert(title,msg) - droid.dialogSetPositiveButtonText('OK') - droid.dialogShow() - droid.dialogGetResponse() - droid.dialogDismiss() - -def modal_input(title, msg, value = None, etype=None): - droid.dialogCreateInput(title, msg, value, etype) - droid.dialogSetPositiveButtonText('OK') - droid.dialogSetNegativeButtonText('Cancel') - droid.dialogShow() - response = droid.dialogGetResponse().result - droid.dialogDismiss() - if response.get('which') == 'positive': - return response.get('value') - -def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'): - droid.dialogCreateAlert(q, msg) - droid.dialogSetPositiveButtonText(pos_text) - droid.dialogSetNegativeButtonText(neg_text) - droid.dialogShow() - response = droid.dialogGetResponse().result - droid.dialogDismiss() - return response.get('which') == 'positive' - -def edit_label(addr): - v = modal_input('Edit label',None,wallet.labels.get(addr)) - if v is not None: - if v: - wallet.labels[addr] = v - else: - if addr in wallet.labels.keys(): - wallet.labels.pop(addr) - wallet.update_tx_history() - wallet.save() - droid.fullSetProperty("labelTextView", "text", v) - -def select_from_contacts(): - title = 'Contacts:' - droid.dialogCreateAlert(title) - l = [] - for i in range(len(wallet.addressbook)): - addr = wallet.addressbook[i] - label = wallet.labels.get(addr,addr) - l.append( label ) - droid.dialogSetItems(l) - droid.dialogSetPositiveButtonText('New contact') - droid.dialogShow() - response = droid.dialogGetResponse().result - droid.dialogDismiss() - - if response.get('which') == 'positive': - return 'newcontact' - - result = response.get('item') - print result - if result is not None: - addr = wallet.addressbook[result] - return addr - - -def select_from_addresses(): - droid.dialogCreateAlert("Addresses:") - l = [] - for i in range(len(wallet.addresses)): - addr = wallet.addresses[i] - label = wallet.labels.get(addr,addr) - l.append( label ) - droid.dialogSetItems(l) - droid.dialogShow() - response = droid.dialogGetResponse() - result = response.result.get('item') - droid.dialogDismiss() - if result is not None: - addr = wallet.addresses[result] - return addr - - -def protocol_name(p): - if p == 't': return 'TCP/stratum' - if p == 'h': return 'HTTP/Stratum' - if p == 'n': return 'TCP/native' - -def protocol_dialog(host, protocol, z): - droid.dialogCreateAlert('Protocol',host) - if z: - protocols = z.keys() - else: - protocols = ['t','h','n'] - l = [] - current = protocols.index(protocol) - for p in protocols: - l.append(protocol_name(p)) - droid.dialogSetSingleChoiceItems(l, current) - droid.dialogSetPositiveButtonText('OK') - droid.dialogSetNegativeButtonText('Cancel') - droid.dialogShow() - response = droid.dialogGetResponse().result - if not response: return - if response.get('which') == 'positive': - response = droid.dialogGetSelectedItems().result[0] - droid.dialogDismiss() - p = protocols[response] - port = z[p] - return host + ':' + port + ':' + p - - - - -def make_layout(s, scrollable = False): - content = """ - - <LinearLayout - android:id="@+id/zz" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="#ff222222"> - - <TextView - android:id="@+id/textElectrum" - android:text="Electrum" - android:textSize="7pt" - android:textColor="#ff4444ff" - android:gravity="left" - android:layout_height="wrap_content" - android:layout_width="match_parent" - /> - </LinearLayout> - - %s """%s - - if scrollable: - content = """ - <ScrollView - android:id="@+id/scrollview" - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <LinearLayout - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" > - - %s - - </LinearLayout> - </ScrollView> - """%content - - - return """<?xml version="1.0" encoding="utf-8"?> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/background" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#ff000022"> - - %s - </LinearLayout>"""%content - - - - -def main_layout(): - return make_layout(""" - <TextView android:id="@+id/balanceTextView" - android:layout_width="match_parent" - android:text="" - android:textColor="#ffffffff" - android:textAppearance="?android:attr/textAppearanceLarge" - android:padding="7dip" - android:textSize="8pt" - android:gravity="center_vertical|center_horizontal|left"> - </TextView> - - <TextView android:id="@+id/historyTextView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Recent transactions" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="center_vertical|center_horizontal|center"> - </TextView> - - %s """%get_history_layout(15),True) - - - -def qr_layout(addr): - return make_layout(""" - - <TextView android:id="@+id/addrTextView" - android:layout_width="match_parent" - android:layout_height="50" - android:text="%s" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="center_vertical|center_horizontal|center"> - </TextView> - - <ImageView - android:id="@+id/qrView" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="350" - android:antialias="false" - android:src="file:///sdcard/sl4a/qrcode.bmp" /> - - <TextView android:id="@+id/labelTextView" - android:layout_width="match_parent" - android:layout_height="50" - android:text="%s" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="center_vertical|center_horizontal|center"> - </TextView> - - """%(addr,wallet.labels.get(addr,'')), True) - -payto_layout = make_layout(""" - - <TextView android:id="@+id/recipientTextView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Pay to:" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="left"> - </TextView> - - - <EditText android:id="@+id/recipient" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:tag="Tag Me" android:inputType="text"> - </EditText> - - <LinearLayout android:id="@+id/linearLayout1" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <Button android:id="@+id/buttonQR" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="From QR code"></Button> - <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="From Contacts"></Button> - </LinearLayout> - - - <TextView android:id="@+id/labelTextView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Description:" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="left"> - </TextView> - - <EditText android:id="@+id/label" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:tag="Tag Me" android:inputType="text"> - </EditText> - - <TextView android:id="@+id/amountLabelTextView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Amount:" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="left"> - </TextView> - - <EditText android:id="@+id/amount" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:tag="Tag Me" android:inputType="numberDecimal"> - </EditText> - - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" android:id="@+id/linearLayout1"> - <Button android:id="@+id/buttonPay" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Send"></Button> - </LinearLayout>""",False) - - - -settings_layout = make_layout(""" <ListView - android:id="@+id/myListView" - android:layout_width="match_parent" - android:layout_height="wrap_content" />""") - - - -def get_history_values(n): - values = [] - h = wallet.get_tx_history() - - length = min(n, len(h)) - for i in range(length): - line = h[-i-1] - v = line['value'] - try: - dt = datetime.datetime.fromtimestamp( line['timestamp'] ) - if dt.date() == dt.today().date(): - time_str = str( dt.time() ) - else: - time_str = str( dt.date() ) - conf = 'v' - - except: - print line['timestamp'] - time_str = 'pending' - conf = 'o' - - tx_hash = line['tx_hash'] - label = wallet.labels.get(tx_hash) - is_default_label = (label == '') or (label is None) - if is_default_label: label = line['default_label'] - values.append((conf, ' ' + time_str, ' ' + format_satoshis(v,True), ' ' + label )) - - return values - - -def get_history_layout(n): - rows = "" - i = 0 - values = get_history_values(n) - for v in values: - a,b,c,d = v - color = "#ff00ff00" if a == 'v' else "#ffff0000" - rows += """ - <TableRow> - <TextView - android:id="@+id/hl_%d_col1" - android:layout_column="0" - android:text="%s" - android:textColor="%s" - android:padding="3" /> - <TextView - android:id="@+id/hl_%d_col2" - android:layout_column="1" - android:text="%s" - android:padding="3" /> - <TextView - android:id="@+id/hl_%d_col3" - android:layout_column="2" - android:text="%s" - android:padding="3" /> - <TextView - android:id="@+id/hl_%d_col4" - android:layout_column="3" - android:text="%s" - android:padding="4" /> - </TableRow>"""%(i,a,color,i,b,i,c,i,d) - i += 1 - - output = """ -<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:stretchColumns="0,1,2,3"> - %s -</TableLayout>"""% rows - return output - - -def set_history_layout(n): - values = get_history_values(n) - i = 0 - for v in values: - a,b,c,d = v - droid.fullSetProperty("hl_%d_col1"%i,"text", a) - - if a == 'v': - droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00") - else: - droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000") - - droid.fullSetProperty("hl_%d_col2"%i,"text", b) - droid.fullSetProperty("hl_%d_col3"%i,"text", c) - droid.fullSetProperty("hl_%d_col4"%i,"text", d) - i += 1 - - - - -status_text = '' -def update_layout(): - global status_text - if not wallet.interface.is_connected: - text = "Not connected..." - elif wallet.blocks == 0: - text = "Server not ready" - elif not wallet.up_to_date: - text = "Synchronizing..." - else: - c, u = wallet.get_balance() - text = "Balance:"+format_satoshis(c) - if u : text += ' [' + format_satoshis(u,True).strip() + ']' - - - # vibrate if status changed - if text != status_text: - if status_text and wallet.interface.is_connected and wallet.up_to_date: - droid.vibrate() - status_text = text - - droid.fullSetProperty("balanceTextView", "text", status_text) - - if wallet.up_to_date: - set_history_layout(15) - - - - -def pay_to(recipient, amount, fee, label): - - if wallet.use_encryption: - password = droid.dialogGetPassword('Password').result - if not password: return - else: - password = None - - droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...") - droid.dialogShow() - - try: - tx = wallet.mktx( recipient, amount, label, password, fee) - except BaseException, e: - modal_dialog('error', e.message) - droid.dialogDismiss() - return - - droid.dialogDismiss() - - r, h = wallet.sendtx( tx ) - if r: - modal_dialog('Payment sent', h) - return True - else: - modal_dialog('Error', h) - - - - - -def recover(): - - droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?") - droid.dialogSetPositiveButtonText('Create') - droid.dialogSetNeutralButtonText('Restore') - droid.dialogSetNegativeButtonText('Cancel') - droid.dialogShow() - response = droid.dialogGetResponse().result - droid.dialogDismiss() - if response.get('which') == 'negative': - exit(1) - - is_recovery = response.get('which') == 'neutral' - - if not is_recovery: - wallet.new_seed(None) - else: - if modal_question("Input method",None,'QR Code', 'mnemonic'): - code = droid.scanBarcode() - r = code.result - if r: - seed = r['extras']['SCAN_RESULT'] - else: - exit(1) - else: - m = modal_input('Mnemonic','please enter your code') - try: - seed = mnemonic.mn_decode(m.split(' ')) - except: - modal_dialog('error: could not decode this seed') - exit(1) - - wallet.seed = str(seed) - - modal_dialog('Your seed is:', wallet.seed) - modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(wallet.seed)) ) - - msg = "recovering wallet..." if is_recovery else "creating wallet..." - droid.dialogCreateSpinnerProgress("Electrum", msg) - droid.dialogShow() - - wallet.init_mpk( wallet.seed ) - WalletSynchronizer(wallet,True).start() - wallet.update() - - droid.dialogDismiss() - droid.vibrate() - - if is_recovery: - if wallet.is_found(): - wallet.update_tx_history() - wallet.fill_addressbook() - modal_dialog("recovery successful") - else: - if not modal_question("no transactions found for this seed","do you want to keep this wallet?"): - exit(1) - - change_password_dialog() - wallet.save() - - - -def make_new_contact(): - code = droid.scanBarcode() - r = code.result - if r: - data = r['extras']['SCAN_RESULT'] - if data: - if re.match('^bitcoin:', data): - address, _, _, _, _, _, _ = wallet.parse_url(data, None, None) - elif wallet.is_valid(data): - address = data - else: - address = None - if address: - if modal_question('Add to contacts?', address): - wallet.addressbook.append(address) - wallet.save() - else: - modal_dialog('Invalid address', data) - - -do_refresh = False - -def update_callback(): - global do_refresh - print "gui callback", wallet.interface.is_connected, wallet.up_to_date - do_refresh = True - droid.eventPost("refresh",'z') - -def main_loop(): - global do_refresh - - update_layout() - out = None - quitting = False - while out is None: - - event = droid.eventWait(1000).result - if event is None: - if do_refresh: - update_layout() - do_refresh = False - continue - - print "got event in main loop", repr(event) - if event == 'OK': continue - if event is None: continue - #if event["name"]=="refresh": - - - # request 2 taps before we exit - if event["name"]=="key": - if event["data"]["key"] == '4': - if quitting: - out = 'quit' - else: - quitting = True - else: quitting = False - - if event["name"]=="click": - id=event["data"]["id"] - - elif event["name"]=="settings": - out = 'settings' - - elif event["name"] in menu_commands: - out = event["name"] - - if out == 'contacts': - global contact_addr - contact_addr = select_from_contacts() - if contact_addr == 'newcontact': - make_new_contact() - contact_addr = None - if not contact_addr: - out = None - - elif out == "receive": - global receive_addr - receive_addr = select_from_addresses() - if receive_addr: - amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal") - if amount: - receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount) - - if not receive_addr: - out = None - - - return out - - -def payto_loop(): - global recipient - if recipient: - droid.fullSetProperty("recipient","text",recipient) - recipient = None - - out = None - while out is None: - event = droid.eventWait().result - print "got event in payto loop", event - - if event["name"] == "click": - id = event["data"]["id"] - - if id=="buttonPay": - - droid.fullQuery() - recipient = droid.fullQueryDetail("recipient").result.get('text') - label = droid.fullQueryDetail("label").result.get('text') - amount = droid.fullQueryDetail('amount').result.get('text') - - if not wallet.is_valid(recipient): - modal_dialog('Error','Invalid Bitcoin address') - continue - - try: - amount = int( 100000000 * Decimal(amount) ) - except: - modal_dialog('Error','Invalid amount') - continue - - result = pay_to(recipient, amount, wallet.fee, label) - if result: - out = 'main' - - elif id=="buttonContacts": - addr = select_from_contacts() - droid.fullSetProperty("recipient","text",addr) - - elif id=="buttonQR": - code = droid.scanBarcode() - r = code.result - if r: - data = r['extras']['SCAN_RESULT'] - if data: - if re.match('^bitcoin:', data): - payto, amount, label, _, _, _, _ = wallet.parse_url(data, None, None) - droid.fullSetProperty("recipient", "text",payto) - droid.fullSetProperty("amount", "text", amount) - droid.fullSetProperty("label", "text", label) - else: - droid.fullSetProperty("recipient", "text", data) - - - elif event["name"] in menu_commands: - out = event["name"] - - elif event["name"]=="key": - if event["data"]["key"] == '4': - out = 'main' - - #elif event["name"]=="screen": - # if event["data"]=="destroy": - # out = 'main' - - return out - - -receive_addr = '' -contact_addr = '' -recipient = '' - -def receive_loop(): - out = None - while out is None: - event = droid.eventWait().result - print "got event", event - if event["name"]=="key": - if event["data"]["key"] == '4': - out = 'main' - - elif event["name"]=="clipboard": - droid.setClipboard(receive_addr) - modal_dialog('Address copied to clipboard',receive_addr) - - elif event["name"]=="edit": - edit_label(receive_addr) - - return out - -def contacts_loop(): - global recipient - out = None - while out is None: - event = droid.eventWait().result - print "got event", event - if event["name"]=="key": - if event["data"]["key"] == '4': - out = 'main' - - elif event["name"]=="clipboard": - droid.setClipboard(contact_addr) - modal_dialog('Address copied to clipboard',contact_addr) - - elif event["name"]=="edit": - edit_label(contact_addr) - - elif event["name"]=="paytocontact": - recipient = contact_addr - out = 'send' - - elif event["name"]=="deletecontact": - if modal_question('delete contact', contact_addr): - out = 'main' - - return out - - -def server_dialog(plist): - droid.dialogCreateAlert("Public servers") - droid.dialogSetItems( plist.keys() ) - droid.dialogSetPositiveButtonText('Private server') - droid.dialogShow() - response = droid.dialogGetResponse().result - droid.dialogDismiss() - - if response.get('which') == 'positive': - return modal_input('Private server', None) - - i = response.get('item') - if i is not None: - response = plist.keys()[i] - return response - - -def seed_dialog(): - if wallet.use_encryption: - password = droid.dialogGetPassword('Seed').result - if not password: return - else: - password = None - - try: - seed = wallet.pw_decode( wallet.seed, password) - except: - modal_dialog('error','incorrect password') - return - - modal_dialog('Your seed is',seed) - modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(seed)) ) - -def change_password_dialog(): - if wallet.use_encryption: - password = droid.dialogGetPassword('Your wallet is encrypted').result - if password is None: return - else: - password = None - - try: - seed = wallet.pw_decode( wallet.seed, password) - except: - modal_dialog('error','incorrect password') - return - - new_password = droid.dialogGetPassword('Choose a password').result - if new_password == None: - return - - if new_password != '': - password2 = droid.dialogGetPassword('Confirm new password').result - if new_password != password2: - modal_dialog('error','passwords do not match') - return - - wallet.update_password(seed, password, new_password) - if new_password: - modal_dialog('Password updated','your wallet is encrypted') - else: - modal_dialog('No password','your wallet is not encrypted') - return True - - -def settings_loop(): - - - def set_listview(): - server, port, p = wallet.server.split(':') - fee = str( Decimal( wallet.fee)/100000000 ) - is_encrypted = 'yes' if wallet.use_encryption else 'no' - protocol = protocol_name(p) - droid.fullShow(settings_layout) - droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed']) - - set_listview() - - out = None - while out is None: - event = droid.eventWait().result - print "got event", event - if event == 'OK': continue - if not event: continue - - plist = {} - for item in wallet.interface.servers: - host, pp = item - z = {} - for item2 in pp: - protocol, port = item2 - z[protocol] = port - plist[host] = z - - if event["name"] == "itemclick": - pos = event["data"]["position"] - host, port, protocol = wallet.server.split(':') - - if pos == "0": #server - host = server_dialog(plist) - if host: - p = plist[host] - port = p['t'] - srv = host + ':' + port + ':t' - try: - wallet.set_server(srv) - except: - modal_dialog('error','invalid server') - set_listview() - - elif pos == "1": #protocol - if host in plist: - srv = protocol_dialog(host, protocol, plist[host]) - if srv: - try: - wallet.set_server(srv) - except: - modal_dialog('error','invalid server') - set_listview() - - elif pos == "2": #port - a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number") - if a_port: - if a_port != port: - srv = host + ':' + a_port + ':'+ protocol - try: - wallet.set_server(srv) - except: - modal_dialog('error','invalid port number') - set_listview() - - elif pos == "3": #fee - fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ', str( Decimal( wallet.fee)/100000000 ), "numberDecimal") - if fee: - try: - fee = int( 100000000 * Decimal(fee) ) - except: - modal_dialog('error','invalid fee value') - if wallet.fee != fee: - wallet.fee = fee - wallet.save() - set_listview() - - elif pos == "4": - if change_password_dialog(): - set_listview() - - elif pos == "5": - seed_dialog() - - - elif event["name"] in menu_commands: - out = event["name"] - - elif event["name"] == 'cancel': - out = 'main' - - elif event["name"] == "key": - if event["data"]["key"] == '4': - out = 'main' - - return out - - - - -menu_commands = ["send", "receive", "settings", "contacts", "main"] -droid = android.Android() -wallet = Wallet() -wallet.register_callback(update_callback) - -wallet.set_path("/sdcard/electrum.dat") -wallet.read() -if not wallet.file_exists: - recover() -else: - WalletSynchronizer(wallet,True).start() - - -s = 'main' - -def add_menu(s): - droid.clearOptionsMenu() - if s == 'main': - droid.addOptionsMenuItem("Send","send",None,"") - droid.addOptionsMenuItem("Receive","receive",None,"") - droid.addOptionsMenuItem("Contacts","contacts",None,"") - droid.addOptionsMenuItem("Settings","settings",None,"") - elif s == 'receive': - droid.addOptionsMenuItem("Copy","clipboard",None,"") - droid.addOptionsMenuItem("Label","edit",None,"") - elif s == 'contacts': - droid.addOptionsMenuItem("Copy","clipboard",None,"") - droid.addOptionsMenuItem("Label","edit",None,"") - droid.addOptionsMenuItem("Pay to","paytocontact",None,"") - #droid.addOptionsMenuItem("Delete","deletecontact",None,"") - -def make_bitmap(addr): - # fixme: this is highly inefficient - droid.dialogCreateSpinnerProgress("please wait") - droid.dialogShow() - try: - import pyqrnative, bmp - qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L) - qr.addData(addr) - qr.make() - k = qr.getModuleCount() - assert k == 33 - bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp") - finally: - droid.dialogDismiss() - - - -while True: - add_menu(s) - if s == 'main': - droid.fullShow(main_layout()) - s = main_loop() - #droid.fullDismiss() - - elif s == 'send': - droid.fullShow(payto_layout) - s = payto_loop() - #droid.fullDismiss() - - elif s == 'receive': - make_bitmap(receive_addr) - droid.fullShow(qr_layout(receive_addr)) - s = receive_loop() - - elif s == 'contacts': - make_bitmap(contact_addr) - droid.fullShow(qr_layout(contact_addr)) - s = contacts_loop() - - elif s == 'settings': - #droid.fullShow(settings_layout) - s = settings_loop() - #droid.fullDismiss() - else: - break - -droid.makeToast("Bye!") diff --git a/lib/gui.py b/lib/gui.py @@ -331,23 +331,7 @@ def run_network_dialog( wallet, parent ): status = "Please choose a server." server = interface.server - - if not wallet.interface.servers: - servers_list = [] - for x in DEFAULT_SERVERS: - h,port,protocol = x.split(':') - servers_list.append( (h,[(protocol,port)] ) ) - else: - servers_list = wallet.interface.servers - - plist = {} - for item in servers_list: - host, pp = item - z = {} - for item2 in pp: - protocol, port = item2 - z[protocol] = port - plist[host] = z + plist, servers_list = interface.get_servers_list() dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status) diff --git a/lib/gui_android.py b/lib/gui_android.py @@ -0,0 +1,970 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + + +import android + +from electrum import SimpleConfig, Interface, WalletSynchronizer, Wallet, format_satoshis, mnemonic_encode, mnemonic_decode +from decimal import Decimal +import datetime, re + + + +def modal_dialog(title, msg = None): + droid.dialogCreateAlert(title,msg) + droid.dialogSetPositiveButtonText('OK') + droid.dialogShow() + droid.dialogGetResponse() + droid.dialogDismiss() + +def modal_input(title, msg, value = None, etype=None): + droid.dialogCreateInput(title, msg, value, etype) + droid.dialogSetPositiveButtonText('OK') + droid.dialogSetNegativeButtonText('Cancel') + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + if response.get('which') == 'positive': + return response.get('value') + +def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'): + droid.dialogCreateAlert(q, msg) + droid.dialogSetPositiveButtonText(pos_text) + droid.dialogSetNegativeButtonText(neg_text) + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + return response.get('which') == 'positive' + +def edit_label(addr): + v = modal_input('Edit label',None,wallet.labels.get(addr)) + if v is not None: + if v: + wallet.labels[addr] = v + else: + if addr in wallet.labels.keys(): + wallet.labels.pop(addr) + wallet.update_tx_history() + wallet.save() + droid.fullSetProperty("labelTextView", "text", v) + +def select_from_contacts(): + title = 'Contacts:' + droid.dialogCreateAlert(title) + l = [] + for i in range(len(wallet.addressbook)): + addr = wallet.addressbook[i] + label = wallet.labels.get(addr,addr) + l.append( label ) + droid.dialogSetItems(l) + droid.dialogSetPositiveButtonText('New contact') + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + + if response.get('which') == 'positive': + return 'newcontact' + + result = response.get('item') + print result + if result is not None: + addr = wallet.addressbook[result] + return addr + + +def select_from_addresses(): + droid.dialogCreateAlert("Addresses:") + l = [] + for i in range(len(wallet.addresses)): + addr = wallet.addresses[i] + label = wallet.labels.get(addr,addr) + l.append( label ) + droid.dialogSetItems(l) + droid.dialogShow() + response = droid.dialogGetResponse() + result = response.result.get('item') + droid.dialogDismiss() + if result is not None: + addr = wallet.addresses[result] + return addr + + +def protocol_name(p): + if p == 't': return 'TCP/stratum' + if p == 'h': return 'HTTP/Stratum' + if p == 'n': return 'TCP/native' + +def protocol_dialog(host, protocol, z): + droid.dialogCreateAlert('Protocol',host) + if z: + protocols = z.keys() + else: + protocols = ['t','h','n'] + l = [] + current = protocols.index(protocol) + for p in protocols: + l.append(protocol_name(p)) + droid.dialogSetSingleChoiceItems(l, current) + droid.dialogSetPositiveButtonText('OK') + droid.dialogSetNegativeButtonText('Cancel') + droid.dialogShow() + response = droid.dialogGetResponse().result + if not response: return + if response.get('which') == 'positive': + response = droid.dialogGetSelectedItems().result[0] + droid.dialogDismiss() + p = protocols[response] + port = z[p] + return host + ':' + port + ':' + p + + + + +def make_layout(s, scrollable = False): + content = """ + + <LinearLayout + android:id="@+id/zz" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#ff222222"> + + <TextView + android:id="@+id/textElectrum" + android:text="Electrum" + android:textSize="7pt" + android:textColor="#ff4444ff" + android:gravity="left" + android:layout_height="wrap_content" + android:layout_width="match_parent" + /> + </LinearLayout> + + %s """%s + + if scrollable: + content = """ + <ScrollView + android:id="@+id/scrollview" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + %s + + </LinearLayout> + </ScrollView> + """%content + + + return """<?xml version="1.0" encoding="utf-8"?> + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/background" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#ff000022"> + + %s + </LinearLayout>"""%content + + + + +def main_layout(): + return make_layout(""" + <TextView android:id="@+id/balanceTextView" + android:layout_width="match_parent" + android:text="" + android:textColor="#ffffffff" + android:textAppearance="?android:attr/textAppearanceLarge" + android:padding="7dip" + android:textSize="8pt" + android:gravity="center_vertical|center_horizontal|left"> + </TextView> + + <TextView android:id="@+id/historyTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Recent transactions" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="center_vertical|center_horizontal|center"> + </TextView> + + %s """%get_history_layout(15),True) + + + +def qr_layout(addr): + return make_layout(""" + + <TextView android:id="@+id/addrTextView" + android:layout_width="match_parent" + android:layout_height="50" + android:text="%s" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="center_vertical|center_horizontal|center"> + </TextView> + + <ImageView + android:id="@+id/qrView" + android:gravity="center" + android:layout_width="match_parent" + android:layout_height="350" + android:antialias="false" + android:src="file:///sdcard/sl4a/qrcode.bmp" /> + + <TextView android:id="@+id/labelTextView" + android:layout_width="match_parent" + android:layout_height="50" + android:text="%s" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="center_vertical|center_horizontal|center"> + </TextView> + + """%(addr,wallet.labels.get(addr,'')), True) + +payto_layout = make_layout(""" + + <TextView android:id="@+id/recipientTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Pay to:" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="left"> + </TextView> + + + <EditText android:id="@+id/recipient" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="Tag Me" android:inputType="text"> + </EditText> + + <LinearLayout android:id="@+id/linearLayout1" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <Button android:id="@+id/buttonQR" android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="From QR code"></Button> + <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="From Contacts"></Button> + </LinearLayout> + + + <TextView android:id="@+id/labelTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Description:" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="left"> + </TextView> + + <EditText android:id="@+id/label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="Tag Me" android:inputType="text"> + </EditText> + + <TextView android:id="@+id/amountLabelTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Amount:" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="left"> + </TextView> + + <EditText android:id="@+id/amount" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="Tag Me" android:inputType="numberDecimal"> + </EditText> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" android:id="@+id/linearLayout1"> + <Button android:id="@+id/buttonPay" android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="Send"></Button> + </LinearLayout>""",False) + + + +settings_layout = make_layout(""" <ListView + android:id="@+id/myListView" + android:layout_width="match_parent" + android:layout_height="wrap_content" />""") + + + +def get_history_values(n): + values = [] + h = wallet.get_tx_history() + length = min(n, len(h)) + for i in range(length): + tx_hash, conf, is_mine, value, fee, balance, timestamp = h[-i-1] + try: + dt = datetime.datetime.fromtimestamp( timestamp ) + if dt.date() == dt.today().date(): + time_str = str( dt.time() ) + else: + time_str = str( dt.date() ) + except: + time_str = 'pending' + + conf_str = 'v' if conf else 'o' + label, is_default_label = wallet.get_label(tx_hash) + values.append((conf_str, ' ' + time_str, ' ' + format_satoshis(value,True), ' ' + label )) + + return values + + +def get_history_layout(n): + rows = "" + i = 0 + values = get_history_values(n) + for v in values: + a,b,c,d = v + color = "#ff00ff00" if a == 'v' else "#ffff0000" + rows += """ + <TableRow> + <TextView + android:id="@+id/hl_%d_col1" + android:layout_column="0" + android:text="%s" + android:textColor="%s" + android:padding="3" /> + <TextView + android:id="@+id/hl_%d_col2" + android:layout_column="1" + android:text="%s" + android:padding="3" /> + <TextView + android:id="@+id/hl_%d_col3" + android:layout_column="2" + android:text="%s" + android:padding="3" /> + <TextView + android:id="@+id/hl_%d_col4" + android:layout_column="3" + android:text="%s" + android:padding="4" /> + </TableRow>"""%(i,a,color,i,b,i,c,i,d) + i += 1 + + output = """ +<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:stretchColumns="0,1,2,3"> + %s +</TableLayout>"""% rows + return output + + +def set_history_layout(n): + values = get_history_values(n) + i = 0 + for v in values: + a,b,c,d = v + droid.fullSetProperty("hl_%d_col1"%i,"text", a) + + if a == 'v': + droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00") + else: + droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000") + + droid.fullSetProperty("hl_%d_col2"%i,"text", b) + droid.fullSetProperty("hl_%d_col3"%i,"text", c) + droid.fullSetProperty("hl_%d_col4"%i,"text", d) + i += 1 + + + + +status_text = '' +def update_layout(): + global status_text + if not wallet.interface.is_connected: + text = "Not connected..." + elif not wallet.up_to_date: + text = "Synchronizing..." + else: + c, u = wallet.get_balance() + text = "Balance:"+format_satoshis(c) + if u : text += ' [' + format_satoshis(u,True).strip() + ']' + + + # vibrate if status changed + if text != status_text: + if status_text and wallet.interface.is_connected and wallet.up_to_date: + droid.vibrate() + status_text = text + + droid.fullSetProperty("balanceTextView", "text", status_text) + + if wallet.up_to_date: + set_history_layout(15) + + + + +def pay_to(recipient, amount, fee, label): + + if wallet.use_encryption: + password = droid.dialogGetPassword('Password').result + if not password: return + else: + password = None + + droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...") + droid.dialogShow() + + try: + tx = wallet.mktx( recipient, amount, label, password, fee) + except BaseException, e: + modal_dialog('error', e.message) + droid.dialogDismiss() + return + + droid.dialogDismiss() + + r, h = wallet.sendtx( tx ) + if r: + modal_dialog('Payment sent', h) + return True + else: + modal_dialog('Error', h) + + + + + +def recover(): + + droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?") + droid.dialogSetPositiveButtonText('Create') + droid.dialogSetNeutralButtonText('Restore') + droid.dialogSetNegativeButtonText('Cancel') + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + if response.get('which') == 'negative': + exit(1) + + is_recovery = response.get('which') == 'neutral' + + if not is_recovery: + wallet.new_seed(None) + else: + if modal_question("Input method",None,'QR Code', 'mnemonic'): + code = droid.scanBarcode() + r = code.result + if r: + seed = r['extras']['SCAN_RESULT'] + else: + exit(1) + else: + m = modal_input('Mnemonic','please enter your code') + try: + seed = mnemonic_decode(m.split(' ')) + except: + modal_dialog('error: could not decode this seed') + exit(1) + + wallet.seed = str(seed) + + modal_dialog('Your seed is:', wallet.seed) + modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(wallet.seed)) ) + + msg = "recovering wallet..." if is_recovery else "creating wallet..." + droid.dialogCreateSpinnerProgress("Electrum", msg) + droid.dialogShow() + + wallet.init_mpk( wallet.seed ) + WalletSynchronizer(wallet,True).start() + wallet.update() + + droid.dialogDismiss() + droid.vibrate() + + if is_recovery: + if wallet.is_found(): + wallet.update_tx_history() + wallet.fill_addressbook() + modal_dialog("recovery successful") + else: + if not modal_question("no transactions found for this seed","do you want to keep this wallet?"): + exit(1) + + change_password_dialog() + wallet.save() + + + +def make_new_contact(): + code = droid.scanBarcode() + r = code.result + if r: + data = r['extras']['SCAN_RESULT'] + if data: + if re.match('^bitcoin:', data): + address, _, _, _, _, _, _ = wallet.parse_url(data, None, None) + elif wallet.is_valid(data): + address = data + else: + address = None + if address: + if modal_question('Add to contacts?', address): + wallet.addressbook.append(address) + wallet.save() + else: + modal_dialog('Invalid address', data) + + +do_refresh = False + +def update_callback(): + global do_refresh + print "gui callback", wallet.interface.is_connected, wallet.up_to_date + do_refresh = True + droid.eventPost("refresh",'z') + +def main_loop(): + global do_refresh + + update_layout() + out = None + quitting = False + while out is None: + + event = droid.eventWait(1000).result + if event is None: + if do_refresh: + update_layout() + do_refresh = False + continue + + print "got event in main loop", repr(event) + if event == 'OK': continue + if event is None: continue + #if event["name"]=="refresh": + + + # request 2 taps before we exit + if event["name"]=="key": + if event["data"]["key"] == '4': + if quitting: + out = 'quit' + else: + quitting = True + else: quitting = False + + if event["name"]=="click": + id=event["data"]["id"] + + elif event["name"]=="settings": + out = 'settings' + + elif event["name"] in menu_commands: + out = event["name"] + + if out == 'contacts': + global contact_addr + contact_addr = select_from_contacts() + if contact_addr == 'newcontact': + make_new_contact() + contact_addr = None + if not contact_addr: + out = None + + elif out == "receive": + global receive_addr + receive_addr = select_from_addresses() + if receive_addr: + amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal") + if amount: + receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount) + + if not receive_addr: + out = None + + + return out + + +def payto_loop(): + global recipient + if recipient: + droid.fullSetProperty("recipient","text",recipient) + recipient = None + + out = None + while out is None: + event = droid.eventWait().result + print "got event in payto loop", event + + if event["name"] == "click": + id = event["data"]["id"] + + if id=="buttonPay": + + droid.fullQuery() + recipient = droid.fullQueryDetail("recipient").result.get('text') + label = droid.fullQueryDetail("label").result.get('text') + amount = droid.fullQueryDetail('amount').result.get('text') + + if not wallet.is_valid(recipient): + modal_dialog('Error','Invalid Bitcoin address') + continue + + try: + amount = int( 100000000 * Decimal(amount) ) + except: + modal_dialog('Error','Invalid amount') + continue + + result = pay_to(recipient, amount, wallet.fee, label) + if result: + out = 'main' + + elif id=="buttonContacts": + addr = select_from_contacts() + droid.fullSetProperty("recipient","text",addr) + + elif id=="buttonQR": + code = droid.scanBarcode() + r = code.result + if r: + data = r['extras']['SCAN_RESULT'] + if data: + if re.match('^bitcoin:', data): + payto, amount, label, _, _, _, _ = wallet.parse_url(data, None, None) + droid.fullSetProperty("recipient", "text",payto) + droid.fullSetProperty("amount", "text", amount) + droid.fullSetProperty("label", "text", label) + else: + droid.fullSetProperty("recipient", "text", data) + + + elif event["name"] in menu_commands: + out = event["name"] + + elif event["name"]=="key": + if event["data"]["key"] == '4': + out = 'main' + + #elif event["name"]=="screen": + # if event["data"]=="destroy": + # out = 'main' + + return out + + +receive_addr = '' +contact_addr = '' +recipient = '' + +def receive_loop(): + out = None + while out is None: + event = droid.eventWait().result + print "got event", event + if event["name"]=="key": + if event["data"]["key"] == '4': + out = 'main' + + elif event["name"]=="clipboard": + droid.setClipboard(receive_addr) + modal_dialog('Address copied to clipboard',receive_addr) + + elif event["name"]=="edit": + edit_label(receive_addr) + + return out + +def contacts_loop(): + global recipient + out = None + while out is None: + event = droid.eventWait().result + print "got event", event + if event["name"]=="key": + if event["data"]["key"] == '4': + out = 'main' + + elif event["name"]=="clipboard": + droid.setClipboard(contact_addr) + modal_dialog('Address copied to clipboard',contact_addr) + + elif event["name"]=="edit": + edit_label(contact_addr) + + elif event["name"]=="paytocontact": + recipient = contact_addr + out = 'send' + + elif event["name"]=="deletecontact": + if modal_question('delete contact', contact_addr): + out = 'main' + + return out + + +def server_dialog(plist): + droid.dialogCreateAlert("Public servers") + droid.dialogSetItems( plist.keys() ) + droid.dialogSetPositiveButtonText('Private server') + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + if not response: return + + if response.get('which') == 'positive': + return modal_input('Private server', None) + + i = response.get('item') + if i is not None: + response = plist.keys()[i] + return response + + +def seed_dialog(): + if wallet.use_encryption: + password = droid.dialogGetPassword('Seed').result + if not password: return + else: + password = None + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + modal_dialog('error','incorrect password') + return + + modal_dialog('Your seed is',seed) + modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(seed)) ) + +def change_password_dialog(): + if wallet.use_encryption: + password = droid.dialogGetPassword('Your wallet is encrypted').result + if password is None: return + else: + password = None + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + modal_dialog('error','incorrect password') + return + + new_password = droid.dialogGetPassword('Choose a password').result + if new_password == None: + return + + if new_password != '': + password2 = droid.dialogGetPassword('Confirm new password').result + if new_password != password2: + modal_dialog('error','passwords do not match') + return + + wallet.update_password(seed, password, new_password) + if new_password: + modal_dialog('Password updated','your wallet is encrypted') + else: + modal_dialog('No password','your wallet is not encrypted') + return True + + +def settings_loop(): + + + def set_listview(): + server, port, p = interface.server.split(':') + fee = str( Decimal( wallet.fee)/100000000 ) + is_encrypted = 'yes' if wallet.use_encryption else 'no' + protocol = protocol_name(p) + droid.fullShow(settings_layout) + droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed']) + + set_listview() + + out = None + while out is None: + event = droid.eventWait().result + print "got event", event + if event == 'OK': continue + if not event: continue + + plist, servers_list = interface.get_servers_list() + + if event["name"] == "itemclick": + pos = event["data"]["position"] + host, port, protocol = interface.server.split(':') + + if pos == "0": #server + host = server_dialog(plist) + if host: + p = plist[host] + port = p['t'] + srv = host + ':' + port + ':t' + wallet.config.set_key("server", srv, True) + try: + wallet.interface.set_server(srv) + except: + modal_dialog('error','invalid server') + set_listview() + + elif pos == "1": #protocol + if host in plist: + srv = protocol_dialog(host, protocol, plist[host]) + if srv: + try: + wallet.interface.set_server(srv) + except: + modal_dialog('error','invalid server') + set_listview() + + elif pos == "2": #port + a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number") + if a_port: + if a_port != port: + srv = host + ':' + a_port + ':'+ protocol + try: + wallet.interface.set_server(srv) + except: + modal_dialog('error','invalid port number') + set_listview() + + elif pos == "3": #fee + fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ', str( Decimal( wallet.fee)/100000000 ), "numberDecimal") + if fee: + try: + fee = int( 100000000 * Decimal(fee) ) + except: + modal_dialog('error','invalid fee value') + if wallet.fee != fee: + wallet.fee = fee + wallet.save() + set_listview() + + elif pos == "4": + if change_password_dialog(): + set_listview() + + elif pos == "5": + seed_dialog() + + + elif event["name"] in menu_commands: + out = event["name"] + + elif event["name"] == 'cancel': + out = 'main' + + elif event["name"] == "key": + if event["data"]["key"] == '4': + out = 'main' + + return out + +def add_menu(s): + droid.clearOptionsMenu() + if s == 'main': + droid.addOptionsMenuItem("Send","send",None,"") + droid.addOptionsMenuItem("Receive","receive",None,"") + droid.addOptionsMenuItem("Contacts","contacts",None,"") + droid.addOptionsMenuItem("Settings","settings",None,"") + elif s == 'receive': + droid.addOptionsMenuItem("Copy","clipboard",None,"") + droid.addOptionsMenuItem("Label","edit",None,"") + elif s == 'contacts': + droid.addOptionsMenuItem("Copy","clipboard",None,"") + droid.addOptionsMenuItem("Label","edit",None,"") + droid.addOptionsMenuItem("Pay to","paytocontact",None,"") + #droid.addOptionsMenuItem("Delete","deletecontact",None,"") + + +def make_bitmap(addr): + # fixme: this is highly inefficient + droid.dialogCreateSpinnerProgress("please wait") + droid.dialogShow() + try: + import pyqrnative, bmp + qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L) + qr.addData(addr) + qr.make() + k = qr.getModuleCount() + assert k == 33 + bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp") + finally: + droid.dialogDismiss() + + + + +droid = android.Android() +menu_commands = ["send", "receive", "settings", "contacts", "main"] +wallet = None +interface = None + +class ElectrumGui: + + def __init__(self, w, config, app=None): + global wallet, interface + wallet = w + interface = wallet.interface + interface.register_callback('updated',update_callback) + interface.register_callback('connected', update_callback) + interface.register_callback('disconnected', update_callback) + interface.register_callback('disconnecting', update_callback) + + def server_list_changed(self): pass + + def main(self, url): + s = 'main' + while True: + add_menu(s) + if s == 'main': + droid.fullShow(main_layout()) + s = main_loop() + + elif s == 'send': + droid.fullShow(payto_layout) + s = payto_loop() + + elif s == 'receive': + make_bitmap(receive_addr) + droid.fullShow(qr_layout(receive_addr)) + s = receive_loop() + + elif s == 'contacts': + make_bitmap(contact_addr) + droid.fullShow(qr_layout(contact_addr)) + s = contacts_loop() + + elif s == 'settings': + s = settings_loop() + + else: + break + + droid.makeToast("Bye!") diff --git a/lib/gui_qt.py b/lib/gui_qt.py @@ -1372,22 +1372,7 @@ class ElectrumWindow(QMainWindow): status = _("Please choose a server.") server = interface.server - - plist = {} - if not wallet.interface.servers: - servers_list = [] - for x in DEFAULT_SERVERS: - h,port,protocol = x.split(':') - servers_list.append( (h,[(protocol,port)] ) ) - else: - servers_list = wallet.interface.servers - for item in servers_list: - _host, pp = item - z = {} - for item2 in pp: - _protocol, _port = item2 - z[_protocol] = _port - plist[_host] = z + plist, servers_list = interface.get_servers_list() d = QDialog(parent) d.setModal(1) diff --git a/lib/interface.py b/lib/interface.py @@ -490,6 +490,28 @@ class Interface(threading.Thread): self.is_connected = False # this exits the polling loop self.trigger_callback('disconnecting') # for actively disconnecting + + def get_servers_list(self): + plist = {} + if not self.servers: + servers_list = [] + for x in DEFAULT_SERVERS: + h,port,protocol = x.split(':') + servers_list.append( (h,[(protocol,port)] ) ) + else: + servers_list = self.servers + + for item in servers_list: + _host, pp = item + z = {} + for item2 in pp: + _protocol, _port = item2 + z[_protocol] = _port + plist[_host] = z + + return plist, servers_list + + def is_empty(self, channel): q = self.responses.get(channel) if q: diff --git a/lib/simple_config.py b/lib/simple_config.py @@ -8,32 +8,29 @@ from version import ELECTRUM_VERSION, SEED_VERSION class SimpleConfig: - def __init__(self, options=None): + def __init__(self, options={}): # system conf, readonly self.system_config = {} self.read_system_config() # user conf, writeable + self.user_dir = user_dir() self.user_config = {} self.read_user_config() # command-line options - self.options_config = {} - if options: - if options.server: self.options_config['server'] = options.server - if options.proxy: self.options_config['proxy'] = options.proxy - if options.gui: self.options_config['gui'] = options.gui - + self.options_config = options self.wallet_config = {} self.wallet_file_exists = False - self.init_path(options) + self.init_path(self.options_config.get('wallet_path')) print_error( "path", self.path ) if self.path: self.read_wallet_config(self.path) + def set_key(self, key, value, save = False): @@ -62,8 +59,7 @@ class SimpleConfig: def get(self, key, default=None): # 1. command-line options always override everything - if self.options_config.has_key(key): - # print "found", key, "in options" + if self.options_config.has_key(key) and self.options_config.get(key) is not None: out = self.options_config.get(key) # 2. user configuration @@ -123,7 +119,9 @@ class SimpleConfig: def read_user_config(self): - name = os.path.join( user_dir(), 'electrum.conf') + if not self.user_dir: return + + name = os.path.join( self.user_dir, 'electrum.conf') if os.path.exists(name): try: import ConfigParser @@ -140,17 +138,9 @@ class SimpleConfig: pass - def init_path(self, options): + def init_path(self, path): """Set the path of the wallet.""" - path = None - if options: - # this will call read_wallet_config only if there is a wallet_path value in options - try: - path = options.wallet_path - except: - pass - if not path: path = self.get('default_wallet_path') @@ -159,23 +149,22 @@ class SimpleConfig: return # Look for wallet file in the default data directory. - # Keeps backwards compatibility. - wallet_dir = user_dir() - # Make wallet directory if it does not yet exist. - if not os.path.exists(wallet_dir): - os.mkdir(wallet_dir) - self.path = os.path.join(wallet_dir, "electrum.dat") + if not os.path.exists(self.user_dir): + os.mkdir(self.user_dir) + self.path = os.path.join(self.user_dir, "electrum.dat") def save_user_config(self): + if not self.user_dir: return + import ConfigParser config = ConfigParser.RawConfigParser() config.add_section('client') for k,v in self.user_config.items(): config.set('client', k, v) - with open( os.path.join( user_dir(), 'electrum.conf'), 'wb') as configfile: + with open( os.path.join( self.user_dir, 'electrum.conf'), 'wb') as configfile: config.write(configfile) @@ -207,6 +196,7 @@ class SimpleConfig: f = open(self.path,"w") f.write( s ) f.close() - import stat - os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE) + if self.get('gui') != 'android': + import stat + os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE) diff --git a/lib/util.py b/lib/util.py @@ -17,14 +17,14 @@ def print_error(*args): def user_dir(): if "HOME" in os.environ: - return os.path.join(os.environ["HOME"], ".electrum") + return os.path.join(os.environ["HOME"], ".electrum") elif "LOCALAPPDATA" in os.environ: - return os.path.join(os.environ["LOCALAPPDATA"], "Electrum") + return os.path.join(os.environ["LOCALAPPDATA"], "Electrum") elif "APPDATA" in os.environ: - return os.path.join(os.environ["APPDATA"], "Electrum") + return os.path.join(os.environ["APPDATA"], "Electrum") else: - raise BaseException("No home directory found in environment variables.") - + #raise BaseException("No home directory found in environment variables.") + return def appdata_dir(): """Find the path to the application data directory; add an electrum folder and return path.""" diff --git a/lib/wallet.py b/lib/wallet.py @@ -379,7 +379,9 @@ class Wallet: is_pruned = False v_in = v_out = v = 0 d = self.transactions.get(tx_hash) - if not d: return 0 + if not d: + return 0, 0, 0 + for item in d.get('inputs'): addr = item.get('address') if addr in addresses: @@ -611,7 +613,8 @@ class Wallet: self.transactions[tx_hash] = tx tx_height = tx.get('height') - if tx_height>0: self.verifier.add(tx_hash, tx_height) + if self.verifier and tx_height>0: + self.verifier.add(tx_hash, tx_height) self.update_tx_outputs(tx_hash) @@ -632,7 +635,7 @@ class Wallet: for tx_hash, tx_height in hist: if tx_height>0: # add it in case it was previously unconfirmed - self.verifier.add(tx_hash, tx_height) + if self.verifier: self.verifier.add(tx_hash, tx_height) # set the height in case it changed tx = self.transactions.get(tx_hash) if tx: @@ -692,8 +695,8 @@ class Wallet: def get_default_label(self, tx_hash): tx = self.transactions.get(tx_hash) + default_label = '' if tx: - default_label = '' is_mine, _, _ = self.get_tx_value(tx_hash) if is_mine: for o in tx['outputs']: @@ -1202,8 +1205,9 @@ class WalletSynchronizer(threading.Thread): if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx: missing_tx.append( (tx_hash, tx_height) ) else: - timestamp = self.wallet.verifier.get_timestamp(tx_height) - self.wallet.set_tx_timestamp(tx_hash, timestamp) + if self.wallet.verifier: + timestamp = self.wallet.verifier.get_timestamp(tx_height) + self.wallet.set_tx_timestamp(tx_hash, timestamp) elif method == 'blockchain.transaction.get': tx_hash = params[0] @@ -1239,6 +1243,6 @@ class WalletSynchronizer(threading.Thread): d = deserialize.parse_Transaction(vds) d['height'] = tx_height d['tx_hash'] = tx_hash - d['timestamp'] = self.wallet.verifier.get_timestamp(tx_height) + if self.wallet.verifier: d['timestamp'] = self.wallet.verifier.get_timestamp(tx_height) return d