diff --git a/src/11-joy.conf b/src/11-joy.conf new file mode 100755 index 0000000..73d7ba4 --- /dev/null +++ b/src/11-joy.conf @@ -0,0 +1,12 @@ +# +# Make sure joysticks don't control the mouse +# + +Section "InputClass" + Identifier "evdev joystick catchall" + MatchIsJoystick "off" + MatchDevicePath "/dev/input/event*" + Driver "evdev" + Option "StartMouseEnabled" "False" + Option "StartKeysEnabled" "False" +EndSection diff --git a/src/A2PI.PO b/src/A2PI.PO index e1c2e1a..3e11e1b 100755 Binary files a/src/A2PI.PO and b/src/A2PI.PO differ diff --git a/src/Makefile b/src/Makefile index 1a10585..88ad371 100755 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -BIN=a2serclk a2pid a2mon a2term dskread dskwrite bintomon bload brun text2merlin merlin2text +BIN= a2serclk a2pid a2joy a2joymou a2mon a2term dskread dskwrite bintomon bload brun text2merlin merlin2text all: $(BIN) fusea2pi: fusea2pi.c a2lib.c diff --git a/src/a2joy.c b/src/a2joy.c new file mode 100755 index 0000000..6c128f9 --- /dev/null +++ b/src/a2joy.c @@ -0,0 +1,177 @@ +/* + * Copyright 2013, David Schmenk + */ +#include "a2lib.c" +#include +#include +#include +#include +#define FALSE 0 +#define TRUE (!FALSE) +#define POLL_HZ 20 /* must be greater than 1 */ +struct timespec tv; +struct input_event evkey, evabsx, evabsy, evsync; +#define BTTN_IO 0xC061 +#define READGP0 0x380 +#define READGP1 0x388 +char readgp[] = { + 0xA2, 0x00, // LDX #PADDLE + 0x78, // SEI + 0x20, 0x1E, 0xFB, // JSR PREAD + 0x98, // TYA + 0x60, // RTS +}; +/* + * Error handling. + */ +int isdebug, stop = FALSE; +#define prdbg(s) do{if(isdebug)fprintf(stderr,(s));}while(0) +#define die(str, args...) do { \ + prdbg(str); \ + exit(-1); \ +} while(0) +static void sig_bye(int signo) +{ + stop = TRUE; +} + +void main(int argc, char **argv) +{ + struct uinput_user_dev uidev; + int a2fd, joyfd, absx, absy, gptoggle; + unsigned char prevbttns[2], bttns[2]; + + int pifd = a2open("127.0.0.1"); + if (pifd < 0) + { + perror("Unable to connect to Apple II Pi"); + exit(EXIT_FAILURE); + } + /* + * Are we running debug? + */ + if ((argc > 1) && (strcmp(argv[1], "-D") == 0)) + { + isdebug = TRUE; + } + else + { + if (daemon(0, 0) != 0) + die("a2joy: daemon() failure"); + isdebug = FALSE; + } + /* + * Create joystick input device + */ + prdbg("a2joy: Create joystick input device\n"); + joyfd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (joyfd < 0) + die("error: uinput open"); + if (ioctl(joyfd, UI_SET_EVBIT, EV_KEY) < 0) + die("error: uinput ioctl EV_KEY"); + if (ioctl(joyfd, UI_SET_KEYBIT, BTN_TRIGGER) < 0) + die("error: uinput ioctl BTN_LEFT"); + if (ioctl(joyfd, UI_SET_KEYBIT, BTN_THUMB) < 0) + die("error: uinput ioctl BTN_RIGHT"); + if (ioctl(joyfd, UI_SET_EVBIT, EV_ABS) < 0) + die("error: ioctl EV_ABS"); + if (ioctl(joyfd, UI_SET_ABSBIT, ABS_X) < 0) + die("error: ioctl ABS_X"); + if (ioctl(joyfd, UI_SET_ABSBIT, ABS_Y) < 0) + die("error: ioctl ABS_Y"); + if (ioctl(joyfd, UI_SET_EVBIT, EV_SYN) < 0) + die("error: ioctl EV_SYN"); + bzero(&uidev, sizeof(uidev)); + snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "Apple2 Pi Joystick"); + uidev.id.bustype = BUS_RS232; + uidev.id.vendor = 0x05ac; /* apple */ + uidev.id.product = 0x2e; + uidev.id.version = 1; + uidev.absmax[0] = 255; + uidev.absmin[0] = 0; + uidev.absfuzz[0] = 0; + uidev.absflat[0] = 0; + uidev.absmax[1] = 255; + uidev.absmin[1] = 0; + uidev.absfuzz[1] = 0; + uidev.absflat[1] = 0; + write(joyfd, &uidev, sizeof(uidev)); + if (ioctl(joyfd, UI_DEV_CREATE) < 0) + die("error: ioctl DEV_CREATE"); + /* + * Initialize event structures. + */ + bzero(&evkey, sizeof(evkey)); + bzero(&evsync, sizeof(evsync)); + bzero(&evabsx, sizeof(evabsx)); + bzero(&evabsy, sizeof(evabsy)); + evkey.type = EV_KEY; + evabsx.type = EV_ABS; + evabsx.code = ABS_X; + evabsy.type = EV_ABS; + evabsy.code = ABS_Y; + evsync.type = EV_SYN; + /* + * Add signal handlers. + */ + if (signal(SIGINT, sig_bye) == SIG_ERR) + die("signal"); + if (signal(SIGHUP, sig_bye) == SIG_ERR) + die("signal"); + /* + * Set poll rate. + */ + tv.tv_sec = 0; + tv.tv_nsec = isdebug ? 1E+9/2 : 1E+9/POLL_HZ; + /* + * Download 6502 code. + */ + readgp[1] = 0; + a2write(pifd, READGP0, sizeof(readgp), readgp); + readgp[1] = 1; + a2write(pifd, READGP1, sizeof(readgp), readgp); + a2call(pifd, READGP0, &evabsx.value); + a2read(pifd, BTTN_IO, 2, prevbttns); + a2call(pifd, READGP1, &evabsy.value); + gptoggle = 0; + /* + * Poll joystick loop. + */ + prdbg("a2joy: Enter poll loop\n"); + while (!stop) + { + if (gptoggle) + a2quickcall(pifd, READGP0, &absx); + else + a2quickcall(pifd, READGP1, &absy); + a2read(pifd, BTTN_IO, 2, bttns); + gptoggle ^= 1; + if (isdebug) fprintf(stderr, "a2joy (%d, %d) [%d %d]\n", absx, absy, bttns[0] >> 7, bttns[1] >> 7); + evabsx.value = absx; + evabsy.value = absy; + write(joyfd, &evabsx, sizeof(evabsx)); + write(joyfd, &evabsy, sizeof(evabsy)); + write(joyfd, &evsync, sizeof(evsync)); + if ((bttns[0] & 0x80) != prevbttns[0]) + { + prevbttns[0] = bttns[0] & 0x80; + evkey.code = BTN_TRIGGER; + evkey.value = bttns[0] >> 7; + write(joyfd, &evkey, sizeof(evkey)); + write(joyfd, &evsync, sizeof(evsync)); + } + if ((bttns[1] & 0x80) != prevbttns[1]) + { + prevbttns[1] = bttns[1] & 0x80; + evkey.code = BTN_THUMB; + evkey.value = bttns[1] >> 7; + write(joyfd, &evkey, sizeof(evkey)); + write(joyfd, &evsync, sizeof(evsync)); + } + nanosleep(&tv, NULL); + } + a2close(pifd); + ioctl(joyfd, UI_DEV_DESTROY); + close(joyfd); + prdbg("\na2joy: Exit\n"); +} diff --git a/src/a2joymou.c b/src/a2joymou.c new file mode 100755 index 0000000..f127910 --- /dev/null +++ b/src/a2joymou.c @@ -0,0 +1,190 @@ + /* + * Copyright 2013, David Schmenk + */ +#include "a2lib.c" +#include +#include +#include +#include +#define FALSE 0 +#define TRUE (!FALSE) +#define POLL_HZ 30 /* must be greater than 1 */ +struct timespec tv; +struct input_event evkey, evrelx, evrely, evsync; +#define BTTN_IO 0xC061 +#define READGP0 0x380 +#define READGP1 0x388 +char readgp[] = { + 0xA2, 0x00, // LDX #PADDLE + 0x78, // SEI + 0x20, 0x1E, 0xFB, // JSR PREAD + 0x98, // TYA + 0x60, // RTS +}; +int accel[20] = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 7, 9, 10}; +/* + * Error handling. + */ +int isdebug, stop = FALSE; +#define prdbg(s) do{if(isdebug)fprintf(stderr,(s));}while(0) +#define die(str, args...) do { \ + prdbg(str); \ + exit(-1); \ +} while(0) +static void sig_bye(int signo) +{ + stop = TRUE; +} + +void main(int argc, char **argv) +{ + struct uinput_user_dev uidev; + int a2fd, joyfd, relx, rely, cntrx, cntry, gptoggle; + unsigned char prevbttns[2], bttns[2]; + + int pifd = a2open("127.0.0.1"); + if (pifd < 0) + { + perror("Unable to connect to Apple II Pi"); + exit(EXIT_FAILURE); + } + /* + * Are we running debug? + */ + if ((argc > 1) && (strcmp(argv[1], "-D") == 0)) + { + isdebug = TRUE; + } + else + { + if (daemon(0, 0) != 0) + die("a2joy: daemon() failure"); + isdebug = FALSE; + } + /* + * Create joystick input device + */ + prdbg("a2joy: Create joystick input device\n"); + joyfd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (joyfd < 0) + die("error: uinput open"); + if (ioctl(joyfd, UI_SET_EVBIT, EV_KEY) < 0) + die("error: uinput ioctl EV_KEY"); + if (ioctl(joyfd, UI_SET_KEYBIT, BTN_LEFT) < 0) + die("error: uinput ioctl BTN_LEFT"); + if (ioctl(joyfd, UI_SET_KEYBIT, BTN_RIGHT) < 0) + die("error: uinput ioctl BTN_RIGHT"); + if (ioctl(joyfd, UI_SET_EVBIT, EV_REL) < 0) + die("error: ioctl EV_rel"); + if (ioctl(joyfd, UI_SET_RELBIT, REL_X) < 0) + die("error: ioctl rel_X"); + if (ioctl(joyfd, UI_SET_RELBIT, REL_Y) < 0) + die("error: ioctl rel_Y"); + if (ioctl(joyfd, UI_SET_EVBIT, EV_SYN) < 0) + die("error: ioctl EV_SYN"); + bzero(&uidev, sizeof(uidev)); + snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "Apple2 Pi Joystick"); + uidev.id.bustype = BUS_RS232; + uidev.id.vendor = 0x05ac; /* apple */ + uidev.id.product = 0x2e; + uidev.id.version = 1; + write(joyfd, &uidev, sizeof(uidev)); + if (ioctl(joyfd, UI_DEV_CREATE) < 0) + die("error: ioctl DEV_CREATE"); + /* + * Initialize event structures. + */ + bzero(&evkey, sizeof(evkey)); + bzero(&evsync, sizeof(evsync)); + bzero(&evrelx, sizeof(evrelx)); + bzero(&evrely, sizeof(evrely)); + evkey.type = EV_KEY; + evrelx.type = EV_REL; + evrelx.code = REL_X; + evrely.type = EV_REL; + evrely.code = REL_Y; + evsync.type = EV_SYN; + /* + * Add signal handlers. + */ + if (signal(SIGINT, sig_bye) == SIG_ERR) + die("signal"); + if (signal(SIGHUP, sig_bye) == SIG_ERR) + die("signal"); + /* + * Set poll rate. + */ + tv.tv_sec = 0; + tv.tv_nsec = isdebug ? 1E+9/2 : 1E+9/POLL_HZ; + /* + * Download 6502 code. + */ + readgp[1] = 0; + a2write(pifd, READGP0, sizeof(readgp), readgp); + readgp[1] = 1; + a2write(pifd, READGP1, sizeof(readgp), readgp); + evrelx.value = 0; + evrely.value = 0; + a2quickcall(pifd, READGP0, &cntrx); + a2read(pifd, BTTN_IO, 2, prevbttns); + a2quickcall(pifd, READGP1, &cntry); + gptoggle = 0; + /* + * Poll joystick loop. + */ + prdbg("a2joymou: Enter poll loop\n"); + while (!stop) + { + if (gptoggle) + { + a2quickcall(pifd, READGP0, &relx); + if (relx >= cntrx + 20) + evrelx.value = (relx - cntrx) / 2; + else if (relx >= cntrx) + evrelx.value = accel[relx - cntrx]; + else if (relx <= cntrx - 20) + evrelx.value = (relx - cntrx) / 2; + else + evrelx.value = -accel[cntrx - relx]; + } + else + { + a2quickcall(pifd, READGP1, &rely); + if (rely >= cntry + 20) + evrely.value = (rely - cntry) / 2; + else if (rely >= cntry) + evrely.value = accel[rely - cntry]; + else if (rely <= cntry - 20) + evrely.value = (rely - cntry) / 2; + else + evrely.value = -accel[cntry - rely]; + } + a2read(pifd, BTTN_IO, 2, bttns); + gptoggle ^= 1; + if (isdebug) fprintf(stderr, "a2joymou (%d, %d) [%d %d]\n", relx, rely, bttns[0] >> 7, bttns[1] >> 7); + write(joyfd, &evrelx, sizeof(evrelx)); + write(joyfd, &evrely, sizeof(evrely)); + write(joyfd, &evsync, sizeof(evsync)); + if ((bttns[0] & 0x80) != prevbttns[0]) + { + prevbttns[0] = bttns[0] & 0x80; + evkey.code = BTN_LEFT; + evkey.value = bttns[0] >> 7; + write(joyfd, &evkey, sizeof(evkey)); + write(joyfd, &evsync, sizeof(evsync)); + } + if ((bttns[1] & 0x80) != prevbttns[1]) + { + prevbttns[1] = bttns[1] & 0x80; + evkey.code = BTN_RIGHT; + evkey.value = bttns[1] >> 7; + write(joyfd, &evkey, sizeof(evkey)); + write(joyfd, &evsync, sizeof(evsync)); + } + nanosleep(&tv, NULL); + } + a2close(pifd); + ioctl(joyfd, UI_DEV_DESTROY); + close(joyfd); + prdbg("\na2joymou: Exit\n"); +} diff --git a/src/a2lib.c b/src/a2lib.c index c63f29c..be81a80 100755 --- a/src/a2lib.c +++ b/src/a2lib.c @@ -76,6 +76,18 @@ int a2write(int fd, int address, int count, char *buffer) return ((unsigned char)writepkt[0] == 0x9E); } int a2call(int fd, int address, int *result) +{ + char callpkt[4]; + callpkt[0] = 0x9A; // call with keyboard flush + callpkt[1] = address; + callpkt[2] = address >> 8; + write(fd, callpkt, 3); + read(fd, callpkt, 2); + if (result) + *result = (unsigned char)callpkt[1]; + return ((unsigned char)callpkt[0] == 0x9E); +} +int a2quickcall(int fd, int address, int *result) { char callpkt[4]; callpkt[0] = 0x94; // call diff --git a/src/a2pid.c b/src/a2pid.c index ca3786f..25c8fce 100755 --- a/src/a2pid.c +++ b/src/a2pid.c @@ -186,11 +186,11 @@ int keycode[256] = { KEY_X, // x code 78 KEY_Y, // y code 79 KEY_Z, // z code 7A - MOD_SHIFT | KEY_LEFTBRACE, // { code 7B - MOD_SHIFT | KEY_BACKSLASH, // | code 7C - MOD_SHIFT | KEY_RIGHTBRACE, // } code 7D - MOD_SHIFT | KEY_GRAVE, // ~ code 7E - KEY_BACKSPACE, // BS code 7F + MOD_SHIFT | KEY_LEFTBRACE, // { code 7B + MOD_SHIFT | KEY_BACKSLASH, // | code 7C + MOD_SHIFT | KEY_RIGHTBRACE, // } code 7D + MOD_SHIFT | KEY_GRAVE, // ~ code 7E + KEY_BACKSPACE, // BS code 7F /* * w/ closed apple scancodes */ @@ -323,6 +323,7 @@ int keycode[256] = { MOD_SHIFT | KEY_GRAVE, // ~ code 7E KEY_DELETE // DELETE code 7F }; +#define KEYCODE_MAX 0x10000 #define RUN 0 #define STOP 1 #define RESET 2 @@ -400,18 +401,18 @@ void sendkey(int fd, int mod, int key) } else { - sendkeycodedown(fd, code); - if (!(key & KEY_PRESS)) - /* - * missed a key down event - * already synthesized one - */ - sendkeycodeup(fd, code); + if (code != -prevkeycode) /* key may have been released before client call */ + { + sendkeycodedown(fd, code); + if (!(key & KEY_PRESS)) + /* + * missed a key down event + * already synthesized one + */ + sendkeycodeup(fd, code); + } } - prevkeycode = (key & KEY_PRESS) ? code : -1; -} -void flushkey(int fd) -{ + prevkeycode = (key & KEY_PRESS) ? code : -KEYCODE_MAX; } void sendbttn(int fd, int mod, int bttn) { @@ -880,21 +881,22 @@ reset: else state = RESET; break; + case 0x9A: /* acknowledge call with keyboard flush*/ + if (prevkeycode >= 0) /* flush keyboard if going away for awhile */ + { + sendkeycodeup(kbdfd, prevkeycode); + prevkeycode = -prevkeycode; + } case 0x94: /* acknowledge call */ if (a2reqlist) /* better have an outstanding request */ { //printf("a2pid: call address 0x%04X\n", a2reqlist->addr); newtio.c_cc[VMIN] = 1; /* blocking read until 1 char received */ tcsetattr(a2fd, TCSANOW, &newtio); - if (!writeword(a2fd, a2reqlist->addr, 0x95)) + if (!writeword(a2fd, a2reqlist->addr, iopkt[0] + 1)) state = RESET; newtio.c_cc[VMIN] = 3; /* blocking read until 3 chars received */ tcsetattr(a2fd, TCSANOW, &newtio); - if (prevkeycode >= 0) - { - sendkeycodeup(kbdfd, prevkeycode); - prevkeycode = -1; - } } else state = RESET; @@ -917,6 +919,7 @@ reset: for (i = 0; i < MAX_CLIENT; i++) if (a2client[i].flags & CLIENT_COUT) write(a2client[i].fd, iopkt, 2); + break; case 0x9E: /* request complete ok */ case 0x9F: /* request complete error */ @@ -1033,10 +1036,11 @@ reset: } break; case 0x94: /* call */ - if (read(a2client[i].fd, iopkt, 2) == 2) + case 0x9A: /* call with keyboard flush */ + if (read(a2client[i].fd, iopkt + 1, 2) == 2) { - addr = (unsigned char)iopkt[0] | ((unsigned char)iopkt[1] << 8); - addreq(a2fd, i, 0x94, addr, 0, NULL); + addr = (unsigned char)iopkt[1] | ((unsigned char)iopkt[2] << 8); + addreq(a2fd, i, iopkt[0], addr, 0, NULL); } break; case 0x96: /* send input char to Apple II */ diff --git a/src/fusea2pi.c b/src/fusea2pi.c index 5772a33..68e4cc5 100755 --- a/src/fusea2pi.c +++ b/src/fusea2pi.c @@ -956,7 +956,6 @@ static int a2pi_readdir(const char *path, void *buf, fuse_fill_dir_t filler, /* * Root directory, fill with volume names. */ - memset(&straw, 0, sizeof(struct stat)); unix_stat(&stentry, 0x0F, 0xC3, 0, 0, 0, 0); filler(buf, ".", &stentry, 0); filler(buf, "..", &stentry, 0); @@ -969,17 +968,22 @@ static int a2pi_readdir(const char *path, void *buf, fuse_fill_dir_t filler, strncpy(filename, volumes + i + 1, l); filename[l] = '\0'; filler(buf, filename, &stentry, 0); + } + memset(&straw, 0, sizeof(struct stat)); + for (i = 0; i < 16; i++) + if (volblks[i] > 0) + { /* - * Add volume raw device. + * Add raw device. */ - slot = (volumes[i] >> 4) & 0x07; - drive = (volumes[i] >> 7) & 0x01; + slot = i & 0x07; + drive = (i >> 3) & 0x01; strcpy(filename, "s0d0.po"); filename[1] = slot + '0'; filename[3] = drive + '1'; straw.st_mode = S_IFREG | 0644; straw.st_nlink = 1; - straw.st_blocks = volblks[slot | (drive << 3)]; + straw.st_blocks = volblks[i]; straw.st_size = straw.st_blocks * 512; filler(buf, filename, &straw, 0); } @@ -1342,6 +1346,13 @@ int main(int argc, char *argv[]) prodos_get_file_info(volpath, &access, &type, &aux, &storage, &numblks, &mod, &create); volblks[volumes[i] >> 4] = aux; } + /* + * Always add 5 1/4 floppy raw devices. + */ + if (volblks[0x06] == 0) + volblks[0x06] = 280; + if (volblks[0x0E] == 0) + volblks[0x0E] = 280; umask(0); return fuse_main(argc, argv, &a2pi_oper, NULL); } diff --git a/src/install.sh b/src/install.sh index f009f29..3a85345 100755 --- a/src/install.sh +++ b/src/install.sh @@ -24,4 +24,14 @@ if [ -f /etc/rc.local ] ; then sed -e '/^exit/i\# Start Apple II Pi' -e '/^exit/i\/usr/local/bin/a2serclk' -e '/^exit/i\/usr/local/bin/a2pid --daemon' /etc/rc.local.bak > /etc/rc.local chmod +x /etc/rc.local fi -fi \ No newline at end of file +fi +# +# Disable joystick as a mouse in X +# +cp 11-joy.conf /usr/share/X11/xorg.conf.d +# +# Create link to new joystick driver for gsportx +# +if [ ! -e /dev/js0 ] ; then + ln -s /dev/input/js0 /dev/js0 +fi