Serial/Telnet Send Break and DTR/RTS tab controls; fix Help and hints; drop unused log path; docs
This commit is contained in:
+3
-1
@@ -148,6 +148,8 @@ Serial profiles open a terminal on a local serial port.
|
|||||||
|
|
||||||
The defaults are 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control, CR line endings, and DTR and RTS asserted.
|
The defaults are 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control, CR line endings, and DTR and RTS asserted.
|
||||||
|
|
||||||
|
While connected, right-click the tab to **Send Break** or toggle **DTR** and **RTS**. Telnet tabs also offer **Send Break**.
|
||||||
|
|
||||||
When the adapter is unplugged the session disconnects; with auto-reconnect on, the port is reopened when the device returns. Serial sessions support everything other terminals do: recording, triggers, broadcast, snippets, find and split panes.
|
When the adapter is unplugged the session disconnects; with auto-reconnect on, the port is reopened when the device returns. Serial sessions support everything other terminals do: recording, triggers, broadcast, snippets, find and split panes.
|
||||||
|
|
||||||
### VNC
|
### VNC
|
||||||
@@ -156,7 +158,7 @@ When the adapter is unplugged the session disconnects; with auto-reconnect on, t
|
|||||||
- **Display**: **View Only**, **Scale to Fit**, **Shared Session** and **Quality (0-9)**.
|
- **Display**: **View Only**, **Scale to Fit**, **Shared Session** and **Quality (0-9)**.
|
||||||
- **SSH Tunnel**: carry VNC traffic through SSH, with host, port, username and password, public key or SSH agent authentication.
|
- **SSH Tunnel**: carry VNC traffic through SSH, with host, port, username and password, public key or SSH agent authentication.
|
||||||
|
|
||||||
The viewer's toolbar shows the connection state and desktop size, and has buttons to switch between scaled and 1:1 display, refresh the screen, **Paste Clipboard** (send your local clipboard to the remote desktop) and **Ctrl+Alt+Del**. Click the desktop to give it keyboard focus.
|
The viewer's toolbar shows the connection state and desktop size, and has buttons to switch between scaled and 1:1 display, refresh the screen, **Paste Clipboard** (send your local clipboard to the remote desktop) and **Ctrl+Alt+Del**. Text the remote desktop copies arrives on your local clipboard. Click the desktop to give it keyboard focus.
|
||||||
|
|
||||||
Use SSH tunnelling for VNC whenever possible. If a VNC profile has no tunnel, Nexus Terminal warns that the traffic is unencrypted and offers **Connect Anyway**, **Edit Profile** or **Cancel**.
|
Use SSH tunnelling for VNC whenever possible. If a VNC profile has no tunnel, Nexus Terminal warns that the traffic is unencrypted and offers **Connect Anyway**, **Edit Profile** or **Cancel**.
|
||||||
|
|
||||||
|
|||||||
+25
-1
@@ -244,7 +244,8 @@ impl App {
|
|||||||
};
|
};
|
||||||
let profile = self.s.svc.sessions.profile(id);
|
let profile = self.s.svc.sessions.profile(id);
|
||||||
let view = self.view_for(&info, profile.as_ref());
|
let view = self.view_for(&info, profile.as_ref());
|
||||||
self.s.panes.insert(id.into(), Pane { info, view, recording: false, local_exited: false, connected_since: None, highlight_until: 0.0 });
|
let lines = profile.as_ref().and_then(|p| p.serial.as_ref()).map_or((true, true), |c| (c.dtr_on_connect, c.rts_on_connect));
|
||||||
|
self.s.panes.insert(id.into(), Pane { info, view, recording: false, local_exited: false, connected_since: None, highlight_until: 0.0, lines });
|
||||||
if tab {
|
if tab {
|
||||||
self.s.tabs.push(Tab { id: id.into(), activity: false, split: None });
|
self.s.tabs.push(Tab { id: id.into(), activity: false, split: None });
|
||||||
self.s.active = Some(id.into());
|
self.s.active = Some(id.into());
|
||||||
@@ -691,6 +692,29 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Telnet or serial break.
|
||||||
|
pub fn send_break(&mut self, id: &str) {
|
||||||
|
match self.s.svc.sessions.send_break(id) {
|
||||||
|
Ok(()) => self.s.info("Break sent"),
|
||||||
|
Err(e) => self.s.error(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles a serial modem line: `dtr` true for DTR, false for RTS.
|
||||||
|
pub fn toggle_line(&mut self, id: &str, dtr: bool) {
|
||||||
|
let Some((d, r)) = self.s.pane(id).map(|p| p.lines) else { return };
|
||||||
|
let (d, r) = if dtr { (!d, r) } else { (d, !r) };
|
||||||
|
match self.s.svc.sessions.set_serial_lines(id, d, r) {
|
||||||
|
Ok(()) => {
|
||||||
|
if let Some(p) = self.s.pane_mut(id) {
|
||||||
|
p.lines = (d, r);
|
||||||
|
}
|
||||||
|
self.s.info(format!("DTR {} · RTS {}", if d { "on" } else { "off" }, if r { "on" } else { "off" }));
|
||||||
|
}
|
||||||
|
Err(e) => self.s.error(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle_broadcast(&mut self) {
|
pub fn toggle_broadcast(&mut self) {
|
||||||
self.s.broadcast = !self.s.broadcast;
|
self.s.broadcast = !self.s.broadcast;
|
||||||
if !self.s.broadcast {
|
if !self.s.broadcast {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub fn title_bar(app: &mut App, ui: &mut Ui) {
|
|||||||
app.s.request(Request::ExportEncrypted(vec![]));
|
app.s.request(Request::ExportEncrypted(vec![]));
|
||||||
}
|
}
|
||||||
if item(ui, icon::FILE_ARROW_DOWN, "Import ~/.ssh/config", "") {
|
if item(ui, icon::FILE_ARROW_DOWN, "Import ~/.ssh/config", "") {
|
||||||
match app.s.svc.import_ssh_config(None, "Imported") {
|
match app.s.svc.import_ssh_config(None, "SSH Config") {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
app.s.success(format!("Imported {n} host(s) from ~/.ssh/config"));
|
app.s.success(format!("Imported {n} host(s) from ~/.ssh/config"));
|
||||||
app.s.reload_profiles();
|
app.s.reload_profiles();
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ fn main() -> eframe::Result {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--screenshot out.png] [--size WxH]");
|
println!(
|
||||||
|
"usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--connect NAME] [--open WINDOW] [--run-local CMD] [--screenshot out.png] [--size WxH] [--version]"
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ impl Recordings {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
if self.list.is_empty() {
|
if self.list.is_empty() {
|
||||||
if !self.loading {
|
if !self.loading {
|
||||||
widgets::empty(ui, icon::RECORD, "No recordings yet", "Use the record button on a terminal tab to capture a session.");
|
widgets::empty(ui, icon::RECORD, "No recordings yet", "Press Ctrl/Cmd+Shift+R in a terminal, or use Session → Toggle recording.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -692,7 +692,7 @@ If your server requires a time-based one-time password (TOTP) during login, stor
|
|||||||
## Jump Hosts (Bastions)
|
## Jump Hosts (Bastions)
|
||||||
If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Nexus Terminal tunnels through each hop automatically.
|
If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Nexus Terminal tunnels through each hop automatically.
|
||||||
## Port Forwarding
|
## Port Forwarding
|
||||||
You can set up **local**, **remote**, and **dynamic** (SOCKS proxy) port forwards in the connection editor. These start automatically when the session connects.
|
You can set up **local** and **remote** port forwards in the connection editor. These start automatically when the session connects.
|
||||||
## Proxy Support
|
## Proxy Support
|
||||||
Open the **Proxy** section in the profile editor to connect through a **SOCKS5** or **HTTP CONNECT** proxy, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured).
|
Open the **Proxy** section in the profile editor to connect through a **SOCKS5** or **HTTP CONNECT** proxy, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured).
|
||||||
## SFTP File Browser
|
## SFTP File Browser
|
||||||
@@ -738,7 +738,7 @@ Enable **Auto-Reconnect** in the Advanced section to reconnect automatically whe
|
|||||||
- **Device** — pick the port from the list (refresh after plugging in an adapter), e.g. `/dev/ttyUSB0`, `/dev/cu.usbserial-…` or `COM3`.
|
- **Device** — pick the port from the list (refresh after plugging in an adapter), e.g. `/dev/ttyUSB0`, `/dev/cu.usbserial-…` or `COM3`.
|
||||||
- **Line settings** — baud rate (default 115200), data bits, parity, stop bits and flow control (none, XON/XOFF, RTS/CTS).
|
- **Line settings** — baud rate (default 115200), data bits, parity, stop bits and flow control (none, XON/XOFF, RTS/CTS).
|
||||||
- **Behaviour** — line ending, local echo, encoding, and whether DTR/RTS are raised on connect.
|
- **Behaviour** — line ending, local echo, encoding, and whether DTR/RTS are raised on connect.
|
||||||
- **Tab controls** — send a break and toggle DTR/RTS from the tab.
|
- **Tab controls** — right-click the tab to send a break or toggle DTR/RTS.
|
||||||
- **Auto-reconnect** — unplugging the adapter disconnects the session; it reopens the port when it comes back.
|
- **Auto-reconnect** — unplugging the adapter disconnects the session; it reopens the port when it comes back.
|
||||||
|
|
||||||
On Linux your user usually needs to be in the `dialout` (or `uucp`) group. On macOS use the `/dev/cu.*` device. A \"busy\" error means another program has the port open.",
|
On Linux your user usually needs to be in the `dialout` (or `uucp`) group. On macOS use the `/dev/cu.*` device. A \"busy\" error means another program has the port open.",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ pub struct Pane {
|
|||||||
pub connected_since: Option<chrono::DateTime<chrono::Local>>,
|
pub connected_since: Option<chrono::DateTime<chrono::Local>>,
|
||||||
/// Bumped to make trigger highlights flash.
|
/// Bumped to make trigger highlights flash.
|
||||||
pub highlight_until: f64,
|
pub highlight_until: f64,
|
||||||
|
/// Serial DTR / RTS as last set (starts from the profile's on-connect settings).
|
||||||
|
pub lines: (bool, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A tab. Its session is `id`; `split` lays out extra panes (which have no tab of their own).
|
/// A tab. Its session is `id`; `split` lays out extra panes (which have no tab of their own).
|
||||||
|
|||||||
+22
-1
@@ -95,7 +95,7 @@ fn strip(app: &mut App, ui: &mut Ui) {
|
|||||||
for id in &order {
|
for id in &order {
|
||||||
let Some(p) = app.s.pane(id) else { continue };
|
let Some(p) = app.s.pane(id) else { continue };
|
||||||
let p_info = p.info.clone();
|
let p_info = p.info.clone();
|
||||||
let (p_recording, p_since) = (p.recording, p.connected_since);
|
let (p_recording, p_since, lines) = (p.recording, p.connected_since, p.lines);
|
||||||
let p = PaneInfo { info: p_info, recording: p_recording, connected_since: p_since };
|
let p = PaneInfo { info: p_info, recording: p_recording, connected_since: p_since };
|
||||||
let tab_activity = app.s.tab(id).is_some_and(|tb| tb.activity);
|
let tab_activity = app.s.tab(id).is_some_and(|tb| tb.activity);
|
||||||
let active = app.s.active.as_deref() == Some(id.as_str());
|
let active = app.s.active.as_deref() == Some(id.as_str());
|
||||||
@@ -203,6 +203,24 @@ fn strip(app: &mut App, ui: &mut Ui) {
|
|||||||
ui.close();
|
ui.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if matches!(p.info.protocol, Protocol::Telnet | Protocol::Serial) {
|
||||||
|
ui.separator();
|
||||||
|
if ui.button(format!("{} Send Break", icon::LIGHTNING)).clicked() {
|
||||||
|
menu_action = Some((id.clone(), "break"));
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.info.protocol == Protocol::Serial {
|
||||||
|
let (d, r) = lines;
|
||||||
|
if ui.button(format!("{} DTR", if d { icon::CHECK_SQUARE } else { icon::SQUARE })).clicked() {
|
||||||
|
menu_action = Some((id.clone(), "dtr"));
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
|
if ui.button(format!("{} RTS", if r { icon::CHECK_SQUARE } else { icon::SQUARE })).clicked() {
|
||||||
|
menu_action = Some((id.clone(), "rts"));
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if !ui.input(|i| i.pointer.primary_down()) {
|
if !ui.input(|i| i.pointer.primary_down()) {
|
||||||
@@ -284,6 +302,9 @@ fn tab_action(app: &mut App, id: &str, a: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"reconnect" => app.reconnect(id),
|
"reconnect" => app.reconnect(id),
|
||||||
|
"break" => app.send_break(id),
|
||||||
|
"dtr" => app.toggle_line(id, true),
|
||||||
|
"rts" => app.toggle_line(id, false),
|
||||||
"close" => app.request_close(id),
|
"close" => app.request_close(id),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ pub struct AppConfig {
|
|||||||
pub db_path: PathBuf,
|
pub db_path: PathBuf,
|
||||||
pub vault_dir: PathBuf,
|
pub vault_dir: PathBuf,
|
||||||
pub recordings_dir: PathBuf,
|
pub recordings_dir: PathBuf,
|
||||||
pub log_path: PathBuf,
|
|
||||||
/// Set when this run copied data across from Alchementary Link.
|
/// Set when this run copied data across from Alchementary Link.
|
||||||
pub migrated_from: Option<PathBuf>,
|
pub migrated_from: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,6 @@ impl AppConfig {
|
|||||||
db_path: data_dir.join("nexus.db"),
|
db_path: data_dir.join("nexus.db"),
|
||||||
vault_dir: data_dir.join("vault"),
|
vault_dir: data_dir.join("vault"),
|
||||||
recordings_dir: data_dir.join("recordings"),
|
recordings_dir: data_dir.join("recordings"),
|
||||||
log_path: data_dir.join("nexus.log"),
|
|
||||||
data_dir,
|
data_dir,
|
||||||
migrated_from: None,
|
migrated_from: None,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user