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_connecting": "Connecting to {alias}...",
|
||||||
"term_connected": "Connected to {alias}",
|
"term_connected": "Connected to {alias}",
|
||||||
"term_disconnected": "Disconnected",
|
"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_reconnecting": "Reconnecting ({n}/{max})...",
|
||||||
"term_connect_failed": "Connection failed: {error}",
|
"term_connect_failed": "Connection failed: {error}",
|
||||||
"term_reconnect_fail": "Disconnected (reconnect failed)",
|
"term_reconnect_fail": "Disconnected (reconnect failed)",
|
||||||
@@ -634,6 +636,8 @@ _RU = {
|
|||||||
"term_connecting": "Подключение к {alias}...",
|
"term_connecting": "Подключение к {alias}...",
|
||||||
"term_connected": "Подключено к {alias}",
|
"term_connected": "Подключено к {alias}",
|
||||||
"term_disconnected": "Отключено",
|
"term_disconnected": "Отключено",
|
||||||
|
"term_click_to_connect": "Двойной клик для подключения к {alias}",
|
||||||
|
"sftp_click_to_connect": "Двойной клик для просмотра файлов",
|
||||||
"term_reconnecting": "Переподключение ({n}/{max})...",
|
"term_reconnecting": "Переподключение ({n}/{max})...",
|
||||||
"term_connect_failed": "Ошибка подключения: {error}",
|
"term_connect_failed": "Ошибка подключения: {error}",
|
||||||
"term_reconnect_fail": "Отключено (не удалось переподключиться)",
|
"term_reconnect_fail": "Отключено (не удалось переподключиться)",
|
||||||
@@ -1157,6 +1161,8 @@ _ZH = {
|
|||||||
"term_connecting": "正在连接 {alias}...",
|
"term_connecting": "正在连接 {alias}...",
|
||||||
"term_connected": "已连接到 {alias}",
|
"term_connected": "已连接到 {alias}",
|
||||||
"term_disconnected": "已断开",
|
"term_disconnected": "已断开",
|
||||||
|
"term_click_to_connect": "双击连接 {alias}",
|
||||||
|
"sftp_click_to_connect": "双击服务器浏览文件",
|
||||||
"term_reconnecting": "重新连接中 ({n}/{max})...",
|
"term_reconnecting": "重新连接中 ({n}/{max})...",
|
||||||
"term_connect_failed": "连接失败:{error}",
|
"term_connect_failed": "连接失败:{error}",
|
||||||
"term_reconnect_fail": "已断开(重连失败)",
|
"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)
|
self._paned.pack(fill="both", expand=True)
|
||||||
|
|
||||||
# Sidebar
|
# 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._paned.add(self.sidebar, minsize=180, width=self.store._sidebar_width)
|
||||||
self.sidebar.add_callback = self._add_server
|
self.sidebar.add_callback = self._add_server
|
||||||
self.sidebar.edit_callback = self._edit_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)
|
# Update session indicators after a short delay (connection is async)
|
||||||
self.after(1500, self.sidebar.update_session_indicators)
|
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):
|
def _add_server(self):
|
||||||
dialog = ServerDialog(self, self.store)
|
dialog = ServerDialog(self, self.store)
|
||||||
self.wait_window(dialog)
|
self.wait_window(dialog)
|
||||||
@@ -356,6 +362,10 @@ class App(ctk.CTk):
|
|||||||
self.tabview.set(_tab_label(tab_key))
|
self.tabview.set(_tab_label(tab_key))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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):
|
def _context_check_status(self, alias: str):
|
||||||
"""Context menu: check single server status in background."""
|
"""Context menu: check single server status in background."""
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ _CONTEXT_ACTIONS = {
|
|||||||
|
|
||||||
|
|
||||||
class Sidebar(ctk.CTkFrame):
|
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)
|
super().__init__(master, width=250, corner_radius=0)
|
||||||
self.store = store
|
self.store = store
|
||||||
self.on_select = on_select
|
self.on_select = on_select
|
||||||
|
self.on_double_click = on_double_click
|
||||||
self.session_pool = session_pool
|
self.session_pool = session_pool
|
||||||
self._selected_alias: str | None = None
|
self._selected_alias: str | None = None
|
||||||
self._server_frames: dict[str, ctk.CTkFrame] = {}
|
self._server_frames: dict[str, ctk.CTkFrame] = {}
|
||||||
@@ -272,6 +273,7 @@ class Sidebar(ctk.CTkFrame):
|
|||||||
# Click handlers
|
# Click handlers
|
||||||
for widget in [frame, info, name_label, detail_label, badge, type_badge, session_ind]:
|
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("<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))
|
widget.bind("<Button-3>", lambda e, a=alias: self._show_context_menu(e, a))
|
||||||
|
|
||||||
self._server_frames[alias] = frame
|
self._server_frames[alias] = frame
|
||||||
@@ -371,6 +373,11 @@ class Sidebar(ctk.CTkFrame):
|
|||||||
if self.on_select:
|
if self.on_select:
|
||||||
self.on_select(alias)
|
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):
|
def _highlight_selected(self):
|
||||||
for alias, frame in self._server_frames.items():
|
for alias, frame in self._server_frames.items():
|
||||||
if alias == self._selected_alias:
|
if alias == self._selected_alias:
|
||||||
|
|||||||
@@ -307,13 +307,17 @@ class FilesTab(ctk.CTkFrame):
|
|||||||
stored_path, stored_sudo = self.session_pool.get_sftp_state(alias)
|
stored_path, stored_sudo = self.session_pool.get_sftp_state(alias)
|
||||||
if stored_path != "/":
|
if stored_path != "/":
|
||||||
self._remote_path = stored_path
|
self._remote_path = stored_path
|
||||||
# The stored sudo mode will be applied when the connection is established
|
self._remote_status.configure(text=t("sftp_click_to_connect"))
|
||||||
self._connect_sftp()
|
|
||||||
else:
|
else:
|
||||||
self._remote_list.populate([])
|
self._remote_list.populate([])
|
||||||
self._remote_status.configure(text=t("connect_to_browse"))
|
self._remote_status.configure(text=t("connect_to_browse"))
|
||||||
self._set_remote_buttons_state("disabled")
|
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 ──
|
# ── SFTP connection ──
|
||||||
|
|
||||||
def _connect_sftp(self):
|
def _connect_sftp(self):
|
||||||
|
|||||||
@@ -97,7 +97,12 @@ class PowershellTab(ctk.CTkFrame):
|
|||||||
self._set_status(t("ps_disconnected"), "#888888")
|
self._set_status(t("ps_disconnected"), "#888888")
|
||||||
return
|
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 ───────────────────────────────────────────────────
|
# ── Connection ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -61,11 +61,16 @@ class TerminalTab(ctk.CTkFrame):
|
|||||||
|
|
||||||
self._current_alias = alias
|
self._current_alias = alias
|
||||||
if alias:
|
if alias:
|
||||||
self._connect()
|
self._terminal.set_status(t("term_click_to_connect").format(alias=alias), "#f59e0b")
|
||||||
else:
|
else:
|
||||||
self._terminal.reset()
|
self._terminal.reset()
|
||||||
self._terminal.set_status(t("term_disconnected"), "#888888")
|
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):
|
def _connect(self):
|
||||||
if not self._current_alias:
|
if not self._current_alias:
|
||||||
return
|
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 info for ServerManager."""
|
||||||
|
|
||||||
__version__ = "1.9.25"
|
__version__ = "1.9.26"
|
||||||
__app_name__ = "ServerManager"
|
__app_name__ = "ServerManager"
|
||||||
__author__ = "aibot777"
|
__author__ = "aibot777"
|
||||||
__description__ = "Desktop GUI for managing remote servers"
|
__description__ = "Desktop GUI for managing remote servers"
|
||||||
|
|||||||
Reference in New Issue
Block a user