abse/gameboy-advance

A tscircuit package created by abse.

Version
0.0.45
License
unset
Stars
0

firmware/game-console/doomgeneric/doomgeneric_linuxvt.c

//doomgeneric for a bare Linux VirtualTerminal
// Copyright (C) 2025 Techflash
// Based on doomgeneric_sdl.c

#include "doomkeys.h"
#include "m_argv.h"
#include "doomgeneric.h"
#include "i_system.h"

// XXX: HACK
// Linux's input-event-codes.h and doomkeys.h have many collisions.
// Redefine some of doomkeys.h's names here to work around this.
// I could try to redefine Linux's... but that sounds incredibly
// fragile, and is very likely not a good idea.
#undef KEY_TAB
#undef KEY_ENTER
#undef KEY_BACKSPACE
#undef KEY_MINUS
#undef KEY_F1
#undef KEY_F2
#undef KEY_F3
#undef KEY_F4
#undef KEY_F5
#undef KEY_F6
#undef KEY_F7
#undef KEY_F8
#undef KEY_F9
#undef KEY_F10
#undef KEY_F11
#define DOOM_KEY_TAB		9
#define DOOM_KEY_ENTER		13
#define DOOM_KEY_MINUS		0x2d
#define DOOM_KEY_BACKSPACE	0x7f
#define DOOM_KEY_F1		(0x80+0x3b)
#define DOOM_KEY_F2		(0x80+0x3c)
#define DOOM_KEY_F3		(0x80+0x3d)
#define DOOM_KEY_F4		(0x80+0x3e)
#define DOOM_KEY_F5		(0x80+0x3f)
#define DOOM_KEY_F6		(0x80+0x40)
#define DOOM_KEY_F7		(0x80+0x41)
#define DOOM_KEY_F8		(0x80+0x42)
#define DOOM_KEY_F9		(0x80+0x43)
#define DOOM_KEY_F10		(0x80+0x44)
#define DOOM_KEY_F11		(0x80+0x57)
#define DOOM_KEY_F12		(0x80+0x58)


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <linux/input-event-codes.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <signal.h>
#include <stdbool.h>

#define KEYQUEUE_SIZE 16

#define MAX_INPUT_DEVS 16
#define INPUT_TYPE_KEYBOARD 0
#define INPUT_TYPE_JOYSTICK 1
#define INPUT_TYPE_MOUSE    2
#define INPUT_TYPE_TOUCH    3

// timing stuff
static struct timeval startTime;

// framebuffer stuff 
static uint8_t *fbPtr;
static int fbFd;
static int ttyFd = -1;
static int savedTtyMode;
static unsigned int fbWidth, fbHeight, fbStride, fbBytesPerPixel, fbOffsetX, fbOffsetY;

// input stuff
static int numInputFds = 0;
static int inputFds[MAX_INPUT_DEVS];
static bool shiftPressed = false;
static struct pollfd pollfds[MAX_INPUT_DEVS];

static unsigned short s_KeyQueue[KEYQUEUE_SIZE];
static unsigned int s_KeyQueueWriteIndex = 0;
static unsigned int s_KeyQueueReadIndex = 0;

// XXX: HACK
// Linux's evdev system doesn't make it feasible to just use
// tolower(key) like the existing conversions did, so we
// use a few ranges of maps here to avoid an obscenely long switch/case
static char evdevKeysToASCII1[10] = {
	'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
};
static char evdevKeysToASCII2[12] = {
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']'
};
static char evdevKeysToASCII3[12] = {
	'a', 's', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', ';', '\''
};
static char evdevKeysToASCII4[11] = {
	'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'
};


static char evdevShiftKeysToASCII1[12] = {
	'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}'
};
static char evdevShiftKeysToASCII2[12] = {
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}'
};
static char evdevShiftKeysToASCII3[12] = {
	'a', 's', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', ':', '"'
};
static char evdevShiftKeysToASCII4[11] = {
	'|', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '<', '>', '?'
};


static unsigned char convertToDoomKey(unsigned int key){
	switch (key) {
		case KEY_ENTER:
			key = DOOM_KEY_ENTER;
			break;
		case KEY_ESC:
			key = KEY_ESCAPE;
			break;
		case KEY_LEFT:
			key = KEY_LEFTARROW;
			break;
		case KEY_RIGHT:
			key = KEY_RIGHTARROW;
			break;
		case KEY_UP:
			key = KEY_UPARROW;
			break;
		case KEY_DOWN:
			key = KEY_DOWNARROW;
			break;
		case KEY_LEFTCTRL:
		case KEY_RIGHTCTRL:
			key = KEY_FIRE;
			break;
		case KEY_SPACE:
			key = KEY_USE;
			break;
		case KEY_LEFTSHIFT:
		case KEY_RIGHTSHIFT:
			key = KEY_RSHIFT;
			break;
		case KEY_LEFTALT:
		case KEY_RIGHTALT:
			key = KEY_LALT;
			break;
		case KEY_F2:
			key = DOOM_KEY_F2;
			break;
		case KEY_F3:
			key = DOOM_KEY_F3;
			break;
		case KEY_F4:
			key = DOOM_KEY_F4;
			break;
		case KEY_F5:
			key = DOOM_KEY_F5;
			break;
		case KEY_F6:
			key = DOOM_KEY_F6;
			break;
		case KEY_F7:
			key = DOOM_KEY_F7;
			break;
		case KEY_F8:
			key = DOOM_KEY_F8;
			break;
		case KEY_F9:
			key = DOOM_KEY_F9;
			break;
		case KEY_F10:
			key = DOOM_KEY_F10;
			break;
		case KEY_F11:
			key = DOOM_KEY_F11;
			break;
		case KEY_EQUAL:
			key = KEY_EQUALS;
			break;
		case KEY_MINUS:
			key = DOOM_KEY_MINUS;
			break;
		case KEY_BACKSPACE:
			key = DOOM_KEY_BACKSPACE;
			break;
		case KEY_TAB:
			key = DOOM_KEY_TAB;
			break;

		// sadly, yes, we need to handle every single alphanumeric
		// key here, since evdev doesn't spit out keys in anything
		// remotely resembling ASCII.....
		// though, we can take many shortcuts
		case KEY_1:
		case KEY_2:
		case KEY_3:
		case KEY_4:
		case KEY_5:
		case KEY_6:
		case KEY_7:
		case KEY_8:
		case KEY_9:
		case KEY_0:
			if (shiftPressed)
				key = evdevShiftKeysToASCII1[key - KEY_1];
			else
				key = evdevKeysToASCII1[key - KEY_1];
			break;
		case KEY_Q:
		case KEY_W:
		case KEY_E:
		case KEY_R:
		case KEY_T:
		case KEY_Y:
		case KEY_U:
		case KEY_I:
		case KEY_O:
		case KEY_P:
		case KEY_LEFTBRACE:
		case KEY_RIGHTBRACE:
			if (shiftPressed)
				key = evdevShiftKeysToASCII2[key - KEY_Q];
			else
				key = evdevKeysToASCII2[key - KEY_Q];
			break;
		case KEY_A:
		case KEY_S:
		case KEY_D:
		case KEY_F:
		case KEY_G:
		case KEY_H:
		case KEY_J:
		case KEY_K:
		case KEY_L:
		case KEY_SEMICOLON:
		case KEY_APOSTROPHE:
			if (shiftPressed)
				key = evdevShiftKeysToASCII3[key - KEY_A];
			else
				key = evdevKeysToASCII3[key - KEY_A];
			break;
		case KEY_BACKSLASH:
		case KEY_Z:
		case KEY_X:
		case KEY_C:
		case KEY_V:
		case KEY_B:
		case KEY_N:
		case KEY_M:
		case KEY_COMMA:
		case KEY_DOT:
		case KEY_SLASH:
			if (shiftPressed)
				key = evdevShiftKeysToASCII4[key - KEY_BACKSLASH];
			else
				key = evdevKeysToASCII4[key - KEY_BACKSLASH];
			break;
		default:
			key = 0xFF;
			break;
	}

	return key;
}

static void addKeyToQueue(int pressed, unsigned char keyCode) {
	if ((keyCode == KEY_LEFTSHIFT || keyCode == KEY_RIGHTSHIFT) &&
		(pressed == 1 || pressed == 0))
		shiftPressed = pressed;

		
	unsigned char key = convertToDoomKey(keyCode);
	if (key == 0xFF) // unknown, don't process it
		return;

	if (pressed > 1 || pressed < 0) // bogus value
		return;

	unsigned short keyData = (pressed << 8) | key;

	s_KeyQueue[s_KeyQueueWriteIndex] = keyData;
	s_KeyQueueWriteIndex++;
	s_KeyQueueWriteIndex %= KEYQUEUE_SIZE;
}



static void checkKeys() {
	int ret, i;
	bool keepGoing;
	struct input_event ev;

	keepGoing = true;
	while (keepGoing) {
		keepGoing = false; // exit if we haven't gotten anything
		ret = poll(pollfds, numInputFds, 0);  // don't wait at all, just give anything we've got

		if (ret < 0) {
			// borked
			return;
		}

		for (i = 0; i < MAX_INPUT_DEVS; i++) {
			if (pollfds[i].revents & POLLIN) {
				read(inputFds[i], &ev, sizeof(ev));
				keepGoing = true; // we read something, so keep trying
				addKeyToQueue(ev.value, ev.code);
			}
		}
	}

	// we read the entire backlog, get back to doom
	return;
}


#define TEST_KEY(k) (keybits[(k)/8] & (1 << ((k)%8)))
static int isKeyboard(const char *devPath) {
	unsigned long evbits;
	unsigned char keybits[KEY_MAX/8 + 1];
	int fd = open(devPath, O_RDONLY);
	if (fd < 0) {
		perror("Failed to open device");
		return 0;
	}

	evbits = 0;
	if (ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), &evbits) < 0) {
		close(fd);
		return 0;
	}

	/* Must support EV_KEY */
	if (!(evbits & (1 << EV_KEY))) {
		close(fd);
		return 0;
	}


	memset(keybits, 0, sizeof(keybits));
	if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) < 0) {
		close(fd);
		return 0;
	}

	if (TEST_KEY(KEY_A) && TEST_KEY(KEY_ENTER)) {
		close(fd);
		return 1;  /* looks like a keyboard */
	}

	close(fd);
	return 0;
}

static void checkInputDevs() {
	struct dirent *dp;
	DIR *dir;

	/* check for devices */
	dir = opendir("/dev/input");

	while ((dp = readdir(dir)) != NULL) {
		char fullpath[268]; /* d_name is 256 bytes */
		int fd;

		if (numInputFds >= MAX_INPUT_DEVS)
			I_Error("Out of room in input devices array");

		printf("checking %s\n", dp->d_name);
		if (strncmp(dp->d_name, "event", 5) != 0)
			continue;

		sprintf(fullpath, "/dev/input/%s", dp->d_name);

		printf("%s is a valid event device\n", fullpath);
		if (!isKeyboard(fullpath)) {
			printf("%s is not a keyboard, moving on\n", fullpath);
			continue;
		}

		fd = open(fullpath, O_RDONLY | O_NONBLOCK);
		if (fd < 0) {
			// not necessarily fatal
			perror("Failed to open device");
			continue;
		}

		pollfds[numInputFds].fd = fd;
		pollfds[numInputFds].events = POLLIN;
		inputFds[numInputFds++] = fd;
		printf("adding %s keyboard\n", fullpath);
		ioctl(fd, EVIOCGRAB, 1); // grab exclusive access to the device
	}
	closedir(dir);
}


static void RestoreVtMode(void)
{
	if (ttyFd < 0)
		return;

	ioctl(ttyFd, KDSETMODE, savedTtyMode);
	close(ttyFd);
	ttyFd = -1;
}

static void signal_handler(int sig)
{
	RestoreVtMode();
	signal(sig, SIG_DFL);
	raise(sig);
}

void DG_Init() {
	int ret;
	struct fb_var_screeninfo info;
	struct fb_fix_screeninfo finfo;

	//
	// set up the framebuffer
	//
	fbFd = open("/dev/fb0", O_RDWR);
	if (fbFd < 0)
		I_Error("Failed to open /dev/fb0: %s", strerror(errno));

	// get info
	ret = ioctl(fbFd, FBIOGET_VSCREENINFO, &info);
	if (ret != 0)
		I_Error("Failed to get framebuffer info: %s", strerror(errno));

	// get other info (this can optionally fail, since we can guess the stride)
	ret = ioctl(fbFd, FBIOGET_FSCREENINFO, &finfo);
	if (ret != 0) {
		printf("Failed to get framebuffer info: %s", strerror(errno));
		fbStride = fbWidth * fbBytesPerPixel;
	}
	else {
		fbStride = finfo.line_length;
	}

	fbWidth = info.xres;
	fbHeight = info.yres;
	fbBytesPerPixel = info.bits_per_pixel / 8;

	// to center the image on screen
	fbOffsetX = ((fbWidth - DOOMGENERIC_RESX) / 2) * fbBytesPerPixel;
	fbOffsetY = ((fbHeight - DOOMGENERIC_RESY) / 2) * fbStride;

	fbPtr = mmap(NULL, fbStride * fbHeight, PROT_READ | PROT_WRITE,
			MAP_SHARED, fbFd, 0);

	if (!fbPtr)
		I_Error("Failed to mmap /dev/fb0: %s", strerror(errno));
	

	ttyFd = open("/dev/tty0", O_RDWR);
	if (ttyFd < 0)
		I_Error("Failed to open TTY device: %s", strerror(errno));

	if (ioctl(ttyFd, KDGETMODE, &savedTtyMode) < 0)
		I_Error("Failed to get TTY mode: %s", strerror(errno));

	// Register cleanup before switching modes, in case no keyboard is found.
	if (atexit(RestoreVtMode) != 0)
		I_Error("Failed to register TTY cleanup");
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGSEGV, signal_handler);
	signal(SIGABRT, signal_handler);

	if (ioctl(ttyFd, KDSETMODE, KD_GRAPHICS) < 0)
		I_Error("Failed to set TTY graphics mode: %s", strerror(errno));

	// Some drivers blank the display when switching modes.
	if (ioctl(fbFd, FBIOBLANK, FB_BLANK_UNBLANK) < 0)
		fprintf(stderr, "Warning: failed to unblank framebuffer: %s\n", strerror(errno));


	// clear the screen
	memset(fbPtr, 0, fbStride * fbHeight);

	//
	// set up input
	//
	checkInputDevs();
	
	if (numInputFds == 0)
		I_Error("Failed to find any compatible input device, see the logs above for potential problems");

	// get the start time
	gettimeofday(&startTime, NULL);
}

void DG_DrawFrame() {
	// we need to do it line-by-line like this to account for the
	// fact that the system framebuffer resolution is very likely
	// larger than the doomgeneric render resolution.
	for (int line = 0; line < DOOMGENERIC_RESY; line++) {
		memcpy(
			(void *)((uintptr_t)(fbPtr) + (fbStride * line) + fbOffsetY + fbOffsetX),
				(void *)(((uintptr_t)DG_ScreenBuffer) + (DOOMGENERIC_RESX * line * fbBytesPerPixel)),
			 (fbBytesPerPixel * DOOMGENERIC_RESX)
		);
	}

	checkKeys();
}

void DG_SleepMs(uint32_t ms) {
	usleep(ms * 1000);
}

uint32_t DG_GetTicksMs() {
	struct timeval curTime;
	long seconds, usec;

	gettimeofday(&curTime, NULL);
	seconds = curTime.tv_sec - startTime.tv_sec;
	usec = curTime.tv_usec - startTime.tv_usec;

	return (seconds * 1000) + (usec / 1000);
}

int DG_GetKey(int* pressed, unsigned char* doomKey) {
	checkKeys();

	if (s_KeyQueueReadIndex == s_KeyQueueWriteIndex) {
		//key queue is empty

		return 0;
	}
	else {
		unsigned short keyData = s_KeyQueue[s_KeyQueueReadIndex];
		s_KeyQueueReadIndex++;
		s_KeyQueueReadIndex %= KEYQUEUE_SIZE;

		*pressed = keyData >> 8;
		*doomKey = keyData & 0xFF;

		return 1;
	}

}

void DG_SetWindowTitle(const char * title) {
	printf("Window Title: %s\n", title);
}

int main(int argc, char **argv) {
	doomgeneric_Create(argc, argv);

	while (1)
	{
		doomgeneric_Tick();
	}


	return 0;
}
, '%', '^', '\u0026', '*', '(', ')', '{', '}'\n};\nstatic char evdevShiftKeysToASCII2[12] = {\n\t'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}'\n};\nstatic char evdevShiftKeysToASCII3[12] = {\n\t'a', 's', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', ':', '\"'\n};\nstatic char evdevShiftKeysToASCII4[11] = {\n\t'|', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '\u003c', '\u003e', '?'\n};\n\n\nstatic unsigned char convertToDoomKey(unsigned int key){\n\tswitch (key) {\n\t\tcase KEY_ENTER:\n\t\t\tkey = DOOM_KEY_ENTER;\n\t\t\tbreak;\n\t\tcase KEY_ESC:\n\t\t\tkey = KEY_ESCAPE;\n\t\t\tbreak;\n\t\tcase KEY_LEFT:\n\t\t\tkey = KEY_LEFTARROW;\n\t\t\tbreak;\n\t\tcase KEY_RIGHT:\n\t\t\tkey = KEY_RIGHTARROW;\n\t\t\tbreak;\n\t\tcase KEY_UP:\n\t\t\tkey = KEY_UPARROW;\n\t\t\tbreak;\n\t\tcase KEY_DOWN:\n\t\t\tkey = KEY_DOWNARROW;\n\t\t\tbreak;\n\t\tcase KEY_LEFTCTRL:\n\t\tcase KEY_RIGHTCTRL:\n\t\t\tkey = KEY_FIRE;\n\t\t\tbreak;\n\t\tcase KEY_SPACE:\n\t\t\tkey = KEY_USE;\n\t\t\tbreak;\n\t\tcase KEY_LEFTSHIFT:\n\t\tcase KEY_RIGHTSHIFT:\n\t\t\tkey = KEY_RSHIFT;\n\t\t\tbreak;\n\t\tcase KEY_LEFTALT:\n\t\tcase KEY_RIGHTALT:\n\t\t\tkey = KEY_LALT;\n\t\t\tbreak;\n\t\tcase KEY_F2:\n\t\t\tkey = DOOM_KEY_F2;\n\t\t\tbreak;\n\t\tcase KEY_F3:\n\t\t\tkey = DOOM_KEY_F3;\n\t\t\tbreak;\n\t\tcase KEY_F4:\n\t\t\tkey = DOOM_KEY_F4;\n\t\t\tbreak;\n\t\tcase KEY_F5:\n\t\t\tkey = DOOM_KEY_F5;\n\t\t\tbreak;\n\t\tcase KEY_F6:\n\t\t\tkey = DOOM_KEY_F6;\n\t\t\tbreak;\n\t\tcase KEY_F7:\n\t\t\tkey = DOOM_KEY_F7;\n\t\t\tbreak;\n\t\tcase KEY_F8:\n\t\t\tkey = DOOM_KEY_F8;\n\t\t\tbreak;\n\t\tcase KEY_F9:\n\t\t\tkey = DOOM_KEY_F9;\n\t\t\tbreak;\n\t\tcase KEY_F10:\n\t\t\tkey = DOOM_KEY_F10;\n\t\t\tbreak;\n\t\tcase KEY_F11:\n\t\t\tkey = DOOM_KEY_F11;\n\t\t\tbreak;\n\t\tcase KEY_EQUAL:\n\t\t\tkey = KEY_EQUALS;\n\t\t\tbreak;\n\t\tcase KEY_MINUS:\n\t\t\tkey = DOOM_KEY_MINUS;\n\t\t\tbreak;\n\t\tcase KEY_BACKSPACE:\n\t\t\tkey = DOOM_KEY_BACKSPACE;\n\t\t\tbreak;\n\t\tcase KEY_TAB:\n\t\t\tkey = DOOM_KEY_TAB;\n\t\t\tbreak;\n\n\t\t// sadly, yes, we need to handle every single alphanumeric\n\t\t// key here, since evdev doesn't spit out keys in anything\n\t\t// remotely resembling ASCII.....\n\t\t// though, we can take many shortcuts\n\t\tcase KEY_1:\n\t\tcase KEY_2:\n\t\tcase KEY_3:\n\t\tcase KEY_4:\n\t\tcase KEY_5:\n\t\tcase KEY_6:\n\t\tcase KEY_7:\n\t\tcase KEY_8:\n\t\tcase KEY_9:\n\t\tcase KEY_0:\n\t\t\tif (shiftPressed)\n\t\t\t\tkey = evdevShiftKeysToASCII1[key - KEY_1];\n\t\t\telse\n\t\t\t\tkey = evdevKeysToASCII1[key - KEY_1];\n\t\t\tbreak;\n\t\tcase KEY_Q:\n\t\tcase KEY_W:\n\t\tcase KEY_E:\n\t\tcase KEY_R:\n\t\tcase KEY_T:\n\t\tcase KEY_Y:\n\t\tcase KEY_U:\n\t\tcase KEY_I:\n\t\tcase KEY_O:\n\t\tcase KEY_P:\n\t\tcase KEY_LEFTBRACE:\n\t\tcase KEY_RIGHTBRACE:\n\t\t\tif (shiftPressed)\n\t\t\t\tkey = evdevShiftKeysToASCII2[key - KEY_Q];\n\t\t\telse\n\t\t\t\tkey = evdevKeysToASCII2[key - KEY_Q];\n\t\t\tbreak;\n\t\tcase KEY_A:\n\t\tcase KEY_S:\n\t\tcase KEY_D:\n\t\tcase KEY_F:\n\t\tcase KEY_G:\n\t\tcase KEY_H:\n\t\tcase KEY_J:\n\t\tcase KEY_K:\n\t\tcase KEY_L:\n\t\tcase KEY_SEMICOLON:\n\t\tcase KEY_APOSTROPHE:\n\t\t\tif (shiftPressed)\n\t\t\t\tkey = evdevShiftKeysToASCII3[key - KEY_A];\n\t\t\telse\n\t\t\t\tkey = evdevKeysToASCII3[key - KEY_A];\n\t\t\tbreak;\n\t\tcase KEY_BACKSLASH:\n\t\tcase KEY_Z:\n\t\tcase KEY_X:\n\t\tcase KEY_C:\n\t\tcase KEY_V:\n\t\tcase KEY_B:\n\t\tcase KEY_N:\n\t\tcase KEY_M:\n\t\tcase KEY_COMMA:\n\t\tcase KEY_DOT:\n\t\tcase KEY_SLASH:\n\t\t\tif (shiftPressed)\n\t\t\t\tkey = evdevShiftKeysToASCII4[key - KEY_BACKSLASH];\n\t\t\telse\n\t\t\t\tkey = evdevKeysToASCII4[key - KEY_BACKSLASH];\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tkey = 0xFF;\n\t\t\tbreak;\n\t}\n\n\treturn key;\n}\n\nstatic void addKeyToQueue(int pressed, unsigned char keyCode) {\n\tif ((keyCode == KEY_LEFTSHIFT || keyCode == KEY_RIGHTSHIFT) \u0026\u0026\n\t\t(pressed == 1 || pressed == 0))\n\t\tshiftPressed = pressed;\n\n\t\t\n\tunsigned char key = convertToDoomKey(keyCode);\n\tif (key == 0xFF) // unknown, don't process it\n\t\treturn;\n\n\tif (pressed \u003e 1 || pressed \u003c 0) // bogus value\n\t\treturn;\n\n\tunsigned short keyData = (pressed \u003c\u003c 8) | key;\n\n\ts_KeyQueue[s_KeyQueueWriteIndex] = keyData;\n\ts_KeyQueueWriteIndex++;\n\ts_KeyQueueWriteIndex %= KEYQUEUE_SIZE;\n}\n\n\n\nstatic void checkKeys() {\n\tint ret, i;\n\tbool keepGoing;\n\tstruct input_event ev;\n\n\tkeepGoing = true;\n\twhile (keepGoing) {\n\t\tkeepGoing = false; // exit if we haven't gotten anything\n\t\tret = poll(pollfds, numInputFds, 0); // don't wait at all, just give anything we've got\n\n\t\tif (ret \u003c 0) {\n\t\t\t// borked\n\t\t\treturn;\n\t\t}\n\n\t\tfor (i = 0; i \u003c MAX_INPUT_DEVS; i++) {\n\t\t\tif (pollfds[i].revents \u0026 POLLIN) {\n\t\t\t\tread(inputFds[i], \u0026ev, sizeof(ev));\n\t\t\t\tkeepGoing = true; // we read something, so keep trying\n\t\t\t\taddKeyToQueue(ev.value, ev.code);\n\t\t\t}\n\t\t}\n\t}\n\n\t// we read the entire backlog, get back to doom\n\treturn;\n}\n\n\n#define TEST_KEY(k) (keybits[(k)/8] \u0026 (1 \u003c\u003c ((k)%8)))\nstatic int isKeyboard(const char *devPath) {\n\tunsigned long evbits;\n\tunsigned char keybits[KEY_MAX/8 + 1];\n\tint fd = open(devPath, O_RDONLY);\n\tif (fd \u003c 0) {\n\t\tperror(\"Failed to open device\");\n\t\treturn 0;\n\t}\n\n\tevbits = 0;\n\tif (ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), \u0026evbits) \u003c 0) {\n\t\tclose(fd);\n\t\treturn 0;\n\t}\n\n\t/* Must support EV_KEY */\n\tif (!(evbits \u0026 (1 \u003c\u003c EV_KEY))) {\n\t\tclose(fd);\n\t\treturn 0;\n\t}\n\n\n\tmemset(keybits, 0, sizeof(keybits));\n\tif (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) \u003c 0) {\n\t\tclose(fd);\n\t\treturn 0;\n\t}\n\n\tif (TEST_KEY(KEY_A) \u0026\u0026 TEST_KEY(KEY_ENTER)) {\n\t\tclose(fd);\n\t\treturn 1; /* looks like a keyboard */\n\t}\n\n\tclose(fd);\n\treturn 0;\n}\n\nstatic void checkInputDevs() {\n\tstruct dirent *dp;\n\tDIR *dir;\n\n\t/* check for devices */\n\tdir = opendir(\"/dev/input\");\n\n\twhile ((dp = readdir(dir)) != NULL) {\n\t\tchar fullpath[268]; /* d_name is 256 bytes */\n\t\tint fd;\n\n\t\tif (numInputFds \u003e= MAX_INPUT_DEVS)\n\t\t\tI_Error(\"Out of room in input devices array\");\n\n\t\tprintf(\"checking %s\\n\", dp-\u003ed_name);\n\t\tif (strncmp(dp-\u003ed_name, \"event\", 5) != 0)\n\t\t\tcontinue;\n\n\t\tsprintf(fullpath, \"/dev/input/%s\", dp-\u003ed_name);\n\n\t\tprintf(\"%s is a valid event device\\n\", fullpath);\n\t\tif (!isKeyboard(fullpath)) {\n\t\t\tprintf(\"%s is not a keyboard, moving on\\n\", fullpath);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfd = open(fullpath, O_RDONLY | O_NONBLOCK);\n\t\tif (fd \u003c 0) {\n\t\t\t// not necessarily fatal\n\t\t\tperror(\"Failed to open device\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tpollfds[numInputFds].fd = fd;\n\t\tpollfds[numInputFds].events = POLLIN;\n\t\tinputFds[numInputFds++] = fd;\n\t\tprintf(\"adding %s keyboard\\n\", fullpath);\n\t\tioctl(fd, EVIOCGRAB, 1); // grab exclusive access to the device\n\t}\n\tclosedir(dir);\n}\n\n\nstatic void RestoreVtMode(void)\n{\n\tif (ttyFd \u003c 0)\n\t\treturn;\n\n\tioctl(ttyFd, KDSETMODE, savedTtyMode);\n\tclose(ttyFd);\n\tttyFd = -1;\n}\n\nstatic void signal_handler(int sig)\n{\n\tRestoreVtMode();\n\tsignal(sig, SIG_DFL);\n\traise(sig);\n}\n\nvoid DG_Init() {\n\tint ret;\n\tstruct fb_var_screeninfo info;\n\tstruct fb_fix_screeninfo finfo;\n\n\t//\n\t// set up the framebuffer\n\t//\n\tfbFd = open(\"/dev/fb0\", O_RDWR);\n\tif (fbFd \u003c 0)\n\t\tI_Error(\"Failed to open /dev/fb0: %s\", strerror(errno));\n\n\t// get info\n\tret = ioctl(fbFd, FBIOGET_VSCREENINFO, \u0026info);\n\tif (ret != 0)\n\t\tI_Error(\"Failed to get framebuffer info: %s\", strerror(errno));\n\n\t// get other info (this can optionally fail, since we can guess the stride)\n\tret = ioctl(fbFd, FBIOGET_FSCREENINFO, \u0026finfo);\n\tif (ret != 0) {\n\t\tprintf(\"Failed to get framebuffer info: %s\", strerror(errno));\n\t\tfbStride = fbWidth * fbBytesPerPixel;\n\t}\n\telse {\n\t\tfbStride = finfo.line_length;\n\t}\n\n\tfbWidth = info.xres;\n\tfbHeight = info.yres;\n\tfbBytesPerPixel = info.bits_per_pixel / 8;\n\n\t// to center the image on screen\n\tfbOffsetX = ((fbWidth - DOOMGENERIC_RESX) / 2) * fbBytesPerPixel;\n\tfbOffsetY = ((fbHeight - DOOMGENERIC_RESY) / 2) * fbStride;\n\n\tfbPtr = mmap(NULL, fbStride * fbHeight, PROT_READ | PROT_WRITE,\n\t\t\tMAP_SHARED, fbFd, 0);\n\n\tif (!fbPtr)\n\t\tI_Error(\"Failed to mmap /dev/fb0: %s\", strerror(errno));\n\t\n\n\tttyFd = open(\"/dev/tty0\", O_RDWR);\n\tif (ttyFd \u003c 0)\n\t\tI_Error(\"Failed to open TTY device: %s\", strerror(errno));\n\n\tif (ioctl(ttyFd, KDGETMODE, \u0026savedTtyMode) \u003c 0)\n\t\tI_Error(\"Failed to get TTY mode: %s\", strerror(errno));\n\n\t// Register cleanup before switching modes, in case no keyboard is found.\n\tif (atexit(RestoreVtMode) != 0)\n\t\tI_Error(\"Failed to register TTY cleanup\");\n\tsignal(SIGINT, signal_handler);\n\tsignal(SIGTERM, signal_handler);\n\tsignal(SIGSEGV, signal_handler);\n\tsignal(SIGABRT, signal_handler);\n\n\tif (ioctl(ttyFd, KDSETMODE, KD_GRAPHICS) \u003c 0)\n\t\tI_Error(\"Failed to set TTY graphics mode: %s\", strerror(errno));\n\n\t// Some drivers blank the display when switching modes.\n\tif (ioctl(fbFd, FBIOBLANK, FB_BLANK_UNBLANK) \u003c 0)\n\t\tfprintf(stderr, \"Warning: failed to unblank framebuffer: %s\\n\", strerror(errno));\n\n\n\t// clear the screen\n\tmemset(fbPtr, 0, fbStride * fbHeight);\n\n\t//\n\t// set up input\n\t//\n\tcheckInputDevs();\n\t\n\tif (numInputFds == 0)\n\t\tI_Error(\"Failed to find any compatible input device, see the logs above for potential problems\");\n\n\t// get the start time\n\tgettimeofday(\u0026startTime, NULL);\n}\n\nvoid DG_DrawFrame() {\n\t// we need to do it line-by-line like this to account for the\n\t// fact that the system framebuffer resolution is very likely\n\t// larger than the doomgeneric render resolution.\n\tfor (int line = 0; line \u003c DOOMGENERIC_RESY; line++) {\n\t\tmemcpy(\n\t\t\t(void *)((uintptr_t)(fbPtr) + (fbStride * line) + fbOffsetY + fbOffsetX),\n\t\t\t\t(void *)(((uintptr_t)DG_ScreenBuffer) + (DOOMGENERIC_RESX * line * fbBytesPerPixel)),\n\t\t\t (fbBytesPerPixel * DOOMGENERIC_RESX)\n\t\t);\n\t}\n\n\tcheckKeys();\n}\n\nvoid DG_SleepMs(uint32_t ms) {\n\tusleep(ms * 1000);\n}\n\nuint32_t DG_GetTicksMs() {\n\tstruct timeval curTime;\n\tlong seconds, usec;\n\n\tgettimeofday(\u0026curTime, NULL);\n\tseconds = curTime.tv_sec - startTime.tv_sec;\n\tusec = curTime.tv_usec - startTime.tv_usec;\n\n\treturn (seconds * 1000) + (usec / 1000);\n}\n\nint DG_GetKey(int* pressed, unsigned char* doomKey) {\n\tcheckKeys();\n\n\tif (s_KeyQueueReadIndex == s_KeyQueueWriteIndex) {\n\t\t//key queue is empty\n\n\t\treturn 0;\n\t}\n\telse {\n\t\tunsigned short keyData = s_KeyQueue[s_KeyQueueReadIndex];\n\t\ts_KeyQueueReadIndex++;\n\t\ts_KeyQueueReadIndex %= KEYQUEUE_SIZE;\n\n\t\t*pressed = keyData \u003e\u003e 8;\n\t\t*doomKey = keyData \u0026 0xFF;\n\n\t\treturn 1;\n\t}\n\n}\n\nvoid DG_SetWindowTitle(const char * title) {\n\tprintf(\"Window Title: %s\\n\", title);\n}\n\nint main(int argc, char **argv) {\n\tdoomgeneric_Create(argc, argv);\n\n\twhile (1)\n\t{\n\t\tdoomgeneric_Tick();\n\t}\n\n\n\treturn 0;\n}\n","content_mimetype":"text/x-c","created_at":"2026-11-05T18:49:28.654Z"}; window.SSR_PACKAGE_BUILDS = []; window.SSR_RELATED_PACKAGES = [{"package_id":"1bb71080-c9d9-4806-8dad-9546e8b9913b","creator_account_id":"03854e77-22c2-44a0-9cfc-2d4ed6741105","latest_package_release_id":"92d10956-aa95-4eba-8e98-42e66c16f463","last_built_release_id":"92d10956-aa95-4eba-8e98-42e66c16f463","latest_package_release_fs_sha":"md5-73c7349eef4667e7cbc7beecb7e35036","latest_pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=92d10956-aa95-4eba-8e98-42e66c16f463\u0026file_path=dist%2Findex%2Fpcb.svg","latest_sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=92d10956-aa95-4eba-8e98-42e66c16f463\u0026file_path=dist%2Findex%2Fschematic.svg","latest_cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=92d10956-aa95-4eba-8e98-42e66c16f463\u0026file_path=dist%2Findex%2F3d.png","latest_version":"1.0.5","license":"unset","owner_org_id":"7978967e-b956-4ac2-b813-667a08f6310a","org_owner_tscircuit_handle":"abse","owner_github_username":null,"github_repo_full_name":null,"is_source_from_github":false,"description":null,"name":"abse/boostxl-uln2003","unscoped_name":"boostxl-uln2003","star_count":0,"created_at":"2026-08-12T10:10:06.978Z","updated_at":"2026-08-12T22:29:23.783Z","ai_description":"This code defines a React component that models a through-hole or surface-mount LED with specified pin labels, footprint, silkscreen, and 3D CAD model for PCB layout.","ai_usage_instructions":null,"is_private":false,"is_unlisted":false,"is_public":true,"public_dist_enabled":false,"has_public_dist":true,"starred_at":null,"default_view":"files","website":null,"github_installation_id":null,"allow_pr_previews":false,"related_type":"same_author","thumbnail_url":"https://api.tscircuit.com/package_files/view?package_release_id=92d10956-aa95-4eba-8e98-42e66c16f463\u0026file_path=dist%2Findex%2Fpcb.svg"},{"package_id":"1a688437-24fc-4e9f-921c-1ddc4fc53cf5","creator_account_id":"84198f66-bb86-4dc1-97f0-cdf2cf2c843a","latest_package_release_id":"37e98b52-0b1d-4f91-8f06-8e5e321f5e45","last_built_release_id":"37e98b52-0b1d-4f91-8f06-8e5e321f5e45","latest_package_release_fs_sha":"md5-a234ce80dacb6ff214fc7c28c619fd53","latest_pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=37e98b52-0b1d-4f91-8f06-8e5e321f5e45\u0026file_path=dist%2Fassembly%2Fpcb.svg","latest_sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=37e98b52-0b1d-4f91-8f06-8e5e321f5e45\u0026file_path=dist%2Fassembly%2Fschematic.svg","latest_cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=37e98b52-0b1d-4f91-8f06-8e5e321f5e45\u0026file_path=dist%2Fassembly%2F3d.png","latest_version":"1.12.2","license":"ISC","owner_org_id":"07e44f25-c5d3-4583-9c34-2c7bb568f85b","org_owner_tscircuit_handle":"AnasSarkiz","owner_github_username":"AnasSarkiz","github_repo_full_name":null,"is_source_from_github":false,"description":null,"name":"AnasSarkiz/NEMA14_CH32X035G8U6","unscoped_name":"NEMA14_CH32X035G8U6","star_count":0,"created_at":"2026-10-07T17:53:37.598Z","updated_at":"2026-10-16T19:05:31.590Z","ai_description":"A NEMA stepper motor is shown with proposed M3 screws/spacers, four wired motor leads and splices, and a JST SHR-04V-S connector.","ai_usage_instructions":null,"is_private":false,"is_unlisted":false,"is_public":true,"public_dist_enabled":true,"has_public_dist":true,"starred_at":null,"default_view":"files","website":null,"github_installation_id":null,"allow_pr_previews":false,"related_type":"random","thumbnail_url":"https://api.tscircuit.com/package_files/view?package_release_id=37e98b52-0b1d-4f91-8f06-8e5e321f5e45\u0026file_path=dist%2Fassembly%2Fpcb.svg"}]; window.SSR_PACKAGE_ROUTE = {"author":"abse","packageName":"gameboy-advance","packageNameWithScope":"abse/gameboy-advance","version":null,"isLegacyRoute":false,"kind":"file","filePath":"firmware/game-console/doomgeneric/doomgeneric_linuxvt.c"};