first commit
This commit is contained in:
commit
8620ff31ac
|
|
@ -0,0 +1,349 @@
|
|||
# RDP Thin Client System - Complete Setup
|
||||
|
||||
Vollständiges Ansible-Deployment für dedizierte RDP-Thin-Clients auf Debian 13 (Trixie).
|
||||
|
||||
## Features
|
||||
|
||||
- Headless Auto-Login
|
||||
- Profilbasierte RDP-Verbindungen
|
||||
- Audio/Mikrofon-Weiterleitung (Bluetooth-Headsets!)
|
||||
- USB/Smart-Card Redirection
|
||||
- Python-GUI für Profilverwaltung
|
||||
- Tastenkombination zum Session-Verlassen (Strg+Alt+Q)
|
||||
- Bluetooth-Manager-Integration
|
||||
|
||||
---
|
||||
|
||||
## 1. Verzeichnisstruktur
|
||||
|
||||
rdp-thin-client/
|
||||
├── ansible/
|
||||
│ ├── playbook.yml # Hauptinstallation
|
||||
│ ├── inventory.ini # Deine Clients
|
||||
│ └── group_vars/all.yml # Globale Variablen
|
||||
├── files/
|
||||
│ ├── rdp-profile-manager.py # GUI für Profile
|
||||
│ ├── rdp-launcher.sh # FreeRDP-Wrapper
|
||||
│ ├── session-watcher.py # Tastenkombination überwachen
|
||||
│ └── branding/ # FreeRDP-Wrapper
|
||||
|
||||
│ ├── boot-logo.png
|
||||
|
||||
│ ├── boot-logocreator.html #boot logo creator tool in webbrowser
|
||||
|
||||
│ ├── grub-background.png
|
||||
|
||||
│ └── login-background.png
|
||||
|
||||
└── config/
|
||||
└── profiles.ini.example
|
||||
|
||||
## 2. Ansible Inventory
|
||||
|
||||
ansible/inventory.ini:
|
||||
|
||||
```ini
|
||||
[rdp_clients]
|
||||
thin-client-01 ansible_host=192.168.1.101
|
||||
thin-client-02 ansible_host=192.168.1.102
|
||||
thin-client-03 ansible_host=192.168.1.103
|
||||
|
||||
[rdp_clients:vars]
|
||||
ansible_user=root
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Ansible Variables
|
||||
|
||||
**ansible/group_vars/all.yml:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
# System User
|
||||
thin_client_user: rdpuser
|
||||
thin_client_password: "{{ 'rdpuser' | password_hash('sha512') }}"
|
||||
|
||||
# Display Manager
|
||||
display_manager: lightdm
|
||||
|
||||
# RDP Client
|
||||
rdp_client: freerdp
|
||||
|
||||
# Audio System
|
||||
audio_system: pipewire
|
||||
|
||||
# Profile Directory
|
||||
profile_dir: /home/{{ thin_client_user }}/.config/rdp-profiles
|
||||
profile_file: "{{ profile_dir }}/profiles.ini"
|
||||
|
||||
# Exit Hotkey
|
||||
exit_hotkey: "Control+Alt+q"
|
||||
|
||||
# Packages
|
||||
base_packages:
|
||||
- xorg
|
||||
- openbox
|
||||
- lightdm
|
||||
- python3
|
||||
- python3-tk
|
||||
- python3-configparser
|
||||
- freerdp2-x11
|
||||
- pulseaudio
|
||||
- pipewire
|
||||
- pipewire-pulse
|
||||
- pipewire-audio
|
||||
- bluez
|
||||
- blueman
|
||||
- pcscd
|
||||
- libpcsclite1
|
||||
- libccid
|
||||
- xinput
|
||||
- xdotool
|
||||
- pcmanfm
|
||||
- lxterminal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Playbook
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Setup RDP Thin Client
|
||||
hosts: rdp_clients
|
||||
become: yes
|
||||
|
||||
tasks:
|
||||
# === BASE SYSTEM ===
|
||||
- name: Update APT cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Install base packages
|
||||
apt:
|
||||
name: "{{ base_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Create thin client user
|
||||
user:
|
||||
name: "{{ thin_client_user }}"
|
||||
password: "{{ thin_client_password }}"
|
||||
shell: /bin/bash
|
||||
groups: audio,video,bluetooth,plugdev
|
||||
append: yes
|
||||
|
||||
- name: Configure auto-login for LightDM
|
||||
copy:
|
||||
dest: /etc/lightdm/lightdm.conf.d/50-autologin.conf
|
||||
content: |
|
||||
[Seat:*]
|
||||
autologin-user={{ thin_client_user }}
|
||||
autologin-user-timeout=0
|
||||
user-session=openbox
|
||||
|
||||
# === OPENBOX CONFIGURATION ===
|
||||
- name: Create openbox config directory
|
||||
file:
|
||||
path: /home/{{ thin_client_user }}/.config/openbox
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure Openbox autostart
|
||||
copy:
|
||||
dest: /home/{{ thin_client_user }}/.config/openbox/autostart
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# Start PipeWire
|
||||
pipewire &
|
||||
pipewire-pulse &
|
||||
|
||||
# Start Bluetooth
|
||||
blueman-applet &
|
||||
|
||||
# Start Session Watcher (monitors exit hotkey)
|
||||
/usr/local/bin/session-watcher.py &
|
||||
|
||||
# Start RDP Launcher
|
||||
/usr/local/bin/rdp-launcher.sh &
|
||||
|
||||
- name: Configure Openbox menu (right-click context menu)
|
||||
copy:
|
||||
dest: /home/{{ thin_client_user }}/.config/openbox/menu.xml
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
content: |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/usr/local/bin/rdp-profile-manager.py
|
||||
|
||||
|
||||
|
||||
|
||||
blueman-manager
|
||||
|
||||
|
||||
|
||||
|
||||
pcmanfm
|
||||
|
||||
|
||||
|
||||
|
||||
lxterminal
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
systemctl reboot
|
||||
|
||||
|
||||
|
||||
|
||||
systemctl poweroff
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# === AUDIO CONFIGURATION ===
|
||||
- name: Enable PipeWire services for user
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: yes
|
||||
scope: user
|
||||
daemon_reload: yes
|
||||
loop:
|
||||
- pipewire.service
|
||||
- pipewire-pulse.service
|
||||
become_user: "{{ thin_client_user }}"
|
||||
|
||||
# === BLUETOOTH CONFIGURATION ===
|
||||
- name: Enable Bluetooth service
|
||||
systemd:
|
||||
name: bluetooth
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
# === SMART CARD CONFIGURATION ===
|
||||
- name: Enable pcscd service
|
||||
systemd:
|
||||
name: pcscd
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
# === PROFILE DIRECTORY ===
|
||||
- name: Create RDP profile directory
|
||||
file:
|
||||
path: "{{ profile_dir }}"
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Create empty profiles.ini if not exists
|
||||
copy:
|
||||
dest: "{{ profile_file }}"
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0644'
|
||||
content: |
|
||||
# RDP Profile Configuration
|
||||
# Auto-generated by RDP Thin Client Setup
|
||||
force: no
|
||||
|
||||
# === COPY SCRIPTS ===
|
||||
- name: Copy RDP Profile Manager
|
||||
copy:
|
||||
src: ../src/rdp-profile-manager.py
|
||||
dest: /usr/local/bin/rdp-profile-manager.py
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy RDP Launcher
|
||||
copy:
|
||||
src: ../src/rdp-launcher.sh
|
||||
dest: /usr/local/bin/rdp-launcher.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy Session Watcher
|
||||
copy:
|
||||
src: ../src/session-watcher.py
|
||||
dest: /usr/local/bin/session-watcher.py
|
||||
mode: '0755'
|
||||
|
||||
# === CLEANUP ===
|
||||
- name: Remove unnecessary packages
|
||||
apt:
|
||||
name:
|
||||
- gnome-*
|
||||
- libreoffice-*
|
||||
state: absent
|
||||
autoremove: yes
|
||||
|
||||
- name: Disable unnecessary services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: no
|
||||
state: stopped
|
||||
loop:
|
||||
- ModemManager
|
||||
- cups
|
||||
ignore_errors: yes
|
||||
|
||||
handlers:
|
||||
- name: Reboot system
|
||||
reboot:
|
||||
msg: "Rebooting to apply thin client configuration"
|
||||
reboot_timeout: 300
|
||||
|
||||
|
||||
```
|
||||
|
||||
### 5. Ansible installieren und RDP-Client Debian Hosts vorbereiten
|
||||
|
||||
bash
|
||||
|
||||
```bash
|
||||
# Ansible installieren
|
||||
sudo apt update
|
||||
sudo apt install ansible sshpass
|
||||
|
||||
# SSH-Keys generieren (falls noch nicht vorhanden)
|
||||
ssh-keygen -t rsa -b 4096
|
||||
|
||||
# SSH-Keys auf Thin Clients kopieren
|
||||
ssh-copy-id root@192.168.1.101
|
||||
ssh-copy-id root@192.168.1.102
|
||||
ssh-copy-id root@192.168.1.1
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
cd rdp-thin-client/ansible
|
||||
|
||||
# Syntax-Check
|
||||
ansible-playbook -i inventory.ini playbook.yml --syntax-check
|
||||
|
||||
# Dry-Run (Check Mode)
|
||||
ansible-playbook -i inventory.ini playbook.yml --check
|
||||
|
||||
# Deployment ausführen
|
||||
ansible-playbook -i inventory.ini playbook.yml
|
||||
|
||||
# Nur bestimmte Hosts
|
||||
ansible-playbook -i inventory.ini playbook.yml --limit thin-client-01
|
||||
|
||||
# Mit Verbose-Output
|
||||
ansible-playbook -i inventory.ini playbook.yml -vvv
|
||||
```
|
||||
Binary file not shown.
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
# System User
|
||||
thin_client_user: rdpuser
|
||||
thin_client_password: "{{ 'rdpuser' | password_hash('sha512') }}"
|
||||
|
||||
# Display Manager
|
||||
display_manager: lightdm
|
||||
|
||||
# RDP Client
|
||||
rdp_client: freerdp
|
||||
|
||||
# Audio System
|
||||
audio_system: pipewire
|
||||
|
||||
# Profile Directory
|
||||
profile_dir: /home/{{ thin_client_user }}/.config/rdp-profiles
|
||||
profile_file: "{{ profile_dir }}/profiles.ini"
|
||||
|
||||
# Exit Hotkey
|
||||
exit_hotkey: "Control+Alt+q"
|
||||
|
||||
# Packages
|
||||
base_packages:
|
||||
- xorg
|
||||
- openbox
|
||||
- lightdm
|
||||
- python3
|
||||
- python3-tk
|
||||
- python3-pip
|
||||
- pipewire
|
||||
- pipewire-pulse
|
||||
- pipewire-audio
|
||||
- pipewire-alsa
|
||||
- wireplumber
|
||||
- bluez
|
||||
- blueman
|
||||
- pcscd
|
||||
- libpcsclite1
|
||||
- libccid
|
||||
- xinput
|
||||
- xdotool
|
||||
- pcmanfm
|
||||
- lxterminal
|
||||
- zenity
|
||||
- pavucontrol
|
||||
- helvum
|
||||
- udiskie
|
||||
- arandr
|
||||
- autorandr
|
||||
- numlockx
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[rdp_clients]
|
||||
thin-client-01 ansible_host=192.168.0.13
|
||||
thin-client-02 ansible_host=192.168.0.29
|
||||
thin-client-03 ansible_host=192.168.0.23
|
||||
|
||||
[rdp_clients:vars]
|
||||
ansible_user=root
|
||||
#ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[rdp_clients]
|
||||
thin-client-01 ansible_host=192.168.1.101
|
||||
thin-client-02 ansible_host=192.168.1.102
|
||||
thin-client-03 ansible_host=192.168.1.103
|
||||
|
||||
[rdp_clients:vars]
|
||||
ansible_user=root
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||
|
|
@ -0,0 +1,649 @@
|
|||
---
|
||||
# ansible/playbook.yml
|
||||
# RDP Thin Client Setup - Main Playbook v14
|
||||
|
||||
- name: Setup RDP Thin Client
|
||||
hosts: rdp_clients
|
||||
become: yes
|
||||
|
||||
tasks:
|
||||
# Non-free Repos aktivieren
|
||||
- name: Enable non-free and contrib repositories
|
||||
lineinfile:
|
||||
path: /etc/apt/sources.list
|
||||
regexp: '^deb http://deb.debian.org/debian/ {{ ansible_distribution_release }} main$'
|
||||
line: 'deb http://deb.debian.org/debian/ {{ ansible_distribution_release }} main contrib non-free non-free-firmware'
|
||||
backrefs: yes
|
||||
when: ansible_distribution == 'Debian'
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Enable non-free and contrib for security updates
|
||||
lineinfile:
|
||||
path: /etc/apt/sources.list
|
||||
regexp: '^deb http://security.debian.org/debian-security {{ ansible_distribution_release }}-security main$'
|
||||
line: 'deb http://security.debian.org/debian-security {{ ansible_distribution_release }}-security main contrib non-free non-free-firmware'
|
||||
backrefs: yes
|
||||
when: ansible_distribution == 'Debian'
|
||||
ignore_errors: yes
|
||||
# === BASE SYSTEM ===
|
||||
- name: Update APT cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Ensure sudo is installed
|
||||
apt:
|
||||
name: sudo
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Remove PulseAudio if present (conflicts with PipeWire)
|
||||
apt:
|
||||
name:
|
||||
- pulseaudio
|
||||
- pulseaudio-utils
|
||||
state: absent
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Install base packages
|
||||
apt:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ base_packages }}"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Ensure util-linux is installed (provides lsblk)
|
||||
apt:
|
||||
name: util-linux
|
||||
state: present
|
||||
|
||||
- name: Install udiskie for USB automount
|
||||
apt:
|
||||
name: udiskie
|
||||
state: present
|
||||
|
||||
|
||||
|
||||
- name: Update APT cache again for emoji fonts
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install Emoji fonts for GUI
|
||||
apt:
|
||||
name: fonts-noto-color-emoji
|
||||
state: present
|
||||
|
||||
- name: Install FreeRDP (try multiple package names for Debian 12/13 compatibility)
|
||||
apt:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- freerdp3-x11
|
||||
- freerdp2-x11
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Find FreeRDP binary location
|
||||
shell: which xfreerdp 2>/dev/null || which xfreerdp3 2>/dev/null || echo "not_found"
|
||||
register: freerdp_location
|
||||
changed_when: false
|
||||
|
||||
- name: Create xfreerdp symlink if only xfreerdp3 exists
|
||||
file:
|
||||
src: /usr/bin/xfreerdp3
|
||||
dest: /usr/bin/xfreerdp
|
||||
state: link
|
||||
when: "'xfreerdp3' in freerdp_location.stdout and 'not_found' not in freerdp_location.stdout"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display FreeRDP installation status
|
||||
debug:
|
||||
msg: "FreeRDP found at: {{ freerdp_location.stdout }}"
|
||||
|
||||
- name: Create thin client user
|
||||
user:
|
||||
name: "{{ thin_client_user }}"
|
||||
password: "{{ thin_client_password }}"
|
||||
shell: /bin/bash
|
||||
groups: audio,video,bluetooth,plugdev,netdev,sudo
|
||||
append: yes
|
||||
|
||||
- name: Allow rdpuser to use sudo for package installation
|
||||
lineinfile:
|
||||
path: /etc/sudoers.d/rdpuser
|
||||
line: '{{ thin_client_user }} ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /sbin/reboot, /sbin/shutdown'
|
||||
create: yes
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
|
||||
- name: Create media directory for USB mounts
|
||||
file:
|
||||
path: /media/rdpuser
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Create udiskie config directory
|
||||
file:
|
||||
path: /home/{{ thin_client_user }}/.config/udiskie
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure udiskie to mount in /media/rdpuser
|
||||
copy:
|
||||
dest: /home/{{ thin_client_user }}/.config/udiskie/config.yml
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0644'
|
||||
content: |
|
||||
device_config:
|
||||
- id_type: filesystem
|
||||
mount_path: /media/rdpuser/{device_file}
|
||||
|
||||
|
||||
- name: Create LightDM config directory
|
||||
file:
|
||||
path: /etc/lightdm/lightdm.conf.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure auto-login for LightDM
|
||||
copy:
|
||||
dest: /etc/lightdm/lightdm.conf.d/50-autologin.conf
|
||||
content: |
|
||||
[Seat:*]
|
||||
autologin-user={{ thin_client_user }}
|
||||
autologin-user-timeout=0
|
||||
user-session=openbox
|
||||
|
||||
# === OPENBOX CONFIGURATION ===
|
||||
- name: Create openbox config directory
|
||||
file:
|
||||
path: /home/{{ thin_client_user }}/.config/openbox
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure Openbox autostart
|
||||
copy:
|
||||
dest: /home/{{ thin_client_user }}/.config/openbox/autostart
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
content: |
|
||||
#!/bin/bash
|
||||
#Nummernblock aktivieren
|
||||
numlockx on &
|
||||
# Auto-load display configuration
|
||||
# Try to load the last saved profile, fallback to common, then auto-detect
|
||||
if [ -f ~/.config/autorandr/last_profile ]; then
|
||||
LAST_PROFILE=$(cat ~/.config/autorandr/last_profile)
|
||||
autorandr --load "$LAST_PROFILE" 2>/dev/null || autorandr --change
|
||||
else
|
||||
autorandr --load common 2>/dev/null || autorandr --change
|
||||
fi
|
||||
# Deaktiviere Energiesparfunktionen
|
||||
xset s off # Screensaver aus
|
||||
xset -dpms # DPMS (Display Power Management) aus
|
||||
xset s noblank # Kein Blank-Screen
|
||||
|
||||
# Start PipeWire
|
||||
pipewire &
|
||||
pipewire-pulse &
|
||||
|
||||
# Start Bluetooth
|
||||
blueman-applet &
|
||||
|
||||
# Start USB automount (udiskie)
|
||||
udiskie --tray --automount --notify &
|
||||
|
||||
# Session Watcher läuft als systemd-Service
|
||||
|
||||
# Start RDP Launcher
|
||||
/usr/local/bin/rdp-launcher.sh &
|
||||
|
||||
- name: Configure Openbox menu (right-click context menu)
|
||||
copy:
|
||||
dest: /home/{{ thin_client_user }}/.config/openbox/menu.xml
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
content: |
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openbox_menu xmlns="http://openbox.org/3.4/menu">
|
||||
<menu id="root-menu" label="Openbox">
|
||||
<item label="RDP Profile Manager">
|
||||
<action name="Execute">
|
||||
<command>/usr/local/bin/rdp-profile-manager.py</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Bluetooth Manager">
|
||||
<action name="Execute">
|
||||
<command>blueman-manager</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Datei Manager">
|
||||
<action name="Execute">
|
||||
<command>pcmanfm</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Sound Einstellungen">
|
||||
<action name="Execute">
|
||||
<command>pavucontrol</command>
|
||||
</action>
|
||||
</item>
|
||||
<menu id="display-menu" label="Anzeigeeinstellungen">
|
||||
<item label="Anzeige konfigurieren">
|
||||
<action name="Execute">
|
||||
<command>arandr</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Aktuelles Profil speichern">
|
||||
<action name="Execute">
|
||||
<command>sh -c 'profile=$(zenity --entry --title="Display Profil speichern" --text="Profilnamen eingeben:"); [ -n "$profile" ] && autorandr --save "$profile" --force && echo "$profile" > ~/.config/autorandr/last_profile && zenitiy --info --text="Profil gespeichert: $profile"'</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Profil laden">
|
||||
<action name="Execute">
|
||||
<command>sh -c 'profile=$(zenity --list --title="Display Profil laden" --column="Profile" $(autorandr --list));[ -n "$profile" ] && autorandr --load "$profile" && echo "$profile" > ~/.config/autorandr/last_profile && zenity --info --text="Dieses Profil wird automatisch beim nächsten Start geladen"'</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Delete Profile">
|
||||
<action name="Execute">
|
||||
<command>sh -c 'profile=$(zenity --list --title="Display Profil löschen" --text="Profile auswählen zum löschen:" --column="Profiles" $(autorandr --list));[ -n "$profile" ] && zenity --question --title="Löschen bestätigen" --text="Delete profile: $profile?" && autorandr --remove "$profile" && zenity --info --text="Profil gelöscht: $profile"'</command></action></item>
|
||||
<menu id="gpu-menu" label="Grafiktreiber"><item label="Install Intel Graphics Driver">
|
||||
<action name="Execute">
|
||||
<command>lxterminal -e "bash -c 'echo Installing Intel graphics driver...; sudo apt update && sudo apt install -y xserver-xorg-video-intel firmware-misc-nonfree && echo Done! Please reboot for changes to take effect.; read -p Press Enter to close...'"</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Install AMD/Radeon Driver">
|
||||
<action name="Execute">
|
||||
<command>lxterminal -e "bash -c 'echo Installing AMD/Radeon graphics driver...; sudo apt update && sudo apt install -y firmware-amd-graphics xserver-xorg-video-amdgpu xserver-xorg-video-radeon && echo Done! Please reboot for changes to take effect.; read -p Press Enter to close...'"</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Install NVIDIA Driver">
|
||||
<action name="Execute">
|
||||
<command>lxterminal -e "bash -c 'echo Installing NVIDIA proprietary driver...; sudo apt update && sudo apt install -y nvidia-driver firmware-misc-nonfree && echo Done! Please reboot for changes to take effect.; read -p Press Enter to close...'"</command>
|
||||
</action>
|
||||
</item>
|
||||
<separator />
|
||||
<item label="Show Current GPU Info">
|
||||
<action name="Execute">
|
||||
<command>lxterminal -e "bash -c 'echo === Graphics Card Info ===; lspci | grep -i vga; echo; echo === Loaded Driver ===; lsmod | grep -iE video|radeon|amdgpu|nouveau|nvidia|i915; echo; echo === Xorg Driver ===; grep -i driver /var/log/Xorg.0.log 2>/dev/null | tail -n 20; read -p Press Enter to close...'"</command>
|
||||
</action>
|
||||
</item>
|
||||
</menu>
|
||||
</menu><item label="Terminal">
|
||||
<action name="Execute">
|
||||
<command>lxterminal</command>
|
||||
</action>
|
||||
</item>
|
||||
<separator />
|
||||
<item label="Neustart">
|
||||
<action name="Execute">
|
||||
<command>systemctl reboot</command>
|
||||
</action>
|
||||
</item>
|
||||
<item label="Ausschalten">
|
||||
<action name="Execute">
|
||||
<command>systemctl poweroff</command>
|
||||
</action>
|
||||
</item>
|
||||
</menu>
|
||||
</openbox_menu>
|
||||
|
||||
|
||||
# === AUDIO CONFIGURATION ===
|
||||
# PipeWire wird über Openbox autostart gestartet (siehe autostart config)
|
||||
# Keine systemd user services nötig
|
||||
|
||||
# === BLUETOOTH CONFIGURATION ===
|
||||
- name: Check if Bluetooth hardware is present
|
||||
stat:
|
||||
path: /sys/class/bluetooth
|
||||
register: bluetooth_hw
|
||||
|
||||
- name: Enable Bluetooth service (if hardware present)
|
||||
systemd:
|
||||
name: bluetooth
|
||||
enabled: yes
|
||||
state: started
|
||||
when: bluetooth_hw.stat.exists
|
||||
ignore_errors: yes
|
||||
|
||||
# === SMART CARD CONFIGURATION ===
|
||||
- name: Check if pcscd is installed
|
||||
command: which pcscd
|
||||
register: pcscd_installed
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
|
||||
- name: Enable pcscd service (socket-activated on demand)
|
||||
systemd:
|
||||
name: pcscd
|
||||
enabled: yes
|
||||
when: pcscd_installed.rc == 0
|
||||
ignore_errors: yes
|
||||
|
||||
# === PROFILE DIRECTORY ===
|
||||
- name: Create RDP profile directory
|
||||
file:
|
||||
path: "{{ profile_dir }}"
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Create empty profiles.ini if not exists
|
||||
copy:
|
||||
dest: "{{ profile_file }}"
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0644'
|
||||
content: |
|
||||
# RDP Profile Configuration
|
||||
# Auto-generated by RDP Thin Client Setup
|
||||
force: no
|
||||
|
||||
# === INSTALL PYTHON DEPENDENCIES ===
|
||||
- name: Install python-xlib via pip
|
||||
shell: pip3 install python-xlib --break-system-packages
|
||||
args:
|
||||
creates: /usr/local/lib/python3.11/dist-packages/Xlib
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Alternative - Install python-xlib from apt
|
||||
apt:
|
||||
name: python3-xlib
|
||||
state: present
|
||||
ignore_errors: yes
|
||||
|
||||
# === COPY SCRIPTS ===
|
||||
- name: Copy RDP Profile Manager
|
||||
copy:
|
||||
src: ../files/rdp-profile-manager.py
|
||||
dest: /usr/local/bin/rdp-profile-manager.py
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy RDP Launcher
|
||||
copy:
|
||||
src: ../files/rdp-launcher.sh
|
||||
dest: /usr/local/bin/rdp-launcher.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy Session Watcher
|
||||
copy:
|
||||
src: ../files/session-watcher.py
|
||||
dest: /usr/local/bin/session-watcher.py
|
||||
mode: '0755'
|
||||
|
||||
# === SESSION WATCHER SYSTEMD SERVICE ===
|
||||
- name: Create systemd user service directory
|
||||
file:
|
||||
path: /home/{{ thin_client_user }}/.config/systemd/user
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Create Session Watcher systemd service
|
||||
copy:
|
||||
dest: /home/{{ thin_client_user }}/.config/systemd/user/session-watcher.service
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=RDP Session Watcher - Exit Hotkey Monitor
|
||||
After=graphical.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/session-watcher.py
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
Environment=DISPLAY=:0
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
||||
- name: Create default.target.wants directory
|
||||
file:
|
||||
path: /home/{{ thin_client_user }}/.config/systemd/user/default.target.wants
|
||||
state: directory
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Create symlink to enable Session Watcher service
|
||||
file:
|
||||
src: /home/{{ thin_client_user }}/.config/systemd/user/session-watcher.service
|
||||
dest: /home/{{ thin_client_user }}/.config/systemd/user/default.target.wants/session-watcher.service
|
||||
owner: "{{ thin_client_user }}"
|
||||
group: "{{ thin_client_user }}"
|
||||
state: link
|
||||
force: yes
|
||||
ignore_errors: yes
|
||||
|
||||
# === BRANDING ===
|
||||
- name: Check if branding files exist
|
||||
stat:
|
||||
path: ../files/branding/boot-logo.png
|
||||
register: branding_check
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Create branding directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- /usr/share/pixmaps/hackersoft
|
||||
- /boot/grub
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Copy boot logo for Plymouth
|
||||
copy:
|
||||
src: ../files/branding/boot-logo.png
|
||||
dest: /usr/share/pixmaps/hackersoft/boot-logo.png
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Copy GRUB background
|
||||
copy:
|
||||
src: ../files/branding/grub-background.png
|
||||
dest: /boot/grub/hackersoft-bg.png
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Configure LightDM to use custom background
|
||||
lineinfile:
|
||||
path: /etc/default/grub
|
||||
regexp: '^#?GRUB_BACKGROUND='
|
||||
line: 'GRUB_BACKGROUND="/boot/grub/hackersoft-bg.png"'
|
||||
when: branding_check.stat.exists
|
||||
notify: Update GRUB
|
||||
|
||||
# === POWER MANAGEMENT DEAKTIVIEREN ===
|
||||
- name: Disable systemd suspend/hibernate
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
masked: yes
|
||||
loop:
|
||||
- sleep.target
|
||||
- suspend.target
|
||||
- hibernate.target
|
||||
- hybrid-sleep.target
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create logind config directory
|
||||
file:
|
||||
path: /etc/systemd/logind.conf.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create systemd logind config to disable power management
|
||||
copy:
|
||||
dest: /etc/systemd/logind.conf.d/no-suspend.conf
|
||||
content: |
|
||||
[Login]
|
||||
HandlePowerKey=ignore
|
||||
HandleSuspendKey=ignore
|
||||
HandleHibernateKey=ignore
|
||||
HandleLidSwitch=ignore
|
||||
HandleLidSwitchExternalPower=ignore
|
||||
IdleAction=ignore
|
||||
mode: '0644'
|
||||
notify: Restart systemd-logind
|
||||
|
||||
- name: Enable NumLock in LightDM
|
||||
lineinfile:
|
||||
path: /etc/lightdm/lightdm.conf
|
||||
regexp: '^#?greeter-setup-script='
|
||||
line: 'greeter-setup-script=/usr/bin/numlockx on'
|
||||
create: yes
|
||||
|
||||
- name: Install Plymouth for boot splash
|
||||
apt:
|
||||
name:
|
||||
- plymouth
|
||||
- plymouth-themes
|
||||
state: present
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Create custom Plymouth theme directory
|
||||
file:
|
||||
path: /usr/share/plymouth/themes/hackersoft
|
||||
state: directory
|
||||
mode: '0755'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Create Plymouth theme configuration
|
||||
copy:
|
||||
dest: /usr/share/plymouth/themes/hackersoft/hackersoft.plymouth
|
||||
content: |
|
||||
[Plymouth Theme]
|
||||
Name=HackerSoft
|
||||
Description=HackerSoft RDP Thin Client Boot Splash
|
||||
ModuleName=script
|
||||
|
||||
[script]
|
||||
ImageDir=/usr/share/plymouth/themes/hackersoft
|
||||
ScriptFile=/usr/share/plymouth/themes/hackersoft/hackersoft.script
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Create Plymouth script
|
||||
copy:
|
||||
dest: /usr/share/plymouth/themes/hackersoft/hackersoft.script
|
||||
content: |
|
||||
logo.image = Image("boot-logo.png");
|
||||
logo.sprite = Sprite(logo.image);
|
||||
logo.opacity = 1.0;
|
||||
|
||||
screen_width = Window.GetWidth();
|
||||
screen_height = Window.GetHeight();
|
||||
logo_width = logo.image.GetWidth();
|
||||
logo_height = logo.image.GetHeight();
|
||||
|
||||
logo.x = screen_width / 2 - logo_width / 2;
|
||||
logo.y = screen_height / 2 - logo_height / 2;
|
||||
logo.sprite.SetPosition(logo.x, logo.y, 0);
|
||||
|
||||
fun refresh_callback() {
|
||||
logo.sprite.SetOpacity(logo.opacity);
|
||||
}
|
||||
|
||||
Plymouth.SetRefreshFunction(refresh_callback);
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Copy logo to Plymouth theme
|
||||
copy:
|
||||
src: ../files/branding/boot-logo.png
|
||||
dest: /usr/share/plymouth/themes/hackersoft/boot-logo.png
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Set Plymouth theme
|
||||
command: plymouth-set-default-theme -R hackersoft
|
||||
when: branding_check.stat.exists
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create backgrounds directory if needed
|
||||
file:
|
||||
path: /usr/share/backgrounds
|
||||
state: directory
|
||||
mode: '0755'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Check if desktop-background.png exists
|
||||
stat:
|
||||
path: ../files/branding/desktop-background.png
|
||||
register: desktop_bg_check
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Copy desktop background (dedicated file)
|
||||
copy:
|
||||
src: ../files/branding/desktop-background.png
|
||||
dest: /usr/share/backgrounds/hackersoft-wallpaper.png
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists and desktop_bg_check.stat.exists
|
||||
|
||||
- name: Copy desktop background (fallback to login-background)
|
||||
copy:
|
||||
src: ../files/branding/login-background.png
|
||||
dest: /usr/share/backgrounds/hackersoft-wallpaper.png
|
||||
mode: '0644'
|
||||
when: branding_check.stat.exists and not desktop_bg_check.stat.exists
|
||||
|
||||
- name: Install feh for wallpaper management
|
||||
apt:
|
||||
name: feh
|
||||
state: present
|
||||
when: branding_check.stat.exists
|
||||
|
||||
- name: Set Openbox wallpaper in autostart
|
||||
lineinfile:
|
||||
path: /home/{{ thin_client_user }}/.config/openbox/autostart
|
||||
line: 'feh --bg-scale /usr/share/backgrounds/hackersoft-wallpaper.png &'
|
||||
insertafter: '^fi'
|
||||
when: branding_check.stat.exists
|
||||
|
||||
# === CLEANUP ===
|
||||
- name: Remove unnecessary packages
|
||||
apt:
|
||||
name:
|
||||
- gnome-*
|
||||
- libreoffice-*
|
||||
state: absent
|
||||
autoremove: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Disable unnecessary services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: no
|
||||
state: stopped
|
||||
loop:
|
||||
- ModemManager
|
||||
- cups
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Final message
|
||||
debug:
|
||||
msg: "RDP Thin Client setup complete! Please reboot the system."
|
||||
|
||||
handlers:
|
||||
- name: Update GRUB
|
||||
command: update-grub
|
||||
|
||||
- name: Restart systemd-logind
|
||||
systemd:
|
||||
name: systemd-logind
|
||||
state: restarted
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Check if X is running
|
||||
if ! pgrep -x "Xorg" > /dev/null; then
|
||||
echo "ERROR: X Server not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if PipeWire is running
|
||||
if ! pgrep -x "pipewire" > /dev/null; then
|
||||
echo "WARNING: PipeWire not running"
|
||||
fi
|
||||
|
||||
# Check if Bluetooth is running
|
||||
if ! systemctl is-active --quiet bluetooth; then
|
||||
echo "WARNING: Bluetooth service not active"
|
||||
fi
|
||||
|
||||
echo "OK: System healthy"
|
||||
exit 0
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[wawi]
|
||||
server = 172.0.2.5
|
||||
username = admin
|
||||
domain =
|
||||
resolution = client
|
||||
redirect_audio = True
|
||||
redirect_microphone = True
|
||||
redirect_usb = True
|
||||
redirect_smartcard = True
|
||||
redirect_printers = False
|
||||
exit_hotkey = Control+Alt+q
|
||||
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 &
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
Loading…
Reference in New Issue