Fix cursor and improve shell (#48)

* Fix cursor and improve shell

* Update README
This commit is contained in:
Terence Boldt 2021-11-26 22:51:52 -05:00 committed by GitHub
parent a929790624
commit de69c54007
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 383 additions and 319 deletions

View File

@ -19,6 +19,7 @@ OutputByte = $c08d+SLOT*$10
InputFlags = $c08b+SLOT*$10 InputFlags = $c08b+SLOT*$10
OutputFlags = $c087+SLOT*$10 OutputFlags = $c087+SLOT*$10
ResetCommand = $00
ReadBlockCommand = $01 ReadBlockCommand = $01
WriteBlockCommand = $02 WriteBlockCommand = $02
GetTimeCommand = $03 GetTimeCommand = $03
@ -47,15 +48,23 @@ BasL = $28
htab80 = $057b htab80 = $057b
BasCalc = $fbc1 BasCalc = $fbc1
LastChar = $06
ESC = $9b ESC = $9b
.org $2000 .org $2000
Start: Start:
jsr $c300 ; force 80 columns jsr $c300 ; force 80 columns
lda LastChar
pha
bit ClearKeyboard bit ClearKeyboard
lda #ResetCommand
jsr SendByte
lda #ShellCommand lda #ShellCommand
jsr SendByte jsr SendByte
jsr DumpOutput jsr DumpOutput
pla
sta LastChar
rts rts
DumpOutput: DumpOutput:
@ -64,7 +73,7 @@ DumpOutput:
cmp #$00 cmp #$00
beq endOutput beq endOutput
pha pha
jsr InvertChar jsr ClearCursor
pla pla
cmp #'H' cmp #'H'
beq setColumn beq setColumn
@ -79,7 +88,7 @@ DumpOutput:
cmp #'U' cmp #'U'
beq moveUp beq moveUp
jsr PrintChar jsr PrintChar
jsr InvertChar jsr SetCursor
jmp DumpOutput jmp DumpOutput
checkInput: checkInput:
bit Keyboard ;check for keypress bit Keyboard ;check for keypress
@ -93,19 +102,19 @@ endOutput:
rts rts
clearScreen: clearScreen:
jsr Home jsr Home
jsr InvertChar jsr SetCursor
jmp DumpOutput jmp DumpOutput
setColumn: setColumn:
jsr GetByte jsr GetByte
sta htab sta htab
sta htab80 sta htab80
jsr InvertChar jsr SetCursor
jmp DumpOutput jmp DumpOutput
setRow: setRow:
jsr GetByte jsr GetByte
sta vtab sta vtab
jsr BasCalc jsr BasCalc
jsr InvertChar jsr SetCursor
jmp DumpOutput jmp DumpOutput
setTop: setTop:
jsr GetByte jsr GetByte
@ -119,7 +128,7 @@ moveUp:
dec vtab dec vtab
lda vtab lda vtab
jsr BasCalc jsr BasCalc
jsr InvertChar jsr SetCursor
jmp DumpOutput jmp DumpOutput
SendByte: SendByte:
@ -143,6 +152,8 @@ finishWrite:
rts rts
GetByte: GetByte:
bit Keyboard ; skip byte read if key pressed
bcc keyPressed
lda #$1d ;set read flag low lda #$1d ;set read flag low
sta OutputFlags sta OutputFlags
waitRead: waitRead:
@ -151,6 +162,7 @@ waitRead:
bcc readByte bcc readByte
bit Keyboard ;keypress will abort waiting to read bit Keyboard ;keypress will abort waiting to read
bpl waitRead bpl waitRead
keyPressed:
lda #$1f ;set all flags high and exit lda #$1f ;set all flags high and exit
sta OutputFlags sta OutputFlags
sec ;failure sec ;failure
@ -169,26 +181,47 @@ finishRead:
end: end:
rts rts
InvertChar: SetCursor:
lda htab80 ;get horizontal location / 2 lda htab80 ;get horizontal location / 2
lsr lsr
tay tay
lda TextPage2 lda TextPage2
bcc invert bcc setChar
lda TextPage1 lda TextPage1
invert: setChar:
lda (BasL),y 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 eor #$80
jmp storeChar
upperCase:
and #$1f
jmp storeChar
noop:
storeChar:
sta (BasL),y sta (BasL),y
lda TextPage1 lda TextPage1
screen40:
rts rts
HelpCommand: ClearCursor:
.byte "a2help",$00 lda htab80 ;get horizontal location / 2
PromptCommand: lsr
.byte "a2prompt",$00 tay
OldPromptChar: lda TextPage2
.byte "]" bcc restoreChar
DrawCursor: lda TextPage1
.byte $80 restoreChar:
lda LastChar
sta (BasL),y
lda TextPage1
rts

Binary file not shown.

View File

@ -23,6 +23,7 @@ Current file: Shell.asm
000000r 1 InputFlags = $c08b+SLOT*$10 000000r 1 InputFlags = $c08b+SLOT*$10
000000r 1 OutputFlags = $c087+SLOT*$10 000000r 1 OutputFlags = $c087+SLOT*$10
000000r 1 000000r 1
000000r 1 ResetCommand = $00
000000r 1 ReadBlockCommand = $01 000000r 1 ReadBlockCommand = $01
000000r 1 WriteBlockCommand = $02 000000r 1 WriteBlockCommand = $02
000000r 1 GetTimeCommand = $03 000000r 1 GetTimeCommand = $03
@ -51,152 +52,181 @@ Current file: Shell.asm
000000r 1 htab80 = $057b 000000r 1 htab80 = $057b
000000r 1 BasCalc = $fbc1 000000r 1 BasCalc = $fbc1
000000r 1 000000r 1
000000r 1 LastChar = $06
000000r 1
000000r 1 ESC = $9b 000000r 1 ESC = $9b
000000r 1 000000r 1
000000r 1 .org $2000 000000r 1 .org $2000
002000 1 Start: 002000 1 Start:
002000 1 20 00 C3 jsr $c300 ; force 80 columns 002000 1 20 00 C3 jsr $c300 ; force 80 columns
002003 1 2C 10 C0 bit ClearKeyboard 002003 1 A5 06 lda LastChar
002006 1 A9 09 lda #ShellCommand 002005 1 48 pha
002008 1 20 94 20 jsr SendByte 002006 1 2C 10 C0 bit ClearKeyboard
00200B 1 20 0F 20 jsr DumpOutput 002009 1 A9 00 lda #ResetCommand
00200E 1 60 rts 00200B 1 20 9F 20 jsr SendByte
00200F 1 00200E 1 A9 09 lda #ShellCommand
00200F 1 DumpOutput: 002010 1 20 9F 20 jsr SendByte
00200F 1 20 B2 20 jsr GetByte 002013 1 20 1A 20 jsr DumpOutput
002012 1 B0 2A bcs checkInput 002016 1 68 pla
002014 1 C9 00 cmp #$00 002017 1 85 06 sta LastChar
002016 1 F0 39 beq endOutput 002019 1 60 rts
002018 1 48 pha 00201A 1
002019 1 20 DB 20 jsr InvertChar 00201A 1 DumpOutput:
00201C 1 68 pla 00201A 1 20 BD 20 jsr GetByte
00201D 1 C9 48 cmp #'H' 00201D 1 B0 2A bcs checkInput
00201F 1 F0 3A beq setColumn 00201F 1 C9 00 cmp #$00
002021 1 C9 56 cmp #'V' 002021 1 F0 39 beq endOutput
002023 1 F0 44 beq setRow 002023 1 48 pha
002025 1 C9 43 cmp #'C' 002024 1 20 1C 21 jsr ClearCursor
002027 1 F0 29 beq clearScreen 002027 1 68 pla
002029 1 C9 54 cmp #'T' 002028 1 C9 48 cmp #'H'
00202B 1 F0 4A beq setTop 00202A 1 F0 3A beq setColumn
00202D 1 C9 42 cmp #'B' 00202C 1 C9 56 cmp #'V'
00202F 1 F0 4E beq setBottom 00202E 1 F0 44 beq setRow
002031 1 C9 55 cmp #'U' 002030 1 C9 43 cmp #'C'
002033 1 F0 52 beq moveUp 002032 1 F0 29 beq clearScreen
002035 1 20 ED FD jsr PrintChar 002034 1 C9 54 cmp #'T'
002038 1 20 DB 20 jsr InvertChar 002036 1 F0 4A beq setTop
00203B 1 4C 0F 20 jmp DumpOutput 002038 1 C9 42 cmp #'B'
00203E 1 checkInput: 00203A 1 F0 4E beq setBottom
00203E 1 2C 00 C0 bit Keyboard ;check for keypress 00203C 1 C9 55 cmp #'U'
002041 1 10 CC bpl DumpOutput ;keep dumping output if no keypress 00203E 1 F0 52 beq moveUp
002043 1 AD 00 C0 lda Keyboard ;send keypress to RPi 002040 1 20 ED FD jsr PrintChar
002046 1 29 7F and #$7f 002043 1 20 EB 20 jsr SetCursor
002048 1 20 94 20 jsr SendByte 002046 1 4C 1A 20 jmp DumpOutput
00204B 1 2C 10 C0 bit ClearKeyboard 002049 1 checkInput:
00204E 1 4C 0F 20 jmp DumpOutput 002049 1 2C 00 C0 bit Keyboard ;check for keypress
002051 1 endOutput: 00204C 1 10 CC bpl DumpOutput ;keep dumping output if no keypress
002051 1 60 rts 00204E 1 AD 00 C0 lda Keyboard ;send keypress to RPi
002052 1 clearScreen: 002051 1 29 7F and #$7f
002052 1 20 58 FC jsr Home 002053 1 20 9F 20 jsr SendByte
002055 1 20 DB 20 jsr InvertChar 002056 1 2C 10 C0 bit ClearKeyboard
002058 1 4C 0F 20 jmp DumpOutput 002059 1 4C 1A 20 jmp DumpOutput
00205B 1 setColumn: 00205C 1 endOutput:
00205B 1 20 B2 20 jsr GetByte 00205C 1 60 rts
00205E 1 85 24 sta htab 00205D 1 clearScreen:
002060 1 8D 7B 05 sta htab80 00205D 1 20 58 FC jsr Home
002063 1 20 DB 20 jsr InvertChar 002060 1 20 EB 20 jsr SetCursor
002066 1 4C 0F 20 jmp DumpOutput 002063 1 4C 1A 20 jmp DumpOutput
002069 1 setRow: 002066 1 setColumn:
002069 1 20 B2 20 jsr GetByte 002066 1 20 BD 20 jsr GetByte
00206C 1 85 25 sta vtab 002069 1 85 24 sta htab
00206E 1 20 C1 FB jsr BasCalc 00206B 1 8D 7B 05 sta htab80
002071 1 20 DB 20 jsr InvertChar 00206E 1 20 EB 20 jsr SetCursor
002074 1 4C 0F 20 jmp DumpOutput 002071 1 4C 1A 20 jmp DumpOutput
002077 1 setTop: 002074 1 setRow:
002077 1 20 B2 20 jsr GetByte 002074 1 20 BD 20 jsr GetByte
00207A 1 85 22 sta $22 002077 1 85 25 sta vtab
00207C 1 4C 0F 20 jmp DumpOutput 002079 1 20 C1 FB jsr BasCalc
00207F 1 setBottom: 00207C 1 20 EB 20 jsr SetCursor
00207F 1 20 B2 20 jsr GetByte 00207F 1 4C 1A 20 jmp DumpOutput
002082 1 85 23 sta $23 002082 1 setTop:
002084 1 4C 0F 20 jmp DumpOutput 002082 1 20 BD 20 jsr GetByte
002087 1 moveUp: 002085 1 85 22 sta $22
002087 1 C6 25 dec vtab 002087 1 4C 1A 20 jmp DumpOutput
002089 1 A5 25 lda vtab 00208A 1 setBottom:
00208B 1 20 C1 FB jsr BasCalc 00208A 1 20 BD 20 jsr GetByte
00208E 1 20 DB 20 jsr InvertChar 00208D 1 85 23 sta $23
002091 1 4C 0F 20 jmp DumpOutput 00208F 1 4C 1A 20 jmp DumpOutput
002094 1 002092 1 moveUp:
002094 1 SendByte: 002092 1 C6 25 dec vtab
002094 1 48 pha 002094 1 A5 25 lda vtab
002095 1 waitWrite: 002096 1 20 C1 FB jsr BasCalc
002095 1 AD FB C0 lda InputFlags 002099 1 20 EB 20 jsr SetCursor
002098 1 2A rol 00209C 1 4C 1A 20 jmp DumpOutput
002099 1 2A rol 00209F 1
00209A 1 B0 F9 bcs waitWrite 00209F 1 SendByte:
00209C 1 68 pla 00209F 1 48 pha
00209D 1 8D FD C0 sta OutputByte 0020A0 1 waitWrite:
0020A0 1 A9 1E lda #$1e ; set bit 0 low to indicate write started 0020A0 1 AD FB C0 lda InputFlags
0020A2 1 8D F7 C0 sta OutputFlags 0020A3 1 2A rol
0020A5 1 finishWrite: 0020A4 1 2A rol
0020A5 1 AD FB C0 lda InputFlags 0020A5 1 B0 F9 bcs waitWrite
0020A8 1 2A rol 0020A7 1 68 pla
0020A9 1 2A rol 0020A8 1 8D FD C0 sta OutputByte
0020AA 1 90 F9 bcc finishWrite 0020AB 1 A9 1E lda #$1e ; set bit 0 low to indicate write started
0020AC 1 A9 1F lda #$1f 0020AD 1 8D F7 C0 sta OutputFlags
0020AE 1 8D F7 C0 sta OutputFlags 0020B0 1 finishWrite:
0020B1 1 60 rts 0020B0 1 AD FB C0 lda InputFlags
0020B2 1 0020B3 1 2A rol
0020B2 1 GetByte: 0020B4 1 2A rol
0020B2 1 A9 1D lda #$1d ;set read flag low 0020B5 1 90 F9 bcc finishWrite
0020B4 1 8D F7 C0 sta OutputFlags 0020B7 1 A9 1F lda #$1f
0020B7 1 waitRead: 0020B9 1 8D F7 C0 sta OutputFlags
0020B7 1 AD FB C0 lda InputFlags 0020BC 1 60 rts
0020BA 1 2A rol 0020BD 1
0020BB 1 90 0C bcc readByte 0020BD 1 GetByte:
0020BD 1 2C 00 C0 bit Keyboard ;keypress will abort waiting to read 0020BD 1 2C 00 C0 bit Keyboard ; skip byte read if key pressed
0020C0 1 10 F5 bpl waitRead 0020C0 1 90 10 bcc keyPressed
0020C2 1 A9 1F lda #$1f ;set all flags high and exit 0020C2 1 A9 1D lda #$1d ;set read flag low
0020C4 1 8D F7 C0 sta OutputFlags 0020C4 1 8D F7 C0 sta OutputFlags
0020C7 1 38 sec ;failure 0020C7 1 waitRead:
0020C8 1 60 rts 0020C7 1 AD FB C0 lda InputFlags
0020C9 1 readByte: 0020CA 1 2A rol
0020C9 1 AD FE C0 lda InputByte 0020CB 1 90 0C bcc readByte
0020CC 1 48 pha 0020CD 1 2C 00 C0 bit Keyboard ;keypress will abort waiting to read
0020CD 1 A9 1F lda #$1f ;set all flags high 0020D0 1 10 F5 bpl waitRead
0020CF 1 8D F7 C0 sta OutputFlags 0020D2 1 keyPressed:
0020D2 1 finishRead: 0020D2 1 A9 1F lda #$1f ;set all flags high and exit
0020D2 1 AD FB C0 lda InputFlags 0020D4 1 8D F7 C0 sta OutputFlags
0020D5 1 2A rol 0020D7 1 38 sec ;failure
0020D6 1 90 FA bcc finishRead 0020D8 1 60 rts
0020D8 1 68 pla 0020D9 1 readByte:
0020D9 1 18 clc ;success 0020D9 1 AD FE C0 lda InputByte
0020DA 1 end: 0020DC 1 48 pha
0020DA 1 60 rts 0020DD 1 A9 1F lda #$1f ;set all flags high
0020DB 1 0020DF 1 8D F7 C0 sta OutputFlags
0020DB 1 InvertChar: 0020E2 1 finishRead:
0020DB 1 AD 7B 05 lda htab80 ;get horizontal location / 2 0020E2 1 AD FB C0 lda InputFlags
0020DE 1 4A lsr 0020E5 1 2A rol
0020DF 1 A8 tay 0020E6 1 90 FA bcc finishRead
0020E0 1 AD 55 C0 lda TextPage2 0020E8 1 68 pla
0020E3 1 90 03 bcc invert 0020E9 1 18 clc ;success
0020E5 1 AD 54 C0 lda TextPage1 0020EA 1 end:
0020E8 1 invert: 0020EA 1 60 rts
0020E8 1 B1 28 lda (BasL),y 0020EB 1
0020EA 1 49 80 eor #$80 0020EB 1 SetCursor:
0020EC 1 91 28 sta (BasL),y 0020EB 1 AD 7B 05 lda htab80 ;get horizontal location / 2
0020EE 1 AD 54 C0 lda TextPage1 0020EE 1 4A lsr
0020F1 1 screen40: 0020EF 1 A8 tay
0020F1 1 60 rts 0020F0 1 AD 55 C0 lda TextPage2
0020F2 1 0020F3 1 90 03 bcc setChar
0020F2 1 HelpCommand: 0020F5 1 AD 54 C0 lda TextPage1
0020F2 1 61 32 68 65 .byte "a2help",$00 0020F8 1 setChar:
0020F6 1 6C 70 00 0020F8 1 B1 28 lda (BasL),y
0020F9 1 PromptCommand: 0020FA 1 85 06 sta LastChar ; save so ClearCursor will pick it up
0020F9 1 61 32 70 72 .byte "a2prompt",$00 0020FC 1 C9 E0 cmp #$e0
0020FD 1 6F 6D 70 74 0020FE 1 10 0C bpl lowerCase
002101 1 00 002100 1 C9 C0 cmp #$c0
002102 1 OldPromptChar: 002102 1 10 0D bpl upperCase
002102 1 5D .byte "]" 002104 1 C9 A0 cmp #$a0
002103 1 DrawCursor: 002106 1 10 04 bpl symbol
002103 1 80 .byte $80 002108 1 C9 80 cmp #$80
002103 1 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

View File

@ -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. 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 ## 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 ## Features
1. Boot message which waits for RPi to be ready 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` 7. `cd ProDOS-Utilities`
8. `cd ~/Apple2-IO-RPi/RaspberryPi/apple2driver` 8. `cd ~/Apple2-IO-RPi/RaspberryPi/apple2driver`
9. `go build` 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 ## 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. 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.

Binary file not shown.

View File

@ -8,13 +8,10 @@ package a2io
import ( import (
"fmt" "fmt"
"time"
) )
var escapeSequence string var escapeSequence string
var operatingSystemSequence bool var operatingSystemSequence bool
var htab, vtab, savedHtab, savedVtab int
var applicationMode bool
var windowTop int var windowTop int
var windowBottom = 23 var windowBottom = 23
@ -24,28 +21,34 @@ func sendCharacter(comm A2Io, b byte) {
return return
} }
if b == 13 { if b == 13 {
fmt.Printf("CR\n") // fmt.Printf("\nCR\n")
comm.WriteByte('H') comm.WriteByte('H')
comm.WriteByte(0) comm.WriteByte(0)
return return
} }
if b > 13 && b < 32 { if b > 0x0d && b < 0x20 || b > 0x80 && b < 0xa0 {
fmt.Printf("Control code: %02X\n", b) // fmt.Printf("Control code: %02X\n", b)
return
}
if b >= 0xa0 {
comm.WriteByte('+' | 0x80)
// fmt.Printf("Code: %02X\n", b)
return return
} }
if len(escapeSequence) == 0 { if len(escapeSequence) == 0 {
fmt.Printf("%c", b) fmt.Printf("%c", b)
} }
if len(escapeSequence) > 0 { if len(escapeSequence) > 0 {
// Parse the escape codes that don't end with a letter
escapeSequence += string(b) escapeSequence += string(b)
if escapeSequence == "^[]" { if escapeSequence == "^[]" {
fmt.Printf("Start operating system sequence\n") // fmt.Printf("Start operating system sequence\n")
operatingSystemSequence = true operatingSystemSequence = true
return return
} }
if operatingSystemSequence { if operatingSystemSequence {
if b == 0x07 { if b == 0x07 {
fmt.Printf("Operating system sequence: %s\n", escapeSequence) // fmt.Printf("Operating system sequence: %s\n", escapeSequence)
operatingSystemSequence = false operatingSystemSequence = false
escapeSequence = "" escapeSequence = ""
} }
@ -53,187 +56,167 @@ func sendCharacter(comm A2Io, b byte) {
} }
// save cursor // save cursor
if escapeSequence == "^[7" { if escapeSequence == "^[7" {
savedHtab = htab
savedVtab = vtab
fmt.Printf("Save Cursor (%d, %d): %s\n", htab, vtab, escapeSequence)
escapeSequence = "" escapeSequence = ""
} }
// restore cursor // restore cursor
if escapeSequence == "^[8" { if escapeSequence == "^[8" {
htab = savedHtab escapeSequence = ""
vtab = savedVtab }
comm.WriteByte('H') if escapeSequence == "^[>" {
comm.WriteByte(byte(htab)) // this sequence is undocumented and shows up exiting "top" command
comm.WriteByte('V')
comm.WriteByte(byte(vtab))
fmt.Printf("Restore Cursor (%d, %d): %s\n", htab, vtab, escapeSequence)
escapeSequence = "" escapeSequence = ""
} }
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') { 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 { switch b {
// Set cursor location // Set cursor location
case 'H', 'f': case 'H', 'f':
if escapeSequence == "^[[H" || escapeSequence == "^[[;H" { if escapeSequence == "^[[H" || escapeSequence == "^[[;H" {
htab = 0
vtab = 0
comm.WriteByte(0x19) // ^Y moves to home position comm.WriteByte(0x19) // ^Y moves to home position
fmt.Printf("Home: %s\n", escapeSequence) // fmt.Printf("Home: %s\n", escapeSequence)
escapeSequence = "" escapeSequence = ""
} else { } else {
var ignore string var ignore string
var htab, vtab int
fmt.Sscanf(escapeSequence, "^[[%d;%d%s", &vtab, &htab, &ignore) fmt.Sscanf(escapeSequence, "^[[%d;%d%s", &vtab, &htab, &ignore)
htab-- htab--
vtab-- vtab--
if htab < 0 { if htab < 0 { // this occastionally gets called with 0 that becomes -1
htab = 0 htab = 0
} }
if vtab > 23 { if vtab > 23 { // top command sets vtab 25 on exit even in 24 line mode
vtab = 23 vtab = 23
} }
comm.WriteByte('H') comm.WriteByte('H')
comm.WriteByte(byte(htab)) comm.WriteByte(byte(htab))
comm.WriteByte('V') comm.WriteByte('V')
comm.WriteByte(byte(vtab)) 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 = "" escapeSequence = ""
} }
case 'r': case 'r':
fmt.Sscanf(escapeSequence, "^[[%d;%dr", &windowTop, &windowBottom) fmt.Sscanf(escapeSequence, "^[[%d;%dr", &windowTop, &windowBottom)
windowTop-- windowTop--
//windowBottom--
comm.WriteByte('T') comm.WriteByte('T')
comm.WriteByte(byte(windowTop)) comm.WriteByte(byte(windowTop))
comm.WriteByte('B') comm.WriteByte('B')
comm.WriteByte(byte(windowBottom)) 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 = "" escapeSequence = ""
case 'A': case 'A':
if escapeSequence == "^[[A" || escapeSequence == "^[A" { if escapeSequence == "^[[A" || escapeSequence == "^[A" {
vtab--
comm.WriteByte('U') comm.WriteByte('U')
fmt.Printf("Up: %s\n", escapeSequence) // fmt.Printf("Up: %s\n", escapeSequence)
} else { } else {
var up int var up int
fmt.Sscanf(escapeSequence, "^[[%dA", &up) fmt.Sscanf(escapeSequence, "^[[%dA", &up)
vtab -= up
for i := 0; i < up; i++ { for i := 0; i < up; i++ {
comm.WriteByte('U') comm.WriteByte('U')
} }
fmt.Printf("Up (%d): %s\n", up, escapeSequence) // fmt.Printf("Up (%d): %s\n", up, escapeSequence)
} }
escapeSequence = "" escapeSequence = ""
case 'B': case 'B':
if escapeSequence == "^[(B" || escapeSequence == "^[)B" || escapeSequence == "^[B" { if escapeSequence == "^[(B" || escapeSequence == "^[)B" || escapeSequence == "^[B" {
escapeSequence = "" escapeSequence = ""
} else if escapeSequence == "^[[B" { } else if escapeSequence == "^[[B" {
vtab++
comm.WriteByte(0x0a) comm.WriteByte(0x0a)
fmt.Printf("Down: %s\n", escapeSequence) // fmt.Printf("Down: %s\n", escapeSequence)
} else { } else {
var down int var down int
fmt.Sscanf(escapeSequence, "^[[%dB", &down) fmt.Sscanf(escapeSequence, "^[[%dB", &down)
vtab += down
for i := 0; i < down; i++ { for i := 0; i < down; i++ {
comm.WriteByte(0x0a) comm.WriteByte(0x0a)
} }
fmt.Printf("Down (%d): %s\n", down, escapeSequence) // fmt.Printf("Down (%d): %s\n", down, escapeSequence)
} }
escapeSequence = "" escapeSequence = ""
case 'C': case 'C':
if escapeSequence == "^[[C" || escapeSequence == "^[C" { if escapeSequence == "^[[C" || escapeSequence == "^[C" {
htab++
comm.WriteByte(0x1c) comm.WriteByte(0x1c)
fmt.Printf("Right: %s\n", escapeSequence) // fmt.Printf("Right: %s\n", escapeSequence)
} else { } else {
var right int var right int
fmt.Sscanf(escapeSequence, "^[[%dC", &right) fmt.Sscanf(escapeSequence, "^[[%dC", &right)
htab += right
for i := 0; i < right; i++ { for i := 0; i < right; i++ {
comm.WriteByte(0x1c) comm.WriteByte(0x1c)
} }
fmt.Printf("Right (%d): %s\n", right, escapeSequence) // fmt.Printf("Right (%d): %s\n", right, escapeSequence)
} }
escapeSequence = "" escapeSequence = ""
case 'D': case 'D':
if escapeSequence == "^[[D" || escapeSequence == "^[D" { if escapeSequence == "^[[D" || escapeSequence == "^[D" {
htab--
comm.WriteByte(0x08) comm.WriteByte(0x08)
fmt.Printf("Left: %s\n", escapeSequence) // fmt.Printf("Left: %s\n", escapeSequence)
} else { } else {
var left int var left int
fmt.Sscanf(escapeSequence, "^[[%dD", &left) fmt.Sscanf(escapeSequence, "^[[%dD", &left)
htab -= left
for i := 0; i < left; i++ { for i := 0; i < left; i++ {
comm.WriteByte(0x08) comm.WriteByte(0x08)
} }
fmt.Printf("Left (%d): %s\n", left, escapeSequence) // fmt.Printf("Left (%d): %s\n", left, escapeSequence)
} }
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 { if len(escapeSequence) > 0 {
fmt.Printf("Unhandled escape sequence: %s\n", escapeSequence) fmt.Printf("Unhandled escape sequence: %s\n", escapeSequence)
} }
@ -243,43 +226,20 @@ func sendCharacter(comm A2Io, b byte) {
return return
} }
//fmt.Print(string(b)) //fmt.Print(string(b))
htabIncrement := 0
switch b { switch b {
// convert LF to CR for Apple II compatiblity // convert LF to CR for Apple II compatiblity
case 10: case 10:
b = 13 b = 13
vtab++
htab = 0
htabIncrement = 0
case 13: case 13:
htab = 0
htabIncrement = 0
return return
// convert TAB to spaces // convert TAB to spaces
case 9: case 9:
b = ' ' b = ' '
b |= 0x80 b |= 0x80
err := comm.WriteByte(b) 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)
}
} }
b |= 0x80
comm.WriteByte(b)
} }
func readCharacter(comm A2Io) (string, error) { func readCharacter(comm A2Io) (string, error) {
@ -301,13 +261,3 @@ func readCharacter(comm A2Io) (string, error) {
} }
return s, err return s, err
} }
func updateTabs(comm A2Io) {
if htab >= 80 {
htab = 0
vtab++
}
if vtab > windowBottom {
vtab = windowBottom
}
}

View File

@ -46,8 +46,12 @@ func ExecCommand() {
a2help() a2help()
return return
} }
if linuxCommand == "a2lower" {
a2lower(false)
return
}
if linuxCommand == "A2LOWER" { if linuxCommand == "A2LOWER" {
a2lower() a2lower(true)
return return
} }
if linuxCommand == "a2wifi" { if linuxCommand == "a2wifi" {
@ -158,15 +162,16 @@ func getStdin(stdin io.WriteCloser, done chan bool, inputComplete chan bool, use
inputComplete <- true inputComplete <- true
return return
default: default:
s, err := comm.ReadCharacter() b, err := comm.ReadByte()
if err == nil { if err == nil {
if s == string(byte(0x00)) { if b == 0x00 || b == 0x03 {
stdin.Close() stdin.Close()
userCancelled <- true userCancelled <- true
fmt.Printf("\nUser cancelled stdin\n") fmt.Printf("\nUser cancelled stdin\n")
return 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() { func a2help() {
comm.WriteString("\r" + 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" + "Built-in commands:\r" +
"------------------\r" +
"a2help - display this message\r" + "a2help - display this message\r" +
"a2wifi - set up wifi\r" + "a2wifi - set up wifi\r" +
"A2LOWER - force lowercase for II+\r" + "A2LOWER - force lowercase for II+\r" +
"a2lower - disable force lowercase for II+\r" +
"\r") "\r")
} }
func a2lower() { func a2lower(enable bool) {
forceLowercase = true forceLowercase = enable
comm.WriteString("All commands will be converted to lowercase\r") comm.WriteString(fmt.Sprintf("All commands will be converted to lowercase: %t\r", forceLowercase))
} }
func a2wifi() { func a2wifi() {

View File

@ -8,6 +8,7 @@ package handlers
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
@ -36,8 +37,8 @@ func ShellCommand() {
inputComplete := make(chan bool) inputComplete := make(chan bool)
userCancelled := make(chan bool) userCancelled := make(chan bool)
go getStdin(ptmx, outputComplete, inputComplete, userCancelled) go ptyIn(ptmx, outputComplete, inputComplete, userCancelled)
go getStdout(ptmx, outputComplete, userCancelled) go ptyOut(ptmx, outputComplete, userCancelled)
for { for {
select { 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))
}
}
}
}