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:
@@ -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": "已断开(重连失败)",
|
||||
|
||||
12
gui/app.py
12
gui/app.py
@@ -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."""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
160
plans/disable-terminal-autoconnect.md
Normal file
160
plans/disable-terminal-autoconnect.md
Normal 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. Переключение между серверами одним кликом → нет автоподключений, быстрое переключение
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user