Binding asyncio sur libfreerdp3, pensé comme l'équivalent RDP d'asyncvnc2 pour GCM (gnome-connection-manager) — même ergonomie, même intégration asyncio native, pas de sous-processus xfreerdp à piloter.
import asyncio
import asyncrdp
async def main():
async with asyncrdp.connect("192.168.1.10", username="alice", password="secret") as client:
frame = await client.get_frame()
print(client.frame_size)
asyncio.run(main())Ce n'est plus un squelette : le cœur de la bibliothèque a été compilé, exécuté et testé contre un vrai serveur RDP (xrdp), avec de vraies données transitant dans les deux sens — pas seulement une relecture de code. Le détail complet des tests, bugs trouvés/corrigés et limitations connues est dans CLAUDE.md.
Résumé de ce qui est validé en conditions réelles :
| Fonctionnalité | Statut |
|---|---|
| Connexion, TLS/NLA, affichage (GDI/frames) | ✅ testé |
| Clavier, souris | ✅ testé |
| Resize dynamique | ✅ testé |
| Clipboard texte (bidirectionnel) | ✅ testé, round-trip identique |
| Clipboard image (bidirectionnel) | ✅ testé, byte-identique |
| Clipboard fichiers (bidirectionnel, avec dossiers récursifs) | ✅ testé, contenu identique |
| Disque redirigé (lecture + écriture) | ✅ testé, les deux sens |
| Multi-écran | ✅ testé |
| Audio | |
| Pipeline graphique RDPGFX / H.264 | ✅ validé de bout en bout (5 frames réelles reçues en continu) — nécessite de recompiler FreeRDP avec -DWITH_OPENH264=ON (le paquet système en est dépourvu). Voir H264_BUILD_GUIDE.md |
| Imprimante, série, parallèle | |
| USB | |
| Intégration GTK4 (affichage + ponts clipboard) |
asyncrdp/
├── pyproject.toml Métadonnée du package (PEP 621)
├── setup.py Minimal — nécessaire pour cffi_modules
├── build_ffi.py Script de compilation du module cffi
├── MANIFEST.in Inclut _shim.c dans la distribution source
├── src/asyncrdp/
│ ├── __init__.py Point d'entrée public (réexporte l'API)
│ ├── _core.py Bibliothèque cœur — API publique asyncio
│ └── _shim.c Shim C compilé contre libfreerdp3
├── integrations/gtk4/
│ ├── gcm_gtk4_display_bridge.py Widget GTK4 (Gtk.Picture + input)
│ └── gcm_gtk4_clipboard_bridge.py Pont Gdk.Clipboard <-> asyncrdp.Clipboard
├── examples/
│ ├── test_connect_minimal.py Test minimal : connexion + quelques frames
│ ├── test_connect_full.py Test protocole (autonome)
│ └── test_full_suite.py Suite complète, y compris tests dépendants de l'environnement
├── docs/
│ └── H264_BUILD_GUIDE.md Recompiler FreeRDP avec support H.264 réel
└── scripts/
└── setup_build.sh Installation manuelle des dépendances (hors pip)
Le package asyncrdp (sous src/) ne dépend que du module compilé asyncrdp._asyncrdp_cffi (généré par build_ffi.py à l'installation) et de loguru. Les fichiers sous integrations/gtk4/ dépendent en plus de PyGObject (gi.repository.Gtk/Gdk/GdkPixbuf) — installer l'extra pip install asyncrdp[gtk4] — et sont pensés pour être déplacés tels quels dans le futur plugin RDP de GCM une fois son architecture à plugins en place ; ils ne font aucune hypothèse sur le reste de GCM au-delà de asyncrdp.Client.
FreeRDP n'a pas de binding Python officiel, et le protocole RDP (négociation TLS/NLA, codecs d'image, canaux virtuels) est trop complexe à réimplémenter raisonnablement. Le choix a été cffi (mode API, compilation réelle) + un petit shim C (src/asyncrdp/_shim.c) appelant directement l'API C de FreeRDP — les mêmes fonctions qu'utilise xfreerdp — plutôt que de spawn xfreerdp en sous-processus (ce qui aurait été plus simple mais aurait interdit l'accès direct au framebuffer et des callbacks clipboard propres).
Prérequis système (Debian/Ubuntu) — voir aussi scripts/setup_build.sh pour un script prêt à l'emploi :
sudo apt install freerdp3-dev libwinpr3-dev build-essential pkg-config python3-devPuis, depuis la racine du dépôt cloné :
pip install .
# ou, pour du développement (rebuild auto si le shim change) :
pip install -e .
# avec le pont GTK4 :
pip install -e ".[gtk4]"pip install compile automatiquement le shim C via cffi_modules — pas besoin de lancer build_ffi.py séparément.
async with asyncrdp.connect(host, port=3389, username="u", password="p") as client:
...options = asyncrdp.RdpOptions(
width=1920, height=1080, color_depth=32,
redirect_clipboard=True,
redirect_drives=True,
drives=[asyncrdp.DriveMapping("home", "/home/alice")],
redirect_printers=True,
printers=[asyncrdp.PrinterMapping("MonImprimante", is_default=True)],
serial_ports=[asyncrdp.SerialMapping("COM1", "/dev/ttyUSB0")],
audio_playback=True,
monitors=[
asyncrdp.MonitorDef(0, 0, 1920, 1080, is_primary=True),
asyncrdp.MonitorDef(1920, 0, 1280, 1024),
],
)
async with asyncrdp.connect(host, username="u", password="p", options=options) as client:
...Tous les champs ont un défaut raisonnable — ne préciser que ce qui doit changer. Voir la docstring de RdpOptions dans asyncrdp.py pour la liste complète.
raw = await client.get_frame() # BGRA32 brut
width, height = client.frame_sizeclient.keyboard.write("bonjour")
client.keyboard.key_press("return") # touches nommées : return, tab, escape, f1-f12, flèches...
client.mouse.move(100, 200)
client.mouse.click("left") # ou button_press()/button_release() séparés pour un drag
client.mouse.scroll(120) # positif = molette vers le hautclient.request_resize(1024, 768) # le serveur peut clamp/ignorer ; confirmation via les frames suivantesclient.clipboard.on_remote_text_changed = lambda text: print("reçu:", text)
client.clipboard.on_remote_image_changed = lambda dib: ... # DIB brut (BITMAPINFOHEADER/V5 + pixels)
client.clipboard.on_remote_files_changed = lambda files: ... # list[RemoteFileInfo]
client.clipboard.get_local_text = lambda: "mon texte"
client.clipboard.announce_local_text("mon texte")
data = await client.clipboard.request_remote_file_contents(index)Voir integrations/gtk4/gcm_gtk4_clipboard_bridge.py pour un exemple complet de câblage vers Gdk.Clipboard (texte, image, fichiers).
# Connexion minimale, sans redirections
python examples/test_connect_minimal.py <host> <user> <password>
# Suite protocole (frames, input, resize, clipboard) — autonome, tourne contre n'importe quel serveur RDP
python examples/test_connect_full.py <host> <user> <password>
# Suite complète, y compris tests nécessitant un accès shell à la session distante
python examples/test_full_suite.py <host> <user> <password> \
--session-display :11 --session-user rdptest \
--drive-mount-path /home/rdptest/thinclient_drives/<nom_du_disque>Les deux derniers arguments de test_full_suite.py ne sont utiles qu'en environnement de développement avec accès shell à la machine hébergeant la session RDP (typiquement un serveur de test local) — ils ne s'appliquent pas à un usage normal en production.
- Noms de champs FreeRDP à revérifier en cas de changement de version :
asyncrdp_shim.ccontient des commentaires signalant les points sensibles (freerdp_get_last_error_stringvs_name, layout des structuresRDPDR_*,CLIPRDR_FORMAT_DATA_RESPONSE.common.*). - Formats clipboard riches : image limitée à
CF_DIB/CF_DIBV5(pas deCF_DIBV6/formats PNG natifs) ; fichiers : pas de reprise sur coupure réseau pendant un téléchargement. - USB : le shim transmet une chaîne de sélection à
urbdrcmais n'énumère pas lui-même les périphériques (prévu côté appelant, via libusb). - Redirections imprimante/série/parallèle : dépendent entièrement du support serveur — de nombreux serveurs RDP Linux (dont xrdp dans sa configuration par défaut) ne les implémentent pas.
- Voir
CLAUDE.mdpour l'historique complet des bugs trouvés et le détail des zones non testées.