mirror of
https://github.com/pdxlocations/contact.git
synced 2026-03-28 17:12:35 +01:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91d331af4f | ||
|
|
be92ac5de3 | ||
|
|
10179c4179 | ||
|
|
6e45caaac2 | ||
|
|
152555156e | ||
|
|
056b8b5f5f | ||
|
|
07f0721fd5 | ||
|
|
cf9276d399 | ||
|
|
482d158b15 | ||
|
|
11bd9c75ed | ||
|
|
1b2701e1a1 | ||
|
|
7fcc9abd76 | ||
|
|
9bfddb954d | ||
|
|
deb231fc63 | ||
|
|
bbe9e66fa5 | ||
|
|
aa7d98b1b0 | ||
|
|
8895784503 | ||
|
|
695b4949c0 | ||
|
|
404bac9133 | ||
|
|
4698b81a3f | ||
|
|
e7e9f24fe2 | ||
|
|
01f67ea8b5 | ||
|
|
c19684c119 | ||
|
|
0b0d8c482b | ||
|
|
f9774b2248 | ||
|
|
d6db1e1832 | ||
|
|
4c85aaecdf | ||
|
|
d8fc02b28a | ||
|
|
039673bb18 | ||
|
|
d81e694ee6 | ||
|
|
dfea291d21 | ||
|
|
41c60a49e9 | ||
|
|
fb3138883f | ||
|
|
767f0e2288 | ||
|
|
43680f8afb | ||
|
|
e15f625716 | ||
|
|
c090b3dd58 | ||
|
|
f472a3040c | ||
|
|
4a0c49b7d6 | ||
|
|
3034a1464a | ||
|
|
80fe10c050 | ||
|
|
0962c5b284 | ||
|
|
1b3abdebf2 | ||
|
|
a710374fe9 | ||
|
|
cb088c51d4 | ||
|
|
ccb46b8553 | ||
|
|
35748d071e | ||
|
|
7e85085b98 | ||
|
|
7493d21c1a | ||
|
|
d0af0e6af1 | ||
|
|
796c40b560 | ||
|
|
1c0704b940 | ||
|
|
6384777bb6 | ||
|
|
2fbaee5fc5 | ||
|
|
06e71331b6 | ||
|
|
fea705a09f | ||
|
|
a47a4a9b32 |
@@ -1,6 +1,8 @@
|
||||
import sqlite3
|
||||
import globals
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from utilities.utils import get_name_from_number
|
||||
|
||||
def get_table_name(channel):
|
||||
@@ -69,6 +71,8 @@ def update_ack_nak(channel, timestamp, message, ack):
|
||||
print(f"Unexpected error in update_ack_nak: {e}")
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
def load_messages_from_db():
|
||||
"""Load messages from the database for all channels and update globals.all_messages and globals.channel_list."""
|
||||
try:
|
||||
@@ -82,18 +86,18 @@ def load_messages_from_db():
|
||||
|
||||
# Iterate through each table and fetch its messages
|
||||
for table_name in tables:
|
||||
quoted_table_name = f'"{table_name}"' # Quote the table name becuase we begin with numerics and contain spaces
|
||||
quoted_table_name = f'"{table_name}"' # Quote the table name because we begin with numerics and contain spaces
|
||||
table_columns = [i[1] for i in db_cursor.execute(f'PRAGMA table_info({quoted_table_name})')]
|
||||
if("ack_type" not in table_columns):
|
||||
if "ack_type" not in table_columns:
|
||||
update_table_query = f"ALTER TABLE {quoted_table_name} ADD COLUMN ack_type TEXT"
|
||||
db_cursor.execute(update_table_query)
|
||||
|
||||
query = f'SELECT user_id, message_text, ack_type FROM {quoted_table_name}'
|
||||
query = f'SELECT user_id, message_text, timestamp, ack_type FROM {quoted_table_name}'
|
||||
|
||||
try:
|
||||
# Fetch all messages from the table
|
||||
db_cursor.execute(query)
|
||||
db_messages = [(row[0], row[1], row[2]) for row in db_cursor.fetchall()] # Save as tuples
|
||||
db_messages = [(row[0], row[1], row[2], row[3]) for row in db_cursor.fetchall()] # Save as tuples
|
||||
|
||||
# Extract the channel name from the table name
|
||||
channel = table_name.split("_")[1]
|
||||
@@ -109,23 +113,32 @@ def load_messages_from_db():
|
||||
if channel not in globals.all_messages:
|
||||
globals.all_messages[channel] = []
|
||||
|
||||
# Add messages to globals.all_messages in tuple format
|
||||
for user_id, message, ack_type in db_messages:
|
||||
if user_id == str(globals.myNodeNum):
|
||||
ack_str = globals.ack_unknown_str
|
||||
if(ack_type == "Implicit"):
|
||||
ack_str = globals.ack_implicit_str
|
||||
elif(ack_type == "Ack"):
|
||||
ack_str = globals.ack_str
|
||||
elif(ack_type == "Nak"):
|
||||
ack_str = globals.nak_str
|
||||
# Add messages to globals.all_messages grouped by hourly timestamp
|
||||
hourly_messages = {}
|
||||
for user_id, message, timestamp, ack_type in db_messages:
|
||||
hour = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:00')
|
||||
if hour not in hourly_messages:
|
||||
hourly_messages[hour] = []
|
||||
|
||||
ack_str = globals.ack_unknown_str
|
||||
if ack_type == "Implicit":
|
||||
ack_str = globals.ack_implicit_str
|
||||
elif ack_type == "Ack":
|
||||
ack_str = globals.ack_str
|
||||
elif ack_type == "Nak":
|
||||
ack_str = globals.nak_str
|
||||
|
||||
if user_id == str(globals.myNodeNum):
|
||||
formatted_message = (f"{globals.sent_message_prefix}{ack_str}: ", message)
|
||||
else:
|
||||
else:
|
||||
formatted_message = (f"{globals.message_prefix} {get_name_from_number(int(user_id), 'short')}: ", message)
|
||||
|
||||
if formatted_message not in globals.all_messages[channel]:
|
||||
globals.all_messages[channel].append(formatted_message)
|
||||
|
||||
hourly_messages[hour].append(formatted_message)
|
||||
|
||||
# Flatten the hourly messages into globals.all_messages[channel]
|
||||
for hour, messages in sorted(hourly_messages.items()):
|
||||
globals.all_messages[channel].append((f"-- {hour} --", ""))
|
||||
globals.all_messages[channel].extend(messages)
|
||||
|
||||
except sqlite3.Error as e:
|
||||
print(f"SQLite error while loading messages from table '{table_name}': {e}")
|
||||
|
||||
18
globals.py
18
globals.py
@@ -1,25 +1,27 @@
|
||||
import os
|
||||
|
||||
# App Variables
|
||||
app_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
db_file_path = os.path.join(app_directory, "client.db")
|
||||
log_file_path = os.path.join(app_directory, "client.log")
|
||||
|
||||
interface = None
|
||||
display_log = False
|
||||
all_messages = {}
|
||||
channel_list = []
|
||||
notifications = set()
|
||||
packet_buffer = []
|
||||
node_list = []
|
||||
myNodeNum = 0
|
||||
selected_channel = 0
|
||||
selected_message = 0
|
||||
selected_node = 0
|
||||
current_window = 0
|
||||
interface = None
|
||||
display_log = False
|
||||
|
||||
# User Configurable
|
||||
db_file_path = os.path.join(app_directory, "client.db")
|
||||
log_file_path = os.path.join(app_directory, "client.log")
|
||||
message_prefix = ">>"
|
||||
sent_message_prefix = message_prefix + " Sent"
|
||||
notification_symbol = "*"
|
||||
ack_implicit_str = "[◌]"
|
||||
ack_str = "[✓]"
|
||||
nak_str = "[x]"
|
||||
ack_unknown_str = "[…]"
|
||||
notification_symbol = "*"
|
||||
node_list = []
|
||||
ack_unknown_str = "[…]"
|
||||
@@ -10,7 +10,6 @@ def get_user_input(prompt):
|
||||
|
||||
# Create a new window for user input
|
||||
input_win = curses.newwin(height, width, start_y, start_x)
|
||||
input_win.clear()
|
||||
input_win.border()
|
||||
|
||||
# Display the prompt
|
||||
@@ -18,15 +17,12 @@ def get_user_input(prompt):
|
||||
input_win.addstr(3, 2, "Enter value: ")
|
||||
input_win.refresh()
|
||||
|
||||
# Enable user input
|
||||
curses.echo()
|
||||
curses.curs_set(1)
|
||||
|
||||
user_input = ""
|
||||
while True:
|
||||
key = input_win.getch(3, 15 + len(user_input)) # Adjust cursor position dynamically
|
||||
if key == 27 or key == curses.KEY_LEFT: # ESC or Left Arrow
|
||||
curses.noecho()
|
||||
curses.curs_set(0)
|
||||
return None # Exit without returning a value
|
||||
elif key == ord('\n'): # Enter key
|
||||
@@ -40,7 +36,6 @@ def get_user_input(prompt):
|
||||
input_win.addstr(3, 15, user_input)
|
||||
|
||||
curses.curs_set(0)
|
||||
curses.noecho()
|
||||
|
||||
# Clear the input window
|
||||
input_win.clear()
|
||||
@@ -102,7 +97,7 @@ def get_repeated_input(current_value):
|
||||
repeated_win.clear()
|
||||
repeated_win.border()
|
||||
repeated_win.addstr(1, 2, "Enter comma-separated values:", curses.A_BOLD)
|
||||
repeated_win.addstr(3, 2, f"Current: {', '.join(current_value)}")
|
||||
repeated_win.addstr(3, 2, f"Current: {', '.join(map(str, current_value))}")
|
||||
repeated_win.addstr(5, 2, f"New value: {user_input}")
|
||||
repeated_win.refresh()
|
||||
|
||||
|
||||
2
main.py
2
main.py
@@ -3,7 +3,7 @@
|
||||
'''
|
||||
Contact - A Console UI for Meshtastic by http://github.com/pdxlocations
|
||||
Powered by Meshtastic.org
|
||||
V 1.0.1
|
||||
V 1.0.3
|
||||
'''
|
||||
|
||||
import curses
|
||||
|
||||
@@ -5,13 +5,15 @@ from ui.curses_ui import draw_packetlog_win, draw_node_list, draw_messages_windo
|
||||
from db_handler import save_message_to_db, maybe_store_nodeinfo_in_db
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
def on_receive(packet, interface):
|
||||
global nodes_win
|
||||
|
||||
# update packet log
|
||||
# Update packet log
|
||||
globals.packet_buffer.append(packet)
|
||||
if len(globals.packet_buffer) > 20:
|
||||
# trim buffer to 20 packets
|
||||
# Trim buffer to 20 packets
|
||||
globals.packet_buffer = globals.packet_buffer[-20:]
|
||||
|
||||
if globals.display_log:
|
||||
@@ -20,10 +22,9 @@ def on_receive(packet, interface):
|
||||
if 'decoded' not in packet:
|
||||
return
|
||||
|
||||
# Assume any incoming packet could update the last seen time for a node, so we
|
||||
# may need to reorder the list. This could probably be limited to specific packets.
|
||||
# Assume any incoming packet could update the last seen time for a node
|
||||
new_node_list = get_node_list()
|
||||
if(new_node_list != globals.node_list):
|
||||
if new_node_list != globals.node_list:
|
||||
globals.node_list = new_node_list
|
||||
draw_node_list()
|
||||
|
||||
@@ -42,6 +43,7 @@ def on_receive(packet, interface):
|
||||
channel_number = packet['channel']
|
||||
else:
|
||||
channel_number = 0
|
||||
|
||||
if packet['to'] == globals.myNodeNum:
|
||||
if packet['from'] in globals.channel_list:
|
||||
pass
|
||||
@@ -65,15 +67,35 @@ def on_receive(packet, interface):
|
||||
if globals.channel_list[channel_number] not in globals.all_messages:
|
||||
globals.all_messages[globals.channel_list[channel_number]] = []
|
||||
|
||||
# Timestamp handling
|
||||
current_timestamp = int(packet['rxTime']) # Use the packet's rxTime for timestamp
|
||||
current_hour = datetime.fromtimestamp(current_timestamp).strftime('%Y-%m-%d %H:00')
|
||||
|
||||
# Retrieve the last timestamp if available
|
||||
channel_messages = globals.all_messages[globals.channel_list[channel_number]]
|
||||
if channel_messages:
|
||||
# Check the last entry for a timestamp
|
||||
for entry in reversed(channel_messages):
|
||||
if entry[0].startswith("--"):
|
||||
last_hour = entry[0].strip("- ").strip()
|
||||
break
|
||||
else:
|
||||
last_hour = None
|
||||
else:
|
||||
last_hour = None
|
||||
|
||||
# Add a new timestamp if it's a new hour
|
||||
if last_hour != current_hour:
|
||||
globals.all_messages[globals.channel_list[channel_number]].append((f"-- {current_hour} --", ""))
|
||||
|
||||
globals.all_messages[globals.channel_list[channel_number]].append((f"{globals.message_prefix} {message_from_string} ", message_string))
|
||||
|
||||
if(refresh_channels):
|
||||
if refresh_channels:
|
||||
draw_channel_list()
|
||||
if(refresh_messages):
|
||||
draw_messages_window()
|
||||
if refresh_messages:
|
||||
draw_messages_window(True)
|
||||
|
||||
save_message_to_db(globals.channel_list[channel_number], message_from_id, message_string)
|
||||
|
||||
except KeyError as e:
|
||||
print(f"Error processing packet: {e}")
|
||||
|
||||
print(f"Error processing packet: {e}")
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
from meshtastic import BROADCAST_NUM
|
||||
from db_handler import save_message_to_db, update_ack_nak
|
||||
from meshtastic.protobuf import mesh_pb2, portnums_pb2
|
||||
@@ -106,14 +107,14 @@ def on_response_traceroute(packet):
|
||||
globals.all_messages[globals.channel_list[channel_number]] = []
|
||||
globals.all_messages[globals.channel_list[channel_number]].append((f"{globals.message_prefix} {message_from_string}", msg_str))
|
||||
|
||||
if(refresh_channels):
|
||||
if refresh_channels:
|
||||
draw_channel_list()
|
||||
if(refresh_messages):
|
||||
draw_messages_window()
|
||||
if refresh_messages:
|
||||
draw_messages_window(True)
|
||||
save_message_to_db(globals.channel_list[channel_number], packet['from'], msg_str)
|
||||
|
||||
def send_message(message, destination=BROADCAST_NUM, channel=0):
|
||||
|
||||
def send_message(message, destination=BROADCAST_NUM, channel=0):
|
||||
myid = globals.myNodeNum
|
||||
send_on_channel = 0
|
||||
channel_id = globals.channel_list[channel]
|
||||
@@ -136,11 +137,32 @@ def send_message(message, destination=BROADCAST_NUM, channel=0):
|
||||
if channel_id not in globals.all_messages:
|
||||
globals.all_messages[channel_id] = []
|
||||
|
||||
# Handle timestamp logic
|
||||
current_timestamp = int(datetime.now().timestamp()) # Get current timestamp
|
||||
current_hour = datetime.fromtimestamp(current_timestamp).strftime('%Y-%m-%d %H:00')
|
||||
|
||||
# Retrieve the last timestamp if available
|
||||
channel_messages = globals.all_messages[channel_id]
|
||||
if channel_messages:
|
||||
# Check the last entry for a timestamp
|
||||
for entry in reversed(channel_messages):
|
||||
if entry[0].startswith("--"):
|
||||
last_hour = entry[0].strip("- ").strip()
|
||||
break
|
||||
else:
|
||||
last_hour = None
|
||||
else:
|
||||
last_hour = None
|
||||
|
||||
# Add a new timestamp if it's a new hour
|
||||
if last_hour != current_hour:
|
||||
globals.all_messages[channel_id].append((f"-- {current_hour} --", ""))
|
||||
|
||||
globals.all_messages[channel_id].append((globals.sent_message_prefix + globals.ack_unknown_str + ": ", message))
|
||||
|
||||
timestamp = save_message_to_db(channel_id, myid, message)
|
||||
|
||||
ack_naks[sent_message_data.id] = {'channel' : channel_id, 'messageIndex' : len(globals.all_messages[channel_id]) - 1, 'timestamp' : timestamp }
|
||||
ack_naks[sent_message_data.id] = {'channel': channel_id, 'messageIndex': len(globals.all_messages[channel_id]) - 1, 'timestamp': timestamp}
|
||||
|
||||
def send_traceroute():
|
||||
r = mesh_pb2.RouteDiscovery()
|
||||
|
||||
@@ -40,14 +40,13 @@ def save_changes(interface, menu_path, modified_settings):
|
||||
|
||||
elif menu_path[1] == "User Settings": # for user configs
|
||||
config_category = "User Settings"
|
||||
long_name = modified_settings.get("longName", None)
|
||||
short_name = modified_settings.get("shortName", None)
|
||||
#TODO add is_licensed
|
||||
node.setOwner(long_name, short_name, is_licensed=False)
|
||||
logging.info(f"Updated {config_category} with Long Name: {long_name} and Short Name {short_name}")
|
||||
long_name = modified_settings.get("longName")
|
||||
short_name = modified_settings.get("shortName")
|
||||
is_licensed = modified_settings.get("isLicensed")
|
||||
node.setOwner(long_name, short_name, is_licensed)
|
||||
logging.info(f"Updated {config_category} with Long Name: {long_name} and Short Name {short_name} and Licensed Mode {is_licensed}")
|
||||
return
|
||||
|
||||
|
||||
elif menu_path[1] == "Channels": # for channel configs
|
||||
config_category = "Channels"
|
||||
|
||||
@@ -80,8 +79,6 @@ def save_changes(interface, menu_path, modified_settings):
|
||||
else:
|
||||
config_category = None
|
||||
|
||||
|
||||
|
||||
for config_item, new_value in modified_settings.items():
|
||||
# Check if the category exists in localConfig
|
||||
if hasattr(node.localConfig, config_category):
|
||||
|
||||
86
settings.py
86
settings.py
@@ -1,14 +1,13 @@
|
||||
import curses
|
||||
import meshtastic.serial_interface
|
||||
|
||||
from save_to_radio import settings_factory_reset, settings_reboot, settings_reset_nodedb, settings_shutdown
|
||||
from ui.menus import generate_menu_from_protobuf
|
||||
from input_handlers import get_bool_selection, get_repeated_input, get_user_input, get_enum_input, get_fixed32_input
|
||||
from save_to_radio import save_changes
|
||||
from ui.colors import setup_colors
|
||||
|
||||
import logging
|
||||
|
||||
from save_to_radio import settings_factory_reset, settings_reboot, settings_reset_nodedb, settings_shutdown, save_changes
|
||||
from ui.menus import generate_menu_from_protobuf
|
||||
from input_handlers import get_bool_selection, get_repeated_input, get_user_input, get_enum_input, get_fixed32_input
|
||||
from ui.colors import setup_colors
|
||||
from utilities.arg_parser import setup_parser
|
||||
from utilities.interfaces import initialize_interface
|
||||
import globals
|
||||
|
||||
def display_menu(current_menu, menu_path, selected_index, show_save_option):
|
||||
global menu_win
|
||||
@@ -61,7 +60,7 @@ def display_menu(current_menu, menu_path, selected_index, show_save_option):
|
||||
|
||||
menu_win.refresh()
|
||||
|
||||
def settings_menu(sdscr, interface):
|
||||
def settings_menu(stdscr, interface):
|
||||
|
||||
menu = generate_menu_from_protobuf(interface)
|
||||
current_menu = menu["Main Menu"]
|
||||
@@ -119,60 +118,50 @@ def settings_menu(sdscr, interface):
|
||||
if confirmation == "True":
|
||||
settings_reboot(interface)
|
||||
logging.info(f"Node Reboot Requested by menu")
|
||||
continue
|
||||
|
||||
break
|
||||
elif selected_option == "Reset Node DB":
|
||||
confirmation = get_bool_selection("Are you sure you want to Reset Node DB?", 0)
|
||||
if confirmation == "True":
|
||||
settings_reset_nodedb(interface)
|
||||
logging.info(f"Node DB Reset Requested by menu")
|
||||
continue
|
||||
break
|
||||
elif selected_option == "Shutdown":
|
||||
confirmation = get_bool_selection("Are you sure you want to Shutdown?", 0)
|
||||
if confirmation == "True":
|
||||
settings_shutdown(interface)
|
||||
logging.info(f"Node Shutdown Requested by menu")
|
||||
continue
|
||||
break
|
||||
elif selected_option == "Factory Reset":
|
||||
confirmation = get_bool_selection("Are you sure you want to Factory Reset?", 0)
|
||||
if confirmation == "True":
|
||||
settings_factory_reset(interface)
|
||||
logging.info(f"Factory Reset Requested by menu")
|
||||
continue
|
||||
field_info = current_menu.get(selected_option)
|
||||
break
|
||||
|
||||
field_info = current_menu.get(selected_option)
|
||||
if isinstance(field_info, tuple):
|
||||
field, current_value = field_info
|
||||
|
||||
if selected_option == 'longName' or selected_option == 'shortName':
|
||||
new_value = get_user_input(f"Current value for {selected_option}: {current_value}")
|
||||
|
||||
modified_settings[selected_option] = (new_value)
|
||||
current_menu[selected_option] = (field, new_value)
|
||||
if selected_option in ['longName', 'shortName', 'isLicensed']:
|
||||
if selected_option in ['longName', 'shortName']:
|
||||
new_value = get_user_input(f"Current value for {selected_option}: {current_value}")
|
||||
current_menu[selected_option] = (field, new_value)
|
||||
|
||||
elif selected_option == 'isLicensed':
|
||||
new_value = get_bool_selection(f"Current value for {selected_option}: {current_value}", str(current_value))
|
||||
new_value = new_value == "True"
|
||||
current_menu[selected_option] = (field, new_value)
|
||||
|
||||
for option, (field, value) in current_menu.items():
|
||||
modified_settings[option] = value
|
||||
|
||||
elif field.type == 8: # Handle boolean type
|
||||
new_value = get_bool_selection(selected_option, str(current_value))
|
||||
try:
|
||||
# Validate and convert input to a valid boolean
|
||||
if isinstance(new_value, str):
|
||||
# Handle string representations of booleans
|
||||
new_value_lower = new_value.lower()
|
||||
if new_value_lower in ("true", "yes", "1", "on"):
|
||||
new_value = True
|
||||
elif new_value_lower in ("false", "no", "0", "off"):
|
||||
new_value = False
|
||||
else:
|
||||
raise ValueError("Invalid string for boolean")
|
||||
else:
|
||||
# Convert other types directly to bool
|
||||
new_value = bool(new_value)
|
||||
|
||||
except ValueError as e:
|
||||
logging.info(f"Invalid input for boolean: {e}")
|
||||
|
||||
new_value = new_value == "True"
|
||||
|
||||
elif field.label == field.LABEL_REPEATED: # Handle repeated field
|
||||
new_value = get_repeated_input(current_value)
|
||||
new_value = current_value if new_value is None else [int(item) for item in new_value]
|
||||
|
||||
elif field.enum_type: # Enum field
|
||||
enum_options = [v.name for v in field.enum_type.values]
|
||||
@@ -189,20 +178,16 @@ def settings_menu(sdscr, interface):
|
||||
new_value = get_user_input(f"Current value for {selected_option}: {current_value}")
|
||||
new_value = current_value if new_value is None else float(new_value)
|
||||
|
||||
|
||||
else: # Handle other field types
|
||||
new_value = get_user_input(f"Current value for {selected_option}: {current_value}")
|
||||
new_value = current_value if new_value is None else new_value
|
||||
|
||||
# Navigate to the correct nested dictionary based on the menu_path
|
||||
current_nested = modified_settings
|
||||
|
||||
for key in menu_path[3:]: # Skip "Main Menu"
|
||||
current_nested = current_nested.setdefault(key, {})
|
||||
modified_settings = modified_settings.setdefault(key, {})
|
||||
|
||||
# Add the new value to the appropriate level
|
||||
current_nested[selected_option] = new_value
|
||||
modified_settings[selected_option] = new_value
|
||||
|
||||
# modified_settings[selected_option] = (new_value)
|
||||
current_menu[selected_option] = (field, new_value)
|
||||
else:
|
||||
current_menu = current_menu[selected_option]
|
||||
@@ -225,8 +210,11 @@ def settings_menu(sdscr, interface):
|
||||
selected_index = 0
|
||||
|
||||
elif key == 27: # Escape key
|
||||
menu_win.clear()
|
||||
menu_win.refresh()
|
||||
break
|
||||
|
||||
|
||||
def main(stdscr):
|
||||
logging.basicConfig( # Run `tail -f client.log` in another terminal to view live
|
||||
filename="settings.log",
|
||||
@@ -237,11 +225,11 @@ def main(stdscr):
|
||||
curses.curs_set(0)
|
||||
stdscr.keypad(True)
|
||||
|
||||
interface = meshtastic.serial_interface.SerialInterface()
|
||||
parser = setup_parser()
|
||||
args = parser.parse_args()
|
||||
globals.interface = initialize_interface(args)
|
||||
|
||||
stdscr.clear()
|
||||
stdscr.refresh()
|
||||
settings_menu(stdscr, interface)
|
||||
settings_menu(stdscr, globals.interface)
|
||||
|
||||
if __name__ == "__main__":
|
||||
curses.wrapper(main)
|
||||
420
ui/curses_ui.py
420
ui/curses_ui.py
@@ -7,18 +7,65 @@ from message_handlers.tx_handler import send_message, send_traceroute
|
||||
import ui.dialog
|
||||
from ui.colors import setup_colors
|
||||
|
||||
def get_msg_window_lines():
|
||||
packetlog_height = packetlog_win.getmaxyx()[0] if globals.display_log else 0
|
||||
return messages_box.getmaxyx()[0] - 2 - packetlog_height
|
||||
|
||||
def refresh_pad(window):
|
||||
win_height = channel_box.getmaxyx()[0]
|
||||
|
||||
selected_item = globals.selected_channel
|
||||
pad = channel_pad
|
||||
box = channel_box
|
||||
lines = box.getmaxyx()[0] - 2
|
||||
start_index = max(0, selected_item - (win_height - 3)) # Leave room for borders
|
||||
|
||||
if(window == 1):
|
||||
pad = messages_pad
|
||||
box = messages_box
|
||||
lines = get_msg_window_lines()
|
||||
selected_item = globals.selected_message
|
||||
start_index = globals.selected_message
|
||||
|
||||
if(window == 2):
|
||||
pad = nodes_pad
|
||||
box = nodes_box
|
||||
lines = box.getmaxyx()[0] - 2
|
||||
selected_item = globals.selected_node
|
||||
start_index = max(0, selected_item - (win_height - 3)) # Leave room for borders
|
||||
|
||||
|
||||
pad.refresh(start_index, 0,
|
||||
box.getbegyx()[0] + 1, box.getbegyx()[1] + 1,
|
||||
box.getbegyx()[0] + lines, box.getbegyx()[1] + box.getmaxyx()[1] - 2)
|
||||
|
||||
def highlight_line(highlight, window, line):
|
||||
pad = channel_pad
|
||||
select_len = 0
|
||||
color = curses.color_pair(1)
|
||||
|
||||
if(window == 2):
|
||||
pad = nodes_pad
|
||||
select_len = len(get_name_from_number(globals.node_list[line], "long"))
|
||||
|
||||
if(window == 0):
|
||||
channel = list(globals.all_messages.keys())[line]
|
||||
win_width = channel_box.getmaxyx()[1]
|
||||
|
||||
if(isinstance(channel, int)):
|
||||
channel = get_name_from_number(channel, type="long")
|
||||
select_len = min(len(channel), win_width - 4)
|
||||
|
||||
if line == globals.selected_channel and highlight == False:
|
||||
color = curses.color_pair(2)
|
||||
|
||||
pad.chgat(line, 1, select_len, color | curses.A_REVERSE if highlight else color)
|
||||
|
||||
def add_notification(channel_number):
|
||||
handle_notification(channel_number, add=True)
|
||||
globals.notifications.add(channel_number)
|
||||
|
||||
def remove_notification(channel_number):
|
||||
handle_notification(channel_number, add=False)
|
||||
channel_win.box()
|
||||
|
||||
def handle_notification(channel_number, add=True):
|
||||
if add:
|
||||
globals.notifications.add(channel_number) # Add the channel to the notification tracker
|
||||
else:
|
||||
globals.notifications.discard(channel_number) # Remove the channel from the notification tracker
|
||||
globals.notifications.discard(channel_number)
|
||||
|
||||
def draw_text_field(win, text):
|
||||
win.border()
|
||||
@@ -59,142 +106,144 @@ def draw_splash(stdscr):
|
||||
|
||||
|
||||
def draw_channel_list():
|
||||
|
||||
channel_win.clear()
|
||||
win_height, win_width = channel_win.getmaxyx()
|
||||
channel_pad.clear()
|
||||
win_height, win_width = channel_box.getmaxyx()
|
||||
start_index = max(0, globals.selected_channel - (win_height - 3)) # Leave room for borders
|
||||
|
||||
for i, channel in enumerate(list(globals.all_messages.keys())[start_index:], start=0):
|
||||
channel_pad.resize(len(globals.all_messages), channel_box.getmaxyx()[1])
|
||||
|
||||
for i, channel in enumerate(list(globals.all_messages.keys())):
|
||||
# Convert node number to long name if it's an integer
|
||||
if isinstance(channel, int):
|
||||
channel = get_name_from_number(channel, type='long')
|
||||
|
||||
# Determine whether to add the notification
|
||||
notification = " " + globals.notification_symbol if start_index + i in globals.notifications else ""
|
||||
notification = " " + globals.notification_symbol if i in globals.notifications else ""
|
||||
|
||||
# Truncate the channel name if it's too long to fit in the window
|
||||
truncated_channel = channel[:win_width - 5] + '-' if len(channel) > win_width - 5 else channel
|
||||
if i < win_height - 2 : # Check if there is enough space in the window
|
||||
if start_index + i == globals.selected_channel and globals.current_window == 0:
|
||||
channel_win.addstr(i + 1, 1, truncated_channel + notification, curses.color_pair(1) | curses.A_REVERSE)
|
||||
if i == globals.selected_channel:
|
||||
if globals.current_window == 0:
|
||||
channel_pad.addstr(i, 1, truncated_channel + notification, curses.color_pair(1) | curses.A_REVERSE)
|
||||
remove_notification(globals.selected_channel)
|
||||
else:
|
||||
channel_win.addstr(i + 1, 1, truncated_channel + notification, curses.color_pair(1))
|
||||
channel_win.box()
|
||||
channel_win.refresh()
|
||||
channel_pad.addstr(i, 1, truncated_channel + notification, curses.color_pair(2))
|
||||
else:
|
||||
channel_pad.addstr(i, 1, truncated_channel + notification, curses.color_pair(1))
|
||||
|
||||
channel_box.attrset(curses.color_pair(2) if globals.current_window == 0 else curses.color_pair(0))
|
||||
channel_box.box()
|
||||
channel_box.attrset(curses.color_pair(0))
|
||||
channel_box.refresh()
|
||||
|
||||
def draw_messages_window():
|
||||
refresh_pad(0)
|
||||
|
||||
def draw_messages_window(scroll_to_bottom = False):
|
||||
"""Update the messages window based on the selected channel and scroll position."""
|
||||
messages_win.clear()
|
||||
messages_pad.clear()
|
||||
|
||||
channel = globals.channel_list[globals.selected_channel]
|
||||
|
||||
if channel in globals.all_messages:
|
||||
messages = globals.all_messages[channel]
|
||||
num_messages = len(messages)
|
||||
max_messages = messages_win.getmaxyx()[0] - 2 # Max messages that fit in the window
|
||||
|
||||
# Adjust for packetlog height if log is visible
|
||||
if globals.display_log:
|
||||
packetlog_height = packetlog_win.getmaxyx()[0]
|
||||
max_messages -= packetlog_height - 1
|
||||
if max_messages < 1:
|
||||
max_messages = 1
|
||||
msg_line_count = 0
|
||||
|
||||
# Calculate the scroll position based on the current selection
|
||||
max_scroll_position = max(0, num_messages - max_messages)
|
||||
start_index = max(0, min(globals.selected_message, max_scroll_position))
|
||||
|
||||
# Dynamically calculate max_messages based on visible messages and wraps
|
||||
row = 1
|
||||
visible_message_count = 0
|
||||
for index, (prefix, message) in enumerate(messages[start_index:], start=start_index):
|
||||
row = 0
|
||||
for (prefix, message) in messages:
|
||||
full_message = f"{prefix}{message}"
|
||||
wrapped_lines = textwrap.wrap(full_message, messages_win.getmaxyx()[1] - 2)
|
||||
|
||||
if row + len(wrapped_lines) - 1 > messages_win.getmaxyx()[0] - 2: # Check if it fits in the window
|
||||
break
|
||||
|
||||
visible_message_count += 1
|
||||
row += len(wrapped_lines)
|
||||
|
||||
# Adjust max_messages to match visible messages
|
||||
max_messages = visible_message_count
|
||||
|
||||
# Re-render the visible messages
|
||||
row = 1
|
||||
for index, (prefix, message) in enumerate(messages[start_index:start_index + max_messages], start=start_index):
|
||||
full_message = f"{prefix}{message}"
|
||||
wrapped_lines = textwrap.wrap(full_message, messages_win.getmaxyx()[1] - 2)
|
||||
wrapped_lines = textwrap.wrap(full_message, messages_box.getmaxyx()[1] - 2)
|
||||
msg_line_count += len(wrapped_lines)
|
||||
messages_pad.resize(msg_line_count, messages_box.getmaxyx()[1])
|
||||
|
||||
for line in wrapped_lines:
|
||||
# Highlight the row if it's the selected message
|
||||
if index == globals.selected_message and globals.current_window == 1:
|
||||
color = curses.A_REVERSE # Highlighted row color
|
||||
else:
|
||||
color = curses.color_pair(4) if prefix.startswith(globals.sent_message_prefix) else curses.color_pair(3)
|
||||
messages_win.addstr(row, 1, line, color)
|
||||
color = curses.color_pair(1) if prefix.startswith("--") else (curses.color_pair(3) if prefix.startswith(globals.sent_message_prefix) else curses.color_pair(2))
|
||||
messages_pad.addstr(row, 1, line, color)
|
||||
row += 1
|
||||
|
||||
messages_win.box()
|
||||
messages_win.refresh()
|
||||
messages_box.attrset(curses.color_pair(2) if globals.current_window == 1 else curses.color_pair(0))
|
||||
messages_box.box()
|
||||
messages_box.attrset(curses.color_pair(0))
|
||||
messages_box.refresh()
|
||||
|
||||
if(scroll_to_bottom):
|
||||
globals.selected_message = max(msg_line_count - get_msg_window_lines(), 0)
|
||||
else:
|
||||
globals.selected_message = max(min(globals.selected_message, msg_line_count - get_msg_window_lines()), 0)
|
||||
|
||||
refresh_pad(1)
|
||||
|
||||
draw_packetlog_win()
|
||||
|
||||
|
||||
def draw_node_list():
|
||||
|
||||
nodes_win.clear()
|
||||
win_height = nodes_win.getmaxyx()[0]
|
||||
nodes_pad.clear()
|
||||
win_height = nodes_box.getmaxyx()[0]
|
||||
start_index = max(0, globals.selected_node - (win_height - 3)) # Calculate starting index based on selected node and window height
|
||||
|
||||
for i, node in enumerate(globals.node_list[start_index:], start=1):
|
||||
if i < win_height - 1 : # Check if there is enough space in the window
|
||||
if globals.selected_node + 1 == start_index + i and globals.current_window == 2:
|
||||
nodes_win.addstr(i, 1, get_name_from_number(node, "long"), curses.color_pair(1) | curses.A_REVERSE)
|
||||
else:
|
||||
nodes_win.addstr(i, 1, get_name_from_number(node, "long"), curses.color_pair(1))
|
||||
nodes_pad.resize(len(globals.node_list), nodes_box.getmaxyx()[1])
|
||||
|
||||
nodes_win.box()
|
||||
nodes_win.refresh()
|
||||
for i, node in enumerate(globals.node_list):
|
||||
if globals.selected_node == i and globals.current_window == 2:
|
||||
nodes_pad.addstr(i, 1, get_name_from_number(node, "long"), curses.color_pair(1) | curses.A_REVERSE)
|
||||
else:
|
||||
nodes_pad.addstr(i, 1, get_name_from_number(node, "long"), curses.color_pair(1))
|
||||
|
||||
nodes_box.attrset(curses.color_pair(2) if globals.current_window == 2 else curses.color_pair(0))
|
||||
nodes_box.box()
|
||||
nodes_box.attrset(curses.color_pair(0))
|
||||
nodes_box.refresh()
|
||||
|
||||
def select_channels(direction):
|
||||
channel_list_length = len(globals.channel_list)
|
||||
globals.selected_channel += direction
|
||||
refresh_pad(2)
|
||||
|
||||
if globals.selected_channel < 0:
|
||||
globals.selected_channel = channel_list_length - 1
|
||||
elif globals.selected_channel >= channel_list_length:
|
||||
globals.selected_channel = 0
|
||||
def select_channel(idx):
|
||||
old_selected_channel = globals.selected_channel
|
||||
globals.selected_channel = max(0, min(idx, len(globals.channel_list) - 1))
|
||||
draw_messages_window(True)
|
||||
|
||||
draw_channel_list()
|
||||
draw_messages_window()
|
||||
# For now just re-draw channel list when clearing notifications, we can probably make this more efficient
|
||||
if globals.selected_channel in globals.notifications:
|
||||
remove_notification(globals.selected_channel)
|
||||
draw_channel_list()
|
||||
return
|
||||
highlight_line(False, 0, old_selected_channel)
|
||||
highlight_line(True, 0, globals.selected_channel)
|
||||
refresh_pad(0)
|
||||
|
||||
def select_messages(direction):
|
||||
messages_length = len(globals.all_messages[globals.channel_list[globals.selected_channel]])
|
||||
def scroll_channels(direction):
|
||||
new_selected_channel = globals.selected_channel + direction
|
||||
|
||||
if new_selected_channel < 0:
|
||||
new_selected_channel = len(globals.channel_list) - 1
|
||||
elif new_selected_channel >= len(globals.channel_list):
|
||||
new_selected_channel = 0
|
||||
|
||||
select_channel(new_selected_channel)
|
||||
|
||||
def scroll_messages(direction):
|
||||
globals.selected_message += direction
|
||||
|
||||
if globals.selected_message < 0:
|
||||
globals.selected_message = messages_length - 1
|
||||
elif globals.selected_message >= messages_length:
|
||||
globals.selected_message = 0
|
||||
msg_line_count = messages_pad.getmaxyx()[0]
|
||||
globals.selected_message = max(0, min(globals.selected_message, msg_line_count - get_msg_window_lines()))
|
||||
|
||||
draw_messages_window()
|
||||
refresh_pad(1)
|
||||
|
||||
def select_nodes(direction):
|
||||
node_list_length = len(globals.node_list)
|
||||
globals.selected_node += direction
|
||||
def select_node(idx):
|
||||
old_selected_node = globals.selected_node
|
||||
globals.selected_node = max(0, min(idx, len(globals.node_list) - 1))
|
||||
|
||||
if globals.selected_node < 0:
|
||||
globals.selected_node = node_list_length - 1
|
||||
elif globals.selected_node >= node_list_length:
|
||||
globals.selected_node = 0
|
||||
highlight_line(False, 2, old_selected_node)
|
||||
highlight_line(True, 2, globals.selected_node)
|
||||
refresh_pad(2)
|
||||
|
||||
draw_node_list()
|
||||
def scroll_nodes(direction):
|
||||
new_selected_node = globals.selected_node + direction
|
||||
|
||||
if new_selected_node < 0:
|
||||
new_selected_node = len(globals.node_list) - 1
|
||||
elif new_selected_node >= len(globals.node_list):
|
||||
new_selected_node = 0
|
||||
|
||||
select_node(new_selected_node)
|
||||
|
||||
def draw_packetlog_win():
|
||||
|
||||
@@ -241,7 +290,7 @@ def draw_packetlog_win():
|
||||
|
||||
|
||||
def main_ui(stdscr):
|
||||
global messages_win, nodes_win, channel_win, function_win, packetlog_win
|
||||
global messages_pad, messages_box, nodes_pad, nodes_box, channel_pad, channel_box, function_win, packetlog_win
|
||||
stdscr.keypad(True)
|
||||
get_channels()
|
||||
|
||||
@@ -254,68 +303,106 @@ def main_ui(stdscr):
|
||||
nodes_width = 5 * (width // 16)
|
||||
messages_width = width - channel_width - nodes_width
|
||||
|
||||
channel_win = curses.newwin(height - 6, channel_width, 3, 0)
|
||||
messages_win = curses.newwin(height - 6, messages_width, 3, channel_width)
|
||||
packetlog_win = curses.newwin(int(height / 3), messages_width, height - int(height / 3) - 3, channel_width)
|
||||
nodes_win = curses.newwin(height - 6, nodes_width, 3, channel_width + messages_width)
|
||||
channel_box = curses.newwin(height - 6, channel_width, 3, 0)
|
||||
messages_box = curses.newwin(height - 6, messages_width, 3, channel_width)
|
||||
nodes_box = curses.newwin(height - 6, nodes_width, 3, channel_width + messages_width)
|
||||
|
||||
# Will be resized to what we need when drawn
|
||||
messages_pad = curses.newpad(1, 1)
|
||||
nodes_pad = curses.newpad(1,1)
|
||||
channel_pad = curses.newpad(1,1)
|
||||
|
||||
function_win = curses.newwin(3, width, height - 3, 0)
|
||||
packetlog_win = curses.newwin(int(height / 3), messages_width, height - int(height / 3) - 3, channel_width)
|
||||
|
||||
draw_centered_text_field(function_win, f"↑→↓← = Select ENTER = Send ` = Settings / = Toggle Log ESC = Quit")
|
||||
|
||||
# Enable scrolling for messages and nodes windows
|
||||
messages_win.scrollok(True)
|
||||
nodes_win.scrollok(True)
|
||||
channel_win.scrollok(True)
|
||||
|
||||
draw_channel_list()
|
||||
draw_node_list()
|
||||
draw_messages_window()
|
||||
draw_centered_text_field(function_win, f"↑→↓← = Select ENTER = Send ` = Settings ^P = Packet Log ESC = Quit")
|
||||
|
||||
# Draw boxes around windows
|
||||
channel_win.box()
|
||||
channel_box.attrset(curses.color_pair(2))
|
||||
channel_box.box()
|
||||
channel_box.attrset(curses.color_pair(0))
|
||||
entry_win.box()
|
||||
messages_win.box()
|
||||
nodes_win.box()
|
||||
nodes_box.box()
|
||||
messages_box.box()
|
||||
function_win.box()
|
||||
|
||||
# Refresh all windows
|
||||
entry_win.refresh()
|
||||
messages_win.refresh()
|
||||
nodes_win.refresh()
|
||||
channel_win.refresh()
|
||||
function_win.refresh()
|
||||
|
||||
channel_box.refresh()
|
||||
function_win.refresh()
|
||||
nodes_box.refresh()
|
||||
messages_box.refresh()
|
||||
input_text = ""
|
||||
|
||||
entry_win.keypad(True)
|
||||
curses.curs_set(1)
|
||||
|
||||
draw_channel_list()
|
||||
draw_node_list()
|
||||
draw_messages_window(True)
|
||||
|
||||
while True:
|
||||
draw_text_field(entry_win, f"Input: {input_text}")
|
||||
draw_text_field(entry_win, f"Input: {input_text[-(width - 10):]}")
|
||||
|
||||
# Get user input from entry window
|
||||
entry_win.move(1, len(input_text) + 8)
|
||||
char = entry_win.getch()
|
||||
char = entry_win.get_wch()
|
||||
|
||||
# draw_debug(f"Keypress: {char}")
|
||||
|
||||
if char == curses.KEY_UP:
|
||||
if globals.current_window == 0:
|
||||
select_channels(-1)
|
||||
globals.selected_message = len(globals.all_messages[globals.channel_list[globals.selected_channel]]) - 1
|
||||
scroll_channels(-1)
|
||||
elif globals.current_window == 1:
|
||||
select_messages(-1)
|
||||
scroll_messages(-1)
|
||||
elif globals.current_window == 2:
|
||||
select_nodes(-1)
|
||||
scroll_nodes(-1)
|
||||
|
||||
elif char == curses.KEY_DOWN:
|
||||
if globals.current_window == 0:
|
||||
select_channels(1)
|
||||
globals.selected_message = len(globals.all_messages[globals.channel_list[globals.selected_channel]]) - 1
|
||||
scroll_channels(1)
|
||||
elif globals.current_window == 1:
|
||||
select_messages(1)
|
||||
scroll_messages(1)
|
||||
elif globals.current_window == 2:
|
||||
select_nodes(1)
|
||||
scroll_nodes(1)
|
||||
|
||||
elif char == curses.KEY_HOME:
|
||||
if globals.current_window == 0:
|
||||
select_channel(0)
|
||||
elif globals.current_window == 1:
|
||||
globals.selected_message = 0
|
||||
refresh_pad(1)
|
||||
elif globals.current_window == 2:
|
||||
select_node(0)
|
||||
|
||||
elif char == curses.KEY_END:
|
||||
if globals.current_window == 0:
|
||||
select_channel(len(globals.channel_list) - 1)
|
||||
elif globals.current_window == 1:
|
||||
msg_line_count = messages_pad.getmaxyx()[0]
|
||||
globals.selected_message = max(msg_line_count - get_msg_window_lines(), 0)
|
||||
refresh_pad(1)
|
||||
elif globals.current_window == 2:
|
||||
select_node(len(globals.node_list) - 1)
|
||||
|
||||
elif char == curses.KEY_PPAGE:
|
||||
if globals.current_window == 0:
|
||||
select_channel(globals.selected_channel - (channel_box.getmaxyx()[0] - 2)) # select_channel will bounds check for us
|
||||
elif globals.current_window == 1:
|
||||
globals.selected_message = max(globals.selected_message - get_msg_window_lines(), 0)
|
||||
refresh_pad(1)
|
||||
elif globals.current_window == 2:
|
||||
select_node(globals.selected_node - (nodes_box.getmaxyx()[0] - 2)) # select_node will bounds check for us
|
||||
|
||||
elif char == curses.KEY_NPAGE:
|
||||
if globals.current_window == 0:
|
||||
select_channel(globals.selected_channel + (channel_box.getmaxyx()[0] - 2)) # select_channel will bounds check for us
|
||||
elif globals.current_window == 1:
|
||||
msg_line_count = messages_pad.getmaxyx()[0]
|
||||
globals.selected_message = min(globals.selected_message + get_msg_window_lines(), msg_line_count - get_msg_window_lines())
|
||||
refresh_pad(1)
|
||||
elif globals.current_window == 2:
|
||||
select_node(globals.selected_node + (nodes_box.getmaxyx()[0] - 2)) # select_node will bounds check for us
|
||||
|
||||
elif char == curses.KEY_LEFT or char == curses.KEY_RIGHT:
|
||||
delta = -1 if char == curses.KEY_LEFT else 1
|
||||
@@ -323,25 +410,57 @@ def main_ui(stdscr):
|
||||
old_window = globals.current_window
|
||||
globals.current_window = (globals.current_window + delta) % 3
|
||||
|
||||
if old_window == 0 or globals.current_window == 0:
|
||||
draw_channel_list()
|
||||
if old_window == 1 or globals.current_window == 1:
|
||||
draw_messages_window()
|
||||
if old_window == 2 or globals.current_window == 2:
|
||||
draw_node_list()
|
||||
if old_window == 0:
|
||||
channel_box.attrset(curses.color_pair(0))
|
||||
channel_box.box()
|
||||
channel_box.refresh()
|
||||
highlight_line(False, 0, globals.selected_channel)
|
||||
refresh_pad(0)
|
||||
if old_window == 1:
|
||||
messages_box.attrset(curses.color_pair(0))
|
||||
messages_box.box()
|
||||
messages_box.refresh()
|
||||
refresh_pad(1)
|
||||
elif old_window == 2:
|
||||
nodes_box.attrset(curses.color_pair(0))
|
||||
nodes_box.box()
|
||||
nodes_box.refresh()
|
||||
highlight_line(False, 2, globals.selected_node)
|
||||
refresh_pad(2)
|
||||
|
||||
if globals.current_window == 0:
|
||||
channel_box.attrset(curses.color_pair(2))
|
||||
channel_box.box()
|
||||
channel_box.attrset(curses.color_pair(0))
|
||||
channel_box.refresh()
|
||||
highlight_line(True, 0, globals.selected_channel)
|
||||
refresh_pad(0)
|
||||
elif globals.current_window == 1:
|
||||
messages_box.attrset(curses.color_pair(2))
|
||||
messages_box.box()
|
||||
messages_box.attrset(curses.color_pair(0))
|
||||
messages_box.refresh()
|
||||
refresh_pad(1)
|
||||
elif globals.current_window == 2:
|
||||
nodes_box.attrset(curses.color_pair(2))
|
||||
nodes_box.box()
|
||||
nodes_box.attrset(curses.color_pair(0))
|
||||
nodes_box.refresh()
|
||||
highlight_line(True, 2, globals.selected_node)
|
||||
refresh_pad(2)
|
||||
|
||||
# Check for Esc
|
||||
elif char == 27:
|
||||
elif char == chr(27):
|
||||
break
|
||||
|
||||
# Check for Ctrl + t
|
||||
elif char == 20:
|
||||
elif char == chr(20):
|
||||
send_traceroute()
|
||||
curses.curs_set(0) # Hide cursor
|
||||
ui.dialog.dialog(stdscr, "Traceroute Sent", "Results will appear in messages window.\nNote: Traceroute is limited to once every 30 seconds.")
|
||||
curses.curs_set(1) # Show cursor again
|
||||
|
||||
elif char == curses.KEY_ENTER or char == 10 or char == 13:
|
||||
elif char in (chr(curses.KEY_ENTER), chr(10), chr(13)):
|
||||
if globals.current_window == 2:
|
||||
node_list = globals.node_list
|
||||
if node_list[globals.selected_node] not in globals.channel_list:
|
||||
@@ -354,19 +473,19 @@ def main_ui(stdscr):
|
||||
|
||||
draw_node_list()
|
||||
draw_channel_list()
|
||||
draw_messages_window()
|
||||
draw_messages_window(True)
|
||||
|
||||
else:
|
||||
# Enter key pressed, send user input as message
|
||||
send_message(input_text, channel=globals.selected_channel)
|
||||
draw_messages_window()
|
||||
draw_messages_window(True)
|
||||
|
||||
# Clear entry window and reset input text
|
||||
input_text = ""
|
||||
entry_win.clear()
|
||||
# entry_win.refresh()
|
||||
|
||||
elif char == curses.KEY_BACKSPACE or char == 127:
|
||||
elif char in (curses.KEY_BACKSPACE, chr(127)):
|
||||
if input_text:
|
||||
input_text = input_text[:-1]
|
||||
y, x = entry_win.getyx()
|
||||
@@ -375,20 +494,25 @@ def main_ui(stdscr):
|
||||
entry_win.move(y, x - 1)
|
||||
entry_win.refresh()
|
||||
|
||||
elif char == 96: # ` Launch the settings interface
|
||||
elif char == "`": # ` Launch the settings interface
|
||||
curses.curs_set(0)
|
||||
settings_menu(stdscr, globals.interface)
|
||||
curses.curs_set(1)
|
||||
|
||||
elif char == 47:
|
||||
elif char == chr(16):
|
||||
# Display packet log
|
||||
if globals.display_log is False:
|
||||
globals.display_log = True
|
||||
draw_messages_window()
|
||||
draw_messages_window(True)
|
||||
else:
|
||||
globals.display_log = False
|
||||
packetlog_win.clear()
|
||||
draw_messages_window()
|
||||
draw_messages_window(True)
|
||||
else:
|
||||
# Append typed character to input text
|
||||
input_text += chr(char)
|
||||
if(isinstance(char, str)):
|
||||
input_text += char
|
||||
else:
|
||||
input_text += chr(char)
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ def generate_menu_from_protobuf(interface):
|
||||
|
||||
menu_structure["Main Menu"]["User Settings"] = {
|
||||
"longName": (None, current_user_config.get("longName", "Not Set")),
|
||||
"shortName": (None, current_user_config.get("shortName", "Not Set"))
|
||||
"shortName": (None, current_user_config.get("shortName", "Not Set")),
|
||||
"isLicensed": (None, current_user_config.get("isLicensed", "False"))
|
||||
}
|
||||
|
||||
else:
|
||||
|
||||
@@ -57,17 +57,15 @@ def convert_to_camel_case(string):
|
||||
|
||||
def get_name_from_number(number, type='long'):
|
||||
name = ""
|
||||
for node in globals.interface.nodes.values():
|
||||
nodes_snapshot = list(globals.interface.nodes.values())
|
||||
|
||||
for node in nodes_snapshot:
|
||||
if number == node['num']:
|
||||
if type == 'long':
|
||||
name = node['user']['longName']
|
||||
return name
|
||||
return node['user']['longName']
|
||||
elif type == 'short':
|
||||
name = node['user']['shortName']
|
||||
return name
|
||||
return node['user']['shortName']
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
name = str(decimal_to_hex(number)) # If long name not found, use the ID as string
|
||||
return name
|
||||
|
||||
# If no match is found, use the ID as a string
|
||||
return str(decimal_to_hex(number))
|
||||
Reference in New Issue
Block a user