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
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

Binary file not shown.

View File

@ -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

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.
## 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.

Binary file not shown.

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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))
}
}
}
}