This repository holds an implementation of a VNC server that can supply the contents of a PyCairo surface to multiple clients.
Currently a work in progress, it is intended to incorporate most of the simple features of VNC screen, together with the keyboard and mouse input.
The code is intentionally compatible between Python 2 and Python 3.
The code for the VNC server is all in the cairovnc
directory. It'll be at varying degrees
of completeness as time goes on.
The CairoVNC package is structured such that users of the system (the 'animator', within this documentation) should usually only access the CairoVNCServer and CairoVNCOptions objects. The 'animator' controls the Cairo surface, and the CairoVNCServer object manages the server and its connections.
The CairoVNCServer provides a VNC server which will spawn a new thread for each connection. These connections may come and go, and their updates will be managed by the CairoVNCServer system. The connections may supply events (such as mouse operations and key presses) to the animator through a thread safe queue whcih it may consume.
The animator may prevent access to the surface it is updating through a thread lock to prevent unsafe operations and incomplete frames.
The CairoVNC server can be created with the cairovnc.CairoVNCServer
object creation. This
object takes the surface which should be used as the only required parameter. It also
takes a number of optional named parameters which are commonly used:
host
andport
: Supplies the address and port that the server should listen on.surface_lock
: Supplies athreading.Lock
object which will be used around all access to the surface.options
: Supplies acairovnc.CairoVNCOptions
object which provides all the other exposed configurables.
Creating the CairoVNCServer
object does not begin listening immediately. The server can be
started and stopped under the control of the animator system. The server can run in one of three
models:
- Daemonised: This model is activated by using the
daemonise
method. This will start a separate thread for the server. It will run until the process exits, or thestop
method is called (from another thread). - Blocking: This model is activated by using the
serve_forever
method. This will start the server on the current thread. It will run until thestop
method is called (from another thread). - Polled: This model is activated by using the
start
method to start the server listening, and must then be polled for connections with thepoll
method. Thepoll
method can block for a period or return immediately. When the server is no longer needed thestop
method should be called.
It is not expected that the Polled model be used often; the Daemonised and Blocking models are more likely to be useful to users.
The basic creation of a server might be thus:
server = cairovnc.CairoVNCServer(port=5900, surface=surface)
server.serve_forever()
The more advanced cases may create an options object, and place the server into a separate thread to run.
options = cairovnc.CairoVNCOptions(port=5900, password='secret')
options.max_clients = 20
server = cairovnc.CairoVNCServer(surface=surface, surface_lock=surface_lock,
options=options)
server.daemonise()
while still_animating:
with surface_lock:
render_frame()
time.sleep(frame_period)
server.stop()
Events from the remote connection are delivered to a queue and can be retrieved by calling the
get_event
method. This method takes a timeout to allow it to poll for a period or to return
immediately. The events that are delivered will depend on the client's capabilities. Not all
events will be of interest to the animator. The event type can be recognised either by checking
the type of VNCEvent
instance that has been supplied, or examining the name
property of the
event. All events have a timestamp
object which contains the time.time()
value when the event
was queued.
There are currently 3 events which can be delivered:
VNCEventKey
: (namekey
)
This event delivers a key press or release event.- Property
key
: Contains the VNC key symbol codes for the event. These are largely the ASCII codes with some special values for control keys. Consult theevents.py
source for these constants. - Property
down
:True
if the event is for a key press,False
if the event is for a key release. The event may be retriggered if auto-repeat is enabled on the client system, resulting in multipleTrue
events being delivered before aFalse
indicates the release.
- Property
VNCEventMove
: (namemove
)
This event delivers a pointer movement.- Property
x
,y
: Contains the coordinates of the pointer within the frame buffer, as positive pixel offsets from the top left corner. - Property
buttons
: Contains a bitmask of the mouse buttons which are currently active. Bit 0 corresponds to the first mouse button. Protocol limitations mean that only the first 8 buttons will be delivered reliably by this event.
- Property
VNCEventClick
: (nameclick
)
This event delivers a pointer click or release; it is the pointer analogue of the key event.- Property
x
,y
: Contains the coordinates of the pointer within the frame buffer, as positive pixel offsets from the top left corner. - Property
button
: Contains the mouse button number, starting from 0 for the first button. - Property
down
:True
if the event is for a click,False
if the event is for a release.
- Property
In all the examples they are listening on VNC display 2 (port 5902).
Note: For reasons that are unclear, it is impossible to connect to a VNC without a password using the macOS Screen Sharing tool. For these examples without a password, you are recommended to use a different client.
The basic usage of the VNC server is that you have a static Cairo surface and you draw to it in on one thread, and on another thread the VNC server runs. This example has a simple animation that shows coloured squares (one bounces), and a bezier curve which has a moving control point.
python example_animation.py
If it is necessary to change the size of the surface, the VNC server must be informed of
this fact. This is done by calling the change_surface
method on the server, supplying
the new surface. This will issue the necessary messages to the clients which are connected
to notify them that the surface contents have changed, and send the new frame buffer at
the next request.
The following example is similar to the basic example, above, but every 2 seconds the surface is changed in size.
python example_new_surface.py
Whilst the surface is being updated by the animation thread, the surface might need to be supplied to a VNC client. In the prior examples, there was no consideration for this, which might mean that partial frame content was delivered to the VNC clients. To prevent this from happening, it is possible to use a lock around updates to the surface. This ensures that only whole frames are delivered to the client.
The following example adds this locking to the surfaces.
python example_locking.py
Passwords can be supplied for connection to the server. It is required to have the des
module installed for passwords to be supported. It is possible to specify two passwords
for the server - the standard password which uses the options as supplied, and a
'read only' password, which will enable the 'read_only' option if it is used.
The following example adds passwords to the server. The passwords are password
and
readonly
.
python example_password.py
The server can accept key and pointer events (when not in 'read only' mode). These
events are placed on a queue and can be read out by the animator. Events will only
be delivered for clients which are not 'read only', so by setting the read_only
option, the events will never be delivered.
Note: If the events are not read by the animator, the VNC clients will block waiting for the queues to drain.
The following example adds input events, allowing one of the control points to be moved with the pointer, and changing the colour of the squares when the mouse buttons are clicked.
python example_input.py
The VNC protocol is client-driven. This means that the client is meant to request frames at a rate that is appropriate for it. The server is then meant to service those requests at a speed that is appropriate to it. This should result in the framerate being the minimum of the two ends, allowing for network bandwidth.
However, some clients don't do this. For example, the Apple Screen Sharing client sends a single FrameUpdateRequest and then does nothing further (unless the size of the desktop is changed). This means that a strictly compliant server will show only the first frame to such clients.
'Push frames' are frames that are sent unsolicited to the client - a protocol
violation, but one which appears necessary for at least the Apple Screen Sharing
Client. To use push frames, it is necessary to inform the CairoVNCServer
object that the frame has changed. This is achieved by the change_frame
method.
Only clients that are recognised as requiring push frames will be affected by
this flag. However, they can be explicitly enabled for all clients by setting
the push_requests
flag in the options. Clients which are requesting frames from
the server through the normal mechanism will be unaffected by these notifications.
The following example adds the push frame support to the input example. It also
removes the surface changes, as this obscures the effect of push frames. Because
this example is intended for use with the Apple Screen Sharing client, which does
not support unauthenticated connections, there is also a password of pass
on
the example.
python example_push.py
Whilst debugging, and during development, it is often useful to know what has
happened for each client. This is internally handled through the client_log
methods
in the server, and can be exposed by enabling the verbose
option.
python example_verbose.py
The desktop name is used to identify what the server calls the desktop that is being
served. It can be supplied on creation of the server, in the options object or as
a parameter. The name can be changed whilst the server is running by calling the
change_name
method on the server object, with the new name.
Support for changing the deslktop name is not universal, and some clients will remain with the name which was initially delivered.
python example_name.py
There are no test suites as yet. It's all manually tested, I'm afraid.