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.gzview_img.c raw
// 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;
}