first commit
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -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 |
@@ -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 &
|
||||
Executable
+446
@@ -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()
|
||||
Executable
+201
@@ -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)
|
||||
Reference in New Issue
Block a user