// gcc -o view_image view_img.c -lSDL3 -lSDL3_image -lSDL3_ttf -ljansson #include #include #include #include #include #include #include // Include this header for 'stat' #include #include // For JSON handling #include #include #include // For be64toh() on some systems; on others use if available. #include #include #include #include #include #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 #include 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; }