Skip to content

Repository files navigation

asyncrdp

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())

État du projet

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 ⚠️ négociation de canal confirmée, charge utile jamais testée (pas de pile audio disponible en sandbox de dev)
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 ⚠️ le client fonctionne, mais le serveur de test (xrdp) ne les supporte pas — non validable dans cet environnement
USB ⚠️ jamais testé (pas de périphérique physique disponible)
Intégration GTK4 (affichage + ponts clipboard) ⚠️ écrite, jamais exécutée (pas d'environnement graphique local)

Architecture

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.

Pourquoi du C plutôt que du pur Python ?

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).

Installation

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-dev

Puis, 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.

Utilisation

Connexion simple

async with asyncrdp.connect(host, port=3389, username="u", password="p") as client:
    ...

Réglages façon mstsc (RdpOptions)

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.

Affichage

raw = await client.get_frame()      # BGRA32 brut
width, height = client.frame_size

Clavier / souris

client.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 haut

Resize dynamique

client.request_resize(1024, 768)   # le serveur peut clamp/ignorer ; confirmation via les frames suivantes

Clipboard riche (texte / image / fichiers)

client.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).

Tests

# 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.

Limitations connues

  • Noms de champs FreeRDP à revérifier en cas de changement de version : asyncrdp_shim.c contient des commentaires signalant les points sensibles (freerdp_get_last_error_string vs _name, layout des structures RDPDR_*, CLIPRDR_FORMAT_DATA_RESPONSE.common.*).
  • Formats clipboard riches : image limitée à CF_DIB/CF_DIBV5 (pas de CF_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 à urbdrc mais 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.md pour l'historique complet des bugs trouvés et le détail des zones non testées.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages