VNCDOTOOL(1)

NAME
vncdotool - VNCDoTool Documentation

INTRODUCTION

            target
                <https://pypi.python.org/pypi/vncdotool>

            alt Python Versions
[image: Actions Status] [image]
 <https://github.com/sibson/vndotool/actions>[image: ReadTheDocs] [image]
 <https://vncdotool.readthedocs.io/en/latest/>[image: Code style: black] [image]
 <https://github.com/psf/black>.SH VNCDOTOOL

vncdotool is a command line VNC client. It can be useful to automating interactions with virtual machines or hardware devices that are otherwise difficult to control.

It's under active development and seems to be working, but please report any problems you have.

  Quick Start

To use vncdotool you will need a VNC server. Most virtualization products include one, or use RealVNC, TightVNC or clone your Desktop using x11vnc.

Once, you have a server running you can install vncdotool from pypi:

            pip install vncdotool

and then send a message to the vncserver with:

            vncdo -s vncserver type "hello world"

The vncserver argument needs to be in the format address[:display|::port]. For example:

            # connect to 192.168.1.1 on default port 5900
            vncdo -s 192.168.1.1 type "hello world"

            # connect to localhost on display :3 (port 5903)
            vncdo -s localhost:3 type "hello world"

            # connect to myvncserver.com on port 5902 (two colons needed)
            vncdo -s myvncserver.com::5902 type "hello world"

            # connect via IPv6 to localhost on display :3 (port 5903)
            vncdo -s '[::1]:3' type "hello IPv6"
            #         ^   ^ mind those square brackets around the IPv6 address

You can also take a screen capture with:

            vncdo -s vncserver capture screen.png

More documentation can be found on Read the Docs <http://vncdotool.readthedocs.org>.

  Feedback

If you need help getting VNCDoTool working try the community at Stackoverflow <https://stackoverflow.com/questions/ask?tags=vncdotool>.

Patches, and ideas for improvements are welcome and appreciated, via GitHub <http://github.com/sibson/vncdotool> issues. If you are reporting a bug or issue please include the version of both vncdotool and the VNC server you are using it with.

  Acknowledgements

Thanks to Chris Liechti, techtonik and Todd Whiteman for developing the RFB and DES implementations used by vncdotool. Also, to the TigerVNC <http://sourceforge.net/apps/mediawiki/tigervnc/index.php?title=Main_Page> project for creating a community focus RFB specification document

INSTALLATION

vncdotool is available on PyPI <https://pypi.python.org/pypi>, so in most cases you should be able to simply run:

            pip install vncdotool

vncdotool relies on a number of libraries, the two major ones are Pillow <https://python-pillow.org/>, the Python Imaging Library and Twisted <https://twistedmatrix.com/>, an asynchronous networking library. While vncdotool should work with any recent version of these libraries sometimes things break. If you are having issues getting things to work you can try using a stable set of libraries and if you aren't already using it, and you should be, use a virtualenv <https://virtualenv.pypa.io/>.:

            pip install virtualenv
            virtualenv venv-vncdotool
            # XXX requirements.txt from vncdotool source tree
            pip install -r requirements.txt
            pip install -e .

  Windows

If you are not familiar with Python, the most reliable way to install vncdotool is to use binary packages.

            1.  Download Python, current 64-bit Windows Version, from Official Python <https://python.org/downloads/> website <https://www.python.org/downloads/>

            2.  Start the installation > On the first screen of the installer...

                        •   Check the box for "Add Python to PATH",

                        •   Any other check boxes can be left checked or unchecked per their default

                        •   Click "Customize installation"

                        •   On the Optional Features install page, no changes are needed

                        •

                            On the Advanced Options page...

                                •   Make sure the boxes for "Install for all users" and "Add Python to environment variables" are checked

                                •   Uncheck "Precompile standard library" unless needed for other projects

                                •   Any other check boxes can be left checked or unchecked per their default

                        •   Click "Install" at the bottom

            3.

                Open an elevated Windows PowerShell console:

                    •   Type "PowerShell" (without quotes) in the search field of the Windows taskbar

                    •   Click "Run as administrator" in the right pane above the search field

                    •   Type or paste each of the following lines below, pressing Enter after each line and waiting for each line to process (change “Python39” to different version as applicable):

                                [Environment]::SetEnvironmentVariable("Path", "$env:Path;C:\Program Files\Python39\;C:\Program Files\Python39\Scripts\", "User")
                                python -m pip install --upgrade pip
                                pip install vncdotool

            4.  At a Powershell prompt:

                        vncdo.exe --server som.eip.add.res type "Hello World"

            5.  If Hello World shows up on the remote machine that has a VNC server running then its time to celebrate. Otherwise, first check you can connect from your local machine to the remote using a normal GUI VNC Client. Once you get the normal GUI client working try vncdotool again and if you still have problems try the community at Stackoverflow <https://stackoverflow.com/questions/ask?tags=vncdotool>.

USAGE

  Basic Usage

Once installed you can use the vncdotool command to send key-presses. Alphanumerics are straightforward just specify the character. For other keys longer names are used:

            > vncdo key a
            > vncdo key 5
            > vncdo key .
            > vncdo key enter
            > vncdo key shift-a
            > vncdo key ctrl-C
            > vncdo key ctrl-alt-del

To type longer strings when entering data or commands you can use the type c command, which does not support special characters:

            > vncdo type "hello world"

You can control the mouse pointer with move and click commands. NOTE, you should almost always issue a move before a click, as in:

            > vncdo move 100 100 click 1

The following would seem to be equivalent but would actually click at (0, 0). This occurs due to how click events are encoded by VNC, meaning you need to initialise the position of the mouse.:

            > vncdo move 100 100
            > vncdo click 1

If you have the Python Imaging Library (Pillow <http://www.pythonware.com/products/pil>) installed you can also make screen captures of the session:

            > vncdo capture screenshot.png

With Pillow <http://www.pythonware.com/products/pil> installed, you can wait for the screen to match a known image:

            > vncdo expect somescreen.png 0

Putting it all together you can specify multiple actions on a single command line. You could automate a login with the following:

            > vncdo type username key enter expect password_prompt.png
            > vncdo type password move 100 150 click 1 expect welcome_screen.png

Sometimes you only care about a portion of the screen, in which case you can use rcapture and rexpect. For instance, if your login window appears at x=100, y=200 and is 400 pixels wide by 250 high you could do:

            > vncdo rcapture region.png 100 200 400 250
            > vncdo rexpect region.png 100 200 0

  Running Scripts

For more complex automation you can read commands from stdin or a file. The file format is simply a collection of actions:

            > echo "type hello" | vncdo -

Or if you had a file called login.vdo with the following content:

            # select the name text box, enter your name and submit
            move 100 100 click 1 type "my name" key tab key enter

            # grab the result
            capture screenshot.png

You could run it with the following command:

            > vncdo login.vdo

  Creating Scripts

While you can create scripts by hand it can often be a time consuming process. To make the process easier vncdotool provides a log mode that allows a user to record a VNC session to a script which is playable by vncdo. vnclog act as a man-in-the-middle to record the VNC commands you issue with a client. So you will have your vnclog connect to your server and your viewer connect to vnclog
        vncviewer ---> vnclog ---> vncserver

For best results be sure to set your vncviewer client to use the RAW encoding. Others encoding may work but are not fully supported at this time.

The quickest way to get started is to run:

            > vnclog --viewer vncviewer keylog.vdo

For more control you can launch the viewer separately but be sure to connect to the correct ports:

            > vnclog keylog.vdo
            > vncviewer localhost:2  # do something and then exit viewer
            > vncdo keylog.vdo

By running with --forever vnclog will create a new file for every client connection and record each clients activity. This can be useful for quickly recording a number of testcases.:

            > vnclog --forever --listen 6000 /tmp
            > vncviewer localhost::6000
            # do some stuff then exit and start new session
            > vncviewer localhost::6000
            # do some other stuff
            > ls /tmp/*.vdo

EMBEDDING IN PYTHON APPLICATIONS

vncdotool is built with the Twisted <http://twistedmatrix.com/> framework, as such it best integrates with other Twisted Applications. Rewriting your application to use Twisted may not be an option, so vncdotool provides a compatibility layer. It uses a separate thread to run the Twisted reactor and communicates with the main program using a thread-safe Queue.

Warning:
        While the Twisted reactor runs as a daemon thread, the reactor itself will start additional worker threads, which are no daemon threads. Therefore the Reactor must be shut down explicitly by calling vncdotool.api.shutdown() <#vncdotool.api.shutdown>. Otherwise your application will not terminate as those worker threads remain running in the background.

        This also applied when using the API as a context manager: As the reactor cannot be restarted, it is a design decision to not shut it down as the end of the context. That would prevent the API from being used multiple times in the same process.

To use the synchronous API you can do the following:

            from vncdotool import api
            client = api.connect('vncserver', password=None)

The first argument passed to the connect() <#vncdotool.api.connect> method is the VNC server to connect to, and it needs to be in the format address[:display|::port]. For example:

            # connect to 192.168.1.1 on default port 5900
            client = api.connect('192.168.1.1', password=None)

            # connect to localhost on display :3 (port 5903)
            client = api.connect('localhost:3', password=None)

            # connect to myvncserver.com on port 5902 (two colons needed)
            client = api.connect('myvncserver.com::5902', password=None)

You can then call any of the methods available on vncdotool.client.VNCDoToolClient <#vncdotool.client.VNCDoToolClient> and they will block until completion. For example:

            client.captureScreen('screenshot.png')
            client.keyPress('enter')
            client.expectScreen('login_success.png', maxrms=10)

It is possible to set a per-client timeout in seconds to prevent calls from blocking indefinitely.

            client.timeout = 10
            try:
                client.captureScreen('screenshot.png')
            except TimeoutError:
                print('Timeout when capturing screen')

In case of too many timeout errors, it is recommended to reset the client connection via the disconnect and connect() <#vncdotool.api.connect> methods.

The vncdotool.client.VNCDoToolClient <#vncdotool.client.VNCDoToolClient> supports the context manager protocol.

            with api.connect('vnchost:display') as client:
                client.captureScreen('screenshot.png')

The synchronous API can be used to automate the starting of a Virtual Machine or other application:

            vmtool.start('myvirtualmachine.img')
            client.connect('vmaddress::5950')
            client.expectScreen('booted.png')
            for k in 'username':
                client.keyPress(k)
            client.keyPress('enter')
            for k in 'password':
                client.keyPress(k)
            client.keyPress('enter')
            client.expectScreen('loggedin.png')
            client.disconnect()

            # continue with your testing session or other work

COMMAND REFERENCE

  click BUTTON

  capture FILENAME.PNG

  drag X Y

  expect FILENAME.PNG FUZZ

  key KEY

  keydown KEY

  keyup KEY

  move X Y

  mousedown BUTTON

  mousemove X Y

  mouseup BUTTON

  pause SECONDS

  rcapture FILENAME.PNG X Y W H

  rexpect FILENAME.PNG X Y FUZZ

  type STRING

RELEASE HISTORY

  1.3.0 (2026-04-03)

            •   Fix functional test suite (@phahn)

            •   Python 3.12 is supported, Python 3.7 support removed (@phahn)

            •   Improve documentation (@phahn)

            •   Improve PEP-484 type hinting (@phahn)

            •   Fix mouse dragging (@phahn)

            •   Improve special key handling, fix key-down/key-up discrepancy with force_caps (@phahn, #270)

            •   Allow specifying a format with captureScreen (@erjiang, #293)

            •   Allow input literal - via client.keyPress with minus keyword (#302)

            •   Fix: typefile and pastefile now accept - as filename for stdin (#307)

            •   Switch from pycryptodomex to cryptography.io (@geofft, #278)

            •   Remove transitive dependency zope.interface (#298)

            •   Bump minimum Pillow version to 10.0.1 (#312)

  1.2.0 (2023-06-06)

            •   fixes for api.shutdown and disconnect raise exceptions, #256

  1.1.0 (2023-04-01)

Huge thanks to @pmhahn for single handedly driving conversion to modern Python3, as well as cleaning up a ton of outstanding issues.

            •   [BREAKING] drop python 2.x support, thanks @pmhahn

            •   Use built-in Unittest and mock for testing

            •   PEP-484 type hinting, thanks @pmhahn

            •   Doc improvements, thanks @luke-jr, @pmhahn, @samiraguiar

            •   Test for byte handling, thanks @ponty, refs #177

            •   Internal implementation of DES replaced by PyCrotodomeX

            •   Support for Apple Remote Desktop (ARD), thanks @andywgrant, @pmhahn

            •   Support for pseudo-encoding LastRec, thanks @pmhahn

            •   Support for Extended QEMU Key Events, thanks @pmhahn

            •   Support IPv6 addresses for server connection, thanks @pmhahn

            •   Bugfix, use configured log outputs over stdout, thanks @pevogam

            •   Bugfix, handle invalid password, thanks @dozysun

            •   Bugfixes for loggingproxy, thanks @joachimmetz, @pmhahn, @guicho271828

  1.0.0 (2020-04-10)

            •   add ZRLE encoding, thanks Adrian Weiler

            •   drop python2 support

            •   fix mouseDrag behaviour, thanks Samir Aguiar

  0.13.0 (2019-11-21)

            •   new flag --incremental-refreshes, increased compatibility of capture, thanks Amir Rossert

            •   exit non-zero and print to stderr for unknown commands, thanks Amir Rossert

  0.12.1 (2018-12-06)

            •   bugfix expectRegion to use cropped images for compare, thanks Michael Fürnschuß

            •   direct support for building RPMs, thanks Plamen Dimitrov

  0.12.0 (2018-04-07)

            •   connect via UNIX sockets, thanks Matteo Cafasso

            •   bugfix, XTightVNC initial connection, thanks Antti Kervinen

  0.11.2 (2017-09-24)

            •   fix version metadata, thanks Kevin Gottsman

  0.11.1 (2017-07-23)

            •   add api.client.disconnect()

            •   fix python2.x compatibility, thanks Ostrosablin Vitaly

  0.11.0 (2017-06-09)

            •   enable PSEUDO_DESKTOP_SIZE_ENCODING by default to allow desktop resizing, thanks rebasegod

            •   python 3.0 support, thanks jamtwister

            •   added pastefile command, thanks Rogan Dawes

            •   debian packaging improvements, thanks Alexander Kläser

            •   fix loggingproxy, thanks Matthias Weckbecker

  0.10.0 (2016-03-03)

            •   drop official 2.6 support, it'll probably work for a while still

            •   use frombytes rather than fromstring for compatibility with PIL

            •   vnclog works with password protected servers using --password-required

            •   exit more reliably after an error

            •   use increatmental frameBufferUpdateRequests, appears to be compatible with more servers

            •   include basic version negotiation with servers, thanks Ezra Bühler

  0.9.0 (2015-05-08)

            •   add special keys [~!@#$%^&*()_+{}|:"<>?] to --force-caps, for servers that don't handle them, Tyler Oderkirk, Aragats Amirkhanyan

            •   improve vnclog performance with TCP_NODELAY, Ian Britten

            •   by default pause 10ms between sending commands, better compatibility with servers

            •   better handle screen resizing, Daniel Stelter-Gliese

            •   API, fix deadlocks due to threaded init of PIL, thanks Antti Kervinen

            •   API, support password protected server, thanks Antti Kervinen

            •   API, able to connect to multiple servers, Daniel Stelter-Gliese

            •   drop official support for py2.4 and py2.5

            •   use Pillow rather than PIL

Thanks to Jan Sedlák, Daniel Stelter-Gliese, Antti Kervinen, Anatoly Techtonik, Tyler Oderkirk and Aragats Amirkhanyan for helping make this release possible

  0.8.0 (2013-08-06)

            •   improved documentation using sphinx

            •   regional capture and expect that operate on a portion of the display

            •   --force-caps, better compatibility when sending UPPERCASE to servers

            •   --timeout, exit with an error after a given number of seconds

            •   experimental synchronous API for easier integration with non-Twisted apps

  0.3.0 (2012-12-22)

            •   main program renamed to vncdo, vncdotool continues an alias for now

            •   use host:display, host::port syntax like other vnc tools, removed -d

            •   read/play commands from stdin or file

            •   vnclog, creates scripts from captured interactive sessions

            •   better control over mouse in screen captures with --nocursor and --localcursor

            •   mousemove, sleep command aliases to match xdotool

            •   keyup/keydown commands for more control over keypresses

            •   send SetEncodings on connect, thanks Matias Suarez for fix

            •   debian packaging

            •   type "Hello World" now preserves capitalization

            •   basic compatibility with VNC 4.0 servers, found in some KVMs

            •   improved frameUpdate handling

            •   --warp to replay script faster than real-time

            •   --delay, insert a delay between sending commands

  0.2.0 (2012-08-07)

            •   add pause, mouseup, mousedown, drag commands

            •   only require Twisted 11.1.0, so we can have py2.4 support

            •

                bugfixes, thanks Christopher Holm for reporting

                    •   vncdotool type -something now works

                    •   no longer silently fail for unsupported image formats

  0.1.1 (2011-05-18)

            •   add PIL to requires

            •   fix bug where incorrect mouse button is sent

  0.1.0 (2011-03-03)

            •   first release

            •   commands: press, type, move, click, capture, expect

CONTRIBUTING

Code and Issue tracking is provided by Github <https://github.com/sibson/vncdotool>. There is also a mailing list setup via Google Groups <https://groups.google.com/forum/#!forum/vncdotool>.

CODE DOCUMENTATION

  api Module

Helpers to allow vncdotool to be intergrated into other applications.

Warning:
        EXPERIMENTAL. This feature is under development, your help testing and debugging is appreciated.

    vncdotool.api.connect(server: str) -> ThreadedVNCClientProxy

    vncdotool.api.connect(server: str, password: str | None) -> ThreadedVNCClientProxy

    vncdotool.api.connect(server: str, password: str | None, factory_class: type[VNCDoToolFactory <#vncdotool.client.VNCDoToolFactory>]) -> ThreadedVNCClientProxy

    vncdotool.api.connect(server: str, password: str | None, factory_class: type[VNCDoToolFactory <#vncdotool.client.VNCDoToolFactory>], proxy: type[TProxy]) -> TProxy

    vncdotool.api.connect(server: str, password: str | None, factory_class: type[VNCDoToolFactory <#vncdotool.client.VNCDoToolFactory>], proxy: type[TProxy], timeout: float | None) -> TProxy

    vncdotool.api.connect(server: str, password: str | None, factory_class: type[VNCDoToolFactory <#vncdotool.client.VNCDoToolFactory>], proxy: type[TProxy], timeout: float | None, username: str | None) -> TProxy
        Connect to a VNCServer and return a Client instance that is usable in the main thread of non-Twisted Python Applications,

            >>> from vncdotool import api
            >>> with api.connect('host') as client
            >>>     client.keyPress('c')

        You may then call any regular VNCDoToolClient method on client from your application code.

        If you are using a GUI toolkit or other major async library please read Choosing a Reactor and GUI Toolkit Integration <https://docs.twistedmatrix.com/en/stable/core/howto/choosing-reactor.html> for a better method of intergrating vncdotool.

    vncdotool.api.shutdown() -> None
        Shutdown background thread running Twisted reactor.

  client Module

Twisted based VNC client protocol and factory.

    exception vncdotool.client.AuthenticationError
        Bases: VNCDoException

        VNC Server requires Authentication

    class vncdotool.client.VMWareClient
        Bases: VNCDoToolClient

        SINGLE_PIXLE_UPDATE = b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00'

        dataReceived(data: bytes) -> None
            Called whenever data is received.

            Use this method to translate to a higher-level message. Usually, some callback will be made upon the receipt of each complete protocol message.

            @param data: a string of indeterminate length. Please keep in mind
                that you will probably need to buffer some data, as partial (or multiple) protocol messages may be received! I recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.

    class vncdotool.client.VMWareFactory
        Bases: VNCDoToolFactory

        protocol
            alias of VMWareClient

    exception vncdotool.client.VNCDoException
        Bases: Exception

    class vncdotool.client.VNCDoToolClient
        Bases: RFBClient

        MAX_DESKTOP_SIZE = 65536

        SPECIAL_KEYS_US = '~!@#$%^&*()_+{}|:"<>?'

        bell()

        buttons = 0

        captureRegion(fp: str | Path | IO[bytes], x: int, y: int, w: int, h: int, incremental: bool = False) -> Deferred
            Save a region of the current display to filename

        captureScreen(fp: str | Path | IO[bytes], incremental: bool = False, format: str | None = None) -> Deferred
            Capture and save the current VNC screen display to a file.

            Parameters:

                fp (TFile): The destination where the screenshot will be saved.
                    It can be a string path, a pathlib.Path object, or a file-like object opened in binary mode.

                incremental (bool, optional):

                    •   False (default): Captures the entire screen.

                    •   True: Captures only the regions of the screen that have changed since the last capture.

                format (str | None, optional):

                    •   See Pillow's list of image formats: <https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html>

                    •   If set to None, Pillow will determine the format based on the provided file name.

        cmask: Image | None = None

        commitUpdate(rectangles: list[Tuple[int, int, int, int]] | None = None) -> None
            called after a series of updateRectangle(), copyRectangle() or fillRectangle() are finished.

            Typicaly, here is the place to request the next screen update with framebufferUpdateRequest() with incremental=True.

            Parameters
                rectangles -- a list of tuples (x,y,w,h) with the updated rectangles.

        connectionLost(reason: Failure) -> None
            Called when the connection is shut down.

            Clear any circular references here, and any external references to this Protocol. The connection has been closed.

            @type reason: L{twisted.python.failure.Failure}

        connectionMade() -> None
            Called when a connection is made.

            This may be considered the initializer of the protocol, because it is called when the connection is completed. For clients, this is called once the connection to the server has been established; for servers, this is called after an accept() call stops blocking and a socket has been received. If you need to send any greeting or initial message, do it here.

        copy_text(text: str) -> None
            The server has new ISO 8859-1 (Latin-1) text in its cut buffer. (aka clipboard)

        cursor: Image | None = None

        deferred: Deferred | None = None

        drawCursor() -> None

        encoding = 0

        expectRegion(filename: str, x: int, y: int, maxrms: float = 0) -> Deferred
            Wait until a portion of the screen matches the target image

            The region compared is defined by the box (x, y), (x + image.width, y + image.height)

        expectScreen(filename: str, maxrms: float = 0) -> Deferred
            Wait until the display matches a target image

            Parameters

                •   filename -- an image file to read and compare against.

                •   maxrms -- the maximum root mean square between histograms of the screen and target image.

        image_mode = 'RGBX'

        keyDown(key: str) -> TClient

        keyPress(key: str) -> TClient
            Send a key press to the server

            Parameters
                key -- either [a-z] or a from KEYMAP.

        keyUp(key: str) -> TClient

        mouseDown(button: int) -> TClient
            Send a mouse button down at the last set position

            Parameters
                button -- [1-n]

        mouseDrag(x: int, y: int, step: int = 1) -> Iterator[Deferred]
            Move the mouse point to position (x, y) in increments of step

        mouseMove(x: int, y: int) -> TClient
            Move the mouse pointer to position (x, y)

        mousePress(button: int) -> TClient
            Send a mouse click at the last set position

            Parameters
                button -- [1-n]

        mouseUp(button: int) -> TClient
            Send mouse button released at the last set position

            Parameters
                button -- [1-n]

        paste(message: str) -> TClient

        pause(duration: float) -> Deferred

        refreshScreen(incremental: bool = False) -> Deferred

        screen: Image | None = None

        setImageMode() -> None
            Check support for PixelFormats announced by server or select client supported alternative.

        updateCursor(x: int, y: int, width: int, height: int, image: bytes, mask: bytes) -> None
            New cursor, focuses at (x, y)

        updateDesktopSize(width: int, height: int) -> None
            New desktop size of width*height.

        updateRectangle(x: int, y: int, width: int, height: int, data: bytes) -> None
            new bitmap data.

            Parameters
                data -- bytes in the pixel format set up earlier.

        vncConnectionMade() -> None
            connection is initialized and ready. typicaly, the pixel format is set here.

        vncRequestPassword() -> None
            a password is needed to log on, use sendPassword() to send one.

        x = 0

        y = 0

    class vncdotool.client.VNCDoToolFactory
        Bases: RFBFactory

        clientConnectionFailed(connector: <InterfaceClass twisted.internet.interfaces.IConnector>, reason: Failure) -> None
            Called when a connection has failed to connect.

            It may be useful to call connector.connect() - this will reconnect.

            @type reason: L{twisted.python.failure.Failure}

        clientConnectionLost(connector: <InterfaceClass twisted.internet.interfaces.IConnector>, reason: Failure) -> None
            Called when an established connection is lost.

            It may be useful to call connector.connect() - this will reconnect.

            @type reason: L{twisted.python.failure.Failure}

        clientConnectionMade(protocol: VNCDoToolClient <#vncdotool.client.VNCDoToolClient>) -> None

        force_caps = False

        last_rect = True

        nocursor = False

        password: str | None = None

        protocol
            alias of VNCDoToolClient

        pseudocursor = False

        pseudodesktop = True

        qemu_extended_key = True

        shared = True

        username: str | None = None

    vncdotool.client.factory_connect(factory: VNCDoToolFactory <#vncdotool.client.VNCDoToolFactory>, host: str, port: int, family: AddressFamily) -> None

  rfb Module

RFB protocol implementattion, client side.

Override RFBClient and RFBFactory in your application. See vncviewer.py for an example.

Reference: <http://www.realvnc.com/docs/rfbproto.pdf>

    class vncdotool.rfb.AuthTypes(*values)
        Bases: IntEnumLookup

        RFC 6143 <https://datatracker.ietf.org/doc/html/rfc6143.html> §7.1.2. Security Handshake.

        APPLE_31 = 31

        APPLE_32 = 32

        APPLE_33 = 33

        APPLE_34 = 34

        APPLE_35 = 35

        DIFFIE_HELLMAN = 30

        INTEGRATED_SSH = 24

        INVALID = 0

        MD5 = 21

        MSLOGON2 = 113

        NONE = 1

        REALVNC_10 = 10

        REALVNC_11 = 11

        REALVNC_12 = 12

        REALVNC_128 = 128

        REALVNC_131 = 131

        REALVNC_132 = 132

        REALVNC_134 = 134

        REALVNC_14 = 14

        REALVNC_15 = 15

        REALVNC_192 = 192

        REALVNC_3 = 3

        REALVNC_4 = 4

        REALVNC_7 = 7

        REALVNC_8 = 8

        REALVNC_9 = 9

        RSA_AES = 5

        RSA_AES256 = 129

        RSA_AES256_2STEP = 133

        RSA_AES256_UNENCRYPTED = 130

        RSA_AES_2STEP = 13

        RSA_AES_UNENCRYPTED = 6

        SASL = 20

        SECURE_TUNNEL = 23

        TIGHT = 16

        TLS = 18

        ULTRA = 17

        VENCRYPT = 19

        VNC_AUTHENTICATION = 2

        XVP = 22

    class vncdotool.rfb.Encoding(*values)
        Bases: IntEnumLookup

        encoding-type for setEncodings().

        APPLE_1000 = 1000

        APPLE_1001 = 1001

        APPLE_1002 = 1002

        APPLE_1011 = 1011

        APPLE_1100 = 1100

        APPLE_1101 = 1101

        APPLE_1102 = 1102

        APPLE_1103 = 1103

        APPLE_1104 = 1104

        APPLE_1105 = 1105

        CACHE = 4294901760

        CACHE_ENABLE = 4294901761

        CACHE_ZIP = 4294901767

        CAR_CONNECTIVITY_523 = -523

        CLIENT_REDIRECT = -311

        COPY_RECTANGLE = 1

        CORRE = 4

        ENABLE_KEEP_ALIVE = 4294934529

        FTP_PROTOCOl_VERSION = 4294934530

        H264 = 20

        HEXTILE = 5

        HITACHI_ZYWRLE = 17

        JPEG = 21

        JPEG_23 = -23

        JPEG_24 = -24

        JPEG_25 = -25

        JPEG_26 = -26

        JPEG_27 = -27

        JPEG_28 = -28

        JPEG_29 = -29

        JPEG_30 = -30

        JPEG_31 = -31

        JPEG_32 = -32

        JRLE = 22

        KEYBOARD_LED_STATE = 4294836224

        LIBVNCSERVER_0XFFFE0004 = 4294836228

        LIBVNCSERVER_219 = -219

        LIBVNCSERVER_220 = -220

        LIBVNCSERVER_221 = -221

        LIBVNCSERVER_222 = -222

        OLIVE_CALL_CONTROL = -310

        OPEN_H264 = 50

        PLUGIN_STREAMING = 3231835599

        POINTER_POS = -225

        POPA = -306

        PSEUDO_COMPRESSION_LEVEL_247 = -247

        PSEUDO_COMPRESSION_LEVEL_248 = -248

        PSEUDO_COMPRESSION_LEVEL_249 = -249

        PSEUDO_COMPRESSION_LEVEL_250 = -250

        PSEUDO_COMPRESSION_LEVEL_251 = -251

        PSEUDO_COMPRESSION_LEVEL_252 = -252

        PSEUDO_COMPRESSION_LEVEL_253 = -253

        PSEUDO_COMPRESSION_LEVEL_254 = -254

        PSEUDO_COMPRESSION_LEVEL_255 = -255

        PSEUDO_COMPRESSION_LEVEL_256 = -256

        PSEUDO_CONTINUOUS_UPDATES = -313

        PSEUDO_CURSOR = -239

        PSEUDO_CURSOR_WITH_ALPHA = -314

        PSEUDO_DESKTOP_NAME = -307

        PSEUDO_DESKTOP_SIZE = -223

        PSEUDO_EXTENDED_CLIPBOARD = 3231835598

        PSEUDO_EXTENDED_DESKTOP_SIZE = -308

        PSEUDO_FENCE = -312

        PSEUDO_GII = -305

        PSEUDO_JPEG_FINE_GRAINED_QUALITY_LEVEL = -412

        PSEUDO_JPEG_SUBSAMLING_LEVEL = -763

        PSEUDO_LAST_RECT = -224

        PSEUDO_QEMU_AUDIO = -259

        PSEUDO_QEMU_EXTENDED_KEY_EVENT = -258

        PSEUDO_QEMU_LED_STATE = -261

        PSEUDO_QEMU_POINTER_MODTION_CHANGE = -257

        PSEUDO_VMWARE_CURSOR = 1464686180

        PSEUDO_VMWARE_CURSOR_POSITION = 1464686182

        PSEUDO_VMWARE_CURSOR_STATE = 1464686181

        PSEUDO_VMWARE_DISPLAY_MODE_CHANGE = 1464686185

        PSEUDO_VMWARE_KEY_REPEAT = 1464686183

        PSEUDO_VMWARE_LED_STATE = 1464686184

        PSEUDO_VMWARE_VIRTUAL_MACHINE_STATE = 1464686186

        PSEUDO_XVO = -309

        PSEUDO_X_CURSOR = -240

        QEMU_262 = -262

        RAW = 0

        REAL_1024 = 1024

        RRE = 2

        SERVER_IDENTITY = 4294836227

        SERVER_STATE = 4294934528

        SESSION = 4294934531

        SOLID_COLOR = 4294901765

        SOL_MONO_ZIP = 4294901768

        SUPPORTED_ENCODINGS = 4294836226

        SUPPORTED_MESSAGES = 4294836225

        TIGHT = 7

        TIGHT_1 = -1

        TIGHT_226 = -226

        TIGHT_241 = -241

        TIGHT_33 = -33

        TIGHT_PNG = -260

        TRLE = 15

        ULTRA = 9

        ULTRA2 = 10

        ULTRA_ZIP = 4294901769

        VA_H264 = 1211250228

        VMWARE_0X574D5600 = 1464686080

        VMWARE_273 = -273

        XOR_ENABLE = 4294901766

        XOR_MONO_RECT_ZLIB = 4294901763

        XOR_MULTI_COLOR_ZLIB = 4294901764

        XOR_ZLIB = 4294901762

        ZLIB = 6

        ZLIBHEX = 8

        ZRLE = 16

        classmethod lookup(value: int) -> object

        static s32(value: int) -> int

    class vncdotool.rfb.HextileEncoding(*values)
        Bases: IntFlag

        RFC 6153 <https://datatracker.ietf.org/doc/html/rfc6153.html> §7.7.4. Hextile Encoding.

        ANY_SUBRECTS = 8

        BACKGROUND_SPECIFIED = 2

        FOREGROUND_SPECIFIED = 4

        RAW = 1

        SUBRECTS_COLORED = 16

    class vncdotool.rfb.IntEnumLookup(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)
        Bases: IntEnum

        classmethod lookup(value: int) -> object

    class vncdotool.rfb.MsgS2C(*values)
        Bases: IntEnumLookup

        RFC 6143 <https://datatracker.ietf.org/doc/html/rfc6143.html> §7.6. Server-to-Client Messages.

        BELL = 2

        CAR_CONNECTIVITY = 128

        END_OF_CONTINUOUS_UPDATES = 150

        FILE_TRANSFER = 7

        FRAMEBUFFER_UPDATE = 0

        GII_SERVER_MESSAGE = 253

        KEEP_ALIVE = 13

        KEY_FRAME_UPDATE = 5

        OLIVE_CALL_CONTROL = 249

        QEMU_SERVER_MESSAGE = 255

        RESIZE_FRAME_BUFFER_15 = 15

        RESIZE_FRAME_BUFFER_4 = 4

        SERVER_CUT_TEXT = 3

        SERVER_FENCE = 248

        SERVER_STATE = 173

        SET_COLOUR_MAP_ENTRIES = 1

        TEXT_CHAT = 11

        TIGHT = 252

        ULTRA_10 = 10

        ULTRA_12 = 12

        ULTRA_14 = 14

        ULTRA_6 = 6

        ULTRA_8 = 8

        ULTRA_9 = 9

        VMWARE_127 = 127

        VMWARE_254 = 254

        XVP_SERVER_MESSAGE = 250

    class vncdotool.rfb.PixelFormat(bpp: int = 32, depth: int = 24, bigendian: bool = False, truecolor: bool = True, redmax: int = 255, greenmax: int = 255, bluemax: int = 255, redshift: int = 0, greenshift: int = 8, blueshift: int = 16)
        Bases: object

        RFC 6143 <https://datatracker.ietf.org/doc/html/rfc6143.html> §7.4. Pixel Format Data Structure.

        STRUCT: ClassVar = Struct('!BB??HHHBBBxxx')

        VALIDATE: ClassVar = False

        bigendian: bool = False

        bluemax: int = 255

        blueshift: int = 16

        bpp: int = 32

        property bypp: int

        depth: int = 24

        classmethod from_bytes(block: bytes) -> PixelFormat <#vncdotool.rfb.PixelFormat>

        greenmax: int = 255

        greenshift: int = 8

        redmax: int = 255

        redshift: int = 0

        to_bytes() -> bytes

        truecolor: bool = True

    class vncdotool.rfb.RFBClient
        Bases: Protocol

        MAX_CLIENT_VERSION = (3, 8)

        SUPPORTED_AUTHS = {AuthTypes.NONE, AuthTypes.VNC_AUTHENTICATION, AuthTypes.DIFFIE_HELLMAN}

        SUPPORTED_ENCODINGS = {Encoding.PSEUDO_QEMU_EXTENDED_KEY_EVENT, Encoding.PSEUDO_CURSOR, Encoding.PSEUDO_LAST_RECT, Encoding.PSEUDO_DESKTOP_SIZE, Encoding.RAW, Encoding.COPY_RECTANGLE, Encoding.RRE, Encoding.CORRE, Encoding.HEXTILE, Encoding.ZRLE}

        SUPPORTED_SERVER_VERSIONS = {(3, 3), (3, 7), (3, 8), (3, 889), (4, 0), (4, 1), (5, 0)}

        ardRequestCredentials() -> None

        beginUpdate() -> None
            called before a series of updateRectangle(), copyRectangle() or fillRectangle().

        bell()

        property bypp: int

        clientCutText(message: str) -> None
            The client has new ISO 8859-1 (Latin-1) text in its cut buffer. (aka clipboard)

        commitUpdate(rectangles: list[Tuple[int, int, int, int]] | None = None) -> None
            called after a series of updateRectangle(), copyRectangle() or fillRectangle() are finished.

            Typicaly, here is the place to request the next screen update with framebufferUpdateRequest() with incremental=True.

            Parameters
                rectangles -- a list of tuples (x,y,w,h) with the updated rectangles.

        copyRectangle(srcx: int, srcy: int, x: int, y: int, width: int, height: int) -> None
            used for copyrect encoding. copy the given rectangle (src, srxy, width, height) to the target coords (x,y)

        copy_text(text: str) -> None
            The server has new ISO 8859-1 (Latin-1) text in its cut buffer. (aka clipboard)

        dataReceived(data: bytes) -> None
            Called whenever data is received.

            Use this method to translate to a higher-level message. Usually, some callback will be made upon the receipt of each complete protocol message.

            @param data: a string of indeterminate length. Please keep in mind
                that you will probably need to buffer some data, as partial (or multiple) protocol messages may be received! I recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.

        expect(handler: Callable[[...], None], size: int, *args: Any, **kwargs: Any) -> None

        fillRectangle(x: int, y: int, width: int, height: int, color: bytes) -> None
            fill the area with the color.

            Parameters
                color -- bytes in the pixel format set up earlier.

        framebufferUpdateRequest(x: int = 0, y: int = 0, width: int | None = None, height: int | None = None, incremental: bool = False) -> None

        keyEvent(key: int, down: bool = True) -> None
            For most ordinary keys, the "keysym" is the same as the corresponding ASCII value. Other common keys are shown in the KEY_ constants.

        pointerEvent(x: int, y: int, buttonmask: int = 0) -> None
            Indicates either pointer movement or a pointer button press or release. The pointer is now at (x-position, y-position), and the current state of buttons 1 to 8 are represented by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed).

        sendPassword(password: str) -> None
            send password

        setEncodings(list_of_encodings: Collection[Encoding <#vncdotool.rfb.Encoding>]) -> None

        setPixelFormat(pixel_format: PixelFormat <#vncdotool.rfb.PixelFormat>) -> None

        set_color_map(first: int, colors: list[tuple[int, int, int]]) -> None
            The server is using a new color map.

        updateCursor(x: int, y: int, width: int, height: int, image: bytes, mask: bytes) -> None
            New cursor, focuses at (x, y)

        updateDesktopSize(width: int, height: int) -> None
            New desktop size of width*height.

        updateRectangle(x: int, y: int, width: int, height: int, data: bytes) -> None
            new bitmap data.

            Parameters
                data -- bytes in the pixel format set up earlier.

        vncAuthFailed(reason: Failure) -> None
            called when the authentication failed. the connection is closed.

        vncConnectionMade() -> None
            connection is initialized and ready. typicaly, the pixel format is set here.

        vncRequestPassword() -> None
            a password is needed to log on, use sendPassword() to send one.

    class vncdotool.rfb.RFBFactory(password: str | None = None, shared: bool = False)
        Bases: ClientFactory

        A factory for remote frame buffer connections.

        protocol
            alias of RFBClient

  command Module

Command line interface to interact with a VNC Server.

    exception vncdotool.command.CommandParseError
        Bases: RuntimeError

    class vncdotool.command.ExitingProcess
        Bases: ProcessProtocol

        errReceived(data: bytes) -> None
            Some data was received from stderr.

        processExited(reason: Failure) -> None
            This will be called when the subprocess exits.

            @type reason: L{twisted.python.failure.Failure}

    exception vncdotool.command.TimeoutError
        Bases: RuntimeError

    class vncdotool.command.VNCDoCLIClient
        Bases: VNCDoToolClient

        vncRequestPassword() -> None
            a password is needed to log on, use sendPassword() to send one.

    class vncdotool.command.VNCDoCLIFactory
        Bases: VNCDoToolFactory

        clientConnectionFailed(connector: <InterfaceClass twisted.internet.interfaces.IConnector>, reason: Failure) -> None
            Called when a connection has failed to connect.

            It may be useful to call connector.connect() - this will reconnect.

            @type reason: L{twisted.python.failure.Failure}

        clientConnectionLost(connector: <InterfaceClass twisted.internet.interfaces.IConnector>, reason: Failure) -> None
            Called when an established connection is lost.

            It may be useful to call connector.connect() - this will reconnect.

            @type reason: L{twisted.python.failure.Failure}

        done(exit_code: int) -> None

        error(reason: Failure) -> None

        protocol
            alias of VNCDoCLIClient

    class vncdotool.command.VNCDoToolOptionParser(usage=None, option_list=None, option_class=<class 'optparse.Option'>, version=None, conflict_handler='error', description=None, formatter=None, add_help_option=True, prog=None, epilog=None)
        Bases: OptionParser

        format_help(formatter: HelpFormatter | None = None) -> str

    vncdotool.command.add_standard_options(parser: OptionParser) -> OptionParser

    vncdotool.command.build_command_list(factory: VNCDoCLIFactory <#vncdotool.command.VNCDoCLIFactory>, args: list[str], delay: float | None = None, warp: float = 1.0, incremental_refreshes: bool = False) -> None

    vncdotool.command.build_proxy(options: Values) -> VNCLoggingServerFactory <#vncdotool.loggingproxy.VNCLoggingServerFactory>

    vncdotool.command.build_tool(options: Values, args: list[str]) -> VNCDoCLIFactory <#vncdotool.command.VNCDoCLIFactory>

    vncdotool.command.log_connected(pcol: TClient) -> TClient

    vncdotool.command.log_exceptions(type_: type[BaseException], value: BaseException, tb: TracebackType | None) -> None

    vncdotool.command.parse_server(server: str) -> tuple[AddressFamily, str, int]

    vncdotool.command.setup_logging(options: Values) -> None

    vncdotool.command.vncdo() -> None

    vncdotool.command.vnclog() -> None

  loggingproxy Module

    class vncdotool.loggingproxy.MsgC2S(*values)
        Bases: IntEnumLookup

        RFC 6143 <https://datatracker.ietf.org/doc/html/rfc6143.html> §7.5. Client-to-Server Messages.

        CAR_CONNECTIVITY = 128

        CLIENT_CUT_TEXT = 6

        CLIENT_FENCE = 248

        ENABLE_CONTINUOUS_UPDATES = 150

        FILE_TRANSFER = 7

        FRAMEBUFFER_UPDATE_REQUEST = 3

        GII_CLIENT_MESSAGE = 253

        KEEP_ALIVE = 13

        KEY_EVENT = 4

        KEY_FRAME_REquest = 12

        NOTIFY_PLUGIN_STREAMING = 80

        OLIVE_CALL_CONTROL = 249

        POINTER_EVENT = 5

        QEMU_CLIENT_MESSAGE = 255

        REQUEST_SESSION = 20

        SET_DESKTOP_SIZE = 251

        SET_ENCODING = 2

        SET_PIXEL_FORMAT = 0

        SET_SCALE = 8

        SET_SCALE_FACTOR = 15

        SET_SERVER_INPUT = 9

        SET_SESSION = 21

        SET_SW = 10

        TEXT_CHAT = 11

        TIGHT = 252

        ULTRA_14 = 14

        ULTRA_16 = 16

        ULTRA_17 = 17

        ULTRA_18 = 18

        ULTRA_19 = 19

        VMWARE_127 = 127

        VMWARE_254 = 254

        XVP_CLIENT_MESSAGE = 250

    class vncdotool.loggingproxy.NullTransport
        Bases: object

        addressFamily = 0

        loseConnection() -> None

        setTcpNoDelay(enabled: bool) -> None

        write(data: bytes) -> None

        writeSequence(data: bytes) -> None

    exception vncdotool.loggingproxy.ProtocolError
        Bases: Exception

        VNC Protocol error

    class vncdotool.loggingproxy.QemuClientMessage(*values)
        Bases: IntEnumLookup

        QEMU Client Message <https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#qemu-client-message>

        AUDIO = 1

        EXTENDED_KEY_EVENT = 0

    class vncdotool.loggingproxy.RFBServer
        Bases: Protocol

        connectionMade() -> None
            Called when a connection is made.

            This may be considered the initializer of the protocol, because it is called when the connection is completed. For clients, this is called once the connection to the server has been established; for servers, this is called after an accept() call stops blocking and a socket has been received. If you need to send any greeting or initial message, do it here.

        dataReceived(data: bytes) -> None
            Called whenever data is received.

            Use this method to translate to a higher-level message. Usually, some callback will be made upon the receipt of each complete protocol message.

            @param data: a string of indeterminate length. Please keep in mind
                that you will probably need to buffer some data, as partial (or multiple) protocol messages may be received! I recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.

        handle_clientCutText(block: bytes) -> None

        handle_framebufferUpdate(x: int, y: int, w: int, h: int, incremental: bool) -> None

        handle_keyEvent(key: int, down: bool) -> None

        handle_keyEventExtended(keysym: int, down: bool, keycode: int) -> None

        handle_pointerEvent(x: int, y: int, buttonmask: int) -> None

        handle_setEncodings(encodings: Sequence[int]) -> None

        handle_setPixelFormat(pixel_format: PixelFormat <#vncdotool.rfb.PixelFormat>) -> None

    class vncdotool.loggingproxy.VNCLoggingClient
        Bases: VNCDoToolClient

        Specialization of a VNCDoToolClient that will save screen captures.

        capture_file: str | None = None

        commitUpdate(rectangles: list[Tuple[int, int, int, int]] | None = None) -> None
            called after a series of updateRectangle(), copyRectangle() or fillRectangle() are finished.

            Typicaly, here is the place to request the next screen update with framebufferUpdateRequest() with incremental=True.

            Parameters
                rectangles -- a list of tuples (x,y,w,h) with the updated rectangles.

    class vncdotool.loggingproxy.VNCLoggingClientFactory
        Bases: ProxyClientFactory

        protocol
            alias of VNCLoggingClientProxy

    class vncdotool.loggingproxy.VNCLoggingClientProxy
        Bases: ProxyClient

        Accept data from a server and forward to logger and downstream client.

                VNC server -> VNCLoggingClientProxy -> VNC client
                                                    -> VNCLoggingClient

        dataReceived(data: bytes) -> None
            Called whenever data is received.

            Use this method to translate to a higher-level message. Usually, some callback will be made upon the receipt of each complete protocol message.

            @param data: a string of indeterminate length. Please keep in mind
                that you will probably need to buffer some data, as partial (or multiple) protocol messages may be received! I recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.

        ncaptures = 0

        startLogging(peer: VNCLoggingServerProxy <#vncdotool.loggingproxy.VNCLoggingServerProxy>) -> None

        vnclog: VNCLoggingClient <#vncdotool.loggingproxy.VNCLoggingClient> | None = None

    class vncdotool.loggingproxy.VNCLoggingServerFactory(host, port)
        Bases: ProxyFactory

        clientConnectionLost(client: VNCLoggingServerProxy <#vncdotool.loggingproxy.VNCLoggingServerProxy>) -> None

        clientConnectionMade(client: VNCLoggingServerProxy <#vncdotool.loggingproxy.VNCLoggingServerProxy>) -> None

        force_caps = False

        getRecorder() -> Callable[[str], int]

        last_rect = True

        nocursor = False

        output: IO[str] | str = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>

        password_required = False

        protocol
            alias of VNCLoggingServerProxy

        pseudocursor = False

        pseudodesktop = True

        qemu_extended_key = True

        shared = True

    class vncdotool.loggingproxy.VNCLoggingServerProxy
        Bases: ProxyServer, RFBServer

        Proxy in the middle, decodes and logs RFB messages before sending them upstream.

                VNC client -> VNCLoggingServerProxy -> VNC server
                                                    -> RFBServer

        buttons = 0

        clientProtocolFactory
            alias of VNCLoggingClientFactory

        connectionLost(reason: Failure) -> None
            Called when the connection is shut down.

            Clear any circular references here, and any external references to this Protocol. The connection has been closed.

            @type reason: L{twisted.python.failure.Failure}

        connectionMade() -> None
            Called when a connection is made.

            This may be considered the initializer of the protocol, because it is called when the connection is completed. For clients, this is called once the connection to the server has been established; for servers, this is called after an accept() call stops blocking and a socket has been received. If you need to send any greeting or initial message, do it here.

        dataReceived(data: bytes) -> None
            Called whenever data is received.

            Use this method to translate to a higher-level message. Usually, some callback will be made upon the receipt of each complete protocol message.

            @param data: a string of indeterminate length. Please keep in mind
                that you will probably need to buffer some data, as partial (or multiple) protocol messages may be received! I recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.

        handle_keyEvent(key: int, down: bool) -> None

        handle_pointerEvent(x: int, y: int, buttonmask: int) -> None

        recorder: Callable[[str], int] | None = None

        server: str | None = None

    •   Index <>

    •   Module Index <>

    •   Search Page <>

Author
Marc Sibson

Copyright
2013-2024, Marc Sibson