draw_trad_img git · master
Live viewer for streamed trading chart images (SSL client)
C 100%git clone https://git.christianimmanuel.de/sdl-graphics/draw_trad_img.gitwget https://git.christianimmanuel.de/sdl-graphics/draw_trad_img/archive/draw_trad_img.tar.gz..
view_img.c | 829 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 829 insertions(+) diff --git a/view_img.c b/view_img.c new file mode 100644 index 0000000..fd95901 --- /dev/null +++ b/view_img.c @@ -0,0 +1,829 @@ +// gcc -o view_image view_img.c -lSDL3 -lSDL3_image -lSDL3_ttf -ljansson + + + +#include <SDL3/SDL.h> +#include <SDL3_image/SDL_image.h> +#include <SDL3_ttf/SDL_ttf.h> + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <sys/stat.h> // Include this header for 'stat' +#include <string.h> +#include <jansson.h> // For JSON handling +#include <ctype.h> +#include <arpa/inet.h> +#include <endian.h> // For be64toh() on some systems; on others use <arpa/inet.h> if available. +#include <dirent.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <sys/time.h> + + + +#define SERVER_PORT 9999 +#define SERVER_IP "162.19.227.194" // Server IP address + +#define HEADER_SIZE 8 +#define BUF_SIZE 1024 + + +#define CONFIG_FILE "config_draw.json" +#define MAX_INPUT_LENGTH 256 + +int window_width = 0, window_height = 0; + +int legend_is_active = 1; + +int usleep(__useconds_t usec); +char* strdup(const char*); +int symlink(const char *target, const char *linkpath); +uint64_t be64toh(uint64_t big_endian_64bits); + +// Global variables (make sure these are defined in one source file) + + + +#include <time.h> +#include <pthread.h> + + +pthread_mutex_t request_mutex = PTHREAD_MUTEX_INITIALIZER; + +typedef enum { + R_TYPE_UNKNOWN = 0, + R_TYPE_IMAGE, + R_TYPE_CONFIG +} request_type_t; + +typedef enum { + R_STAT_UNKNOWN = -1, + R_STAT_STOPPED, + R_STAT_STARTED, + R_STAT_RUNNING, + R_STAT_DONE +} request_status_t; + +typedef struct { + int success; + char message[256]; + pthread_t thread; + request_type_t type; + request_status_t status; + void *response_value; +} request_t; + + + +request_t request_global = {0, "\0", 0, R_TYPE_UNKNOWN, R_STAT_STOPPED, NULL}; + +int file_exists(const char *path) { + struct stat buffer; + return (stat(path, &buffer) == 0); +} + +void copy_file(const char *source, const char *destination) { + FILE *src = fopen(source, "rb"); + if (!src) return; + + FILE *dest = fopen(destination, "wb"); + if (!dest) { + fclose(src); + return; + } + + char buffer[4096]; + size_t bytes; + while ((bytes = fread(buffer, 1, sizeof(buffer), src)) > 0) { + fwrite(buffer, 1, bytes, dest); + } + + fclose(src); + fclose(dest); +} + +void linkConfig(const char *target, const char *linkpath) { + // Remove existing symlink or file if it exists + if (access(linkpath, F_OK) == 0) { + if (unlink(linkpath) == -1) { + perror("unlink failed"); + return; + } + } + + // Create new symlink + if (symlink(target, linkpath) == -1) { + perror("symlink failed"); + } else { + printf("Symbolic link created: %s -> %s\n", target, linkpath); + } +} + + +#define IMAGE_DIR "images" // Directory where images are stored + +// Function to find the most recently modified image in the folder +const char* getLatestImagePath() { + DIR *dir = opendir(IMAGE_DIR); + if (!dir) { + perror("Failed to open directory"); + return NULL; + } + + struct dirent *entry; + struct stat file_stat; + time_t latest_time = 0; + const char *latest_file = NULL; + + while ((entry = readdir(dir)) != NULL) { + // Build the full path for the current file + char filepath[256]; + snprintf(filepath, sizeof(filepath), "%s/%s", IMAGE_DIR, entry->d_name); + + // Use stat() to get file information and check if it's a regular file + if (stat(filepath, &file_stat) == 0 && S_ISREG(file_stat.st_mode)) { + // Check if this file is more recent than the current latest + if (file_stat.st_mtime > latest_time) { + latest_time = file_stat.st_mtime; + latest_file = strdup(filepath); // Save the latest file path + } + } + } + closedir(dir); + + return latest_file; // Return the path of the most recently modified image +} + + +void render_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, int x, int y) { + if (!font) { // Check if font is loaded + printf("Error: Font not loaded!\n"); + return; + } + + if (!text || *text == '\0') { // Check if text is empty + return; + } + + SDL_Color textColor = {0, 0, 0, 255}; // Black text color + SDL_Surface *surface = TTF_RenderText_Blended_Wrapped(font, text, strlen(text), textColor, window_width); + + if (!surface) { // Check if text rendering failed + printf("Error: Could not render text! SDL_ttf error: %s\n", SDL_GetError()); + return; + } + + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { // Check if texture creation failed + printf("Error: Could not create texture! SDL error: %s\n", SDL_GetError()); + SDL_DestroySurface(surface); + return; + } + + SDL_FRect dest = {x, y, surface->w, surface->h}; + + // Draw white background behind text + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 187); // White color + SDL_RenderFillRect(renderer, &dest); + + // Render text over the white background + SDL_RenderTexture(renderer, texture, NULL, &dest); + + SDL_DestroySurface(surface); + SDL_DestroyTexture(texture); +} + + +// Function to check if the file has changed based on modification time +int file_has_changed(const char *filename, time_t *last_mod_time) { + struct stat file_stat; + if (stat(filename, &file_stat) == -1) { + perror("stat"); + return 0; + } + + if (file_stat.st_mtime > *last_mod_time) { + *last_mod_time = file_stat.st_mtime; + return 1; + } + return 0; +} + + + +char *foldJson(char *json_str) { + size_t len = strlen(json_str); + char *result = malloc(len + 1); // worst-case: no newlines removed + if (!result) return NULL; + + int array_nesting = 0; + int in_string = 0; // Are we inside a string literal? + int escape = 0; // Is the previous char a backslash? + size_t out_index = 0; + + int inside_first_object = 0; + + for (size_t i = 0; i < len; i++) { + char c = json_str[i]; + + // Handle string literal boundaries and escapes. + if (in_string) { + result[out_index++] = c; + if (escape) { + escape = 0; + } else if (c == '\\') { + escape = 1; + } else if (c == '"') { + in_string = 0; + } + continue; + } else { + if (c == '"') { + in_string = 1; + result[out_index++] = c; + continue; + } + } + + if (!inside_first_object && c == '{') { + inside_first_object = 1; + continue; + } + + // Update array nesting outside of strings. + if (c == '[' || c == '{') { + array_nesting++; + result[out_index++] = c; + continue; + } else if (c == ']' || c == '}') { + if (array_nesting > 0) + array_nesting--; + result[out_index++] = c; + continue; + } + + if (array_nesting > 0 && result[out_index-1] == ',' && result[out_index-2] == '}' ) { + result[out_index++] = '\n'; + for (int i = 0; i < 8; i++) + result[out_index++] = ' '; + continue; + } + // If we are inside an array and encounter a newline or carriage return... + if ((c == '\n' || c == '\r' || c == ' ' ) && array_nesting > 0) { + // Option: replace newline with a single space (if not already a space) + if (out_index > 0 && result[out_index - 1] != ' ') + result[out_index++] = ' '; + // Otherwise, simply skip the newline character. + continue; + } + + // Otherwise, just copy the character. + result[out_index++] = c; + } + + result[out_index] = '\0'; + return result; +} + + +void receiveImage(uint64_t img_size, int sockfd) { + size_t total_received = 0; + unsigned char *img_data = malloc(img_size); + if (!img_data) { + perror("malloc failed"); + return; + } + + char buffer[BUF_SIZE]; + + while (total_received < img_size) { + ssize_t len = recvfrom(sockfd, buffer, BUF_SIZE, 0, NULL, NULL); + if (len < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + fprintf(stderr, "Timeout waiting for image data.\n"); + continue; // Retry instead of returning immediately + } else { + perror("recvfrom failed"); + free(img_data); // Prevent memory leak + return; + } + } + if (len == 0) { + fprintf(stderr, "Unexpected connection closure\n"); + free(img_data); + return; + } + + size_t bytes_to_copy = (total_received + len > img_size) + ? (img_size - total_received) + : len; + memcpy(img_data + total_received, buffer, bytes_to_copy); + total_received += bytes_to_copy; + } + + if (total_received != img_size) { + fprintf(stderr, "Incomplete image received: expected %llu, got %zu bytes\n", + (unsigned long long)img_size, total_received); + free(img_data); + return; + } + + printf("Received complete image (%zu bytes)\n", total_received); + + + // --- Create an SDL_Surface from the Received Data --- + SDL_IOStream *rw = SDL_IOFromMem(img_data, total_received); + if (rw) { + SDL_Surface *new_surface = IMG_Load_IO(rw, true); + if (new_surface) { + pthread_mutex_lock(&request_mutex); + request_global.success = 1; + request_global.response_value = (void*)new_surface; + pthread_mutex_unlock(&request_mutex); + } else { + printf("IMG_Load_RW failed: %s\n", SDL_GetError()); + } + } else { + printf("SDL_RWFromMem failed: %s\n", SDL_GetError()); + } + + free(img_data); +} + +void receiveConfig(uint64_t config_size, int sockfd) { + // Allocate buffer and receive full config + printf("receiveConfig() START\n"); + char *config_data = malloc(config_size + 1); + if (!config_data) { + perror("malloc failed"); + return; + } + + size_t total_received = 0; + while (total_received < config_size) { + ssize_t chunk = recvfrom(sockfd, config_data + total_received, + config_size - total_received, 0, NULL, NULL); + if (chunk <= 0) { + perror("recvfrom failed"); + free(config_data); + return; + } + total_received += chunk; + } + config_data[total_received] = '\0'; // Null-terminate JSON string + + // Parse JSON + printf("Received JSON string:\n%s\n", config_data); + + json_t *root; + json_error_t error; + root = json_loads(config_data, 0, &error); + free(config_data); // Free buffer after parsing + + if (!root) { + fprintf(stderr, "JSON parse error: %s\n", error.text); + return; + } + + pthread_mutex_lock(&request_mutex); + request_global.success = 1; + request_global.response_value = (void*)root; + pthread_mutex_unlock(&request_mutex); + + + // Example: Extract values + const char *symbol = json_string_value(json_object_get(root, "symbol")); + const char *interval = json_string_value(json_object_get(root, "interval")); + printf("Config Received:\n Symbol: %s\n Interval: %s\n", + symbol ? symbol : "NULL", interval ? interval : "NULL"); + + printf("receiveConfig() END\n"); +} + + + + +// This is the thread function that sends the UDP request and receives the response. +void* apiThread_func(void* arg) { + // For demonstration, we print the request and type. + // In your real code, this function will do the UDP sendto/recvfrom logic. + // Send a UDP request for an updated image. + printf("Request Api\n"); + char request[256] = "\0"; + pthread_mutex_lock(&request_mutex); + strcpy(request, (const char*)request_global.message); + request_global.success = 0; + request_global.status = R_STAT_RUNNING; + pthread_mutex_unlock(&request_mutex); + + printf("API thread started with request: %s\n", request); + + // --- Begin UDP request/response logic --- + // Example: send request, wait for header, then process the response. + // (Replace the following sleep with your actual UDP code.) + + + // Setup UDP socket once for the thread. + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("Socket creation failed"); + return NULL; + } + + // Set a timeout on recvfrom calls (e.g., 2 seconds). + struct timeval tv; + tv.tv_sec = 6; + tv.tv_usec = 0; + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + perror("Error setting socket timeout"); + } + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(SERVER_PORT); + server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); + + + char trys = 0; + while (1) { // This loop shal never run twice actually... troll its for breaking -> GOTO + trys++; + if (strlen(request) == 0) break;; + printf("(%3d)Request string -> %s\n", trys, request); + + ssize_t s = sendto(sockfd, request, strlen(request), 0, // ✅ Fix: Use strlen() + (struct sockaddr *)&server_addr, sizeof(server_addr)); + + if (s < 0) { + perror("sendto failed"); + usleep(100 * 1000); // 100ms delay befor sending it again + continue; + } + + unsigned char header[12] = {0}; + ssize_t header_len = recvfrom(sockfd, header, sizeof(header), 0, NULL, NULL); + if (header_len < 12) { + usleep(100 * 1000); // 100ms delay befor sending it again + fprintf(stderr, "Header not received properly. Received %zd bytes.\n", header_len); + break; + } + + // Parse the type (first 4 bytes) + char type[5] = {0}; + memcpy(type, header, 4); + uint64_t expected_size; + memcpy(&expected_size, header + 4, 8); + expected_size = be64toh(expected_size); + + if (strncmp(type, "IMG ", 4) == 0) { + // It's an image. Now extract the size from the next 8 bytes. + printf("Receiving image of size: %llu bytes\n", (unsigned long long)expected_size); + receiveImage(expected_size, sockfd); + } else if (strncmp(type, "CONF", 4) == 0) { + // It's a config update. + // char config_data[1024] = {0}; + printf("Receiving Config\n"); + receiveConfig(expected_size, sockfd); + } else { + printf("Unknown message type: %s\n", type); + } + + printf("Request data received.\n"); + + + break; // never run twice! + } + close(sockfd); + // --- End UDP request/response logic --- + + + pthread_mutex_lock(&request_mutex); + request_global.status = R_STAT_DONE; + pthread_mutex_unlock(&request_mutex); + return NULL; +} + +// Call this function to start a new API thread if one is not already running. +void startApiThread(const char *req_api) { + // Assume that the global request_api and request_type are updated elsewhere. + request_status_t status = R_STAT_UNKNOWN; + pthread_mutex_lock(&request_mutex); + if (!strcmp(req_api, "image")) + request_global.type = R_TYPE_IMAGE; + else + request_global.type = R_TYPE_CONFIG; + strcpy(request_global.message, req_api); + status = request_global.status; + request_global.status = R_STAT_STARTED; + pthread_mutex_unlock(&request_mutex); + if (status > 0) return; // Already running + if (pthread_create(&request_global.thread, NULL, apiThread_func, NULL) != 0) { + perror("pthread_create failed"); + } +} + +// Call this function in your main loop to check if the API thread has finished. +// If it has, join it and return 1; otherwise return 0. +int prooveAndFinishAbiThread() { + request_status_t done = R_STAT_UNKNOWN; + pthread_t thread; + pthread_mutex_lock(&request_mutex); + done = request_global.status; + thread = request_global.thread; + pthread_mutex_unlock(&request_mutex); + + if (done != R_STAT_DONE) + return 0; // Still Running + + // The thread has finished; join it to clean up resources. + if (pthread_join(thread, NULL) != 0) { + perror("Proove finished but pthread_join failed"); + } + + pthread_mutex_lock(&request_mutex); + request_global.status = R_STAT_STOPPED; + pthread_mutex_unlock(&request_mutex); + return 1; // ALL DONE +} + + +int getConfInt(json_t* config, char k[]) { + json_t* json_value = json_object_get(config, k); + if (json_value && json_is_integer(json_value)) { + return json_integer_value(json_value); + } else { + char* s = json_dumps(config, JSON_INDENT(4)); + fprintf(stderr, "%s", s); + free(s); + printf("Error: '%s' not found or not an integer!\n", k); + } + return 0; +} + +int main() { + SDL_Surface* surface = NULL; + json_t* config = NULL; + + startApiThread("legend_is_active 1"); + + printf("Loading Config."); + while (!config) { + printf("."); + if (prooveAndFinishAbiThread()) { + pthread_mutex_lock(&request_mutex); + config = (json_t*)request_global.response_value; + pthread_mutex_unlock(&request_mutex); + printf("Config loaded!\n"); + } + sleep(1); + } + printf("\n"); + + + + // Initialize SDL + if (!SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); + json_decref(config); + return 1; + } + + /* + // Initialize SDL_image + if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) { + fprintf(stderr, "SDL_image could not initialize! IMG_Error: %s\n", IMG_GetError()); + SDL_Quit(); + json_decref(config); + return 1; + } + */ + + if (!TTF_Init()) { + fprintf(stderr, "TTF_Init could not initialize! TTF_Error: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + // Get image dimensions + int img_width = 1200; + int img_height = 680; + + // Create window with the SDL_WINDOW_RESIZABLE flag for resizable window + SDL_Window *window = SDL_CreateWindow("render_graph", img_width, img_height, SDL_WINDOW_RESIZABLE); + if (!window) { + fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError()); + SDL_DestroySurface(surface); + SDL_Quit(); + json_decref(config); + return 1; + } + + // Create renderer + SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL); + if (!renderer) { + fprintf(stderr, "Renderer could not be created! SDL_Error: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_DestroySurface(surface); + SDL_Quit(); + json_decref(config); + return 1; + } + + + TTF_Font *font = TTF_OpenFont("/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf", 24); + + SDL_Texture *texture; + + int quit = 0; + char input_text[MAX_INPUT_LENGTH] = ""; + SDL_Event e; + + SDL_StartTextInput(window); + + float mouseX, mouseY; + + char new_request[256] = "\0"; + + // Main loop + while (!quit) { + int render_position = getConfInt(config, "render_position"); + int limit = getConfInt(config, "limit"); + int candles = getConfInt(config, "candles"); + + + // Handle events + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { + quit = 1; + } else if (e.type == SDL_EVENT_TEXT_INPUT) { + if (strlen(input_text) + strlen(e.text.text) < MAX_INPUT_LENGTH) { + printf("TextInput %s\n", input_text); + strcat(input_text, e.text.text); + } + } else if (e.type == SDL_EVENT_KEY_DOWN && strlen(input_text) > 0) { + switch (e.key.key) { + case SDLK_LEFT: { + int n = (render_position + candles/8 < limit - candles) ? render_position + candles/8 : render_position; + if (!strlen(new_request)) + sprintf(new_request, "render_position %d", n); + } + case SDLK_RIGHT: { + int n = (render_position - candles/8 >= 0) ? render_position - candles/8 : 0; + if (!strlen(new_request)) + sprintf(new_request, "render_position %d", n); + } + case SDLK_UP: { + int n = (candles - candles/8 > candles/8) ? candles - candles/8 : candles; + if (!strlen(new_request)) + sprintf(new_request, "candles %d", n); + } + case SDLK_DOWN: { + int n = (candles + candles/8 <= limit) ? candles + candles/8 : candles; + if (!strlen(new_request)) + sprintf(new_request, "candles %d", n); + } + case SDLK_BACKSPACE: { + input_text[strlen(input_text) - 1] = '\0'; + break; + } + case SDLK_RETURN: { + if (strcmp(input_text, "m") == 0) { + legend_is_active = !legend_is_active; + snprintf(input_text, sizeof(input_text), "legend_is_active %d", legend_is_active); + } + if (!strlen(new_request)) + strcpy(new_request, input_text); + input_text[0] = '\0'; + } + default: + break; + } +#undef COPY_TO_REQUEST + } + } + + request_t request_copy; + pthread_mutex_lock(&request_mutex); + request_copy = request_global; + pthread_mutex_unlock(&request_mutex); + switch (request_copy.status) { + case R_STAT_STOPPED: { + char request[256]; + if (!request_copy.success) + strcpy(request, request_copy.message); + else if (new_request[0] != '\0') { + strcpy(request, new_request); + new_request[0] = '\0'; + } else { + strcpy(request, "image"); + } + + startApiThread(request); + break; } + case R_STAT_DONE: { + if (prooveAndFinishAbiThread()) { + if (request_copy.success) { + switch (request_copy.type) { + case R_TYPE_IMAGE: { + printf("''''NEW IMAGE!\n"); + SDL_Surface* surface_new = (SDL_Surface*)request_copy.response_value; + SDL_DestroySurface(surface); + surface = SDL_ConvertSurface(surface_new, surface_new->format); + SDL_DestroySurface(surface_new); + break; } + case R_TYPE_CONFIG: { + printf("''''NEW CONFIG!\n"); + json_t* config_new = (json_t*)request_copy.response_value; + json_decref(config); + config = json_copy(config_new); + json_decref(config_new); + continue; } + default: break; + } + } + } + break; } + default: break; + + } + + + + + + if (!surface) continue; + + img_width = surface->w; + img_height = surface->h; + + SDL_DestroyTexture(texture); + texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + fprintf(stderr, "Unable to create texture! SDL_Error: %s\n", SDL_GetError()); + } + + + + + // Get current window size + SDL_GetWindowSize(window, &window_width, &window_height); + + // Create a scaling factor for the image based on the window size + float scale_x = (float)window_width / img_width; + float scale_y = (float)window_height / img_height; + float scale = (scale_x < scale_y) ? scale_x : scale_y; // Use the smallest scale factor to fit the image + + // Adjust the image size based on the scale factor + int new_width = img_width * scale; + int new_height = img_height * scale; + + // Clear screen and render the texture scaled to fit the window + SDL_RenderClear(renderer); + SDL_FRect dest_rect = { 0, 0, new_width, new_height }; + SDL_RenderTexture(renderer, texture, NULL, &dest_rect); + + // Text rendern + if (strlen(input_text) > 0) { + render_text(renderer, font, input_text, 20, 40); + // Handle user input to update config + char* console_text = input_text; + char* console_text_ = json_dumps(config, JSON_INDENT(4)); + // printf("%s", console_text_); return 1; + console_text = foldJson(console_text_); + free(console_text_); + + render_text(renderer, font, console_text, 20, 60); + render_text(renderer, font, input_text, 20, 40); + // Reset renderer state before drawing mouse pattern + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // Example: set draw color to black + } + + + // Draw X as a dotted pattern + SDL_GetMouseState(&mouseX, &mouseY); + for (int i = -img_width; i <= img_width; i += 4) { + SDL_RenderPoint(renderer, mouseX + i, mouseY - 10); // Draw main diagonal + SDL_RenderPoint(renderer, mouseX - 8, mouseY - i); // Draw anti-diagonal + } + + + SDL_RenderPresent(renderer); + + // Delay to avoid overloading the CPU + SDL_Delay(16); + } + + // Clean up + TTF_CloseFont(font); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + TTF_Quit(); + SDL_Quit(); + json_decref(config); + return 0; +}