first commit

This commit is contained in:
2025-12-22 14:50:34 +01:00
commit 8620ff31ac
16 changed files with 2392 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

+412
View File
@@ -0,0 +1,412 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HackerSoft Boot Logo Generator</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5em;
}
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
}
.control-group {
display: flex;
flex-direction: column;
}
label {
font-weight: 600;
margin-bottom: 8px;
color: #555;
}
input, select {
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: #667eea;
}
.canvas-container {
text-align: center;
margin: 30px 0;
padding: 20px;
background: #000;
border-radius: 10px;
}
canvas {
max-width: 100%;
border: 3px solid #667eea;
border-radius: 5px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.button-group {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
button {
padding: 15px 30px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #28a745;
color: white;
}
.btn-secondary:hover {
background: #218838;
transform: translateY(-2px);
}
.info-box {
background: #e3f2fd;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
border-left: 5px solid #2196f3;
}
.info-box h3 {
color: #1976d2;
margin-bottom: 10px;
}
.info-box ul {
margin-left: 20px;
color: #555;
}
.info-box li {
margin: 5px 0;
}
.presets {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
}
.preset-btn {
padding: 8px 15px;
background: #f0f0f0;
border: 2px solid #ddd;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
.preset-btn:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
</style>
</head>
<body>
<div class="container">
<h1>🎨 HackerSoft Boot Logo Generator</h1>
<div class="controls">
<div class="control-group">
<label for="mainText">Haupttext:</label>
<input type="text" id="mainText" value="HackerSoft">
</div>
<div class="control-group">
<label for="subText">Untertitel:</label>
<input type="text" id="subText" value="RDP Thin Client">
</div>
<div class="control-group">
<label for="companyText">Firma (unten):</label>
<input type="text" id="companyText" value="Hacker-Net Telekommunikation">
</div>
<div class="control-group">
<label for="mainColor">Hauptfarbe:</label>
<input type="color" id="mainColor" value="#00ff88">
</div>
<div class="control-group">
<label for="accentColor">Akzentfarbe:</label>
<input type="color" id="accentColor" value="#667eea">
</div>
<div class="control-group">
<label for="bgStyle">Hintergrund:</label>
<select id="bgStyle">
<option value="gradient">Gradient (Dunkel)</option>
<option value="solid-dark">Einfarbig Dunkel</option>
<option value="solid-black">Schwarz</option>
<option value="matrix">Matrix-Style</option>
</select>
</div>
</div>
<div class="presets">
<button class="preset-btn" onclick="loadPreset('hacker')">🟢 Hacker Green</button>
<button class="preset-btn" onclick="loadPreset('corporate')">🔵 Corporate Blue</button>
<button class="preset-btn" onclick="loadPreset('fire')">🔴 Fire Red</button>
<button class="preset-btn" onclick="loadPreset('modern')">⚪ Modern Minimal</button>
</div>
<div class="canvas-container">
<canvas id="logoCanvas" width="1920" height="1080"></canvas>
</div>
<div class="button-group">
<button class="btn-primary" onclick="generateLogo()">🎨 Logo Generieren</button>
<button class="btn-secondary" onclick="downloadLogo('boot-logo')">💾 Boot-Logo Download</button>
<button class="btn-secondary" onclick="downloadLogo('grub-background')">💾 GRUB Background Download</button>
<button class="btn-secondary" onclick="downloadLogo('login-background')">💾 Login Background Download</button>
</div>
<div class="info-box">
<h3>📋 Verwendung:</h3>
<ul>
<li><strong>1.</strong> Text und Farben anpassen</li>
<li><strong>2.</strong> "Logo Generieren" klicken</li>
<li><strong>3.</strong> Alle 3 Varianten downloaden</li>
<li><strong>4.</strong> Dateien nach <code>rdp-thin-client/files/branding/</code> kopieren</li>
<li><strong>5.</strong> Ansible-Playbook ausführen</li>
</ul>
<br>
<strong>💡 Tipp:</strong> Die "Presets" geben dir verschiedene Farb-Styles!
</div>
</div>
<script>
const canvas = document.getElementById('logoCanvas');
const ctx = canvas.getContext('2d');
// Presets
const presets = {
hacker: {
mainColor: '#00ff88',
accentColor: '#00cc66',
bgStyle: 'matrix'
},
corporate: {
mainColor: '#4a90e2',
accentColor: '#667eea',
bgStyle: 'gradient'
},
fire: {
mainColor: '#ff4444',
accentColor: '#ff8800',
bgStyle: 'gradient'
},
modern: {
mainColor: '#ffffff',
accentColor: '#888888',
bgStyle: 'solid-black'
}
};
function loadPreset(preset) {
const p = presets[preset];
document.getElementById('mainColor').value = p.mainColor;
document.getElementById('accentColor').value = p.accentColor;
document.getElementById('bgStyle').value = p.bgStyle;
generateLogo();
}
function generateLogo() {
const mainText = document.getElementById('mainText').value;
const subText = document.getElementById('subText').value;
const companyText = document.getElementById('companyText').value;
const mainColor = document.getElementById('mainColor').value;
const accentColor = document.getElementById('accentColor').value;
const bgStyle = document.getElementById('bgStyle').value;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Background
drawBackground(bgStyle, mainColor, accentColor);
// Main Text
ctx.font = 'bold 180px Arial';
ctx.fillStyle = mainColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Text with glow effect
ctx.shadowColor = mainColor;
ctx.shadowBlur = 30;
ctx.fillText(mainText, canvas.width / 2, canvas.height / 2 - 80);
// Subtitle
ctx.shadowBlur = 20;
ctx.font = 'bold 60px Arial';
ctx.fillStyle = accentColor;
ctx.fillText(subText, canvas.width / 2, canvas.height / 2 + 50);
// Company text at bottom
ctx.shadowBlur = 10;
ctx.font = '40px Arial';
ctx.fillStyle = '#888888';
ctx.fillText(companyText, canvas.width / 2, canvas.height - 100);
// Version/Build info
ctx.font = '30px Arial';
ctx.fillStyle = '#666666';
ctx.fillText('v1.0 | Debian 12/13', canvas.width / 2, canvas.height - 50);
// Reset shadow
ctx.shadowBlur = 0;
// Decorative elements
drawDecorations(mainColor, accentColor);
}
function drawBackground(style, mainColor, accentColor) {
switch(style) {
case 'gradient':
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#1a1a2e');
gradient.addColorStop(0.5, '#16213e');
gradient.addColorStop(1, '#0f0f1e');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
break;
case 'solid-dark':
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
break;
case 'solid-black':
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
break;
case 'matrix':
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Matrix effect
for (let i = 0; i < 50; i++) {
ctx.fillStyle = mainColor + '20';
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const size = Math.random() * 30 + 10;
ctx.fillRect(x, y, 2, size);
}
break;
}
}
function drawDecorations(mainColor, accentColor) {
// Corner accents
ctx.strokeStyle = mainColor;
ctx.lineWidth = 5;
// Top-left
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.lineTo(50, 50);
ctx.lineTo(150, 50);
ctx.stroke();
// Top-right
ctx.beginPath();
ctx.moveTo(canvas.width - 50, 150);
ctx.lineTo(canvas.width - 50, 50);
ctx.lineTo(canvas.width - 150, 50);
ctx.stroke();
// Bottom-left
ctx.beginPath();
ctx.moveTo(50, canvas.height - 150);
ctx.lineTo(50, canvas.height - 50);
ctx.lineTo(150, canvas.height - 50);
ctx.stroke();
// Bottom-right
ctx.beginPath();
ctx.moveTo(canvas.width - 50, canvas.height - 150);
ctx.lineTo(canvas.width - 50, canvas.height - 50);
ctx.lineTo(canvas.width - 150, canvas.height - 50);
ctx.stroke();
}
function downloadLogo(filename) {
const link = document.createElement('a');
link.download = filename + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
// Generate initial logo
window.onload = function() {
generateLogo();
};
// Auto-update on input change
document.querySelectorAll('input, select').forEach(el => {
el.addEventListener('input', generateLogo);
});
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

+238
View File
@@ -0,0 +1,238 @@
#!/bin/bash
# RDP Launcher - Startet RDP-Verbindungen aus Profilen
# Speichere als: files/rdp-launcher.sh
CONFIG_FILE="$HOME/.config/rdp-profiles/profiles.ini"
PROFILE_NAME="$1"
# Funktion zum sicheren Starten des Profile Managers (ohne Doppelstart)
start_profile_manager() {
# Prüfe ob Manager bereits läuft
if pgrep -f "rdp-profile-manager.py" > /dev/null; then
echo "Profile Manager is already running"
return
fi
/usr/local/bin/rdp-profile-manager.py &
}
# Finde FreeRDP binary (Debian 12 = xfreerdp, Debian 13 = xfreerdp3)
# Prüfe ZUERST auf xfreerdp3, da es auch einen xfreerdp-Symlink geben könnte
if command -v xfreerdp3 &> /dev/null; then
FREERDP_CMD="xfreerdp3"
FREERDP_VERSION=3
elif command -v xfreerdp &> /dev/null; then
FREERDP_CMD="xfreerdp"
FREERDP_VERSION=2
else
zenity --error --text="FreeRDP nicht gefunden! Bitte installieren." 2>/dev/null
echo "ERROR: FreeRDP not found"
exit 1
fi
echo "Using $FREERDP_CMD (Version $FREERDP_VERSION)"
# Funktion zum Auslesen von INI-Werten
get_ini_value() {
local section="$1"
local key="$2"
local default="$3"
value=$(awk -F ' *= *' -v section="$section" -v key="$key" '
/^\[.*\]$/ {
gsub(/^\[|\]$/, "", $0)
current_section = $0
next
}
current_section == section && $1 == key {
# Remove leading/trailing whitespace from value
gsub(/^[ \t]+|[ \t]+$/, "", $2)
print $2
exit
}
' "$CONFIG_FILE")
echo "${value:-$default}"
}
# Wenn kein Profil angegeben, starte Profile Manager
if [ -z "$PROFILE_NAME" ]; then
# Check if profiles exist
if [ ! -s "$CONFIG_FILE" ] || ! grep -q '^\[' "$CONFIG_FILE"; then
# No profiles, start manager
start_profile_manager
exit 0
fi
# Suche nach Autostart-Profil
PROFILE_NAME=$(awk -F'[][]' '
/^\[/ { section=$2 }
/^autostart[[:space:]]*=[[:space:]]*[Tt]rue/ && section { print section; exit }
' "$CONFIG_FILE")
# Wenn kein Autostart-Profil gefunden, starte Manager
if [ -z "$PROFILE_NAME" ]; then
echo "No autostart profile found, starting Profile Manager..."
start_profile_manager
exit 0
fi
echo "Starting autostart profile: $PROFILE_NAME"
fi
# Funktion zum sicheren Starten des Profile Managers (ohne Doppelstart)
start_profile_manager() {
# Prüfe ob Manager bereits läuft
if pgrep -f "rdp-profile-manager.py" > /dev/null; then
echo "Profile Manager is already running"
return
fi
/usr/local/bin/rdp-profile-manager.py &
}
# Lese Profil-Konfiguration
SERVER=$(get_ini_value "$PROFILE_NAME" "server")
USERNAME=$(get_ini_value "$PROFILE_NAME" "username")
DOMAIN=$(get_ini_value "$PROFILE_NAME" "domain")
RESOLUTION=$(get_ini_value "$PROFILE_NAME" "resolution" "client")
MULTIMON=$(get_ini_value "$PROFILE_NAME" "multimon" "False")
REDIRECT_AUDIO=$(get_ini_value "$PROFILE_NAME" "redirect_audio" "True")
REDIRECT_MIC=$(get_ini_value "$PROFILE_NAME" "redirect_microphone" "True")
REDIRECT_USB=$(get_ini_value "$PROFILE_NAME" "redirect_usb" "True")
REDIRECT_SMARTCARD=$(get_ini_value "$PROFILE_NAME" "redirect_smartcard" "True")
REDIRECT_PRINTERS=$(get_ini_value "$PROFILE_NAME" "redirect_printers" "False")
if [ -z "$SERVER" ]; then
zenity --error --text="Profil '$PROFILE_NAME' nicht gefunden!" 2>/dev/null || \
echo "ERROR: Profile '$PROFILE_NAME' not found!"
exit 1
fi
# Wenn kein Username hinterlegt, frage danach
if [ -z "$USERNAME" ]; then
USERNAME=$(zenity --entry --title="Benutzername" --text="Benutzername für $SERVER:" 2>/dev/null)
if [ -z "$USERNAME" ]; then
echo "Aborted: No username provided"
start_profile_manager
exit 0
fi
fi
# Baue FreeRDP-Befehl
CMD="$FREERDP_CMD"
# Server und Benutzer
CMD="$CMD /v:$SERVER"
CMD="$CMD /u:$USERNAME"
if [ -n "$DOMAIN" ]; then
CMD="$CMD /d:$DOMAIN"
fi
# Auflösung
if [ "$RESOLUTION" = "client" ]; then
CMD="$CMD /dynamic-resolution"
# smart-sizing nur bei FreeRDP2
if [ "$FREERDP_VERSION" -eq 2 ]; then
CMD="$CMD /smart-sizing"
fi
else
CMD="$CMD /size:$RESOLUTION"
fi
# Multi-Monitor
if [ "$MULTIMON" = "True" ] || [ "$MULTIMON" = "true" ]; then
CMD="$CMD /multimon"
echo "Multi-Monitor mode enabled"
fi
# Audio
if [ "$REDIRECT_AUDIO" = "True" ]; then
CMD="$CMD /audio-mode:0"
CMD="$CMD /sound:sys:pulse"
else
CMD="$CMD /audio-mode:2"
fi
# Mikrofon
if [ "$REDIRECT_MIC" = "True" ]; then
CMD="$CMD /microphone:sys:pulse"
fi
# USB-Geräte
if [ "$REDIRECT_USB" = "True" ]; then
if [ "$FREERDP_VERSION" -eq 3 ]; then
# FreeRDP3 - USB-Geräte (Dongles, Smart Cards)
CMD="$CMD /usb:auto"
# USB-Sticks als Laufwerk durchreichen - FESTES Verzeichnis für Hotswap!
echo "Redirecting USB media directory for hotswap support..."
MEDIA_DIR="/media/rdpuser"
# Erstelle Verzeichnis falls nicht vorhanden
mkdir -p "$MEDIA_DIR"
# Reiche das komplette media-Verzeichnis durch
CMD="$CMD /drive:hotplug,*"
echo "USB Hotswap enabled: $MEDIA_DIR"
else
# FreeRDP2
CMD="$CMD /usb:id,dev:*"
MEDIA_DIR="/media/rdpuser"
mkdir -p "$MEDIA_DIR"
CMD="$CMD /drive:USB-Media,$MEDIA_DIR"
fi
fi
# Smart Cards
if [ "$REDIRECT_SMARTCARD" = "True" ]; then
CMD="$CMD /smartcard"
fi
# Drucker
if [ "$REDIRECT_PRINTERS" = "True" ]; then
CMD="$CMD /printer"
fi
# Zusätzliche Optionen
CMD="$CMD /cert:ignore"
CMD="$CMD /compression"
CMD="$CMD /clipboard"
CMD="$CMD /gfx:AVC444"
CMD="$CMD /network:auto"
# Zusätzliche Parameter nur für FreeRDP2
if [ "$FREERDP_VERSION" -eq 2 ]; then
CMD="$CMD +fonts"
CMD="$CMD +aero"
CMD="$CMD +glyph-cache"
fi
# Vollbild
CMD="$CMD /f"
# Log
echo "Connecting to: $SERVER as $USERNAME"
echo "Command: $CMD"
# Frage nach Passwort
PASSWORD=$(zenity --password --title="RDP Verbindung zu $SERVER" 2>/dev/null)
if [ -z "$PASSWORD" ]; then
echo "Aborted by user"
start_profile_manager
exit 0
fi
# Starte RDP-Verbindung
# FreeRDP3 hat Probleme mit /from-stdin, nutze /p: stattdessen
if [ "$FREERDP_VERSION" -eq 3 ]; then
$CMD /p:"$PASSWORD"
else
# FreeRDP2 mit stdin
echo "$PASSWORD" | $CMD /from-stdin
fi
# Nach Beenden der Verbindung - zeige Profile Manager wieder
/usr/local/bin/rdp-profile-manager.py &
+446
View File
@@ -0,0 +1,446 @@
#!/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()
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""
Session Watcher - Überwacht Tastenkombination zum Verlassen der RDP-Sitzung
Speichere als: files/session-watcher.py
"""
import subprocess
import sys
import time
import configparser
from pathlib import Path
from Xlib import X, XK, display
from Xlib.ext import record
from Xlib.protocol import rq
CONFIG_FILE = Path.home() / ".config" / "rdp-profiles" / "profiles.ini"
class SessionWatcher:
def __init__(self):
self.display = display.Display()
self.root = self.display.screen().root
self.ctx = None
self.pressed_keys = set()
# Default hotkey
self.exit_hotkey = "Control+Alt+q"
# Parse hotkey from active profile (if any)
self.load_hotkey()
def load_hotkey(self):
"""Lädt Hotkey aus dem ersten aktiven Profil"""
if not CONFIG_FILE.exists():
return
config = configparser.ConfigParser()
config.read(CONFIG_FILE)
# Nutze ersten Profil-Hotkey als Standard
for section in config.sections():
hotkey = config[section].get('exit_hotkey', '').strip()
if hotkey:
self.exit_hotkey = hotkey
break
print(f"Exit hotkey: {self.exit_hotkey}")
def parse_hotkey(self):
"""Wandelt Hotkey-String in Keycodes um"""
parts = self.exit_hotkey.lower().split('+')
modifiers = []
key = None
for part in parts:
part = part.strip()
if part in ['control', 'ctrl']:
modifiers.append('Control_L')
modifiers.append('Control_R')
elif part in ['alt']:
modifiers.append('Alt_L')
modifiers.append('Alt_R')
elif part in ['shift']:
modifiers.append('Shift_L')
modifiers.append('Shift_R')
elif part in ['super', 'win', 'meta']:
modifiers.append('Super_L')
modifiers.append('Super_R')
else:
key = part
return modifiers, key
def check_rdp_running(self):
"""Prüft ob FreeRDP läuft"""
try:
result = subprocess.run(['pgrep', '-x', 'xfreerdp'],
capture_output=True, text=True)
return result.returncode == 0
except:
return False
def kill_rdp(self):
"""Beendet alle RDP-Verbindungen - AGGRESSIV!"""
print("Killing RDP sessions...")
try:
# Erst SIGTERM versuchen (sauber)
result = subprocess.run(['pkill', '-15', 'xfreerdp'], check=False)
# Warte kurz
time.sleep(0.5)
# Prüfe ob noch läuft
check = subprocess.run(['pgrep', '-x', 'xfreerdp'],
capture_output=True, check=False)
if check.returncode == 0:
# Immer noch da? KILL IT WITH FIRE! (SIGKILL)
print("RDP process still running, using SIGKILL...")
subprocess.run(['pkill', '-9', 'xfreerdp'], check=False)
subprocess.run(['pkill', '-9', 'xfreerdp3'], check=False)
time.sleep(0.3)
# Auch xfreerdp3 killen (falls vorhanden)
subprocess.run(['pkill', '-9', 'xfreerdp3'], check=False)
time.sleep(0.5)
# Starte Profile Manager
subprocess.Popen(['/usr/local/bin/rdp-profile-manager.py'])
except Exception as e:
print(f"Error killing RDP: {e}")
def event_handler(self, reply):
"""Behandelt Tastatur-Events"""
if reply.category != record.FromServer:
return
if reply.client_swapped:
return
if not len(reply.data) or reply.data[0] < 2:
return
data = reply.data
while len(data):
event, data = rq.EventField(None).parse_binary_value(
data, self.display.display, None, None)
if event.type == X.KeyPress:
keysym = self.display.keycode_to_keysym(event.detail, 0)
key_name = XK.keysym_to_string(keysym)
if key_name:
self.pressed_keys.add(key_name)
self.check_hotkey()
elif event.type == X.KeyRelease:
keysym = self.display.keycode_to_keysym(event.detail, 0)
key_name = XK.keysym_to_string(keysym)
if key_name and key_name in self.pressed_keys:
self.pressed_keys.discard(key_name)
def check_hotkey(self):
"""Prüft ob Exit-Hotkey gedrückt wurde"""
modifiers, key = self.parse_hotkey()
# Check if any modifier variant is pressed
modifier_pressed = False
for mod in modifiers:
if mod in self.pressed_keys:
modifier_pressed = True
break
if not modifier_pressed:
return
# Check key
if key and key.lower() in [k.lower() for k in self.pressed_keys]:
# Hotkey matched!
if self.check_rdp_running():
print("Exit hotkey detected - killing RDP session")
self.pressed_keys.clear() # Prevent multiple triggers
self.kill_rdp()
def run(self):
"""Startet Event-Loop"""
# Setup record extension
self.ctx = self.display.record_create_context(
0,
[record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': (X.KeyPress, X.KeyRelease),
'errors': (0, 0),
'client_started': False,
'client_died': False,
}]
)
self.display.record_enable_context(self.ctx, self.event_handler)
self.display.record_free_context(self.ctx)
print("Session watcher started - monitoring for exit hotkey")
while True:
event = self.display.next_event()
if __name__ == '__main__':
try:
watcher = SessionWatcher()
watcher.run()
except KeyboardInterrupt:
print("\nSession watcher stopped")
sys.exit(0)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)