diff --git a/rdp_client.py b/rdp_client.py index c192641..047fc7a 100755 --- a/rdp_client.py +++ b/rdp_client.py @@ -41,9 +41,12 @@ import sys class RDPClient: def __init__(self): self.root = tk.Tk() - self.root.title("RDP Client - Professional (Press F1 for shortcuts)") - self.root.geometry("1000x700") - self.root.minsize(800, 600) + self.root.title("RDP Client - Professional") + self.root.geometry("1200x800") + self.root.minsize(900, 650) + + # Modern styling + self._setup_modern_theme() # Configuration self.config_dir = os.path.expanduser("~/.config/rdp-client") @@ -83,6 +86,96 @@ class RDPClient: self.logger.info("RDP Client initialized successfully") + def _setup_modern_theme(self): + """Setup modern visual theme and styling""" + # Configure the main window + self.root.configure(bg='#f8f9fa') + + # Create and configure a modern style + style = ttk.Style() + + # Try to use a modern theme if available + try: + style.theme_use('clam') # More modern than default + except: + pass + + # Define modern color palette + self.colors = { + 'primary': '#0066cc', # Modern blue + 'primary_dark': '#004499', # Darker blue for hover + 'secondary': '#6c757d', # Muted gray + 'success': '#28a745', # Green + 'danger': '#dc3545', # Red + 'warning': '#ffc107', # Yellow + 'info': '#17a2b8', # Cyan + 'light': '#f8f9fa', # Light gray + 'dark': '#343a40', # Dark gray + 'white': '#ffffff', + 'border': '#dee2e6', # Light border + 'hover': '#e9ecef' # Hover gray + } + + # Configure ttk styles with modern colors + style.configure('Modern.TFrame', + background=self.colors['white'], + relief='flat', + borderwidth=1) + + style.configure('Card.TFrame', + background=self.colors['white'], + relief='solid', + borderwidth=1, + bordercolor=self.colors['border']) + + style.configure('Modern.TLabel', + background=self.colors['white'], + foreground=self.colors['dark'], + font=('Segoe UI', 10)) + + style.configure('Title.TLabel', + background=self.colors['white'], + foreground=self.colors['dark'], + font=('Segoe UI', 12, 'bold')) + + style.configure('Modern.TButton', + background=self.colors['primary'], + foreground=self.colors['white'], + borderwidth=0, + focuscolor='none', + font=('Segoe UI', 9)) + + style.map('Modern.TButton', + background=[('active', self.colors['primary_dark']), + ('pressed', self.colors['primary_dark'])]) + + style.configure('Success.TButton', + background=self.colors['success'], + foreground=self.colors['white'], + borderwidth=0, + focuscolor='none', + font=('Segoe UI', 9)) + + style.configure('Danger.TButton', + background=self.colors['danger'], + foreground=self.colors['white'], + borderwidth=0, + focuscolor='none', + font=('Segoe UI', 9)) + + style.configure('Modern.Treeview', + background=self.colors['white'], + foreground=self.colors['dark'], + fieldbackground=self.colors['white'], + borderwidth=1, + relief='solid') + + style.configure('Modern.Treeview.Heading', + background=self.colors['light'], + foreground=self.colors['dark'], + relief='flat', + font=('Segoe UI', 9, 'bold')) + def _setup_logging(self): """Setup logging configuration""" self.logger = logging.getLogger('rdp_client') @@ -342,13 +435,9 @@ class RDPClient: return fallback def _setup_gui(self): - """Setup the main GUI""" - # Configure style - style = ttk.Style() - style.theme_use('clam') - - # Main container - main_frame = ttk.Frame(self.root, padding="10") + """Setup the main GUI with modern styling""" + # Main container with modern styling + main_frame = ttk.Frame(self.root, style='Modern.TFrame', padding="20") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Configure grid weights @@ -357,92 +446,149 @@ class RDPClient: main_frame.columnconfigure(1, weight=1) main_frame.rowconfigure(1, weight=1) - # Title - title_label = ttk.Label(main_frame, text="RDP Client - Professional", - font=('Arial', 16, 'bold')) - title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20)) + # Modern header with icon and title + header_frame = ttk.Frame(main_frame, style='Modern.TFrame') + header_frame.grid(row=0, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 20)) + header_frame.columnconfigure(1, weight=1) - # Left panel - Connections - left_frame = ttk.Frame(main_frame) - left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10)) - left_frame.rowconfigure(1, weight=1) - left_frame.rowconfigure(1, weight=1) # Only saved connections now - left_frame.columnconfigure(0, weight=1) + # Title with modern styling + title_label = ttk.Label(header_frame, text="๐Ÿ–ฅ๏ธ RDP Client Professional", + style='Title.TLabel', font=('Segoe UI', 18, 'bold')) + title_label.grid(row=0, column=0, sticky=tk.W) - # Saved Connections (now the main/only connections list) - saved_label = ttk.Label(left_frame, text="Saved Connections", font=('Arial', 10, 'bold')) - saved_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 5)) + # Help button in header + help_btn = ttk.Button(header_frame, text="โ“ Help (F1)", + command=self._show_help, style='Modern.TButton') + help_btn.grid(row=0, column=2, sticky=tk.E) - # Saved connections frame with listbox and scrollbar - saved_frame = ttk.Frame(left_frame) - saved_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) - saved_frame.rowconfigure(0, weight=1) - saved_frame.columnconfigure(0, weight=1) + # Left panel - Connections (modern card style) + left_card = ttk.Frame(main_frame, style='Card.TFrame', padding="15") + left_card.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 15)) + left_card.rowconfigure(1, weight=1) + left_card.columnconfigure(0, weight=1) - self.connections_listbox = tk.Listbox(saved_frame, font=('Arial', 9), width=30) - scrollbar = ttk.Scrollbar(saved_frame, orient="vertical", command=self.connections_listbox.yview) + # Connections header with count + connections_header = ttk.Frame(left_card, style='Modern.TFrame') + connections_header.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 15)) + connections_header.columnconfigure(0, weight=1) + + self.connections_title = ttk.Label(connections_header, text="๐Ÿ“ Saved Connections", + style='Title.TLabel') + self.connections_title.grid(row=0, column=0, sticky=tk.W) + + # Modern connections listbox with better styling + listbox_frame = ttk.Frame(left_card, style='Modern.TFrame') + listbox_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + listbox_frame.rowconfigure(0, weight=1) + listbox_frame.columnconfigure(0, weight=1) + + # Replace old listbox with modern styling + self.connections_listbox = tk.Listbox( + listbox_frame, + font=('Segoe UI', 10), + bg=self.colors['white'], + fg=self.colors['dark'], + selectbackground=self.colors['primary'], + selectforeground=self.colors['white'], + borderwidth=0, + highlightthickness=1, + highlightcolor=self.colors['primary'], + relief='flat', + activestyle='none' + ) + + scrollbar = ttk.Scrollbar(listbox_frame, orient="vertical", command=self.connections_listbox.yview) self.connections_listbox.configure(yscrollcommand=scrollbar.set) - self.connections_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + self.connections_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 2)) scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) - # Connection buttons - conn_buttons_frame = ttk.Frame(left_frame) - conn_buttons_frame.grid(row=2, column=0, pady=(10, 0), sticky=(tk.W, tk.E)) + # Modern action buttons with icons + buttons_frame = ttk.Frame(left_card, style='Modern.TFrame') + buttons_frame.grid(row=2, column=0, pady=(15, 0), sticky=(tk.W, tk.E)) + buttons_frame.columnconfigure(0, weight=1) + buttons_frame.columnconfigure(1, weight=1) + buttons_frame.columnconfigure(2, weight=1) - ttk.Button(conn_buttons_frame, text="Connect", - command=self._connect_selected).pack(side=tk.LEFT, padx=(0, 5)) - ttk.Button(conn_buttons_frame, text="Edit", - command=self._edit_selected).pack(side=tk.LEFT, padx=(0, 5)) - ttk.Button(conn_buttons_frame, text="Delete", - command=self._delete_selected).pack(side=tk.LEFT) + # Primary action buttons + ttk.Button(buttons_frame, text="๐Ÿ”— Connect", + command=self._connect_selected, style='Success.TButton').grid( + row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5)) - # Right panel - Actions and Details - right_frame = ttk.Frame(main_frame) - right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S)) - right_frame.rowconfigure(1, weight=1) - right_frame.columnconfigure(0, weight=1) + ttk.Button(buttons_frame, text="โœ๏ธ Edit", + command=self._edit_selected, style='Modern.TButton').grid( + row=0, column=1, sticky=(tk.W, tk.E), padx=2.5) - # Actions frame - actions_frame = ttk.LabelFrame(right_frame, text="Actions", padding="10") - actions_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10)) + ttk.Button(buttons_frame, text="๐Ÿ—‘๏ธ Delete", + command=self._delete_selected, style='Danger.TButton').grid( + row=0, column=2, sticky=(tk.W, tk.E), padx=(5, 0)) - ttk.Button(actions_frame, text="New Connection", - command=self._new_connection, width=20).pack(pady=2) - ttk.Button(actions_frame, text="Test Connection", - command=self._test_selected_connection, width=20).pack(pady=2) - ttk.Button(actions_frame, text="Import Connections", - command=self._import_connections, width=20).pack(pady=2) - ttk.Button(actions_frame, text="Export Connections", - command=self._export_connections, width=20).pack(pady=2) - ttk.Button(actions_frame, text="Clear All Credentials", - command=self._clear_credentials, width=20).pack(pady=2) + # Right panel - Actions and Details (modern card style) + right_card = ttk.Frame(main_frame, style='Card.TFrame', padding="15") + right_card.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S)) + right_card.rowconfigure(1, weight=1) + right_card.columnconfigure(0, weight=1) - # Details frame - details_frame = ttk.LabelFrame(right_frame, text="Connection Details", padding="10") - details_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) - details_frame.columnconfigure(1, weight=1) + # Actions section with modern styling + actions_header = ttk.Label(right_card, text="โšก Quick Actions", style='Title.TLabel') + actions_header.grid(row=0, column=0, sticky=tk.W, pady=(0, 15)) - # Details labels + actions_frame = ttk.Frame(right_card, style='Modern.TFrame') + actions_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=(0, 15)) + actions_frame.columnconfigure(0, weight=1) + actions_frame.columnconfigure(1, weight=1) + + ttk.Button(actions_frame, text="โž• New Connection", + command=self._new_connection, style='Modern.TButton').grid( + row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5), pady=3) + + ttk.Button(actions_frame, text="๐Ÿงช Test Connection", + command=self._test_selected_connection, style='Modern.TButton').grid( + row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=3) + + ttk.Button(actions_frame, text="๐Ÿ“ฅ Import", + command=self._import_connections, style='Modern.TButton').grid( + row=1, column=0, sticky=(tk.W, tk.E), padx=(0, 5), pady=3) + + ttk.Button(actions_frame, text="๐Ÿ“ค Export", + command=self._export_connections, style='Modern.TButton').grid( + row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=3) + + ttk.Button(actions_frame, text="๐Ÿ” Clear Credentials", + command=self._clear_credentials, style='Danger.TButton').grid( + row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=3) + + # Details section with modern card styling + details_label = ttk.Label(right_card, text="๐Ÿ“‹ Connection Details", style='Title.TLabel') + details_label.grid(row=1, column=0, sticky=tk.W, pady=(20, 15)) + + details_card = ttk.Frame(right_card, style='Card.TFrame', padding="10") + details_card.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + details_card.columnconfigure(1, weight=1) + + # Modern details labels with better styling self.details_labels = {} details_fields = [ - ("Server:", "server"), - ("Username:", "username"), - ("Domain:", "domain"), - ("Resolution:", "resolution"), - ("Color Depth:", "color_depth"), - ("Multi-Monitor:", "multimon"), - ("Sound:", "sound"), - ("Clipboard:", "clipboard"), - ("Drive Sharing:", "drives"), - ("Created:", "created") + ("๐Ÿ–ฅ๏ธ Server:", "server"), + ("๐Ÿ‘ค Username:", "username"), + ("๐Ÿข Domain:", "domain"), + ("๐Ÿ“บ Resolution:", "resolution"), + ("๐ŸŽจ Color Depth:", "color_depth"), + ("๐Ÿ–ผ๏ธ Multi-Monitor:", "multimon"), + ("๐Ÿ”Š Sound:", "sound"), + ("๐Ÿ“‹ Clipboard:", "clipboard"), + ("๐Ÿ’พ Drive Sharing:", "drives"), + ("๐Ÿ“… Created:", "created") ] for i, (label, field) in enumerate(details_fields): - ttk.Label(details_frame, text=label, font=('Arial', 9, 'bold')).grid( - row=i, column=0, sticky=tk.W, pady=2) - value_label = ttk.Label(details_frame, text="", font=('Arial', 9)) - value_label.grid(row=i, column=1, sticky=tk.W, padx=(10, 0), pady=2) + label_widget = ttk.Label(details_card, text=label, style='Modern.TLabel', + font=('Segoe UI', 9, 'bold')) + label_widget.grid(row=i, column=0, sticky=tk.W, pady=4, padx=(0, 10)) + + value_label = ttk.Label(details_card, text="โ€”", style='Modern.TLabel', + font=('Segoe UI', 9)) + value_label.grid(row=i, column=1, sticky=tk.W, pady=4) self.details_labels[field] = value_label # Bind listbox selection @@ -452,19 +598,19 @@ class RDPClient: # Keyboard shortcuts self._setup_keyboard_shortcuts() - # Bottom frame - Status - status_frame = ttk.Frame(main_frame) - status_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(10, 0)) + # Modern status bar + status_frame = ttk.Frame(main_frame, style='Modern.TFrame') + status_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(20, 0)) status_frame.columnconfigure(0, weight=1) - self.status_var = tk.StringVar(value="Ready") + self.status_var = tk.StringVar(value="โœ… Ready") status_label = ttk.Label(status_frame, textvariable=self.status_var, - font=('Arial', 9), foreground='gray') + style='Modern.TLabel', font=('Segoe UI', 9)) status_label.grid(row=0, column=0, sticky=tk.W) - # Exit button - ttk.Button(status_frame, text="Exit", - command=self.root.quit).grid(row=0, column=1, sticky=tk.E) + # Modern exit button + ttk.Button(status_frame, text="โŒ Exit", + command=self.root.quit, style='Danger.TButton').grid(row=0, column=1, sticky=tk.E) def _setup_keyboard_shortcuts(self): """Setup keyboard shortcuts for the application""" @@ -497,13 +643,11 @@ class RDPClient: def _add_keyboard_shortcuts_info(self): """Add keyboard shortcuts information to the status bar or help""" - # You could create a help dialog or status tooltip here - # For now, we'll add it to the window title when focused def show_shortcuts_hint(event): - self.status_var.set("Shortcuts: Ctrl+N=New, Enter=Connect, Del=Delete, F2=Edit, Ctrl+T=Test, F5=Refresh, Ctrl+Q=Quit") + self.status_var.set("โŒจ๏ธ Shortcuts: Ctrl+N=New, Enter=Connect, Del=Delete, F2=Edit, Ctrl+T=Test, F5=Refresh, Ctrl+Q=Quit") def clear_shortcuts_hint(event): - self.status_var.set("Ready") + self.status_var.set("โœ… Ready") # Show shortcuts hint when connections listbox gets focus self.connections_listbox.bind('', show_shortcuts_hint) @@ -555,10 +699,14 @@ Multi-Monitor Support: for name in sorted(self.connections.keys(), key=str.lower): self.connections_listbox.insert(tk.END, name) + # Update connections count in title + count = len(self.connections) + self.connections_title.config(text=f"๐Ÿ“ Saved Connections ({count})") + # Clear details if no connections if not self.connections: for label in self.details_labels.values(): - label.config(text="") + label.config(text="โ€”") def _update_details(self, conn): """Update the details panel with connection info""" @@ -612,7 +760,7 @@ Multi-Monitor Support: self._add_to_history(name) self._refresh_connections_list() - self.status_var.set(f"Connection '{name}' saved successfully") + self.status_var.set(f"๐Ÿ’พ Connection '{name}' saved successfully") def _edit_selected(self): """Edit selected connection""" @@ -676,7 +824,7 @@ Multi-Monitor Support: self._add_to_history(new_name) self._refresh_connections_list() - self.status_var.set(f"Connection '{new_name}' updated successfully") + self.status_var.set(f"โœ๏ธ Connection '{new_name}' updated successfully") def _delete_selected(self): """Delete selected connection""" @@ -707,7 +855,7 @@ Multi-Monitor Support: for label in self.details_labels.values(): label.config(text="") - self.status_var.set(f"Connection '{name}' deleted") + self.status_var.set(f"๐Ÿ—‘๏ธ Connection '{name}' deleted") def _connect_selected(self, event=None): """Connect to selected connection""" @@ -751,7 +899,7 @@ Multi-Monitor Support: username = conn['username'] self.logger.info(f"Attempting RDP connection to {server} as {username}") - self.status_var.set(f"Connecting to {server}...") + self.status_var.set(f"๐Ÿ”— Connecting to {server}...") # Test connectivity first if not self._test_server_connectivity(server): @@ -1050,7 +1198,7 @@ Multi-Monitor Support: self._save_credentials() self._refresh_connections_list() - self.status_var.set(f"Successfully imported {imported_count} connections") + self.status_var.set(f"๐Ÿ“ฅ Successfully imported {imported_count} connections") messagebox.showinfo("Import Complete", f"Successfully imported {imported_count} connections.") except FileNotFoundError: @@ -1097,7 +1245,7 @@ Multi-Monitor Support: with open(file_path, 'w') as f: json.dump(export_data, f, indent=2) - self.status_var.set(f"Connections exported to {os.path.basename(file_path)}") + self.status_var.set(f"๐Ÿ“ค Connections exported to {os.path.basename(file_path)}") messagebox.showinfo("Export Complete", f"Successfully exported {len(self.connections)} connections to:\n{file_path}") except Exception as e: @@ -1108,7 +1256,7 @@ Multi-Monitor Support: if messagebox.askyesno("Confirm", "Clear all saved passwords?"): self.credentials.clear() self._save_credentials() - self.status_var.set("All credentials cleared") + self.status_var.set("๐Ÿ” All credentials cleared") self.logger.info("All credentials cleared by user") def _test_selected_connection(self):