v1.9.26: disable terminal auto-connect on single click, require double-click

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chrome-storm-c442
2026-03-06 05:09:54 -05:00
parent c21b263b24
commit 7522908404
9 changed files with 204 additions and 7 deletions

View File

@@ -111,6 +111,8 @@ _EN = {
"term_connecting": "Connecting to {alias}...",
"term_connected": "Connected to {alias}",
"term_disconnected": "Disconnected",
"term_click_to_connect": "Double-click to connect to {alias}",
"sftp_click_to_connect": "Double-click server to browse files",
"term_reconnecting": "Reconnecting ({n}/{max})...",
"term_connect_failed": "Connection failed: {error}",
"term_reconnect_fail": "Disconnected (reconnect failed)",
@@ -634,6 +636,8 @@ _RU = {
"term_connecting": "Подключение к {alias}...",
"term_connected": "Подключено к {alias}",
"term_disconnected": "Отключено",
"term_click_to_connect": "Двойной клик для подключения к {alias}",
"sftp_click_to_connect": "Двойной клик для просмотра файлов",
"term_reconnecting": "Переподключение ({n}/{max})...",
"term_connect_failed": "Ошибка подключения: {error}",
"term_reconnect_fail": "Отключено (не удалось переподключиться)",
@@ -1157,6 +1161,8 @@ _ZH = {
"term_connecting": "正在连接 {alias}...",
"term_connected": "已连接到 {alias}",
"term_disconnected": "已断开",
"term_click_to_connect": "双击连接 {alias}",
"sftp_click_to_connect": "双击服务器浏览文件",
"term_reconnecting": "重新连接中 ({n}/{max})...",
"term_connect_failed": "连接失败:{error}",
"term_reconnect_fail": "已断开(重连失败)",

View File

@@ -154,7 +154,7 @@ class App(ctk.CTk):
self._paned.pack(fill="both", expand=True)
# Sidebar
self.sidebar = Sidebar(self._paned, self.store, on_select=self._on_server_select, session_pool=self.session_pool)
self.sidebar = Sidebar(self._paned, self.store, on_select=self._on_server_select, on_double_click=self._on_server_connect, session_pool=self.session_pool)
self._paned.add(self.sidebar, minsize=180, width=self.store._sidebar_width)
self.sidebar.add_callback = self._add_server
self.sidebar.edit_callback = self._edit_server
@@ -307,6 +307,12 @@ class App(ctk.CTk):
# Update session indicators after a short delay (connection is async)
self.after(1500, self.sidebar.update_session_indicators)
def _on_server_connect(self, alias: str):
"""Double-click: connect interactive tabs (terminal, files, powershell)."""
for key, widget in self._tab_instances.items():
if hasattr(widget, "connect"):
widget.connect()
def _add_server(self):
dialog = ServerDialog(self, self.store)
self.wait_window(dialog)
@@ -356,6 +362,10 @@ class App(ctk.CTk):
self.tabview.set(_tab_label(tab_key))
except Exception:
pass
# Connect the target tab if it supports explicit connection
widget = self._tab_instances.get(tab_key)
if widget and hasattr(widget, "connect"):
widget.connect()
def _context_check_status(self, alias: str):
"""Context menu: check single server status in background."""

View File

@@ -34,10 +34,11 @@ _CONTEXT_ACTIONS = {
class Sidebar(ctk.CTkFrame):
def __init__(self, master, store, on_select=None, session_pool=None):
def __init__(self, master, store, on_select=None, on_double_click=None, session_pool=None):
super().__init__(master, width=250, corner_radius=0)
self.store = store
self.on_select = on_select
self.on_double_click = on_double_click
self.session_pool = session_pool
self._selected_alias: str | None = None
self._server_frames: dict[str, ctk.CTkFrame] = {}
@@ -272,6 +273,7 @@ class Sidebar(ctk.CTkFrame):
# Click handlers
for widget in [frame, info, name_label, detail_label, badge, type_badge, session_ind]:
widget.bind("<Button-1>", lambda e, a=alias: self._select(a))
widget.bind("<Double-Button-1>", lambda e, a=alias: self._on_double_click(a))
widget.bind("<Button-3>", lambda e, a=alias: self._show_context_menu(e, a))
self._server_frames[alias] = frame
@@ -371,6 +373,11 @@ class Sidebar(ctk.CTkFrame):
if self.on_select:
self.on_select(alias)
def _on_double_click(self, alias: str):
self._select(alias)
if self.on_double_click:
self.on_double_click(alias)
def _highlight_selected(self):
for alias, frame in self._server_frames.items():
if alias == self._selected_alias:

View File

@@ -307,13 +307,17 @@ class FilesTab(ctk.CTkFrame):
stored_path, stored_sudo = self.session_pool.get_sftp_state(alias)
if stored_path != "/":
self._remote_path = stored_path
# The stored sudo mode will be applied when the connection is established
self._connect_sftp()
self._remote_status.configure(text=t("sftp_click_to_connect"))
else:
self._remote_list.populate([])
self._remote_status.configure(text=t("connect_to_browse"))
self._set_remote_buttons_state("disabled")
def connect(self):
"""Explicitly connect SFTP (double-click or context menu)."""
if self._current_alias and not self._sftp:
self._connect_sftp()
# ── SFTP connection ──
def _connect_sftp(self):

View File

@@ -97,7 +97,12 @@ class PowershellTab(ctk.CTkFrame):
self._set_status(t("ps_disconnected"), "#888888")
return
self._connect(alias)
self._set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b")
def connect(self):
"""Explicitly connect WinRM (double-click or context menu)."""
if self._current_alias and not self._client:
self._connect(self._current_alias)
# ── Connection ───────────────────────────────────────────────────

View File

@@ -61,11 +61,16 @@ class TerminalTab(ctk.CTkFrame):
self._current_alias = alias
if alias:
self._connect()
self._terminal.set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b")
else:
self._terminal.reset()
self._terminal.set_status(t("term_disconnected"), "#888888")
def connect(self):
"""Explicitly connect (double-click or context menu)."""
if self._current_alias and not self._session:
self._connect()
def _connect(self):
if not self._current_alias:
return

View File

@@ -0,0 +1,160 @@
# Отключить автоподключение терминала при одинарном клике
## Контекст
При одинарном клике на сервер в sidebar все табы (terminal, files, powershell) сразу подключаются к серверу. Пользователь хочет просто переключаться между серверами без автоподключения. Подключение — только по двойному клику.
## Подход
- **Одинарный клик** — выбрать сервер, обновить табы (info, setup, keys и т.д.), но НЕ подключаться к terminal/files/powershell
- **Двойной клик** — выбрать сервер + подключить все "connecting" табы (terminal, files, powershell)
- **Контекстное меню** "Open Terminal" / "Browse Files" — тоже подключает
Tkinter при двойном клике генерирует оба события: `<Button-1>` (первый клик) → `<Double-Button-1>`. Это нам на руку: первый клик выберет сервер, двойной клик — подключит. Debounce не нужен.
## Изменения — 4 файла
### 1. `gui/sidebar.py` — добавить двойной клик
**Строка 37** — добавить `on_double_click` в конструктор:
```python
def __init__(self, master, store, on_select=None, on_double_click=None, session_pool=None):
...
self.on_select = on_select
self.on_double_click = on_double_click
```
**Строки 272-275** — добавить `<Double-Button-1>` binding:
```python
for widget in [frame, info, name_label, detail_label, badge, type_badge, session_ind]:
widget.bind("<Button-1>", lambda e, a=alias: self._select(a))
widget.bind("<Double-Button-1>", lambda e, a=alias: self._on_double_click(a))
widget.bind("<Button-3>", lambda e, a=alias: self._show_context_menu(e, a))
```
**После `_select()`** (строка 372) — новый метод:
```python
def _on_double_click(self, alias: str):
self._select(alias)
if self.on_double_click:
self.on_double_click(alias)
```
### 2. `gui/tabs/terminal_tab.py` — убрать автоподключение
**Строки 49-67**`set_server()`: заменить `self._connect()` на показ статуса:
```python
def set_server(self, alias: str | None):
if alias == self._current_alias:
return
if self._current_alias and self._session and self.session_pool:
buf = self._terminal.get_current_buffer()
self.session_pool.store_shell_state(self._current_alias, buf)
self._disconnect()
self._current_alias = alias
if alias:
self._terminal.set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b")
else:
self._terminal.reset()
self._terminal.set_status(t("term_disconnected"), "#888888")
```
**Добавить публичный метод `connect()`** после `set_server()`:
```python
def connect(self):
"""Explicitly connect (double-click or context menu)."""
if self._current_alias and not self._session:
self._connect()
```
### 3. `gui/tabs/files_tab.py` — убрать автоподключение
**Строки 304-311**`set_server()`: заменить `self._connect_sftp()` на статус:
```python
if alias:
if self.session_pool:
stored_path, stored_sudo = self.session_pool.get_sftp_state(alias)
if stored_path != "/":
self._remote_path = stored_path
self._remote_status.configure(text=t("sftp_click_to_connect"))
else:
...
```
**Добавить публичный метод `connect()`**:
```python
def connect(self):
"""Explicitly connect SFTP (double-click or context menu)."""
if self._current_alias and not self._sftp:
self._connect_sftp()
```
### 4. `gui/tabs/powershell_tab.py` — убрать автоподключение
**Строка 100** — заменить `self._connect(alias)` на статус:
```python
if alias is None:
self._set_status(t("ps_disconnected"), "#888888")
return
self._set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b")
```
**Добавить публичный метод `connect()`**:
```python
def connect(self):
"""Explicitly connect WinRM (double-click or context menu)."""
if self._current_alias and not self._client:
self._connect(self._current_alias)
```
### 5. `gui/app.py` — подключить двойной клик
**Строка 157** — передать `on_double_click`:
```python
self.sidebar = Sidebar(self._paned, self.store,
on_select=self._on_server_select,
on_double_click=self._on_server_connect,
session_pool=self.session_pool)
```
**Новый метод `_on_server_connect()`** (после `_on_server_select`):
```python
def _on_server_connect(self, alias: str):
"""Double-click: connect interactive tabs (terminal, files, powershell)."""
for key, widget in self._tab_instances.items():
if hasattr(widget, "connect"):
widget.connect()
```
**Строки 350-358**`_context_open_tab()`: добавить вызов `connect()`:
```python
def _context_open_tab(self, alias: str, tab_key: str):
self._on_server_select(alias)
self.sidebar._select(alias)
if tab_key in self._tab_keys:
try:
self.tabview.set(_tab_label(tab_key))
except Exception:
pass
# Connect the target tab if it supports explicit connection
widget = self._tab_instances.get(tab_key)
if widget and hasattr(widget, "connect"):
widget.connect()
```
### 6. `core/i18n.py` — 2 ключа перевода
Рядом с `term_disconnected`:
| Ключ | EN | RU | ZH |
|------|----|----|-----|
| `term_click_to_connect` | `Double-click to connect to {alias}` | `Двойной клик для подключения к {alias}` | `双击连接 {alias}` |
| `sftp_click_to_connect` | `Double-click server to browse files` | `Двойной клик для просмотра файлов` | `双击服务器浏览文件` |
## Верификация
1. `python build.py` — собрать exe
2. Запустить exe, одинарный клик на SSH-сервер → терминал показывает "Двойной клик для подключения", файлы показывают аналогичное сообщение, info таб работает как раньше
3. Двойной клик на сервер → терминал и файлы подключаются
4. Правый клик → "Open Terminal" → терминал подключается
5. Переключение между серверами одним кликом → нет автоподключений, быстрое переключение

View File

@@ -1,6 +1,6 @@
"""Version info for ServerManager."""
__version__ = "1.9.25"
__version__ = "1.9.26"
__app_name__ = "ServerManager"
__author__ = "aibot777"
__description__ = "Desktop GUI for managing remote servers"