;------------------------------------------------------------------------------
;COMSS.ASM : 2-April-1995: A COMx screen saver based on RI and DCD!
;Copyright (C)1995 Cornel Huth
;------------------------------------------------------------------------------
;Since my fax prg still has no real screen saver after all this time (I won't
;mention it) I've blown an evening writing this (from scratch, mind you). The
;difference between standard screen savers and this is that this one uses the
;comport to detect when to blank and when to unblank.  Upon a Ring-Indicator
;(RI) COMSS unblanks and remains so so long as Data-Carrier Detect (DCD) is
;valid.  As coded, it expects a VGA, but you can change this if you have an
;assembler and a little asm know-how.  The blanking of the screen is enough
;for most monitors to move into a standby mode (e.g. NEC 4FGe).  If you need
;specific support for your monitor (see the BLANK routines below), code it
;as is required.
;
;The program is operated like this:
;
;Install:
;
; C>COMSS n     where n is your modem com port, 1 through 4
;               for example, C>COMSS 2  if the fax-modem is on COM2
;
;After a good install, you still need to activate it:
;
; C>COMSS +     to activate the screen saver, or:
; C>COMSS -     to deactivate the screen saver
;
;When active, you can still unblank the screen at any time by turning on
;the ScrollLock.  It remains visible so long as ScrollLock is On.  It will,
;of course, all become visible as soon as someone calls and remain so while
;they are connected (and for 20 seconds after they hangup).  Memory use is
;about 300 bytes when resident.  It makes use of INT1C vector, and chains to
;the previous handler.  It remains in memory until your reboot.  It's really
;pretty simple stuff, and it could be done much more easily in the fax program
;(probably 5 lines of Pascal), but the thing is, it does what I want it to.
;
;For updates to this program, or for other programs and, especially, programmer
;toolkits for database, soundcard, multimedia, graphics, etc., for
;DOS, Windows, and OS/2 compilers (BASIC, C, C++, etc.), check out my sites
;listed below:
;
;(FTP) ftp.crl.com /users/ro/cornel --- (WWW) ftp://ftp.crl.com/users/ro/cornel
;BBS/fax: +1-210-684-8065 / Monday-Friday after 5pm / Weekends 24 hours [-0500]
;            - Bullet/DOS - Bullet/Win - Bullet/2 - Ruckus/DOS -
;
;e-mail: cornel@crl.com
;
;------------------------------------------------------------------------------
BPTR EQU <BYTE PTR>
WPTR EQU <WORD PTR>
DPTR EQU <DWORD PTR>

                .286  ;for pusha/popa

IS_VGA = 1      ;set only     (should work for EGA, too)
IS_MDA = 0      ;one of these (should work for Herc, too)
IS_CGA = 0      ;to true      (may work for MCGA)
;tested devices were DRAM-based VGA cards (Paradise VGAplus & ET4000w32p/D)

PROCESS_TIME    EQU 9  ;process only every 9 ticks, or twice per second
DELAY_TIME      EQU 40 ;process-times to wait after RI/DCD lost before blanking
                       ;40*9=360 is approximately 20 seconds

_text SEGMENT 'code'
        ORG 100h

start:  jmp     Install ;this gets overwritten with "CH"
        ;the above code is three bytes

isActive        BYTE 0 	;since a 3-byte jmp use BYTE here
OLD_1C          DWORD 0 ;previous INT1C vector
comPort         WORD 0  ;comPort's base I/O address +6 where
                        ; DCD(7) and RI(6) states is at base+6 (n)=bit pos
isBlanked       BYTE 0  ;=1 if display is blanked
isShownTemp     BYTE 0  ;=1 if screen is temporarily being shown (ScrollLock on)
processTime     BYTE PROCESS_TIME
delayTime       BYTE DELAY_TIME

SS_1C:
ADJUST_ID = $-100h      ;difference between INT1C entry and ID word

        pusha
        push    ds
        push    es

        push    cs
        pop     ds
        dec     ds:processTime
        jnz     done
        ;-----------

        mov     ds:processTime,PROCESS_TIME
        dec     ds:delayTime
        jnz     @F
        mov     ds:delayTime,DELAY_TIME ;if =DELAY_TIME then counted down

@@:	sub     ax,ax
        mov     es,ax                   ;es->0:0 throughout

        mov     cx,0001                 ;convenient 0 and 1 (PRESEVE CX!)
        cmp     ds:isActive,ch          ;have activated screen saver?
        je      ssAllOn                 ;no
        ;--------------

        mov     al,BPTR es:[417h]
        and     al,10h
        mov     ds:isShownTemp,al       ;if 10h then ScrollLock On

        ;ss is active, check if RI/DCD, and if so unblank if needed
        ;if not RI/DCD, then check if delayTime counted down (if so blank)
        ;however, if ScrollLock On, then do not blank (or unblank if blanked
        ;for as long as ScrollLock is On)

        mov     dx,ds:comPort
        in      al,dx                   ;read status
        test    al,11000000y            ;DCD or RI?
        jz      ss10                    ;no
        cmp     ds:isBlanked,ch         ;is it blanked?
        je      @F                      ;no
        call    ssUnblank               ;yes, so unblank
        mov     ds:isBlanked,ch         ;...say so
@@:     mov     ds:delayTime,DELAY_TIME ;reset to full delay time
        jmp     done                    ;to cover RI toggles, DCD lost, etc.
        ;-----------
ss10:
        ;if ScrollLock is On, check if isBlanked and if so unblank
        ;if ScrollLock is off, check if isBlanked and if not BLANK
        ;(check before delayTime so can get immediate display if requested)

        cmp     ds:isShownTemp,ch       ;forced display-on?
        je      ss11                    ;no, so blank it (if time)

        mov     ds:delayTime,DELAY_TIME ;give it full delay before next blank
        cmp     ds:isBlanked,ch         ;already blanked?
        je      done                    ;no
        ;-----------

        call    ssUnblank               ;yes, unblank it
        mov     ds:isBlanked,ch
        jmp     done                    ;and DONE
        ;-----------

        ;RI/DCD is not on, check if delayTime counted down yet...
ss11:
        cmp     ds:delayTime,DELAY_TIME ;if at max then counted down
        jne     done                    ;not at max, so nothing to do
        ;-----------

        cmp     ds:isBlanked,ch         ;already blanked?
        jne     done                    ;yes
        ;-----------

        call    ssBlank                 ;time to BLANK the screen
        mov     ds:isBlanked,cl         ;say so
        jmp     done
        ;-----------

        ;---------------------------------------------------------
        ;screen saver is supposed to be off (display should be on)
ssAllOn:
        cmp     ds:isBlanked,ch         ;is screen blanked?
        je      done                    ;no, nothing to do
        ;-----------

        call    ssUnblank               ;yes, unblank it
        mov     ds:isBlanked,ch         ;and say so
done:                                   ;back to INT8 handler
        pop     es
        pop     ds
        popa
        pushf
        call DPTR OLD_1C
        iret

        ;---------------
        ;unblank display
ssUnblank:
IF IS_VGA
        mov     dx,es:[463h]    ;3D4 if color, 3B4 if mono
        add     dl,6
        cli
        in      al,dx           ;input status #1 (clear flip-flop/3C0 to index)
        mov     dx,03C0h        ;AC
        or      al,20h          ;bit5, display on (may not work on VRAM cards)
        out     dx,al           ;                 (but that's a SWAG)
        sti
        retn
ELSEIF IS_MDA
        mov     dx,03B8h
        mov     al,00101001y    ;bit5=blink on,3=display on,0=adapter on
        out     dx,al
        retn
ELSEIF IS_CGA
        mov     dx,03D8h
        mov     al,00101001y    ;bit5=blink on,3=display on,0=80 columns
        out     dx,al
        retn
ELSE
        .ERR <Must specify IS_VGA= or IS_MDA= or IS_CGA= 1>
ENDIF

        ;---------------
        ;blank display
ssBlank:
IF IS_VGA
        mov     dx,es:[463h]    ;3D4 if color, 3B4 if mono
        add     dl,6
        cli
        in      al,dx           ;input status #1 (clear flip-flop/3C0 to index)
        mov     dx,03C0h        ;AC
        and     al,NOT 20h      ;bit5, display off (may not work on VRAM cards)
        out     dx,al
        sti
        retn
ELSEIF IS_MDA
        mov     dx,03B8h
        mov     al,00100001y    ;bit5=blink on,3=display off,0=adapter on
        out     dx,al
        retn
ELSEIF IS_CGA
        mov     dx,03D8h
        mov     al,00100001y    ;bit5=blink on,3=display off,0=80 columns
        out     dx,al
        retn
ENDIF

        ;-------------- end of resident code --------------

Install:
        cld
        mov     ah,30h
        int 21h
        cmp     al,3
        mov     dx,OFFSET Bomb0msg      ;need DOS 3
        jb      StdOut

        mov     ax,351Ch
        int 21h
        mov     si,bx
        sub     si,ADJUST_ID            ;(MASM forced me to...)
        cmp     WPTR es:[si],"HC"       ;already installed?
        je      InstallOnOff            ;yes, process command line

        mov     WPTR cs:OLD_1C,bx       ;save current INT1C vector
        mov     WPTR cs:OLD_1C+2,es     ;since mouse, lan, etc. may use it
        mov     bx,80h
        cmp     BPTR cs:[bx],bh
Ioo:
        mov     dx,OFFSET InstComMsg
        je      StdOut
@@:     inc     bx              ;skip whitespace
        mov     al,BPTR cs:[bx]
        cmp     al,0Dh          ;end-of-line?
        je      Ioo             ;yes
        cmp     al,20h          ;whitespace?
        jbe     @B              ;yes

        cmp     al,"?"
        je      IshowHelp

        ;read comport (only 1-4 are valid)

        cmp     al,"1"
        jb      @F
        cmp     al,"4"
        ja      @F
        jmp     iCont
@@:     test    ax,ax           ;set ZF=1
        jmp     Ioo
iCont:
        mov     BPTR cs:ComMsg,al
        sub     bx,bx
        mov     ds,bx           ;ds->0:0
        sub     al,"1"
        cbw
        add     ax,ax           ;ax=0,2,4,6
        add     ax,400h         ;ax=400h...406h
        mov     bx,ax
        mov     ax,WPTR [bx]    ;ax=base port
        add     ax,6            ;ax=status register port
        mov     cs:comPort,ax

        ;release environment

        mov     es,WPTR cs:[2Ch]        ;PSP:2C=environ segment, release it
        mov     ah,49h
        int 21h

        ;translate code down 176 bytes down (100h-50h=B0h)

        mov     ax,cs
        mov     ds,ax
        mov     es,ax
        mov     si,OFFSET start         ;ds:si->cs:100 (100h)
        mov     WPTR [si],"HC"          ;flag our existance for on/off'ing
        mov     di,50h                  ;es:di->PSP:50 ( 50h)
        mov     cx,OFFSET Install-100h  ;size in bytes
        rep movsb

        mov     ax,cs                   ;take over INT1C vector...
        sub     ax,(100h-50h)/16
        mov     ds,ax                   ;ds != cs
        mov     dx,OFFSET SS_1C         ;ds:dx->translated SS_1C
        mov     ax,251Ch
        int 21h                         ;SS_1C is now being called 18 times/sec
        mov     dx,OFFSET InstallMsg
StdOut:
        push    cs
        pop     ds
        mov     ah,9
        int 21h
        cmp     dx,OFFSET InstallMsg
        jne     BombOut
        mov     dx,OFFSET Install-0B0h  ;relative PSP:0, of xlated size
        int 27h      ;OFFSET Install is relative 0100h so subtract xlation

BombOut:
        mov     ax,4C01h
        int 21h

        ;----------------------------------------------------
        ;already installed so process command line for action
        ;es:si->xlated start: address

InstallOnOff:
        mov     bx,80h
        cmp     BPTR cs:[bx],bh
Ioo2:
        mov     dx,OFFSET InstDupMsg
        je      StdOut
@@:     inc     bx              ;skip whitespace
        mov     al,BPTR cs:[bx]
        cmp     al,0Dh          ;end-of-line?
        je      Ioo2            ;yes
        cmp     al,20h          ;whitespace?
        jbe     @B              ;yes

        cmp     al,"?"
        je      IshowHelp

        ;only options are "+" for activate, and "-" for deactivate

        mov     dx,OFFSET OffMsg
        mov     ah,0
        cmp     al,"-"
        je      @F
        mov     dx,OFFSET OnMsg
        mov     ah,1
        cmp     al,"+"
        je      @F
        test    ax,ax           	;set ZF=1
        jmp     Ioo2
@@:     mov     BPTR es:[si+3],ah       ;isActive
        jmp     StdOut

IshowHelp:
        push    cs
        pop     ds
        mov     dx,OFFSET HelpMsg
        mov     ah,9
        int 21h
        jmp     BombOut

HelpMsg BYTE 13,10
BYTE "COMSS 1.0 - a COMx screen saver, (C)1995 Cornel Huth - Freeware",13,13,10
BYTE "Use:",13,10
BYTE "C>COMSS n     first, must install, where n is comport 1-4, then...",13,10
BYTE "C>COMSS +     (plus sign) to activate screen saver",13,10
BYTE "C>COMSS -     (minus sign) to deactivate screen saver",13,13,10
BYTE "Scroll-Lock ON forces screen on, otherwise on only with DCD/RI",13,10,"$"

Bomb0msg    BYTE "COMSS: DOS 3+ needed",13,10,"$"
InstallMsg  BYTE "COMSS: installed for COM"
ComMsg      BYTE "x (off)",13,10,"$"
InstComMsg  BYTE "COMSS: unknown comport (use C>COMSS ?)",13,10,"$"
InstDupMsg  BYTE "COMSS: installed already/invalid command line",13,10,"$"
OnMsg       BYTE "COMSS: active",13,10,"$"
OffMsg      BYTE "COMSS: off",13,10,"$"

_text ENDS
      END start
