Reset the terminal before opening the LXC shell
Write ESC c, the sequence `reset` sends, inside suspend() right before ssh. The container shell then starts on a clean screen with no leftover TUI terminal modes. It runs locally, so the container does not need `reset` installed and `pct enter` is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
40feb125b4
commit
cccdaf7209
3 files changed
+26
No files matched your search
@@ -210,6 +210,10 @@ root's `authorized_keys` (verified: `ssh -o BatchMode=yes root@mainframe pct sta
|
|||||||
- Exit 255 (ssh itself failed) -> error notify naming the guest and the code. Other
|
- Exit 255 (ssh itself failed) -> error notify naming the guest and the code. Other
|
||||||
exit codes are the container shell's last status (`exit` after a failed command
|
exit codes are the container shell's last status (`exit` after a failed command
|
||||||
gives 1) and are not errors.
|
gives 1) and are not errors.
|
||||||
|
- Inside `suspend()`, right before ssh, the terminal gets a full reset (`ESC c`, what
|
||||||
|
`reset` sends) so the container shell starts on a clean screen without leftover TUI
|
||||||
|
terminal modes (user request). Done locally, not in the container: no dependency on
|
||||||
|
`reset` existing there, and `pct enter` stays unchanged.
|
||||||
- `OSError` from `subprocess.run` is caught *inside* the `suspend()` block: Textual
|
- `OSError` from `subprocess.run` is caught *inside* the `suspend()` block: Textual
|
||||||
8.2.8's `suspend()` has no try/finally, so an exception escaping it leaves the app
|
8.2.8's `suspend()` has no try/finally, so an exception escaping it leaves the app
|
||||||
suspended.
|
suspended.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Textual app for managing Proxmox guests through cv4pve-cli."""
|
"""Textual app for managing Proxmox guests through cv4pve-cli."""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from textual import work
|
from textual import work
|
||||||
from textual.app import App, ComposeResult, SuspendNotSupported
|
from textual.app import App, ComposeResult, SuspendNotSupported
|
||||||
@@ -358,6 +359,7 @@ class GuestApp(App):
|
|||||||
try:
|
try:
|
||||||
with self.suspend():
|
with self.suspend():
|
||||||
try: # inside: Textual's suspend() skips the resume if its body raises
|
try: # inside: Textual's suspend() skips the resume if its body raises
|
||||||
|
_reset_terminal()
|
||||||
code = subprocess.run(cmd, check=False).returncode
|
code = subprocess.run(cmd, check=False).returncode
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
error = _message(exc)
|
error = _message(exc)
|
||||||
@@ -426,6 +428,12 @@ class GuestApp(App):
|
|||||||
self._start_job(guest, "edit", job)
|
self._start_job(guest, "edit", job)
|
||||||
|
|
||||||
|
|
||||||
|
def _reset_terminal() -> None:
|
||||||
|
"""Full terminal reset (ESC c, what `reset` sends): clean screen, no leftover TUI modes."""
|
||||||
|
sys.stdout.write("\033c")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
def _scaled(value, unit: int):
|
def _scaled(value, unit: int):
|
||||||
return "-" if value is None else round(value / unit)
|
return "-" if value is None else round(value / unit)
|
||||||
|
|
||||||
|
|||||||
@@ -907,3 +907,17 @@ async def test_table_reloads_after_shell(fake, monkeypatch):
|
|||||||
await press_t(app, pilot)
|
await press_t(app, pilot)
|
||||||
await settle(app, pilot)
|
await settle(app, pilot)
|
||||||
assert fake.list_calls > before
|
assert fake.list_calls > before
|
||||||
|
|
||||||
|
|
||||||
|
async def test_terminal_is_reset_inside_suspend_before_ssh(fake, monkeypatch):
|
||||||
|
# user request: a clean screen (and no leftover TUI terminal modes) in the container shell
|
||||||
|
fake.guests = [RUNNING_CT]
|
||||||
|
app = make_app()
|
||||||
|
events = patch_shell(monkeypatch, app)
|
||||||
|
resets = []
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"cv4pve_tui.app._reset_terminal", lambda: resets.append(list(events))
|
||||||
|
)
|
||||||
|
async with app.run_test() as pilot:
|
||||||
|
await press_t(app, pilot)
|
||||||
|
assert resets == [[("enter", None)]] # inside suspend, before ssh ran
|
||||||
Reference in new issue
Block a user