The NVDA Remote protocol facilitates communication between two NVDA instances, enabling remote assistance and collaboration. It uses a client-server model where either client can act as the controlling (master) or controlled (slave) machine.
- Clients connect to a relay server using a TCP connection over SSL/TLS.
- Clients authenticate by joining a shared channel.
- The relay server facilitates message passing between connected clients.
Messages are serialized as JSON objects with a 'type' field indicating the message type. Each message is terminated with a newline character ('\n').
- Upon connection, the client sends a
protocol_version
message. - If versions are incompatible, an error is sent and the connection is closed.
Below is a detailed specification of each message type using JSONSchema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"protocol_version": {
"type": "object",
"properties": {
"type": { "const": "protocol_version" },
"version": { "type": "integer" }
},
"required": ["type", "version"]
},
"join": {
"type": "object",
"properties": {
"type": { "const": "join" },
"channel": { "type": "string" },
"connection_type": { "enum": ["master", "slave"] }
},
"required": ["type", "channel", "connection_type"]
},
"channel_joined": {
"type": "object",
"properties": {
"type": { "const": "channel_joined" },
"channel": { "type": "string" },
"clients": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"connection_type": { "enum": ["master", "slave"] }
},
"required": ["id", "connection_type"]
}
}
},
"required": ["type", "channel", "clients"]
},
"client_joined": {
"type": "object",
"properties": {
"type": { "const": "client_joined" },
"client": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"connection_type": { "enum": ["master", "slave"] }
},
"required": ["id", "connection_type"]
}
},
"required": ["type", "client"]
},
"client_left": {
"type": "object",
"properties": {
"type": { "const": "client_left" },
"client": { "type": "integer" }
},
"required": ["type", "client"]
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"key": {
"type": "object",
"properties": {
"type": { "const": "key" },
"vk_code": { "type": "integer" },
"scan_code": { "type": "integer" },
"extended": { "type": "boolean" },
"pressed": { "type": "boolean" }
},
"required": ["type", "vk_code", "scan_code", "extended", "pressed"]
},
"speak": {
"type": "object",
"properties": {
"type": { "const": "speak" },
"sequence": {
"type": "array",
"items": {
"oneOf": [
{ "type": "string" },
{
"type": "array",
"items": [
{ "type": "string" },
{ "type": "object" }
],
"minItems": 2,
"maxItems": 2
}
]
}
},
"priority": { "type": "string" }
},
"required": ["type", "sequence", "priority"]
},
"cancel": {
"type": "object",
"properties": {
"type": { "const": "cancel" }
},
"required": ["type"]
},
"pause_speech": {
"type": "object",
"properties": {
"type": { "const": "pause_speech" },
"switch": { "type": "boolean" }
},
"required": ["type", "switch"]
},
"tone": {
"type": "object",
"properties": {
"type": { "const": "tone" },
"hz": { "type": "number" },
"length": { "type": "number" },
"left": { "type": "number" },
"right": { "type": "number" }
},
"required": ["type", "hz", "length", "left", "right"]
},
"wave": {
"type": "object",
"properties": {
"type": { "const": "wave" },
"fileName": { "type": "string" },
"asynchronous": { "type": "boolean" }
},
"required": ["type", "fileName"]
},
"display": {
"type": "object",
"properties": {
"type": { "const": "display" },
"cells": { "type": "array", "items": { "type": "integer" } }
},
"required": ["type", "cells"]
},
"braille_input": {
"type": "object",
"properties": {
"type": { "const": "braille_input" },
"dots": { "type": "integer" },
"space": { "type": "boolean" },
"routingIndex": { "type": "integer" }
},
"required": ["type"]
},
"set_clipboard_text": {
"type": "object",
"properties": {
"type": { "const": "set_clipboard_text" },
"text": { "type": "string" }
},
"required": ["type", "text"]
},
"send_SAS": {
"type": "object",
"properties": {
"type": { "const": "send_SAS" }
},
"required": ["type"]
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"set_braille_info": {
"type": "object",
"properties": {
"type": { "const": "set_braille_info" },
"name": { "type": "string" },
"numCells": { "type": "integer" }
},
"required": ["type", "name", "numCells"]
},
"set_display_size": {
"type": "object",
"properties": {
"type": { "const": "set_display_size" },
"sizes": { "type": "array", "items": { "type": "integer" } }
},
"required": ["type", "sizes"]
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ping": {
"type": "object",
"properties": {
"type": { "const": "ping" }
},
"required": ["type"]
},
"error": {
"type": "object",
"properties": {
"type": { "const": "error" },
"message": { "type": "string" }
},
"required": ["type", "message"]
}
}
}
- All connections are encrypted using SSL/TLS.
- Clients can verify the server's certificate fingerprint to prevent man-in-the-middle attacks.
- The channel key acts as a shared secret for authentication.
- Connection errors trigger automatic reconnection attempts.
- Protocol errors are communicated using the
error
message type.
This protocol documentation provides a high-level overview of the NVDA Remote functionality. For detailed implementation, refer to the source code files, particularly transport.py
, session.py
, and serializer.py
.