Pointer

August 21, 2026 ยท View on GitHub

The prefix for all packets and capabilities should be pointer.
(older versions used the mouse prefix)

ComponentLink
clientxpra.client.subsystem.pointer
client connectionxpra.server.source.pointer
serverxpra.server.subsystem.pointer

There is some platform specific code to handle mouse wheel.
Links pending.

The client side wheel handling lives in the same pointer subsystem: see mouse wheel below.

The client should expose the following pointer dictionary in its hello packet:

CapabilityValueInformation
initial-positionx and y pair of coordinatesOptional
double_clickdictionarycontains just two integer attributes: time (in milliseconds) and distance
syncbooleanOptional, see pointer synchronization
recordbooleanOptional, legacy alias for sync
grabsbooleanLegacy only, superseded by the window capability of the same name

Modern packets keep the pointer field as a non-negative (absolute_x, absolute_y) pair normalized against the client monitor layout. They carry alternative coordinate spaces in the properties dictionary:

  • raw-position: the absolute position before layout normalization
  • window-position: (window_x, window_y)
  • monitor: {"index": monitor_index, "position": (monitor_x, monitor_y)}

Alternatively, the client can just supply the value True instead of the dictionary and the server will use default values.

The server exposes these hello capabilities:

CapabilityValueInformation
input-devicesstringthe pointer device in use: xtest, uinput, xi, ..
wheel.precisebooleanwhether the pointer device can inject fractional wheel motion, see mouse wheel
wheel.emulationbooleanthe server emulates the wheel with button events when its device cannot do it, so pointer-wheel packets are always accepted
pointer.relativebooleanassumed available since v5.0.3
pointer.optionalbooleanthe client may omit the pointer data from its packets
Packet TypeDirectionArguments
pointer-motionclient to serverdevice_id, sequence, wid, pointer, properties
pointer-buttonclient to serverdevice_id, sequence, wid, button, pressed, pointer, properties
pointer-wheelclient to serverwid, button, distance, pointer, modifiers, buttons, properties (scaled-distance)
pointer-motionserver to clientdevice_id, sequence, wid, pointer, properties
pointer-buttonserver to clientdevice_id, sequence, wid, button, pressed, pointer, properties
pointer-wheelserver to clientwid, button, distance, pointer, modifiers
pointer-positionserver to clientwid, x, y, relative-x, relative-y

When a client enables the sync capability, the server echoes back to it the pointer events it receives from all the other clients connected to the same session, using the exact same packet types.
This is used by the recording client to capture the input events, and by regular clients started with sharing=sync (or sharing=sync-pointer), so that each user can see what the other users are doing: the position received is shown as a pointer overlay, in the same way as the pointer position updates sent by shadow servers.

Only the pointer-motion packets carry a window-position property, so this is the only packet type which can update the overlay.

The server can refuse the synchronization requested by a client using the sync socket option, which applies to the pointer synchronization and to the window position and focus synchronization.
Unlike the record socket option, it is enabled by default. The value can be a boolean, all, or a comma separated list of subsystems:

xpra start --bind-tcp=0.0.0.0:10000,sync=no
xpra start --bind-tcp=0.0.0.0:10000,sync=pointer,focus

Wheel events reach the pointer subsystem as fractional deltas (wheel_event, which accumulates them per axis), and are sent as pointer-wheel packets carrying the exact distance multiplied by 1000.

What the server does with them depends on its pointer device:

  • devices which can inject fractional wheel motion (uinput and wayland) apply the distance directly - these advertise wheel.precise
  • the others (xtest, which is the X11 default, macos and win32) quantize the distance into discrete steps and emulate each step with a button press and release pair on the wheel button (4 / 5 for the vertical axis, 6 / 7 for the horizontal one), keeping the remainder for the next event

So pointer-wheel is the packet used in both cases, and the button emulation is a server side implementation detail rather than a legacy path.

The emulation used to be done by the clients, which is why the scroll speed settings (XPRA_MOUSE_SCROLL_MULTIPLIER and XPRA_MOUSE_SCROLL_SQRT_SCALE) are still applied client side: the client sends the distance those settings turn into, as the scaled-distance property of the packet, and that is what the server quantizes.
Servers with a precise device ignore it and use the exact distance, and servers too old to know about it just ignore the extra property.

What is left over after quantizing is always less than half a click, so it is carried over to the next event: this is how a slow scroll eventually adds up to a click. It can only ever shorten the next scroll, never reverse it.

Clients too old to know about wheel.emulation still emulate the wheel themselves when the server does not advertise wheel.precise, sending pointer-button packets instead.

The mousewheel client option is parsed into a wheel_map translating the wheel buttons, which is how the axes can be inverted (invert-x, invert-y, invert-z, invert-all), and into a wheel_smooth flag - coarse disables the smooth scrolling events at the toolkit level, so only the discrete button events are generated.