main.c (6965B)
1 #define _GNU_SOURCE 2 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <math.h> 6 #include <signal.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <sys/select.h> 12 #include <sys/stat.h> 13 #include <sys/time.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 #include <unistd.h> 17 18 #define WIDTH 512 19 #define HEIGHT 320 20 21 #define str(x) stra(x) 22 #define stra(x) #x 23 24 #define die(fnname) \ 25 do { \ 26 perror(fnname); \ 27 exit(EXIT_FAILURE); \ 28 } while(0) 29 30 #define x(fn, ...) \ 31 do { \ 32 if(fn(__VA_ARGS__) < 0) { \ 33 perror(#fn); \ 34 exit(EXIT_FAILURE); \ 35 } \ 36 } while(0) 37 38 int fix_fft(short *fr, short *fi, short m, short inverse); 39 40 static pid_t 41 launch_xvfb(void) 42 { 43 char displayfd[16]; 44 int r; 45 pid_t pid; 46 int fds[2]; 47 x(pipe2, &fds[0], 0); 48 pid = fork(); 49 if(pid < 0) { 50 die("fork"); 51 } else if(!pid) { 52 x(snprintf, displayfd, sizeof(displayfd), "%d", fds[1]); 53 x(close, fds[0]); 54 execlp("Xvfb", "Xvfb", "-screen", "0", str(WIDTH) "x" str(HEIGHT) "x24", "-fbdir", ".", "-displayfd", displayfd, "-nolisten", "tcp", NULL); 55 die("execl"); 56 exit(EXIT_FAILURE); 57 } 58 x(close, fds[1]); 59 r = read(fds[0], &displayfd[1], sizeof(displayfd) - 1); 60 if(r < 0) die("read"); 61 x(close, fds[0]); 62 displayfd[r] = '\0'; 63 displayfd[0] = ':'; 64 x(setenv, "DISPLAY", displayfd, 1); 65 x(setenv, "ALSA_CONFIG_PATH", "asoundrc", 1); 66 return pid; 67 } 68 69 static pid_t 70 launch_uxnemu(int *write_fd, int *read_fd, int *sound_fd) 71 { 72 pid_t pid; 73 int fds[6]; 74 x(pipe2, &fds[0], O_CLOEXEC); 75 x(pipe2, &fds[2], O_CLOEXEC); 76 x(pipe2, &fds[4], O_CLOEXEC); 77 pid = fork(); 78 if(pid < 0) { 79 die("fork"); 80 } else if(!pid) { 81 x(dup2, fds[0], 0); 82 x(dup2, fds[3], 1); 83 x(dup2, fds[5], 3); 84 execl("../../bin/uxnemu", "uxnemu", "autotest.rom", NULL); 85 die("execl"); 86 } 87 x(close, fds[0]); 88 x(close, fds[3]); 89 x(close, fds[5]); 90 *write_fd = fds[1]; 91 *read_fd = fds[2]; 92 *sound_fd = fds[4]; 93 return pid; 94 } 95 96 static void 97 terminate(pid_t pid) 98 { 99 int signals[] = {SIGINT, SIGTERM, SIGKILL}; 100 int status; 101 size_t i; 102 for(i = 0; i < sizeof(signals) / sizeof(int) * 10; ++i) { 103 if(kill(pid, signals[i / 10])) { 104 break; 105 } 106 usleep(100000); 107 if(pid == waitpid(pid, &status, WNOHANG)) { 108 return; 109 } 110 } 111 waitpid(pid, &status, 0); 112 } 113 114 static int 115 open_framebuffer(void) 116 { 117 for(;;) { 118 int fd = open("Xvfb_screen0", O_RDONLY | O_CLOEXEC); 119 if(fd >= 0) { 120 return fd; 121 } 122 if(errno != ENOENT) { 123 perror("open"); 124 return fd; 125 } 126 usleep(100000); 127 } 128 } 129 130 #define PPM_HEADER "P6\n" str(WIDTH) " " str(HEIGHT) "\n255\n" 131 132 static void 133 save_screenshot(int fb_fd, const char *filename) 134 { 135 unsigned char screen[WIDTH * HEIGHT * 4 + 4]; 136 int fd = open(filename, O_WRONLY | O_CREAT, 0666); 137 int i; 138 if(fd < 0) { 139 die("screenshot open"); 140 } 141 x(write, fd, PPM_HEADER, strlen(PPM_HEADER)); 142 x(lseek, fb_fd, 0xca0, SEEK_SET); 143 x(read, fb_fd, &screen[4], WIDTH * HEIGHT * 4); 144 for(i = 0; i < WIDTH * HEIGHT; ++i) { 145 screen[i * 3 + 2] = screen[i * 4 + 4]; 146 screen[i * 3 + 1] = screen[i * 4 + 5]; 147 screen[i * 3 + 0] = screen[i * 4 + 6]; 148 } 149 x(write, fd, screen, WIDTH * HEIGHT * 3); 150 x(close, fd); 151 } 152 153 static void 154 systemf(char *format, ...) 155 { 156 char *command; 157 va_list ap; 158 va_start(ap, format); 159 x(vasprintf, &command, format, ap); 160 system(command); 161 free(command); 162 } 163 164 int uxn_read_fd, sound_fd; 165 166 static int 167 byte(void) 168 { 169 char c; 170 if(read(uxn_read_fd, &c, 1) != 1) { 171 return 0; 172 } 173 return (unsigned char)c; 174 } 175 176 #define NEW_FFT_SIZE_POW2 10 177 #define NEW_FFT_SIZE (1 << NEW_FFT_SIZE_POW2) 178 #define NEW_FFT_USEC (5000 * NEW_FFT_SIZE / 441) 179 180 unsigned char left_peak, right_peak; 181 182 static int 183 detect_peak(short *real, short *imag) 184 { 185 int i, peak = 0, peak_i; 186 for(i = 0; i < NEW_FFT_SIZE; ++i) { 187 int v = real[i] * real[i] + imag[i] * imag[i]; 188 if(peak < v) { 189 peak = v; 190 peak_i = i; 191 } else if(peak > v * 10) { 192 return peak_i; 193 } 194 } 195 return 0; 196 } 197 198 static int 199 analyse_sound(short *samples) 200 { 201 short real[NEW_FFT_SIZE], imag[NEW_FFT_SIZE]; 202 int i; 203 for(i = 0; i < NEW_FFT_SIZE * 2; ++i) { 204 if(samples[i * 2]) break; 205 } 206 if(i == NEW_FFT_SIZE * 2) return 0; 207 for(i = 0; i < NEW_FFT_SIZE; ++i) { 208 real[i] = samples[i * 4]; 209 imag[i] = samples[i * 4 + 2]; 210 } 211 fix_fft(real, imag, NEW_FFT_SIZE_POW2, 0); 212 return detect_peak(real, imag); 213 } 214 215 static int 216 read_sound(void) 217 { 218 static short samples[NEW_FFT_SIZE * 4]; 219 static size_t len = 0; 220 int r = read(sound_fd, ((char *)samples) + len, sizeof(samples) - len); 221 if(r > 0) { 222 len += r; 223 if(len == sizeof(samples)) { 224 left_peak = analyse_sound(&samples[0]); 225 right_peak = analyse_sound(&samples[1]); 226 len = 0; 227 return 1; 228 } 229 } 230 return 0; 231 } 232 233 static void 234 main_loop(int uxn_write_fd, int fb_fd) 235 { 236 struct timeval next_sound = {0, 0}; 237 for(;;) { 238 struct timeval now; 239 struct timeval *timeout; 240 fd_set fds; 241 FD_ZERO(&fds); 242 FD_SET(uxn_read_fd, &fds); 243 x(gettimeofday, &now, NULL); 244 if(now.tv_sec > next_sound.tv_sec || (now.tv_sec == next_sound.tv_sec && now.tv_usec > next_sound.tv_usec)) { 245 FD_SET(sound_fd, &fds); 246 timeout = NULL; 247 } else { 248 now.tv_sec = 0; 249 now.tv_usec = NEW_FFT_USEC; 250 timeout = &now; 251 } 252 x(select, uxn_read_fd > sound_fd ? uxn_read_fd + 1 : sound_fd + 1, &fds, NULL, NULL, timeout); 253 if(FD_ISSET(uxn_read_fd, &fds)) { 254 int c, x, y; 255 unsigned char blue; 256 switch(c = byte()) { 257 case 0x00: /* also used for EOF */ 258 printf("exiting\n"); 259 return; 260 /* 01-06 mouse */ 261 case 0x01 ... 0x05: 262 systemf("xdotool click %d", c); 263 break; 264 case 0x06: 265 x = (byte() << 8) | byte(); 266 y = (byte() << 8) | byte(); 267 systemf("xdotool mousemove %d %d", x, y); 268 break; 269 /* 07-08 Screen */ 270 case 0x07: 271 x = (byte() << 8) | byte(); 272 y = (byte() << 8) | byte(); 273 lseek(fb_fd, 0xca0 + (x + y * WIDTH) * 4, SEEK_SET); 274 read(fb_fd, &blue, 1); 275 blue = blue / 0x11; 276 write(uxn_write_fd, &blue, 1); 277 break; 278 case 0x08: 279 save_screenshot(fb_fd, "test.ppm"); 280 break; 281 /* 09-0a Audio */ 282 case 0x09: 283 write(uxn_write_fd, &left_peak, 1); 284 break; 285 case 0x0a: 286 write(uxn_write_fd, &right_peak, 1); 287 break; 288 /* 11-7e Controller/key */ 289 case 0x11 ... 0x1c: 290 systemf("xdotool key F%d", c - 0x10); 291 break; 292 case '0' ... '9': 293 case 'A' ... 'Z': 294 case 'a' ... 'z': 295 systemf("xdotool key %c", c); 296 break; 297 default: 298 printf("unhandled command 0x%02x\n", c); 299 break; 300 } 301 } 302 if(FD_ISSET(sound_fd, &fds)) { 303 if(!next_sound.tv_sec) { 304 x(gettimeofday, &next_sound, NULL); 305 } 306 next_sound.tv_usec += NEW_FFT_USEC * read_sound(); 307 if(next_sound.tv_usec > 1000000) { 308 next_sound.tv_usec -= 1000000; 309 ++next_sound.tv_sec; 310 } 311 } 312 } 313 } 314 315 int 316 main(void) 317 { 318 pid_t xvfb_pid = launch_xvfb(); 319 int fb_fd = open_framebuffer(); 320 if(fb_fd >= 0) { 321 int uxn_write_fd; 322 pid_t uxnemu_pid = launch_uxnemu(&uxn_write_fd, &uxn_read_fd, &sound_fd); 323 main_loop(uxn_write_fd, fb_fd); 324 terminate(uxnemu_pid); 325 x(close, uxn_write_fd); 326 x(close, uxn_read_fd); 327 x(close, sound_fd); 328 x(close, fb_fd); 329 } 330 terminate(xvfb_pid); 331 return 0; 332 }