@ -1,27 +1,54 @@
# Copyright (c) 2014 Luke Montalvo <lukemontalvo@gmail.com>
# This file adds a alternative commandline interface
#
# Dependencies:
# * from python2-pip
# * python2-pythondialog
# * dialog
import curses
import shared
import os
import sys
import StringIO
import time
from time import strftime , localtime
from threading import Timer
import curses
import dialog
from dialog import Dialog
import shared
import ConfigParser
from addresses import *
quit = False
menutab = 1
menu = [ " Inbox " , " Send " , " Sent " , " Your Identities " , " Subscriptions " , " Address Book " , " Blacklist " , " Network Status " ]
log = " "
logpad = None
inventorydata = 0
startuptime = time . time ( )
addresses = [ ]
addrcur = 0
addrcopy = 0
class printLog :
def write ( self , output ) :
global log
log + = output
def flush ( self ) :
pass
class errLog :
def write ( self , output ) :
global log
log + = " ! " + output
def flush ( self ) :
pass
printlog = printLog ( )
errlog = errLog ( )
def cpair ( a ) :
r = curses . color_pair ( a )
@ -42,18 +69,81 @@ def drawmenu(stdscr):
menustr + = " "
stdscr . addstr ( 2 , 5 , menustr , curses . A_UNDERLINE )
def resetlookups ( ) :
inventorydata = shared . numberOfInventoryLookupsPerformed
shared . numberOfInventoryLookupsPerformed = 0
Timer ( 2 , resetlookups , ( ) ) . start ( )
def drawtab ( stdscr ) :
if menutab in range ( 0 , len ( menu ) ) :
if menutab in range ( 1 , len ( menu ) + 1 ) :
if menutab == 1 : # Inbox
stdscr . addstr ( 3 , 5 , " new messages " )
pass
elif menutab == 2 : # Send
stdscr . addstr ( 3 , 5 , " to: from: " )
pass
elif menutab == 3 : # Sent
pass
elif menutab == 4 : # Identities
stdscr . addstr ( 3 , 5 , " Label " , curses . A_BOLD )
stdscr . addstr ( 3 , 50 , " Address " , curses . A_BOLD )
stdscr . addstr ( 3 , 100 , " Stream " , curses . A_BOLD )
for i , item in enumerate ( addresses ) :
a = 0
if i == addrcur :
a = curses . A_REVERSE
stdscr . addstr ( 3 + i , 5 , item [ 0 ] , cpair ( item [ 3 ] ) | a )
if i == addrcur : # Highlight current address
a = a | curses . A_REVERSE
if item [ 1 ] == True and item [ 3 ] not in [ 8 , 9 ] : # Embolden enabled, non-special addresses
a = a | curses . A_BOLD
stdscr . addstr ( 4 + i , 5 , item [ 0 ] , a )
stdscr . addstr ( 4 + i , 50 , item [ 2 ] , cpair ( item [ 3 ] ) | a )
stdscr . addstr ( 4 + i , 100 , str ( 1 ) , a )
elif menutab == 5 : # Subscriptions
pass
elif menutab == 6 : # Address book
pass
elif menutab == 7 : # Blacklist
pass
elif menutab == 8 : # Network status
# Connection data
stdscr . addstr ( 4 , 5 , " Total Connections: " + str ( len ( shared . connectedHostsList ) ) . ljust ( 2 ) )
stdscr . addstr ( 6 , 6 , " Stream # " , curses . A_BOLD )
stdscr . addstr ( 6 , 17 , " Connections " , curses . A_BOLD )
streamcount = [ ]
for host , stream in shared . connectedHostsList . items ( ) :
if stream > = len ( streamcount ) :
streamcount . append ( 1 )
else :
streamcount [ stream ] + = 1
for i , item in enumerate ( streamcount ) :
if i < 5 :
if i == 0 :
stdscr . addstr ( 7 + i , 6 , " ? " )
else :
stdscr . addstr ( 7 + i , 6 , str ( i ) )
stdscr . addstr ( 7 + i , 17 , str ( item ) )
# Uptime and processing data
stdscr . addstr ( 6 , 35 , " Since startup on " + unicode ( strftime ( shared . config . get ( ' bitmessagesettings ' , ' timeformat ' ) , localtime ( int ( startuptime ) ) ) ) )
stdscr . addstr ( 7 , 40 , " Processed " + str ( shared . numberOfMessagesProcessed ) . ljust ( 4 ) + " person-to-person messages. " )
stdscr . addstr ( 8 , 40 , " Processed " + str ( shared . numberOfBroadcastsProcessed ) . ljust ( 4 ) + " broadcast messages. " )
stdscr . addstr ( 9 , 40 , " Processed " + str ( shared . numberOfPubkeysProcessed ) . ljust ( 4 ) + " public keys. " )
# Inventory data
stdscr . addstr ( 11 , 35 , " Inventory lookups per second: " + str ( int ( inventorydata / 2 ) ) . ljust ( 3 ) )
# Log
stdscr . addstr ( 13 , 6 , " Log " , curses . A_BOLD )
n = log . count ( ' \n ' )
if n > 0 :
l = log . split ( ' \n ' )
if n > 512 :
del l [ : ( n - 256 ) ]
logpad . erase ( )
n = len ( l )
for i , item in enumerate ( l ) :
a = 0
if len ( item ) > 0 and item [ 0 ] == ' ! ' :
a = curses . color_pair ( 1 )
item = item [ 1 : ]
logpad . addstr ( i , 0 , item , a )
logpad . refresh ( n - curses . LINES + 2 , 0 , 14 , 6 , curses . LINES - 2 , curses . COLS - 7 )
stdscr . refresh ( )
def redraw ( stdscr ) :
@ -61,6 +151,10 @@ def redraw(stdscr):
stdscr . border ( )
drawmenu ( stdscr )
stdscr . refresh ( )
def dialogreset ( stdscr ) :
stdscr . clear ( )
stdscr . keypad ( 1 )
curses . curs_set ( 0 )
def handlech ( c , stdscr ) :
if c != curses . ERR :
if c in range ( 256 ) :
@ -70,20 +164,176 @@ def handlech(c, stdscr):
elif chr ( c ) == ' q ' :
global quit
quit = True
elif chr ( c ) == ' \n ' :
if menutab == 4 :
curses . curs_set ( 1 )
d = Dialog ( dialog = " dialog " )
d . set_background_title ( " Your Identities Dialog Box " )
r , t = d . menu ( " Do what with \" " + addresses [ addrcur ] [ 0 ] + " \" : \" " + addresses [ addrcur ] [ 2 ] + " \" ? " ,
choices = [ ( " 1 " , " Create new address " ) ,
( " 2 " , " Copy address to internal buffer " ) ,
( " 3 " , " Rename " ) ,
( " 4 " , " Enable " ) ,
( " 5 " , " Disable " ) ,
( " 6 " , " Delete " ) ,
( " 7 " , " Special address behavior " ) ] )
if r == d . DIALOG_OK :
if t == " 1 " : # Create new address
d . set_background_title ( " Create new address " )
d . scrollbox ( unicode ( " Here you may generate as many addresses as you like. \n "
" Indeed, creating and abandoning addresses is encouraged. \n "
" Deterministic addresses have several pros and cons: \n "
" \n Pros: \n "
" * You can recreate your addresses on any computer from memory \n "
" * You need not worry about backing up your keys.dat file as long as you \n can remember your passphrase \n "
" Cons: \n "
" * You must remember (or write down) your passphrase in order to recreate \n your keys if they are lost \n "
" * You must also remember the address version and stream numbers \n "
" * If you choose a weak passphrase someone may be able to brute-force it \n and then send and receive messages as you " ) ,
exit_label = " Continue " )
r , t = d . menu ( " Choose an address generation technique " ,
choices = [ ( " 1 " , " Use a random number generator " ) ,
( " 2 " , " Use a passphrase " ) ] )
if r == d . DIALOG_OK :
if t == " 1 " :
d . set_background_title ( " Randomly generate address " )
r , t = d . inputbox ( " Label (not shown to anyone except you) " )
label = " "
if r == d . DIALOG_OK and len ( t ) > 0 :
label = t
r , t = d . menu ( " Choose a stream " ,
choices = [ ( " 1 " , " Use the most available stream " ) , ( " " , " (Best if this is the first of many addresses you will create) " ) ,
( " 2 " , " Use the same stream as an existing address " ) , ( " " , " (Saves you some bandwidth and processing power) " ) ] )
if r == d . DIALOG_OK :
if t == " 1 " :
stream = 1
elif t == " 2 " :
addrs = [ ]
for i , item in enumerate ( addresses ) :
addrs . append ( [ str ( i ) , item [ 2 ] ] )
r , t = d . menu ( " Choose an existing address ' s stream " , choices = addrs )
if r == d . DIALOG_OK :
stream = decodeAddress ( addrs [ int ( t ) ] [ 1 ] ) [ 2 ]
shorten = False
r , t = d . checklist ( " Miscellaneous options " ,
choices = [ ( " 1 " , " Spend time shortening the address " , shorten ) ] )
if r == d . DIALOG_OK and " 1 " in t :
shorten = True
shared . addressGeneratorQueue . put ( ( " createRandomAddress " , 4 , stream , label , 1 , " " , shorten ) )
elif t == " 2 " :
d . set_background_title ( " Make deterministic addresses " )
r , t = d . passwordform ( " Enter passphrase " ,
[ ( " Passphrase " , 1 , 1 , " " , 2 , 1 , 64 , 128 ) ,
( " Confirm passphrase " , 3 , 1 , " " , 4 , 1 , 64 , 128 ) ] ,
form_height = 4 , insecure = True )
if r == d . DIALOG_OK :
if t [ 0 ] == t [ 1 ] :
passphrase = t [ 0 ]
r , t = d . rangebox ( " Number of addresses to generate " ,
width = 48 , min = 1 , max = 99 , init = 8 )
if r == d . DIALOG_OK :
number = t
stream = 1
shorten = False
r , t = d . checklist ( " Miscellaneous options " ,
choices = [ ( " 1 " , " Spend time shortening the address " , shorten ) ] )
if r == d . DIALOG_OK and " 1 " in t :
shorten = True
d . scrollbox ( unicode ( " In addition to your passphrase, be sure to remember the following numbers: \n "
" \n * Address version number: " + str ( 4 ) + " \n "
" * Stream number: " + str ( stream ) ) ,
exit_label = " Continue " )
shared . addressGeneratorQueue . put ( ( ' createDeterministicAddresses ' , 4 , stream , " unused deterministic address " , number , str ( passphrase ) , shorten ) )
else :
d . scrollbox ( unicode ( " Passphrases do not match " ) , exit_label = " Continue " )
elif t == " 2 " : # Copy address to internal buffer
addrcopy = addrcur
elif t == " 3 " : # Rename address label
a = addresses [ addrcur ] [ 2 ]
label = addresses [ addrcur ] [ 0 ]
r , t = d . inputbox ( " New address label " , init = label )
if r == d . DIALOG_OK :
label = t
shared . config . set ( a , " label " , label )
# Write config
with open ( shared . appdata + ' keys.dat ' , ' wb ' ) as configfile :
shared . config . write ( configfile )
addresses [ addrcur ] [ 0 ] = label
elif t == " 4 " : # Enable address
a = addresses [ addrcur ] [ 2 ]
shared . config . set ( a , " enabled " , " true " ) # Set config
# Write config
with open ( shared . appdata + ' keys.dat ' , ' wb ' ) as configfile :
shared . config . write ( configfile )
# Change color
if shared . safeConfigGetBoolean ( a , ' chan ' ) :
addresses [ addrcur ] [ 3 ] = 9 # orange
elif shared . safeConfigGetBoolean ( a , ' mailinglist ' ) :
addresses [ addrcur ] [ 3 ] = 5 # magenta
else :
addresses [ addrcur ] [ 3 ] = 0 # black
addresses [ addrcur ] [ 1 ] = True
shared . reloadMyAddressHashes ( ) # Reload address hashes
elif t == " 5 " : # Disable address
a = addresses [ addrcur ] [ 2 ]
shared . config . set ( a , " enabled " , " false " ) # Set config
addresses [ addrcur ] [ 3 ] = 8 # Set color to gray
# Write config
with open ( shared . appdata + ' keys.dat ' , ' wb ' ) as configfile :
shared . config . write ( configfile )
addresses [ addrcur ] [ 1 ] = False
shared . reloadMyAddressHashes ( ) # Reload address hashes
elif t == " 6 " : # Delete address
pass
elif t == " 7 " : # Special address behavior
a = addresses [ addrcur ] [ 2 ]
d . set_background_title ( " Special address behavior " )
if shared . safeConfigGetBoolean ( a , " chan " ) :
d . scrollbox ( unicode ( " This is a chan address. You cannot use it as a pseudo-mailing list. " ) , exit_label = " Continue " )
else :
m = shared . safeConfigGetBoolean ( a , " mailinglist " )
r , t = d . radiolist ( " Select address behavior " ,
choices = [ ( " 1 " , " Behave as a normal address " , not m ) , ( " 2 " , " Behave as a pseudo-mailing-list address " , m ) ] )
if r == d . DIALOG_OK :
if t == " 1 " and m == True :
shared . config . set ( a , " mailinglist " , " false " )
if addresses [ addrcur ] [ 1 ] :
addresses [ addrcur ] [ 3 ] = 0 # Set color to black
else :
addresses [ addrcur ] [ 3 ] = 8 # Set color to gray
elif t == " 2 " and m == False :
try :
mn = shared . config . get ( a , " mailinglistname " )
except ConfigParser . NoOptionError :
mn = " "
r , t = d . inputbox ( " Mailing list name " , init = mn )
if r == d . DIALOG_OK :
mn = t
shared . config . set ( a , " mailinglist " , " true " )
shared . config . set ( a , " mailinglistname " , mn )
addresses [ addrcur ] [ 3 ] = 6 # Set color to magenta
# Write config
with open ( shared . appdata + ' keys.dat ' , ' wb ' ) as configfile :
shared . config . write ( configfile )
dialogreset ( stdscr )
else :
global addrcur
if c == curses . KEY_UP :
if ( addrcur > 0 ) :
if menutab == 4 and addrcur > 0 :
addrcur - = 1
elif c == curses . KEY_DOWN :
if ( addrcur < len ( addresses ) - 1 ) :
if menutab == 4 and addrcur < len ( addresses ) - 1 :
addrcur + = 1
redraw ( stdscr )
def runwrapper ( ) :
sys . stdout = printlog
sys . stderr = errlog
stdscr = curses . initscr ( )
global logpad
logpad = curses . newpad ( 1024 , curses . COLS )
stdscr . nodelay ( 1 )
curses . curs_set ( 0 )
curses . start_color ( )
@ -92,29 +342,43 @@ def runwrapper():
shutdown ( )
def run ( stdscr ) :
# Init list of address in 'Your Identities' tab
configSections = shared . config . sections ( )
# Schedule inventory lookup data
resetlookups ( )
# Init color pairs
if curses . has_colors ( ) :
curses . init_pair ( 1 , curses . COLOR_RED , curses . COLOR_BLACK ) # red
curses . init_pair ( 2 , curses . COLOR_GREEN , curses . COLOR_BLACK ) # green
curses . init_pair ( 3 , curses . COLOR_YELLOW , curses . COLOR_BLACK ) # yellow
curses . init_pair ( 4 , curses . COLOR_BLUE , curses . COLOR_BLACK ) # blue
curses . init_pair ( 5 , curses . COLOR_MAGENTA , curses . COLOR_BLACK ) # magenta
curses . init_pair ( 6 , curses . COLOR_CYAN , curses . COLOR_BLACK ) # cyan
curses . init_pair ( 7 , curses . COLOR_WHITE , curses . COLOR_BLACK ) # white
if curses . can_change_color ( ) :
curses . init_color ( 8 , 500 , 500 , 500 ) # gray
curses . init_pair ( 8 , 8 , 0 )
curses . init_color ( 9 , 844 , 465 , 0 ) # orange
curses . init_pair ( 9 , 9 , 0 )
else :
global menutab
menutab = 4
curses . beep ( )
else :
curses . init_pair ( 8 , curses . COLOR_WHITE , curses . COLOR_BLACK ) # grayish
curses . init_pair ( 9 , curses . COLOR_YELLOW , curses . COLOR_BLACK ) # orangish
# Init list of address in 'Your Identities' tab
configSections = shared . config . sections ( )
for addressInKeysFile in configSections :
if addressInKeysFile != " bitmessagesettings " :
isEnabled = shared . config . getboolean ( addressInKeysFile , " enabled " )
addresses . append ( [ shared . config . get ( addressInKeysFile , " label " ) , isEnabled , str ( decodeAddress ( addressInKeysFile ) [ 2 ] ) ] )
addresses . append ( [ shared . config . get ( addressInKeysFile , " label " ) , isEnabled , addressInKeysFile ] )
# Set address color
if not isEnabled :
addresses [ len ( addresses ) - 1 ] . append ( 8 ) # gray
elif shared . safeConfigGetBoolean ( addressInKeysFile , ' chan ' ) :
addresses [ len ( addresses ) - 1 ] . append ( 9 ) # orange
elif shared . safeConfigGetBoolean ( addressInKeysFile , ' mailinglist ' ) :
addresses [ len ( addresses ) - 1 ] . append ( 5 ) # magenta
else :
addresses [ len ( addresses ) - 1 ] . append ( 0 ) # black
addresses . reverse ( )
# Load messages from database
"""
@ -140,5 +404,6 @@ def shutdown():
sys . stdout = printlog
shared . doCleanShutdown ( )
sys . stdout = sys . __stdout__
sys . stderr = sys . __stderr__
os . _exit ( 0 )