VBoxVNC.cpp revision ad328b84daa9bc249fcad5fbe53063d3f418cb22
/* $Id$ */
/** @file
* VBoxVNC - VNC VRDE module.
*/
/*
* Contributed by Ivo Smits <Ivo@UFO-Net.nl>, Howard Su and
* Christophe Devriese <christophe.devriese@gmail.com>
*
* Copyright (C) 2011-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#define LOG_GROUP LOG_GROUP_VRDE
#define VNC_SIZEOFRGBA 4
#define VNC_PASSWORDSIZE 20
#define VNC_ADDRESSSIZE 60
#define VNC_PORTSSIZE 20
#define VNC_ADDRESS_OPTION_MAX 500
class VNCServerImpl
{
public:
{
mFrameBuffer = NULL;
uClients = 0;
}
{
if (mFrameBuffer)
if (mCursor)
}
private:
// VNC password
// the structure we pass to libvncserver
char *apszVNCPasswordStruct[2];
// VNC related variables
void *mCallback;
unsigned char *mScreenBuffer;
unsigned char *mFrameBuffer;
{
c = ((c >> 0) & 0xff) << 16 |
((c >> 8) & 0xff) << 8 |
((c >> 16) & 0xff) << 0;
return c;
}
static VRDEENTRYPOINTS_4 Entries;
static DECLCALLBACK(void) VRDEDisconnect(HVRDESERVER hServer, uint32_t u32ClientId, bool fReconnect);
static DECLCALLBACK(void) VRDEUpdate(HVRDESERVER hServer, unsigned uScreenId, void *pvUpdate,uint32_t cbUpdate);
static DECLCALLBACK(void) VRDEAudioSamples(HVRDESERVER hServer, const void *pvSamples, uint32_t cSamples, VRDEAUDIOFORMAT format);
static DECLCALLBACK(void) VRDEAudioVolume(HVRDESERVER hServer, uint16_t u16Left, uint16_t u16Right);
void *pvParm,
void *pvData,
void *pvBuffer,
const char *pszServer,
const char *pszUser,
const char *pszDomain,
const char *pszPassword,
const char *pszCookie);
void *pvCtx,
};
{ VRDE_INTERFACE_VERSION_3, sizeof(VRDEENTRYPOINTS_3) },
};
/** Destroy the server instance.
*
* @param hServer The server instance handle.
*
* @return IPRT status code.
*/
{
return;
}
/** The server should start to accept clients connections.
*
* @param hServer The server instance handle.
* @param fEnable Whether to enable or disable client connections.
* When is false, all existing clients are disconnected.
*
* @return IPRT status code.
*/
{
#ifdef LOG_ENABLED
// enable logging
rfbLogEnable(true);
#endif
LogFlowFunc(("enter\n"));
// query server for the framebuffer
rfbScreenInfoPtr vncServer = rfbGetScreen(0, NULL, info.cWidth, info.cHeight, 8, 3, VNC_SIZEOFRGBA);
#ifndef LIBVNCSERVER_IPv6
// get listen address
{
}
// get listen port
else
{
const char szFeatName[] = "Property/TCP/Ports";
const uint32_t featLen = sizeof(VRDEFEATURE) + RT_MAX(sizeof(VNC_PORTSSIZE), sizeof(szFeatName)) - 1;
feature->u32ClientId = 0;
rc = instance->mCallbacks->VRDECallbackProperty(instance->mCallback, VRDE_QP_FEATURE, feature, featLen, &cbOut);
{
else
}
else
}
// notify about the actually used port
#else
// with IPv6 from here
/*
This is the deal:
Four new options are available:
- VNCAddress4 -> IPv4 address to use
- VNCPort4 -> IPv4 Port to use
- VNCAddress6 -> IPv6 address to use
- VNCPort6 -> IPv6 port to use
By default we prefer IPv6 over IPv4.
The address length can be unlimited as the interface identifier is not
limited by any specs - if this is wrong, and someone knows the line
and the RFC number, i'd appreciate a message :)
THE MAXIMUM LENGTH OF THE RETURNED VALUES MUST NOT BE GREATER THAN:
--> VBOX_ADDRESS_OPTION_MAX <--
which is defined at the top of this file.
The way to determine which address to use is as follows:
1st - get address information from VRDEProperties
2nd - if the address information is IPv4 get VNCAddress6 and VNCPort6
2nd - if the address information is IPv6 get VNCAddress4 and VNCPort4
check both, VNCAddress4 and VNCAddress6 as well as the ports.
3389 is not a valid VNC port, therefore we assume it's not
been set
If one of the addresses is empty we assume to listen on any
IPv4: 0.0.0.0
IPv6: ::
2nd - if everything is empty -> listen on all interfaces
3rd - check if the addresses are valid hand them to libvncserver
to open the initial sockets.
4th - after the sockets have been opened, the used port of the
*/
*/
// this should be put somewhere else
char szIPv6ListenAll[] = "::";
char szIPv4ListenAll[] = "0.0.0.0";
uint32_t ulServerPort4 = 0;
uint32_t ulServerPort6 = 0;
char *pszTCPAddress = NULL;
char *pszTCPPort = NULL;
char *pszVNCAddress4 = NULL;
char *pszVNCPort4 = NULL;
char *pszVNCAddress6 = NULL;
char *pszVNCPort6 = NULL;
char *pszServerAddress4 = NULL;
char *pszServerAddress6 = NULL;
// get address
rc = -1;
&cbOut);
// get port (range)
rc = -1;
&cbOut);
// get tcp ports option from vrde
rc = -1;
pVRDEFeature->u32ClientId = 0;
/** @todo r=bird: I believe this code is more than a little of confused
* about how to use RTStrCopy... The 2nd parameter is the size of the
* destination buffer, not the size of the source string!! */
cbOut = 1;
&cbOut);
if (RT_SUCCESS(rc))
{
}
// get VNCAddress4
rc = -1;
pVRDEFeature->u32ClientId = 0;
cbOut = 1;
&cbOut);
if (RT_SUCCESS(rc))
{
}
// VNCPort4
rc = -1;
pVRDEFeature->u32ClientId = 0;
&cbOut);
if (RT_SUCCESS(rc))
{
if (cbOut < 6)
{
}
}
// VNCAddress6
rc = -1;
pVRDEFeature->u32ClientId = 0;
&cbOut);
if (RT_SUCCESS(rc))
{
}
// VNCPort6
pVRDEFeature->u32ClientId = 0;
cbOut = 5;
&cbOut);
if (RT_SUCCESS(rc))
{
}
if (RT_SUCCESS(rc))
{
if (strlen(pszTCPPort) > 0)
{
ulServerPort4 = 0;
}
if (RT_SUCCESS(rc))
{
}
else
{
}
if (strlen(pszVNCPort6) > 0)
{
ulServerPort6 = 0;
}
}
if (RT_SUCCESS(rc))
{
if (strlen(pszTCPPort) > 0)
{
ulServerPort6 = 0;
}
if (RT_SUCCESS(rc))
{
}
else
{
}
if (strlen(pszVNCPort4) > 0)
{
ulServerPort4 = 0;
}
}
if ((pszServerAddress4 != pszTCPAddress) && (pszServerAddress6 != pszTCPAddress) && (strlen(pszTCPAddress) > 0))
{
// here we go, we prefer IPv6 over IPv4;
resSize = 42;
if (RT_SUCCESS(rc))
{
}
else
{
}
if (!pszServerAddress6)
{
resSize = 16;
if (RT_SUCCESS(rc))
{
}
else
{
}
}
}
{
resSize = 16;
if (RT_SUCCESS(rc))
}
{
resSize = 42;
if (RT_SUCCESS(rc))
}
if (!pszServerAddress4)
{
if (RT_SUCCESS(rc))
else
}
if (!pszServerAddress6)
{
if (RT_SUCCESS(rc))
else
}
if (pszVNCPort4 && ulServerPort4 == 0)
{
ulServerPort4 = 0;
}
if (pszVNCPort6 && ulServerPort6 == 0)
{
ulServerPort6 = 0;
}
if (ulServerPort4 == 0 || ulServerPort6 == 0)
{
}
else
{
}
// notify about the actually used port
int port = 0;
if (vncServer->listen6Sock < 0)
{
port = 0;
}
if (pVRDEFeature)
if (pszTCPAddress)
{
if (pszTCPAddress == pszServerAddress4)
if (pszTCPAddress == pszServerAddress6)
}
if (pszTCPPort)
if (pszVNCAddress4)
if (pszVNCPort4)
if (pszGetAddrInfo4)
if (pszVNCAddress6)
if (pszGetAddrInfo6)
// with ipv6 to here
#endif
// let's get the password
const char szFeatName[] = "Property/VNCPassword";
const uint32_t featLen = sizeof(VRDEFEATURE) + RT_MAX(sizeof(instance->szVNCPassword), sizeof(szFeatName)) - 1;
feature->u32ClientId = 0;
rc = instance->mCallbacks->VRDECallbackProperty(instance->mCallback, VRDE_QP_FEATURE, feature, featLen, &cbOut);
if (RT_SUCCESS(rc))
{
LogRel(("VNC: Configuring password\n"));
}
else
return VINF_SUCCESS;
}
/** The server should disconnect the client.
*
* @param hServer The server instance handle.
* @param u32ClientId The client identifier.
* @param fReconnect Whether to send a "REDIRECT to the same server" packet to the
* client before disconnecting.
*
* @return IPRT status code.
*/
bool fReconnect)
{
}
{
// RGB 555 (1 bit unused)
}
{
// RGB 565 (all bits used, 1 extra bit for green)
}
/**
* Inform the server that the display was resized.
* The server will query information about display
* from the application via callbacks.
*
* @param hServer Handle of VRDE server instance.
*/
{
if (!RT_SUCCESS(rc))
{
return;
}
LogRel(("VNCServerImpl::VRDEResize to %dx%dx%dbpp\n", info.cWidth, info.cHeight, info.cBitsPerPixel));
// we always alloc an RGBA buffer
unsigned char *FrameBuffer = (unsigned char *)RTMemAlloc(info.cWidth * info.cHeight * VNC_SIZEOFRGBA); // RGBA
{
uint32_t i, j;
for (i = 0, j = 0; i < info.cWidth * info.cHeight * VNC_SIZEOFRGBA; i += VNC_SIZEOFRGBA, j += info.cBitsPerPixel / 8)
{
FrameBuffer[i] = b;
FrameBuffer[i + 1] = g;
FrameBuffer[i + 2] = r;
}
}
{
uint32_t i, j;
for (i = 0, j = 0;
{
FrameBuffer[i],
FrameBuffer[i + 1],
FrameBuffer[i + 2]);
}
}
rfbNewFramebuffer(instance->mVNCServer, (char *)FrameBuffer, info.cWidth, info.cHeight, 8, 3, VNC_SIZEOFRGBA);
if (temp)
}
/**
* Send a update.
*
* @param hServer Handle of VRDE server instance.
* @param uScreenId The screen index.
* @param pvUpdate Pointer to VBoxGuest.h::VRDEORDERHDR structure with extra data.
* @param cbUpdate Size of the update data.
*/
{
ptr += sizeof(VRDEORDERHDR);
{
/* Inform the VRDE server that the current display update sequence is
* completed. At this moment the framebuffer memory contains a definite
* image, that is synchronized with the orders already sent to VRDE client.
* The server can now process redraw requests from clients or initial
* fullscreen updates for new clients.
*/
}
else
{
if (sizeof(VRDEORDERHDR) != cbUpdate)
{
ptr += sizeof(VRDEORDERCODE);
{
case VRDE_ORDER_SOLIDRECT:
{
return;
}
///@todo: more orders
}
}
{
{
// RGB to BGR
{
}
}
}
{
{
{
}
}
}
rfbMarkRectAsModified(instance->mVNCServer, order->x, order->y, order->x+order->w, order->y+order->h);
}
}
/**
* Set the mouse pointer shape.
*
* @param hServer Handle of VRDE server instance.
* @param pPointer The pointer shape information.
*/
const VRDECOLORPOINTER *pPointer)
{
unsigned char *mem = (unsigned char *)malloc(pPointer->u16Width * pPointer->u16Height * VNC_SIZEOFRGBA);
{
{
*maskmem++;
}
}
{
{
// put the color value;
*(mem++) = 0xff;
}
}
}
/**
* Hide the mouse pointer.
*
* @param hServer Handle of VRDE server instance.
*/
{
///@todo: what's behavior for this. hide doesn't seems right
//rfbSetCursor(instance->mVNCServer, NULL);
}
/**
* Queues the samples to be sent to clients.
*
* @param hServer Handle of VRDE server instance.
* @param pvSamples Address of samples to be sent.
* @param cSamples Number of samples.
* @param format Encoded audio format for these samples.
*
* @note Initialized to NULL when the application audio callbacks are NULL.
*/
const void *pvSamples,
{
}
/**
* Sets the sound volume on clients.
*
* @param hServer Handle of VRDE server instance.
* @param left 0..0xFFFF volume level for left channel.
* @param right 0..0xFFFF volume level for right channel.
*
* @note Initialized to NULL when the application audio callbacks are NULL.
*/
{
}
/**
* Sends a USB request.
*
* @param hServer Handle of VRDE server instance.
* @param u32ClientId An identifier that allows the server to find the corresponding client.
* The identifier is always passed by the server as a parameter
* of the FNVRDEUSBCALLBACK. Note that the value is the same as
* in the VRDESERVERCALLBACK functions.
* @param pvParm Function specific parameters buffer.
* @param cbParm Size of the buffer.
*
* @note Initialized to NULL when the application USB callbacks are NULL.
*/
void *pvParm,
{
}
/**
* Called by the application when (VRDE_CLIPBOARD_FUNCTION_*):
* - (0) guest announces available clipboard formats;
* - (1) guest requests clipboard data;
* - (2) guest responds to the client's request for clipboard data.
*
* @param hServer The VRDE server handle.
* @param u32Function The cause of the call.
* @param u32Format Bitmask of announced formats or the format of data.
* @param pvData Points to: (1) buffer to be filled with clients data;
* (2) data from the host.
* @param cbData Size of 'pvData' buffer in bytes.
* @param pcbActualRead Size of the copied data in bytes.
*
* @note Initialized to NULL when the application clipboard callbacks are NULL.
*/
void *pvData,
{
}
/**
* Query various information from the VRDE server.
*
* @param hServer The VRDE server handle.
* @param index VRDE_QI_* identifier of information to be returned.
* @param pvBuffer Address of memory buffer to which the information must be written.
* @param cbBuffer Size of the memory buffer in bytes.
* @param pcbOut Size in bytes of returned information value.
*
* @remark The caller must check the *pcbOut. 0 there means no information was returned.
* A value greater than cbBuffer means that information is too big to fit in the
* buffer, in that case no information was placed to the buffer.
*/
void *pvBuffer,
{
*pcbOut = 0;
switch (index)
{
case VRDE_QI_ACTIVE: /* # of active clients */
case VRDE_QI_NUMBER_OF_CLIENTS: /* # of connected clients */
{
{
}
break;
}
///@todo lots more queries to implement
default:
break;
}
}
/**
* The server should redirect the client to the specified server.
*
* @param hServer The server instance handle.
* @param u32ClientId The client identifier.
* @param pszServer The server to redirect the client to.
* @param pszUser The username to use for the redirection.
* Can be NULL.
* @param pszDomain The domain. Can be NULL.
* @param pszPassword The password. Can be NULL.
* @param u32SessionId The ID of the session to redirect to.
* @param pszCookie The routing token used by a load balancer to
* route the redirection. Can be NULL.
*/
const char *pszServer,
const char *pszUser,
const char *pszDomain,
const char *pszPassword,
const char *pszCookie)
{
}
/**
* Audio input open request.
*
* @param hServer Handle of VRDE server instance.
* @param pvCtx To be used in VRDECallbackAudioIn.
* @param u32ClientId An identifier that allows the server to find the corresponding client.
* @param audioFormat Preferred format of audio data.
* @param u32SamplesPerBlock Preferred number of samples in one block of audio input data.
*
* @note Initialized to NULL when the VRDECallbackAudioIn callback is NULL.
*/
void *pvCtx,
{
}
/**
* Audio input close request.
*
* @param hServer Handle of VRDE server instance.
* @param u32ClientId An identifier that allows the server to find the corresponding client.
*
* @note Initialized to NULL when the VRDECallbackAudioIn callback is NULL.
*/
{
}
void *pvCallback)
{
{
}
{
///@todo: this is incorrect and it will cause crash if client call unsupport func.
// since they are same in order, let's just change header
}
else
return VERR_VERSION_MISMATCH;
return VINF_SUCCESS;
}
{
/* Conversion table for key code range 32-127 (which happen to equal the ASCII codes).
* Values 0xe0?? indicate that a 0xe0 scancode will be sent first (extended keys), then code ?? is sent */
static unsigned codes_low[] =
{
// Conversion table for VNC key code range 32-127
0x39, 0x02, 0x28, 0x04, 0x05, 0x06, 0x08, 0x28, 0x0a, 0x0b, 0x09, 0x0d, 0x33, 0x0c, 0x34, 0x35, //space, !"#$%&'()*+`-./
0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x27, 0x27, 0x33, 0x0d, 0x34, 0x35, 0x03, //0123456789:;<=>?@
0x1e, 0x30, 0x2e, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, //A-M
0x31, 0x18, 0x19, 0x10, 0x13, 0x1f, 0x14, 0x16, 0x2f, 0x11, 0x2d, 0x15, 0x2c, //N-Z
0x1a, 0x2b, 0x1b, 0x07, 0x0c, 0x29, //[\]^_`
0x1e, 0x30, 0x2e, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, //a-m
0x31, 0x18, 0x19, 0x10, 0x13, 0x1f, 0x14, 0x16, 0x2f, 0x11, 0x2d, 0x15, 0x2c, //n-z
0x1a, 0x2b, 0x1b, 0x29 //{|}~
};
int code = -1;
if (keycode < 32)
{
//ASCII control codes.. unused..
}
else if (keycode < 127)
{
//DEL is in high area
}
{
}
else
{
switch(keycode)
{
//case 65301: break; SysRq
//case 65377: break; //Print screen
}
}
if (code == -1)
{
return;
}
if (code > 0xff)
{
instance->mCallbacks->VRDECallbackInput(instance->mCallback, VRDE_INPUT_SCANCODE, &point, sizeof(point));
}
instance->mCallbacks->VRDECallbackInput(instance->mCallback, VRDE_INPUT_SCANCODE, &point, sizeof(point));
}
{
unsigned button = 0;
point.x = x;
point.y = y;
instance->mCallbacks->VRDECallbackInput(instance->mCallback, VRDE_INPUT_POINT, &point, sizeof(point));
}
{
///@todo: we need auth user here
return RFB_CLIENT_ACCEPT;
}
{
}
VNCServerImpl *g_VNCServer = 0;
void *pvCallback,
{
if (!g_VNCServer)
{
g_VNCServer = new VNCServerImpl();
}
if (RT_SUCCESS(rc))
{
}
return rc;
}
static const char * const supportedProperties[] =
{
};
DECLEXPORT(const char * const *) VRDESupportedProperties(void)
{
LogFlowFunc(("enter\n"));
return supportedProperties;
}