Mirai's Miscellaneous Misadventures

M19 / engines / fbdev.c

// copyright 2022 zamfofex
// license: AGPLv3 or later

// todo: this is currently not working, fix it

#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#include <time.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <linux/input.h>

#include "../mimimi.h"

static struct termios mimimi_fbdev_tio;

static void mimimi_fbdev_exit(void)
{
	ioctl(0, KDSETMODE, KD_TEXT);
	tcsetattr(0, TCSANOW, &mimimi_fbdev_tio);
}

struct mimimi_fbdev
{
	unsigned char *buffer;
	int width;
	int height;
	int x_offset;
	int y_offset;
	int line_length;
};

static void mimimi_fbdev_blank(struct mimimi_fbdev *data)
{
	for (int y = 0 ; y < data->height ; y++)
	{
		for (int x = 0 ; x < data->width ; x++)
		{
			int offset_x = (x + data->x_offset) * 4;
			int offset_y = (y + data->y_offset) * data->line_length;
			
			int offset = offset_x + offset_y;
			
			data->buffer[offset + 0] = 0xEE;
			data->buffer[offset + 1] = 0xEE;
			data->buffer[offset + 2] = 0xEE;
			data->buffer[offset + 3] = 0xFF;
		}
	}
}

static void mimimi_fbdev_stamp(void *opaque, int x0, int y0, struct mimimi_image *image)
{
	struct mimimi_fbdev *data = opaque;
	
	static unsigned char xterm[] = {0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF};
	
	for (int y = 0 ; y < image->height ; y++)
	for (int x = 0 ; x < image->width ; x++)
	{
		if (y + y0 < 0) continue;
		if (x + x0 < 0) continue;
		
		if (y + y0 >= data->height) continue;
		if (x + x0 >= data->width) continue;
		
		int offset_x = (x + x0 + data->x_offset) * 4;
		int offset_y = (y + y0 + data->y_offset) * data->line_length;
		
		int offset = offset_x + offset_y;
		
		unsigned char color = image->colors[x + y * image->width];
		if (color == 0) continue;
		
		color -= 16;
		unsigned char r, g, b;
		r = xterm[(color / 36) % 6];
		g = xterm[(color / 6) % 6];
		b = xterm[color % 6];
		
		data->buffer[offset + 0] = b;
		data->buffer[offset + 1] = g;
		data->buffer[offset + 2] = r;
		data->buffer[offset + 3] = 0xFF;
	}
}

int main(void)
{
	int fd = open("/dev/fb0", O_RDWR);
	if (fd < 0) return -1;
	
	struct fb_fix_screeninfo finfo;
	if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) < 0) return -1;
	
	struct fb_var_screeninfo vinfo;
	if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) return -1;
	
	void *aux = malloc(finfo.smem_len);
	if (aux == NULL) return -1;
	
	unsigned char *buffer = mmap(NULL, finfo.smem_len, PROT_WRITE, MAP_SHARED, fd, 0);
	if ((intptr_t) buffer == -1) return -1;
	
	int bytes_per_pixel = vinfo.bits_per_pixel / 8;
	
	if (bytes_per_pixel != 4 && bytes_per_pixel != 3) return -1;
	
	unsigned int width = mimimi_width;
	unsigned int height = mimimi_height;
	
	if (vinfo.xres < width)
		width = vinfo.xres;
	if (vinfo.yres < height)
		height = vinfo.yres;
	
	tcgetattr(0, &mimimi_fbdev_tio);
	tcflag_t flag = mimimi_fbdev_tio.c_lflag;
	mimimi_fbdev_tio.c_lflag &= ~ICANON & ~ECHO;
	
	atexit(mimimi_fbdev_exit);
	
	ioctl(0, KDSETMODE, KD_GRAPHICS);
	tcsetattr(0, TCSANOW, &mimimi_fbdev_tio);
	mimimi_fbdev_tio.c_lflag = flag;
	
	struct mimimi_fbdev data = {aux, width, height, vinfo.xoffset, vinfo.yoffset, finfo.line_length};
	
	struct mimimi_engine engine = {&data, &mimimi_fbdev_stamp};
	struct mimimi_game *game = malloc(mimimi_game_size);
	mimimi_start(game, &engine, mimimi_test);
	
	static char *paths[] =
	{
		"/dev/input/event0", "/dev/input/event1", "/dev/input/event2", "/dev/input/event3",
		"/dev/input/event4", "/dev/input/event5", "/dev/input/event6", "/dev/input/event7",
		"/dev/input/event8", "/dev/input/event9", "/dev/input/event10", "/dev/input/event11",
		"/dev/input/event12", "/dev/input/event13", "/dev/input/event14", "/dev/input/event15",
		"/dev/input/event16", "/dev/input/event17", "/dev/input/event18", "/dev/input/event19",
		"/dev/input/event20", "/dev/input/event21", "/dev/input/event22", "/dev/input/event23",
		"/dev/input/event24", "/dev/input/event25", "/dev/input/event26", "/dev/input/event27",
		"/dev/input/event28", "/dev/input/event29", "/dev/input/event30", "/dev/input/event31",
	};
	
	int in_count;
	int in[32];
	for (in_count = 0 ; in_count < 32 ; in_count++)
	{
		int fd = open(paths[in_count], O_RDONLY);
		if (fd < 0) break;
		in[in_count] = fd;
	}
	
	struct timespec past;
	
	struct mimimi_keys keys = {0, 0};
	
	for (;;)
	{
		struct timespec tv;
		clock_gettime(CLOCK_MONOTONIC, &tv);
		
		long int delay =
			(tv.tv_sec - past.tv_sec) * 1000000000 +
			(tv.tv_nsec - past.tv_nsec);
		
		if (delay < 0) delay = 0;
		
		past.tv_sec = tv.tv_sec;
		past.tv_nsec = tv.tv_nsec;
		
		unsigned long int nsec;
		if (delay < 33333333) nsec = 33333333 - delay;
		else nsec = 0;
		
		tv.tv_sec = nsec / 1000000000;
		tv.tv_nsec = nsec % 1000000000;
		
		int nfds = 0;
		fd_set fds;
		FD_ZERO(&fds);
		for (int i = 0 ; i < in_count ; i++)
		{
			if (nfds < in[i]) nfds = in[i];
			FD_SET(in[i], &fds);
		}
		
		for (;;)
		{
			int available = pselect(nfds, &fds, NULL, NULL, &tv, NULL);
			if (available == -1) return -1;
			
			if (available == 0) break;
			
			tv.tv_sec = 0;
			tv.tv_nsec = 0;
			
			for (int i = 0 ; i < in_count ; i++)
			{
				int fd = in[i];
				if (FD_ISSET(fd, &fds) == 0) continue;
				
				int size = sizeof (struct input_event);
				struct input_event event;
				ssize_t r = read(fd, &event, size);
				if (r < size) return -1;
				
				if (event.type != EV_KEY) continue;
				if (event.value == 2) continue;
				
				switch (event.code)
				{
					case KEY_LEFT:
						keys.left = event.value;
						break;
					case KEY_RIGHT:
						keys.right = event.value;
						break;
					case KEY_Q:
						if (event.value == 0)
							return 0;
						break;
				}
			}
		}
		
		mimimi_fbdev_blank(&data);
		mimimi_step(game, keys);
		memcpy(buffer, aux, finfo.smem_len);
	}
}