sftp works with keyring, bookmark and edit bookmark

This commit is contained in:
2025-08-16 01:06:34 +02:00
parent cc48f874ac
commit 48034626f1
7 changed files with 355 additions and 119 deletions

View File

@@ -23,16 +23,14 @@ class NavigationManager:
def handle_path_entry_return(self, event: tk.Event) -> None:
"""
Handles the Return key press in the path entry field.
It attempts to navigate to the entered path. If the path is a file,
it navigates to the containing directory and selects the file.
Args:
event: The tkinter event that triggered this handler.
"""
path_text = self.dialog.widget_manager.path_entry.get().strip()
potential_path = os.path.realpath(os.path.expanduser(path_text))
is_sftp = self.dialog.current_fs_type == "sftp"
if is_sftp:
self.navigate_to(path_text)
else:
potential_path = os.path.realpath(os.path.expanduser(path_text))
if os.path.isdir(potential_path):
self.navigate_to(potential_path)
elif os.path.isfile(potential_path):
@@ -45,20 +43,34 @@ class NavigationManager:
def navigate_to(self, path: str, file_to_select: Optional[str] = None) -> None:
"""
Navigates to a specified directory path.
This is the core navigation method. It validates the path, checks for
read permissions, updates the dialog's current directory, manages the
navigation history, and refreshes the file view.
Args:
path (str): The absolute path to navigate to.
file_to_select (str, optional): If provided, this filename will be
selected after navigation. Defaults to None.
Navigates to a specified directory path, supporting both local and SFTP filesystems.
"""
try:
real_path = os.path.realpath(
os.path.abspath(os.path.expanduser(path)))
is_sftp = self.dialog.current_fs_type == "sftp"
if is_sftp:
# Resolve tilde to the remote home directory for SFTP
if path == '~' or path.startswith('~/'):
home_dir = self.dialog.sftp_manager.home_dir
if home_dir:
# Manual path joining with forward slashes
if path.startswith('~/'):
# home_dir might be '/', so avoid '//'
path = home_dir.rstrip('/') + '/' + path[2:]
else:
path = home_dir
else: # Fallback if home_dir is not set
path = '/'
# The SFTP manager will handle path validation.
if not self.dialog.sftp_manager.path_is_dir(path):
self.dialog.widget_manager.search_status_label.config(
text=f"Error: Directory '{os.path.basename(path)}' not found on SFTP server.")
return
real_path = path
else:
# Local filesystem logic
real_path = os.path.realpath(os.path.abspath(os.path.expanduser(path)))
if not os.path.isdir(real_path):
self.dialog.widget_manager.search_status_label.config(
text=f"{LocaleStrings.CFD['error_title']}: {LocaleStrings.CFD['directory']} '{os.path.basename(path)}' {LocaleStrings.CFD['not_found']}")
@@ -67,6 +79,7 @@ class NavigationManager:
self.dialog.widget_manager.search_status_label.config(
text=f"{LocaleStrings.CFD['access_to']} '{os.path.basename(path)}' {LocaleStrings.CFD['denied']}")
return
self.dialog.current_dir = real_path
if self.dialog.history_pos < len(self.dialog.history) - 1:
self.dialog.history = self.dialog.history[:self.dialog.history_pos + 1]
@@ -75,19 +88,17 @@ class NavigationManager:
self.dialog.history_pos = len(self.dialog.history) - 1
self.dialog.widget_manager.search_animation.stop()
# Clear previous selection state before populating new view
self.dialog.selected_item_frames.clear()
self.dialog.result = None
self.dialog.view_manager.populate_files(
item_to_select=file_to_select)
self.dialog.view_manager.populate_files(item_to_select=file_to_select)
self.update_nav_buttons()
self.dialog.update_selection_info() # Use the new central update method
self.dialog.update_selection_info()
self.dialog.update_action_buttons_state()
except Exception as e:
self.dialog.widget_manager.search_status_label.config(
text=f"{LocaleStrings.CFD['error_title']}: {e}")
error_message = f"Error navigating to '{path}': {e}"
self.dialog.widget_manager.search_status_label.config(text=error_message)
def go_back(self) -> None:
"""Navigates to the previous directory in the history."""

View File

@@ -157,6 +157,21 @@ class SettingsDialog(tk.Toplevel):
ttk.Label(sftp_frame, text=LocaleStrings.SET["paramiko_found"],
font=("TkDefaultFont", 9)).pack(anchor="w")
# Keyring status
try:
import keyring
keyring_available = True
except ImportError:
keyring_available = False
if keyring_available:
ttk.Label(sftp_frame, text="Keyring library found. Passwords will be stored securely.",
font=("TkDefaultFont", 9)).pack(anchor="w", pady=(5,0))
else:
ttk.Label(sftp_frame, text="Keyring library not found. Passwords cannot be saved.",
font=("TkDefaultFont", 9, "italic")).pack(anchor="w", pady=(5,0))
self.keep_bookmarks_checkbutton = ttk.Checkbutton(sftp_frame, text=LocaleStrings.SET["keep_sftp_bookmarks"],
variable=self.keep_bookmarks_on_reset)
self.keep_bookmarks_checkbutton.pack(anchor="w", pady=(5,0))

View File

@@ -7,10 +7,18 @@ except ImportError:
paramiko = None
PARAMIKO_AVAILABLE = False
try:
import keyring
KEYRING_AVAILABLE = True
except ImportError:
keyring = None
KEYRING_AVAILABLE = False
class SFTPManager:
def __init__(self):
self.client = None
self.sftp = None
self.home_dir = None
def connect(self, host, port, username, password=None, key_file=None, passphrase=None):
if not PARAMIKO_AVAILABLE:
@@ -27,15 +35,28 @@ class SFTPManager:
password=password,
key_filename=key_file,
passphrase=passphrase,
timeout=10
timeout=10,
allow_agent=False,
look_for_keys=False
)
self.sftp = self.client.open_sftp()
self.home_dir = self.get_home_directory()
return True, "Connection successful."
except Exception as e:
self.client = None
self.sftp = None
return False, str(e)
def get_home_directory(self):
if not self.sftp:
return None
try:
# normalize('.') is a common way to get the default directory, usually home.
return self.sftp.normalize('.')
except Exception:
# Fallback to root if normalize fails
return "/"
def disconnect(self):
if self.sftp:
self.sftp.close()
@@ -43,6 +64,7 @@ class SFTPManager:
if self.client:
self.client.close()
self.client = None
self.home_dir = None
def list_directory(self, path):
if not self.sftp:

View File

@@ -283,8 +283,14 @@ class WidgetManager:
]
self.sidebar_buttons = []
for config in sidebar_buttons_config:
# Special case for "Computer" button to not disconnect SFTP
if config['path'] == '/':
command = lambda p=config['path']: self.dialog.navigation_manager.navigate_to(p)
else:
command = lambda p=config['path']: self.dialog.handle_sidebar_bookmark_click(p)
btn = ttk.Button(sidebar_buttons_frame, text=f" {config['name']}", image=config['icon'], compound="left",
command=lambda p=config['path']: self.dialog.navigation_manager.navigate_to(p), style="Dark.TButton.Borderless")
command=command, style="Dark.TButton.Borderless")
btn.pack(fill="x", pady=1)
self.sidebar_buttons.append((btn, f" {config['name']}"))
@@ -314,12 +320,19 @@ class WidgetManager:
command=lambda d=data: self.dialog.connect_sftp_bookmark(d), style="Dark.TButton.Borderless")
btn.grid(row=row_counter, column=0, sticky="ew")
row_counter += 1
btn.bind("<Button-3>", lambda event,
n=name: self._show_sftp_bookmark_context_menu(event, n))
btn.bind("<Button-3>", lambda event, n=name, d=data: self._show_sftp_bookmark_context_menu(event, n, d))
self.sftp_bookmark_buttons.append(btn)
def _show_sftp_bookmark_context_menu(self, event, name):
def _show_sftp_bookmark_context_menu(self, event, name, data):
context_menu = tk.Menu(self.dialog, tearoff=0)
edit_icon = self.dialog.icon_manager.get_icon('key_small')
context_menu.add_command(
label="Edit Bookmark", # Replace with LocaleString later
image=edit_icon,
compound=tk.LEFT,
command=lambda: self.dialog.edit_sftp_bookmark(name, data))
trash_icon = self.dialog.icon_manager.get_icon('trash_small2')
context_menu.add_command(
label=LocaleStrings.UI["remove_bookmark"],
@@ -384,7 +397,7 @@ class WidgetManager:
button_text = f" {device_name[:15]}\n{device_name[15:]}"
btn = ttk.Button(self.devices_scrollable_frame, text=button_text, image=icon, compound="left",
command=lambda p=mount_point: self.dialog.navigation_manager.navigate_to(p), style="Dark.TButton.Borderless")
command=lambda p=mount_point: self.dialog.handle_sidebar_bookmark_click(p), style="Dark.TButton.Borderless")
btn.pack(fill="x", pady=1)
self.device_buttons.append((btn, button_text))

View File

@@ -104,17 +104,25 @@ class ViewManager:
def _get_folder_content_count(self, folder_path: str) -> Optional[int]:
"""
Counts the number of items in a given folder.
Counts the number of items in a given folder, supporting both local and SFTP.
"""
if self.dialog.current_fs_type == "sftp":
return None
try:
if self.dialog.current_fs_type == "sftp":
if not self.dialog.sftp_manager.path_is_dir(folder_path):
return None
items, error = self.dialog.sftp_manager.list_directory(folder_path)
if error:
return None
else:
if not os.path.isdir(folder_path) or not os.access(folder_path, os.R_OK):
return None
items = os.listdir(folder_path)
if not self.dialog.show_hidden_files.get():
# For SFTP, items are attrs, for local they are strings
if self.dialog.current_fs_type == "sftp":
items = [item for item in items if not item.filename.startswith('.')]
else:
items = [item for item in items if not item.startswith('.')]
return len(items)
@@ -618,9 +626,12 @@ class ViewManager:
"""
Programmatically selects a file in the current view.
"""
is_sftp = self.dialog.current_fs_type == "sftp"
if self.dialog.view_mode.get() == "list":
for item_id, path in self.dialog.item_path_map.items():
if os.path.basename(path) == filename:
basename = path.split('/')[-1] if is_sftp else os.path.basename(path)
if basename == filename:
self.dialog.tree.selection_set(item_id)
self.dialog.tree.focus(item_id)
self.dialog.tree.see(item_id)
@@ -630,6 +641,11 @@ class ViewManager:
return
container_frame = self.dialog.icon_canvas.winfo_children()[0]
if is_sftp:
# Ensure forward slashes for SFTP paths
target_path = f"{self.dialog.current_dir}/{filename}".replace("//", "/")
else:
target_path = os.path.join(self.dialog.current_dir, filename)
for widget in container_frame.winfo_children():

View File

@@ -148,6 +148,8 @@ class CustomFileDialog(tk.Toplevel):
def reload_config_and_rebuild_ui(self) -> None:
"""Reloads the configuration and rebuilds the entire UI."""
is_sftp_connected = (self.current_fs_type == "sftp")
self.load_settings()
self.geometry(self.settings["window_size_preset"])
@@ -163,6 +165,10 @@ class CustomFileDialog(tk.Toplevel):
self._initialize_managers()
if is_sftp_connected:
self.widget_manager.sftp_button.config(
command=self.disconnect_sftp, style="Header.TButton.Active.Round")
self.widget_manager.filename_entry.bind(
"<Return>", self.search_manager.execute_search)
self.view_manager._update_view_mode_buttons()
@@ -198,13 +204,38 @@ class CustomFileDialog(tk.Toplevel):
self.config(cursor="watch")
self.update_idletasks()
if is_new_connection and credentials.get("save_bookmark"):
bookmark_name = credentials["bookmark_name"]
bookmark_data = {
"host": credentials["host"],
"port": credentials["port"],
"username": credentials["username"],
"initial_path": credentials["initial_path"],
"key_file": credentials["key_file"],
}
try:
import keyring
service_name = f"customfiledialog-sftp"
if credentials["password"]:
keyring.set_password(service_name, f"{bookmark_name}_password", credentials["password"])
bookmark_data["password_in_keyring"] = True
if credentials["passphrase"]:
keyring.set_password(service_name, f"{bookmark_name}_passphrase", credentials["passphrase"])
bookmark_data["passphrase_in_keyring"] = True
self.config_manager.add_bookmark(bookmark_name, bookmark_data)
self.after(100, self.reload_config_and_rebuild_ui)
except Exception as e:
MessageDialog(message_type="error", text=f"Could not save bookmark: {e}").show()
success, message = self.sftp_manager.connect(
host=credentials['host'],
port=credentials['port'],
username=credentials['username'],
password=credentials['password'],
key_file=credentials['key_file'],
passphrase=credentials['passphrase']
host=credentials.get('host'),
port=credentials.get('port'),
username=credentials.get('username'),
password=credentials.get('password'),
key_file=credentials.get('key_file'),
passphrase=credentials.get('passphrase')
)
self.config(cursor="")
@@ -216,48 +247,78 @@ class CustomFileDialog(tk.Toplevel):
initial_path = credentials.get("initial_path", "/")
self.navigation_manager.navigate_to(initial_path)
if is_new_connection:
save_bookmark = MessageDialog(
message_type="ask",
text="Connection successful. Save as bookmark?",
buttons=["Yes", "No"]
).show()
if save_bookmark:
bookmark_name = InputDialog(
self,
title="Save Bookmark",
prompt="Enter a name for the bookmark:",
initial_value=credentials['host']
).show()
if bookmark_name:
self.config_manager.add_bookmark(
bookmark_name, credentials)
self.reload_config_and_rebuild_ui()
else:
MessageDialog(message_type="error",
text=f"Connection failed: {message}").show()
def connect_sftp_bookmark(self, data):
self.connect_sftp(data, is_new_connection=False)
credentials = data.copy()
try:
import keyring
service_name = f"customfiledialog-sftp"
bookmark_name = next(name for name, b_data in self.config_manager.load_bookmarks().items() if b_data == data)
def remove_sftp_bookmark(self, name):
confirm = MessageDialog(
if credentials.get("password_in_keyring"):
credentials["password"] = keyring.get_password(service_name, f"{bookmark_name}_password")
if credentials.get("passphrase_in_keyring"):
credentials["passphrase"] = keyring.get_password(service_name, f"{bookmark_name}_passphrase")
except (ImportError, StopIteration, Exception) as e:
MessageDialog(message_type="error", text=f"Could not retrieve credentials: {e}").show()
return
self.connect_sftp(credentials, is_new_connection=False)
def edit_sftp_bookmark(self, name: str, data: dict):
"""Opens the credentials dialog to edit an existing SFTP bookmark."""
data['bookmark_name'] = name
dialog = CredentialsDialog(self, title=f"Edit Bookmark: {name}", initial_data=data, is_edit_mode=True)
new_data = dialog.show()
if new_data:
self.remove_sftp_bookmark(name, confirm=False)
self.connect_sftp(new_data, is_new_connection=True)
def remove_sftp_bookmark(self, name: str, confirm: bool = True):
"""Removes an SFTP bookmark and its credentials from the keyring."""
do_remove = False
if confirm:
confirm_dialog = MessageDialog(
message_type="ask",
text=f"Remove bookmark '{name}'?",
buttons=["Yes", "No"]).show()
if confirm:
buttons=["Yes", "No"])
if confirm_dialog.show():
do_remove = True
else:
do_remove = True
if do_remove:
try:
import keyring
service_name = f"customfiledialog-sftp"
try:
keyring.delete_password(service_name, f"{name}_password")
except keyring.errors.PasswordDeleteError:
pass
try:
keyring.delete_password(service_name, f"{name}_passphrase")
except keyring.errors.PasswordDeleteError:
pass
except ImportError:
pass
except Exception as e:
print(f"Could not remove credentials from keyring for {name}: {e}")
self.config_manager.remove_bookmark(name)
self.reload_config_and_rebuild_ui()
def disconnect_sftp(self):
def disconnect_sftp(self, path_to_navigate_to: Optional[str] = None):
self.sftp_manager.disconnect()
self.current_fs_type = "local"
self.widget_manager.sftp_button.config(
command=self.open_sftp_dialog, style="Header.TButton.Borderless.Round")
self.navigation_manager.navigate_to(os.path.expanduser("~"))
target_path = path_to_navigate_to if path_to_navigate_to else os.path.expanduser("~")
self.navigation_manager.navigate_to(target_path)
def go_to_local_home(self):
if self.current_fs_type == "sftp":
@@ -265,6 +326,12 @@ class CustomFileDialog(tk.Toplevel):
else:
self.navigation_manager.navigate_to(os.path.expanduser("~"))
def handle_sidebar_bookmark_click(self, local_path: str):
if self.current_fs_type == "sftp":
self.disconnect_sftp(path_to_navigate_to=local_path)
else:
self.navigation_manager.navigate_to(local_path)
def update_animation_settings(self) -> None:
"""Updates the search animation icon based on current settings."""
use_pillow = self.settings.get('use_pillow_animation', False)
@@ -497,6 +564,15 @@ class CustomFileDialog(tk.Toplevel):
"""
self._update_disk_usage()
status_text = ""
is_sftp = self.current_fs_type == 'sftp'
# Helper to get basename safely
def get_basename(path):
if not path:
return ""
if is_sftp:
return path.split('/')[-1]
return os.path.basename(path)
if self.dialog_mode == 'multi':
selected_paths = self.result if isinstance(
@@ -504,7 +580,7 @@ class CustomFileDialog(tk.Toplevel):
self.widget_manager.filename_entry.delete(0, tk.END)
if selected_paths:
filenames = [
f'"{os.path.basename(p)}"' for p in selected_paths]
f'"{get_basename(p)}"' for p in selected_paths]
self.widget_manager.filename_entry.insert(
0, " ".join(filenames))
count = len(selected_paths)
@@ -512,20 +588,27 @@ class CustomFileDialog(tk.Toplevel):
else:
status_text = ""
else:
if status_info and (self.current_fs_type == 'sftp' or os.path.exists(status_info)):
path_exists = False
if status_info:
if is_sftp:
path_exists = self.sftp_manager.exists(status_info)
else:
path_exists = os.path.exists(status_info)
if status_info and path_exists:
self.result = status_info
basename = get_basename(status_info)
self.widget_manager.filename_entry.delete(0, tk.END)
self.widget_manager.filename_entry.insert(
0, os.path.basename(status_info))
self.widget_manager.filename_entry.insert(0, basename)
if self.view_manager._is_dir(status_info):
content_count = self.view_manager._get_folder_content_count(
status_info)
content_count = self.view_manager._get_folder_content_count(status_info)
if content_count is not None:
status_text = f"'{os.path.basename(status_info)}' ({content_count} {LocaleStrings.CFD['entries']})"
status_text = f"'{basename}' ({content_count} {LocaleStrings.CFD['entries']})"
else:
status_text = f"'{os.path.basename(status_info)}'"
status_text = f"'{basename}'"
else:
status_text = f"'{os.path.basename(status_info)}'"
status_text = f"'{basename}'"
elif status_info:
status_text = status_info
@@ -539,17 +622,17 @@ class CustomFileDialog(tk.Toplevel):
self.widget_manager.storage_bar['value'] = 0
return
try:
# This can fail on certain file types like symlinks to other filesystems.
total, used, free = shutil.disk_usage(self.current_dir)
free_str = self._format_size(free)
self.widget_manager.storage_label.config(
text=f"{LocaleStrings.CFD['free_space']}: {free_str}")
self.widget_manager.storage_bar['value'] = (used / total) * 100
except FileNotFoundError:
except (FileNotFoundError, PermissionError):
# If disk usage cannot be determined, just show N/A instead of an error.
self.widget_manager.storage_label.config(
text=f"{LocaleStrings.CFD['free_space']}: {LocaleStrings.CFD['unknown']}")
text=f"{LocaleStrings.CFD['free_space']}: N/A")
self.widget_manager.storage_bar['value'] = 0
self.widget_manager.search_status_label.config(
text=LocaleStrings.CFD["directory_not_found"])
def on_open(self) -> None:
"""Handles the 'Open' or 'OK' action based on the dialog mode."""

View File

@@ -143,16 +143,25 @@ class MessageDialog:
class CredentialsDialog:
"""
A dialog for securely entering SSH/SFTP credentials.
A dialog for securely entering and editing SSH/SFTP credentials.
"""
def __init__(self, master: Optional[tk.Tk] = None, title: str = "SFTP Connection"):
def __init__(self, master: Optional[tk.Tk] = None, title: str = "SFTP Connection",
initial_data: Optional[dict] = None, is_edit_mode: bool = False):
self.master = master
self.result = None
self.is_edit_mode = is_edit_mode
self.initial_data = initial_data or {}
self.window = tk.Toplevel(master)
self.window.title(title)
self.window.resizable(False, False)
try:
import keyring
self.keyring_available = True
except ImportError:
self.keyring_available = False
style = ttk.Style(self.window)
style.configure("Creds.TEntry", padding=(5, 2))
@@ -168,7 +177,6 @@ class CredentialsDialog:
# Port
ttk.Label(frame, text="Port:").grid(row=1, column=0, sticky="w", pady=2)
self.port_entry = ttk.Entry(frame, width=10, style="Creds.TEntry")
self.port_entry.insert(0, "22")
self.port_entry.grid(row=1, column=1, sticky="w", pady=2)
# Username
@@ -177,9 +185,8 @@ class CredentialsDialog:
self.username_entry.grid(row=2, column=1, sticky="ew", pady=2)
# Initial Path
ttk.Label(frame, text="Initial Path:").grid(row=3, column=0, sticky="w", pady=2)
ttk.Label(frame, text="Initial Remote Directory:").grid(row=3, column=0, sticky="w", pady=2)
self.path_entry = ttk.Entry(frame, width=40, style="Creds.TEntry")
self.path_entry.insert(0, "~")
self.path_entry.grid(row=3, column=1, sticky="ew", pady=2)
# Auth Method
@@ -213,15 +220,37 @@ class CredentialsDialog:
self.passphrase_entry = ttk.Entry(frame, show="*", width=40, style="Creds.TEntry")
self.passphrase_entry.grid(row=7, column=1, sticky="ew", pady=2)
# Bookmark
self.bookmark_frame = ttk.LabelFrame(frame, text="Bookmark", padding=10)
self.bookmark_frame.grid(row=8, column=0, columnspan=2, sticky="ew", pady=5)
self.save_bookmark_var = tk.BooleanVar()
self.save_bookmark_check = ttk.Checkbutton(self.bookmark_frame, text="Save as bookmark", variable=self.save_bookmark_var, command=self._toggle_bookmark_name)
self.save_bookmark_check.pack(anchor="w")
if not self.keyring_available:
keyring_info_label = ttk.Label(self.bookmark_frame,
text="Python 'keyring' library not found.\nPasswords will not be saved.",
font=("TkDefaultFont", 9, "italic"))
keyring_info_label.pack(anchor="w", pady=(5,0))
self.save_bookmark_check.config(state=tk.DISABLED)
self.bookmark_name_label = ttk.Label(self.bookmark_frame, text="Bookmark Name:")
self.bookmark_name_entry = ttk.Entry(self.bookmark_frame, style="Creds.TEntry")
# Buttons
button_frame = ttk.Frame(frame)
button_frame.grid(row=8, column=1, sticky="e", pady=(15, 0))
connect_button = ttk.Button(button_frame, text="Connect", command=self._on_connect)
button_frame.grid(row=9, column=1, sticky="e", pady=(15, 0))
connect_text = "Save Changes" if self.is_edit_mode else "Connect"
connect_button = ttk.Button(button_frame, text=connect_text, command=self._on_connect)
connect_button.pack(side="left", padx=5)
cancel_button = ttk.Button(button_frame, text="Cancel", command=self._on_cancel)
cancel_button.pack(side="left")
self._populate_initial_data()
self._toggle_auth_fields()
self.window.bind("<Return>", lambda event: self._on_connect())
self.window.protocol("WM_DELETE_WINDOW", self._on_cancel)
@@ -233,6 +262,30 @@ class CredentialsDialog:
LxTools.center_window_cross_platform(self.window, self.window.winfo_width(), self.window.winfo_height())
self.host_entry.focus_set()
def _populate_initial_data(self):
if not self.initial_data:
self.port_entry.insert(0, "22")
self.path_entry.insert(0, "~")
return
self.host_entry.insert(0, self.initial_data.get("host", ""))
self.port_entry.insert(0, self.initial_data.get("port", "22"))
self.username_entry.insert(0, self.initial_data.get("username", ""))
self.path_entry.insert(0, self.initial_data.get("initial_path", "~"))
if self.initial_data.get("key_file"):
self.auth_method.set("keyfile")
self.keyfile_entry.insert(0, self.initial_data.get("key_file", ""))
else:
self.auth_method.set("password")
if self.is_edit_mode:
# In edit mode, we don't show the "save as bookmark" option,
# as we are already editing one. The name is fixed.
self.bookmark_frame.grid_remove()
# We still need to know the bookmark name for saving.
self.bookmark_name_entry.insert(0, self.initial_data.get("bookmark_name", ""))
def _get_ssh_keys(self) -> List[str]:
ssh_path = os.path.expanduser("~/.ssh")
keys = []
@@ -283,7 +336,26 @@ class CredentialsDialog:
self.window.update_idletasks()
self.window.geometry("")
def _toggle_bookmark_name(self):
if self.save_bookmark_var.get():
self.bookmark_name_label.pack(anchor="w", pady=(5,0))
self.bookmark_name_entry.pack(fill="x")
else:
self.bookmark_name_label.pack_forget()
self.bookmark_name_entry.pack_forget()
self.window.update_idletasks()
self.window.geometry("")
def _on_connect(self):
save_bookmark = self.save_bookmark_var.get() or self.is_edit_mode
bookmark_name = self.bookmark_name_entry.get()
if save_bookmark and not bookmark_name:
# In edit mode, the bookmark name comes from initial_data, so this check is for new bookmarks
if not self.is_edit_mode:
MessageDialog(message_type="error", text="Bookmark name cannot be empty.", master=self.window).show()
return
self.result = {
"host": self.host_entry.get(),
"port": int(self.port_entry.get() or 22),
@@ -292,6 +364,8 @@ class CredentialsDialog:
"password": self.password_entry.get() if self.auth_method.get() == "password" else None,
"key_file": self.keyfile_entry.get() if self.auth_method.get() == "keyfile" else None,
"passphrase": self.passphrase_entry.get() if self.auth_method.get() == "keyfile" else None,
"save_bookmark": save_bookmark,
"bookmark_name": bookmark_name
}
self.window.destroy()
@@ -304,6 +378,8 @@ class CredentialsDialog:
return self.result
class InputDialog:
"""
A simple dialog for getting a single line of text input from the user.