diff --git a/README.md b/README.md index 5ac5e27..da16b44 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,21 @@ This is a GUI frontend that runs on top of bitmonerod and simplewallet (in RPC m ### Windows, Linux, and Mac The requirements for running the program from the source files are: Python 2.7, Kivy 1.8, and pygame 1.9.1. The program is also only intended for windows at the moment; it may be semi-functional on linux or mac, but I don't think it will work properly. If you have these requirements, then you just need to add your binary, simplewallet.exe(currently designed to work only with the latest tagged release), and optionally wallet files to your cryptonoteRPCwalletGUI folder, or unzip the cryptonoteRPCwalletGUI contents into the folder where you currently have your binaries and wallet files. For the time being they have to be in the same local directory. -If you're using the binary (v0.0.1), there are no requirements other than an x64 Windows computer. +If you're using the binary (v0.0.2), there are no requirements other than an x64 Windows computer. ## Running It should be OK to launch the program with an instance of bitmonerod and/or simplewallet running. -On the initial run, it will prompt you to create a new wallet or to import one by typing/copying in the path of the keys file. This will create a folder in the local directory where the new/imported wallet data is stored, and a file called CONFIG.file that let's lightWallet know which account is currently the default one being used. If you would like to create/import a new wallet, you can simply delete CONFIG.file and it will run the initial config prompt again (this won't affect any wallet folders already in the lightWallet directory). +On the initial run, it will prompt you to create a new wallet or to import one by typing/copying in the path of the keys file. This will create a folder in the your user's My Documents directory in a folder called lightWallet (typically C:\Users\yourUser\Documents\lightWallet) where the new/imported wallet data is stored, and a file called CONFIG.file that let's lightWallet know which account is currently the default one being used. If you would like to create/import a new wallet, you can simply delete CONFIG.file and it will run the initial config prompt again (this won't affect any wallet folders already in the lightWallet directory). ## Known issues -The transaction history and calculated balance features are not fully working yet. This is something I plan to address, but since the wallet itself works (and simplewallet itself doesn't really implement a transaction history), and you can always see your balances with the total and unlocked balance readings, I decided to go ahead and release this alpha version, and will work on this in the future. - If you manage to crash the program, you will probably get an instance of simplewallet.exe hanging around, so to get rid of it you will need to go into the task manager processes tab and end the simplewallet process, or restart your computer. -The import keys does not work with deprecated wallets (wallets with no electrum seed) at the moment. This is an issue with simplewallet that will be addressed in the next release. +The import keys does not work with deprecated wallets (wallets with no electrum seed) at the moment. This is an issue with simplewallet that will be addressed in the next release of simplewallet. ## Future Work -There are several features I'd like to implement, the most important of which is probably tab for an address book, where you can store addresses (and payment ids) that are each a button, and when you click the button it autofills everything on the transfer tab. Of course I'm working on the record keeping of all incoming and outgoing transactions and the calculated balance which is derived from them. This is a really key feature that should probably be a part of any wallet, but the wallet is still functional at the moment, and the transaction history is quasi-functional. +There are several features I'd like to implement, the most important of which is probably a tab for an address book, where you can store addresses (and payment ids) that are each a button, and when you click the button it autofills everything on the transfer tab. -I'd like to figure out why the stdout output of simplewallet.exe lags sometimes, and it doesn't look like the wallet is quite synced up; this doesn't affect anything except making the wallet appear like it is a few blocks behind the daemon. Another thing I want to change is using argparse rather than argv to parse command line arguments. +I'd like to figure out why the stdout output of simplewallet.exe lags sometimes, and it doesn't look like the wallet is quite synced up; this doesn't affect anything except making the wallet appear like it is a few blocks behind the daemon or vice-versa. Another thing I want to change is using argparse rather than argv to parse command line arguments. I want to clean up the interface and prettify it a bit more too. I'm open to suggestions, so please open an issue or email me if you have any ideas, complaints, etc. \ No newline at end of file diff --git a/lib/checkbalance.py b/lib/checkbalance.py index cd8cc76..4a4c225 100644 --- a/lib/checkbalance.py +++ b/lib/checkbalance.py @@ -10,7 +10,7 @@ payload = json.dumps({ }) -def CheckBalanceSimplewallet(): +def checkBalanceSimplewallet(): """function to make rpc call to simplewallet to get current balance""" # print 'Attempting {0} RPC call'.format(CheckBalanceSimplewallet.__name__) try: @@ -33,4 +33,4 @@ def CheckBalanceSimplewallet(): if __name__ == "__main__": - print CheckBalanceSimplewallet() \ No newline at end of file + print checkBalanceSimplewallet() \ No newline at end of file diff --git a/lib/checklastblock.py b/lib/checklastblock.py index 4fd7ead..2fadbd4 100644 --- a/lib/checklastblock.py +++ b/lib/checklastblock.py @@ -16,7 +16,7 @@ payload = json.dumps({ }) -def CheckLastBlock(serverURL=default_URL): +def checkLastBlock(serverURL=default_URL): """Try and get last block info and return formatted text""" # print('Attempting {0} RPC call'.format(CheckLastBlock.__name__)) try: @@ -40,4 +40,4 @@ def CheckLastBlock(serverURL=default_URL): if __name__ == "__main__": - CheckLastBlock() \ No newline at end of file + checkLastBlock() \ No newline at end of file diff --git a/lib/transferfunds.py b/lib/transferfunds.py index bcce2e8..90201fa 100644 --- a/lib/transferfunds.py +++ b/lib/transferfunds.py @@ -5,7 +5,7 @@ from ctypes import c_uint64 serverURL = 'http://localhost:19091/json_rpc' -def transferfundsrpccall(amount, address, mixin, paymentid): +def transferFundsRPCCall(amount, address, mixin, paymentid): """function to transfer funds to a single address""" atomicamount = c_uint64(int((float(amount)*1e12))) address = str(address) @@ -61,4 +61,4 @@ def transferfundsrpccall(amount, address, mixin, paymentid): if __name__ == "__main__": - transferfundsrpccall() \ No newline at end of file + transferFundsRPCCall() \ No newline at end of file diff --git a/lightWallet.kv b/lightWallet.kv index a9f2dd4..edf5056 100644 --- a/lightWallet.kv +++ b/lightWallet.kv @@ -1,11 +1,11 @@ #:kivy 1.8.0 : - id: rootwidget + id: root_widget + orientation: "vertical" + root_widget: root_widget # Tx history tab id items -# tx_list: tx_list - # tx_scroller: tx_scroller - # scroll_box: scroll_box + # empty # Transfer tab id items address_input_textinput: address_input_textinput amount_input_textinput: amount_input_textinput @@ -33,17 +33,31 @@ padding: 30, 30, 30, 30 orientation: "vertical" Label: - text: "Transaction history list (scrollable)" + text: "Transaction outputs history list (scrollable).\nClick button to copy tx_id." + size_hint_y: 0.1 + halign: "center" + MarkupLabel: + text: "[color=00ffff][b]Unspent tx outputs[/color][/b]" size_hint_y: 0.1 ScrollView: - id: tx_scroller - size_hint: 1, 1 + id: unspent_tx_scroller + size_hint: 1, 0.35 GridLayout: cols: 1 spacing: 7 size_hint_y: None - id: tx_list - + id: unspent_tx_list + MarkupLabel: + text: "[color=00ffff][b]Spent tx outputs[/color][/b]" + size_hint_y: 0.1 + ScrollView: + id: spent_tx_scroller + size_hint: 1, 0.35 + GridLayout: + cols: 1 + spacing: 7 + size_hint_y: None + id: spent_tx_list AccordionItem: title: "Send XMR" BoxLayout: @@ -121,11 +135,11 @@ MarkupLabel: size_hint_x: 0.7 halign: "center" - text: "Please donate XMR to Cool Mining Club\nfor generously running a bitmonerod open node." + text: "Please donate to Cool Mining for generously\nrunning an open node and supporting lightWallet." Button: size_hint_x: 0.3 halign: "center" - text: "Click to\ndonate XMR to\nCool Mining Club" + text: "Click to donate XMR\nto Cool Mining Club" on_press: root.donate_CMC() BoxLayout: orientation: 'horizontal' @@ -133,11 +147,11 @@ MarkupLabel: size_hint_x: 0.7 halign: "center" - text: "Please donate XMR to Monero core developers\nfor generously donating their time and effort." + text: "Please donate to Monero core developers\nfor generously donating their time and effort." Button: size_hint_x: 0.3 halign: "center" - text: "Click to\ndonate XMR to\ncore Monero devs" + text: "Click to donate XMR\nto the core Monero devs" on_press: root.donate_core() AccordionItem: @@ -202,7 +216,7 @@ orientation: 'horizontal' size_hint_y: 0.25 Button: - text: "Save wallet\n(Attempts to autosave\nafter every minute)" + text: "Save wallet\n(Attempts to autosave every minute)" halign: "center" on_press: root.saveWallet(0.1) MarkupLabel: @@ -236,10 +250,10 @@ # text: "http://xmr1.coolmining.club:5012" # text: "http://localhost:18081" Button: - text: "Change daemon\naddress in\nCONFIG.file" + text: "Click to change tab\norientation." halign: "center" size_hint_x: 0.4 - # on_press: root.resetDaemon() + on_press: root.changeTabs() Label: size_hint_y: 0.1 BoxLayout: @@ -302,7 +316,7 @@ : id: create_wallet_popup title: "Create Wallet Window" - auto_dismiss: "False" + auto_dismiss: False size_hint: 0.75, 0.75 wallet_name: wallet_name wallet_pw: wallet_pw @@ -310,26 +324,31 @@ BoxLayout: padding: 60, 60, 60, 60 orientation: 'vertical' + MarkupLabel: + text: "[color=#00ffff][b]Wallet name must be alphanumeric (0-9, a-z, A-Z, -, ., (), [], {}, +, _, etc.).\nPassword may also contain special characters such as !, @, #, etc.[/color][/b]" Label: text: "Enter wallet name:" - TabTextInput: + AsciiInput: id: wallet_name next: wallet_pw + on_text_validate: root.submitForm() Label: text: "Enter wallet password:" TabTextInput: id: wallet_pw password: True next: repeat_wallet_pw + on_text_validate: root.submitForm() Label: text: "Repeat wallet password:" TabTextInput: id: repeat_wallet_pw password: True - next: wallet_pw + next: wallet_name + on_text_validate: root.submitForm() Button: text: "Click to create wallet" - on_press: root.createWallet() + on_press: root.submitForm() : @@ -347,10 +366,11 @@ size_hint_y: 0.3 halign: "center" text: "Enter wallet keys path (make sure you include file extension)\nNote: this will copy your keys file to a local subdirectory;\nit will not use your original wallet file(s).\nExample - [b][color=00ffff]C:\\Users\\Satoshi\\SecretStuff\\Wallets\\Monero\\wallet.bin.keys[/b][/color]:" - TabTextInput: + AsciiSlashInput: size_hint_y: 0.1 id: wallet_path next: wallet_pw + on_text_validate: root.submitForm() Label: size_hint_y: 0.1 text: "Enter wallet password:" @@ -359,6 +379,7 @@ id: wallet_pw password: True next: repeat_wallet_pw + on_text_validate: root.submitForm() Label: size_hint_y: 0.1 text: "Repeat wallet password:" @@ -367,13 +388,14 @@ id: repeat_wallet_pw password: True next: wallet_path + on_text_validate: root.submitForm() Label: size_hint_y: 0.1 text: "Note - this wallet will be set as your default wallet" Button: size_hint_y: 0.15 text: "Click to load wallet" - on_press: root.loadWallet() + on_press: root.submitForm() @@ -392,11 +414,12 @@ size_hint: 1, 0.1 id: wallet_pw password: True + on_text_validate: root.submitForm() Label: size_hint: 1, 0.2 Button: text: "Click to launch wallet" - on_press: root.launchWallet() + on_press: root.submitForm() size_hint: 1, 0.5 diff --git a/lightWallet.py b/lightWallet.py index f5e445d..2323115 100644 --- a/lightWallet.py +++ b/lightWallet.py @@ -4,13 +4,11 @@ from kivy.config import Config from kivy.uix.accordion import Accordion from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button -from kivy.uix.gridlayout import GridLayout from kivy.uix.label import Label from kivy.properties import ObjectProperty from kivy.core.clipboard import Clipboard from kivy.clock import Clock from kivy.uix.popup import Popup -from kivy.storage.jsonstore import JsonStore from kivy.uix.textinput import TextInput import os @@ -21,34 +19,16 @@ import datetime import re import random import shutil -import signal from subprocess import PIPE, Popen, STDOUT from threading import Thread from Queue import Queue, Empty -from lib.checklastblock import CheckLastBlock +from lib.checklastblock import checkLastBlock from lib.savewallet import storeWallet -from lib.transferfunds import transferfundsrpccall -from lib.checkbalance import CheckBalanceSimplewallet - - -# sys.stdout = sys.stderr - - -# class flushfile(object): -# def __init__(self, f): -# self.f = f -# -# def write(self, x): -# self.f.write(x) -# self.f.flush() -# -# sys.stdout = flushfile(sys.stdout) - - -# Set unbuffered output -# msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) +from lib.transferfunds import transferFundsRPCCall +from lib.checkbalance import checkBalanceSimplewallet +from lib.gettransfers import getTransfers # Set POSIX argument for linux for something... @@ -61,7 +41,7 @@ donate_core_address = "46BeWrHpwXmHDpDEUmZBWZfoQpdc6HaERCNmx1pEYL2rAcuwufPN9rXHH # URLs for servers cool_mining_URL = "http://xmr1.coolmining.club:5012" localhost_URL = "http://localhost:18081" - +daemon_URL = cool_mining_URL # parser gets command line args parser = argparse.ArgumentParser() @@ -75,43 +55,6 @@ def enqueue_output(out, queue): out.close() -def read_output(process, stdout, queue): - while True: - last_queue_read = time.time() - # out666 = stdout.readline(1) - - out = stdout.readline() - # print(out) - # if len(out) < 2: - # out = stdout.readline(1) - # print("other out "+out) - # if out == '' and process.poll() != None: - if out == '': - print("passing last loop run @ {0}".format(last_queue_read)) - # print("breaking last loop run @ {0}".format(last_queue_read)) - # break - pass - if out != '': - print("queueing last loop run @ {0}".format(last_queue_read)) - line = out.rstrip() - queue.put_nowait(line) -# sys.stdout.flush() - - -# Function to split tx output lines -def tx_split(line): - split_line = line.rstrip().split(': ') - try: - amount = float(split_line[1].split(',')[0]) - tx = split_line[2].strip('<').strip('>') - date = split_line[0].split('.')[0] - return amount, tx, date - except ValueError: - return "Error", "Error", "Error" - except IndexError: - return "Error", "Error", "Error" - - # Check if a string is hex only def hex_match(strg, search=re.compile(r'[^a-fA-F0-9]').search): return not bool(search(strg)) @@ -152,13 +95,31 @@ class TabTextInput(TextInput): def _keyboard_on_key_down(self, window, keycode, text, modifiers): key, key_str = keycode - if key in (9, 13) and self.next is not None: + if key == 9 and self.next is not None: self.next.focus = True self.next.select_all() else: super(TabTextInput, self)._keyboard_on_key_down(window, keycode, text, modifiers) +class AsciiInput(TabTextInput): + """ TabTextInput class that only allows ascii input""" + pattern = re.compile('[^0-9a-zA-Z+_=;.\-\(\)\[\]{}]') + def insert_text(self, substring, from_undo=False): + pattern = self.pattern + s = re.sub(pattern, '', substring) + return super(AsciiInput, self).insert_text(s, from_undo=from_undo) + + +class AsciiSlashInput(TabTextInput): + """ TabTextInput class that only allows ascii input and slashes""" + pattern = re.compile('[^0-9a-zA-Z+_=;:/.\-\(\)\[\]{}\\\]') + def insert_text(self, substring, from_undo=False): + pattern = self.pattern + s = re.sub(pattern, '', substring) + return super(AsciiSlashInput, self).insert_text(s, from_undo=from_undo) + + class InitialPopup(Popup): """ Initial popup widget """ def loadWalletPopup(self): @@ -181,7 +142,18 @@ class CreateWalletPopup(Popup): wallet_pw = ObjectProperty() repeat_wallet_pw = ObjectProperty() - def createWallet(self): + def __init__(self, *args, **kwargs): + super(CreateWalletPopup, self).__init__(*args, **kwargs) + + def _keyboard_on_key_down(self, window, keycode, text, modifiers): + key, key_str = keycode + print(key, key_str) + if key in (13, 271): + self.submitForm() + else: + super(CreateWalletPopup, self)._keyboard_on_key_down(window, keycode, text, modifiers) + + def submitForm(self): electrum_coming, electrum_1, electrum_2, electrum_3, electrum_4 = None, None, None, None, None wallet_name = self.wallet_name.text wallet_pw = self.wallet_pw.text @@ -192,7 +164,7 @@ class CreateWalletPopup(Popup): os.mkdir(wallet_dir) else: sys.exit(7777) - p = Popen(["simplewallet", "--generate-new-wallet", wallet_file, "--password", wallet_pw, "--set_log", "0"], + p = Popen(["simplewallet", "--generate-new-wallet", wallet_file, "--password", wallet_pw], stdout=PIPE, stdin=PIPE, bufsize=1, @@ -200,7 +172,7 @@ class CreateWalletPopup(Popup): q = Queue() t = Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True # thread dies with the program - p.stdin.write("0\r\n") + p.stdin.write(str("0\r\n")) p.stdin.flush() t.start() t_start = time.time() @@ -228,7 +200,7 @@ class CreateWalletPopup(Popup): if 130 < len(line.rstrip()) < 350: electrum_coming = True print(line.rstrip()) - # print("simplewallet output:", line.rstrip()) + print("simplewallet output:", line.rstrip()) except Empty: # print('no output yet') pass @@ -251,20 +223,24 @@ class CreateWalletPopup(Popup): wallet_name, address, view_key, electrum_line_2+' '+electrum_line_3+' '+electrum_line_4)) with open(os.path.join(App.get_running_app().root.data_dir, "CONFIG.file"), 'w') as f: f.write("Account name= " + wallet_name + "\n") - f.write("Bitmonerod daemon server= {0}".format(cool_mining_URL)) + f.write("Bitmonerod daemon server= {0}".format(daemon_URL)) self.dismiss() - content = Button( + confirm_box = BoxLayout(orientation='vertical') + confirm_label = Label( text="Your wallet has been created in the\nMy Documents/lightWallet/{0}_walletData directory.\nThe info.txt document in that folder contains your wallet recovery seed.\nPlease keep this seed safe, you can restore a corrupted/lost wallet with it.\nSomeone else can also bypass your password and steal your XMR with it.\nI suggest deleting or encrypting this file.\nClick to dismiss.".format( wallet_name)) - walletCreatedPopup = Popup(title="Wallet created window", content=content, size_hint=(0.75, 0.75)) - content.bind(on_press=walletCreatedPopup.dismiss) + confirm_button = Button(text="Click to dismiss", size_hint_y=0.2) + confirm_box.add_widget(confirm_label) + confirm_box.add_widget(confirm_button) + walletCreatedPopup = Popup(title="Wallet created window", content=confirm_box, size_hint=(0.75, 0.75), auto_dismiss=False) + confirm_button.bind(on_press=walletCreatedPopup.dismiss) walletCreatedPopup.open() App.get_running_app().root.daemon_thread = Thread(target=App.get_running_app().root.checkDaemonStatus, - args=(App.get_running_app().root.daemon_queue, cool_mining_URL)) + args=(App.get_running_app().root.daemon_queue, daemon_URL)) App.get_running_app().root.daemon_thread.daemon = True App.get_running_app().root.daemon_thread.start() App.get_running_app().root.launchWallet(wallet_name, wallet_pw) - App.get_running_app().root.daemon_server_textinput.text = cool_mining_URL + App.get_running_app().root.daemon_server_textinput.text = daemon_URL else: content = Button(text="Check passwords!!!\nClick to dismiss") pwErrorPopup = Popup(title="Password match error window", @@ -279,7 +255,7 @@ class LoadWalletPopup(Popup): wallet_pw = ObjectProperty() repeat_wallet_pw = ObjectProperty() - def loadWallet(self): + def submitForm(self): split_name = os.path.split(self.wallet_path.text)[1].split('.') wallet_name = '.'.join(tuple(split_name[0:len(split_name) - 1])) print(split_name, wallet_name) @@ -331,7 +307,7 @@ class LoadWalletPopup(Popup): os.mkdir(wallet_dir) with open(os.path.join(App.get_running_app().root.data_dir, "CONFIG.file"), 'w') as f: f.write("Account name= " + wallet_name + "\n") - f.write("Bitmonerod daemon server= {0}".format(cool_mining_URL)) + f.write("Bitmonerod daemon server= {0}".format(daemon_URL)) with open(os.path.join(wallet_dir, wallet_name+".address.txt"), 'w') as f: f.write(address) shutil.copyfile(self.wallet_path.text, os.path.join(wallet_dir, wallet_name+".keys")) @@ -341,10 +317,10 @@ class LoadWalletPopup(Popup): wallet_name)) walletCopiedPopup = Popup(title="Wallet keys copied window", content=content, size_hint=(0.75, 0.75)) content.bind(on_press=walletCopiedPopup.dismiss) - App.get_running_app().root.daemon_server_textinput.text = cool_mining_URL + App.get_running_app().root.daemon_server_textinput.text = daemon_URL walletCopiedPopup.open() App.get_running_app().root.daemon_thread = Thread(target=App.get_running_app().root.checkDaemonStatus, - args=(App.get_running_app().root.daemon_queue, cool_mining_URL)) + args=(App.get_running_app().root.daemon_queue, daemon_URL)) App.get_running_app().root.daemon_thread.daemon = True App.get_running_app().root.daemon_thread.start() App.get_running_app().root.launchWallet(wallet_name, wallet_pw) @@ -375,7 +351,7 @@ class PasswordPopup(Popup): self.walletname_label.text = "It seems you've entered an incorrect password,\nPlease re-enter your password to open {0} account:".format( self.wallet_name) - def launchWallet(self): + def submitForm(self): wallet_pw = self.wallet_pw.text App.get_running_app().root.launchWallet(self.wallet_name, wallet_pw) self.dismiss() @@ -396,7 +372,7 @@ class TransferPopup(Popup): self.transfer_popup_label.text = self.popuptext def transfer(self): - return_boolean, return_value = transferfundsrpccall(self.amount, self.address, self.mixin, self.paymentid) + return_boolean, return_value = transferFundsRPCCall(self.amount, self.address, self.mixin, self.paymentid) txid_popup = TxIDPopup(return_boolean, return_value, self.amount, self.address, self.mixin, self.paymentid) txid_popup.open() @@ -454,6 +430,7 @@ class RootWidget(Accordion): data_dir = os.path.join(os.path.expanduser('~'), "Documents", "lightWallet") # Tx list items # tx_layout = GridLayout(cols=1, spacing=10, size_hint_y=None) + root_widget = ObjectProperty() # Transfer XMR id items address_input_textinput = ObjectProperty() amount_input_textinput = ObjectProperty() @@ -484,6 +461,12 @@ class RootWidget(Accordion): wallet_thread = None save_thread = None save_queue = Queue() + tx_thread = None + tx_queue = Queue() + spent_tx = [] + unspent_tx = [] + spent_tx_dict = {} + unspent_tx_dict = {} # Other variables wallet_height = None calculated_balance = 0.0 @@ -499,7 +482,7 @@ class RootWidget(Accordion): super(RootWidget, self).__init__(**kwargs) if not os.path.isdir(self.data_dir): os.makedirs(self.data_dir) - self.daemon_server_textinput.text = cool_mining_URL + self.daemon_server_textinput.text = daemon_URL if os.path.isfile(os.path.join(self.data_dir, 'CONFIG.file')): with open(os.path.join(self.data_dir, 'CONFIG.file'), 'r') as f: lines = f.readlines() @@ -515,13 +498,18 @@ class RootWidget(Accordion): Clock.schedule_once(self.initialConfig, 0.5) else: Clock.schedule_once(self.initialConfig, 0.5) - Clock.schedule_interval(self.checkDaemon, 2) self.balance_thread = Thread(target=self.checkBalanceStatus, args=(self.balance_queue,)) self.balance_thread.daemon = True self.balance_thread.start() - Clock.schedule_interval(self.checkBalance, 2) - Clock.schedule_interval(self.saveWallet, 60) - self.ids.tx_list.bind(minimum_height=self.ids.tx_list.setter('height')) + self.tx_thread = Thread(target=self.checkTransactionsStatus, args=(self.tx_queue,)) + self.tx_thread.daemon = True + self.tx_thread.start() + Clock.schedule_interval(self.checkDaemon, 7) + Clock.schedule_interval(self.checkBalance, 7) + Clock.schedule_interval(self.checkTransactions, 12) + Clock.schedule_interval(self.saveWallet, 180) + self.ids.unspent_tx_list.bind(minimum_height=self.ids.unspent_tx_list.setter('height')) + self.ids.spent_tx_list.bind(minimum_height=self.ids.spent_tx_list.setter('height')) def initialConfig(self, dt): ip = InitialPopup() @@ -534,12 +522,9 @@ class RootWidget(Accordion): def checkDaemonStatus(self, daemon_queue, server): """Method to be threaded for checking bitmonerod status""" while True: - # print(server) - height, reward, timesince, localtime = CheckLastBlock(server + "/json_rpc") + height, reward, timesince, localtime = checkLastBlock(server + "/json_rpc") daemon_queue.put((height, reward, timesince, localtime)) - # print("sleeping") - time.sleep(1) - #self.checkDaemonStatus(daemon_queue, self.daemon_server_textinput.text) + time.sleep(5) def checkDaemon(self, dt): """RPC call to check bitmonerod for blockchain info""" @@ -563,26 +548,19 @@ class RootWidget(Accordion): def checkBalanceStatus(self, balance_queue): """Method to be threaded for checking balance status""" while True: - balance, unlocked_balance = CheckBalanceSimplewallet() - # print("Putting {0} and {1} in queue". format(balance, unlocked_balance)) + balance, unlocked_balance = checkBalanceSimplewallet() balance_queue.put((balance, unlocked_balance)) - time.sleep(1) - #self.checkDaemonStatus(daemon_queue, self.daemon_server_textinput.text) + time.sleep(5) def checkBalance(self, dt): """RPC call to check bitmonerod for blockchain info""" - # print("checking queue") try: balance, unlocked_balance = self.balance_queue.get_nowait() - # print("Got from queue: {0} and {1}".format(balance, unlocked_balance)) try: self.total_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(float(balance)) self.unlocked_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(float(unlocked_balance)) self.unlocked_balance, self.total_balance = float(unlocked_balance), float(balance) - # self.total_balance_label.text = "[b][color=00ffff]{0}[/b][/color]".format(balance) - # self.unlocked_balance_label.text = "[b][color=00ffff]{0}[/b][/color] XMR".format(unlocked_balance) except ValueError: - # print("value error") self.total_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") self.unlocked_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") except Empty: @@ -590,40 +568,49 @@ class RootWidget(Accordion): self.total_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") self.unlocked_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") - # def resetDaemon(self): - # print("resetting") - # save_thread = Thread(target=storeWallet) - # save_thread.daemon = True - # save_thread.start() - # save_thread.join() - # self.wallet_process.kill() - # if sys.platform == 'win32': - # try: - # os.system("taskkill /im simplewallet.exe /f") - # except: - # print("Simplewallet.exe not running") - # self.daemon_thread.join(0.5) - # self.daemon_thread = Thread(target=self.checkDaemonStatus, - # args=(self.daemon_queue, self.daemon_server_textinput.text)) - # self.daemon_thread.daemon = True - # self.daemon_thread.start() - # self.wallet_process = Popen(["simplewallet", "--wallet-file", "./{0}_walletData/{1}.keys".format( - # self.wallet_name.replace('.', ''), self.wallet_name), "--password", self.wallet_pw, - # "--daemon-address", self.daemon_server_textinput.text, "--rpc-bind-port", "19091"], - # # stdin=PIPE, - # stdout=PIPE, - # stderr=STDOUT, - # bufsize=1, - # shell=True) - # self.wallet_thread = Thread(target=enqueue_output, - # args=(self.wallet_process.stdout, self.wallet_queue)) - # print("finished reset") + def checkTransactionsStatus(self, tx_queue): + """Method to be threaded for checking balance status""" + while True: + unspent_txs, spent_txs = getTransfers() + tx_queue.put((unspent_txs, spent_txs)) + time.sleep(10) + + def checkTransactions(self, dt): + """RPC call to check bitmonerod for blockchain info""" + try: + unspent_txs, spent_txs = self.tx_queue.get_nowait() + if len(unspent_txs) > len(self.unspent_tx) or len(spent_txs) > len(self.spent_tx): + self.unspent_tx = unspent_txs + self.spent_tx = spent_txs + self.unspent_tx_dict = {} + self.spent_tx_dict = {} + self.ids.unspent_tx_list.clear_widgets() + self.ids.spent_tx_list.clear_widgets() + temp_balance = 0.0 + for idx, val in enumerate(self.unspent_tx): + self.unspent_tx_dict[idx] = Button(text="tx_id: {0} | amount: {1:.4f}".format(val[0], val[1]), + size_hint_y=None, height=15, font_size=9) + self.unspent_tx_dict[idx].bind(on_press=self._create_button_callback(val[0])) + self.ids.unspent_tx_list.add_widget(self.unspent_tx_dict[idx]) + temp_balance += val[1] + for idx, val in enumerate(self.spent_tx): + self.spent_tx_dict[idx] = Button(text="tx_id: {0} | amount: {1:.4f}".format(val[0], val[1]), + size_hint_y=None, height=15, font_size=9) + self.spent_tx_dict[idx].bind(on_press=self._create_button_callback(val[0])) + self.ids.spent_tx_list.add_widget(self.spent_tx_dict[idx]) + self.calculated_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(float(temp_balance)) + except Empty: + pass + + def _create_button_callback(self, val): + def callback(button): + Clipboard.put(val) + return callback def launchWallet(self, wallet_name, wallet_pw): self.wallet_name = wallet_name self.wallet_pw = wallet_pw wallet_dir = os.path.join(self.data_dir, "{0}_walletData".format(wallet_name.replace('.', ''))) - tx_file = os.path.join(self.data_dir, "{0}_walletData".format(wallet_name.replace('.', '')), "tx.txt") wallet_file = os.path.join(wallet_dir, wallet_name) address_file = os.path.join(wallet_dir, wallet_name+".address.txt") if not os.path.isfile(wallet_file): @@ -673,37 +660,13 @@ class RootWidget(Accordion): if os.path.isfile(address_file): with open(address_file, 'r') as f: self.address = f.read().rstrip() - - # Check if tx file exists and create it if not or read txs if it does - if not os.path.isfile(tx_file): - with open(tx_file, 'w+') as previous_txs: - previous_txs.write("{0}\t{1}\t{2}\t{3}\n".format("tx type", "tx time", "XMR amount", "tx hash")) - if os.path.isfile(tx_file): - with open(tx_file, 'r') as previous_txs: - txs = previous_txs.read().split('\n') - for idx, line in enumerate(txs): - temp_line = line.split('\t') - if idx > 0 and len(temp_line) == 4: - l = Label(text="{0} {1} XMR on {2} in tx: {3}".format( - temp_line[0], temp_line[1], temp_line[2], temp_line[3]), - size_hint_y=None, height=15, font_size=9) - self.ids.tx_list.add_widget(l) - if temp_line[0] == "Received": - self.calculated_balance += float(temp_line[2]) - elif temp_line[0] == "Spent": - self.calculated_balance -= float(temp_line[2]) time.sleep(2) self.calculated_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(self.calculated_balance) self.wallet_process = Popen(["simplewallet", "--wallet-file", wallet_file, "--password", wallet_pw, "--daemon-address", self.daemon_server_textinput.text, "--rpc-bind-port", "19091"], - # "--set_log", "0"], - # stdin=PIPE, stdout=PIPE, stderr=STDOUT, bufsize=1) - # shell=True) - # self.wallet_thread = Thread(target=read_output, - # args=(self.wallet_process, self.wallet_process.stdout, self.wallet_queue)) self.wallet_thread = Thread(target=enqueue_output, args=(self.wallet_process.stdout, self.wallet_queue)) self.wallet_thread.daemon = True # thread dies with the program @@ -714,7 +677,6 @@ class RootWidget(Accordion): def readWalletQueue(self, dt): wallet_dir = os.path.join(self.data_dir, "{0}_walletData".format(self.wallet_name.replace('.', ''))) - tx_file = os.path.join(self.data_dir, "{0}_walletData".format(self.wallet_name.replace('.', '')), "tx.txt") wallet_file = os.path.join(wallet_dir, self.wallet_name) try: new_line = self.wallet_queue.get_nowait() @@ -749,39 +711,8 @@ class RootWidget(Accordion): self.unlocked_balance = float(split_line[3].split(': ')[1]) self.total_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(self.total_balance) self.unlocked_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(self.unlocked_balance) - elif "Received money" in new_line and "with tx" in new_line: - this_amount, this_tx, this_date = tx_split(new_line) - self.calculated_balance += this_amount - self.calculated_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color] XMR".format( - self.calculated_balance) - l = Label(text="Received {0:0.4f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), - size_hint_y=None, height=15, font_size=9) - # self.ids.tx_scroller.clear_widgets() - self.ids.tx_list.add_widget(l) - # self.ids.tx_scroller.add_widget(self.ids.tx_list) - with open(tx_file, 'a') as tx_file: - tx_file.write("{0}\t{1}\t{2:0.4f}\t{3}\n".format("Received", this_date, this_amount, this_tx)) - elif "Spent money" in new_line and "with tx" in new_line: - this_amount, this_tx, this_date = tx_split(new_line) - self.calculated_balance += this_amount - self.calculated_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format( - self.calculated_balance) - l = Label(text="Spent {0:0.4f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), - size_hint_y=None, height=15, font_size=9) - # self.ids.tx_scroller.remove_widget(self.ids.tx_list) - self.ids.tx_list.add_widget(l) - # self.ids.tx_scroller.add_widget(self.ids.tx_list) - with open(tx_file, 'a') as tx_file: - tx_file.write("{0}\t{1}\t{2:0.4f}\t{3}\n".format("Spent", this_date, this_amount, this_tx)) - # elif "Payment found" in newLine: - # with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file: - # tx_file.write(newLine + '\n') except Empty: pass - # if not self.wallet_thread.is_alive(): - # print("dead") - # self.wallet_thread.start() - # Clock.schedule_interval(self.readWalletQueue, 0.01) def saveWallet(self, dt): try: @@ -890,6 +821,13 @@ class RootWidget(Accordion): core_donate_popup = DonateCore() core_donate_popup.open() + def changeTabs(self): + print(self.root_widget.orientation) + if self.root_widget.orientation == "horizontal": + self.root_widget.orientation = "vertical" + else: + self.root_widget.orientation = "horizontal" + class lightWalletApp(App): def build(self): @@ -900,7 +838,20 @@ class lightWalletApp(App): if __name__ == '__main__': Config.set('kivy', 'exit_on_escape', 0) Config.set('kivy', 'window_icon', "./lilicon.ico") + Config.set('input', 'mouse', 'mouse,disable_multitouch') lightWalletApp().run() + try: + tx_file = os.path.join(App.get_running_app().root.data_dir, + "{0}_walletData".format(App.get_running_app().root.wallet_name.replace('.', '')), "tx.txt") + with open(tx_file, 'w') as txs: + txs.write("Unspent tx outputs:\n") + for i in App.get_running_app().root.unspent_tx: + txs.write("tx_id: {0} | amount: {1:.4f}\n".format(i[0], i[1])) + txs.write("\nSpent tx outputs:\n") + for i in App.get_running_app().root.spent_tx: + txs.write("tx_id: {0} | amount: {1:.4f}\n".format(i[0], i[1])) + except: + print("Problem writing tx file") if sys.platform == 'win32': try: App.get_running_app().root.wallet_process.kill()