/* Map the window on the screen */
xcb_map_window(conn, win);
}
// To make things simple with X11, we only support monochrome display, which is
// inverted: As soon as the color of the pixel is non-black, we show a black
// pixel. If the pixel is white, we show black.
void draw_pixels()
{
xcb_get_geometry_reply_t *geom;
xcb_clear_area(
conn, 0, win, 0, 0, geom->width, geom->height);
// Figure out inner size to maximize our screen's aspect ratio
int psize = geom->height / vdp.tms.height;
if (geom->width / vdp.tms.width < psize) {
// width is the constraint
psize = geom->width / vdp.tms.width;
}
int innerw = psize * vdp.tms.width;
int innerh = psize * vdp.tms.height;
int innerx = (geom->width - innerw) / 2;
int innery = (geom->height - innerh) / 2;
free(geom);
int drawcnt = 0;
for (int i=0; i<vdp.tms.width; i++) {
for (int j=0; j<vdp.tms.height; j++) {
if (vdp_pixel(&vdp, i, j)) {
int x = innerx + (i*psize);
int y = innery + (j*psize);
rectangles[drawcnt].x = x;
rectangles[drawcnt].y = y;
rectangles[drawcnt].height = psize;
rectangles[drawcnt].width = psize;
drawcnt++;
}
}
}
if (drawcnt) {
xcb_poly_fill_rectangle(
conn, win, fg, drawcnt, rectangles);
}
vdp_changed = false;
xcb_flush(conn);
}
// Returns true to exist event loop
static bool _handle_keypress(xcb_generic_event_t *e)
{
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
if (ev->detail == 0x09) { // ESC
return true;
}
bool ispressed = e->response_type == XCB_KEY_PRESS;
// change keycode into symbol
xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply(
conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL);
if (km->length) {
xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1);
if (use_kbd) {
if ((keysyms[0] == XK_Shift_L) || (keysyms[0] == XK_Shift_R)) {
kbd_pressshift(&kbd, ispressed);
} else if (ispressed) {
fprintf(stderr, "pressing %x\n", keysyms[0]);
kbd_presskey(&kbd, keysyms[0]);
}
} else { // pad
switch (keysyms[0]) {
case 'w':
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
break;
case 'a':
pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
break;
case 's':
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
break;
case 'd':
pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
break;
case 'h':
pad_setbtn(&pad, PAD_BTN_A, ispressed);
break;
case 'j':
pad_setbtn(&pad, PAD_BTN_B, ispressed);
break;
case 'k':
pad_setbtn(&pad, PAD_BTN_C, ispressed);
break;
case 'l':
pad_setbtn(&pad, PAD_BTN_START, ispressed);
break;
}
}
}
free(km);
return false;
}
void event_loop()
{
while (1) {
for (int i=0; i<100; i++) {
if (!emul_step()) {
fprintf(stderr, "CPU halted, quitting\n");
usleep(1000 * 1000);
break;
}
spi_pulse(&spi);
}
if (vdp_changed) {
// To avoid overdrawing, we'll let the CPU run a bit to finish its
// drawing operation.
for (int i=0; i<10000; i++) {
if (!emul_step()) {
fprintf(stderr, "CPU halted, quitting\n");
usleep(1000 * 1000);
break;
}
spi_pulse(&spi);
}
draw_pixels();
}
// A low tech way of checking when the window was closed. The proper way
// involving WM_DELETE is too complicated.
xcb_get_geometry_reply_t *geom;
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
if (geom == NULL) {
return; // window has been closed.
} else {
free(geom);
}
xcb_generic_event_t *e = xcb_poll_for_event(conn);
if (!e) {
continue;
}
switch (e->response_type & ~0x80) {
/* ESC to exit */
case XCB_KEY_RELEASE:
case XCB_KEY_PRESS:
if (_handle_keypress(e)) return;
break;
case XCB_EXPOSE: {
draw_pixels();
break;
}
default: {
break;
}
}
free(e);
}
}