This release is a pre-release and may not be stable for production use.
Simple RDP
A Python RDP client library designed for automation purposes. Unlike traditional RDP clients, Simple RDP does not provide an interactive session. Instead, it exposes screen capture and input transmission capabilities for building automation workflows.
[!CAUTION] Security Warning: No TLS Certificate Validation
This library does NOT validate TLS certificates when connecting to RDP servers. This means:
- Connections are vulnerable to man-in-the-middle (MITM) attacks
- Server identity is not verified
- Do not use in production environments or over untrusted networks
This limitation is known and will be addressed in a future release. For now, only use this library in trusted network environments (e.g., local development, isolated lab networks).
Features
- Screen Capture: Capture the remote desktop screen as PIL Images
- Input Transmission: Send mouse movements, clicks, and keyboard input
- NLA/CredSSP Authentication: Full support for Network Level Authentication
- Automation-Focused: Built specifically for automation, not interactive use
- Async Support: Built with asyncio for non-blocking operations
Requirements
- Python 3.11+
- Windows RDP server with NLA enabled
Installation
poetry install
Configuration
Create a .env file in the project root with your RDP connection settings:
cp .env.example .env
# Edit .env with your settings
RDP_HOST=192.168.1.100
RDP_USER=your_username
RDP_PASS=your_password
Usage
Basic Connection and Screenshot
import asyncio
import os
from dotenv import load_dotenv
from simple_rdp import RDPClient
load_dotenv()
async def main():
async with RDPClient(
host=os.environ["RDP_HOST"],
username=os.environ["RDP_USER"],
password=os.environ["RDP_PASS"],
width=1920,
height=1080,
) as client:
# Wait for screen to fully render
await asyncio.sleep(2)
# Capture and save screenshot
await client.save_screenshot("desktop.png")
# Or get PIL Image directly
img = await client.screenshot()
print(f"Captured: {img.size}")
if __name__ == "__main__":
asyncio.run(main())
Sending Input
import asyncio
import os
from dotenv import load_dotenv
from simple_rdp import RDPClient
load_dotenv()
async def main():
async with RDPClient(
host=os.environ["RDP_HOST"],
username=os.environ["RDP_USER"],
password=os.environ["RDP_PASS"],
) as client:
await asyncio.sleep(2)
# Mouse operations
await client.mouse_move(100, 200)
await client.mouse_click(100, 200) # Left click
await client.mouse_click(100, 200, button=2) # Right click
await client.mouse_click(100, 200, double_click=True) # Double click
await client.mouse_drag(100, 100, 300, 300) # Drag from (100,100) to (300,300)
# Keyboard operations
await client.send_text("Hello, World!") # Type text
await client.send_key(0x1C) # Send Enter key (scancode)
await client.send_key("a") # Send 'a' as unicode
if __name__ == "__main__":
asyncio.run(main())
Manual Connection Management
import asyncio
import os
from dotenv import load_dotenv
from simple_rdp import RDPClient
load_dotenv()
async def main():
client = RDPClient(
host=os.environ["RDP_HOST"],
username=os.environ["RDP_USER"],
password=os.environ["RDP_PASS"],
domain="MYDOMAIN", # Optional domain
)
try:
await client.connect()
print(f"Connected: {client.width}x{client.height}")
await asyncio.sleep(2)
await client.save_screenshot("screenshot.png")
finally:
await client.disconnect()
if __name__ == "__main__":
asyncio.run(main())
API Reference
RDPClient
Constructor
RDPClient(
host: str,
port: int = 3389,
username: str | None = None,
password: str | None = None,
domain: str | None = None,
width: int = 1920,
height: int = 1080,
color_depth: int = 32,
)
Properties
host- The RDP server hostnameport- The RDP server portis_connected- Whether the client is connectedwidth- Desktop width in pixelsheight- Desktop height in pixels
Methods
connect()- Establish connection to the RDP serverdisconnect()- Disconnect from the serverscreenshot()- Capture the current screen as a PIL Imagesave_screenshot(path)- Save a screenshot to a filesend_key(key, is_press=True, is_release=True)- Send a keyboard keysend_text(text)- Type a text stringmouse_move(x, y)- Move the mouse to a positionmouse_click(x, y, button=1, double_click=False)- Click at a positionmouse_drag(x1, y1, x2, y2, button=1)- Drag from one position to another
Development
Setup
poetry install
# Optional: Install Rust RLE acceleration (100x faster)
cd rle-fast && maturin develop --release && cd ..
Running Tests
# Unit tests (no RDP connection needed)
poetry run pytest tests/ --ignore=tests/e2e
# E2E tests (requires RDP server)
cp .env.example .env # Edit with your credentials
poetry run pytest tests/e2e/
# With coverage
poetry run pytest tests/ --ignore=tests/e2e --cov=src/simple_rdp
Linting and Type Checking
poetry run ruff check src/
poetry run mypy src/
Pre-commit Hooks
poetry run pre-commit install
poetry run pre-commit run --all-files
Project Structure
simple-rdp/
├── src/
│ └── simple_rdp/
│ ├── __init__.py # Package exports
│ ├── client.py # Main RDPClient class
│ ├── capabilities.py # RDP capability sets
│ ├── credssp.py # CredSSP/NLA authentication
│ ├── mcs.py # MCS/T.125 layer
│ ├── pdu.py # RDP PDU layer
│ ├── rle.py # RLE bitmap decompression
│ ├── screen.py # Display class for video encoding
│ └── input.py # Input handling utilities
├── tests/
│ ├── test_client.py # Client unit tests
│ ├── test_screen.py # Display unit tests
│ ├── test_input.py # Input unit tests
│ └── e2e/ # End-to-end tests (need real RDP)
│ ├── test_basic_connection.py
│ ├── test_video_recording.py
│ ├── test_performance.py
│ └── test_display.py
├── agents/
│ └── tools/
│ └── analyze_image.py # AI image analysis tool
├── rle-fast/ # Rust RLE acceleration (optional)
│ ├── Cargo.toml
│ ├── pyproject.toml
│ └── src/lib.rs
├── .env.example # Environment template
├── pyproject.toml
└── README.md
Performance
The library includes optional Rust acceleration for RLE bitmap decompression:
| Mode | Screenshot FPS | Event Loop Usage |
|---|---|---|
| Pure Python | ~15 FPS | ~50% |
| Rust + GIL release | ~30 FPS | ~10% |
Install Rust acceleration with:
cd rle-fast && maturin develop --release
The library automatically uses Rust when available, falling back to pure Python.
Protocol Support
- X.224 Connection Sequence
- TLS/SSL encryption
- CredSSP v6 (NLA authentication with NTLM)
- MCS Connect/Channel Join
- RDP capability exchange
- Fast-Path output (bitmap updates)
- Interleaved RLE bitmap decompression
- Slow-path input (keyboard/mouse)
License
MIT
Metadata
Release files for simple-rdp 0.1.0a20260201030130
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| simple_rdp-0.1.0a20260201030130.tar.gz | 42.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| simple_rdp-0.1.0a20260201030130-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.1 kB
Release files / simple_rdp-0.1.0a20260201030130.tar.gz
| Download URL | simple_rdp-0.1.0a20260201030130.tar.gz |
|---|---|
| Size | 42.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
acfa12efb18dd3374645214560ea89b512fd8edf787cb06b980ffd1877b30eb6
|
|
BLAKE2b-256 checksum How to use checksums |
8180d5706f7669a546f2625310c499524ff243e4fc9057674ceaaf7f5cb3a8ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 1, 2026.
Transparency logRelease files / simple_rdp-0.1.0a20260201030130-py3-none-any.whl
| Download URL | simple_rdp-0.1.0a20260201030130-py3-none-any.whl |
|---|---|
| Size | 44.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
33d3a5738effb050c0673b0c88e758d2749e5147527523be174a3f3216944d5e
|
|
BLAKE2b-256 checksum How to use checksums |
49b7f2cb0875e7df40c24d22a456a79119a0e1d07d5355fb1e551c97791041e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 1, 2026.
Transparency log