Fix cursor and improve shell (#48)
* Fix cursor and improve shell * Update README
This commit is contained in:
parent
a929790624
commit
de69c54007
|
@ -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
|
||||
|
|
BIN
Apple2/Shell.bin
BIN
Apple2/Shell.bin
Binary file not shown.
316
Apple2/Shell.lst
316
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
|
||||
|
|
|
@ -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.
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue