447 lines
19 KiB
Python
Executable File
447 lines
19 KiB
Python
Executable File
#!/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('<Double-1>', 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()
|