Mon, 29 Oct 2018 19:37:14 +0100
Swapped MLT and HLT SSR pins to match he real hardware. More doxygen changes.
/** * @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 <cgarry@sweeneydesign.co.uk> * @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 ///< Tile dimension #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 ///< Red maximum #define GREEN_MAX 7 ///< Green maximum #define BLUE_MAX 3 ///< Blue maximum, together 8 bits. #define RED_SHIFT 5 ///< Red shift in color byte #define GREEN_SHIFT 2 ///< Green shift in color byte #define BLUE_SHIFT 0 ///< Blue shift in color byte /* Initial default RGB332 */ uint8_t Bits_Per_Pixel = 8; ///< Current number of bits per pixel uint16_t Red_Max = RED_MAX; ///< Current Red maxmimum uint16_t Green_Max = GREEN_MAX; ///< Current Green maximum uint16_t Blue_Max = BLUE_MAX; ///< Current Blue maximum uint8_t Red_Shift = RED_SHIFT; ///< Current Red bits shift uint8_t Green_Shift = GREEN_SHIFT; ///< Current Green bits shift uint8_t Blue_Shift = BLUE_SHIFT; ///< Current Blue bits shift bool AltPixels = false; ///< Alternate the pixels /* Client to Server message types */ #define SET_PIXEL_FORMAT 0 ///< Set pixel format #define FIX_COLOUR_MAP_ENTRIES 1 ///< Fix color map entries (not used) #define SET_ENCODINGS 2 ///< Set encodings #define FRAME_BUFFER_UPDATE_REQ 3 ///< Request frame buffer update #define KEY_EVENT 4 ///< Keyboard event (not used) #define POINTER_EVENT 5 ///< Pointer event, translated to touch events #define CLIENT_CUT_TEXT 6 ///< Text editing, not used. /* Macros to split colour to bytes */ #define COLOUR2BYTE1(col) ((col>>8)&0xFF) ///< High part #define COLOUR2BYTE0(col) (col&0xFF) ///< Low part /* 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 /** * @brief VNC messages. */ struct strMessage { int length; ///< Length of the message uint8_t message[MESSAGE_BUFFER_SIZE]; ///< The message }; uint8_t VNC_pointer_button = 0; ///< Button mask for the mouse pointer uint16_t VNC_pointer_x = 0; ///< Mouse position X uint16_t VNC_pointer_y = 0; ///< Mouse position Y /* Define size of each thread's stack */ #define MIN_STACK_SIZE 3072 ///< Minimal task stack size /** * @brief Messages */ static char server_ProtocolVersion[] = "RFB 003.003\n"; static char bad_protocol[] = "Unsupported ProtocolVersion"; static char server_busy[] = "Server is Busy"; static char sound_bell[] = "\2"; static char desktop_name[] = "MBSE BrewBoard"; // Hardcoded, don't change or the VNC webclient breaks. /** * @brief 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) ///< Nr of tiles on the Y axis. #define LAST_TILE_HEIGHT TILE_SIZE ///< Height of the last tile. #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) ///< Nr of tiles on the X axis. #define LAST_TILE_WIDTH TILE_SIZE ///< Width of the last tile. #endif /** * @brief Array for marking tiles that have been updated */ int tile_updated[NUM_TILES_Y_AXIS+1][NUM_TILES_X_AXIS+1]; EventGroupHandle_t xEventGroupVNC; ///< Variable to signal that a client is connected and initialised const int VNC_CLIENT_UPDATE_REQ = BIT0; ///< Client update request event int vnc_client_sock = -1; ///< Client network socket bool vnc_client_connected = false; ///< Client connected? int SoundBellCount; ///< Count the client's bell bool VNC_WS_run = false; ///< Websocket running int VNC_WS_num = -1; ///< Websocket connection number /** * @brief 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 /** * @brief 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; } } 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; /* 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]; /* 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; uint16_t *ptr_to_uint16; struct strMessage _msg; VNC_WS_run = true; 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) { switch (_msg.message[0]) { case SET_PIXEL_FORMAT: /* 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; } } } 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]; } _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]); } } } 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"); } }