mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-17 23:04:24 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed3596b89a | ||
|
|
84ea13acbd | ||
|
|
bd631f6f5f | ||
|
|
5bcb4a3cec | ||
|
|
1913dff3fd | ||
|
|
0a5663b985 | ||
|
|
0ebdb9c826 | ||
|
|
52dfebcfa2 | ||
|
|
29feb1cab1 | ||
|
|
73fffe519a | ||
|
|
22f18324fc | ||
|
|
0affe9f283 | ||
|
|
dfd87a2119 | ||
|
|
c2f0f969fb | ||
|
|
9244f00e21 | ||
|
|
b1b2a2a302 | ||
|
|
b08ae0cf02 | ||
|
|
730c17c981 | ||
|
|
15a4ec2af9 |
31
README.md
31
README.md
@@ -1,5 +1,4 @@
|
||||
Nomad Network - Communicate Freely
|
||||
==========
|
||||
# Nomad Network - Communicate Freely
|
||||
|
||||
Off-grid, resilient mesh communication with strong encryption, forward secrecy and extreme privacy.
|
||||
|
||||
@@ -26,11 +25,6 @@ If you'd rather want to use an LXMF client with a graphical user interface, you
|
||||
## Current Status
|
||||
The current version of the program should be considered a beta release. The program works well, but there will most probably be bugs and possibly sub-optimal performance in some scenarios. On the other hand, this is the ideal time to have an influence on the direction of the development of Nomad Network. To do so, join the discussion, report bugs and request features here on the GitHub project.
|
||||
|
||||
### Feature roadmap
|
||||
- Network-wide propagated bulletins and discussion threads
|
||||
- Collaborative maps and geospatial information sharing
|
||||
- Facilitation of trade and barter
|
||||
|
||||
## How do I get started?
|
||||
The easiest way to install Nomad Network is via pip:
|
||||
|
||||
@@ -119,6 +113,29 @@ You can help support the continued development of open, free and private communi
|
||||
```
|
||||
- Ko-Fi: https://ko-fi.com/markqvist
|
||||
|
||||
## Development Roadmap
|
||||
|
||||
- New major features
|
||||
- Network-wide propagated bulletins and discussion threads
|
||||
- Collaborative maps and geospatial information sharing
|
||||
- Facilitation of trade and barter
|
||||
- Minor improvements and fixes
|
||||
- Link status (RSSI and SNR) in conversation or conv list
|
||||
- Ctrl-M shorcut for jumping to menu
|
||||
- Share node with other users / send node info to user
|
||||
- Fix internal editor failing on some OSes with no "editor" alias
|
||||
- Possibly add a required-width header
|
||||
- Improve browser handling of remote link close
|
||||
- Better navigation handling when requests fail (also because of closed links)
|
||||
- Retry failed messages mechanism
|
||||
- Re-arrange buttons to be more consistent
|
||||
- Input field for pages
|
||||
- Post mechanism
|
||||
- Term compatibility notice in readme
|
||||
- Selected icon in conversation list
|
||||
- Possibly a Search Local Nodes function
|
||||
- Possibly add via entry in node info box, next to distance
|
||||
|
||||
## Caveat Emptor
|
||||
Nomad Network is beta software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy-breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
|
||||
|
||||
@@ -227,6 +227,35 @@ class Conversation:
|
||||
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
||||
return False
|
||||
|
||||
def paper_output(self, content="", title=""):
|
||||
if self.send_destination:
|
||||
try:
|
||||
dest = self.send_destination
|
||||
source = self.app.lxmf_destination
|
||||
desired_method = LXMF.LXMessage.PAPER
|
||||
|
||||
lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method)
|
||||
qr_code = lxm.as_qr()
|
||||
qr_tmp_path = self.app.tmpfilespath+"/"+str(RNS.hexrep(lxm.hash, delimit=False))
|
||||
qr_code.save(qr_tmp_path)
|
||||
|
||||
print_result = self.app.print_file(qr_tmp_path)
|
||||
os.unlink(qr_tmp_path)
|
||||
|
||||
if print_result:
|
||||
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
||||
self.messages.append(ConversationMessage(message_path))
|
||||
|
||||
return print_result
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while generating paper message, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else:
|
||||
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
||||
return False
|
||||
|
||||
def message_notification(self, message):
|
||||
if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail:
|
||||
RNS.log("Direct delivery of "+str(message)+" failed. Retrying as propagated message.", RNS.LOG_VERBOSE)
|
||||
|
||||
@@ -98,6 +98,7 @@ class NomadNetworkApp:
|
||||
self.ignoredpath = self.configdir+"/ignored"
|
||||
self.logfilepath = self.configdir+"/logfile"
|
||||
self.errorfilepath = self.configdir+"/errors"
|
||||
self.pnannouncedpath = self.configdir+"/pnannounced"
|
||||
self.storagepath = self.configdir+"/storage"
|
||||
self.identitypath = self.configdir+"/storage/identity"
|
||||
self.cachepath = self.configdir+"/storage/cache"
|
||||
@@ -105,6 +106,7 @@ class NomadNetworkApp:
|
||||
self.conversationpath = self.configdir+"/storage/conversations"
|
||||
self.directorypath = self.configdir+"/storage/directory"
|
||||
self.peersettingspath = self.configdir+"/storage/peersettings"
|
||||
self.tmpfilespath = self.configdir+"/storage/tmp"
|
||||
|
||||
self.pagespath = self.configdir+"/storage/pages"
|
||||
self.filespath = self.configdir+"/storage/files"
|
||||
@@ -145,6 +147,11 @@ class NomadNetworkApp:
|
||||
if not os.path.isdir(self.cachepath):
|
||||
os.makedirs(self.cachepath)
|
||||
|
||||
if not os.path.isdir(self.tmpfilespath):
|
||||
os.makedirs(self.tmpfilespath)
|
||||
else:
|
||||
self.clear_tmp_dir()
|
||||
|
||||
if os.path.isfile(self.configpath):
|
||||
try:
|
||||
self.config = ConfigObj(self.configpath)
|
||||
@@ -290,11 +297,24 @@ class NomadNetworkApp:
|
||||
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
|
||||
|
||||
self.message_router.enable_propagation()
|
||||
try:
|
||||
with open(self.pnannouncedpath, "wb") as pnf:
|
||||
pnf.write(msgpack.packb(time.time()))
|
||||
pnf.close()
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while writing Propagation Node announce timestamp. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
|
||||
self.node = nomadnet.Node(self)
|
||||
else:
|
||||
self.node = None
|
||||
if os.path.isfile(self.pnannouncedpath):
|
||||
try:
|
||||
RNS.log("Sending indication to peered LXMF Propagation Node that this node is no longer participating", RNS.LOG_DEBUG)
|
||||
self.message_router.disable_propagation()
|
||||
os.unlink(self.pnannouncedpath)
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while indicating that this LXMF Propagation Node is no longer participating. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.Transport.register_announce_handler(nomadnet.Conversation)
|
||||
RNS.Transport.register_announce_handler(nomadnet.Directory)
|
||||
@@ -514,6 +534,26 @@ class NomadNetworkApp:
|
||||
|
||||
return False
|
||||
|
||||
def print_file(self, filename):
|
||||
print_command = self.print_command+" "+filename
|
||||
|
||||
try:
|
||||
return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while executing print command: "+str(print_command), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
if return_code == 0:
|
||||
RNS.log("Successfully printed "+str(filename)+" using print command: "+print_command, RNS.LOG_DEBUG)
|
||||
return True
|
||||
|
||||
else:
|
||||
RNS.log("Printing "+str(filename)+" failed using print command: "+print_command, RNS.LOG_DEBUG)
|
||||
return False
|
||||
|
||||
|
||||
def print_message(self, message, received = None):
|
||||
try:
|
||||
template = self.printing_template_msg
|
||||
@@ -547,8 +587,7 @@ class NomadNetworkApp:
|
||||
f.write(output.encode("utf-8"))
|
||||
f.close()
|
||||
|
||||
print_command = "lp -d thermal -o cpi=16 -o lpi=8 "+filename
|
||||
return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
self.print_file(filename)
|
||||
|
||||
os.unlink(filename)
|
||||
|
||||
@@ -576,6 +615,12 @@ class NomadNetworkApp:
|
||||
if os.path.isfile(self.conversationpath + "/" + source_hash + "/unread"):
|
||||
os.unlink(self.conversationpath + "/" + source_hash + "/unread")
|
||||
|
||||
def clear_tmp_dir(self):
|
||||
if os.path.isdir(self.tmpfilespath):
|
||||
for file in os.listdir(self.tmpfilespath):
|
||||
fpath = self.tmpfilespath+"/"+file
|
||||
os.unlink(fpath)
|
||||
|
||||
def createDefaultConfig(self):
|
||||
self.config = ConfigObj(__default_nomadnet_config__)
|
||||
self.config.filename = self.configpath
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.2.7"
|
||||
__version__ = "0.3.1"
|
||||
|
||||
@@ -121,7 +121,9 @@ GLYPHS = {
|
||||
("decoration_menu", " +", " +", " \uf93a"),
|
||||
("unread_menu", " !", " \u2709", urm_char),
|
||||
("globe", "", "", "\uf484"),
|
||||
("sent", "/\\", "\u2191", "\ufbf4")
|
||||
("sent", "/\\", "\u2191", "\ufbf4"),
|
||||
("papermsg", "P", "\u25a4", "\uf719"),
|
||||
("qrcode", "QR", "\u25a4", "\uf029"),
|
||||
}
|
||||
|
||||
class TextUI:
|
||||
|
||||
@@ -14,13 +14,13 @@ class ConversationListDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info [C-x] Delete [C-r] Sync [C-n] New [C-g] Fullscreen"), "shortcutbar")
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info [C-x] Delete [C-r] Sync [C-n] New [C-u] Ingest URI [C-g] Fullscreen"), "shortcutbar")
|
||||
|
||||
class ConversationDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-k] Clear [C-w] Close [C-t] Title [C-p] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar")
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-p] Paper Msg [C-t] Title [C-k] Clear [C-w] Close [C-u] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar")
|
||||
|
||||
class ConversationsArea(urwid.LineBox):
|
||||
def keypress(self, size, key):
|
||||
@@ -30,6 +30,8 @@ class ConversationsArea(urwid.LineBox):
|
||||
self.delegate.delete_selected_conversation()
|
||||
elif key == "ctrl n":
|
||||
self.delegate.new_conversation()
|
||||
elif key == "ctrl u":
|
||||
self.delegate.ingest_lxm_uri()
|
||||
elif key == "ctrl r":
|
||||
self.delegate.sync_conversations()
|
||||
elif key == "ctrl g":
|
||||
@@ -330,6 +332,110 @@ class ConversationsDisplay():
|
||||
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||
self.columns_widget.contents[0] = (overlay, options)
|
||||
|
||||
def ingest_lxm_uri(self):
|
||||
self.dialog_open = True
|
||||
lxm_uri = ""
|
||||
e_uri = urwid.Edit(caption="URI : ",edit_text=lxm_uri)
|
||||
|
||||
def dismiss_dialog(sender):
|
||||
self.update_conversation_list()
|
||||
self.dialog_open = False
|
||||
|
||||
def confirmed(sender):
|
||||
try:
|
||||
local_delivery_signal = "local_delivery_occurred"
|
||||
duplicate_signal = "duplicate_lxm"
|
||||
lxm_uri = e_uri.get_edit_text()
|
||||
|
||||
ingest_result = self.app.message_router.ingest_lxm_uri(
|
||||
lxm_uri,
|
||||
signal_local_delivery=local_delivery_signal,
|
||||
signal_duplicate=duplicate_signal
|
||||
)
|
||||
|
||||
if ingest_result == False:
|
||||
raise ValueError("The URI contained no decodable messages")
|
||||
|
||||
elif ingest_result == local_delivery_signal:
|
||||
rdialog_pile = urwid.Pile([
|
||||
urwid.Text("Message was decoded, decrypted successfully, and added to your conversation list."),
|
||||
urwid.Text(""),
|
||||
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||
])
|
||||
rdialog_pile.error_display = False
|
||||
|
||||
rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
|
||||
rdialog.delegate = self
|
||||
bottom = self.listbox
|
||||
|
||||
roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||
|
||||
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||
self.columns_widget.contents[0] = (roverlay, options)
|
||||
|
||||
elif ingest_result == duplicate_signal:
|
||||
rdialog_pile = urwid.Pile([
|
||||
urwid.Text("The decoded message has already been processed by the LXMF Router, and will not be ingested again."),
|
||||
urwid.Text(""),
|
||||
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||
])
|
||||
rdialog_pile.error_display = False
|
||||
|
||||
rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
|
||||
rdialog.delegate = self
|
||||
bottom = self.listbox
|
||||
|
||||
roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||
|
||||
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||
self.columns_widget.contents[0] = (roverlay, options)
|
||||
|
||||
else:
|
||||
if self.app.enable_node:
|
||||
propagation_text = "The decoded message was not addressed to this LXMF address, but has been added to the propagation node queues, and will be distributed on the propagation network."
|
||||
else:
|
||||
propagation_text = "The decoded message was not addressed to this LXMF address, and has been discarded."
|
||||
|
||||
rdialog_pile = urwid.Pile([
|
||||
urwid.Text(propagation_text),
|
||||
urwid.Text(""),
|
||||
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||
])
|
||||
rdialog_pile.error_display = False
|
||||
|
||||
rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
|
||||
rdialog.delegate = self
|
||||
bottom = self.listbox
|
||||
|
||||
roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||
|
||||
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||
self.columns_widget.contents[0] = (roverlay, options)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not ingest LXM URI. The contained exception was: "+str(e), RNS.LOG_VERBOSE)
|
||||
if not dialog_pile.error_display:
|
||||
dialog_pile.error_display = True
|
||||
options = dialog_pile.options(height_type="pack")
|
||||
dialog_pile.contents.append((urwid.Text(""), options))
|
||||
dialog_pile.contents.append((urwid.Text(("error_text", "Could ingest LXM from URI data. Check your input."), align="center"), options))
|
||||
|
||||
dialog_pile = urwid.Pile([
|
||||
e_uri,
|
||||
urwid.Text(""),
|
||||
urwid.Columns([("weight", 0.45, urwid.Button("Ingest", on_press=confirmed)), ("weight", 0.1, urwid.Text("")), ("weight", 0.45, urwid.Button("Back", on_press=dismiss_dialog))])
|
||||
])
|
||||
dialog_pile.error_display = False
|
||||
|
||||
dialog = DialogLineBox(dialog_pile, title="Ingest message URI")
|
||||
dialog.delegate = self
|
||||
bottom = self.listbox
|
||||
|
||||
overlay = urwid.Overlay(dialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||
|
||||
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||
self.columns_widget.contents[0] = (overlay, options)
|
||||
|
||||
def delete_conversation(self, source_hash):
|
||||
if source_hash in ConversationsDisplay.cached_conversation_widgets:
|
||||
conversation = ConversationsDisplay.cached_conversation_widgets[source_hash]
|
||||
@@ -636,6 +742,8 @@ class MessageEdit(urwid.Edit):
|
||||
def keypress(self, size, key):
|
||||
if key == "ctrl d":
|
||||
self.delegate.send_message()
|
||||
elif key == "ctrl p":
|
||||
self.delegate.paper_message()
|
||||
elif key == "ctrl k":
|
||||
self.delegate.clear_editor()
|
||||
elif key == "up":
|
||||
@@ -800,7 +908,7 @@ class ConversationWidget(urwid.WidgetWrap):
|
||||
self.toggle_focus_area()
|
||||
elif key == "ctrl w":
|
||||
self.close()
|
||||
elif key == "ctrl p":
|
||||
elif key == "ctrl u":
|
||||
self.conversation.purge_failed()
|
||||
self.conversation_changed(None)
|
||||
elif key == "ctrl t":
|
||||
@@ -860,6 +968,34 @@ class ConversationWidget(urwid.WidgetWrap):
|
||||
else:
|
||||
pass
|
||||
|
||||
def paper_message(self):
|
||||
content = self.content_editor.get_edit_text()
|
||||
title = self.title_editor.get_edit_text()
|
||||
if not content == "":
|
||||
if self.conversation.paper_output(content, title):
|
||||
self.clear_editor()
|
||||
else:
|
||||
self.paper_message_failed()
|
||||
|
||||
def paper_message_failed(self):
|
||||
def dismiss_dialog(sender):
|
||||
self.dialog_open = False
|
||||
self.conversation_changed(None)
|
||||
|
||||
dialog = DialogLineBox(
|
||||
urwid.Pile([
|
||||
urwid.Text("Could not output paper message,\ncheck your settings. See the log\nfile for any error messages.\n", align="center"),
|
||||
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||
]), title="!"
|
||||
)
|
||||
dialog.delegate = self
|
||||
bottom = self.messagelist
|
||||
|
||||
overlay = urwid.Overlay(dialog, bottom, align="center", width=34, valign="middle", height="pack", left=2, right=2)
|
||||
|
||||
self.frame.contents["body"] = (overlay, self.frame.options())
|
||||
self.frame.set_focus("body")
|
||||
|
||||
def close(self):
|
||||
self.delegate.close_conversation(self)
|
||||
|
||||
@@ -890,6 +1026,9 @@ class LXMessageWidget(urwid.WidgetWrap):
|
||||
elif message.lxm.method == LXMF.LXMessage.PROPAGATED and message.lxm.state == LXMF.LXMessage.SENT:
|
||||
header_style = "msg_header_propagated"
|
||||
title_string = g["sent"]+" "+title_string
|
||||
elif message.lxm.method == LXMF.LXMessage.PAPER and message.lxm.state == LXMF.LXMessage.PAPER:
|
||||
header_style = "msg_header_propagated"
|
||||
title_string = g["papermsg"]+" "+title_string
|
||||
elif message.lxm.state == LXMF.LXMessage.SENT:
|
||||
header_style = "msg_header_sent"
|
||||
title_string = g["sent"]+" "+title_string
|
||||
|
||||
@@ -670,19 +670,19 @@ For future reference, you can download the Reticulum Manual in PDF format here:
|
||||
|
||||
It might be nice to keep that handy when you are not connected to the Internet, as it is full of information and examples that are also very relevant to Nomad Network.
|
||||
|
||||
>The Unsigned.io Testnet
|
||||
>The Reticulum Testnet
|
||||
|
||||
If you have Internet access, and just want to get started experimenting, you are welcome to join the Unsigned.io RNS Testnet. The testnet is just that, an informal network for testing and experimenting. It will be up most of the time, and anyone can join, but it also means that there's no guarantees for service availability.
|
||||
|
||||
The Testnet also runs the latest version of Reticulum, often even a short while before it is publicly released, which means strange behaviour might occur. If none of that scares you, add the following interface to your Reticulum configuration file to join:
|
||||
|
||||
>>
|
||||
[[RNS Testnet Frankfurt]]
|
||||
[[RNS Testnet Zurich]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = yes
|
||||
outgoing = True
|
||||
target_host = frankfurt.rns.unsigned.io
|
||||
target_port = 4965
|
||||
target_host = zurich.connect.reticulum.network
|
||||
target_port = 4242
|
||||
<
|
||||
|
||||
If you connect to the testnet, you can leave nomadnet running for a while and wait for it to receive announces from other nodes on the network that host pages or services, or you can try connecting directly to some nodes listed here:
|
||||
|
||||
@@ -894,7 +894,7 @@ class NodeActiveConnections(urwid.WidgetWrap):
|
||||
if self.app.node != None:
|
||||
self.stat_string = str(len(self.app.node.destination.links))
|
||||
|
||||
self.display_widget.set_text("Conneced Now : "+self.stat_string)
|
||||
self.display_widget.set_text("Connected Now : "+self.stat_string)
|
||||
|
||||
def update_stat_callback(self, loop=None, user_data=None):
|
||||
self.update_stat()
|
||||
@@ -1604,7 +1604,14 @@ class LXMFPeerEntry(urwid.WidgetWrap):
|
||||
style = "list_unknown"
|
||||
focus_style = "list_focus"
|
||||
|
||||
widget = ListEntry(sym+" "+display_str+"\n "+str(len(peer.unhandled_messages))+" unhandled LXMs - "+"Last heard "+pretty_date(int(peer.last_heard)))
|
||||
alive_string = "Unknown"
|
||||
if hasattr(peer, "alive"):
|
||||
if peer.alive:
|
||||
alive_string = "Available"
|
||||
else:
|
||||
alive_string = "Unresponsive"
|
||||
|
||||
widget = ListEntry(sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard))+"\n "+str(len(peer.unhandled_messages))+" unhandled LXMs")
|
||||
# urwid.connect_signal(widget, "click", delegate.connect_node, node)
|
||||
|
||||
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
||||
|
||||
4
setup.py
4
setup.py
@@ -23,6 +23,6 @@ setuptools.setup(
|
||||
entry_points= {
|
||||
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
||||
},
|
||||
install_requires=['rns>=0.4.1', 'lxmf>=0.2.4', 'urwid>=2.1.2'],
|
||||
python_requires='>=3.6',
|
||||
install_requires=["rns>=0.4.6", "lxmf>=0.2.8", "urwid>=2.1.2", "qrcode"],
|
||||
python_requires=">=3.6",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user