diff --git a/Apple2/Shell.asm b/Apple2/Shell.asm index cdfe70e..bdd2d5a 100755 --- a/Apple2/Shell.asm +++ b/Apple2/Shell.asm @@ -19,6 +19,7 @@ OutputByte = $c08d+SLOT*$10 InputFlags = $c08b+SLOT*$10 OutputFlags = $c087+SLOT*$10 +ResetCommand = $00 ReadBlockCommand = $01 WriteBlockCommand = $02 GetTimeCommand = $03 @@ -47,15 +48,23 @@ BasL = $28 htab80 = $057b BasCalc = $fbc1 +LastChar = $06 + ESC = $9b .org $2000 Start: jsr $c300 ; force 80 columns + lda LastChar + pha bit ClearKeyboard + lda #ResetCommand + jsr SendByte lda #ShellCommand jsr SendByte jsr DumpOutput + pla + sta LastChar rts DumpOutput: @@ -64,7 +73,7 @@ DumpOutput: cmp #$00 beq endOutput pha - jsr InvertChar + jsr ClearCursor pla cmp #'H' beq setColumn @@ -79,7 +88,7 @@ DumpOutput: cmp #'U' beq moveUp jsr PrintChar - jsr InvertChar + jsr SetCursor jmp DumpOutput checkInput: bit Keyboard ;check for keypress @@ -93,19 +102,19 @@ endOutput: rts clearScreen: jsr Home - jsr InvertChar + jsr SetCursor jmp DumpOutput setColumn: jsr GetByte sta htab sta htab80 - jsr InvertChar + jsr SetCursor jmp DumpOutput setRow: jsr GetByte sta vtab jsr BasCalc - jsr InvertChar + jsr SetCursor jmp DumpOutput setTop: jsr GetByte @@ -119,7 +128,7 @@ moveUp: dec vtab lda vtab jsr BasCalc - jsr InvertChar + jsr SetCursor jmp DumpOutput SendByte: @@ -143,6 +152,8 @@ finishWrite: rts GetByte: + bit Keyboard ; skip byte read if key pressed + bcc keyPressed lda #$1d ;set read flag low sta OutputFlags waitRead: @@ -151,6 +162,7 @@ waitRead: bcc readByte bit Keyboard ;keypress will abort waiting to read bpl waitRead +keyPressed: lda #$1f ;set all flags high and exit sta OutputFlags sec ;failure @@ -169,26 +181,47 @@ finishRead: end: rts -InvertChar: +SetCursor: lda htab80 ;get horizontal location / 2 lsr tay lda TextPage2 - bcc invert + bcc setChar lda TextPage1 -invert: +setChar: lda (BasL),y + sta LastChar ; save so ClearCursor will pick it up + cmp #$e0 + bpl lowerCase + cmp #$c0 + bpl upperCase + cmp #$a0 + bpl symbol + cmp #$80 + bpl noop +symbol: +lowerCase: +invert: eor #$80 + jmp storeChar +upperCase: + and #$1f + jmp storeChar +noop: +storeChar: sta (BasL),y lda TextPage1 -screen40: rts -HelpCommand: -.byte "a2help",$00 -PromptCommand: -.byte "a2prompt",$00 -OldPromptChar: -.byte "]" -DrawCursor: -.byte $80 +ClearCursor: + lda htab80 ;get horizontal location / 2 + lsr + tay + lda TextPage2 + bcc restoreChar + lda TextPage1 +restoreChar: + lda LastChar + sta (BasL),y + lda TextPage1 + rts diff --git a/Apple2/Shell.bin b/Apple2/Shell.bin index 11f24ab..514700a 100644 Binary files a/Apple2/Shell.bin and b/Apple2/Shell.bin differ diff --git a/Apple2/Shell.lst b/Apple2/Shell.lst index 14e5359..96218a7 100644 --- a/Apple2/Shell.lst +++ b/Apple2/Shell.lst @@ -23,6 +23,7 @@ Current file: Shell.asm 000000r 1 InputFlags = $c08b+SLOT*$10 000000r 1 OutputFlags = $c087+SLOT*$10 000000r 1 +000000r 1 ResetCommand = $00 000000r 1 ReadBlockCommand = $01 000000r 1 WriteBlockCommand = $02 000000r 1 GetTimeCommand = $03 @@ -51,152 +52,181 @@ Current file: Shell.asm 000000r 1 htab80 = $057b 000000r 1 BasCalc = $fbc1 000000r 1 +000000r 1 LastChar = $06 +000000r 1 000000r 1 ESC = $9b 000000r 1 000000r 1 .org $2000 002000 1 Start: 002000 1 20 00 C3 jsr $c300 ; force 80 columns -002003 1 2C 10 C0 bit ClearKeyboard -002006 1 A9 09 lda #ShellCommand -002008 1 20 94 20 jsr SendByte -00200B 1 20 0F 20 jsr DumpOutput -00200E 1 60 rts -00200F 1 -00200F 1 DumpOutput: -00200F 1 20 B2 20 jsr GetByte -002012 1 B0 2A bcs checkInput -002014 1 C9 00 cmp #$00 -002016 1 F0 39 beq endOutput -002018 1 48 pha -002019 1 20 DB 20 jsr InvertChar -00201C 1 68 pla -00201D 1 C9 48 cmp #'H' -00201F 1 F0 3A beq setColumn -002021 1 C9 56 cmp #'V' -002023 1 F0 44 beq setRow -002025 1 C9 43 cmp #'C' -002027 1 F0 29 beq clearScreen -002029 1 C9 54 cmp #'T' -00202B 1 F0 4A beq setTop -00202D 1 C9 42 cmp #'B' -00202F 1 F0 4E beq setBottom -002031 1 C9 55 cmp #'U' -002033 1 F0 52 beq moveUp -002035 1 20 ED FD jsr PrintChar -002038 1 20 DB 20 jsr InvertChar -00203B 1 4C 0F 20 jmp DumpOutput -00203E 1 checkInput: -00203E 1 2C 00 C0 bit Keyboard ;check for keypress -002041 1 10 CC bpl DumpOutput ;keep dumping output if no keypress -002043 1 AD 00 C0 lda Keyboard ;send keypress to RPi -002046 1 29 7F and #$7f -002048 1 20 94 20 jsr SendByte -00204B 1 2C 10 C0 bit ClearKeyboard -00204E 1 4C 0F 20 jmp DumpOutput -002051 1 endOutput: -002051 1 60 rts -002052 1 clearScreen: -002052 1 20 58 FC jsr Home -002055 1 20 DB 20 jsr InvertChar -002058 1 4C 0F 20 jmp DumpOutput -00205B 1 setColumn: -00205B 1 20 B2 20 jsr GetByte -00205E 1 85 24 sta htab -002060 1 8D 7B 05 sta htab80 -002063 1 20 DB 20 jsr InvertChar -002066 1 4C 0F 20 jmp DumpOutput -002069 1 setRow: -002069 1 20 B2 20 jsr GetByte -00206C 1 85 25 sta vtab -00206E 1 20 C1 FB jsr BasCalc -002071 1 20 DB 20 jsr InvertChar -002074 1 4C 0F 20 jmp DumpOutput -002077 1 setTop: -002077 1 20 B2 20 jsr GetByte -00207A 1 85 22 sta $22 -00207C 1 4C 0F 20 jmp DumpOutput -00207F 1 setBottom: -00207F 1 20 B2 20 jsr GetByte -002082 1 85 23 sta $23 -002084 1 4C 0F 20 jmp DumpOutput -002087 1 moveUp: -002087 1 C6 25 dec vtab -002089 1 A5 25 lda vtab -00208B 1 20 C1 FB jsr BasCalc -00208E 1 20 DB 20 jsr InvertChar -002091 1 4C 0F 20 jmp DumpOutput -002094 1 -002094 1 SendByte: -002094 1 48 pha -002095 1 waitWrite: -002095 1 AD FB C0 lda InputFlags -002098 1 2A rol -002099 1 2A rol -00209A 1 B0 F9 bcs waitWrite -00209C 1 68 pla -00209D 1 8D FD C0 sta OutputByte -0020A0 1 A9 1E lda #$1e ; set bit 0 low to indicate write started -0020A2 1 8D F7 C0 sta OutputFlags -0020A5 1 finishWrite: -0020A5 1 AD FB C0 lda InputFlags -0020A8 1 2A rol -0020A9 1 2A rol -0020AA 1 90 F9 bcc finishWrite -0020AC 1 A9 1F lda #$1f -0020AE 1 8D F7 C0 sta OutputFlags -0020B1 1 60 rts -0020B2 1 -0020B2 1 GetByte: -0020B2 1 A9 1D lda #$1d ;set read flag low -0020B4 1 8D F7 C0 sta OutputFlags -0020B7 1 waitRead: -0020B7 1 AD FB C0 lda InputFlags -0020BA 1 2A rol -0020BB 1 90 0C bcc readByte -0020BD 1 2C 00 C0 bit Keyboard ;keypress will abort waiting to read -0020C0 1 10 F5 bpl waitRead -0020C2 1 A9 1F lda #$1f ;set all flags high and exit +002003 1 A5 06 lda LastChar +002005 1 48 pha +002006 1 2C 10 C0 bit ClearKeyboard +002009 1 A9 00 lda #ResetCommand +00200B 1 20 9F 20 jsr SendByte +00200E 1 A9 09 lda #ShellCommand +002010 1 20 9F 20 jsr SendByte +002013 1 20 1A 20 jsr DumpOutput +002016 1 68 pla +002017 1 85 06 sta LastChar +002019 1 60 rts +00201A 1 +00201A 1 DumpOutput: +00201A 1 20 BD 20 jsr GetByte +00201D 1 B0 2A bcs checkInput +00201F 1 C9 00 cmp #$00 +002021 1 F0 39 beq endOutput +002023 1 48 pha +002024 1 20 1C 21 jsr ClearCursor +002027 1 68 pla +002028 1 C9 48 cmp #'H' +00202A 1 F0 3A beq setColumn +00202C 1 C9 56 cmp #'V' +00202E 1 F0 44 beq setRow +002030 1 C9 43 cmp #'C' +002032 1 F0 29 beq clearScreen +002034 1 C9 54 cmp #'T' +002036 1 F0 4A beq setTop +002038 1 C9 42 cmp #'B' +00203A 1 F0 4E beq setBottom +00203C 1 C9 55 cmp #'U' +00203E 1 F0 52 beq moveUp +002040 1 20 ED FD jsr PrintChar +002043 1 20 EB 20 jsr SetCursor +002046 1 4C 1A 20 jmp DumpOutput +002049 1 checkInput: +002049 1 2C 00 C0 bit Keyboard ;check for keypress +00204C 1 10 CC bpl DumpOutput ;keep dumping output if no keypress +00204E 1 AD 00 C0 lda Keyboard ;send keypress to RPi +002051 1 29 7F and #$7f +002053 1 20 9F 20 jsr SendByte +002056 1 2C 10 C0 bit ClearKeyboard +002059 1 4C 1A 20 jmp DumpOutput +00205C 1 endOutput: +00205C 1 60 rts +00205D 1 clearScreen: +00205D 1 20 58 FC jsr Home +002060 1 20 EB 20 jsr SetCursor +002063 1 4C 1A 20 jmp DumpOutput +002066 1 setColumn: +002066 1 20 BD 20 jsr GetByte +002069 1 85 24 sta htab +00206B 1 8D 7B 05 sta htab80 +00206E 1 20 EB 20 jsr SetCursor +002071 1 4C 1A 20 jmp DumpOutput +002074 1 setRow: +002074 1 20 BD 20 jsr GetByte +002077 1 85 25 sta vtab +002079 1 20 C1 FB jsr BasCalc +00207C 1 20 EB 20 jsr SetCursor +00207F 1 4C 1A 20 jmp DumpOutput +002082 1 setTop: +002082 1 20 BD 20 jsr GetByte +002085 1 85 22 sta $22 +002087 1 4C 1A 20 jmp DumpOutput +00208A 1 setBottom: +00208A 1 20 BD 20 jsr GetByte +00208D 1 85 23 sta $23 +00208F 1 4C 1A 20 jmp DumpOutput +002092 1 moveUp: +002092 1 C6 25 dec vtab +002094 1 A5 25 lda vtab +002096 1 20 C1 FB jsr BasCalc +002099 1 20 EB 20 jsr SetCursor +00209C 1 4C 1A 20 jmp DumpOutput +00209F 1 +00209F 1 SendByte: +00209F 1 48 pha +0020A0 1 waitWrite: +0020A0 1 AD FB C0 lda InputFlags +0020A3 1 2A rol +0020A4 1 2A rol +0020A5 1 B0 F9 bcs waitWrite +0020A7 1 68 pla +0020A8 1 8D FD C0 sta OutputByte +0020AB 1 A9 1E lda #$1e ; set bit 0 low to indicate write started +0020AD 1 8D F7 C0 sta OutputFlags +0020B0 1 finishWrite: +0020B0 1 AD FB C0 lda InputFlags +0020B3 1 2A rol +0020B4 1 2A rol +0020B5 1 90 F9 bcc finishWrite +0020B7 1 A9 1F lda #$1f +0020B9 1 8D F7 C0 sta OutputFlags +0020BC 1 60 rts +0020BD 1 +0020BD 1 GetByte: +0020BD 1 2C 00 C0 bit Keyboard ; skip byte read if key pressed +0020C0 1 90 10 bcc keyPressed +0020C2 1 A9 1D lda #$1d ;set read flag low 0020C4 1 8D F7 C0 sta OutputFlags -0020C7 1 38 sec ;failure -0020C8 1 60 rts -0020C9 1 readByte: -0020C9 1 AD FE C0 lda InputByte -0020CC 1 48 pha -0020CD 1 A9 1F lda #$1f ;set all flags high -0020CF 1 8D F7 C0 sta OutputFlags -0020D2 1 finishRead: -0020D2 1 AD FB C0 lda InputFlags -0020D5 1 2A rol -0020D6 1 90 FA bcc finishRead -0020D8 1 68 pla -0020D9 1 18 clc ;success -0020DA 1 end: -0020DA 1 60 rts -0020DB 1 -0020DB 1 InvertChar: -0020DB 1 AD 7B 05 lda htab80 ;get horizontal location / 2 -0020DE 1 4A lsr -0020DF 1 A8 tay -0020E0 1 AD 55 C0 lda TextPage2 -0020E3 1 90 03 bcc invert -0020E5 1 AD 54 C0 lda TextPage1 -0020E8 1 invert: -0020E8 1 B1 28 lda (BasL),y -0020EA 1 49 80 eor #$80 -0020EC 1 91 28 sta (BasL),y -0020EE 1 AD 54 C0 lda TextPage1 -0020F1 1 screen40: -0020F1 1 60 rts -0020F2 1 -0020F2 1 HelpCommand: -0020F2 1 61 32 68 65 .byte "a2help",$00 -0020F6 1 6C 70 00 -0020F9 1 PromptCommand: -0020F9 1 61 32 70 72 .byte "a2prompt",$00 -0020FD 1 6F 6D 70 74 -002101 1 00 -002102 1 OldPromptChar: -002102 1 5D .byte "]" -002103 1 DrawCursor: -002103 1 80 .byte $80 -002103 1 +0020C7 1 waitRead: +0020C7 1 AD FB C0 lda InputFlags +0020CA 1 2A rol +0020CB 1 90 0C bcc readByte +0020CD 1 2C 00 C0 bit Keyboard ;keypress will abort waiting to read +0020D0 1 10 F5 bpl waitRead +0020D2 1 keyPressed: +0020D2 1 A9 1F lda #$1f ;set all flags high and exit +0020D4 1 8D F7 C0 sta OutputFlags +0020D7 1 38 sec ;failure +0020D8 1 60 rts +0020D9 1 readByte: +0020D9 1 AD FE C0 lda InputByte +0020DC 1 48 pha +0020DD 1 A9 1F lda #$1f ;set all flags high +0020DF 1 8D F7 C0 sta OutputFlags +0020E2 1 finishRead: +0020E2 1 AD FB C0 lda InputFlags +0020E5 1 2A rol +0020E6 1 90 FA bcc finishRead +0020E8 1 68 pla +0020E9 1 18 clc ;success +0020EA 1 end: +0020EA 1 60 rts +0020EB 1 +0020EB 1 SetCursor: +0020EB 1 AD 7B 05 lda htab80 ;get horizontal location / 2 +0020EE 1 4A lsr +0020EF 1 A8 tay +0020F0 1 AD 55 C0 lda TextPage2 +0020F3 1 90 03 bcc setChar +0020F5 1 AD 54 C0 lda TextPage1 +0020F8 1 setChar: +0020F8 1 B1 28 lda (BasL),y +0020FA 1 85 06 sta LastChar ; save so ClearCursor will pick it up +0020FC 1 C9 E0 cmp #$e0 +0020FE 1 10 0C bpl lowerCase +002100 1 C9 C0 cmp #$c0 +002102 1 10 0D bpl upperCase +002104 1 C9 A0 cmp #$a0 +002106 1 10 04 bpl symbol +002108 1 C9 80 cmp #$80 +00210A 1 10 0A bpl noop +00210C 1 symbol: +00210C 1 lowerCase: +00210C 1 invert: +00210C 1 49 80 eor #$80 +00210E 1 4C 16 21 jmp storeChar +002111 1 upperCase: +002111 1 29 1F and #$1f +002113 1 4C 16 21 jmp storeChar +002116 1 noop: +002116 1 storeChar: +002116 1 91 28 sta (BasL),y +002118 1 AD 54 C0 lda TextPage1 +00211B 1 60 rts +00211C 1 +00211C 1 ClearCursor: +00211C 1 AD 7B 05 lda htab80 ;get horizontal location / 2 +00211F 1 4A lsr +002120 1 A8 tay +002121 1 AD 55 C0 lda TextPage2 +002124 1 90 03 bcc restoreChar +002126 1 AD 54 C0 lda TextPage1 +002129 1 restoreChar: +002129 1 A5 06 lda LastChar +00212B 1 91 28 sta (BasL),y +00212D 1 AD 54 C0 lda TextPage1 +002130 1 60 rts +002130 1 diff --git a/README.md b/README.md index 40b69cd..7139746 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Apple II expansion card using a Raspberry Pi for I/O The purpose of this project is to provide I/O for an Apple II series 8 bit computer via a Raspberry Pi Zero W which is powered by the Apple II expansion bus. This includes using the attached RPi Zero W for it's storage, network and processor to provide new functionality for the Apple II. ## Project Status -So far, this is a project and not a finished product. The current prototype is on the fifth revision and a few have been assembled and tested. It is now possible for the Apple II to boot from and write to a virutal hard drive image stored on the RPi in any slot and execute simple commands on the RPi via the Apple II. The code has very few tests and is incomplete. Note that currently the firmware assumes an 80 column card is in slot 3 and than you have lowercase support. If you have a problem or idea for enhancement, log an issue [here](https://github.com/tjboldt/Apple2-IO-RPi/issues). I recommend starring/watching the project for updates on GitHub. You are welcome to fork the project and submit pull requests which I will review. +So far, this is a project and not a finished product. The current prototype is on the fifth revision and a few have been assembled and tested. It is now possible for the Apple II to boot from and write to a virutal hard drive image stored on the RPi in any slot and run a bash shell on the RPi via the Apple II. The code has very few tests and is incomplete. Note that currently the firmware assumes an 80 column card is in slot 3 and than you have lowercase support. If you have a problem or idea for enhancement, log an issue [here](https://github.com/tjboldt/Apple2-IO-RPi/issues). I recommend starring/watching the project for updates on GitHub. You are welcome to fork the project and submit pull requests which I will review. ## Features 1. Boot message which waits for RPi to be ready @@ -97,7 +97,7 @@ This must be done via ssh directly into the RPi: 7. `cd ProDOS-Utilities` 8. `cd ~/Apple2-IO-RPi/RaspberryPi/apple2driver` 9. `go build` -10. Edit the Driver autostart via cronjob (`crontab -e` then edit the line to be `@reboot /home/pi/Apple2-IO-RPi/RaspberryPi/apple2driver/apple2driver -d1 YOUR_DRIVE.hdv -d2 /home/pi/Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.hdv > /home/pi/Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.log`) or simply `@reboot /home/pi/Apple2-IO-RPi/RaspberryPi/apple2driver/apple2driver > /home/pi/Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.log` if you don't need your own drive image. +10. Edit the Driver autostart via cronjob (`crontab -e` then edit the line to be `@reboot . /home/pi/.profile; /home/pi/Apple2-IO-RPi/RaspberryPi/apple2driver/apple2driver -d1 YOUR_DRIVE.hdv -d2 /home/pi/Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.hdv > /home/pi/Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.log`) or simply `@reboot /home/pi/Apple2-IO-RPi/RaspberryPi/apple2driver/apple2driver > /home/pi/Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.log` if you don't need your own drive image. ## Similar Project If you prefer having Apple II peripherals control a Raspberry Pi rather than simply using the Raspberry Pi to provide storage, network access and processing to the Apple II, have a look at David Schmenk's excellent [Apple2Pi](https://github.com/dschmenk/apple2pi) project. diff --git a/RaspberryPi/Apple2-IO-RPi.hdv b/RaspberryPi/Apple2-IO-RPi.hdv index 0bcd870..7b4dd95 100755 Binary files a/RaspberryPi/Apple2-IO-RPi.hdv and b/RaspberryPi/Apple2-IO-RPi.hdv differ diff --git a/RaspberryPi/apple2driver/a2io/vt100.go b/RaspberryPi/apple2driver/a2io/vt100.go index 72a7b4e..ad7a8cc 100644 --- a/RaspberryPi/apple2driver/a2io/vt100.go +++ b/RaspberryPi/apple2driver/a2io/vt100.go @@ -8,13 +8,10 @@ package a2io import ( "fmt" - "time" ) var escapeSequence string var operatingSystemSequence bool -var htab, vtab, savedHtab, savedVtab int -var applicationMode bool var windowTop int var windowBottom = 23 @@ -24,28 +21,34 @@ func sendCharacter(comm A2Io, b byte) { return } if b == 13 { - fmt.Printf("CR\n") + // fmt.Printf("\nCR\n") comm.WriteByte('H') comm.WriteByte(0) return } - if b > 13 && b < 32 { - fmt.Printf("Control code: %02X\n", b) + if b > 0x0d && b < 0x20 || b > 0x80 && b < 0xa0 { + // fmt.Printf("Control code: %02X\n", b) + return + } + if b >= 0xa0 { + comm.WriteByte('+' | 0x80) + // fmt.Printf("Code: %02X\n", b) return } if len(escapeSequence) == 0 { fmt.Printf("%c", b) } if len(escapeSequence) > 0 { + // Parse the escape codes that don't end with a letter escapeSequence += string(b) if escapeSequence == "^[]" { - fmt.Printf("Start operating system sequence\n") + // fmt.Printf("Start operating system sequence\n") operatingSystemSequence = true return } if operatingSystemSequence { if b == 0x07 { - fmt.Printf("Operating system sequence: %s\n", escapeSequence) + // fmt.Printf("Operating system sequence: %s\n", escapeSequence) operatingSystemSequence = false escapeSequence = "" } @@ -53,187 +56,167 @@ func sendCharacter(comm A2Io, b byte) { } // save cursor if escapeSequence == "^[7" { - savedHtab = htab - savedVtab = vtab - fmt.Printf("Save Cursor (%d, %d): %s\n", htab, vtab, escapeSequence) escapeSequence = "" } // restore cursor if escapeSequence == "^[8" { - htab = savedHtab - vtab = savedVtab - comm.WriteByte('H') - comm.WriteByte(byte(htab)) - comm.WriteByte('V') - comm.WriteByte(byte(vtab)) - fmt.Printf("Restore Cursor (%d, %d): %s\n", htab, vtab, escapeSequence) + escapeSequence = "" + } + if escapeSequence == "^[>" { + // this sequence is undocumented and shows up exiting "top" command escapeSequence = "" } if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') { + // Parse simple escape codes + switch escapeSequence { + // Set/clear application mode for cursor + case "^[[?1h": + //applicationMode = true + // fmt.Printf("Start application mode: %s\n", escapeSequence) + escapeSequence = "" + case "^[[?1l": + //applicationMode = false + comm.WriteByte('T') + comm.WriteByte(0x00) + comm.WriteByte('B') + comm.WriteByte(0x18) + // fmt.Printf("End application mode: %s\n", escapeSequence) + escapeSequence = "" + // Tab to home position + case "^[[f", "^[[;f": + comm.WriteByte(0x19) // ^Y moves to home position + // fmt.Printf("Home: %s\n", escapeSequence) + escapeSequence = "" + // Clear screen + case "^[[2J", "^[[c": + comm.WriteByte(0x0c) // ^L clears the screen + // fmt.Printf("Clear screen: %s\n", escapeSequence) + escapeSequence = "" + case "^[E": + comm.WriteByte(0x0A) // ^J moves cursor down + // fmt.Printf("Move down: %s\n", escapeSequence) + escapeSequence = "" + case "^[D": + comm.WriteByte(0x17) // ^W scrolls up + // fmt.Printf("Scroll up: %s\n", escapeSequence) + escapeSequence = "" + case "^[[K", "^[[0K": + comm.WriteByte(0x1d) // ^] clears to end of line + // fmt.Printf("Clear line right: %s\n", escapeSequence) + escapeSequence = "" + case "^[[2K": + comm.WriteByte(0x1a) // ^Z clears line + // fmt.Printf("Clear line: %s\n", escapeSequence) + escapeSequence = "" + case "^[M": + comm.WriteByte(0x16) // ^V scrolls down + // fmt.Printf("Scroll down: %s\n", escapeSequence) + escapeSequence = "" + case "^[[J": + comm.WriteByte(0x0b) // ^K clears to end of screen + // fmt.Printf("Clear below cursor: %s\n", escapeSequence) + escapeSequence = "" + case "^[[7m": + comm.WriteByte(0x0f) // ^O inverse video + // fmt.Printf("Inverse: %s\n", escapeSequence) + escapeSequence = "" + case "^[[m", "^[[0m", "^[[0;7m", "^[[0;1m": + comm.WriteByte(0x0e) // ^N normal video + // fmt.Printf("Normal: %s\n", escapeSequence) + escapeSequence = "" + } + + // Parse escape codes that need further parsing switch b { // Set cursor location case 'H', 'f': - if escapeSequence == "^[[H" || escapeSequence == "^[[;H" { - htab = 0 - vtab = 0 + if escapeSequence == "^[[H" || escapeSequence == "^[[;H" { comm.WriteByte(0x19) // ^Y moves to home position - fmt.Printf("Home: %s\n", escapeSequence) + // fmt.Printf("Home: %s\n", escapeSequence) escapeSequence = "" } else { var ignore string + var htab, vtab int fmt.Sscanf(escapeSequence, "^[[%d;%d%s", &vtab, &htab, &ignore) htab-- vtab-- - if htab < 0 { + if htab < 0 { // this occastionally gets called with 0 that becomes -1 htab = 0 } - if vtab > 23 { + if vtab > 23 { // top command sets vtab 25 on exit even in 24 line mode vtab = 23 } comm.WriteByte('H') comm.WriteByte(byte(htab)) comm.WriteByte('V') comm.WriteByte(byte(vtab)) - fmt.Printf("Set Cursor (%d, %d): %s\n", htab, vtab, escapeSequence) + // fmt.Printf("Set Cursor (%d, %d): %s\n", htab, vtab, escapeSequence) escapeSequence = "" } case 'r': fmt.Sscanf(escapeSequence, "^[[%d;%dr", &windowTop, &windowBottom) windowTop-- - //windowBottom-- comm.WriteByte('T') comm.WriteByte(byte(windowTop)) comm.WriteByte('B') comm.WriteByte(byte(windowBottom)) - fmt.Printf("Set Window (%d, %d): %s\n", windowTop, windowBottom, escapeSequence) + // fmt.Printf("Set Window (%d, %d): %s\n", windowTop, windowBottom, escapeSequence) escapeSequence = "" case 'A': if escapeSequence == "^[[A" || escapeSequence == "^[A" { - vtab-- comm.WriteByte('U') - fmt.Printf("Up: %s\n", escapeSequence) + // fmt.Printf("Up: %s\n", escapeSequence) } else { var up int fmt.Sscanf(escapeSequence, "^[[%dA", &up) - vtab -= up for i := 0; i < up; i++ { comm.WriteByte('U') } - fmt.Printf("Up (%d): %s\n", up, escapeSequence) + // fmt.Printf("Up (%d): %s\n", up, escapeSequence) } escapeSequence = "" case 'B': if escapeSequence == "^[(B" || escapeSequence == "^[)B" || escapeSequence == "^[B" { escapeSequence = "" } else if escapeSequence == "^[[B" { - vtab++ comm.WriteByte(0x0a) - fmt.Printf("Down: %s\n", escapeSequence) + // fmt.Printf("Down: %s\n", escapeSequence) } else { var down int fmt.Sscanf(escapeSequence, "^[[%dB", &down) - vtab += down for i := 0; i < down; i++ { comm.WriteByte(0x0a) } - fmt.Printf("Down (%d): %s\n", down, escapeSequence) + // fmt.Printf("Down (%d): %s\n", down, escapeSequence) } escapeSequence = "" case 'C': if escapeSequence == "^[[C" || escapeSequence == "^[C" { - htab++ comm.WriteByte(0x1c) - fmt.Printf("Right: %s\n", escapeSequence) + // fmt.Printf("Right: %s\n", escapeSequence) } else { var right int fmt.Sscanf(escapeSequence, "^[[%dC", &right) - htab += right for i := 0; i < right; i++ { comm.WriteByte(0x1c) } - fmt.Printf("Right (%d): %s\n", right, escapeSequence) + // fmt.Printf("Right (%d): %s\n", right, escapeSequence) } escapeSequence = "" case 'D': if escapeSequence == "^[[D" || escapeSequence == "^[D" { - htab-- comm.WriteByte(0x08) - fmt.Printf("Left: %s\n", escapeSequence) + // fmt.Printf("Left: %s\n", escapeSequence) } else { var left int fmt.Sscanf(escapeSequence, "^[[%dD", &left) - htab -= left for i := 0; i < left; i++ { comm.WriteByte(0x08) } - fmt.Printf("Left (%d): %s\n", left, escapeSequence) + // fmt.Printf("Left (%d): %s\n", left, escapeSequence) } escapeSequence = "" } - switch escapeSequence { - // Set/clear application mode for cursor - case "^[[?1h": - applicationMode = true - //comm.WriteByte(0x0c) // ^L clears the screen - fmt.Printf("Start application mode: %s\n", escapeSequence) - escapeSequence = "" - case "^[[?1l": - applicationMode = false - comm.WriteByte('T') - comm.WriteByte(0x00) - comm.WriteByte('B') - comm.WriteByte(0x18) - //comm.WriteByte(0x0c) // ^L clears the screen - fmt.Printf("End application mode: %s\n", escapeSequence) - escapeSequence = "" - // Tab to home position - case "^[[f", "^[[;f": - htab = 0 - vtab = 0 - comm.WriteByte(0x19) // ^Y moves to home position - fmt.Printf("Home: %s\n", escapeSequence) - escapeSequence = "" - // Clear screen - case "^[[2J", "^[[c": - htab = 0 - vtab = 0 - comm.WriteByte(0x0c) // ^L clears the screen - fmt.Printf("Clear screen: %s\n", escapeSequence) - escapeSequence = "" - case "^[E": - comm.WriteByte(0x0A) // ^J moves cursor down - fmt.Printf("Move down: %s\n", escapeSequence) - escapeSequence = "" - case "^[D": - comm.WriteByte(0x17) // ^W scrolls up - fmt.Printf("Scroll up: %s\n", escapeSequence) - escapeSequence = "" - case "^[[K", "^[[0K": - comm.WriteByte(0x1d) // ^] clears to end of line - fmt.Printf("Clear line right: %s\n", escapeSequence) - escapeSequence = "" - case "^[[2K": - comm.WriteByte(0x1a) // ^Z clears line - fmt.Printf("Clear line: %s\n", escapeSequence) - escapeSequence = "" - case "^[M": - comm.WriteByte(0x16) // ^V scrolls down - fmt.Printf("Scroll down: %s\n", escapeSequence) - escapeSequence = "" - case "^[[J": - comm.WriteByte(0x0b) // ^K clears to end of screen - fmt.Printf("Clear below cursor: %s\n", escapeSequence) - escapeSequence = "" - case "^[[7m": - comm.WriteByte(0x0f) // ^O inverse video - fmt.Printf("Inverse: %s\n", escapeSequence) - escapeSequence = "" - case "^[[m", "^[[0m": - comm.WriteByte(0x0e) // ^N normal video - fmt.Printf("Normal: %s\n", escapeSequence) - escapeSequence = "" - } - if len(escapeSequence) > 0 { fmt.Printf("Unhandled escape sequence: %s\n", escapeSequence) } @@ -243,43 +226,20 @@ func sendCharacter(comm A2Io, b byte) { return } //fmt.Print(string(b)) - htabIncrement := 0 switch b { // convert LF to CR for Apple II compatiblity case 10: b = 13 - vtab++ - htab = 0 - htabIncrement = 0 case 13: - htab = 0 - htabIncrement = 0 return // convert TAB to spaces case 9: b = ' ' b |= 0x80 - err := comm.WriteByte(b) - if err != nil { - // try again because could have been cancelled by input - time.Sleep(time.Millisecond * 10) - comm.WriteByte(b) - } - htabIncrement = 2 - default: - htabIncrement = 1 - } - if !applicationMode || htab < 79 { - updateTabs(comm) - b |= 0x80 - htab += htabIncrement - err := comm.WriteByte(b) - if err != nil { - // try again because could have been cancelled by input - time.Sleep(time.Millisecond * 10) - comm.WriteByte(b) - } + comm.WriteByte(b) } + b |= 0x80 + comm.WriteByte(b) } func readCharacter(comm A2Io) (string, error) { @@ -301,13 +261,3 @@ func readCharacter(comm A2Io) (string, error) { } return s, err } - -func updateTabs(comm A2Io) { - if htab >= 80 { - htab = 0 - vtab++ - } - if vtab > windowBottom { - vtab = windowBottom - } -} diff --git a/RaspberryPi/apple2driver/handlers/exec.go b/RaspberryPi/apple2driver/handlers/exec.go index da8eb22..990f26f 100644 --- a/RaspberryPi/apple2driver/handlers/exec.go +++ b/RaspberryPi/apple2driver/handlers/exec.go @@ -46,8 +46,12 @@ func ExecCommand() { a2help() return } + if linuxCommand == "a2lower" { + a2lower(false) + return + } if linuxCommand == "A2LOWER" { - a2lower() + a2lower(true) return } if linuxCommand == "a2wifi" { @@ -158,15 +162,16 @@ func getStdin(stdin io.WriteCloser, done chan bool, inputComplete chan bool, use inputComplete <- true return default: - s, err := comm.ReadCharacter() + b, err := comm.ReadByte() if err == nil { - if s == string(byte(0x00)) { + if b == 0x00 || b == 0x03 { stdin.Close() userCancelled <- true fmt.Printf("\nUser cancelled stdin\n") return } - io.WriteString(stdin, string(s)) + bb := make([]byte, 1) + stdin.Write(bb) } } } @@ -174,20 +179,18 @@ func getStdin(stdin io.WriteCloser, done chan bool, inputComplete chan bool, use func a2help() { comm.WriteString("\r" + - "This is a pseudo shell. Each command is executed as a process. The cd command\r" + - "is intercepted and sets the working directory for the next command. The exit\r" + - "command will exit the shell when not running from firmware.\r" + - "\r" + "Built-in commands:\r" + + "------------------\r" + "a2help - display this message\r" + "a2wifi - set up wifi\r" + "A2LOWER - force lowercase for II+\r" + + "a2lower - disable force lowercase for II+\r" + "\r") } -func a2lower() { - forceLowercase = true - comm.WriteString("All commands will be converted to lowercase\r") +func a2lower(enable bool) { + forceLowercase = enable + comm.WriteString(fmt.Sprintf("All commands will be converted to lowercase: %t\r", forceLowercase)) } func a2wifi() { diff --git a/RaspberryPi/apple2driver/handlers/shell.go b/RaspberryPi/apple2driver/handlers/shell.go index 599b982..96c8c24 100755 --- a/RaspberryPi/apple2driver/handlers/shell.go +++ b/RaspberryPi/apple2driver/handlers/shell.go @@ -8,6 +8,7 @@ package handlers import ( "fmt" + "io" "os" "os/exec" @@ -36,8 +37,8 @@ func ShellCommand() { inputComplete := make(chan bool) userCancelled := make(chan bool) - go getStdin(ptmx, outputComplete, inputComplete, userCancelled) - go getStdout(ptmx, outputComplete, userCancelled) + go ptyIn(ptmx, outputComplete, inputComplete, userCancelled) + go ptyOut(ptmx, outputComplete, userCancelled) for { select { @@ -58,3 +59,50 @@ func ShellCommand() { } } } + +func ptyOut(stdout io.ReadCloser, outputComplete chan bool, userCancelled chan bool) { + for { + select { + case <-userCancelled: + fmt.Printf("User Cancelled stdout\n") + stdout.Close() + return + default: + bb := make([]byte, 1) + n, err := stdout.Read(bb) + if err != nil { + stdout.Close() + outputComplete <- true + fmt.Printf("stdout closed\n") + return + } + if n > 0 { + b := bb[0] + comm.SendCharacter(b) + } + } + } +} + +func ptyIn(stdin io.WriteCloser, done chan bool, inputComplete chan bool, userCancelled chan bool) { + for { + select { + case <-done: + stdin.Close() + inputComplete <- true + fmt.Printf("stdin closed\n") + return + default: + s, err := comm.ReadCharacter() + if err == nil { + if s == string(byte(0x00)) { + stdin.Close() + userCancelled <- true + fmt.Printf("\nUser cancelled stdin\n") + return + } + io.WriteString(stdin, string(s)) + } + } + } +}