diff -r 000000000000 -r b74b0e4902c3 components/vnc_server/vnc-server.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/vnc_server/vnc-server.c Sat Oct 20 13:23:15 2018 +0200 @@ -0,0 +1,1520 @@ +/** + * @file vnc-server.c + * @brief VNC server original written for eCos. + * + * The VNC server runs on the target platform and waits for a client + * (vncviewer program running on a PC) to connect via an ethernet connection + * (port 5900). Once a client has connected, the target platform has a + * virtual screen, keyboard and mouse. + * + * This port of the VNC server has been designed with some limitations in + * order to keep memory and processor power requirements to a minimum. + * + * The VNC client must accept the settings that the VNC server suggests (bits-per-pixel, + * number-of-colours and so on as specified at build time with the eCos configuration + * tool) or the VNC server will just disconnect. + * + * The VNC server only supports CoRRE encoding and RAW encoding for sending + * display data to the client. + * + * The VNC server does not support password authentication, the client is able + * to connect without the user typing in a password. + * + * Only one VNC client may connect to the VNC server at a time. + * + * In reality these limitations are not a problem. + * + * @author Chris Garry + * @author Michiel Broek. + */ + +#include "vnc-server.h" +#include "websocket_server.h" + +static const char *TAG = "vnc-server"; + +#define BACKLOG 5 /* Number of pending connections queue will hold */ +#define MESSAGE_BUFFER_SIZE 60 /* Was 50, but I have seen 52 bytes length */ +#define TILE_SIZE 16 +#define TRUE_COLOUR_FLAG 1 /* True colour is set */ +#define BIG_ENDIAN_FLAG 1 /* Always send colour data big endian */ + +/* Default display parameters */ +#define BITS_PER_PIXEL 8 /* Bits per pixel */ +#define PIXEL_DEPTH 8 /* Usefull bits per pixel */ +#define RED_MAX 7 +#define GREEN_MAX 7 +#define BLUE_MAX 3 +#define RED_SHIFT 5 +#define GREEN_SHIFT 2 +#define BLUE_SHIFT 0 + + +/* Initial default RGB332 */ +uint8_t Bits_Per_Pixel = 8; + +uint16_t Red_Max = RED_MAX; +uint16_t Green_Max = GREEN_MAX; +uint16_t Blue_Max = BLUE_MAX; +uint8_t Red_Shift = RED_SHIFT; +uint8_t Green_Shift = GREEN_SHIFT; +uint8_t Blue_Shift = BLUE_SHIFT; +bool AltPixels = false; + +/* Client to Server message types */ +#define SET_PIXEL_FORMAT 0 +#define FIX_COLOUR_MAP_ENTRIES 1 +#define SET_ENCODINGS 2 +#define FRAME_BUFFER_UPDATE_REQ 3 +#define KEY_EVENT 4 +#define POINTER_EVENT 5 +#define CLIENT_CUT_TEXT 6 + +/* Macros to split colour to bytes */ +#define COLOUR2BYTE1(col) ((col>>8)&0xFF) +#define COLOUR2BYTE0(col) (col&0xFF) + + +/* Thread function prototypes */ +static TaskHandle_t xTaskClientHandler = NULL; ///< Task for VNC clients +static TaskHandle_t xTaskFrameUpdate = NULL; ///< Task for framebuffer updates +static TaskHandle_t xTaskWSclient = NULL; ///< Task for websocket clients + +SemaphoreHandle_t SoundBell_lock = NULL; ///< Lock for the bell sound. + +static QueueHandle_t message_queue; ///< Websockets message queue +const static int message_queue_size = 5; ///< Message queue size + +struct strMessage { + int length; ///< Length of the message + uint8_t message[MESSAGE_BUFFER_SIZE]; ///< The message +}; + + +uint8_t VNC_pointer_button = 0; +uint16_t VNC_pointer_x = 0; +uint16_t VNC_pointer_y = 0; + + +/* Define size of each thread's stack */ +#define MIN_STACK_SIZE 3072 + + +/* Messages */ +char server_ProtocolVersion[] = "RFB 003.003\n"; +char bad_protocol[] = "Unsupported ProtocolVersion"; +char server_busy[] = "Server is Busy"; +char sound_bell[] = "\2"; +char desktop_name[] = "MBSE BrewBoard"; // Hardcoded, don't change or the VNC webclient breaks. + +/* Frame Buffer */ +vnc_color_t frame_buffer[CONFIG_VNC_SERVER_FRAME_HEIGHT+1][CONFIG_VNC_SERVER_FRAME_WIDTH+1]; + +/* Calculate the number of tiles in the X and Y directions */ +#if (CONFIG_VNC_SERVER_FRAME_HEIGHT % TILE_SIZE) != 0 +#define NUM_TILES_Y_AXIS (CONFIG_VNC_SERVER_FRAME_HEIGHT/TILE_SIZE + 1) +#define LAST_TILE_HEIGHT (CONFIG_VNC_SERVER_FRAME_HEIGHT % TILE_SIZE) +#else +#define NUM_TILES_Y_AXIS (CONFIG_VNC_SERVER_FRAME_HEIGHT/TILE_SIZE) +#define LAST_TILE_HEIGHT TILE_SIZE +#endif + +#if (CONFIG_VNC_SERVER_FRAME_WIDTH % TILE_SIZE) != 0 +#define NUM_TILES_X_AXIS (CONFIG_VNC_SERVER_FRAME_WIDTH/TILE_SIZE + 1) +#define LAST_TILE_WIDTH (CONFIG_VNC_SERVER_FRAME_WIDTH % TILE_SIZE) +#else +#define NUM_TILES_X_AXIS (CONFIG_VNC_SERVER_FRAME_WIDTH/TILE_SIZE) +#define LAST_TILE_WIDTH TILE_SIZE +#endif + +/* Array for marking tiles that have been updated */ +int tile_updated[NUM_TILES_Y_AXIS+1][NUM_TILES_X_AXIS+1]; + +/* Conditional variable to signal that a client is connected and initialised */ +EventGroupHandle_t xEventGroupVNC; +const int VNC_CLIENT_UPDATE_REQ = BIT0; +int vnc_client_sock = -1; +bool vnc_client_connected = false; + +/* Variable for sounding the client's bell */ +int SoundBellCount; + +/* Variables for the Websocket client */ +bool VNC_WS_run = false; +int VNC_WS_num = -1; + + + +/* Variable to hold the frame format details */ +vnc_frame_format_t frame_format = {CONFIG_VNC_SERVER_FRAME_WIDTH, CONFIG_VNC_SERVER_FRAME_HEIGHT, frame_buffer, + 1, // RGB332 server native. + 0, // RGB555 + 0, // RGB565 + 0, // BGR233 + 0, // TRUECOLOR0888 +}; + + + +#if 0 +void dump_msg(char *buf, uint16_t buflen, char *mode) +{ + int i, l = 0; + + printf("%s %d", mode, buflen); + for (i = 0; i < buflen; i++) { + if ((i % 16) == 0) { + printf("\n%02d: ", l); + l++; + } + printf("%02x ", buf[i]); + } + printf("\n"); +} +#endif + + +/* Structure to hold the encoding type details */ +volatile struct encoding_type_struct +{ + uint8_t raw; + uint8_t copy_rectangle; + uint8_t rre; + uint8_t corre; + uint8_t hextile; +} encoding_type; + + +static int GetMessageData(int, char *, int); +static int GenTileUpdateData(uint8_t *); +void task_VNCserver(void *); +void task_frame(void *); +void task_WS(void *); + + + +/** + * @brief Serve a new VNC client. + */ +void vnc_netconn_serve(void); + + +/* VNC startup. */ +void VncStartup(void) +{ + /* Initialise mutex & cond vars */ + xEventGroupVNC = xEventGroupCreate(); + SoundBell_lock = xSemaphoreCreateMutex(); + + xTaskCreate(&task_VNCserver, "VNCserver", MIN_STACK_SIZE, NULL, 5, &xTaskClientHandler); + xTaskCreate(&task_frame, "frame_update", MIN_STACK_SIZE, NULL, 6, &xTaskFrameUpdate); +} + + +/** + * @brief Client Handler Task. + * + * This task handles the client initialisation sequence. Once the client + * is initialised this task handles all received messages from the client, + * but does not send any data to the client. + */ +void task_VNCserver(void *pvParameter) +{ + int server_sock; + struct sockaddr_in server_addr; + struct sockaddr_in client_addr; + socklen_t client_addr_size; + + ESP_LOGI(TAG, "Starting VNC server"); + + /* Clear the encoding type structure */ + encoding_type.raw = 0; + encoding_type.copy_rectangle = 0; + encoding_type.rre = 0; + encoding_type.corre = 0; + encoding_type.hextile = 0; + + /* Clear the sound bell counter */ + SoundBellCount = 0; + VNC_pointer_button = 0; + VNC_pointer_x = VNC_pointer_y = 0; + + /* Create socket for incomming connections */ + if ((server_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + ESP_LOGE(TAG, "socket() VNC function failed"); + exit(1); + } + + /* Construct the server address structure */ + memset(&server_addr, 0, sizeof(server_addr)); /* Fill entire structure with 0's */ + server_addr.sin_family = AF_INET; /* Internet address family */ + server_addr.sin_addr.s_addr = INADDR_ANY; /* Autofill with my IP address */ + server_addr.sin_port = htons(CONFIG_VNC_SERVER_PORT); + + /* Bind socket to local address */ + if (bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { + ESP_LOGE(TAG, "bind() VNC function failed"); + exit(1); + } + + /* Set the socket to listen for incoming connections */ + if (listen(server_sock, BACKLOG) < 0) { + ESP_LOGE(TAG, "listen() VNC function failed"); + exit(1); + } + + do { + vnc_client_sock = (accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_size)); + if (vnc_client_sock >= 0) { + vnc_client_connected = true; + vnc_netconn_serve(); + vnc_client_connected = false; + } + vTaskDelay((TickType_t)10); /* allows the freeRTOS scheduler to take over if needed */ + } while (vnc_client_sock >= 0); +} + + + +/** + * @brief Serve a new VNC client and handle the whole session. + */ +void vnc_netconn_serve(void) +{ + long int temp_long; + char protocol_ver[8], message_buffer[MESSAGE_BUFFER_SIZE]; + int i, j, message_len, ProtocolOkay; + uint32_t *ptr_to_uint32; + uint16_t *ptr_to_uint16; + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + char ipstr[INET6_ADDRSTRLEN]; + + getpeername(vnc_client_sock, (struct sockaddr*)&addr, &len); + if (addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + } + ESP_LOGI(TAG, "VNC new client %s socket %d", ipstr, vnc_client_sock); + + /* ProtocolVersion Handshake - begin */ + /* Send ProtocolVersion we want to use to client */ + message_len = sprintf(message_buffer, "RFB 003.003\n"); + if (send(vnc_client_sock, message_buffer, message_len, 0) != message_len) { + goto close_connection; + } + + /* Receive ProtocolVersion the client wants to use */ + if (GetMessageData(vnc_client_sock, &(message_buffer[0]), 12) == 0) { + goto close_connection; + } + + /* Check this is acceptable (RFB 003.xxx is okay) */ + ProtocolOkay = 1; + for (i = 0; i < 8; i++) { + if (message_buffer[i] != server_ProtocolVersion[i]) { + ProtocolOkay = 0; + } + + /* Store the protocol version - ignoring thr 'RFB ' part */ + protocol_ver[i] = message_buffer[i + 4]; + } + protocol_ver[7] = 0; + /* ProtocolVersion Handshake - end */ + + /* Authentication - begin */ + /* Send Authentication scheme to be used to client */ + if (ProtocolOkay == 0) { + /* ProtocolVerion is not okay */ + + /* Generate the Bad ProtocolVerion message */ + ptr_to_uint32 = (uint32_t *) &(message_buffer[0]); + *ptr_to_uint32 = htonl(0); + ptr_to_uint32 = (uint32_t *) &(message_buffer[4]); + *ptr_to_uint32 = htonl(strlen(bad_protocol)); + strcpy(&(message_buffer[8]), bad_protocol); + + if (send(vnc_client_sock, message_buffer, 8 + strlen(bad_protocol), 0) != (8 + strlen(bad_protocol))) { + printf("Call to send() 1 failed\n"); + } + goto close_connection; + + } else if (VNC_WS_num != -1) { + /* Busy with a websocket client */ + ptr_to_uint32 = (uint32_t *) &(message_buffer[0]); + *ptr_to_uint32 = htonl(0); + ptr_to_uint32 = (uint32_t *) &(message_buffer[4]); + *ptr_to_uint32 = htonl(strlen(server_busy)); + strcpy(&(message_buffer[8]), server_busy); + + if (send(vnc_client_sock, message_buffer, 8 + strlen(server_busy), 0) != (8 + strlen(server_busy))) { + printf("Call to send() 1 failed\n"); + } + goto close_connection; + + } else { + /* ProtocolVerion is okay - connect with no authentication*/ + + /* Generate the No Authentication message */ + ptr_to_uint32 = (uint32_t *) &(message_buffer[0]); + *ptr_to_uint32 = htonl((uint32_t)1); + + if (send(vnc_client_sock, message_buffer, 4, 0) != 4) { + goto close_connection; + } + } + /* Authentication - end */ + + /* ClientInitialisation - begin */ + /* Receive initialisation message from client (1 byte) */ + if (GetMessageData(vnc_client_sock, &(message_buffer[0]), 1) == 0) { + goto close_connection; + } + /* Do nothing with this as we only support 1 Client at a time */ + /* ClientInitialisation - end */ + + /* ServerInitialisation - begin */ + + /* Initial default RGB332 */ + Red_Max = RED_MAX; + Green_Max = GREEN_MAX; + Blue_Max = BLUE_MAX; + Red_Shift = RED_SHIFT; + Green_Shift = GREEN_SHIFT; + Blue_Shift = BLUE_SHIFT; + AltPixels = false; + + /* Create Initialisation message for client */ + ptr_to_uint16 = (uint16_t *) &(message_buffer[0]); + *ptr_to_uint16 = htons((uint16_t)CONFIG_VNC_SERVER_FRAME_WIDTH); + + ptr_to_uint16 = (uint16_t *) &(message_buffer[2]); + *ptr_to_uint16 = htons((uint16_t)CONFIG_VNC_SERVER_FRAME_HEIGHT); + + message_buffer[4] = (uint8_t)BITS_PER_PIXEL; + message_buffer[5] = (uint8_t)PIXEL_DEPTH; + message_buffer[6] = (uint8_t)BIG_ENDIAN_FLAG; + message_buffer[7] = (uint8_t)TRUE_COLOUR_FLAG; + + ptr_to_uint16 = (uint16_t *) &(message_buffer[8]); + *ptr_to_uint16 = htons(Red_Max); + + ptr_to_uint16 = (uint16_t *) &(message_buffer[10]); + *ptr_to_uint16 = htons(Green_Max); + + ptr_to_uint16 = (uint16_t *) &(message_buffer[12]); + *ptr_to_uint16 = htons(Blue_Max); + + message_buffer[14] = Red_Shift; + message_buffer[15] = Green_Shift; + message_buffer[16] = Blue_Shift; + + ptr_to_uint32 = (uint32_t *) &(message_buffer[20]); + *ptr_to_uint32 = htonl(strlen(desktop_name)); + strcpy(&(message_buffer[24]), desktop_name); + + if (send(vnc_client_sock, message_buffer, 24 + strlen(desktop_name), 0) != (24 + strlen(desktop_name))) { + printf("Call to send() 3 failed\n"); + } + /* ServerInitialisation - end */ + + /* Cancel any outstanding Sound Bell requests */ + if (xSemaphoreTake(SoundBell_lock, 10) == pdTRUE) { + SoundBellCount = 0; + xSemaphoreGive(SoundBell_lock); + } + + ESP_LOGI(TAG, "VNC client connected (RFB Protocol Ver: %s)", protocol_ver); + + /* Main message handling loop */ + while(1) { + int num_of_encodings; + + /* Receive 1st byte of message from client */ + if (GetMessageData(vnc_client_sock, &(message_buffer[0]), 1) == 0) { + goto close_connection; /* Connection lost */ + } + + switch(message_buffer[0]) { + case SET_PIXEL_FORMAT: + + /* Get the remainder (19 bytes) of message */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 19) == 0) { + goto close_connection; + } + + /* Check pixel format is as expected */ + i = 0; + if (message_buffer[4] != BITS_PER_PIXEL) { + ESP_LOGI(TAG, "SetPixelFormat client wants %d bits-per-pixel", message_buffer[4]); + i++; + } + + if (message_buffer[5] != PIXEL_DEPTH) { + ESP_LOGI(TAG, "SetPixelFormat client wants %d pixel-depth", message_buffer[5]); + i++; + } + + if ((message_buffer[7] & 0x01) != TRUE_COLOUR_FLAG) { + ESP_LOGI(TAG, "SetPixelFormat client wants %d true-colour-flag", message_buffer[7]); + i++; + } + + ptr_to_uint16 = (uint16_t *)&(message_buffer[8]); + if (htons(*ptr_to_uint16) != Red_Max) { + Red_Max = htons(*ptr_to_uint16); + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d red-max", Red_Max); + } + + ptr_to_uint16 = (uint16_t *)&(message_buffer[10]); + if (htons(*ptr_to_uint16) != Green_Max) { + Green_Max = htons(*ptr_to_uint16); + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d green-max", Green_Max); + } + + ptr_to_uint16 = (uint16_t *)&(message_buffer[12]); + if (htons(*ptr_to_uint16) != Blue_Max) { + Blue_Max = htons(*ptr_to_uint16); + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d blue-max", Blue_Max); + } + + if (message_buffer[14] != Red_Shift) { + Red_Shift = message_buffer[14]; + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d red-shift", Red_Shift); + } + + if (message_buffer[15] != Green_Shift) { + Green_Shift = message_buffer[15]; + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d green-shift", Green_Shift); + } + + if (message_buffer[16] != Blue_Shift) { + Blue_Shift = message_buffer[16]; + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d blue-shift", Blue_Shift); + } + + if (i) { + ESP_LOGI(TAG, "SetPixelFormat %d errors, disconnect client", i); + ESP_LOGI(TAG, "Ensure the 'Auto select' is not enabled in your vncviewer options,"); + goto close_connection_quietly; + } + + break; + + case FIX_COLOUR_MAP_ENTRIES: +printf("fix colormap entries\n"); + /* Not supported, just get the data from the buffer and ignore it */ + + /* Get the next 5 bytes of message */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 5) == 0) { + goto close_connection; + } + + /* Calculate how many colour entries are in the buffer */ + i = message_buffer[4]*255 + message_buffer[5]; + i *= 6; + + /* Get this amount of data from the buffer */ + for (j = 0; j < i; j++) { + if (GetMessageData(vnc_client_sock, &(message_buffer[6]), 6) == 0) { + goto close_connection; + } + } + + break; + + case SET_ENCODINGS: + + /* Get the next 3 bytes of message */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 3) == 0) { + goto close_connection; + } + + num_of_encodings = message_buffer[2]*255 + message_buffer[3]; + + /* Get the remainder of message */ + if (GetMessageData(vnc_client_sock, &(message_buffer[0]), 4 * num_of_encodings) == 0) { + goto close_connection; + } + + /* Clear the encoding type structure */ + encoding_type.raw = 0; + encoding_type.copy_rectangle = 0; + encoding_type.rre = 0; + encoding_type.corre = 0; + encoding_type.hextile = 0; + + for (i = 0; i < num_of_encodings; i++) { + switch(message_buffer[3 + (i*4)]) { + case 0: /* Raw encoding */ + encoding_type.raw = i + 1; + break; + case 1: /* Copy rectangle encoding */ + encoding_type.copy_rectangle = i + 1; + break; + case 2: /* RRE encoding */ + encoding_type.rre = i + 1; + break; + case 4: /* CoRRE encoding */ + encoding_type.corre = i + 1; + break; + case 5: /* Hextile encoding */ + encoding_type.hextile = i + 1; + break; + default: /* Unknown coding type - do nothing */ + break; + } + } + + if (encoding_type.corre) { + ESP_LOGI(TAG, "SetEncodings use CORRE"); + } else { + ESP_LOGI(TAG, "SetEncodings use RAW"); + } + break; + + case FRAME_BUFFER_UPDATE_REQ: + /* Get the remainder of message (9 bytes) */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 9) == 0) { + goto close_connection; + } + + if (!message_buffer[1]) { + /* Non-incremental mode - mark the squares that need to be updated */ + for (i = (message_buffer[2]*255 + message_buffer[3])/TILE_SIZE; + i <= (message_buffer[2]*255 + message_buffer[3] + message_buffer[6]*255 + message_buffer[7])/TILE_SIZE; + i++) { + for (j = (message_buffer[4]*255 + message_buffer[5])/TILE_SIZE; + j <= (message_buffer[4]*255 + message_buffer[5] + message_buffer[8]*255 + message_buffer[9])/TILE_SIZE; + j++) { + tile_updated[j][i] = 1; + } + } + } + + /* Signal that there is now a pending update request */ + xEventGroupSetBits(xEventGroupVNC, VNC_CLIENT_UPDATE_REQ); + break; + + case KEY_EVENT: + /* Handle the key event, ignored for brewboard */ + /* Get the remainder of message (7 bytes) */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 7) == 0) { + goto close_connection; + } + break; + + case POINTER_EVENT: + /* Handle the pointer event, simulate the touch screen. */ + /* Get the remainder of message (5 bytes) */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 5) == 0) { + goto close_connection; + } + /* Set global variables that will be read by another task. */ + VNC_pointer_button = message_buffer[1]; + VNC_pointer_x = message_buffer[2]*255 + message_buffer[3]; + VNC_pointer_y = message_buffer[4]*255 + message_buffer[5]; + break; + + case CLIENT_CUT_TEXT: + /* Handle the client has cut text event */ + /* Current we just get and discard the data */ + /* Get the next 7 bytes of the message */ + if (GetMessageData(vnc_client_sock, &(message_buffer[1]), 7) == 0) { + goto close_connection; + } + + ptr_to_uint32 = (uint32_t *)&(message_buffer[4]); + temp_long = htonl(*ptr_to_uint32); + + while (temp_long > 0) { + /* Get the text in chunks MESSAGE_BUFFER_SIZE-1 characters */ + if (temp_long > MESSAGE_BUFFER_SIZE-2) { + if (GetMessageData(vnc_client_sock, &(message_buffer[0]), MESSAGE_BUFFER_SIZE-1) == 0) { + goto close_connection; + } + + message_buffer[MESSAGE_BUFFER_SIZE-1] = 0; + temp_long -= (MESSAGE_BUFFER_SIZE-1); + } else { + if (GetMessageData(vnc_client_sock, &(message_buffer[0]), temp_long) == 0) { + goto close_connection; + } + + message_buffer[temp_long] = 0; + temp_long = 0; + } + } + + break; + + default: + ESP_LOGI(TAG, "Unknown message %d from client", message_buffer[0]); + } + + vTaskDelay( (TickType_t)1); + } + +close_connection: + ESP_LOGI(TAG, "VNC client disconnected"); + +close_connection_quietly: + + /* Cancel any outstanding update requests */ + xEventGroupClearBits(xEventGroupVNC, VNC_CLIENT_UPDATE_REQ); + close(vnc_client_sock); +} + + + +/** + * @brief Frame update task. This thread handles the sending of all frame + * update data to the client and sends the 'ring bell' message to + * the client when required. + * Works for VNC port connected clients and Websocket clients. + * @param pvParameter Ignored + */ +void task_frame(void *pvParameter) +{ + int i, j, x_pos, y_pos, packet_length, num_updated_tiles; + uint16_t *ptr_to_uint16; + /* These are declared static so they don't use thread stack memory */ + static uint8_t FramebufferUpdate_msg[4 + 12 + TILE_SIZE*TILE_SIZE*BITS_PER_PIXEL/8 + 1460]; + static int FrameBufferPtr; /* Pointer to next space in buffer */ + static int tile_updated_local[NUM_TILES_Y_AXIS][NUM_TILES_X_AXIS]; + + ESP_LOGI(TAG, "Starting VNC frame updater"); + + while(1) { + /* Wait until client sends a frame update request */ +wait_for_client: + xEventGroupWaitBits(xEventGroupVNC, VNC_CLIENT_UPDATE_REQ, pdFALSE, pdFALSE, portMAX_DELAY); + + /* Copy tile_updated array to local version and clear copied tiles */ + vTaskSuspendAll(); + num_updated_tiles = 0; + for (i = 0; i < NUM_TILES_Y_AXIS; i++) { + for (j = 0; j < NUM_TILES_X_AXIS; j++) { + if (tile_updated[i][j]) { + tile_updated_local[i][j] = 1; + tile_updated[i][j] = 0; + num_updated_tiles++; /* Keep count of the updated tiles */ + } + } + } + xTaskResumeAll(); + + if (num_updated_tiles) { + /* Cancel update request */ + xEventGroupClearBits(xEventGroupVNC, VNC_CLIENT_UPDATE_REQ); + /* Fill in constant parts of FramebufferUpdate Message */ + FramebufferUpdate_msg[0] = 0; /* Message-type */ + FramebufferUpdate_msg[1] = 0; /* Padding */ + ptr_to_uint16 = (uint16_t *) &(FramebufferUpdate_msg[2]); + *ptr_to_uint16 = htons(num_updated_tiles); /* Number-of-rectangles */ + FrameBufferPtr = 4; + + for (y_pos = 0; y_pos < NUM_TILES_Y_AXIS; y_pos++) { + for (x_pos = 0; x_pos < NUM_TILES_X_AXIS; x_pos++) { + if (tile_updated_local[y_pos][x_pos]) { + /* Send current square data to client */ + + /* x-position */ + FramebufferUpdate_msg[FrameBufferPtr+0] = (x_pos * TILE_SIZE) / 256; + FramebufferUpdate_msg[FrameBufferPtr+1] = (x_pos * TILE_SIZE) % 256; + + /* y-position */ + FramebufferUpdate_msg[FrameBufferPtr+2] = (y_pos * TILE_SIZE) / 256; + FramebufferUpdate_msg[FrameBufferPtr+3] = (y_pos * TILE_SIZE) % 256; + + + /* Set width of tile in packet */ + if (x_pos == (NUM_TILES_X_AXIS -1)) { + /* Last tile in X-axis */ + FramebufferUpdate_msg[FrameBufferPtr+4] = LAST_TILE_WIDTH / 256; + FramebufferUpdate_msg[FrameBufferPtr+5] = LAST_TILE_WIDTH % 256; + } else { + FramebufferUpdate_msg[FrameBufferPtr+4] = TILE_SIZE / 256; + FramebufferUpdate_msg[FrameBufferPtr+5] = TILE_SIZE % 256; + } + + if (y_pos == (NUM_TILES_Y_AXIS -1)) { + /* Last tile in Y-axis */ + FramebufferUpdate_msg[FrameBufferPtr+6] = LAST_TILE_HEIGHT / 256; + FramebufferUpdate_msg[FrameBufferPtr+7] = LAST_TILE_HEIGHT % 256; + } else { + FramebufferUpdate_msg[FrameBufferPtr+6] = TILE_SIZE / 256; + FramebufferUpdate_msg[FrameBufferPtr+7] = TILE_SIZE % 256; + } + + /* Generate the packet data for this tile */ + packet_length = GenTileUpdateData(&(FramebufferUpdate_msg[FrameBufferPtr])); + + /* Send the packet data for this tile to the client */ + FrameBufferPtr += packet_length; + + if (FrameBufferPtr > 1460) { + /* Send the data to the client */ + if (VNC_WS_num != -1) { + ws_server_send_bin_client(VNC_WS_num, (char *)FramebufferUpdate_msg, FrameBufferPtr); + } + if (vnc_client_connected) { + if (send(vnc_client_sock, FramebufferUpdate_msg, FrameBufferPtr, 0) != FrameBufferPtr) { + goto wait_for_client; + } + } + FrameBufferPtr = 0; + } + + tile_updated_local[y_pos][x_pos] = 0; /* Clear the update bit for this square */ + } + } + } + + if (FrameBufferPtr > 0) { + /* Last data for this update, send it to the client */ + if (VNC_WS_num != -1) { + ws_server_send_bin_client(VNC_WS_num, (char *)FramebufferUpdate_msg, FrameBufferPtr); + } + if (vnc_client_connected) { + if (send(vnc_client_sock, FramebufferUpdate_msg, FrameBufferPtr, 0) != FrameBufferPtr) { + goto wait_for_client; + } + } + + FrameBufferPtr = 0; + } + + } else { /* if (num_updated_tiles) */ + /* There was no new display data to send to the client */ + /* Sleep for 1/20th second before checking again */ + vTaskDelay(20 / portTICK_PERIOD_MS); + } + + /* Check for sound bell event */ + if (xSemaphoreTake(SoundBell_lock, 10) == pdTRUE) { + if (SoundBellCount) { + --SoundBellCount; + xSemaphoreGive(SoundBell_lock); + + if (vnc_client_connected) { + if (send(vnc_client_sock, sound_bell, 1, 0) != 1) { + goto wait_for_client; + } + } + if (VNC_WS_num != -1) { + ws_server_send_bin_client(VNC_WS_num, sound_bell, 1); + } + } else { + xSemaphoreGive(SoundBell_lock); + } + } + } +} + + + +/** + * Convert a framebuffer pixel to BGR233 if needed. + * @param pixel The 8 bit pixel value + * @return Then unchanged or changed pixel. + */ +vnc_color_t IRAM_ATTR PixelConvert(vnc_color_t pixel) +{ + if (!AltPixels) + return pixel; + + return (((pixel >> RED_SHIFT) & RED_MAX) << Red_Shift) | + (((pixel >> GREEN_SHIFT) & GREEN_MAX) << Green_Shift) | + (((pixel >> BLUE_SHIFT) & BLUE_MAX) << Blue_Shift); +} + + + +/** + * @brief Generate tile update data function + * + * This function is called by the frame_update thread to generate the message + * data for a tile to send to the client. This function expects the + * x-position, y-position, width and height fields of the buffer to be filled + * in before it is called. + * + * The format of the buffer is: + * packet_buffer[0:1] - x-position of tile + * packet_buffer[2:3] - y-position of tile + * packet_buffer[4:5] - width of tile + * packet_buffer[6:7] - height of tile + * packet_buffer[8 onwards] - Pixel data for the tile + * + * The pixel data will be encoded with CoRRE encoding (if the CDL option is + * enabled and the client can handle it) or RAW encoding if that is smaller + * than CoRRE encoding for that particular tile. + * + * @param packet_buffer Buffer to store tile data + * @return Length of generated data in bytes + */ +static int GenTileUpdateData(uint8_t *packet_buffer) +{ + uint16_t x_pos, y_pos; + int i, j; + int tile_width, tile_height; + int packet_length; + static vnc_color_t tile_buffer[TILE_SIZE][TILE_SIZE]; /* Buffer to hold tile to be encoded */ + vnc_color_t pixel_colour; + vnc_color_t bg_colour; + int no_of_subrects, subrect_width, subrect_height; + int k, l; + + no_of_subrects = 0; /* Set to no sub-rectangles to start with */ + packet_length = 20-4+(Bits_Per_Pixel/8); /* Set to minimum packet length to start with */ + + /* Get the X and Y positions of this tile from the packet buffer */ + x_pos = packet_buffer[0] * 256 + packet_buffer[1]; + y_pos = packet_buffer[2] * 256 + packet_buffer[3]; + + /* Get the tile width and height from the packet buffer */ + tile_width = packet_buffer[4] * 256 + packet_buffer[5]; + tile_height = packet_buffer[6] * 256 + packet_buffer[7]; + + /* Set the encoding type to RRE */ + if (!encoding_type.corre) { + /* CoRRE encoding is not supported - just use raw encoding */ + goto use_raw_encoding; + } + + /* Set encoding type to CoRRE encoding in packet buffer */ + packet_buffer[8+0] = 0; + packet_buffer[8+1] = 0; + packet_buffer[8+2] = 0; + packet_buffer[8+3] = 4; + + /* Copy tile from the main frame buffer to the local tile buffer */ + for (i = 0; i < tile_height; i++) { + for (j = 0; j < tile_width; j++) { + tile_buffer[i][j] = frame_buffer[y_pos + i][x_pos + j]; + } + } + + /* Find the background colour */ + /* We just assume the (0, 0) pixel in the tile is the bgcolour */ + /* Its quick!!! */ + bg_colour = frame_buffer[y_pos][x_pos]; + + /* Set the background colour in the packet buffer */ + if (Bits_Per_Pixel == 8) { + packet_buffer[16] = PixelConvert(bg_colour); /* (vnc_color_t) bg_colour; */ + } else { + packet_buffer[16] = COLOUR2BYTE0(bg_colour); + packet_buffer[16+1] = COLOUR2BYTE1(bg_colour); + } + +#ifdef CYGNUM_VNC_SERVER_CORRE_ENCODING_HACK + /* Add an initial sub-rectangle to paint the background the background colour */ + /* This is required because of a known bug in the VNC viewer (x86 version) */ +//#if BITS_PER_PIXEL == 8 + packet_buffer[packet_length] = (vnc_color_t) bg_colour; + packet_length++; +//#endif +//#if BITS_PER_PIXEL == 16 +// packet_buffer[packet_length] = packet_buffer[16]; +// packet_buffer[packet_length+1] = packet_buffer[16+1]; +// packet_length += 2; +//#endif + packet_buffer[packet_length] = (uint8_t) 0; /* Sub-rect x-pos */ + packet_buffer[packet_length+1] = (uint8_t) 0; /* Sub-rect y-pos*/ + packet_buffer[packet_length+2] = (uint8_t) tile_width; /* Sub-rect width*/ + packet_buffer[packet_length+3] = (uint8_t) tile_height; /* Sub-rect height*/ + packet_length += 4; + no_of_subrects++; /* Increment sub-rectangle count */ +#endif + + /* Scan trough tile and find sub-rectangles */ + for (i = 0; i < tile_height; i++) { + for (j = 0; j < tile_width; j++) { + if (tile_buffer[i][j] != bg_colour) { + /* This is a non-background pixel */ + subrect_width = 1; + pixel_colour = tile_buffer[i][j]; + + /* Extend the sub-rectangle to its maximum width */ + for (subrect_width = 1; subrect_width <= tile_width-j-1; subrect_width++) { + if (tile_buffer[i][j+subrect_width] != pixel_colour) { + goto got_subrect_width; + } + } + +got_subrect_width: + + /* Extend the sub-rectangle to its maximum height */ + for (subrect_height=1; subrect_height <= tile_height-i-1; subrect_height++) { + for (k = j; k < j+subrect_width; k++) { + if (tile_buffer[i+subrect_height][k] != pixel_colour) { + goto got_subrect_height; + } + } + } + +got_subrect_height: + + /* Delete the pixels for the sub-rectangle from the sub-rectangle */ + for (k = i; k < i+subrect_height; k++) { + for (l = j; l < j+subrect_width; l++) { + tile_buffer[k][l] = bg_colour; + } + } + + /* Append new sub-rectangle data to the packet buffer */ + if (Bits_Per_Pixel == 8) { + packet_buffer[packet_length] = PixelConvert(pixel_colour); // (vnc_color_t) pixel_colour; + packet_length++; + } else { + packet_buffer[packet_length] = COLOUR2BYTE0(pixel_colour); + packet_buffer[packet_length+1] = COLOUR2BYTE1(pixel_colour); + packet_length += 2; + } + + packet_buffer[packet_length] = (uint8_t) j; /* Sub-rect x-pos */ + packet_length++; + + packet_buffer[packet_length] = (uint8_t) i; /* Sub-rect y-pos*/ + packet_length++; + + packet_buffer[packet_length] = (uint8_t) subrect_width; /* Sub-rect width*/ + packet_length++; + + packet_buffer[packet_length] = (uint8_t) subrect_height; /* Sub-rect height*/ + packet_length++; + + no_of_subrects++; /* Increment sub-rectangle count */ + + if (packet_length >= 12 + tile_height*tile_width*(BITS_PER_PIXEL/8) - 6) { + /* The next sub-rectangle will make the packet size */ + /* larger than a rew encoded packet - so just use raw */ + goto use_raw_encoding; + } + } + } + } + + /* Fill in no_of_sub-rectangles field in packet buffer */ + packet_buffer[12+0] = 0; + packet_buffer[12+1] = 0; + packet_buffer[12+2] = no_of_subrects / 256; + packet_buffer[12+3] = no_of_subrects % 256; + + /* CoRRE data encoding for tile complete */ + return packet_length; + +use_raw_encoding: + + /* Create packet data using RAW encoding */ + for (i = 0; i < tile_height; i++) { + for (j = 0; j < tile_width; j++) { + if (Bits_Per_Pixel == 8) { + packet_buffer[12 + tile_width * i + j] = PixelConvert(frame_buffer[y_pos + i][x_pos + j]); + } else { + packet_buffer[12 + 2 * tile_width * i + 2*j] = COLOUR2BYTE0(frame_buffer[y_pos + i][x_pos + j]); + packet_buffer[12 + 2 * tile_width * i + 2*j+ 1] = COLOUR2BYTE1(frame_buffer[y_pos + i][x_pos + j]); + } + } + } + + /* Set the encoding type to raw */ + packet_buffer[8+0] = 0; + packet_buffer[8+1] = 0; + packet_buffer[8+2] = 0; + packet_buffer[8+3] = 0; + + return (12 + tile_width*tile_height*(Bits_Per_Pixel/8)); +} + + + +/** + * @brief Get message data function + * This function is called by the client_handler thread to get data + * from the client's socket. + * + * @param socket_fd File descriptor of the socket to get the data from. + * @param *buffer Buffer to store received data in. + * @param num_bytes Number of bytes to attempt to get. + * + * @return 1 on sucessfull completion - 0 on error. + */ +static int GetMessageData(int socket_fd, char *buffer, int num_bytes) +{ + int bytes_rxd; + int message_len = 0; + + while (message_len < num_bytes) { + if ((bytes_rxd = recv(socket_fd, buffer, num_bytes, 0)) <= 0) { + return 0; + } + message_len += bytes_rxd; + } + + return 1; +} + + + +void VncCls(vnc_color_t color) +{ + /* Clear the frame buffer */ + int i, j; + + for (i = 0; i < CONFIG_VNC_SERVER_FRAME_HEIGHT; i++) { + for (j = 0; j < CONFIG_VNC_SERVER_FRAME_WIDTH; j++) { + VncDrawPixel(j, i, color); + } + } +} + + + +void IRAM_ATTR VncDrawPixel(uint16_t x, uint16_t y, vnc_color_t color) +{ + if (x >= CONFIG_VNC_SERVER_FRAME_WIDTH || y >= CONFIG_VNC_SERVER_FRAME_HEIGHT) { + printf("write_frame(%d, %d) tile %d/%d\n", x, y, x/TILE_SIZE, y/TILE_SIZE); + return; + } + + /* Set that pixel to 'colour' */ + frame_buffer[y][x] = color; + + /* Mark the tile for update */ + tile_updated[y/TILE_SIZE][x/TILE_SIZE] = 1; +} + + + +void IRAM_ATTR VncDrawHorzLine(uint16_t x1, uint16_t x2, uint16_t y, vnc_color_t color) +{ + int i; + + /* Draw the line */ + for (i = x1; i <= x2; i++) { + VncDrawPixel(i, y, color); + } +} + + + +void IRAM_ATTR VncDrawVertLine(uint16_t x, uint16_t y1, uint16_t y2, vnc_color_t color) +{ + int i; + + /* Draw the line */ + for (i = y1; i <= y2; i++) { + VncDrawPixel(x, i, color); + } +} + + + +void IRAM_ATTR VncFillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, vnc_color_t color) +{ + /* Draw a solid rectangle */ + int i, j; + + for (i = y1; i <= y2; i++) { + for (j = x1; j <= x2; j++) { + VncDrawPixel(j, i, color); + } + } +} + + + +void VncSoundBell(void) +{ + if (xSemaphoreTake(SoundBell_lock, 10) == pdTRUE) { + SoundBellCount++; + xSemaphoreGive(SoundBell_lock); + } +} + + + +/** + * @brief Websocket task. Created on a websocket connection. + */ +void task_WS(void *pvParameter) +{ + int i, j, num_of_encodings, rest_time; + uint16_t *ptr_to_uint16; + struct strMessage _msg; + + // Do pin/pong. + + VNC_WS_run = true; + rest_time = 0; + + while (VNC_WS_run) { + + /* + * Wait until something is received from the client. Exit the queue receiver + * each 5 milliseconds to allow to evaluate the VNC_WS_run flag and to allow + * a clean exit of this task. + */ + if (xQueueReceive(message_queue, &_msg, 5 / portTICK_PERIOD_MS) == pdTRUE) { +// dump_msg((char *)_msg.message, _msg.length, "pull"); + + switch (_msg.message[0]) { + + case SET_PIXEL_FORMAT: +// printf("SET_PIXEL_FORMAT\n"); + /* Check total message length */ + if (_msg.length >= 20) { + /* Check pixel format is as expected */ + i = 0; + if (_msg.message[4] != BITS_PER_PIXEL) { + ESP_LOGI(TAG, "SetPixelFormat client wants %d bits-per-pixel", _msg.message[4]); + i++; + } + + if (_msg.message[5] != PIXEL_DEPTH) { + ESP_LOGI(TAG, "SetPixelFormat client wants %d pixel-depth", _msg.message[5]); + i++; + } + + if ((_msg.message[7] & 0x01) != TRUE_COLOUR_FLAG) { + ESP_LOGI(TAG, "SetPixelFormat client wants %d true-colour-flag", _msg.message[7]); + i++; + } + + ptr_to_uint16 = (uint16_t *)&(_msg.message[8]); + if (htons(*ptr_to_uint16) != Red_Max) { + Red_Max = htons(*ptr_to_uint16); + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d red-max", Red_Max); + } + + ptr_to_uint16 = (uint16_t *)&(_msg.message[10]); + if (htons(*ptr_to_uint16) != Green_Max) { + Green_Max = htons(*ptr_to_uint16); + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d green-max", Green_Max); + } + + ptr_to_uint16 = (uint16_t *)&(_msg.message[12]); + if (htons(*ptr_to_uint16) != Blue_Max) { + Blue_Max = htons(*ptr_to_uint16); + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d blue-max", Blue_Max); + } + + if (_msg.message[14] != Red_Shift) { + Red_Shift = _msg.message[14]; + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d red-shift", Red_Shift); + } + + if (_msg.message[15] != Green_Shift) { + Green_Shift = _msg.message[15]; + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d green-shift", Green_Shift); + } + + if (_msg.message[16] != Blue_Shift) { + Blue_Shift = _msg.message[16]; + AltPixels = true; + ESP_LOGI(TAG, "SetPixelFormat client granted %d blue-shift", Blue_Shift); + } + + if (i) { + ESP_LOGI(TAG, "SetPixelFormat %d errors, disconnect client", i); + ESP_LOGI(TAG, "Ensure the 'Auto select' is not enabled in your vncviewer options,"); + _msg.length = 0; + ws_server_remove_client(VNC_WS_num); + } + } + break; + + case FIX_COLOUR_MAP_ENTRIES: + printf("FIX_COLOUR_MAP_ENTRIES\n"); + break; + + case SET_ENCODINGS: + if (_msg.length >= 3) { + // Get the total encodings + num_of_encodings = _msg.message[2]*255 + _msg.message[3]; + + /* Clear the encoding type structure */ + encoding_type.raw = 0; + encoding_type.copy_rectangle = 0; + encoding_type.rre = 0; + encoding_type.corre = 0; + encoding_type.hextile = 0; + + for (i = 0; i < num_of_encodings; i++) { + switch(_msg.message[3 + (i*4)]) { + case 0: /* Raw encoding */ + encoding_type.raw = i + 1; + break; + case 1: /* Copy rectangle encoding */ + encoding_type.copy_rectangle = i + 1; + break; + case 2: /* RRE encoding */ + encoding_type.rre = i + 1; + break; + case 4: /* CoRRE encoding */ + encoding_type.corre = i + 1; + break; + case 5: /* Hextile encoding */ + encoding_type.hextile = i + 1; + break; + default: /* Unknown coding type - do nothing */ + break; + } + } + + if (encoding_type.corre) { + ESP_LOGI(TAG, "SetEncodings use CORRE"); + } else { + ESP_LOGI(TAG, "SetEncodings use RAW"); + } + } + break; + + case FRAME_BUFFER_UPDATE_REQ: + if (_msg.length == 10) { + if (!_msg.message[1]) { + /* Non-incremental mode - mark the squares that need to be updated */ + for (i = (_msg.message[2]*255 + _msg.message[3])/TILE_SIZE; + i <= (_msg.message[2]*255 + _msg.message[3] + _msg.message[6]*255 + _msg.message[7])/TILE_SIZE; + i++) { + for (j = (_msg.message[4]*255 + _msg.message[5])/TILE_SIZE; + j <= (_msg.message[4]*255 + _msg.message[5] + _msg.message[8]*255 + _msg.message[9])/TILE_SIZE; + j++) { + tile_updated[j][i] = 1; + } + } + } + + /* Signal that there is now a pending update request */ + xEventGroupSetBits(xEventGroupVNC, VNC_CLIENT_UPDATE_REQ); + } + break; + + case KEY_EVENT: + printf("KEY_EVENT\n"); + /* Handle the key event, ignored for brewboard */ + /* Get the remainder of message (7 bytes) */ + break; + + case POINTER_EVENT: + /* Handle the pointer event, simulate the touch screen. */ + if (_msg.length == 6) { + /* Set global variables that will be read by another task. */ + VNC_pointer_button = _msg.message[1]; + VNC_pointer_x = _msg.message[2]*255 + _msg.message[3]; + VNC_pointer_y = _msg.message[4]*255 + _msg.message[5]; +// printf("POINTER_EVENT button=%02x x=%d y=%d\n", VNC_pointer_button, VNC_pointer_x, VNC_pointer_y); + } + _msg.length = 0; + break; + + case CLIENT_CUT_TEXT: + printf("CLIENT_CUT_TEXT\n"); + break; + + default: + ESP_LOGI(TAG, "Unknown message %d from client", _msg.message[0]); + } + rest_time = 0; + } else { + rest_time++; // Each 5 milliseconds + if (rest_time == 2000) { + // 10 seconds silence, send a PING + rest_time = 0; +// int rc = ws_server_ping(VNC_WS_num); +// ESP_LOGI(TAG, "Send PING to %d, rc=%d", VNC_WS_num, rc); + } + } + } + + VNC_WS_num = -1; + xEventGroupClearBits(xEventGroupVNC, VNC_CLIENT_UPDATE_REQ); + ESP_LOGI(TAG, "task_WS finished"); + vTaskDelete(NULL); +} + + + +int VncStartWS(int num) +{ + char protocol_ver[8], message_buffer[MESSAGE_BUFFER_SIZE]; + int i, ProtocolOkay; + uint32_t *ptr_to_uint32; + uint16_t *ptr_to_uint16; + struct strMessage _msg; + + ESP_LOGI(TAG, "Start VNC WebSocket connection %d", num); + message_queue = xQueueCreate(message_queue_size, sizeof(struct strMessage)); + + /* + * Initial handshake + */ + ws_server_send_bin_client(num, "RFB 003.003\n", 12); + ProtocolOkay = 1; + if (xQueueReceive(message_queue, &_msg, 5000 / portTICK_PERIOD_MS) == pdTRUE) { +// dump_msg((char *)_msg.message, _msg.length, "pull"); + + for (i = 0; i < 8; i++) { + if (_msg.message[i] != server_ProtocolVersion[i]) { + ProtocolOkay = 0; + } + /* Store the protocol version - ignoring thr 'RFB ' part */ + protocol_ver[i] = _msg.message[i + 4]; + } + protocol_ver[7] = 0; + } else { + ESP_LOGE(TAG, "Client timeout after initial message"); + return -5; + } + + if (ProtocolOkay == 0) { + /* Generate the Bad ProtocolVerion message */ + ESP_LOGI(TAG, "Start VNC WebSocket task failed, bad protocol."); + ptr_to_uint32 = (uint32_t *) &(message_buffer[0]); + *ptr_to_uint32 = htonl(0); + ptr_to_uint32 = (uint32_t *) &(message_buffer[4]); + *ptr_to_uint32 = htonl(strlen(bad_protocol)); + strcpy(&(message_buffer[8]), bad_protocol); + ws_server_send_bin_client(num, message_buffer, 8 + strlen(bad_protocol)); + vTaskDelay(500 / portTICK_PERIOD_MS); + ws_server_remove_client(num); + return -2; + } + /* ProtocolVerion is okay - connect with no authentication*/ + + /* Server is busy with another client */ + if (VNC_WS_run || vnc_client_connected) { + ESP_LOGI(TAG, "Start VNC WebSocket task failed, server busy."); + ptr_to_uint32 = (uint32_t *) &(message_buffer[0]); + *ptr_to_uint32 = htonl(0); + ptr_to_uint32 = (uint32_t *) &(message_buffer[4]); + *ptr_to_uint32 = htonl(strlen(server_busy)); + strcpy(&(message_buffer[8]), server_busy); + ws_server_send_bin_client(num, message_buffer, 8 + strlen(server_busy)); + vTaskDelay(500 / portTICK_PERIOD_MS); + ws_server_remove_client(num); + return -1; + } + + /* Generate the No Authentication message */ + ptr_to_uint32 = (uint32_t *) &(message_buffer[0]); + *ptr_to_uint32 = htonl((uint32_t)1); + ws_server_send_bin_client(num, message_buffer, 4); + /* Authentication - end */ + + /* ClientInitialisation - begin */ + /* Receive initialisation message from client (1 byte) */ + if (xQueueReceive(message_queue, &_msg, 5000 / portTICK_PERIOD_MS) == pdFALSE) { + ESP_LOGE(TAG, "Client timeout after auth message"); + return -5; + } + /* Do nothing with this as we only support 1 Client at a time */ + /* ClientInitialisation - end */ + + /* Initial default RGB332 */ + Red_Max = RED_MAX; + Green_Max = GREEN_MAX; + Blue_Max = BLUE_MAX; + Red_Shift = RED_SHIFT; + Green_Shift = GREEN_SHIFT; + Blue_Shift = BLUE_SHIFT; + AltPixels = false; + + /* ServerInitialisation - begin */ + /* Create Initialisation message for client */ + ptr_to_uint16 = (uint16_t *) &(message_buffer[0]); + *ptr_to_uint16 = htons((uint16_t)CONFIG_VNC_SERVER_FRAME_WIDTH); + + ptr_to_uint16 = (uint16_t *) &(message_buffer[2]); + *ptr_to_uint16 = htons((uint16_t)CONFIG_VNC_SERVER_FRAME_HEIGHT); + + message_buffer[4] = (uint8_t)BITS_PER_PIXEL; + message_buffer[5] = (uint8_t)PIXEL_DEPTH; + message_buffer[6] = (uint8_t)BIG_ENDIAN_FLAG; + message_buffer[7] = (uint8_t)TRUE_COLOUR_FLAG; + + ptr_to_uint16 = (uint16_t *) &(message_buffer[8]); + *ptr_to_uint16 = htons(Red_Max); + + ptr_to_uint16 = (uint16_t *) &(message_buffer[10]); + *ptr_to_uint16 = htons(Green_Max); + + ptr_to_uint16 = (uint16_t *) &(message_buffer[12]); + *ptr_to_uint16 = htons(Blue_Max); + + message_buffer[14] = Red_Shift; + message_buffer[15] = Green_Shift; + message_buffer[16] = Blue_Shift; + + ptr_to_uint32 = (uint32_t *) &(message_buffer[20]); + *ptr_to_uint32 = htonl(strlen(desktop_name)); + strcpy(&(message_buffer[24]), desktop_name); + +// dump_msg(message_buffer, 24 + strlen(desktop_name), (char *)"send"); + ws_server_send_bin_client(num, message_buffer, 24 + strlen(desktop_name)); + + /* ServerInitialisation - end */ + ESP_LOGI(TAG, "VNC WebSocket client connected (RFB Protocol Ver: %s)", protocol_ver); + + VNC_WS_num = num; + xTaskCreate(&task_WS, "WSclient", MIN_STACK_SIZE, NULL, 5, &xTaskWSclient); + return 0; +} + + + +void VncStopWS(int num) +{ + if (! VNC_WS_run) { + ESP_LOGI(TAG, "Stop VNC WebSocket, not running."); + return; + } + + if (num != VNC_WS_num) { + ESP_LOGI(TAG, "Stop VNC WebSocket, %d is running, requested %d", VNC_WS_num, num); + return; + } + + ESP_LOGI(TAG, "Stop VNC WebSocket task connection %d", num); + VNC_WS_run = false; + xQueueReset(message_queue); +} + + + +void VncGetWSmessage(char *msg, uint16_t len) +{ + int max; + struct strMessage _msg; + + if (len == 0) + return; + + max = len; + if (max > MESSAGE_BUFFER_SIZE) { + ESP_LOGE(TAG, "VncGetWSmessage need %d bytes in a %d length buffer", len, MESSAGE_BUFFER_SIZE); + max = MESSAGE_BUFFER_SIZE; + } + + for (int i = 0; i < max; i++) { + _msg.message[i] = msg[i]; + } + _msg.length = max; + if (xQueueSendToBack(message_queue, &_msg, 200 / portTICK_PERIOD_MS) == pdFALSE) { + ESP_LOGE(TAG, "VncGetWSmessage() Queue full, message lost"); + } +} +