#!/usr/bin/env python3 """ HackerSoft RDP Profile Manager - GUI für RDP Thin Client Speichere diese Datei als: files/rdp-profile-manager.py Version: 1.1 """ import tkinter as tk from tkinter import ttk, messagebox import configparser import subprocess from pathlib import Path import os CONFIG_DIR = Path.home() / ".config" / "rdp-profiles" CONFIG_FILE = CONFIG_DIR / "profiles.ini" VERSION = "1.1" # Firmeninformationen COMPANY_NAME = "Hacker-Net Telekommunikation" COMPANY_ADDRESS = "Am Wunderburgpark 5b\n26135 Oldenburg" COMPANY_PHONE = "Tel.: +49 441 35065316" COMPANY_EMAIL = "E-Mail: info@hacker-net.de" # Prüfe ob Emoji-Fonts verfügbar sind def check_emoji_support(): """Prüft ob Emoji-Fonts installiert sind""" emoji_fonts = [ '/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf', '/usr/share/fonts/opentype/noto/NotoColorEmoji.ttf', '/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf' ] return any(Path(font).exists() for font in emoji_fonts) # Emoji-Support erkennen USE_EMOJIS = check_emoji_support() # Button-Texte (mit oder ohne Emojis) if USE_EMOJIS: BTN_NEW = "➕ Neues Profil" BTN_EDIT = "✏️ Bearbeiten" BTN_DELETE = "🗑️ Löschen" BTN_CONNECT = "🔌 Verbinden" BTN_BLUETOOTH = "🎧 Bluetooth" BTN_INFO = "ℹ️ Info" BTN_AUTOSTART = "⭐ Autostart" BTN_SAVE = "💾 Speichern" BTN_CANCEL = "❌ Abbrechen" LBL_AUDIO = "🔊 Audio umleiten" LBL_MIC = "🎤 Mikrofon umleiten" LBL_USB = "💾 USB-Geräte umleiten" LBL_SMARTCARD = "💳 Smart Cards umleiten" LBL_PRINTER = "🖨️ Drucker umleiten" else: BTN_NEW = "+ Neues Profil" BTN_EDIT = "Bearbeiten" BTN_DELETE = "Löschen" BTN_CONNECT = "Verbinden" BTN_BLUETOOTH = "Bluetooth" BTN_INFO = "Info" BTN_AUTOSTART = "* Autostart" BTN_SAVE = "Speichern" BTN_CANCEL = "Abbrechen" LBL_AUDIO = "Audio umleiten" LBL_MIC = "Mikrofon umleiten" LBL_USB = "USB-Geräte umleiten" LBL_SMARTCARD = "Smart Cards umleiten" LBL_PRINTER = "Drucker umleiten" class RDPProfileManager: def __init__(self, root): self.root = root self.root.title("HackerSoft - RDP Profile Manager") self.root.geometry("900x600") CONFIG_DIR.mkdir(parents=True, exist_ok=True) if not CONFIG_FILE.exists(): CONFIG_FILE.touch() self.config = configparser.ConfigParser() self.load_profiles() self.create_widgets() self.refresh_profile_list() def load_profiles(self): self.config.read(CONFIG_FILE) def save_profiles(self): with open(CONFIG_FILE, 'w') as f: self.config.write(f) def create_widgets(self): # Top Frame - Buttons top_frame = ttk.Frame(self.root, padding="10") top_frame.pack(fill=tk.X) ttk.Button(top_frame, text=BTN_NEW, command=self.new_profile).pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text=BTN_EDIT, command=self.edit_profile).pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text=BTN_DELETE, command=self.delete_profile).pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text=BTN_CONNECT, command=self.connect_profile).pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text=BTN_AUTOSTART, command=self.toggle_autostart).pack(side=tk.LEFT, padx=5) # Rechte Seite ttk.Button(top_frame, text=BTN_INFO, command=self.show_info).pack(side=tk.RIGHT, padx=5) ttk.Button(top_frame, text=BTN_BLUETOOTH, command=self.open_bluetooth).pack(side=tk.RIGHT, padx=5) # Profile List list_frame = ttk.Frame(self.root, padding="10") list_frame.pack(fill=tk.BOTH, expand=True) scrollbar = ttk.Scrollbar(list_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) columns = ('Autostart', 'Server', 'Benutzer', 'Auflösung', 'Audio', 'USB') self.tree = ttk.Treeview(list_frame, columns=columns, show='tree headings', yscrollcommand=scrollbar.set) scrollbar.config(command=self.tree.yview) self.tree.heading('#0', text='Profil') self.tree.heading('Autostart', text='Auto') self.tree.heading('Server', text='Server') self.tree.heading('Benutzer', text='Benutzer') self.tree.heading('Auflösung', text='Auflösung') self.tree.heading('Audio', text='Audio') self.tree.heading('USB', text='USB') self.tree.column('#0', width=150) self.tree.column('Autostart', width=50) self.tree.column('Server', width=180) self.tree.column('Benutzer', width=120) self.tree.column('Auflösung', width=120) self.tree.column('Audio', width=80) self.tree.column('USB', width=80) self.tree.pack(fill=tk.BOTH, expand=True) self.tree.bind('', lambda e: self.connect_profile()) def show_info(self): """Zeigt Info-Dialog mit Firmendaten""" info_window = tk.Toplevel(self.root) info_window.title("Info") info_window.geometry("450x350") info_window.transient(self.root) info_window.grab_set() # Logo/Header header_frame = ttk.Frame(info_window, padding="20") header_frame.pack(fill=tk.X) ttk.Label(header_frame, text="HackerSoft", font=('Arial', 24, 'bold')).pack() ttk.Label(header_frame, text="RDP Profile Manager", font=('Arial', 12)).pack() ttk.Label(header_frame, text=f"Version {VERSION}", font=('Arial', 10), foreground='gray').pack() # Separator ttk.Separator(info_window, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=20, pady=10) # Firmeninfo info_frame = ttk.Frame(info_window, padding="20") info_frame.pack(fill=tk.BOTH, expand=True) ttk.Label(info_frame, text=COMPANY_NAME, font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0,5)) ttk.Label(info_frame, text=COMPANY_ADDRESS, font=('Arial', 10)).pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=COMPANY_PHONE, font=('Arial', 10)).pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=COMPANY_EMAIL, font=('Arial', 10), foreground='blue').pack(anchor=tk.W, pady=2) # Button button_frame = ttk.Frame(info_window, padding="10") button_frame.pack(fill=tk.X, side=tk.BOTTOM) ttk.Button(button_frame, text="OK", command=info_window.destroy).pack() def toggle_autostart(self): """Setzt/entfernt Autostart für ausgewähltes Profil""" selected = self.tree.selection() if not selected: messagebox.showwarning("Warnung", "Bitte wählen Sie ein Profil aus!") return profile_name = self.tree.item(selected[0])['text'] profile = self.config[profile_name] # Aktueller Autostart-Status current_autostart = profile.getboolean('autostart', False) if current_autostart: # Deaktivieren self.config[profile_name]['autostart'] = 'False' messagebox.showinfo("Autostart", f"Autostart für '{profile_name}' wurde deaktiviert!") else: # Aktivieren - alle anderen deaktivieren for section in self.config.sections(): self.config[section]['autostart'] = 'False' self.config[profile_name]['autostart'] = 'True' messagebox.showinfo("Autostart", f"'{profile_name}' wird jetzt automatisch gestartet!") self.save_profiles() self.refresh_profile_list() def refresh_profile_list(self): self.tree.delete(*self.tree.get_children()) for profile_name in self.config.sections(): profile = self.config[profile_name] autostart_icon = '⭐' if profile.getboolean('autostart', False) else '' username_display = profile.get('username', '-') or '-' self.tree.insert('', tk.END, text=profile_name, values=( autostart_icon, profile.get('server', ''), username_display, profile.get('resolution', 'Client'), '✓' if profile.getboolean('redirect_audio', True) else '✗', '✓' if profile.getboolean('redirect_usb', True) else '✗' )) def new_profile(self): dialog = ProfileDialog(self.root, "Neues Profil") self.root.wait_window(dialog.top) if dialog.result: profile_name = dialog.result['name'] if profile_name in self.config: messagebox.showerror("Fehler", f"Profil '{profile_name}' existiert bereits!") return self.config[profile_name] = { 'server': dialog.result['server'], 'username': dialog.result['username'], 'domain': dialog.result['domain'], 'resolution': dialog.result['resolution'], 'multimon': str(dialog.result['multimon']), 'redirect_audio': str(dialog.result['redirect_audio']), 'redirect_microphone': str(dialog.result['redirect_microphone']), 'redirect_usb': str(dialog.result['redirect_usb']), 'redirect_smartcard': str(dialog.result['redirect_smartcard']), 'redirect_printers': str(dialog.result['redirect_printers']), 'exit_hotkey': dialog.result['exit_hotkey'], 'autostart': 'False' } self.save_profiles() self.refresh_profile_list() messagebox.showinfo("Erfolg", f"Profil '{profile_name}' wurde erstellt!") def edit_profile(self): selected = self.tree.selection() if not selected: messagebox.showwarning("Warnung", "Bitte wählen Sie ein Profil aus!") return profile_name = self.tree.item(selected[0])['text'] profile = self.config[profile_name] dialog = ProfileDialog(self.root, f"Profil bearbeiten: {profile_name}", profile_name, profile) self.root.wait_window(dialog.top) if dialog.result: new_name = dialog.result['name'] if new_name != profile_name: self.config.remove_section(profile_name) self.config[new_name] = { 'server': dialog.result['server'], 'username': dialog.result['username'], 'domain': dialog.result['domain'], 'resolution': dialog.result['resolution'], 'multimon': str(dialog.result['multimon']), 'redirect_audio': str(dialog.result['redirect_audio']), 'redirect_microphone': str(dialog.result['redirect_microphone']), 'redirect_usb': str(dialog.result['redirect_usb']), 'redirect_smartcard': str(dialog.result['redirect_smartcard']), 'redirect_printers': str(dialog.result['redirect_printers']), 'exit_hotkey': dialog.result['exit_hotkey'], 'autostart': profile.get('autostart', 'False') } self.save_profiles() self.refresh_profile_list() messagebox.showinfo("Erfolg", f"Profil '{new_name}' wurde aktualisiert!") def delete_profile(self): selected = self.tree.selection() if not selected: messagebox.showwarning("Warnung", "Bitte wählen Sie ein Profil aus!") return profile_name = self.tree.item(selected[0])['text'] if messagebox.askyesno("Bestätigung", f"Profil '{profile_name}' wirklich löschen?"): self.config.remove_section(profile_name) self.save_profiles() self.refresh_profile_list() messagebox.showinfo("Erfolg", f"Profil '{profile_name}' wurde gelöscht!") def connect_profile(self): selected = self.tree.selection() if not selected: messagebox.showwarning("Warnung", "Bitte wählen Sie ein Profil aus!") return profile_name = self.tree.item(selected[0])['text'] try: subprocess.Popen(['/usr/local/bin/rdp-launcher.sh', profile_name]) self.root.withdraw() except Exception as e: messagebox.showerror("Fehler", f"Verbindung fehlgeschlagen: {str(e)}") def open_bluetooth(self): try: subprocess.Popen(['blueman-manager']) except Exception as e: messagebox.showerror("Fehler", f"Bluetooth Manager konnte nicht gestartet werden: {str(e)}") class ProfileDialog: def __init__(self, parent, title, profile_name=None, profile_data=None): self.result = None self.top = tk.Toplevel(parent) self.top.title(title) self.top.geometry("550x700") self.top.transient(parent) self.top.grab_set() main_frame = ttk.Frame(self.top, padding="20") main_frame.pack(fill=tk.BOTH, expand=True) row = 0 # Profile Name ttk.Label(main_frame, text="Profilname:", font=('', 10, 'bold')).grid(row=row, column=0, sticky=tk.W, pady=5) self.name_var = tk.StringVar(value=profile_name or "") ttk.Entry(main_frame, textvariable=self.name_var, width=40).grid(row=row, column=1, pady=5) row += 1 ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=2, sticky='ew', pady=10) row += 1 # Server ttk.Label(main_frame, text="Server:").grid(row=row, column=0, sticky=tk.W, pady=5) self.server_var = tk.StringVar(value=profile_data.get('server', '') if profile_data else '') ttk.Entry(main_frame, textvariable=self.server_var, width=40).grid(row=row, column=1, pady=5) row += 1 # Username ttk.Label(main_frame, text="Benutzername (optional):").grid(row=row, column=0, sticky=tk.W, pady=5) self.username_var = tk.StringVar(value=profile_data.get('username', '') if profile_data else '') ttk.Entry(main_frame, textvariable=self.username_var, width=40).grid(row=row, column=1, pady=5) row += 1 # Domain ttk.Label(main_frame, text="Domäne (optional):").grid(row=row, column=0, sticky=tk.W, pady=5) self.domain_var = tk.StringVar(value=profile_data.get('domain', '') if profile_data else '') ttk.Entry(main_frame, textvariable=self.domain_var, width=40).grid(row=row, column=1, pady=5) row += 1 # Resolution ttk.Label(main_frame, text="Auflösung:").grid(row=row, column=0, sticky=tk.W, pady=5) self.resolution_var = tk.StringVar(value=profile_data.get('resolution', 'client') if profile_data else 'client') resolution_combo = ttk.Combobox(main_frame, textvariable=self.resolution_var, width=37, state='readonly') resolution_combo['values'] = ('client', '1920x1080', '1680x1050', '1600x900', '1440x900', '1366x768', '1280x1024', '1024x768') resolution_combo.grid(row=row, column=1, pady=5) row += 1 # Multi-Monitor self.multimon_var = tk.BooleanVar(value=profile_data.getboolean('multimon', False) if profile_data else False) ttk.Checkbutton(main_frame, text="Alle Monitore nutzen (Multi-Monitor)", variable=self.multimon_var).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=5) row += 1 ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=2, sticky='ew', pady=15) row += 1 ttk.Label(main_frame, text="Umleitungen:", font=('', 10, 'bold')).grid(row=row, column=0, sticky=tk.W, pady=5) row += 1 # Redirects self.audio_var = tk.BooleanVar(value=profile_data.getboolean('redirect_audio', True) if profile_data else True) ttk.Checkbutton(main_frame, text=LBL_AUDIO, variable=self.audio_var).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=3) row += 1 self.microphone_var = tk.BooleanVar(value=profile_data.getboolean('redirect_microphone', True) if profile_data else True) ttk.Checkbutton(main_frame, text=LBL_MIC, variable=self.microphone_var).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=3) row += 1 self.usb_var = tk.BooleanVar(value=profile_data.getboolean('redirect_usb', True) if profile_data else True) ttk.Checkbutton(main_frame, text=LBL_USB, variable=self.usb_var).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=3) row += 1 self.smartcard_var = tk.BooleanVar(value=profile_data.getboolean('redirect_smartcard', True) if profile_data else True) ttk.Checkbutton(main_frame, text=LBL_SMARTCARD, variable=self.smartcard_var).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=3) row += 1 self.printers_var = tk.BooleanVar(value=profile_data.getboolean('redirect_printers', False) if profile_data else False) ttk.Checkbutton(main_frame, text=LBL_PRINTER, variable=self.printers_var).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=3) row += 1 ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=2, sticky='ew', pady=15) row += 1 # Exit Hotkey ttk.Label(main_frame, text="Tastenkombination:").grid(row=row, column=0, sticky=tk.W, pady=5) self.hotkey_var = tk.StringVar(value=profile_data.get('exit_hotkey', 'Control+Alt+q') if profile_data else 'Control+Alt+q') ttk.Entry(main_frame, textvariable=self.hotkey_var, width=40).grid(row=row, column=1, pady=5) row += 1 ttk.Label(main_frame, text="Format: Control+Alt+q", font=('', 8), foreground='gray').grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=2) row += 1 # Buttons button_frame = ttk.Frame(main_frame) button_frame.grid(row=row, column=0, columnspan=2, pady=20) ttk.Button(button_frame, text=BTN_SAVE, command=self.ok).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text=BTN_CANCEL, command=self.cancel).pack(side=tk.LEFT, padx=5) def ok(self): name = self.name_var.get().strip() server = self.server_var.get().strip() username = self.username_var.get().strip() if not name or not server: messagebox.showerror("Fehler", "Bitte füllen Sie mindestens Profilname und Server aus!") return self.result = { 'name': name, 'server': server, 'username': username, 'domain': self.domain_var.get().strip(), 'resolution': self.resolution_var.get(), 'multimon': self.multimon_var.get(), 'redirect_audio': self.audio_var.get(), 'redirect_microphone': self.microphone_var.get(), 'redirect_usb': self.usb_var.get(), 'redirect_smartcard': self.smartcard_var.get(), 'redirect_printers': self.printers_var.get(), 'exit_hotkey': self.hotkey_var.get().strip() } self.top.destroy() def cancel(self): self.top.destroy() if __name__ == '__main__': root = tk.Tk() app = RDPProfileManager(root) root.mainloop()