/* * Prueba de integración contra un servidor X11 real (por ejemplo Xvfb). * * Una segunda conexión al servidor actúa de "observador": localiza la ventana * de FnixLib, le inyecta eventos con XSendEvent y lee de vuelta con XGetImage * los píxeles que realmente llegaron a la ventana. * * Si DISPLAY no está definido, la prueba se omite (código de salida 77). */ #include "check.h" #include #include #include #include #include #include #define TITLE "FnixLib test window" #define WIDTH 320 #define HEIGHT 240 /* Procesa eventos hasta que `cond` sea verdadera (máximo ~1 s). */ #define PUMP_UNTIL(window, cond, ok) \ do { \ (ok) = false; \ for (int attempt_ = 0; attempt_ < 200 && !(ok); attempt_++) { \ fnix_begin_frame(window); \ if (cond) { \ (ok) = true; \ } else { \ fnix_time_sleep(0.005); \ } \ } \ } while (0) static Window find_window(Display *dpy, Window parent, const char *name) { char *fetched = NULL; if (XFetchName(dpy, parent, &fetched) && fetched != NULL) { const bool match = strcmp(fetched, name) == 0; XFree(fetched); if (match) { return parent; } } Window root_return, parent_return, *children = NULL; unsigned count = 0; if (!XQueryTree(dpy, parent, &root_return, &parent_return, &children, &count)) { return 0; } Window found = 0; for (unsigned i = 0; i < count && found == 0; i++) { found = find_window(dpy, children[i], name); } if (children != NULL) { XFree(children); } return found; } static Window wait_for_window(Display *dpy, const char *name) { for (int i = 0; i < 200; i++) { const Window w = find_window(dpy, DefaultRootWindow(dpy), name); if (w != 0) { return w; } fnix_time_sleep(0.005); } return 0; } static bool wait_viewable(Display *dpy, Window w) { for (int i = 0; i < 200; i++) { XWindowAttributes attrs; if (XGetWindowAttributes(dpy, w, &attrs) && attrs.map_state == IsViewable) { return true; } fnix_time_sleep(0.005); } return false; } /* Lee un píxel de la ventana tal como lo tiene el servidor. */ static unsigned long server_pixel(Display *dpy, Window w, int x, int y) { XImage *image = XGetImage(dpy, w, x, y, 1, 1, AllPlanes, ZPixmap); if (image == NULL) { return ~0UL; } const unsigned long p = XGetPixel(image, 0, 0) & 0xFFFFFFUL; XDestroyImage(image); return p; } /* Espera (hasta ~1 s) a que el píxel del servidor tenga el valor esperado. */ static bool server_pixel_becomes(Display *dpy, Window w, int x, int y, unsigned long expected) { for (int i = 0; i < 200; i++) { if (server_pixel(dpy, w, x, y) == expected) { return true; } fnix_time_sleep(0.005); } return false; } static void send_key(Display *obs, Window win, KeySym keysym, bool press) { XEvent ev; memset(&ev, 0, sizeof ev); ev.xkey.type = press ? KeyPress : KeyRelease; ev.xkey.display = obs; ev.xkey.window = win; ev.xkey.root = DefaultRootWindow(obs); ev.xkey.same_screen = True; ev.xkey.keycode = XKeysymToKeycode(obs, keysym); XSendEvent(obs, win, False, press ? KeyPressMask : KeyReleaseMask, &ev); XFlush(obs); } static void send_motion(Display *obs, Window win, int x, int y) { XEvent ev; memset(&ev, 0, sizeof ev); ev.xmotion.type = MotionNotify; ev.xmotion.display = obs; ev.xmotion.window = win; ev.xmotion.root = DefaultRootWindow(obs); ev.xmotion.same_screen = True; ev.xmotion.x = x; ev.xmotion.y = y; ev.xmotion.is_hint = NotifyNormal; XSendEvent(obs, win, False, PointerMotionMask, &ev); XFlush(obs); } static void send_button(Display *obs, Window win, unsigned button, bool press, int x, int y) { XEvent ev; memset(&ev, 0, sizeof ev); ev.xbutton.type = press ? ButtonPress : ButtonRelease; ev.xbutton.display = obs; ev.xbutton.window = win; ev.xbutton.root = DefaultRootWindow(obs); ev.xbutton.same_screen = True; ev.xbutton.button = button; ev.xbutton.x = x; ev.xbutton.y = y; XSendEvent(obs, win, False, press ? ButtonPressMask : ButtonReleaseMask, &ev); XFlush(obs); } static void send_close_request(Display *obs, Window win) { XEvent ev; memset(&ev, 0, sizeof ev); ev.xclient.type = ClientMessage; ev.xclient.display = obs; ev.xclient.window = win; ev.xclient.message_type = XInternAtom(obs, "WM_PROTOCOLS", False); ev.xclient.format = 32; ev.xclient.data.l[0] = (long)XInternAtom(obs, "WM_DELETE_WINDOW", False); ev.xclient.data.l[1] = CurrentTime; XSendEvent(obs, win, False, NoEventMask, &ev); XFlush(obs); } int main(void) { if (getenv("DISPLAY") == NULL) { fprintf(stderr, "OMITIDA: DISPLAY no está definido (usa Xvfb o una sesión X11)\n"); return 77; } FnixWindow *window = fnix_window_create(TITLE, WIDTH, HEIGHT); if (window == NULL) { fprintf(stderr, "no se pudo crear la ventana: %s\n", fnix_last_error()); return 1; } CHECK(fnix_window_width(window) == WIDTH && fnix_window_height(window) == HEIGHT); CHECK(fnix_canvas_width(fnix_window_canvas(window)) == WIDTH); CHECK(!fnix_window_should_close(window)); Display *obs = XOpenDisplay(NULL); CHECK(obs != NULL); if (obs == NULL) { fnix_window_destroy(window); return CHECK_REPORT("test_window_x11"); } Window win = wait_for_window(obs, TITLE); CHECK(win != 0); if (win == 0) { XCloseDisplay(obs); fnix_window_destroy(window); return CHECK_REPORT("test_window_x11"); } CHECK(wait_viewable(obs, win)); /* --- Fotogramas, delta time y límite de FPS ------------------------- */ fnix_window_set_target_fps(window, 50); /* periodo ideal: 20 ms */ double dt_sum = 0.0; int dt_count = 0; for (int frame = 0; frame < 16; frame++) { fnix_begin_frame(window); if (frame == 0) { CHECK(fnix_window_delta_time(window) == 0.0); } else { dt_sum += fnix_window_delta_time(window); dt_count++; } CHECK(fnix_get_target() == fnix_window_canvas(window)); fnix_clear(FNIX_BLACK); fnix_draw_circle(160, 120, 50, FNIX_RED); fnix_draw_rect(10, 10, 40, 30, FNIX_BLUE); fnix_draw_circle_outline(250, 180, 30, FNIX_GREEN); fnix_end_frame(window); } const double average_dt = dt_sum / dt_count; printf("delta time medio con límite de 50 FPS: %.4f s (ideal 0.0200)\n", average_dt); CHECK(average_dt > 0.017 && average_dt < 0.023); /* --- Lo dibujado llegó de verdad a la ventana ----------------------- */ CHECK(server_pixel_becomes(obs, win, 160, 120, 0xFF0000UL)); /* centro del círculo */ CHECK(server_pixel_becomes(obs, win, 20, 20, 0x0000FFUL)); /* rectángulo azul */ CHECK(server_pixel_becomes(obs, win, 5, 230, 0x000000UL)); /* fondo */ CHECK(server_pixel_becomes(obs, win, 160, 180, 0x000000UL)); /* fuera del círculo */ CHECK(server_pixel_becomes(obs, win, 250 + 30, 180, 0x00FF00UL)); /* contorno verde */ CHECK(server_pixel_becomes(obs, win, 250, 180, 0x000000UL)); /* interior del anillo */ /* --- Teclado --------------------------------------------------------- */ bool ok; CHECK(XKeysymToKeycode(obs, XK_a) != 0); send_key(obs, win, XK_a, true); PUMP_UNTIL(window, fnix_key_pressed(window, FNIX_KEY_A), ok); CHECK(ok); CHECK(fnix_key_down(window, FNIX_KEY_A)); fnix_begin_frame(window); CHECK(fnix_key_down(window, FNIX_KEY_A) && !fnix_key_pressed(window, FNIX_KEY_A)); send_key(obs, win, XK_a, false); PUMP_UNTIL(window, fnix_key_released(window, FNIX_KEY_A), ok); CHECK(ok); CHECK(!fnix_key_down(window, FNIX_KEY_A)); send_key(obs, win, XK_Escape, true); PUMP_UNTIL(window, fnix_key_pressed(window, FNIX_KEY_ESCAPE), ok); CHECK(ok); send_key(obs, win, XK_Escape, false); PUMP_UNTIL(window, fnix_key_released(window, FNIX_KEY_ESCAPE), ok); CHECK(ok); /* --- Ratón ----------------------------------------------------------- */ send_motion(obs, win, 100, 50); PUMP_UNTIL(window, fnix_mouse_x(window) == 100 && fnix_mouse_y(window) == 50, ok); CHECK(ok); send_button(obs, win, Button1, true, 101, 51); PUMP_UNTIL(window, fnix_mouse_pressed(window, FNIX_MOUSE_LEFT), ok); CHECK(ok); CHECK(fnix_mouse_down(window, FNIX_MOUSE_LEFT)); CHECK(fnix_mouse_x(window) == 101 && fnix_mouse_y(window) == 51); send_button(obs, win, Button1, false, 101, 51); PUMP_UNTIL(window, fnix_mouse_released(window, FNIX_MOUSE_LEFT), ok); CHECK(ok); CHECK(!fnix_mouse_down(window, FNIX_MOUSE_LEFT)); send_button(obs, win, Button3, true, 5, 5); PUMP_UNTIL(window, fnix_mouse_pressed(window, FNIX_MOUSE_RIGHT), ok); CHECK(ok); send_button(obs, win, Button3, false, 5, 5); PUMP_UNTIL(window, fnix_mouse_released(window, FNIX_MOUSE_RIGHT), ok); CHECK(ok); send_button(obs, win, Button4, true, 9, 9); /* rueda arriba */ PUMP_UNTIL(window, fnix_mouse_wheel(window) == 1, ok); CHECK(ok); send_button(obs, win, Button5, true, 9, 9); /* rueda abajo */ PUMP_UNTIL(window, fnix_mouse_wheel(window) == -1, ok); CHECK(ok); /* --- Cola de eventos --------------------------------------------------- */ int counts[FNIX_EVENT_MOUSE_WHEEL + 1]; memset(counts, 0, sizeof counts); bool key_a_down_seen = false; FnixEvent event; while (fnix_window_poll_event(window, &event)) { counts[event.type]++; if (event.type == FNIX_EVENT_KEY_DOWN && event.key == FNIX_KEY_A && !event.repeat) { key_a_down_seen = true; } } CHECK(key_a_down_seen); CHECK(counts[FNIX_EVENT_KEY_DOWN] == 2); /* A y Escape */ CHECK(counts[FNIX_EVENT_KEY_UP] == 2); CHECK(counts[FNIX_EVENT_MOUSE_BUTTON_DOWN] == 2); /* izquierdo y derecho */ CHECK(counts[FNIX_EVENT_MOUSE_BUTTON_UP] == 2); CHECK(counts[FNIX_EVENT_MOUSE_MOVE] >= 1); CHECK(counts[FNIX_EVENT_MOUSE_WHEEL] == 2); CHECK(counts[FNIX_EVENT_WINDOW_CLOSE] == 0); /* --- Cierre de ventana pedido por el usuario / gestor de ventanas ---- */ CHECK(!fnix_window_should_close(window)); send_close_request(obs, win); PUMP_UNTIL(window, fnix_window_should_close(window), ok); CHECK(ok); CHECK(fnix_window_poll_event(window, &event) && event.type == FNIX_EVENT_WINDOW_CLOSE); /* --- Destrucción: la ventana desaparece del servidor ------------------ */ fnix_window_destroy(window); XSync(obs, False); bool gone = false; for (int i = 0; i < 200 && !gone; i++) { gone = find_window(obs, DefaultRootWindow(obs), TITLE) == 0; if (!gone) { fnix_time_sleep(0.005); } } CHECK(gone); CHECK(fnix_get_target() == NULL); XCloseDisplay(obs); return CHECK_REPORT("test_window_x11"); }