331 lines
11 KiB
C
331 lines
11 KiB
C
/*
|
|
* 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 <fnix/fnix.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/keysym.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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");
|
|
}
|