VECTORS SEGMENT AT 0H ;Set up segment to intercept Interrupts ORG 9H*4 ;The keyboard Interrupt KEYBOARD_INT LABEL DWORD ORG 1CH*4 ;Timer Interrupt TIMER_VECTOR LABEL DWORD VECTORS ENDS SCREEN SEGMENT AT 0B000H ;A dummy segment to use as the SCREEN ENDS ;Extra Segment ROM_BIOS_DATA SEGMENT AT 40H ;BIOS statuses held here, also keyboard buffer ORG 1AH HEAD DW ? ;Unread chars go from Head to Tail TAIL DW ? BUFFER DW 16 DUP (?) ;The buffer itself BUFFER_END LABEL WORD ROM_BIOS_DATA ENDS CODE_SEG SEGMENT ASSUME CS:CODE_SEG ORG 100H ;ORG = 100H to make this into a .COM file FIRST: JMP LOAD_KEEPER ;First time through COPY_RIGHT DB '(C)1985 S.HOLZNER' ;Ascii autograph PAD DB 20*102 DUP(0) ;Memory storage for pad PAD_CURSOR DW 9*102 ;Current position in pad ATTRIBUTE DB 112 ;Pad Attribute -- reverse video LINE_ATTRIBUTE DB 240 ;Flashing Rev video OLD_ATTRIBUTE DB 7 ;Original screen attrib: normal PAD_OFFSET DW 0 ;Chooses 1st 250 bytes or 2nd FIRST_POSITION DW ? ;Position of 1st char on screen TRIGGER_FLAG DW 0 ;Trigger on or off FULL_FLAG DB 0 ;Buffer Full Flag LINE DW 9 ;Line number, 0-9 SCREEN_SEG_OFFSET DW 0 ;0 for mono, 8000H for graphics IO_CHAR DW ? ;Holds addr of Put or Get_Char STATUS_PORT DW ? ;Video controller status port OLD_KEYBOARD_INT DD ? ;Location of old kbd interrupt FINISHED_FLAG DB 1 ;If not finished,f buffer COMMAND_INDEX DW 1 ;Stores positior timer) ROM_TIMER DD 1 ;The Timer interrupt's address OLD_HEAD DW 0 KEEPER PROC NEAR ;The keyboard interrupt will now come here. ASSUME CS:CODE_SEG PUSH AX ;Save the used registers for good form PUSH BX PUSH CX PUSH DX PUSH DI PUSH SI PUSH DS PUSH ES PUSHF ;First, call old keyboard interrupt CALL OLD_KEYBOARD_INT ASSUME DS:ROM_BIOS_DATA ;Examine the char just put in MOV BX,ROM_BIOS_DATA MOV DS,BX MOV BX,TAIL ;Point to current tail CMP BX,HEAD ;If at head, kbd int has deleted char JE BYE ;So leave MOV DX,HEAD SUB DX,2 ;Point to just read in character CMP DX,OFFSET BUFFER ;Did we undershoot buffer? JAE NOWRAP ;Nope MOV DX,OFFSET BUFFER_END ;Yes -- move to buffer top SUB DX,2 ;Compare two bytes back from head NOWRAP: CMP DX,TAIL ;If it's the tail, buffer is full JNE NOTFULL ;We're OK, jump to NotFull CMP FULL_FLAG,1 ;Check if keyboard buffer full JE BYE ;Yep, leave MOV FULL_FLAG,1 ;Oops, full, set flag and take JMP CHK ; this last character NOTFULL:MOV FULL_FLAG,0 ;Always reset Full_Flag when buff clears CHK: CMP TRIGGER_FLAG,0 ;Is the window on (triggered?) JNE SUBT ;Yep, keep going MOV DX,OLD_HEAD ;Check position of buffer head CMP DX,HEAD JNE CONT MOV OLD_HEAD,0 BYE: JMP OUT CONT: MOV DX,HEAD MOV OLD_HEAD,DX SUBT: SUB BX,2 ;Point to just read in character CMP BX,OFFSET BUFFER ;Did we undershoot buffer? JAE NO_WRAP ;Nope MOV BX,OFFSET BUFFER_END ;Yes -- move to buffer top SUB BX,2 ; NO_WRAP:MOV DX,[BX] ;Char in DX now ;------ CHAR IN DX NOW ------- CMP FINISHED_FLAG,0 JE IN CMP DX,310EH ;Default trigger is a ^N here. JNE NOT_TRIGGER ;No MOV TAIL,BX NOT TRIGGER_FLAG ;Switch Modes CMP TRIGGER_FLAG,0 ;Trigger off? JNE TRIGGER_ON ;No, only other choice is on TRIGGER_OFF: MOV OLD_HEAD,0 ;Reset old head MOV AH,OLD_ATTRIBUTE ;Get ready to restore screen MOV ATTRIBUTE,AH ;Pad and blinking line set to orig. MOV LINE_ATTRIBUTE,AH ; values MOV PAD_OFFSET,10*102 ;Point to 2nd half of pad LEA AX,PUT_CHAR ;Make IO call Put_Char as it scans MOV IO_CHAR,AX ;over all locations in pad on screen CALL IO ;Restore screen CMP LINE,9 ;Was the window turned off without JE IN ; using up-down keys? If so, exit MOV AX,LINE ;No, there is a line to stuff in MOV CL,102 ; keyboard buffer MUL CL ;Find its location in Pad MOV COMMAND_INDEX,AX ;And send to Put CALL PUT ;Which will do actual stuffing IN: JMP OUT ;Done TRIGGER_ON: ;Window just turned on MOV LINE,9 ;Set blinking line to bottom MOV PAD_OFFSET,10*102 ;Point to screen storage part of pad LEA AX,GET_CHAR ;Make IO use Get_char so current screen MOV IO_CHAR,AX ;is stored CALL IO ;Store Screen CALL DISPLAY ;And put up the pad JMP OUT ;Done here. NOT_TRIGGER: TEST TRIGGER_FLAG,1 ;Is Trigger on? JZ RUBOUT_TEST MOV TAIL,BX ;Yes, delete this char from buffer UP: CMP DX,4800H ;An Up cursor key? JNE DOWN ;No, try Down DEC LINE ;Move blinker up one line CMP LINE,0 ;At top? If so, reset JGE NOT_TOP MOV LINE,9 NOT_TOP:CALL DISPLAY ;Display result JMP OUT ;And leave DOWN: CMP DX,5000H ;Perhaps Down cusor key pushed JNE IN ;If not, ignore key INC LINE ;If so, move down one CMP LINE,9 ;If at bottom, wrap to top JLE NOT_BOT MOV LINE,0 NOT_BOT:CALL DISPLAY ;Show results JMP OUT ;And exit RUBOUT_TEST: CMP DX,0E08H ;Is it a Rubout? JNE CHAR_TEST ;No -- try carriage return-line feed MOV BX,PAD_CURSOR ;Yes -- get current pad location CMP BX,9*102 ;Are we at beginning of last line? JLE NEVER_MIND ;Yes -- can't rubout past beginning SUB PAD_CURSOR,2 ;No, rubout this char MOV PAD[BX-2],20H ;Move a space in instead (3920H) MOV PAD[BX-1],39H NEVER_MIND: JMP OUT ;Done here. CHAR_TEST: CMP DL,13 ;Is this a carriage return? JE PLUG ;If yes, plug this line into Pad CMP DL,32 ;If this char < Ascii 32, delete line JGE PLUG MOV PAD_CURSOR,9*102 ;Clear the current line MOV CX,51 MOV BX,9*102 CLEAR: MOV WORD PTR PAD[BX],0 ADD BX,2 LOOP CLEAR JMP OUT ;And exit PLUG: MOV BX,PAD_CURSOR ;Get current pad location CMP BX,10*102-2 ;Are we past the end of the pad? JGE CRLF_TEST ;Yes -- throw away char MOV WORD PTR PAD[BX],DX ;No -- move ASCII code into pad ADD PAD_CURSOR,2 ;Increment pad location CRLF_TEST: CMP DX,1C0DH ;Is it a carriage return-line feed? JNE OUT ;No -- put it in the pad CALL CRLF ;Yes -- move everything up in pad OUT: POP ES ;Having done Pushes, here are the Pops POP DS POP SI POP DI POP DX POP CX POP BX POP AX IRET ;An interrupt needs an IRET KEEPER ENDP DISPLAY PROC NEAR ;Puts the whole pad on the screen PUSH AX MOV ATTRIBUTE,112 ;Use reverse video MOV LINE_ATTRIBUTE,240 MOV PAD_OFFSET,0 ;Use 1st 250 bytes of pad memory LEA AX,PUT_CHAR ;Make IO use Put-Char so it does MOV IO_CHAR,AX CALL IO ;Put result on screen POP AX RET ;Leave DISPLAY ENDP CRLF PROC NEAR ;This handles carriage returns PUSH BX ;Push everything conceivable PUSH CX PUSH DI PUSH SI PUSH DS PUSH ES ASSUME DS:CODE_SEG ;Set DS to Code_Seg here PUSH CS POP DS ASSUME ES:CODE_SEG ;And ES too PUSH DS POP ES LEA DI,PAD ;Get ready to move contents of Pad MOV SI,DI ; up one line ADD SI,102 ;DI-top line, SI-one below top line MOV CX,9*51 MOV BX,PAD_CURSOR ;But first finish line with a 0 CMP BX,9*102+2 ; as a flag letting Put know line is JE POPS ; done. MOV WORD PTR PAD[BX],0 REP MOVSW ;Move up Pad contents MOV CX,51 ;Now fill the last line with spaces MOV AX,3920H REP STOSW ;Using Stosw POPS: MOV PAD_CURSOR,9*102 ;And finally reset Cursor to beginning POP ES ; of the last line again. POP DS POP SI POP DI POP CX POP BX DONE: RET ;And out. CRLF ENDP GET_CHAR PROC NEAR ;Gets a char from screen and advances position ASSUME ES:SCREEN,DS:ROM_BIOS_DATA PUSH DX MOV SI,2 ;Loop twice, once for char, once for attribute MOV DX,STATUS_PORT ;Get ready to read video controller status G_WAIT_LOW: ;Start waiting for a new horizontal scan - IN AL,DX ;Make sure the video controller scan status TEST AL,1 ;is low JNZ G_WAIT_LOW G_WAIT_HIGH: ;After port has gone low, it must go high IN AL,DX ;before it is safe to read directly from TEST AL,1 ;the screen buffer in memory JZ G_WAIT_HIGH MOV AH,ES:[DI] ;Do the move from the screen, one byte at a time INC DI ;Move to next screen location DEC SI ;Decrement loop counter CMP SI,0 ;Are we done? JE LEAVE ;Yes MOV PAD[BX],AH ;No -- put char we got into the pad JMP G_WAIT_LOW ;Do it again LEAVE: MOV OLD_ATTRIBUTE,AH ADD BX,2 POP DX RET GET_CHAR ENDP PUT_CHAR PROC NEAR ;Puts one char on screen and advances position PUSH DX MOV AH,PAD[BX] ;Get the char to be put onto the screen CMP AH,32 JAE GO MOV AH,32 GO: MOV SI,2 ;Loop twice, once for char, once for attribute MOV DX,STATUS_PORT ;Get ready to read video controller status P_WAIT_LOW: ;Start waiting for a new horizontal scan - IN AL,DX ;Make sure the video controller scan status TEST AL,1 ;is low JNZ P_WAIT_LOW P_WAIT_HIGH: ;After port has gone low, it must go high IN AL,DX ;before it is safe to write directly to TEST AL,1 ;the screen buffer in memory JZ P_WAIT_HIGH MOV ES:[DI],AH ;Move to screen, one byte at a time MOV AH,ATTRIBUTE ;Load attribute byte for second pass INC DI ;Point to next screen postion DEC SI ;Decrement loop counter JNZ P_WAIT_LOW ;If not zero, do it one more time ADD BX,2 POP DX RET ;Exeunt PUT_CHAR ENDP IO PROC NEAR ;This scans over all screen positions of the pad ASSUME ES:SCREEN ;Use screen as extra segment MOV BX,SCREEN MOV ES,BX PUSH DS MOV BX,ROM_BIOS_DATA MOV DS,BX MOV BX,4AH MOV BX,DS:[BX] SUB BX,51 ADD BX,BX MOV FIRST_POSITION,BX POP DS MOV DI,SCREEN_SEG_OFFSET ;DI will be pointer to screen postion ADD DI,FIRST_POSITION ;Add width of screen minus pad width MOV BX,PAD_OFFSET ;BX will be pad location pointer MOV CX,10 ;There will be 10 lines LINE_LOOP: PUSH WORD PTR ATTRIBUTE PUSH CX ;Figure out whether this is blinking NEG CX ; line and if so, temporarily change ADD CX,10 ; display attribute CMP CX,LINE JNE NOLINE MOV CL,LINE_ATTRIBUTE MOV ATTRIBUTE,CL NOLINE: POP CX MOV DX,51 ;And 51 spaces across CHAR_LOOP: CALL IO_CHAR ;Call Put-Char or Get-Char DEC DX ;Decrement character loop counter JNZ CHAR_LOOP ;If not zero, scan over next character ADD DI,FIRST_POSITION ;Add width of screen minus pad width POP WORD PTR ATTRIBUTE LOOP LINE_LOOP ;And now go back to do next line RET ;Finished IO ENDP PUT PROC NEAR ;Here it is. ASSUME DS:ROM_BIOS_DATA ;Free DS PUSH DS ;Save all used registers PUSH SI PUSH DI PUSH DX PUSH CX PUSH BX PUSH AX MOV AX,ROM_BIOS_DATA ;Just to make sure MOV DS,AX ;Set DS correctly FIN: MOV FINISHED_FLAG,1 ;Assume we'll finish MOV BX,TAIL ;Prepare to move to buffer's tail MOV SI,COMMAND_INDEX ;Get our source index STUFF: MOV AX,WORD PTR PAD[SI] ADD SI,2 ;Point to the command's next character CMP AX,0 ;Is it a zero? (End of command) JE NO_NEW_CHARACTERS ;Yes, leave with Finished_Flag=1 MOV DX,BX ;Find position in buffer from BX ADD DX,2 ;Move to next position for this word CMP DX,OFFSET BUFFER_END ;Are we past the end? JL NO_WRAP2 ;No, don't wrap MOV DX,OFFSET BUFFER ;Wrap NO_WRAP2: CMP DX,HEAD ;Buffer full but not yet done? JE BUFFER_FULL ;Time to leave, set Finished_Flag=0. ADD COMMAND_INDEX,2 ;Move to next word in command MOV [BX],AX ;Put it into the buffer right here. ADD BX,2 ;Point to next space in buffer CMP BX,OFFSET BUFFER_END ;Wrap here? JL NO_WRAP3 ;No, readjust buffer tail MOV BX,OFFSET BUFFER ;Yes, wrap NO_WRAP3: MOV TAIL,BX ;Reset buffer tail JMP STUFF ;Back to stuff in another character. BUFFER_FULL: ;If buffer is full, let timer take over MOV FINISHED_FLAG,0 ; by setting Finished_Flag to 0. NO_NEW_CHARACTERS: POP AX ;Restore everything before departure. POP BX POP CX POP DX POP DI POP SI POP DS STI RET PUT ENDP ASSUME DS:CODE_SEG INTERCEPT_TIMER PROC NEAR ;This completes filling the buffer PUSHF ;Store used flags PUSH DS ;Save DS since we'll change it PUSH CS ;Put current value of CS into DS POP DS CALL ROM_TIMER ;Make obligatory call PUSHF CMP FINISHED_FLAG,1 ;Do we have to do anything? JE OUT1 ;No, leave CLI ;Yes, start by clearing interrupts PUSH DS ;Save these. PUSH SI PUSH DX PUSH BX PUSH AX ASSUME DS:ROM_BIOS_DATA ;Point to the keyboard buffer again. MOV AX,ROM_BIOS_DATA MOV DS,AX MOV BX,TAIL ;Prepare to put characters in at tail MOV FINISHED_FLAG,1 ;Assume we'll finish MOV SI,COMMAND_INDEX ;Find where we left ourselves STUFF2: MOV AX,WORD PTR PAD[SI] ;The same stuff loop as above. ADD SI,2 ;Point to next command character. CMP AX,0 ;Is it zero? (end of command) JNE OVER ;No, continue. JMP NO_NEW_CHARACTERS2 ;Yes, leave with Finished_Flag=1 OVER: MOV DX,BX ;Find position in buffer from BX ADD DX,2 ;Move to next position for this word CMP DX,OFFSET BUFFER_END ;Are we past the end? JL NO_WRAP4 ;No, don't wrap MOV DX,OFFSET BUFFER ;Do the Wrap rap. NO_WRAP4: CMP DX,HEAD ;Buffer full but not yet done? JE BUFFER_FULL2 ;Time to leave, come back later. ADD COMMAND_INDEX,2 ;Point to next word of command. MOV [BX],AX ;Put into buffer ADD BX,2 ;Point to next space in buffer CMP BX,OFFSET BUFFER_END ;Wrap here? JL NO_WRAP5 ;No, readjust buffer tail MOV BX,OFFSET BUFFER ;Yes, wrap NO_WRAP5: MOV TAIL,BX ;Reset buffer tail JMP STUFF2 ;Back to stuff in another character BUFFER_FULL2: MOV FINISHED_FLAG,0 ;Set flag to not-done-yet. NO_NEW_CHARACTERS2: POP AX ;Restore these. POP BX POP DX POP SI POP DS OUT1: POPF ;And Exit. POP DS IRET ;With customary IRET INTERCEPT_TIMER ENDP LOAD_KEEPER PROC NEAR ;This procedure intializes everything ASSUME DS:VECTORS ;The data segment will be the Interrupt area MOV AX,VECTORS MOV DS,AX MOV AX,KEYBOARD_INT ;Get the old interrupt service routine MOV OLD_KEYBOARD_INT,AX ;address and put it into our location MOV AX,KEYBOARD_INT[2] ;OLD_KEYBOARD_INT so we can call it. MOV OLD_KEYBOARD_INT[2],AX MOV KEYBOARD_INT,OFFSET KEEPER ;Now load the address of our notepad MOV KEYBOARD_INT[2],CS ;routine into the keyboard interrupt MOV AX,TIMER_VECTOR ;Now same for timer MOV ROM_TIMER,AX MOV AX,TIMER_VECTOR[2] MOV ROM_TIMER[2],AX MOV TIMER_VECTOR,OFFSET INTERCEPT_TIMER MOV TIMER_VECTOR[2],CS ;And intercept that too. ASSUME DS:ROM_BIOS_DATA MOV AX,ROM_BIOS_DATA MOV DS,AX MOV BX,OFFSET BUFFER ;Clear the keyboard buffer. MOV HEAD,BX MOV TAIL,BX MOV AH,15 ;Ask for service 15 of INT 10H INT 10H ;This tells us how display is set up MOV STATUS_PORT,03BAH ;Assume this is a monochrome display TEST AL,4 ;Is it? JNZ EXIT ;Yes - jump out MOV SCREEN_SEG_OFFSET,8000H ;No - set up for graphics display MOV STATUS_PORT,03DAH EXIT: MOV DX,OFFSET LOAD_KEEPER ;Set up everything but LOAD_PAD to INT 27H ;stay and attach itself to DOS LOAD_KEEPER ENDP CODE_SEG ENDS END FIRST ;END "FIRST" so 8088 will go to FIRST first.