#!/usr/bin/env python3 """ Mauseinstellungen für RDP Thin Client Verwendet xinput direkt für zuverlässige Konfiguration """ import tkinter as tk from tkinter import ttk import subprocess import re import configparser from pathlib import Path class MouseSettings: CONFIG_DIR = Path.home() / ".config" / "mouse-settings" CONFIG_FILE = CONFIG_DIR / "settings.ini" def __init__(self, root): self.root = root self.root.title("Mauseinstellungen") self.root.geometry("500x300") # Erstelle Config-Verzeichnis self.CONFIG_DIR.mkdir(parents=True, exist_ok=True) self.mouse_ids = self.get_mouse_ids() if not self.mouse_ids: tk.Label(root, text="Keine Maus gefunden!", font=('Arial', 14)).pack(pady=50) return self.create_widgets() self.load_saved_settings() def get_mouse_ids(self): """Finde alle Maus-Device-IDs (alle Pointer mit 'mouse' im Namen)""" try: result = subprocess.run(['xinput', 'list'], capture_output=True, text=True) mouse_ids = [] # Suche nach allen Devices mit "mouse" im Namen (nicht Virtual core) for line in result.stdout.split('\n'): if 'pointer' in line.lower() and 'virtual core' not in line.lower() and 'mouse' in line.lower(): match = re.search(r'id=(\d+)', line) if match: mouse_ids.append(match.group(1)) return mouse_ids if mouse_ids else None except: return None def load_saved_settings(self): """Lade gespeicherte Mauseinstellungen""" if self.CONFIG_FILE.exists(): try: config = configparser.ConfigParser() config.read(self.CONFIG_FILE) speed_percent = config.getint('Mouse', 'speed', fallback=50) self.speed_var.set(speed_percent) self.apply_speed() return except: pass # Fallback: Lade aktuelle System-Einstellungen self.speed_var.set(50) def save_settings(self): """Speichere Mauseinstellungen persistent""" try: config = configparser.ConfigParser() config['Mouse'] = { 'speed': str(self.speed_var.get()) } with open(self.CONFIG_FILE, 'w') as f: config.write(f) except Exception as e: print(f"Fehler beim Speichern: {e}") def create_widgets(self): main_frame = ttk.Frame(self.root, padding="20") main_frame.pack(fill=tk.BOTH, expand=True) # Geschwindigkeit ttk.Label(main_frame, text="Mausgeschwindigkeit:", font=('', 12, 'bold')).pack(anchor=tk.W, pady=(0,10)) speed_frame = ttk.Frame(main_frame) speed_frame.pack(fill=tk.X, pady=10) ttk.Label(speed_frame, text="Langsam").pack(side=tk.LEFT) self.speed_var = tk.IntVar(value=50) self.speed_scale = ttk.Scale(speed_frame, from_=0, to=100, variable=self.speed_var, orient=tk.HORIZONTAL, command=self.apply_speed) self.speed_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) ttk.Label(speed_frame, text="Schnell").pack(side=tk.LEFT) # Wert-Anzeige self.speed_label = ttk.Label(main_frame, text="50%", font=('', 10)) self.speed_label.pack() # Info ttk.Separator(main_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=20) info = ttk.Label(main_frame, text="Änderungen werden sofort angewendet und gespeichert.\n" "Die Einstellung wird beim nächsten Start automatisch geladen.", foreground='gray') info.pack() # Buttons button_frame = ttk.Frame(main_frame) button_frame.pack(side=tk.BOTTOM, pady=10) ttk.Button(button_frame, text="Standard (50%)", command=self.reset_default).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="Schließen", command=self.root.destroy).pack(side=tk.LEFT, padx=5) def apply_speed(self, value=None): """Wende Mausgeschwindigkeit an (für alle gefundenen Mäuse)""" speed_percent = self.speed_var.get() # Konvertiere 0-100 zu verschiedenen Formaten libinput_speed = (speed_percent / 50.0) - 1.0 # -1.0 bis 1.0 evdev_accel = speed_percent / 10.0 # 0 bis 10 matrix_scale = speed_percent / 50.0 # 0 bis 2.0 success_count = 0 methods_used = set() # Wende Einstellungen auf ALLE Mäuse an for mouse_id in self.mouse_ids: success = False method = "" try: # Methode 1: libinput (modern) result = subprocess.run(['xinput', 'set-prop', mouse_id, 'libinput Accel Speed', str(libinput_speed)], capture_output=True, text=True) if result.returncode == 0: success = True method = "libinput" except: pass if not success: try: # Methode 2: evdev (älter) result = subprocess.run(['xinput', 'set-prop', mouse_id, 'Device Accel Constant Deceleration', str(10.0 / max(evdev_accel, 0.1))], capture_output=True, text=True) if result.returncode == 0: success = True method = "evdev" except: pass if not success: try: # Methode 3: Coordinate Transformation Matrix (Fallback für Logitech etc.) # Matrix für Skalierung: [scale, 0, 0, 0, scale, 0, 0, 0, 1] # WICHTIG: Muss als einzelne Werte übergeben werden, nicht als String result = subprocess.run(['xinput', 'set-prop', mouse_id, 'Coordinate Transformation Matrix', str(matrix_scale), '0', '0', '0', str(matrix_scale), '0', '0', '0', '1'], capture_output=True, text=True) if result.returncode == 0: success = True method = "matrix" except: pass if success: success_count += 1 methods_used.add(method) if success_count > 0: methods_str = "+".join(sorted(methods_used)) self.speed_label.config(text=f"{speed_percent}% ({success_count} Maus/Mäuse: {methods_str})") self.save_settings() else: self.speed_label.config(text=f"{speed_percent}% (Fehler!)") def reset_default(self): """Setze auf Standard zurück""" self.speed_var.set(50) self.apply_speed() if __name__ == '__main__': root = tk.Tk() app = MouseSettings(root) root.mainloop()