Skip to content

Screen Capture

Simple RDP provides screen capture capabilities through the screenshot() method.

Basic Screenshot

import asyncio
from simple_rdp import RDPClient


async def main():
    async with RDPClient(
        host="192.168.1.100",
        username="admin",
        password="secret",
    ) as client:
        # Wait for screen to render
        await asyncio.sleep(2)

        # Capture as PIL Image
        img = await client.screenshot()  # (1)!
        print(f"Size: {img.size}")  # (2)!
        print(f"Mode: {img.mode}")  # RGB


asyncio.run(main())
  1. Returns a PIL.Image.Image in RGB mode
  2. Size matches the configured width and height

Save Screenshot

# Save directly to file
await client.save_screenshot("screenshot.png")

# Or save the PIL Image
img = await client.screenshot()
img.save("screenshot.png")
img.save("screenshot.jpg", quality=90)

Continuous Capture

For video recording or monitoring:

import asyncio
from simple_rdp import RDPClient


async def capture_loop():
    async with RDPClient(...) as client:
        await asyncio.sleep(2)  # Wait for initial render

        frame_count = 0
        while frame_count < 100:
            img = await client.screenshot()
            img.save(f"frames/frame_{frame_count:04d}.png")
            frame_count += 1
            await asyncio.sleep(0.1)  # ~10 FPS  # (1)!


asyncio.run(capture_loop())
  1. You can achieve up to 30 FPS with continuous capture

Performance

For higher frame rates, consider saving frames asynchronously.

Screenshot with Cursor

The cursor can be composited into screenshots. Access cursor state via:

# Get cursor position
x, y = client.pointer_position

# Check if cursor is visible
if client.pointer_visible:
    # Get cursor image (PIL Image or None)
    cursor_img = client.pointer_image

    # Get cursor hotspot (click point offset)
    hotspot_x, hotspot_y = client.pointer_hotspot

See Pointer/Cursor for more details.

Image Processing

Screenshots are returned as PIL Images, making it easy to process:

from PIL import Image

img = await client.screenshot()

# Crop a region
region = img.crop((100, 100, 500, 400))

# Resize
thumbnail = img.resize((480, 270))

# Convert to grayscale
gray = img.convert("L")

# Get pixel data
pixels = list(img.getdata())

# Convert to numpy array (with numpy installed)
import numpy as np
arr = np.array(img)

Performance Tips

Disable Wallpaper
client = RDPClient(
    ...,
    show_wallpaper=False,  # Reduces bandwidth
)
Lower Color Depth
client = RDPClient(
    ...,
    color_depth=16,  # 16-bit color is faster
)
Appropriate Resolution

Use only the resolution you need:

client = RDPClient(
    ...,
    width=1280,
    height=720,  # Smaller = faster
)

Screen Update Mechanism

How Screen Capture Works

Simple RDP maintains an internal screen buffer that is updated incrementally as the RDP server sends bitmap updates. The screenshot() method returns a copy of this buffer.

The screen buffer is updated in the background by a receive loop that processes:

  • Fast-Path bitmap updates (compressed with RLE)
  • Bitmap update PDUs
  • Pointer updates

This means screenshots always reflect the current screen state, not just the initial connection.


Video Recording

The RDPClient has an integrated Display component for video recording. Frames are automatically captured as the screen updates.

Basic Video Recording

import asyncio
from simple_rdp import RDPClient


async def record_session():
    async with RDPClient(
        host="192.168.1.100",
        username="admin",
        password="secret",
    ) as client:
        await asyncio.sleep(2)  # Wait for initial render

        # Start file recording (unlimited duration)
        await client.start_file_recording("recording.ts")  # (1)!

        # Do some actions...
        await client.mouse_move(500, 300)
        await asyncio.sleep(5)  # Record for 5 seconds

        # Stop recording
        await client.stop_file_recording()  # (2)!
        await client.stop_streaming()

        # Check stats
        stats = client.get_recording_stats()
        print(f"Frames: {stats['frames_encoded']}")


asyncio.run(record_session())
  1. Starts ffmpeg encoder, records directly to file
  2. Stops recording and closes the file

Save Buffer as Video

For short clips, save the rolling frame buffer (~10 seconds):

import asyncio
from simple_rdp import RDPClient


async def save_clip():
    async with RDPClient(
        host="192.168.1.100",
        username="admin",
        password="secret",
    ) as client:
        await asyncio.sleep(2)

        # Frames are always captured in the background
        # Wait and then save the last ~10 seconds
        await asyncio.sleep(15)

        # Save buffer as video
        await client.save_video("clip.mp4")


asyncio.run(save_clip())

Access Display Directly

For advanced use cases, access the Display component directly:

# Access the integrated Display
display = client.display

# Get encoding statistics
print(f"Frames received: {display.stats['frames_received']}")
print(f"Buffer size: {display.video_buffer_size_mb:.2f} MB")

# Get latest frame as ScreenBuffer
latest = display.get_latest_frame()
if latest:
    print(f"Frame size: {latest.width}x{latest.height}")

# Stream video chunks (for WebSocket, etc.)
async def stream_chunks():
    await client.start_streaming()
    while client.is_streaming:
        chunk = await display.get_next_video_chunk(timeout=1.0)
        if chunk:
            yield chunk.data

See Also

For complete Display API reference, see Display API.