Nimbin[12]?SDL & Graphics / draw_trad_img / view_img.c

draw_trad_img git · master

Live viewer for streamed trading chart images (SSL client)

c images trading ssl · first commit 2025-03-06 · last commit 2025-03-06 (1 year ago) · synced 3 days ago

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 25.8 KB · 829 lines 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;
}