Mirai's Miscellaneous Misadventures

M36 / engines / fbdev.c

1// copyright 2022 zamfofex
2// license: AGPLv3 or later
3
4#include <unistd.h>
5#include <stdlib.h>
6#include <termios.h>
7#include <time.h>
8#include <fcntl.h>
9#include <stdint.h>
10#include <string.h>
11#include <sys/mman.h>
12#include <sys/ioctl.h>
13#include <linux/fb.h>
14#include <linux/kd.h>
15#include <linux/input.h>
16
17#include <mimimi/engines/fbdev.h>
18#include <mimimi/assets.h>
19#include <mimimi/geometry.h>
20#include <mimimi/engines.h>
21#include <mimimi/behaviors.h>
22
23static struct mimimi_size mimimi_fbdev_size = {512, 256};
24static struct termios mimimi_fbdev_tio;
25
26static void mimimi_fbdev_exit(void)
27{
28	ioctl(0, KDSETMODE, KD_TEXT);
29	tcsetattr(0, TCSANOW, &mimimi_fbdev_tio);
30}
31
32struct mimimi_fbdev
33{
34	unsigned char *buffer;
35	int width;
36	int height;
37	int x_offset;
38	int y_offset;
39	int line_length;
40	int bytes_per_pixel;
41};
42
43static void mimimi_fbdev_stamp(void *opaque, int x0, int y0, struct mimimi_image *image)
44{
45	struct mimimi_fbdev *data = opaque;
46	
47	static unsigned char rch[] = {0x11, 0x44, 0x77, 0x99, 0xCC, 0xFF};
48	static unsigned char gch[] = {0x11, 0x33, 0x55, 0x77, 0x99, 0xBB, 0xDD, 0xFF};
49	static unsigned char bch[] = {0x22, 0x55, 0x88, 0xBB, 0xEE};
50	
51	for (int y = 0 ; y < image->height ; y++)
52	for (int x = 0 ; x < image->width ; x++)
53	{
54		if (y + y0 < 0) continue;
55		if (x + x0 < 0) continue;
56		
57		if (y + y0 >= data->height) continue;
58		if (x + x0 >= data->width) continue;
59		
60		int offset_x = (x + x0 + data->x_offset) * data->bytes_per_pixel;
61		int offset_y = (y + y0 + data->y_offset) * data->line_length;
62		
63		int offset = offset_x + offset_y;
64		
65		unsigned char color = image->colors[x + y * image->width];
66		if (color == 0) continue;
67		
68		if (data->bytes_per_pixel == 4)
69			data->buffer[offset + 3] = 0xFF;
70		
71		if (color < 0x10)
72		{
73			unsigned char ch = color * 0x11;
74			data->buffer[offset + 2] = ch;
75			data->buffer[offset + 1] = ch;
76			data->buffer[offset + 0] = ch;
77			continue;
78		}
79		
80		color -= 0x10;
81		unsigned char r, g, b;
82		r = rch[(color / 40) % 6];
83		g = gch[(color / 5) % 8];
84		b = bch[(color / 1) % 5];
85		
86		data->buffer[offset + 2] = r;
87		data->buffer[offset + 1] = g;
88		data->buffer[offset + 0] = b;
89	}
90}
91
92void mimimi_fbdev(struct mimimi_chapter *(*start)(struct mimimi_engine *engine))
93{
94	int fd = open("/dev/fb0", O_RDWR);
95	if (fd < 0) return;
96	
97	struct fb_fix_screeninfo finfo;
98	if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) < 0) return;
99	
100	struct fb_var_screeninfo vinfo;
101	if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) return;
102	
103	void *aux = malloc(finfo.smem_len);
104	if (aux == NULL) return;
105	
106	unsigned char *buffer = mmap(NULL, finfo.smem_len, PROT_WRITE, MAP_SHARED, fd, 0);
107	if ((intptr_t) buffer == -1) return;
108	
109	int bytes_per_pixel = vinfo.bits_per_pixel / 8;
110	
111	if (bytes_per_pixel != 4 && bytes_per_pixel != 3) return;
112	
113	unsigned int width = mimimi_fbdev_size.width;
114	unsigned int height = mimimi_fbdev_size.height;
115	
116	if (vinfo.xres < width)
117		width = vinfo.xres;
118	if (vinfo.yres < height)
119		height = vinfo.yres;
120	
121	tcgetattr(0, &mimimi_fbdev_tio);
122	tcflag_t flag = mimimi_fbdev_tio.c_lflag;
123	mimimi_fbdev_tio.c_lflag &= ~ICANON & ~ECHO;
124	
125	atexit(mimimi_fbdev_exit);
126	
127	ioctl(0, KDSETMODE, KD_GRAPHICS);
128	tcsetattr(0, TCSANOW, &mimimi_fbdev_tio);
129	mimimi_fbdev_tio.c_lflag = flag;
130	
131	struct mimimi_fbdev data = {aux, width, height, vinfo.xoffset, vinfo.yoffset, finfo.line_length, bytes_per_pixel};
132	
133	struct mimimi_engine engine;
134	engine.data = &data;
135	engine.stamp = &mimimi_fbdev_stamp;
136	engine.size = &mimimi_fbdev_size;
137	engine.allocator = mimimi_malloc;
138	struct mimimi_chapter *chapter = (*start)(&engine);
139	struct mimimi_behavior *behavior = chapter->behavior;
140	
141	static char *paths[] =
142	{
143		"/dev/input/event0", "/dev/input/event1", "/dev/input/event2", "/dev/input/event3",
144		"/dev/input/event4", "/dev/input/event5", "/dev/input/event6", "/dev/input/event7",
145		"/dev/input/event8", "/dev/input/event9", "/dev/input/event10", "/dev/input/event11",
146		"/dev/input/event12", "/dev/input/event13", "/dev/input/event14", "/dev/input/event15",
147		"/dev/input/event16", "/dev/input/event17", "/dev/input/event18", "/dev/input/event19",
148		"/dev/input/event20", "/dev/input/event21", "/dev/input/event22", "/dev/input/event23",
149		"/dev/input/event24", "/dev/input/event25", "/dev/input/event26", "/dev/input/event27",
150		"/dev/input/event28", "/dev/input/event29", "/dev/input/event30", "/dev/input/event31",
151	};
152	
153	int in_count;
154	int in[32];
155	for (in_count = 0 ; in_count < 32 ; in_count++)
156	{
157		int fd = open(paths[in_count], O_RDONLY);
158		if (fd < 0) break;
159		in[in_count] = fd;
160	}
161	
162	struct timespec past;
163	
164	for (;;)
165	{
166		struct timespec tv;
167		clock_gettime(CLOCK_MONOTONIC, &tv);
168		
169		long int delay =
170			(tv.tv_sec - past.tv_sec) * 1000000000 +
171			(tv.tv_nsec - past.tv_nsec);
172		
173		if (delay < 0) delay = 0;
174		
175		past.tv_sec = tv.tv_sec;
176		past.tv_nsec = tv.tv_nsec;
177		
178		unsigned long int nsec;
179		if (delay < 33333333) nsec = 33333333 - delay;
180		else nsec = 0;
181		
182		tv.tv_sec = nsec / 1000000000;
183		tv.tv_nsec = nsec % 1000000000;
184		
185		int nfds = 0;
186		fd_set fds;
187		FD_ZERO(&fds);
188		for (int i = 0 ; i < in_count ; i++)
189		{
190			if (nfds < in[i]) nfds = in[i];
191			FD_SET(in[i], &fds);
192		}
193		
194		for (;;)
195		{
196			int available = pselect(nfds, &fds, NULL, NULL, &tv, NULL);
197			if (available == -1) return;
198			
199			if (available == 0) break;
200			
201			tv.tv_sec = 0;
202			tv.tv_nsec = 0;
203			
204			for (int i = 0 ; i < in_count ; i++)
205			{
206				int fd = in[i];
207				if (FD_ISSET(fd, &fds) == 0) continue;
208				
209				int size = sizeof (struct input_event);
210				struct input_event event;
211				ssize_t r = read(fd, &event, size);
212				if (r < size) return;
213				
214				if (event.type != EV_KEY) continue;
215				if (event.value == 2) continue;
216				
217				switch (event.code)
218				{
219				case KEY_LEFT:
220					chapter->left = event.value;
221					break;
222				case KEY_RIGHT:
223					chapter->right = event.value;
224					break;
225				case KEY_Q:
226					if (event.value == 0)
227						return;
228					break;
229				}
230			}
231		}
232		
233		(*behavior->behave)(behavior->data);
234		memcpy(buffer, aux, finfo.smem_len);
235	}
236}