diff --git a/LinuxMint/meshtastic-client_1.0.202512122007_amd64.deb b/LinuxMint/meshtastic-client_1.0.202512122007_amd64.deb new file mode 100644 index 0000000..ef7bbe2 Binary files /dev/null and b/LinuxMint/meshtastic-client_1.0.202512122007_amd64.deb differ diff --git a/LinuxMint/meshtastic_client.py b/LinuxMint/meshtastic_client.py index c05f6d5..699fe9e 100644 --- a/LinuxMint/meshtastic_client.py +++ b/LinuxMint/meshtastic_client.py @@ -196,11 +196,27 @@ class MeshtasticGUI: self.rootframe = ttk.Frame(self.root) self.rootframe.grid(row=0, column=0, sticky="nsew") self.rootframe.rowconfigure(0, weight=1) + self.rootframe.rowconfigure(1, weight=0) self.rootframe.columnconfigure(0, weight=1) self.paned = ttk.Panedwindow(self.rootframe, orient="horizontal") self.paned.grid(row=0, column=0, sticky="nsew") + + # Status bar (keeps non-chat info out of the Messages area) + self.status_conn_var = tk.StringVar(value="Conn: Disconnected") + self.status_pos_var = tk.StringVar(value="POS: -") + self.status_tel_var = tk.StringVar(value="TEL: -") + self.status_info_var = tk.StringVar(value="") + + self.statusbar = ttk.Frame(self.rootframe, padding=(6, 2)) + self.statusbar.grid(row=1, column=0, sticky="ew") + self.statusbar.columnconfigure(3, weight=1) + + ttk.Label(self.statusbar, textvariable=self.status_conn_var).grid(row=0, column=0, sticky="w", padx=(0, 12)) + ttk.Label(self.statusbar, textvariable=self.status_pos_var).grid(row=0, column=1, sticky="w", padx=(0, 12)) + ttk.Label(self.statusbar, textvariable=self.status_tel_var).grid(row=0, column=2, sticky="w", padx=(0, 12)) + ttk.Label(self.statusbar, textvariable=self.status_info_var).grid(row=0, column=3, sticky="ew") # Messages pane self.msg_frame = ttk.Frame(self.paned) self.msg_frame.rowconfigure(1, weight=1) @@ -344,7 +360,7 @@ class MeshtasticGUI: # Load persisted settings (from ./meshtastic_client_settings.json) if present self.load_settings(silent=True) self.apply_theme(self.current_theme) - self._append("Ready. Connection -> Connect (TCP/USB/BLE)") + self._status_update(info="Ready. Connection -> Connect (TCP/USB/BLE)") self._update_title_with_host() # UI helpers ------------------------------------------------------ @@ -355,6 +371,30 @@ class MeshtasticGUI: else: self.root.after(0, lambda: fn(*args, **kwargs)) + def _status_update(self, *, conn: Optional[str] = None, pos: Optional[str] = None, tel: Optional[str] = None, info: Optional[str] = None): + """Update the bottom status bar (UI thread safe).""" + def _do(): + ts = time.strftime("%H:%M:%S") + if conn is not None: + self.status_conn_var.set(f"Conn: {conn}") + if pos is not None: + self.status_pos_var.set(f"POS: {pos} @{ts}" if pos != "-" else "POS: -") + if tel is not None: + self.status_tel_var.set(f"TEL: {tel} @{ts}" if tel != "-" else "TEL: -") + if info is not None: + self.status_info_var.set(f"{info} @{ts}" if info else "") + self._ui(_do) + + def _status_event(self, kind: str, label: str): + """Convenience for updating POS/TEL and other packet status.""" + kind = (kind or "").upper().strip() + if kind == "POS": + self._status_update(pos=label) + elif kind == "TEL": + self._status_update(tel=label) + else: + self._status_update(info=f"{kind}: {label}" if kind else label) + def clear_messages(self): self._ui(lambda: self.txt_messages.delete("1.0", "end")) @@ -387,7 +427,7 @@ class MeshtasticGUI: if self.connected_evt.is_set(): return self.connected_evt.set() - self._append("[+] Connected") + self._status_update(conn="Connected", info="Connected") self.refresh_nodes() try: self._update_channels_from_iface() @@ -400,7 +440,7 @@ class MeshtasticGUI: def on_connection_lost(self, interface=None, **kwargs): def _lost(): self.connected_evt.clear() - self._append("[-] Connection lost") + self._status_update(conn="Disconnected", info="Connection lost") self._ui(_lost) def on_node_updated(self, node=None, interface=None, **kwargs): @@ -490,7 +530,7 @@ class MeshtasticGUI: } tag = tag_map.get(str(app_name or portnum), "INFO") # Avoid spamming for every non-text packet; show only brief line - self._append(f"[{tag}] {label}") + self._status_event(tag, label) self.refresh_nodes() @@ -499,10 +539,10 @@ class MeshtasticGUI: return try: self.iface.sendText("pong", destinationId=dest_id, wantAck=False) - self._append(f"[auto] pong -> {label}") + self._status_update(info=f"auto pong -> {label}") self._append_to_node_chat(str(dest_id), "[auto] pong") except Exception as e: - self._append(f"[auto] pong failed: {e}") + self._status_update(info=f"auto pong failed: {e}") # connection actions ---------------------------------------------- def set_ip_port(self): @@ -573,7 +613,7 @@ class MeshtasticGUI: if SerialInterface is None: self._ui(lambda: messagebox.showerror("Unavailable", "meshtastic.serial_interface not installed.")) return - self._append(f"Connecting Serial {port or '(auto)'} ...") + self._status_update(conn="Connecting (Serial)", info=f"Connecting Serial {port or '(auto)'}...") def run(): try: @@ -594,7 +634,7 @@ class MeshtasticGUI: if BLEInterface is None: self._ui(lambda: messagebox.showerror("Unavailable", "meshtastic.ble_interface not installed (needs bleak).")) return - self._append(f"Connecting BLE {address} ...") + self._status_update(conn="Connecting (BLE)", info=f"Connecting BLE {address}...") def run(): try: @@ -618,7 +658,7 @@ class MeshtasticGUI: except Exception: messagebox.showerror("Port", "Invalid port") return - self._append(f"Connecting TCP {host}:{port} ...") + self._status_update(conn="Connecting (TCP)", info=f"Connecting TCP {host}:{port}...") def run(): try: @@ -675,7 +715,7 @@ class MeshtasticGUI: if BLEInterface is None: messagebox.showerror("Unavailable", "meshtastic.ble_interface not installed (needs bleak).") return - self._append("Scanning BLE for Meshtastic devices ...") + self._status_update(conn="Scanning BLE", info="Scanning BLE for Meshtastic devices...") try: devices = BLEInterface.scan() except Exception as e: @@ -717,7 +757,7 @@ class MeshtasticGUI: pass self.iface = None self.connected_evt.clear() - self._append("[*] Disconnected") + self._status_update(conn="Disconnected", info="Disconnected") # send ------------------------------------------------------------ def _reset_channel_choices(self): @@ -1312,7 +1352,7 @@ class MeshtasticGUI: if not dest: messagebox.showerror("Traceroute", "Cannot determine node ID for traceroute.") return - self._append(f"[trace] Requesting traceroute to {self._node_label(nid)} ({dest})") + self._status_update(info=f"Traceroute -> {self._node_label(nid)} ({dest})") threading.Thread(target=self._do_traceroute, args=(dest,), daemon=True).start() def _cm_delete_node(self): @@ -1338,7 +1378,7 @@ class MeshtasticGUI: try: self.iface.localNode.removeNode(dest) # type: ignore[attr-defined] - self._append(f"[admin] Requested delete of node {label} ({dest})") + self._status_update(info=f"Admin: delete requested for {label} ({dest})") except Exception as e: messagebox.showerror("Delete node", f"Failed to delete node: {e}") return @@ -1700,7 +1740,7 @@ class MeshtasticGUI: elif hasattr(ch, "name"): ch.name = new_name ln.writeChannel(i) - self._append(f"[admin] Renamed channel {i} to '{new_name}'") + self._status_update(info=f"Admin: renamed channel {i} to '{new_name}'") self._update_channels_from_iface() listbox.delete(i) label = f"{i}: {new_name or '(no name)'}" @@ -1811,7 +1851,7 @@ class MeshtasticGUI: self._update_title_with_host() if not silent: - self._append(f"[settings] Loaded from {path}") + self._status_update(info=f"Settings loaded from {path}") def save_settings(self, silent: bool = False) -> None: """Save settings to JSON in the current working directory ('.').""" diff --git a/LinuxMint/meshtastic_client_settings.json b/LinuxMint/meshtastic_client_settings.json index 64f9a7e..6c4b92a 100644 --- a/LinuxMint/meshtastic_client_settings.json +++ b/LinuxMint/meshtastic_client_settings.json @@ -7,6 +7,6 @@ "ble_address": "", "theme": "dark", "channel_choice": "Public (broadcast)", - "window_geometry": "1500x820+201+355", - "sash_fraction": 0.4 + "window_geometry": "1585x961+240+220", + "sash_fraction": 0.38296529968454257 } \ No newline at end of file