Skip to main content
The freerdp_peer API is the low-level foundation for writing an RDP server in C. The sample server in server/Sample/sfreerdp.c is the canonical reference implementation.

Core lifecycle functions

#include <freerdp/peer.h>
#include <freerdp/listener.h>

// Allocate a peer for an already-accepted socket file descriptor
freerdp_peer* freerdp_peer_new(int sockfd);

// Allocate and initialise the rdpContext attached to the peer.
// Must be called after setting ContextSize, ContextNew, ContextFree.
BOOL freerdp_peer_context_new(freerdp_peer* client);

// Variant that copies settings from an existing rdpSettings
BOOL freerdp_peer_context_new_ex(freerdp_peer* client, const rdpSettings* settings);

// Tear down the context (calls ContextFree, releases internal state)
void freerdp_peer_context_free(freerdp_peer* client);

// Release the peer struct itself (call after context_free)
void freerdp_peer_free(freerdp_peer* client);

The listener

Use freerdp_listener to accept incoming connections. For each accepted connection the library calls your PeerAccepted callback with a ready-to-configure freerdp_peer*.
#include <freerdp/listener.h>

freerdp_listener* freerdp_listener_new(void);
void freerdp_listener_free(freerdp_listener* instance);
Key freerdp_listener function pointers:
FieldSignatureDescription
OpenBOOL(instance, bind_address, port)Bind a TCP port
OpenLocalBOOL(instance, path)Bind a Unix socket path
OpenFromSocketBOOL(instance, fd)Use an already-bound socket fd
GetEventHandlesDWORD(instance, events[], count)Get waitable handles for WaitForMultipleObjects
CheckFileDescriptorBOOL(instance)Process pending I/O (calls PeerAccepted for new connections)
Closevoid(instance)Stop listening
PeerAcceptedBOOL(instance, peer)Your callback — called for each new connection

Extending rdpContext

Store per-connection application state by embedding rdpContext as the first member of a larger struct:
// sfreerdp.h
struct test_peer_context {
    rdpContext _p;          // must be first

    RFX_CONTEXT* rfx_context;
    NSC_CONTEXT* nsc_context;
    wStream*     s;
    BOOL         activated;
    HANDLE       vcm;       // virtual channel manager
    UINT32       frame_id;
    // … other per-peer state …
};
typedef struct test_peer_context testPeerContext;
Tell FreeRDP how to allocate and free this extended struct:
static BOOL test_peer_context_new(freerdp_peer* client, rdpContext* ctx)
{
    testPeerContext* context = (testPeerContext*)ctx;
    context->rfx_context = rfx_context_new_ex(TRUE, ...);
    // … initialise other fields …
    return TRUE;
}

static void test_peer_context_free(freerdp_peer* client, rdpContext* ctx)
{
    testPeerContext* context = (testPeerContext*)ctx;
    rfx_context_free(context->rfx_context);
    // … free other fields …
}

static BOOL test_peer_init(freerdp_peer* client)
{
    client->ContextSize = sizeof(testPeerContext);
    client->ContextNew  = test_peer_context_new;
    client->ContextFree = test_peer_context_free;
    return freerdp_peer_context_new(client);
}

Key peer callbacks

Set these function pointers on the freerdp_peer* before calling peer->Initialize(peer).

Connection lifecycle

CallbackSignatureWhen called
CapabilitiesBOOL(peer)Client capabilities have been received
PostConnectBOOL(peer)Full connection sequence complete; safe to send graphics
ActivateBOOL(peer)Client sent Synchronize/Control/Font-List; session is active
LogonBOOL(peer, identity, automatic)Authentication completed or anonymous connection established
ClientCapabilitiesBOOL(peer)Client capability set is available for inspection
PostConnect is the right place to open virtual channels and load resources. Return FALSE from any callback to abort the connection.

Channel and redirection

CallbackSignatureDescription
SendChannelDataBOOL(peer, channelId, data, size)Send raw data on a static channel
SendChannelPacketBOOL(peer, channelId, totalSize, flags, data, chunkSize)Send a channel packet with explicit chunking
ReceiveChannelDataBOOL(peer, channelId, data, size, flags, totalSize)Your callback — called when client sends channel data
SendServerRedirectionBOOL(peer, redirection)Redirect client to another server

Virtual channels (dynamic)

HANDLE peer->VirtualChannelOpen(peer, name, flags);
BOOL   peer->VirtualChannelClose(peer, hChannel);
int    peer->VirtualChannelRead(peer, hChannel, buffer, length);
int    peer->VirtualChannelWrite(peer, hChannel, buffer, length);
void*  peer->VirtualChannelGetData(peer, hChannel);
int    peer->VirtualChannelSetData(peer, hChannel, data);

Authentication

CallbackWhen to use
LogonValidate the user identity after NLA or inspect anonymous connections
LicenseCallbackHandle RDP licensing workflow
RemoteCredentialsHandle Remote Credential Guard (Kerberos TGT + NTLM hashes)

peer->settings configuration

Configure settings through peer->context->settings using freerdp_settings_set_* before calling peer->Initialize(peer). Common settings from the sample server:
rdpSettings* settings = client->context->settings;

// TLS certificate and key
rdpPrivateKey*  key  = freerdp_key_new_from_file_enc(key_file, NULL);
rdpCertificate* cert = freerdp_certificate_new_from_file(cert_file);
freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1);
freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1);

// Security
freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE);
freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE);
freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE);
freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
                            ENCRYPTION_LEVEL_CLIENT_COMPATIBLE);

// Codecs
freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE);
freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE);
freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32);

// Capabilities advertised to client
freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE);
freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE);

The peer event loop

After peer->Initialize(peer) succeeds, run a standard Windows-style wait loop:
HANDLE handles[MAXIMUM_WAIT_OBJECTS];
DWORD  count, status;

while (1)
{
    count = 0;

    // FreeRDP transport handles (connection + channel events)
    DWORD tmp = client->GetEventHandles(client, &handles[count], 32);
    if (tmp == 0) break;
    count += tmp;

    // Optional: virtual channel manager handle
    handles[count++] = WTSVirtualChannelManagerGetEventHandle(context->vcm);

    status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
    if (status == WAIT_FAILED) break;

    // Process incoming data (may trigger PostConnect / Activate callbacks)
    if (!client->CheckFileDescriptor(client)) break;

    // Process virtual channel events
    if (!WTSVirtualChannelManagerCheckFileDescriptor(context->vcm)) break;
}

client->Disconnect(client);
freerdp_peer_context_free(client);
freerdp_peer_free(client);
Run each peer’s event loop in its own thread. The PeerAccepted callback in the sample server spawns a CreateThread for each new connection.

Input callbacks

Register input handlers on client->context->input to receive keyboard and mouse events from the client:
rdpInput* input = client->context->input;

input->SynchronizeEvent      = tf_peer_synchronize_event;
input->KeyboardEvent         = tf_peer_keyboard_event;
input->UnicodeKeyboardEvent  = tf_peer_unicode_keyboard_event;
input->MouseEvent            = tf_peer_mouse_event;
input->RelMouseEvent         = tf_peer_rel_mouse_event;
input->ExtendedMouseEvent    = tf_peer_extended_mouse_event;
Example keyboard handler:
static BOOL tf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
{
    rdpContext* context = input->context;
    freerdp_peer* client = context->peer;

    // 'x' key closes the connection
    if (!(flags & KBD_FLAGS_RELEASE) && code == RDP_SCANCODE_KEY_X)
        return client->Close(client);

    return TRUE;
}

Sending graphics updates

Use client->context->update to push graphics to the client. Always bracket updates with surface frame markers:
rdpUpdate* update = client->context->update;

// Begin frame
SURFACE_FRAME_MARKER fm = { .frameAction = SURFACECMD_FRAMEACTION_BEGIN,
                             .frameId     = context->frame_id };
update->SurfaceFrameMarker(update->context, &fm);

// Send compressed surface bits (RemoteFX or NSCodec)
SURFACE_BITS_COMMAND cmd = { 0 };
// … fill cmd.bmp, cmd.destLeft/Top/Right/Bottom, cmd.cmdType …
update->SurfaceBits(update->context, &cmd);

// End frame
fm.frameAction = SURFACECMD_FRAMEACTION_END;
update->SurfaceFrameMarker(update->context, &fm);
context->frame_id++;
Other useful update callbacks:
CallbackDescription
update->DesktopResizeNotify client of a desktop resolution change
update->SurfaceCommandSend a raw surface command stream
update->RefreshRectCallback called when client requests a refresh
update->SuppressOutputCallback called when client minimises

Complete minimal server example

The following skeleton combines all the pieces above.
#include <freerdp/peer.h>
#include <freerdp/listener.h>

/* --- Per-connection context --- */
typedef struct { rdpContext _p; BOOL activated; } MyContext;

static BOOL my_context_new(freerdp_peer* peer, rdpContext* ctx) { return TRUE; }
static void my_context_free(freerdp_peer* peer, rdpContext* ctx) { }

/* --- Callbacks --- */
static BOOL my_post_connect(freerdp_peer* client)
{
    /* Session is ready. Start sending frames from the main loop. */
    return TRUE;
}

static BOOL my_activate(freerdp_peer* client)
{
    ((MyContext*)client->context)->activated = TRUE;
    return TRUE;
}

/* --- Per-peer thread --- */
static DWORD WINAPI peer_thread(LPVOID arg)
{
    freerdp_peer* client = (freerdp_peer*)arg;
    rdpSettings*  settings;

    client->ContextSize = sizeof(MyContext);
    client->ContextNew  = my_context_new;
    client->ContextFree = my_context_free;
    freerdp_peer_context_new(client);

    settings = client->context->settings;
    freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE);
    /* … set certificate, key, codecs … */

    client->PostConnect = my_post_connect;
    client->Activate    = my_activate;

    client->Initialize(client);

    HANDLE h[32]; DWORD count, status;
    while (1) {
        count = client->GetEventHandles(client, h, 32);
        if (!count) break;
        status = WaitForMultipleObjects(count, h, FALSE, INFINITE);
        if (status == WAIT_FAILED) break;
        if (!client->CheckFileDescriptor(client)) break;
    }

    client->Disconnect(client);
    freerdp_peer_context_free(client);
    freerdp_peer_free(client);
    return 0;
}

/* --- Listener callback --- */
static BOOL peer_accepted(freerdp_listener* instance, freerdp_peer* client)
{
    HANDLE t = CreateThread(NULL, 0, peer_thread, client, 0, NULL);
    CloseHandle(t);
    return TRUE;
}

/* --- main --- */
int main(void)
{
    freerdp_listener* listener = freerdp_listener_new();
    listener->PeerAccepted = peer_accepted;
    listener->Open(listener, NULL, 3389);

    HANDLE h[32]; DWORD count, status;
    while (1) {
        count = listener->GetEventHandles(listener, h, 32);
        status = WaitForMultipleObjects(count, h, FALSE, INFINITE);
        if (status == WAIT_FAILED) break;
        if (!listener->CheckFileDescriptor(listener)) break;
    }

    listener->Close(listener);
    freerdp_listener_free(listener);
    return 0;
}
The full production-quality example is server/Sample/sfreerdp.c. It adds RemoteFX/NSCodec rendering, virtual channel management, audio, and PCAP replay support.