From 44779c022230b024438d66ec3d6a11be2e62f299 Mon Sep 17 00:00:00 2001 From: ingerulro Date: Wed, 18 Apr 2018 08:28:33 +0300 Subject: [PATCH] Rebranding --- .gitignore | 1 + README.md | 64 +-- Resources/icons/logo_black_32.png | Bin 670 -> 0 bytes Resources/icons/logo_black_64.png | Bin 1209 -> 0 bytes Resources/icons/{sumokoin.icns => ombre.icns} | Bin Resources/icons/ombre.ico | Bin 0 -> 20876 bytes Resources/icons/ombre.png | Bin 0 -> 1135 bytes Resources/icons/ombre_16x16.png | Bin 0 -> 702 bytes Resources/icons/ombre_16x16_mac.png | Bin 0 -> 702 bytes Resources/icons/ombre_icon_32.png | Bin 0 -> 1135 bytes Resources/icons/ombre_icon_64.png | Bin 0 -> 3304 bytes Resources/icons/sumokoin.ico | Bin 370070 -> 0 bytes app/QSingleApplication.py | 28 +- app/hub.py | 248 +++++----- html/index.py | 437 +++++++++--------- html/newwallet.py | 2 +- main.py | 65 ++- manager/ProcessManager.py | 69 ++- rpc/__init__.py | 68 +-- settings/__init__.py | 18 +- utils/common.py | 12 +- webui/__init__.py | 333 ++++++++----- 22 files changed, 747 insertions(+), 598 deletions(-) delete mode 100644 Resources/icons/logo_black_32.png delete mode 100644 Resources/icons/logo_black_64.png rename Resources/icons/{sumokoin.icns => ombre.icns} (100%) create mode 100644 Resources/icons/ombre.ico create mode 100644 Resources/icons/ombre.png create mode 100644 Resources/icons/ombre_16x16.png create mode 100644 Resources/icons/ombre_16x16_mac.png create mode 100644 Resources/icons/ombre_icon_32.png create mode 100644 Resources/icons/ombre_icon_64.png delete mode 100644 Resources/icons/sumokoin.ico diff --git a/.gitignore b/.gitignore index 862683a..7a4e336 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ cscope.po.out *.lo *.o *.obj +*.pyc # Precompiled Headers *.gch diff --git a/README.md b/README.md index 0368d8f..fabfa86 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,36 @@ -# Ombre GUI Wallet - -Copyright (c) 2018, OMBRE -Copyright (c) 2017, Sumokoin.org - -**One of the most easy-to-use, intuitive GUI (full) wallets in crypto.** - -# Installation & running from source codes - -1. Clone the repo: - - git clone https://github.com/ombre-projects/OmbreGUIWallet/ SumoGUIWallet - +# Sumokoin GUI Wallet + +Copyright (c) 2017, Sumokoin.org + +**One of the most easy-to-use, intuitive GUI (full) wallets in crypto.** + +![](https://www.sumokoin.org/images/sumokoin-gui-wallet-v0.0.1-b2.png) + + +# Installation & running from source codes + +1. Clone the repo: + + git clone https://github.com/sumoprojects/SumoGUIWallet SumoGUIWallet + 2. Install dependencies (with Python 2.7): - + * Generally, you can use Python `pip` to install required components: - - pip install PySide, requests, psutil - - * or - - pip install -r requirements.txt - + + pip install PySide, requests, psutil + + * or + + pip install -r requirements.txt + * On some OSes, PySide may be required to install from pre-built packages. For example, on Ubuntu 16.04, install PySide with the following command: - - sudo apt install python-pyside - - -3. Build/download Sumokoin binaries from [Ombre repo](https://github.com/ombre-projects/ombre) and put it to `Resources/bin` sub-directory. - -4. Run the wallet (Python 2.7): - - cd /path/to/SumoGUIWallet - python wallet.py + + sudo apt install python-pyside + + +3. Build/download Sumokoin binaries from [Sumokoin repo](https://github.com/sumoprojects/sumokoin) and put it to `Resources/bin` sub-directory. + +4. Run the wallet (Python 2.7): + + cd /path/to/SumoGUIWallet + python wallet.py diff --git a/Resources/icons/logo_black_32.png b/Resources/icons/logo_black_32.png deleted file mode 100644 index 3df2f53e5aa5280b1ec3b71aadf0aaa6d73dc98f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 670 zcmV;P0%84$P)!CiYMPePE=34j2$2|w;KHbtAP6B;)S{J(iWY5JDG0e}7+Dm{sV{0dvC73dE>?dhxhKe=br!new}k+U*gG8 z;P_sFHv*r5C7`C<;D>?NKwkP0I8aUk_Y8PJ$IAuYZ9ty_PfWWbY4Be1 zyS67Wxg?-%pwrmfW&Z~ZCrO|%`{zKVv8RA%>U*CQ_(ikVodNy5rvE|`1l}q5e1y-U zeBP8A{1mXR;O8U!`pqhwC?$a*9k3rjQ-oiYA<*X&xYCnA7f=Cw09GUXD$M_}QW7|; zZ<{nsuA2YtQZ4^uC*`gJJpq0_K+gD`i3^+p9y;LPOu`uhT7XtyM7Dz8hGLupW_DYs zCLQnvC7`^AUe_&HQ~SYA!S4nHy$QVZ0Dl1-1YSAzQD5*5U@IVS8!%S{JOd1vq-8Y+ zoc1B`J0$oK;86tlJaET33$88~1HT_JxE{C`(w8#_w3tqWfj0ozh=DEIhBZ&1FkJyo z1|_0fdgX@6An*y8j(NQsfXhL5z8Ldg#(ys6D&$=Pw><~lkbf;O8;S8izW;4J~<$xgB)D@dH?J3}p%JqyMB+DihyYg3)$N*n~8e2Z=bwKJw zDy@vRDP30~S^@rq0Hjrh>-2(7i{O?>ZRi$d_1zc#11JeLPJP@E-RGQr?>*6{9UaCEU^GzDC?^Y{7@gDYk-fG`iDUOY8v`yeTI5B z34{(R&s(a6fO$S|zuwbkm`|HkX$SuVa617(gJ>l0{%b*J0}!7uO=OQP8K@T@v#wXO;_yvnuf6V zoZ<9oS=ge;RV6U$?b6{I#{`T}R_g<@14NN+#_yi4cLUc1ysy;liD7TA@w*#Pm*gpM zN13->V%VDi+$-gF=yC)M25ODE-{fo`$3OGlpgyStOthH8VWkdu%BXuLsRWEs>Shw} z=I3>#?m-gwet;&Qq{h!iJ6q}JV{b_k7r##lPI5MewATeV<6~=T(oTIpVwdS{3P^;Q z;2V8x+#!FlVg_FolS2W3Zz^@yBq3tn3g?8(Nj`esi<6!ny~*Sgsx{>XUJL9m^2UD% zc=i{t6<8}}D{$Om6nPUPJ%rFlz&@aRISKfsh@)qKjo4p=*p_0z;a7xy8_=j1b@-<_GcGq+fXX-Q!Sl6A?mzyYzN*F-Dz?Z4(s#<*+sr|G=&DlA>gM71k4oOf&4$U zU97YKe~?dsyIMf^N1X~2BXrgQ%<|YcRf1qBN_ja-z;h{LXUjZxW+a4wvtbEX9HX-@ z3|p-UAz@*dv-qDeIy!3Ddq<{5r3pA^dtiFWL1$*(t|cL(V7d(fE*7qhN^ z$J;OL2IeRjr{wr6QHBH&u+w2js}=ii6v_CMqxgQO(1ZL%rmK!_%2k#rLka#~xYXqu zKHnU@P{uh|E63%w3J~xRcujL^bBQ|K5-{b40N+)BgwHf*E|##v4~krj)B_tN{wT-v zIHEZ*g8biDb~`?dG)v~U)-nhi1iUDV=st&jT#&5t9Pdt$E8@&Jxri7BJST6eyX5e5 z9^?0A1hoSHO4|Fd)VYv4@}Jp^6z2y5eI@sp^Z3#(@y2<{OrHdfDbZ;rnPielCIF8C XKSn2km|ZyV00000NkvXXu0mjfkQ5)( diff --git a/Resources/icons/sumokoin.icns b/Resources/icons/ombre.icns similarity index 100% rename from Resources/icons/sumokoin.icns rename to Resources/icons/ombre.icns diff --git a/Resources/icons/ombre.ico b/Resources/icons/ombre.ico new file mode 100644 index 0000000000000000000000000000000000000000..5d137ade6ee8d2f290c090cb1855bf2f0881aa82 GIT binary patch literal 20876 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 XA@)eQ@V5`pr^s`d=y0&w+CAYv6~EIm literal 0 HcmV?d00001 diff --git a/Resources/icons/ombre.png b/Resources/icons/ombre.png new file mode 100644 index 0000000000000000000000000000000000000000..baf70ff5d6144181465a2a98c89c9fb47f9f29f5 GIT binary patch literal 1135 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 PP#b`uzA3z3&n51EILr>F literal 0 HcmV?d00001 diff --git a/Resources/icons/sumokoin.ico b/Resources/icons/sumokoin.ico deleted file mode 100644 index 8f78868171a1e5a6a41152d59c4637be34bac1f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 370070 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 op-iqXQTQeBm9Jds@7@{HrVYu8FHr3
Hint: Wallet file often ends with .bin""") return False - try: + try: copy2(wallet_filename, os.path.join(wallet_dir_path, new_wallet_file)) copy2(wallet_key_filename, os.path.join(wallet_dir_path, \ new_wallet_file + '.keys')) @@ -84,10 +84,10 @@ def import_wallet(self): self._detail_error_msg("Importing Wallet", "Error importing wallet!", str(err)) self.ui.reset_wallet() return False - + if not os.path.exists(new_wallet_file): return False - + wallet_password = None while True: wallet_password, result = self._custom_input_dialog(self.new_wallet_ui, \ @@ -100,11 +100,11 @@ def import_wallet(self): else: break else: - return False - + return False + self.on_new_wallet_show_progress_event.emit('Importing wallet...') self.app_process_events() - + wallet_address_filepath = new_wallet_file + ".address.txt" wallet_address = readFile(wallet_address_filepath) self.ui.wallet_info.wallet_filepath = new_wallet_file @@ -137,7 +137,7 @@ def import_wallet(self): QMessageBox.critical(self.new_wallet_ui, \ 'Error Importing Wallet',\ """Error: Unknown error.
- Imported wallet file may be corrupted. + Imported wallet file may be corrupted. Try to restore it with mnemonic seed instead.""") self.ui.reset_wallet() return False @@ -145,8 +145,8 @@ def import_wallet(self): self._show_wallet_info() self.ui.wallet_info.save() return True - - + + @Slot() def close_new_wallet_dialog(self): log("Closing new wallet dialog...", LEVEL_INFO) @@ -154,7 +154,7 @@ def close_new_wallet_dialog(self): if self.ui.wallet_info.wallet_filepath \ and os.path.exists(self.ui.wallet_info.wallet_filepath): self.ui.show_wallet(); - + @Slot(unicode) def create_new_wallet(self, mnemonic_seed=u''): @@ -172,7 +172,7 @@ def create_new_wallet(self, mnemonic_seed=u''): "Password must contain at least 1 character [a-zA-Z] or number [0-9]\
or special character like !@#$%^&* and not contain spaces") continue - + confirm_password, result = self._custom_input_dialog(self.new_wallet_ui, \ 'Wallet Password', \ "Confirm wallet password:", QLineEdit.Password) @@ -187,13 +187,13 @@ def create_new_wallet(self, mnemonic_seed=u''): break else: break - + if has_password: if not mnemonic_seed: # i.e. create new wallet mnemonic_seed_language = "0" # english seed_language_list = [sl[1] for sl in seed_languages] - lang, ok = QInputDialog.getItem(self.new_wallet_ui, "Mnemonic Seed Language", - "Select a language for wallet mnemonic seed:", + lang, ok = QInputDialog.getItem(self.new_wallet_ui, "Mnemonic Seed Language", + "Select a language for wallet mnemonic seed:", seed_language_list, 0, False) if ok and lang: for sl in seed_languages: @@ -205,12 +205,12 @@ def create_new_wallet(self, mnemonic_seed=u''): 'Mnemonic Seed Language',\ "No language is selected!\
'English' will be used for mnemonic seed") - + self.on_new_wallet_show_progress_event.emit("Restoring wallet..." \ if mnemonic_seed else "Creating wallet...") self.app_process_events() wallet_filepath = os.path.join(wallet_dir_path, str(uuid.uuid4().hex) + '.bin') - wallet_log_path = os.path.join(wallet_dir_path, 'sumo-wallet-cli.log') + wallet_log_path = os.path.join(wallet_dir_path, 'ombre-wallet-cli.log') resources_path = self.app.property("ResPath") if not mnemonic_seed: # i.e. create new wallet self.wallet_cli_manager = WalletCliManager(resources_path, \ @@ -238,7 +238,7 @@ def create_new_wallet(self, mnemonic_seed=u''): self.wallet_cli_manager.send_command(wallet_password) self.app_process_events(0.5) self.wallet_cli_manager.send_command("exit") - + self.app_process_events(2) except Exception, err: log(str(err), LEVEL_ERROR) @@ -249,7 +249,7 @@ def create_new_wallet(self, mnemonic_seed=u''): error_detailed_text) self.on_new_wallet_ui_reset_event.emit() return - + if os.path.exists(wallet_filepath): wallet_address = readFile(wallet_filepath + ".address.txt") self.ui.wallet_info.wallet_filepath = wallet_filepath @@ -258,7 +258,7 @@ def create_new_wallet(self, mnemonic_seed=u''): self.ui.wallet_info.is_loaded = True self.ui.run_wallet_rpc(wallet_password, 2) counter = 0 - + while not self.ui.wallet_rpc_manager.is_ready(): self.app_process_events(0.5) if self.ui.wallet_rpc_manager.block_hex: @@ -280,26 +280,26 @@ def create_new_wallet(self, mnemonic_seed=u''): """Error: Unknown error! Wallet RPC appears not responding.""") self.ui.reset_wallet() return - + self._show_wallet_info() self.ui.wallet_info.save() - else: + else: self.on_new_wallet_ui_reset_event.emit() - + @Slot() def rescan_spent(self): self.app_process_events() self.ui.wallet_rpc_manager.rpc_request.rescan_spent() - + # refresh wallet_info tx cache self.ui.wallet_info.top_tx_height = 0 self.ui.wallet_info.bc_height = -1 self.ui.wallet_info.wallet_transfers = [] self.ui.update_wallet_info() - + self.on_wallet_rescan_spent_completed_event.emit() - - + + @Slot() def rescan_bc(self): result = QMessageBox.question(self.ui, "Rescan Blockchain", \ @@ -308,19 +308,19 @@ def rescan_bc(self): if result == QMessageBox.No: self.on_wallet_rescan_bc_completed_event.emit() return - + self.app_process_events() self.ui.wallet_rpc_manager.rpc_request.rescan_bc() - + # refresh wallet_info tx cache self.ui.wallet_info.top_tx_height = 0 self.ui.wallet_info.bc_height = -1 self.ui.wallet_info.wallet_transfers = [] self.ui.update_wallet_info() - + self.on_wallet_rescan_bc_completed_event.emit() - - + + @Slot(float, str, str, int, int, str, bool, bool) def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_address, sweep_all): if not payment_id and not address.startswith("Sumi"): @@ -330,7 +330,7 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad if result == QMessageBox.No: self.on_wallet_send_tx_completed_event.emit('{"status": "CANCELLED", "message": "Sending coin cancelled"}') return - + if sweep_all: result = QMessageBox.question(self.ui, "Sending all your coins?", \ "This will send all your coins to target address.

Are you sure you want to proceed?", \ @@ -338,21 +338,21 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad if result == QMessageBox.No: self.on_wallet_send_tx_completed_event.emit('{"status": "CANCELLED", "message": "Sending coin cancelled"}') return - + wallet_password, result = self._custom_input_dialog(self.ui, \ "Wallet Password", "Please enter wallet password:", QLineEdit.Password) - + if not result: self.on_wallet_send_tx_completed_event.emit('{"status": "CANCELLED", "message": "Wrong wallet password"}'); return - + if hashlib.sha256(wallet_password).hexdigest() != self.ui.wallet_info.wallet_password: self.on_wallet_send_tx_completed_event.emit('{"status": "CANCELLED", "message": "Wrong wallet password"}'); QMessageBox.warning(self.ui, "Incorrect Wallet Password", "Wallet password is not correct!") return - + amount = int(amount*COIN) - + if sweep_all: _, _, per_subaddress = self.ui.wallet_rpc_manager.rpc_request.get_balance() subaddr_indices = [] @@ -360,10 +360,10 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad if s['unlocked_balance'] > 0: subaddr_indices.append(s['address_index']) ret = self.ui.wallet_rpc_manager.rpc_request.transfer_all(address, payment_id, priority, mixin, 0, subaddr_indices) - else: + else: ret = self.ui.wallet_rpc_manager.rpc_request.transfer_split(amount, \ - address, payment_id, priority, mixin) - + address, payment_id, priority, mixin) + if ret['status'] == "ERROR": self.on_wallet_send_tx_completed_event.emit(json.dumps(ret)); self.app_process_events() @@ -371,11 +371,11 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad msg = "Not possible to send coins. Probably, there is not enough money left for fee." else: msg = ret['message'] - + QMessageBox.critical(self.ui, \ 'Error Sending Coins',\ "ERROR

" + msg) - + if ret['status'] == "OK": if tx_desc: # set the same note for all txs: @@ -383,10 +383,10 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad for i in range(len(ret['tx_hash_list'])): notes.append(tx_desc) self.ui.wallet_rpc_manager.rpc_request.set_tx_notes(ret['tx_hash_list'], notes) - + self.on_wallet_send_tx_completed_event.emit(json.dumps(ret)); self.app_process_events() - + msg = "Coins successfully sent in the following transaction(s):

" for i in range(len(ret['tx_hash_list'])): if sweep_all: @@ -397,7 +397,7 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad print_money2(ret['fee_list'][i])) QMessageBox.information(self.ui, 'Coins Sent', msg) self.ui.update_wallet_info() - + if save_address: desc, _ = self._custom_input_dialog(self.ui, \ 'Saving Address...', \ @@ -406,28 +406,28 @@ def send_tx(self, amount, address, payment_id, priority, mixin, tx_desc, save_ad payment_id, desc) if ret['status'] == "OK": if self.ui.wallet_info.wallet_address_book: - address_entry = {"address": address, - "payment_id": payment_id, - "description": desc[0:200], + address_entry = {"address": address, + "payment_id": payment_id, + "description": desc[0:200], "index": ret["index"] } self.ui.wallet_info.wallet_address_book.append(address_entry) - + QMessageBox.information(self.ui, "Address Saved", \ - "Address (and payment ID) saved to address book.") - - + "Address (and payment ID) saved to address book.") + + @Slot(int) def generate_payment_id(self, hex_length=16): payment_id = binascii.b2a_hex(os.urandom(hex_length/2)) integrated_address = self.ui.wallet_rpc_manager.rpc_request.make_integrated_address(payment_id)["integrated_address"] self.on_generate_payment_id_event.emit(payment_id, integrated_address); - - + + @Slot(str) def copy_text(self, text): QApplication.clipboard().setText(text) - + @Slot() def load_address_book(self): if not self.ui.wallet_info.wallet_address_book: @@ -436,9 +436,9 @@ def load_address_book(self): if ret['status'] == "OK" and "entries" in ret: address_book = ret["entries"] self.ui.wallet_info.wallet_address_book = address_book - + self.on_load_address_book_completed_event.emit( json.dumps(self.ui.wallet_info.wallet_address_book) ) - + @Slot(int) def delete_address_book(self, index): result = QMessageBox.question(self.ui, "Delete Address Book Entry", \ @@ -453,8 +453,8 @@ def delete_address_book(self, index): address_book.pop(i) break self.load_address_book() - - + + def _find_tx(self, transfers, tx_id, bc_height): if transfers["status"] == "OK": if "out" in transfers: @@ -464,7 +464,7 @@ def _find_tx(self, transfers, tx_id, bc_height): tx["status"] = "out" tx["confirmation"] = bc_height - tx["height"] if bc_height > tx["height"] else 0 return tx - + if "in" in transfers: for tx in transfers["in"]: if tx['txid'] == tx_id: @@ -472,7 +472,7 @@ def _find_tx(self, transfers, tx_id, bc_height): tx["status"] = "in" tx["confirmation"] = bc_height - tx["height"] if bc_height > tx["height"] else 0 return tx - + if "pending" in transfers: for tx in transfers["pending"]: if tx['txid'] == tx_id: @@ -480,7 +480,7 @@ def _find_tx(self, transfers, tx_id, bc_height): tx["status"] = "pool" tx["confirmation"] = 0 return tx - + if "pool" in transfers: for tx in transfers["pool"]: if tx['txid'] == tx_id: @@ -489,23 +489,23 @@ def _find_tx(self, transfers, tx_id, bc_height): tx["confirmation"] = 0 return tx return None - + @Slot(int, str) def view_tx_detail(self, height, tx_id): if height > 0: transfers = self.ui.wallet_rpc_manager.rpc_request.get_transfers(filter_by_height=True, min_height=height-1, max_height=height) else: transfers = self.ui.wallet_rpc_manager.rpc_request.get_transfers(tx_pending=True, tx_in_pool=True) - + tx = self._find_tx(transfers, tx_id, self.ui.target_height) if not tx: self.on_tx_detail_found_event.emit('{"status": "ERROR"}') QMessageBox.warning(self.ui, "Transaction Details", "Transaction id: %s

not found!" % tx_id) return - + self.on_tx_detail_found_event.emit( json.dumps(tx) ) - - + + @Slot(int) def load_tx_history(self, current_page=0): if current_page <= 0: current_page = 1 @@ -516,21 +516,21 @@ def load_tx_history(self, current_page=0): for tx in txs: tx["confirmation"] = self.ui.target_height - tx["height"] \ if self.ui.target_height > tx["height"] and tx["height"] > 0 else 0 - + num_of_pages = int(len(all_txs) + txs_per_page - 1)/txs_per_page pagination_slots = 10 start_page = (int(current_page - 1)/pagination_slots)*pagination_slots + 1 end_page = start_page + (pagination_slots - 1) \ if start_page + (pagination_slots - 1) < num_of_pages else num_of_pages ret = {"txs": txs, - "current_page": current_page, + "current_page": current_page, "num_of_pages": num_of_pages, "start_page": start_page, "end_page": end_page} - + self.on_load_tx_history_completed_event.emit( json.dumps(ret) ); - - + + @Slot() def open_new_wallet(self): result = QMessageBox.question(self.ui, "Open/Create New Wallet", \ @@ -538,19 +538,19 @@ def open_new_wallet(self): QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.No) if result == QMessageBox.No: return - + wallet_password, result = self._custom_input_dialog(self.ui, \ "Wallet Password", "Please enter wallet password:", QLineEdit.Password) if not result: return - + if hashlib.sha256(wallet_password).hexdigest() != self.ui.wallet_info.wallet_password: QMessageBox.warning(self.ui, "Incorrect Wallet Password", "Wallet password is not correct!") return - + self.ui.show_new_wallet_ui() - - + + @Slot(str) def view_wallet_key(self, key_type): wallet_password, result = self._custom_input_dialog(self.ui, \ @@ -558,12 +558,12 @@ def view_wallet_key(self, key_type): if not result: self.on_view_wallet_key_completed_event.emit("", ""); return - + if hashlib.sha256(wallet_password).hexdigest() != self.ui.wallet_info.wallet_password: self.on_view_wallet_key_completed_event.emit("", ""); QMessageBox.warning(self.ui, "Incorrect Wallet Password", "Wallet password is not correct!") return - + ret = self.ui.wallet_rpc_manager.rpc_request.query_key(key_type) title = "Wallet Key" if key_type == "mnemonic": @@ -572,66 +572,66 @@ def view_wallet_key(self, key_type): title = "Wallet view-key" if key_type == "spend_key": title = "Wallet spend-key" - + self.on_view_wallet_key_completed_event.emit(title, ret) - - + + @Slot(int) def set_daemon_log_level(self, level): # save log level self.ui.app_settings.settings['daemon']['log_level'] = level self.ui.app_settings.save() - - + + @Slot(int) def set_block_sync_size(self, sync_size): self.ui.app_settings.settings['daemon']['block_sync_size'] = sync_size self.ui.app_settings.save() - + @Slot() def load_app_settings(self): self.on_load_app_settings_completed_event.emit( json.dumps(self.ui.app_settings.settings) ) - + @Slot() def about_app(self): self.ui.about() - + @Slot(str) def open_link(self, link): webbrowser.open(link) - + @Slot() def restart_daemon(self): self.app_process_events(1) - self.ui.sumokoind_daemon_manager.stop() + self.ui.omrbredd_daemon_manager.stop() self.ui.start_deamon() self.app_process_events(5) self.on_restart_daemon_completed_event.emit() - - + + @Slot() def view_daemon_log(self): - log_file = os.path.join(DATA_DIR, 'logs', "sumokoind.log") + log_file = os.path.join(DATA_DIR, 'logs', "ombred.log") log_dialog = LogViewer(parent=self.ui, log_file=log_file) log_dialog.load_log() - - @Slot() + + @Slot() def paste_seed_words(self): text = QApplication.clipboard().text() self.on_paste_seed_words_event.emit(text) - - + + def update_daemon_status(self, status): self.on_daemon_update_status_event.emit(status) - - + + def app_process_events(self, seconds=1): for _ in range(int(seconds*10)): self.app.processEvents() sleep(.1) - - - + + + def _detail_error_msg(self, title, error_text, error_detailed_text): msg = QMessageBox(self.new_wallet_ui) msg.setWindowTitle(title) @@ -641,22 +641,22 @@ def _detail_error_msg(self, title, error_text, error_detailed_text): msg.setIcon(QMessageBox.Critical) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() - - - def _custom_input_dialog(self, ui, title, label, - text_echo_mode=QLineEdit.Normal, + + + def _custom_input_dialog(self, ui, title, label, + text_echo_mode=QLineEdit.Normal, input_mode=QInputDialog.TextInput): - dlg = QInputDialog(ui) + dlg = QInputDialog(ui) dlg.setTextEchoMode(text_echo_mode) dlg.setInputMode(input_mode) dlg.setWindowTitle(title) - dlg.setLabelText(label) - dlg.resize(250, 100) - result = dlg.exec_() + dlg.setLabelText(label) + dlg.resize(250, 100) + result = dlg.exec_() text = dlg.textValue() return (text, result) - - + + def _show_wallet_info(self): wallet_rpc_request = self.ui.wallet_rpc_manager.rpc_request wallet_info = {} @@ -666,15 +666,15 @@ def _show_wallet_info(self): balance, unlocked_balance, _ = wallet_rpc_request.get_balance() wallet_info['balance'] = print_money(balance) wallet_info['unlocked_balance'] = print_money(unlocked_balance) - + self.on_new_wallet_show_info_event.emit(json.dumps(wallet_info)) - - + + on_new_wallet_show_info_event = Signal(str) on_new_wallet_show_progress_event = Signal(str) on_new_wallet_ui_reset_event = Signal() on_new_wallet_update_processed_block_height_event = Signal(str) - + on_daemon_update_status_event = Signal(str) on_main_wallet_ui_reset_event = Signal() on_wallet_update_info_event = Signal(str) @@ -689,4 +689,4 @@ def _show_wallet_info(self): on_load_app_settings_completed_event = Signal(str) on_restart_daemon_completed_event = Signal() on_paste_seed_words_event = Signal(str) - + \ No newline at end of file diff --git a/html/index.py b/html/index.py index 8fbcc6e..a30e5af 100644 --- a/html/index.py +++ b/html/index.py @@ -17,11 +17,11 @@ -moz-box-sizing: border-box; box-sizing: border-box; } - + body { -webkit-user-select: none; user-select: none; - + cursor: default; background-color: #fff; color: #76A500; @@ -34,21 +34,21 @@ height: 100%; overflow: hidden; } - + a, a:hover, a:active, a:focus { text-decoration: none; outline: 0; cursor: default; } - + a, a:active, a:focus{ color: #337AB7; } - + a:hover{ color: #fff; } - + .nav-tabs{ /*width: 760px;*/ } @@ -57,176 +57,176 @@ text-align: center; font-size: 120% } - + .container{ width: 760px; padding: 0; margin: 5px 0px 5px 20px; } - + h3{ text-align: center; margin-bottom: 1em; font-size: 180%; } - - + + .tab-content{ font-size: 12px; } - + .tab-content h3{ margin-top: 0; } - + #balance_tab h4, #balance_tab h5{ color: #76A500; } - + #balance_tab h5 span{ color: #ccc; } - + #settings_tab h3{ margin-top: 20px; margin-bottom: 30px; } - + .syncing{ font-size: 60%; } - - .tab-content .tab-pane { + + .tab-content .tab-pane { position: relative; } - + .form-horizontal .control-label{ text-align: left; } - - + + .progress{ height: 22px; text-align: center; background: #ddd; } - + #progress_bar_text_high{ - font-size: 90%; + font-size: 90%; display: none; } - + #progress_bar_text_low{ font-size: 80%; color: #c7254e; } - + .control-label{ font-weight: bold; } - + .tx-list{ color: #666; margin-right: 20px; font-weight: bold; } - + .tx-list a{ cursor: pointer; } - + .tx-list.tx-out, .tx-list.tx-in, .tx-list.tx-pool, .tx-list.tx-pending, .tx-list.tx-out a, .tx-list.tx-out a:active, .tx-list.tx-out a:focus{ color: #c7254e; margin-bottom: 0; } - + .tx-list.tx-in, .tx-list.tx-in a, .tx-list.tx-in a:active, .tx-list.tx-in a:focus{ color: green; } - + .tx-list.tx-pool, .tx-list.tx-pending, .tx-list.tx-pending a, .tx-list.tx-pending a:active, .tx-list.tx-pending a:focus, .tx-list.tx-pool a, .tx-list.tx-pool a:active, .tx-list.tx-pool a:focus{ color: orange; } - + .tx-list a:hover{ color: #337AB7; } - + .tx-list.txid{ color: inherit; } - + .tx-list.tx-payment-id{ font-weight: normal; } - + .tx-fee-hide, .tx-note-hide, .tx-destinations-hide{ display: none; } - + .tx-list.tx-lock{ color: #666; } - + .modal-progress-text{ color: #333; font-size: 90%; font-weight: bold; margin-left: 10px; } - + #form_receive input, #form_send_tx input, #form_send_tx select{ font-size: 14px; } - + .btn-sm{ border-radius: 0; } - + table { border-spacing: 0; border-collapse: collapse; font-size: 12px; } - + table thead tr{ height: 3em; } - + table tbody tr { color: #aaa; height: 3em; line-height: 1.6em; } - + table thead tr th{ text-align: left; text-size: 18px; padding: auto 1em; } - + table tbody tr td a:hover{ color: #666; cursor: pointer; } - + .address-book-row{ cursor: pointer; } - + #address-book-box{ max-height: 450px; } - + #address-book-box table{ width: 100%; } - + #address-book-box table thead { display: inline-block; width: 100%; } - + #address-book-box table tbody { border-top: none; max-height: 300px; @@ -234,7 +234,7 @@ width: 100%; overflow: auto; } - + #address-book-box table tbody::-webkit-scrollbar-track, .tx-destinations::-webkit-scrollbar-track { @@ -242,8 +242,8 @@ background-color: #F5F5F5; border-radius: 6px; } - - + + #address-book-box table tbody::-webkit-scrollbar, .tx-destinations::-webkit-scrollbar { @@ -251,11 +251,11 @@ background-color: #F5F5F5; border-radius: 6px; } - + .tx-destinations::-webkit-scrollbar{ height: 8px; } - + #address-book-box table tbody::-webkit-scrollbar-thumb, .tx-destinations::-webkit-scrollbar-thumb { @@ -263,35 +263,35 @@ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); background-color: #5BB0F7; } - + .tx-destinations { width: 100%; max-height: 200px; overflow: auto; font-size: 90%; } - + .wallet-settings{ text-align: center; } - + .wallet-settings button{ margin-left: 20px; } - + .form-control.address-box{ font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 85%; color: #000; } - + textarea{ border:none; width:100%; resize:none; font-weight:bold; } - + .panel-default>.panel-heading { color: #666; background-color: #eee; @@ -302,12 +302,12 @@ -ms-user-select: none; user-select: none; } - + .panel-default>.panel-heading a { display: block; padding: 10px 15px; } - + .panel-default>.panel-heading a:after { font-family:'Glyphicons Halflings'; content: ""; @@ -323,62 +323,62 @@ transition: transform .25s linear; -webkit-transition: -webkit-transform .25s linear; } - + .panel-default>.panel-heading a[aria-expanded="true"] { background-color: #2196F3; color: #fff; font-weight: bold; } - + .panel-default>.panel-heading a[aria-expanded="false"] { color: #666; } - + .panel-default>.panel-heading a[aria-expanded="true"]:after { content:"\e114"; - + } - + .panel-default>.panel-heading a[aria-expanded="false"]:after { content:"\e080"; } - + .panel-default > .panel-heading + .panel-collapse > .panel-body { height: 295px; overflow: auto; } - - + + .panel-default > .panel-heading + .panel-collapse > .panel-body::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); border-radius: 6px; background-color: #F5F5F5; } - - + + .panel-default > .panel-heading + .panel-collapse > .panel-body::-webkit-scrollbar { width: 8px; background-color: #F5F5F5; } - + .panel-default > .panel-heading + .panel-collapse > .panel-body::-webkit-scrollbar-thumb { border-radius: 6px; -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); background-color: #5BB0F7; } - + - + - + - + - + - + - + - + -""" +""" \ No newline at end of file diff --git a/html/newwallet.py b/html/newwallet.py index 788b727..b09d820 100644 --- a/html/newwallet.py +++ b/html/newwallet.py @@ -272,7 +272,7 @@
- +
diff --git a/main.py b/main.py index 9693434..ebc4711 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- ## Copyright (c) 2017, The Sumokoin Project (www.sumokoin.org) -## Copyright (c) 2018, The OMBRE Project - -import os +''' +App main function +''' import sys, os, hashlib from PySide import QtCore @@ -17,6 +17,35 @@ from app.hub import Hub from webui import MainWebUI +file_hashes = [ + ('www/scripts/jquery-1.9.1.min.js', 'c12f6098e641aaca96c60215800f18f5671039aecf812217fab3c0d152f6adb4'), + ('www/scripts/bootstrap.min.js', '2979f9a6e32fc42c3e7406339ee9fe76b31d1b52059776a02b4a7fa6a4fd280a'), + ('www/scripts/mustache.min.js', '3258bb61f5b69f33076dd0c91e13ddd2c7fe771882adff9345e90d4ab7c32426'), + ('www/scripts/jquery.qrcode.min.js', 'f4ccf02b69092819ac24575c717a080c3b6c6d6161f1b8d82bf0bb523075032d'), + ('www/scripts/utils.js', 'd0c6870ed19c92cd123c7443cb202c7629f9cd6807daed698485fda25214bdb4'), + + ('www/css/bootstrap.min.css', '9d517cad6f1744ab5eba382ccf0f53969f7d326e1336a6c2771e82830bc2c5ac'), + ('www/css/font-awesome.min.css', 'b8b02026a298258ce5069d7b6723c2034058d99220b6612b54bc0c5bf774dcfb'), + + ('www/css/fonts/fontawesome-webfont.ttf', '7b5a4320fba0d4c8f79327645b4b9cc875a2ec617a557e849b813918eb733499'), + ('www/css/fonts/glyphicons-halflings-regular.ttf', 'e395044093757d82afcb138957d06a1ea9361bdcf0b442d06a18a8051af57456'), + ('www/css/fonts/RoboReg.ttf', 'dc66a0e6527b9e41f390f157a30f96caed33c68d5db0efc6864b4f06d3a41a50'), + ] + +def _check_file_integrity(app): + ''' Check file integrity to make sure all resources loaded + to webview won't be modified by an unknown party ''' + for file_name, file_hash in file_hashes: + file_path = os.path.normpath(os.path.join(app.property("ResPath"), file_name)) + if not os.path.exists(file_path): + return False + data = readFile(file_path) +# print( file_path, hashlib.sha256(data).hexdigest() ) + if hashlib.sha256(data).hexdigest() != file_hash: + return False + + return True + def main(): if getattr(sys, "frozen", False) and sys.platform in ['win32','cygwin','win64']: @@ -27,19 +56,16 @@ def main(): sys.__stdout__ = DummyStream() sys.__stderr__ = DummyStream() sys.__stdin__ = DummyStream() - + # Get application path - - abspath = os.path.abspath(__file__) - app_path = os.path.dirname(abspath) - #os.chdir(app_path) + app_path = getAppPath() if sys.platform == 'darwin' and hasattr(sys, 'frozen'): resources_path = os.path.normpath(os.path.abspath(os.path.join(app_path, "..", "Resources"))) else: resources_path = os.path.normpath(os.path.abspath(os.path.join(app_path, "Resources"))) - + # Application setup - + app = QSingleApplication(sys.argv) app.setOrganizationName('Ombre') app.setOrganizationDomain('www.ombre.io') @@ -48,10 +74,15 @@ def main(): app.setProperty("ResPath", resources_path) if sys.platform == 'darwin': app.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus) - - hub = Hub(app=app) - ui = MainWebUI(app=app, hub=hub, debug=False) - hub.setUI(ui) - app.singleStart(ui) - - sys.exit(app.exec_()) + + if not _check_file_integrity(app): + QMessageBox.critical(None, "Application Fatal Error", """File integrity check failed! +

This could be a result of unknown (maybe, malicious) action
to wallet code files.""") + app.quit() + else: + hub = Hub(app=app) + ui = MainWebUI(app=app, hub=hub, debug=False) + hub.setUI(ui) + app.singleStart(ui) + + sys.exit(app.exec_()) \ No newline at end of file diff --git a/manager/ProcessManager.py b/manager/ProcessManager.py index cd1ca18..c139773 100644 --- a/manager/ProcessManager.py +++ b/manager/ProcessManager.py @@ -1,6 +1,5 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -## Copyright (c) 2018, The OMBRE Project ## Copyright (c) 2017, The Sumokoin Project (www.sumokoin.org) from __future__ import print_function @@ -32,25 +31,25 @@ def __init__(self, proc_args, proc_name=""): Thread.__init__(self) args_array = proc_args.encode( sys.getfilesystemencoding() ).split(u' ') self.proc = Popen(args_array, - shell=False, - stdout=PIPE, stderr=STDOUT, stdin=PIPE, + shell=False, + stdout=PIPE, stderr=STDOUT, stdin=PIPE, creationflags=CREATE_NO_WINDOW) self.proc_name = proc_name self.daemon = True log("[%s] started" % proc_name, LEVEL_INFO, self.proc_name) - + def run(self): for line in iter(self.proc.stdout.readline, b''): log(">>> " + line.rstrip(), LEVEL_DEBUG, self.proc_name) - + if not self.proc.stdout.closed: self.proc.stdout.close() - + def send_command(self, cmd): self.proc.stdin.write( (cmd + u"\n").encode("utf-8") ) sleep(0.1) - - + + def stop(self): if self.is_proc_running(): self.send_command('exit') @@ -73,18 +72,18 @@ def stop(self): else: break log("[%s] stopped" % self.proc_name, LEVEL_INFO, self.proc_name) - + def is_proc_running(self): return (self.proc.poll() is None) + - -class SumokoindManager(ProcessManager): +class OmbredManager(ProcessManager): def __init__(self, resources_path, log_level=0, block_sync_size=10): proc_args = u'%s/bin/ombred --log-level %d --block-sync-size %d' % (resources_path, log_level, block_sync_size) ProcessManager.__init__(self, proc_args, "ombred") self.synced = Event() self.stopped = Event() - + def run(self): # synced_str = "You are now synchronized with the network" err_str = "ERROR" @@ -97,26 +96,26 @@ def run(self): log("[%s]>>> %s" % (self.proc_name, line.rstrip()), LEVEL_ERROR, self.proc_name) else: log("[%s]>>> %s" % (self.proc_name, line.rstrip()), LEVEL_INFO, self.proc_name) - + if not self.proc.stdout.closed: self.proc.stdout.close() - + self.stopped.set() class WalletCliManager(ProcessManager): fail_to_connect_str = "wallet failed to connect to daemon" - + def __init__(self, resources_path, wallet_file_path, wallet_log_path, restore_wallet=False): if not restore_wallet: wallet_args = u'%s/bin/ombre-wallet-cli --generate-new-wallet=%s --log-file=%s' \ % (resources_path, wallet_file_path, wallet_log_path) else: - wallet_args = u'%s/bin/ombre-wallet-cli --log-file=%s --restore-deterministic-wallet' \ + wallet_args = u'%s/bin/ombre-wallet-cli --log-file=%s --daemon-port 19744 --restore-deterministic-wallet' \ % (resources_path, wallet_log_path) ProcessManager.__init__(self, wallet_args, "ombre-wallet-cli") self.ready = Event() self.last_error = "" - + def run(self): is_ready_str = "Background refresh thread started" err_str = "Error:" @@ -129,20 +128,20 @@ def run(self): log("[%s]>>> %s" % (self.proc_name, line.rstrip()), LEVEL_ERROR, self.proc_name) # else: # log("[%s]>>> %s" % (self.proc_name, line.rstrip()), LEVEL_DEBUG, self.proc_name) - + if not self.proc.stdout.closed: self.proc.stdout.close() - + def is_ready(self): return self.ready.is_set() - - + + def is_connected(self): self.send_command("refresh") if self.fail_to_connect_str in self.last_error: return False return True - + class WalletRPCManager(ProcessManager): @@ -151,24 +150,24 @@ def __init__(self, resources_path, wallet_file_path, wallet_password, app, log_l wallet_log_path = os.path.join(os.path.dirname(wallet_file_path), "ombre-wallet-rpc.log") wallet_rpc_args = u'%s/bin/ombre-wallet-rpc --wallet-file %s --log-file %s --rpc-bind-port 19745 --user-agent %s --log-level %d' \ % (resources_path, wallet_file_path, wallet_log_path, self.user_agent, log_level) - + ProcessManager.__init__(self, wallet_rpc_args, "ombre-wallet-rpc") sleep(0.2) self.send_command(wallet_password) - + self.rpc_request = WalletRPCRequest(app, self.user_agent) # self.rpc_request.start() self.ready = False self.block_hex = None self.block_height = 0 - self.is_password_invalid = Event() - + self.is_password_invalid = Event() + def run(self): is_ready_str = "Run net_service loop" err_str = "ERROR" invalid_password = "invalid password" height_regex = re.compile(r"Processed block: \<([a-z0-9]+)\>, height (\d+)") - + for line in iter(self.proc.stdout.readline, b''): if not self.ready and is_ready_str in line: self.ready = True @@ -180,21 +179,21 @@ def run(self): self.is_password_invalid.set() else: log("[%s]>>> %s" % (self.proc_name, line.rstrip()), LEVEL_DEBUG, self.proc_name) - + m_height = height_regex.search(line) if m_height: self.block_hex = m_height.group(1) self.block_height = m_height.group(2) - + if not self.proc.stdout.closed: - self.proc.stdout.close() - + self.proc.stdout.close() + def is_ready(self): return self.ready - + def is_invalid_password(self): return self.is_password_invalid.is_set() - + def stop(self): self.rpc_request.stop_wallet() if self.is_proc_running(): @@ -211,5 +210,5 @@ def stop(self): else: break self.ready = False - log("[%s] stopped" % self.proc_name, LEVEL_INFO, self.proc_name) - + log("[%s] stopped" % self.proc_name, LEVEL_INFO, self.proc_name) + \ No newline at end of file diff --git a/rpc/__init__.py b/rpc/__init__.py index 0f0eb82..c656a3b 100644 --- a/rpc/__init__.py +++ b/rpc/__init__.py @@ -38,16 +38,16 @@ class RPCRequest(Thread): headers = {'content-type': 'application/json'} - + def __init__(self, rpc_input, url, app, user_agent=None): Thread.__init__(self) self.url = url self.rpc_input = rpc_input self.app = app - + if user_agent is not None: self.headers.update({"User-Agent": user_agent}) - + self.response_queue = Queue(1) self.daemon = True @@ -55,17 +55,17 @@ def __init__(self, rpc_input, url, app, user_agent=None): def run(self): res = self._send_request() self.response_queue.put(res) - - + + def stop(self): self.is_stopped = True - - + + def _send_request(self): global rpc_id rpc_id += 1 self.rpc_input.update({"jsonrpc": "2.0", "id": "%d" % rpc_id}) - + try: response = requests.post( self.url, @@ -91,14 +91,14 @@ def _send_request(self): break return res_json['error'] return res_json - - + + def get_result(self): while self.response_queue.empty(): self.app.processEvents() sleep(.1) return self.response_queue.get() - + class DaemonRPCRequest(): @@ -106,36 +106,36 @@ def __init__(self, app): self.port = 19744 self.url = "http://localhost:%d/json_rpc" % self.port self.app = app - + def send_request(self, rpc_input): req = RPCRequest(rpc_input, self.url, self.app) req.start() return req.get_result() - + def get_info(self): rpc_input = {"method": "get_info"} return self.send_request(rpc_input) - - + + class WalletRPCRequest(): def __init__(self, app, user_agent): self.port = 19745 self.url = "http://localhost:%d/json_rpc" % self.port self.app = app self.user_agent = user_agent - + def send_request(self, rpc_input): req = RPCRequest(rpc_input, self.url, self.app, self.user_agent) req.start() return req.get_result() - + def query_key(self, key_type="mnemonic"): rpc_input = {"method":"query_key", "params": {"key_type": key_type}} res = self.send_request(rpc_input) if res['status'] == 'OK': return res['key'] return res['status'] - + def get_address(self, account_index = 0): params = {"account_index": account_index} rpc_input = {"method": "getaddress", @@ -144,14 +144,14 @@ def get_address(self, account_index = 0): if res['status'] == 'OK': return res return res['status'] - + def create_address(self): rpc_input = {"method":"create_address"} res = self.send_request(rpc_input) if res['status'] == 'OK': return res return res['status'] - + def get_balance(self): rpc_input = {"method":"getbalance"} res = self.send_request(rpc_input) @@ -161,7 +161,7 @@ def get_balance(self): per_subaddress = res['per_subaddress'] return (res['balance'], res['unlocked_balance'], per_subaddress) return (0, 0) - + def get_transfers(self, filter_by_height=False, min_height=0, max_height=0, tx_in=True, tx_out=True, tx_pending=False, tx_in_pool=False): rpc_input = {"method":"get_transfers"} params = {} @@ -175,15 +175,15 @@ def get_transfers(self, filter_by_height=False, min_height=0, max_height=0, tx_i params["pool"] = tx_in_pool rpc_input["params"] = params return self.send_request(rpc_input) - + def rescan_spent(self): rpc_input = {"method": "rescan_spent"} return self.send_request(rpc_input) - + def rescan_bc(self): rpc_input = {"method": "rescan_blockchain"} return self.send_request(rpc_input) - + def transfer_split(self, amount, address, payment_id, priority, mixin): rpc_input = {"method": "transfer_split"} params = {"destinations": [{"amount" : amount, "address": address}], @@ -191,10 +191,10 @@ def transfer_split(self, amount, address, payment_id, priority, mixin): "mixin": mixin} if payment_id: params["payment_id"] = payment_id - + rpc_input["params"] = params return self.send_request(rpc_input) - + def transfer_all(self, address, payment_id, priority, mixin, account_index=0, subaddr_indices=[0]): rpc_input = {"method": "sweep_all"} params = { @@ -206,26 +206,26 @@ def transfer_all(self, address, payment_id, priority, mixin, account_index=0, su } if payment_id: params["payment_id"] = payment_id - + rpc_input["params"] = params return self.send_request(rpc_input) - + def set_tx_notes(self, txids, notes): rpc_input = {"method": "set_tx_notes"} params = {"txids": txids, "notes": notes} rpc_input["params"] = params return self.send_request(rpc_input) - + def make_integrated_address(self, payment_id): rpc_input = {"method": "make_integrated_address"} params = {"payment_id": payment_id} rpc_input["params"] = params return self.send_request(rpc_input) - + def get_address_book(self): rpc_input = {"method": "get_address_book"} return self.send_request(rpc_input) - + def add_address_book(self, address, payment_id, desc): rpc_input = {"method": "add_address_book"} params = {"address": address} @@ -235,14 +235,14 @@ def add_address_book(self, address, payment_id, desc): params["description"] = desc rpc_input["params"] = params return self.send_request(rpc_input) - + def delete_address_book(self, index): rpc_input = {"method": "delete_address_book"} params = {"index": index} rpc_input["params"] = params return self.send_request(rpc_input) - + def stop_wallet(self): rpc_input = {"method":"stop_wallet"} return self.send_request(rpc_input) - + \ No newline at end of file diff --git a/settings/__init__.py b/settings/__init__.py index 6324684..ebc14e2 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -## Copyright (c) 2017, The Sumokoin Project (www.sumokoin.org) +## Copyright (c) 2017-2018, The Sumokoin Project (www.sumokoin.org) ''' App top-level settings ''' @@ -14,24 +14,24 @@ from utils.common import getHomeDir, makeDir USER_AGENT = "Ombre GUI Wallet" -APP_NAME = "Ombre Wallet" -VERSION = [0, 0, 1] +APP_NAME = "Ombre GUI Wallet" +VERSION = [1, 0, 0] -_data_dir = makeDir(os.path.join(getHomeDir(), 'OmbreGUIWallet')) +_data_dir = makeDir(os.path.join(getHomeDir(), 'OmbreGUI')) DATA_DIR = _data_dir log_file = os.path.join(DATA_DIR, 'logs', 'app.log') # default logging file log_level = logging.DEBUG # logging level -seed_languages = [("0", "English"), - ("1", "Spanish"), - ("2", "German"), - ("3", "Italian"), +seed_languages = [("0", "English"), + ("1", "Spanish"), + ("2", "German"), + ("3", "Italian"), ("4", "Portuguese"), ("5", "Russian"), ("6", "Japanese"), ] # COIN - number of smallest units in one coin -COIN = 1000000000.0 +COIN = 1000000000.0 \ No newline at end of file diff --git a/utils/common.py b/utils/common.py index 16fcab7..bfbe954 100644 --- a/utils/common.py +++ b/utils/common.py @@ -11,7 +11,7 @@ from cStringIO import StringIO except ImportError: from StringIO import StringIO - + class DummyStream: ''' dummyStream behaves like a stream but does nothing. ''' @@ -20,7 +20,7 @@ def write(self,data): pass def read(self,data): pass def flush(self): pass def close(self): pass - + def getAppPath(): '''Get the path to this script no matter how it's run.''' #Determine if the application is a py/pyw or a frozen exe. @@ -36,7 +36,7 @@ def getAppPath(): dir_path = os.getcwdu() return dir_path - + def getHomeDir(): if sys.platform == 'win32': import winpaths @@ -52,7 +52,7 @@ def getSockDir(): else: homedir = os.path.expanduser("~") return homedir - + def makeDir(d): if not os.path.exists(d): os.makedirs(d) @@ -76,7 +76,7 @@ def readFile(path, offset=0, size=-1, xor_data=False): data = fd.read(size) fd.close() return _xorData(data) if xor_data else data - + def writeFile(path, buf, offset=0, xor_data=False): """Write specified block on file at the given offset""" if xor_data: @@ -100,4 +100,4 @@ def print_money2(amount): except: raise Exception("Error parsing amount. Money amount must be an integer.") return "%s" % ("{:,.9f}".format(amount/1000000000.)) - + \ No newline at end of file diff --git a/webui/__init__.py b/webui/__init__.py index 1790d66..1993c0b 100644 --- a/webui/__init__.py +++ b/webui/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -## Copyright (c) 2017, The Sumokoin Project (www.sumokoin.org) +## Copyright (c) 2017-2018, The Sumokoin Project (www.sumokoin.org) ''' App UIs ''' @@ -16,7 +16,7 @@ from PySide.QtGui import QApplication, QMainWindow, QIcon, \ QSystemTrayIcon, QMenu, QAction, QMessageBox, QFileDialog, \ QInputDialog, QLineEdit - + from PySide.QtCore import QObject, Slot, Signal import PySide.QtCore as qt_core @@ -28,7 +28,12 @@ from utils.logger import log, LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO from utils.common import readFile -from manager.ProcessManager import SumokoindManager, WalletRPCManager +from utils.notify import Notify +MSG_TYPE_INFO = 1 +MSG_TYPE_WARNING = 2 +MSG_TYPE_CRITICAL = 3 + +from manager.ProcessManager import OmbredManager, WalletRPCManager from rpc import RPCRequest, DaemonRPCRequest from classes import AppSettings, WalletInfo @@ -38,6 +43,7 @@ TIMER2_INTERVAL = 60000 MAX_NEW_SUBADDRESSES = 10 +tray_icon_tooltip = "%s v%d.%d.%s" % (APP_NAME, VERSION[0], VERSION[1], VERSION[2]) log_text_tmpl = """ @@ -66,10 +72,10 @@ def __init__(self, parent, log_file): self.view.setCursor(qt_core.Qt.ArrowCursor) self.view.setZoomFactor(1) self.setCentralWidget(self.view) - + self.log_file = log_file self.setWindowTitle("%s - Log view [%s]" % (APP_NAME, os.path.basename(log_file))) - + def load_log(self): if not os.path.exists(self.log_file): _text = "[No logs]" @@ -92,50 +98,50 @@ def __init__(self, html, app, hub, window_size, debug=False): self.html = html self.url = "file:///" \ + os.path.join(self.app.property("ResPath"), "www/index.html").replace('\\', '/') - - + + self.is_first_load = True self.view = web_core.QWebView(self) - + if not self.debug: self.view.setContextMenuPolicy(qt_core.Qt.NoContextMenu) - + self.view.setCursor(qt_core.Qt.ArrowCursor) self.view.setZoomFactor(1) - + self.setWindowTitle(APP_NAME) - self.icon = self._getQIcon('logo_black_64.png') + self.icon = self._getQIcon('ombre_icon_64.png') self.setWindowIcon(self.icon) - + self.setCentralWidget(self.view) self.setFixedSize(window_size) self.center() - + if sys.platform == 'win32': psutil.Process().nice(psutil.HIGH_PRIORITY_CLASS) - - + + def run(self): self.view.loadFinished.connect(self.load_finished) # self.view.load(qt_core.QUrl(self.url)) self.view.setHtml(self.html, qt_core.QUrl(self.url)) - - + + def center(self): frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) - + def load_finished(self): - #This is the actual context/frame a webpage is running in. + #This is the actual context/frame a webpage is running in. # Other frames could include iframes or such. main_page = self.view.page() main_frame = main_page.mainFrame() # ATTENTION here's the magic that sets a bridge between Python to HTML main_frame.addToJavaScriptWindowObject("app_hub", self.hub) - + if self.is_first_load: ## Avoid re-settings on page reload (if happened) change_setting = main_page.settings().setAttribute settings = web_core.QWebSettings @@ -145,23 +151,23 @@ def load_finished(self): change_setting(settings.OfflineWebApplicationCacheEnabled, True) change_setting(settings.JavascriptCanOpenWindows, True) change_setting(settings.PluginsEnabled, False) - + # Show web inspector if debug on if self.debug: self.inspector = web_core.QWebInspector() self.inspector.setPage(self.view.page()) self.inspector.show() - + self.is_first_load = False - + #Tell the HTML side, we are open for business main_frame.evaluateJavaScript("app_ready()") - + def _getQIcon(self, icon_file): return QIcon(os.path.join(self.app.property("ResPath"), 'icons', icon_file)) - + class NewWalletWebUI(BaseWebUI): def __init__(self, app, hub, debug): window_size = qt_core.QSize(810, 560) @@ -169,119 +175,181 @@ def __init__(self, app, hub, debug): self.setWindowFlags(qt_core.Qt.FramelessWindowHint) self.show() - + class MainWebUI(BaseWebUI): def __init__(self, app, hub, debug): window_size = qt_core.QSize(800, 600) BaseWebUI.__init__(self, index.html, app, hub, window_size, debug) self.agent = '%s v.%s' % (USER_AGENT, '.'.join(str(v) for v in VERSION)) log("Starting [%s]..." % self.agent, LEVEL_INFO) - + + # Setup the system tray icon + if sys.platform == 'darwin': + tray_icon = 'ombre_16x16_mac.png' + elif sys.platform == "win32": + tray_icon = 'ombre_16x16.png' + else: + tray_icon = 'ombre_32x32_ubuntu.png' + + self.trayIcon = QSystemTrayIcon(self._getQIcon(tray_icon)) + self.trayIcon.setToolTip(tray_icon_tooltip) + self.app = app self.debug = debug self.hub = hub - + self.app.aboutToQuit.connect(self._handleAboutToQuit) - - self.sumokoind_daemon_manager = None + + self.ombred_daemon_manager = None self.wallet_cli_manager = None self.wallet_rpc_manager = None - + self.new_wallet_ui = None - + self.wallet_info = WalletInfo(app) - + # load app settings self.app_settings = AppSettings() self.app_settings.load() - + ## Blockchain height self.target_height = self.app_settings.settings['blockchain']['height'] self.current_height = 0 - - - - + + # Setup the tray icon context menu + self.trayMenu = QMenu() + + self.showAppAction = QAction('&Show %s' % APP_NAME, self) + f = self.showAppAction.font() + f.setBold(True) + self.showAppAction.setFont(f) + self.trayMenu.addAction(self.showAppAction) + + + self.aboutAction = QAction('&About...', self) + self.trayMenu.addAction(self.aboutAction) + + self.trayMenu.addSeparator() + self.exitAction = QAction('&Exit', self) + self.trayMenu.addAction(self.exitAction) + # Add menu to tray icon + self.trayIcon.setContextMenu(self.trayMenu) + + # connect signals + self.trayIcon.activated.connect(self._handleTrayIconActivate) + self.exitAction.triggered.connect(self.handleExitAction) + self.aboutAction.triggered.connect(self.handleAboutAction) + self.showAppAction.triggered.connect(self._handleShowAppAction) + self.app.aboutToQuit.connect(self._handleAboutToQuit) + + # Setup notification support + self.system_tray_running_notified = False + self.notifier = Notify(APP_NAME) + self.trayIcon.show() + + + def closeEvent(self, event): + """ Override QT close event + """ + event.ignore() + self.hide() + if not self.system_tray_running_notified: + self.notify("%s is still running at system tray." % APP_NAME, + "Running Status") + self.system_tray_running_notified = True + def run(self): self.view.loadFinished.connect(self.load_finished) # self.view.load(qt_core.QUrl(self.url)) self.view.setHtml(self.html, qt_core.QUrl(self.url)) - + self.start_deamon() self.daemon_rpc_request = DaemonRPCRequest(self.app) - + self.timer = QTimer(self) self.timer.timeout.connect(self._update_daemon_status) self.timer.start(10000) - + QTimer.singleShot(1000, self._load_wallet) QTimer.singleShot(2000, self._update_daemon_status) - - + + def start_deamon(self): - #start sumokoind daemon - self.sumokoind_daemon_manager = SumokoindManager(self.app.property("ResPath"), - self.app_settings.settings['daemon']['log_level'], + #start ombred daemon + self.ombred_daemon_manager = OmbredManager(self.app.property("ResPath"), + self.app_settings.settings['daemon']['log_level'], self.app_settings.settings['daemon']['block_sync_size']) - - self.sumokoind_daemon_manager.start() - - + + self.ombred_daemon_manager.start() + + def show_wallet(self): QTimer.singleShot(1000, self.update_wallet_info) if not hasattr(self, "timer2"): self.timer2 = QTimer(self) self.timer2.timeout.connect(self.update_wallet_info) - + self.timer2.stop() self.timer2.start(TIMER2_INTERVAL) self.show() - - + self.trayIcon.show() + + + def hide_wallet(self): + self.hide() + self.trayIcon.hide() + def run_wallet_rpc(self, wallet_password, log_level=0): # first, try to stop any wallet RPC server running try: RPCRequest('wallet_rpc').send_request({"method":"stop_wallet"}) except: pass - + while True: self.hub.app_process_events() - sumokoind_info = self.daemon_rpc_request.get_info() - if sumokoind_info['status'] == "OK": + ombred_info = self.daemon_rpc_request.get_info() + if ombred_info['status'] == "OK": self.wallet_rpc_manager = WalletRPCManager(self.app.property("ResPath"), \ self.wallet_info.wallet_filepath, \ wallet_password, \ self.app, log_level) self.wallet_rpc_manager.start() break - + def _update_daemon_status(self): target_height = 0 - sumokoind_info = self.daemon_rpc_request.get_info() - if sumokoind_info['status'] == "OK": + ombred_info = self.daemon_rpc_request.get_info() + if ombred_info['status'] == "OK": status = "Connected" - self.current_height = int(sumokoind_info['height']) - target_height = int(sumokoind_info['target_height']) + self.current_height = int(ombred_info['height']) + target_height = int(ombred_info['target_height']) if target_height == 0 or target_height < self.current_height: target_height = self.current_height if self.target_height < target_height: self.target_height = target_height; else: - status = sumokoind_info['status'] - - info = {"status": status, - "current_height": self.current_height, + status = ombred_info['status'] + + info = {"status": status, + "current_height": self.current_height, "target_height": self.target_height, } - + self.hub.update_daemon_status(json.dumps(info)) - - + + sync_status = "Disconnected" if ombred_info['status'] != "OK" else "Synchronizing..." + if ombred_info['status'] == "OK" and self.current_height == self.target_height: + sync_status = "Network synchronized" + + self.trayIcon.setToolTip("%s\n%s (%d/%d)" % (tray_icon_tooltip, sync_status, + self.current_height, self.target_height)) + + def update_wallet_info(self): if not self.wallet_rpc_manager.is_ready(): return - + wallet_info = {} try: balance, unlocked_balance, per_subaddress = self.wallet_rpc_manager.rpc_request.get_balance() @@ -301,19 +369,19 @@ def update_wallet_info(self): tx["status"] = "out" txs.append(tx) self.app.processEvents() - + if "in" in transfers: for tx in transfers["in"]: tx["direction"] = "in" tx["status"] = "in" txs.append(tx) self.app.processEvents() - + sorted_txs = sorted(txs, key=lambda k: k['height']) self.wallet_info.add_transfers(sorted_txs) self.wallet_info.bc_height = self.current_height - - pending_transfers = self.wallet_rpc_manager.rpc_request.get_transfers(tx_pending=True, tx_in_pool=True) + + pending_transfers = self.wallet_rpc_manager.rpc_request.get_transfers(tx_pending=True, tx_in_pool=True) pending_txs = [] if pending_transfers["status"] == "OK": if "pending" in pending_transfers: @@ -323,7 +391,7 @@ def update_wallet_info(self): tx["confirmation"] = 0 pending_txs.append(tx) self.app.processEvents() - + if "pool" in pending_transfers: for tx in pending_transfers["pool"]: tx["direction"] = "in" @@ -331,21 +399,21 @@ def update_wallet_info(self): tx["confirmation"] = 0 pending_txs.append(tx) self.app.processEvents() - - self.wallet_info.wallet_pending_transfers = sorted(pending_txs, - key=lambda k: k['timestamp'], + + self.wallet_info.wallet_pending_transfers = sorted(pending_txs, + key=lambda k: k['timestamp'], reverse=True) - + if len(self.wallet_info.wallet_pending_transfers) > 0: wallet_info["recent_txs"] = self.wallet_info.wallet_pending_transfers[:2] else: wallet_info["recent_txs"] = [] - + if len(wallet_info["recent_txs"]) < 2: for tx in self.wallet_info.wallet_transfers[:2 - len(wallet_info["recent_txs"])]: tx["confirmation"] = self.target_height - tx["height"] if self.target_height > tx["height"] else 0 wallet_info["recent_txs"].append(tx) - + adddress_info = self.wallet_rpc_manager.rpc_request.get_address() wallet_info['address'] = adddress_info['address'] wallet_info['used_subaddresses'] = [] @@ -364,11 +432,11 @@ def update_wallet_info(self): else: if subaddress['address_index'] > 0: wallet_info['new_subaddresses'].append(subaddress) - - wallet_info['used_subaddresses'] = sorted(wallet_info['used_subaddresses'], - key=lambda k:k['balance'], + + wallet_info['used_subaddresses'] = sorted(wallet_info['used_subaddresses'], + key=lambda k:k['balance'], reverse=True) - + # auto-generate new subaddresses if not enough available if len(wallet_info['new_subaddresses']) < MAX_NEW_SUBADDRESSES: for _ in range(MAX_NEW_SUBADDRESSES - len(wallet_info['new_subaddresses'])): @@ -376,29 +444,29 @@ def update_wallet_info(self): new_subaddress['label'] = "" new_subaddress['used'] = False wallet_info['new_subaddresses'].append(new_subaddress) - + if len(wallet_info['new_subaddresses']) > MAX_NEW_SUBADDRESSES: wallet_info['new_subaddresses'] = wallet_info['new_subaddresses'][0:MAX_NEW_SUBADDRESSES] - + # print(json.dumps(wallet_info, indent=4)) - + self.hub.on_wallet_update_info_event.emit(json.dumps(wallet_info)) except Exception, err: log(str(err), LEVEL_ERROR) - - + + def show_new_wallet_ui(self): self.reset_wallet(delete_files=False) - self.hide() + self.hide_wallet() self.new_wallet_ui = NewWalletWebUI(self.app, self.hub, self.debug) self.hub.setNewWalletUI(self.new_wallet_ui) self.new_wallet_ui.run() - - + + def reset_wallet(self, delete_files=True): if self.wallet_rpc_manager is not None: self.wallet_rpc_manager.stop() - + wallet_filepath = self.wallet_info.wallet_filepath if delete_files and wallet_filepath and os.path.exists(wallet_filepath): try: @@ -407,18 +475,39 @@ def reset_wallet(self, delete_files=True): os.remove(wallet_filepath + ".address.txt") except: pass - + self.wallet_info.reset() if self.new_wallet_ui: self.hub.on_new_wallet_ui_reset_event.emit() self.hub.on_main_wallet_ui_reset_event.emit() - + + def notify(self, message, title="", icon=None, msg_type=None): + if self.notifier.notifier is not None: + self.notifier.notify(title, message, icon) + else: + self.showMessage(message, title, msg_type) + + def showMessage(self, message, title="", msg_type=None, timeout=2000): + """Displays 'message' through the tray icon's showMessage function, + with title 'title'. 'type' is one of the enumerations of + 'common.MessageTypes'. + """ + if msg_type is None or msg_type == MSG_TYPE_INFO: + icon = QSystemTrayIcon.Information + + elif msg_type == MSG_TYPE_WARNING: + icon = QSystemTrayIcon.Warning + + elif msg_type == MSG_TYPE_CRITICAL: + icon = QSystemTrayIcon.Critical + + title = "%s - %s" % (APP_NAME, title) if title else APP_NAME + self.trayIcon.showMessage(title, message, icon, timeout) + def about(self): - pass - QMessageBox.about(self, "About", - "

Copyright© 2017 - Sumokoin Projects (www.sumokoin.org)
" + - "

Copyright© 2018 - Ombre Project (www.ombre.io)
") - + QMessageBox.about(self, "About", \ + u"%s

Copyright© 2017 -2018 - Sumokoin Projects (www.sumokoin.org)" % self.agent) + def _load_wallet(self): if self.wallet_info.load(): wallet_password = None @@ -435,13 +524,14 @@ def _load_wallet(self): "Password is required to open wallet!") else: break - + if not wallet_password: result = QMessageBox.question(self, "Create/Restore Wallet?", \ "Do you want to create (or restore to) a new wallet instead?", \ QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.No) if result == QMessageBox.No: - self.close() + self.trayIcon.hide() + QTimer.singleShot(250, self.app.quit) else: self.show_new_wallet_ui() return @@ -455,22 +545,47 @@ def _load_wallet(self): "Error: Wallet password is incorrect!

Please retry...") self._load_wallet() return - + self.wallet_info.wallet_password = hashlib.sha256(wallet_password).hexdigest() self.update_wallet_info() if not hasattr(self, "timer2"): self.timer2 = QTimer(self) self.timer2.timeout.connect(self.update_wallet_info) - + self.timer2.stop() self.timer2.start(TIMER2_INTERVAL) else: + self.hide_wallet() self.new_wallet_ui = NewWalletWebUI(self.app, self.hub, self.debug) self.hub.setNewWalletUI(self.new_wallet_ui) self.new_wallet_ui.run() - - + + def _handleTrayIconActivate(self, reason): + if reason == QSystemTrayIcon.DoubleClick: + self.showNormal() + self.activateWindow() + + def handleExitAction(self, show_confirmation=True): + reply = QMessageBox.No + if show_confirmation: + self._handleShowAppAction() + reply=QMessageBox.question(self,'Exit %s?' % APP_NAME, + "Are you sure to exit %s?" % APP_NAME, QMessageBox.Yes,QMessageBox.No) + if not show_confirmation or reply==QMessageBox.Yes: + self.trayIcon.hide() + QTimer.singleShot(250, self.app.quit) + + def _handleShowAppAction(self): + self.showNormal() + self.activateWindow() + + def handleAboutAction(self): + self._handleShowAppAction() + self.about() + + def _handleAboutToQuit(self): + self.hide_wallet() log("%s is about to quit..." % APP_NAME, LEVEL_INFO) if hasattr(self, "timer"): self.timer.stop() @@ -478,8 +593,8 @@ def _handleAboutToQuit(self): self.timer2.stop() if self.wallet_rpc_manager is not None: self.wallet_rpc_manager.stop() - if self.sumokoind_daemon_manager is not None: - self.sumokoind_daemon_manager.stop() - + if self.ombred_daemon_manager is not None: + self.ombred_daemon_manager.stop() + self.app_settings.settings['blockchain']['height'] = self.target_height self.app_settings.save()