From: techflashYT Date: Sun, 13 Jul 2025 02:59:24 +0000 (-0700) Subject: New platform: Linux VT X-Git-Url: https://git.0x19.co/?a=commitdiff_plain;h=5753998234113c0ff9b17fd69f0f49cda7a7d493;p=doomgeneric-snom360 New platform: Linux VT This should work on any Linux box with no additional software installed (e.g. Xorg, SDL). The only requirement is a basic framebuffer, and input via evdev - both are things provided by the kernel. It does not currently have audio support, though I hope to eventually make it work with ALSA if possible. --- diff --git a/doomgeneric/Makefile.linuxvt b/doomgeneric/Makefile.linuxvt new file mode 100644 index 0000000..6340560 --- /dev/null +++ b/doomgeneric/Makefile.linuxvt @@ -0,0 +1,51 @@ +################################################################ +# +# $Id:$ +# +# $Log:$ +# + +ifeq ($(V),1) + VB='' +else + VB=@ +endif + +CC=clang # gcc or g++ +CFLAGS+=-ggdb3 -Os +LDFLAGS+=-Wl,--gc-sections +CFLAGS+=-ggdb3 -Wall -DNORMALUNIX -DLINUX -DSNDSERV -D_DEFAULT_SOURCE # -DUSEASM +LIBS+=-lm -lc + +# subdirectory for objects +OBJDIR=build +OUTPUT=doomgeneric + +SRC_DOOM = dummy.o am_map.o doomdef.o doomstat.o dstrings.o d_event.o d_items.o d_iwad.o d_loop.o d_main.o d_mode.o d_net.o f_finale.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o i_cdmus.o i_endoom.o i_joystick.o i_scale.o i_sound.o i_system.o i_timer.o memio.o m_argv.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o m_menu.o m_misc.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o sha1.o sounds.o statdump.o st_lib.o st_stuff.o s_sound.o tables.o v_video.o wi_stuff.o w_checksum.o w_file.o w_main.o w_wad.o z_zone.o w_file_stdc.o i_input.o i_video.o doomgeneric.o doomgeneric_linuxvt.o mus2mid.o +OBJS += $(addprefix $(OBJDIR)/, $(SRC_DOOM)) + +all: $(OUTPUT) + +clean: + rm -rf $(OBJDIR) + rm -f $(OUTPUT) + rm -f $(OUTPUT).gdb + rm -f $(OUTPUT).map + +$(OUTPUT): $(OBJS) + @echo [Linking $@] + $(VB)$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) \ + -o $(OUTPUT) $(LIBS) + +$(OBJS): | $(OBJDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: %.c + @echo [Compiling $<] + $(VB)$(CC) $(CFLAGS) -c $< -o $@ + +print: + @echo OBJS: $(OBJS) + diff --git a/doomgeneric/doomgeneric_linuxvt.c b/doomgeneric/doomgeneric_linuxvt.c new file mode 100644 index 0000000..74fb0d3 --- /dev/null +++ b/doomgeneric/doomgeneric_linuxvt.c @@ -0,0 +1,520 @@ +//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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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); +} + +void DG_Init() { + int ret; + struct fb_var_screeninfo info; + + // + // 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)); + + fbWidth = info.xres; + fbHeight = info.yres; + fbBytesPerPixel = info.bits_per_pixel / 8; + fbStride = fbWidth * fbBytesPerPixel; + + // to center the image on screen + fbOffsetX = (fbWidth - DOOMGENERIC_RESX) / 2; + fbOffsetY = (fbHeight - DOOMGENERIC_RESY) / 2; + + 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)); + + // 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( + (fbPtr + (fbStride * (line + fbOffsetY)) + (fbOffsetX * fbBytesPerPixel)), + fbBytesPerPixel == 4 ? + DG_ScreenBuffer + (DOOMGENERIC_RESX * line) : + DG_ScreenBuffer + (DOOMGENERIC_RESX * (line / 2)), + (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; +}