/*
    SPY.C

Written by DavidW
Started on 12/11/91

Derived from earlier work by Poh Siew, GeoffO, Joe, CharlesD, JohnH, & PeterM.
*/

#include <p_std.h>
#include <p_file.h>
#include <epoc.h>
#include <wlib.h>
#include <hwif.h>

GLREF_C VOID wsReport(HANDLE);
GLREF_C VOID MonitorBandWidth(VOID);
GLREF_C VOID DisableReads(UINT flag);

GLREF_D UBYTE DatHeapLocked;

LOCAL_D TEXT *cmds[]=
    {
    "dChange data",
    "hUse decimal",
    "pChange processes",
    "uUpdate",
    "bBand width",
    "tTimer off",
    "rRefresh rate",
    "nLog client",
    "mLog all clients",
    "cCompress heaps",
    "wRewalk failed process",
    "gGoto process",
    "lReset least stack",
    "kKill process",
    "xExit",
    NULL
    };

#define DISPLAY_ITEMS   5
#define TIMER_ITEMS     2
#define LOG_ITEMS       2
#define SPECIAL_ITEMS   6

#define ITEMS_BEFORE_DISPLAY    0
#define ITEMS_BEFORE_TIMER      DISPLAY_ITEMS
#define ITEMS_BEFORE_LOG        (ITEMS_BEFORE_TIMER+TIMER_ITEMS)
#define ITEMS_BEFORE_SPECIAL    (ITEMS_BEFORE_LOG+LOG_ITEMS)

#define USE_ITEM    1
#define TIMER_ITEM  ITEMS_BEFORE_TIMER

LOCAL_D H_MENU_DATA mdata[]=
    {
    "Display",DISPLAY_ITEMS,
    "Timer",TIMER_ITEMS,
    "Log",LOG_ITEMS,
    "Special",SPECIAL_ITEMS,
    NULL
    };

GLDEF_D TEXT ** _cmds=(&cmds[0]);
GLDEF_D H_MENU_DATA * _mdata=(&mdata[0]);


LOCAL_D UINT MainWid;

#define TEXT_HEIGHT         10
#define TEXT_ASCENT         8

#define LAY_TOP_BORDER      4
#define Y_TO_FIRST_PROCESS  15

#define SCREEN_WIDTH        240
#define LAY_LEFT_BORDER     4
#define LAY_RIGHT_BORDER    4

#define NAME_COLUMN_INDENT  4
#define NAME_COLUMN_WIDTH   74
#define X_TO_FIRST_COLUMN   (LAY_LEFT_BORDER+NAME_COLUMN_INDENT+NAME_COLUMN_WIDTH)
#define FIRST_COLUMN_WIDTH  32
#define SECOND_COLUMN_WIDTH 50
#define THIRD_COLUMN_WIDTH  72
#define TRAILING_INDENT     4
#define X_TO_END            (SCREEN_WIDTH-LAY_RIGHT_BORDER)

LOCAL_D UWORD cwid[3]={FIRST_COLUMN_WIDTH,SECOND_COLUMN_WIDTH,THIRD_COLUMN_WIDTH};

typedef struct
    {
    TEXT name[E_MAX_NAME+2];
    UWORD bytes_allocated;
    UWORD bytes_free;
    UWORD seg_size;
    UWORD last_free;
    UWORD least_stack;
    UWORD priority;
    UWORD stack_pointer;
    UWORD pid;
    UWORD cells_allocated;
    UWORD cells_free;
    UWORD iosemcnt;
    UWORD status;
    } PROCESS_DATA;

#define PROCESS_SYSTEM          1
#define PROCESS_APPLICATION     2
#define PROCESS_ALL             3

LOCAL_D UWORD which_processes=PROCESS_ALL;

LOCAL_D UWORD using_decimal=FALSE;
LOCAL_D UWORD lastbflags=0;

#define DISPLAY_PROCESS_NAME    0
#define DISPLAY_BYTES_ALLOCED   1
#define DISPLAY_BYTES_FREE      2
#define DISPLAY_SEG_SIZE        3
#define DISPLAY_LAST_FREE       4
#define DISPLAY_LEAST_STACK     5
#define DISPLAY_PRIORITY        6
#define DISPLAY_STACK_POINTER   7
#define DISPLAY_PID             8
#define DISPLAY_CELLS_ALLOCED   9
#define DISPLAY_CELLS_FREE      10
#define DISPLAY_IOSEMCNT        11
#define DISPLAY_STATUS          12

LOCAL_D TEXT *titles[13]=
    {
    "Process name",
    "Alloc bytes",
    "Bytes free",
    "Seg size",
    "Last free",
    "Lst stack",
    "Priority",
    "Stack ptr",
    "Proc ID",
    "Alloc cells",
    "Cells free",
    "IO count",
    "Status"
    };

LOCAL_D TEXT *statuses[]=
    {
    "Curr",
    "Busy",
    "TimQ",
    "Susp",
    "Wait"
    };

LOCAL_D UWORD col[4]=
    {
    DISPLAY_PROCESS_NAME,
    DISPLAY_BYTES_ALLOCED,
    DISPLAY_CELLS_ALLOCED,
    DISPLAY_LEAST_STACK
    };

LOCAL_D PROCESS_DATA proc[E_MAX_PROCESSES-1];
LOCAL_D UWORD numproc;
LOCAL_D HANDLE failpid=NULL;

typedef struct mblock
    {
    UWORD mlen;
    struct mblock *mnext;
    } MBLOCK;

#define SZMBLK sizeof(MBLOCK)
#define SZHD sizeof(UWORD)

#define ERR_FREECELL_BEYOND_MEMHIGH     1
#define ERR_FREECELL_GOES_BACKWARDS     2
#define ERR_CELL_LENGTH_TOO_SMALL       3
#define ERR_CELL_LENGTH_ODD             4
#define ERR_ALLOCED_CELLS_OVERRUN_FREE  5
#define ERR_TOTALS_DONT_MATCH           6

LOCAL_D TEXT *heap_errors[]=
    {
    "Free cell beyond memhigh",
    "Free cell goes backwards",
    "Cell length too small",
    "Cell length odd",
    "Alloced cells don't end at next free cell",
    "Total free + total alloced != segment size"
    };

#define STACK_READ_SIZE     256

LOCAL_D UWORD selproc=0;
LOCAL_D UWORD topproc=0;

LOCAL_D LONG timint=50;
LOCAL_D VOID *timcb;
LOCAL_D WORD timstat;
LOCAL_D UWORD using_timer=TRUE;


LOCAL_C VOID QueueTimer(VOID)
    {
    if (using_timer)
        p_ioa4(timcb,P_FRELATIVE,&timstat,&timint);
    }

LOCAL_C VOID CancelTimer(VOID)
    {
    if (using_timer)
        {
        p_iow2(timcb,P_FCANCEL);
        p_waitstat(&timstat);
        }
    }

LOCAL_C INT GetWidth(TEXT *pb)
    {
    return(gTextWidth(WS_FONT_BASE,G_STY_NORMAL,pb,p_slen(pb)));
    }

LOCAL_C VOID DrawTitles(VOID)
    {
    INT i;
    INT totwid;
    INT width[4];
    INT gap;
    TEXT *buf;
    P_RECT box;

    totwid=0;
    for (i=0; i<=3; i++)
        totwid+=(width[i]=GetWidth(titles[col[i]]));
    gap=(X_TO_END-LAY_LEFT_BORDER-totwid)/3;
    box.tl.x=LAY_LEFT_BORDER;
    box.tl.y=LAY_TOP_BORDER;
    box.br.y=LAY_TOP_BORDER+TEXT_HEIGHT;
    for (i=0; i<=3; i++)
        {
        if (i==3)
            box.br.x=X_TO_END;
        else
            box.br.x=box.tl.x+width[i]+gap;
        buf=(titles[col[i]]);
        gPrintBoxText(&box,TEXT_ASCENT,G_TEXT_ALIGN_LEFT,0,buf,p_slen(buf));
        box.tl.x=box.br.x;
        }
    }

LOCAL_C VOID InvertLine(INT j)
    {
    P_EXTENT ext;

    ext.tl.x=LAY_LEFT_BORDER;
    ext.tl.y=Y_TO_FIRST_PROCESS+(j-topproc)*TEXT_HEIGHT;
    ext.width=X_TO_END-LAY_LEFT_BORDER;
    ext.height=TEXT_HEIGHT;
    gInvObloid(&ext);
    }

LOCAL_C VOID DrawLine(INT j)
    {
    P_RECT box;
    INT len;
    UWORD *base;
    TEXT *fmt;
    TEXT *buf;
    INT margin;
    TEXT out[20];
    INT i;
    INT val;

    box.tl.x=LAY_LEFT_BORDER;
    box.tl.y=Y_TO_FIRST_PROCESS+j*TEXT_HEIGHT;
    box.br.y=box.tl.y+TEXT_HEIGHT;
    j+=topproc;
    if (j>=numproc)
        {
        box.br.x=X_TO_END;
        gClrRect(&box,G_TRMODE_CLR);
        return;
        }
    box.br.x=X_TO_FIRST_COLUMN;
    buf=(&proc[j].name[0]);
    len=p_slen(buf);
    gPrintBoxText(&box,TEXT_ASCENT,G_TEXT_ALIGN_LEFT,
                                                NAME_COLUMN_INDENT,buf,len);
    base=(&proc[j].bytes_allocated)-1;
    for (i=0; i<3; )
        {
        box.tl.x=box.br.x;
        box.br.x=box.tl.x+cwid[i];
        val=(*(base+col[++i]));
        if (col[i]==DISPLAY_STATUS)
            buf=statuses[val];
        else
            {
            if (col[i]==DISPLAY_IOSEMCNT)
                fmt="%d";
            else if (using_decimal || col[i]>=DISPLAY_CELLS_ALLOCED)
                fmt="%u";
            else
                fmt="%x";
            p_atos(buf=(&out[0]),fmt,val);
            }
        margin=(i==3? TRAILING_INDENT: 0);
        gPrintBoxText(&box,TEXT_ASCENT,G_TEXT_ALIGN_RIGHT,
                                                    margin,buf,p_slen(buf));
        }
    if (j==selproc)
        InvertLine(j);
    }

LOCAL_C VOID DrawList(VOID)
    {
    INT j;

    for (j=0; j<6; j++)
        DrawLine(j);
    }

LOCAL_C VOID SetPriority(INT prio)
    {
    p_setpri(p_getpid(),prio);
    }

LOCAL_C VOID PriorityHigh(VOID)
    {
    SetPriority(E_MAX_PRIORITY);
    }

LOCAL_C VOID PriorityLow(VOID)
    {
    SetPriority(E_PRIORITY_BACK+1);
    }

LOCAL_C INT SystemProcessName(TEXT *pname)
    {
    return(!p_bcmp(pname,4,"SYS$",4));
    }

LOCAL_C VOID GetMblock(HANDLE pid,MBLOCK *ofs,MBLOCK *buf)
    {
    p_pcpyfr(pid,ofs,buf,SZMBLK);
    }

LOCAL_C INT WalkHeap(HANDLE pid,UWORD *poffset,PROCESS_DATA *pdata,
                                                        MBLOCK *MemBasePtr)
    {
    MBLOCK *p1,*p2,*memhigh;
    MBLOCK m1,m2;
    
    memhigh=(MBLOCK *)pdata->seg_size;
    p2=MemBasePtr;
    p1=p2+1; /* first allocated cell starts at an mblock after memBasePtr */ 
    FOREVER
        { /* walk every cell from memlow to memhigh */
        GetMblock(pid,p2,&m2);
        *poffset=(UWORD)p2;
        p2=m2.mnext; /* next free cell */
        if (!p2)
            p2=memhigh; /* last free cell */
        else
            {
            GetMblock(pid,p2,&m2);
            if (p2>(memhigh-1))
                return(ERR_FREECELL_BEYOND_MEMHIGH);
            else if (m2.mnext && m2.mnext<p2+1)
                {
                *poffset=(UWORD)p2;
                return(ERR_FREECELL_GOES_BACKWARDS);
                }
            }
        while (p1!=p2)
            { /* walk cells until next free cell */
            GetMblock(pid,p1,&m1);
            *poffset=(UWORD)p1;
            if (m1.mlen<SZMBLK)
               return(ERR_CELL_LENGTH_TOO_SMALL);
            if (m1.mlen&1)
               return(ERR_CELL_LENGTH_ODD);
            pdata->cells_allocated++;
            pdata->bytes_allocated+=m1.mlen;
            if (m1.mlen>((UBYTE *)p2-(UBYTE *)p1))
               return(ERR_ALLOCED_CELLS_OVERRUN_FREE);
            p1=(MBLOCK *)(((UBYTE *)p1)+m1.mlen);
            }
        if (p2==memhigh)
            {
            *poffset=0;
            break;
            }
        p1=(MBLOCK *)(((UBYTE *)p2)+m2.mlen); /* next allocated cell */
        pdata->cells_free++;
        pdata->bytes_free+=m2.mlen;
        pdata->last_free=m2.mlen;
        }
    if (pdata->seg_size!=(UINT)(MemBasePtr)+SZMBLK
                                +pdata->bytes_allocated+pdata->bytes_free)
        return(ERR_TOTALS_DONT_MATCH);
    return(0); /* all OK */
    }

LOCAL_C VOID NotifyError(INT ret,UWORD offset,TEXT *pname)
    {
    TEXT buf[40];

    p_atos(&buf[0],"%s: heap corrupt at %u",pname,offset);
    p_notify(&buf[0],heap_errors[ret-1],0,0,0);
    }

LOCAL_C VOID CollectProcessData(HANDLE pid,TEXT *pname)
    {
    PROCESS_DATA *pdata;
    E_PROC epc;
    UBYTE sbuf[STACK_READ_SIZE+2];
    UBYTE *p;
    UBYTE *pEnd;
    UWORD segoffset;
    WORD locked;
    UWORD offset;
    INT ret;
    UWORD sem[3];

    pdata=(&proc[numproc++]);
    p_bfil(pdata,sizeof(PROCESS_DATA),0);
    p_scpy(&pdata->name[0],pname);
    pdata->pid=pid;
    p_getosd(&epc,(VOID *)(E_PIDMASK&pid),sizeof(E_PROC));
    pdata->seg_size=p_sgsize(epc.dataSeg)<<4;
    pdata->stack_pointer=(UWORD)epc.saveSP;
    pdata->status=epc.status-E_PROC_CURRENT;
    pdata->priority=p_getpri(pid);
    p_getosd(&sem[0],(VOID *)epc.semaphore,6);
    pdata->iosemcnt=sem[2];
    segoffset=0x80;
    pEnd=(&sbuf[STACK_READ_SIZE]);
    *pEnd=0;
    do
        {
        p_pcpyfr(pid,(VOID *)segoffset,&sbuf[0],STACK_READ_SIZE);
        p=(&sbuf[0]);
        while (*p==255)
            p++;       
        segoffset+=STACK_READ_SIZE;
        } while (p==pEnd);
    pdata->least_stack=segoffset-STACK_READ_SIZE+p-(&sbuf[0]);
    if (epc.memBasePtr && !epc.isTask && pid!=failpid)
        {
        locked=0;
        p_pcpyfr(pid,&DatHeapLocked,&locked,1);
        if (!locked)
            {
            ret=WalkHeap(pid,&offset,pdata,(MBLOCK *)epc.memBasePtr);
            if (ret)
                {
                NotifyError(ret,offset,pname);
                failpid=pid;
                }
            }
        }
    }

LOCAL_C VOID BuildList(VOID)
    {
    HANDLE prchand;
    TEXT name[E_MAX_NAME+2];
    HANDLE pid;
    INT first;

    numproc=0;
    first=TRUE;
    prchand=NULL;
    PriorityHigh();
    while ((prchand=p_pfind(prchand,"*",&name[0]))!=E_FILE_NXIST)
        {
        if (first)
            {
            first=FALSE;
            continue;
            }
        if (which_processes!=PROCESS_ALL)
            {
            if (SystemProcessName(&name[0]))
                {
                if (which_processes==PROCESS_APPLICATION)
                    continue;
                }
            else if (which_processes==PROCESS_SYSTEM)
                continue;
            }
        if ((pid=p_pidfind(&name[0]))<0)
            continue;
        CollectProcessData(pid,&name[0]);
        }
    PriorityLow();
    if (topproc>numproc-6)
        topproc=numproc-6;
    if (selproc>numproc-1)
        selproc=numproc-1;
    }

LOCAL_C VOID UpdateList(VOID)
    {
    BuildList();
    DrawList();
    }

LOCAL_C VOID ResetList(VOID)
    {
    CancelTimer();
    UpdateList();
    QueueTimer();
    }

LOCAL_C VOID ChangeColumnsViewed(VOID)
    {
    UWORD copy[3];
    INT j;
    TEXT prompt[10];
    TEXT *pb;

    p_bcpy(&copy[0],&col[1],6);
    pb=(&prompt[7]);        /* points to '1' of "Column 1" */
    FOREVER
        {
        p_scpy(&prompt[0],"Column 1");
        if (uOpenDialog("Change display for each column"))
            return;
        for (j=0; j<3; j++)
            {
            if (uAddChoiceList(&prompt[0],&copy[j],"Bytes allocated",
                    "Bytes free","Segment size","Last free cell",
                    "Least stack","Process priority","Stack pointer",
                    "Process ID","Cells allocated","Cells free",
                    "IO semaphore count","Process status",NULL))
                return;
            (*pb)++;
            }
        if (uRunDialog()<=0)
            return;
        if (copy[0]!=copy[1] && copy[0]!=copy[2] && copy[1]!=copy[2])
            break;
        wInfoMsg("Duplicate columns");
        }
    p_bcpy(&col[1],&copy[0],6);
    DrawTitles();
    ResetList();
    }

LOCAL_C VOID ChangeRefreshRate(VOID)
    {
    H_DI_NUMBER num;

    if (uOpenDialog("Refresh rate"))
        return;
    num.value=(&timint);
    num.low=5;
    num.high=1000;
    if (uAddDialogItem(H_DIALOG_NUMBER,"Tenths of seconds",&num))
        return;
    if (uRunDialog()<=0)
        return;
    CancelTimer();
    QueueTimer();
    }

LOCAL_C VOID ChangeProcessesViewed(VOID)
    {
    if (uOpenDialog(NULL))
        return;
    if (uAddChoiceList("View which processes",&which_processes,
                                        "System","Application","All",NULL))
        return;
    if (uRunDialog()<=0)
        return;
    ResetList();
    }

LOCAL_C VOID ToggleBaseUsed(VOID)
    {
    if (using_decimal^=TRUE)
        {
        wInfoMsg("Display now using decimal");
        cmds[USE_ITEM]="hUse hex";
        }
    else
        {
        wInfoMsg("Display now using hexadecimal");
        cmds[USE_ITEM]="hUse decimal";
        }
    ResetList();
    }

LOCAL_C VOID ToggleTimer(VOID)
    {
    CancelTimer();
    if (using_timer^=TRUE)
        {
        wInfoMsg("Timer re-enabled");
        cmds[TIMER_ITEM]="tTimer off";
        }
    else
        {
        wInfoMsg("Timer suspended");
        cmds[TIMER_ITEM]="tTimer on";
        }
    QueueTimer();
    }

LOCAL_C VOID CompressAllHeaps(VOID)
    {
    p_sgcreate("t$t$",0x7FFF,E_SEGMENT_HIGH);
    wInfoMsg("All heaps compressed");
    ResetList();
    }

LOCAL_C VOID WalkAllHeapsAgain(VOID)
    {
    if (!failpid)
        wInfoMsg("No failed process");
    else
        {
        failpid=NULL;
        wInfoMsg("Now rewalking ALL processes");
        ResetList();
        }
    }

LOCAL_C TEXT *CurrentTaskName(VOID)
    {
    return(&proc[selproc].name[0]);
    }

LOCAL_C VOID NamedInfoMsg(TEXT *fmt)
    {
    TEXT buf[40];

    p_atos(&buf[0],fmt,CurrentTaskName());
    wInfoMsg(&buf[0]);
    }

LOCAL_C VOID GotoCurrentProcess(VOID)
    {
    HANDLE pid;

    pid=proc[selproc].pid;
    if ((INT)wClientInfo(pid)>=0)
        wClientPosition(0,pid);
    else
        NamedInfoMsg("Cannot go to %s");
    }

LOCAL_C INT TryResetLeastStack(VOID)
    {
    HANDLE pid;
    E_PROC epc;
    UBYTE *offset;
    TEXT buf[STACK_READ_SIZE];

    PriorityHigh();
    if (proc[selproc].priority>=E_MAX_PRIORITY)
        {
        PriorityLow();
        NamedInfoMsg("Cannot reset stack of %s");
        return;
        }
    p_bfil(&buf[0],STACK_READ_SIZE,0xFF);
    pid=proc[selproc].pid;
    p_getosd(&epc,(VOID *)(E_PIDMASK&pid),sizeof(E_PROC));
    offset=(UBYTE *)0x80;
    while (offset+STACK_READ_SIZE<epc.saveSP)
        {
        p_pcpyto(pid,offset,&buf[0],STACK_READ_SIZE);
        offset+=STACK_READ_SIZE;
        }
    p_pcpyto(pid,offset,&buf[0],(UINT)(epc.saveSP-offset));
    PriorityLow();
    ResetList();
    }

LOCAL_C INT AddTextItem(TEXT *msg,INT type)
    {
    TEXT buf[40];
    H_DI_TEXT txt;

    txt.type=type;
    uZTStoBCS(txt.str=(&buf[0]),msg);
    return(uAddDialogItem(H_DIALOG_TEXT,NULL,&txt));
    }

LOCAL_C VOID TryKillCurrentProcess(VOID)
    {
    INT ret;
    TEXT buf[40];

    p_atos(&buf[0],"Kill %s",CurrentTaskName());
    if (uOpenDialog(&buf[0]))
        return;
    if (AddTextItem("Are you sure?",H_DTEXT_ALIGN_CENTRE|H_DTEXT_BOLD))
        return;
    if (uAddButtonList("No",-'n',"Yes",'y',NULL))
        return;
    if (uRunDialog()!='y')
        return;
    ret=p_pterminate(proc[selproc].pid,0);
    if (!ret)
        ResetList();
    else
        NamedInfoMsg("Cannot kill %s");
    }

LOCAL_C VOID ManageCommand(INT index)
    {
    switch (index)
        {
    case ITEMS_BEFORE_DISPLAY:
        ChangeColumnsViewed();
        break;
    case ITEMS_BEFORE_DISPLAY+1:
        ToggleBaseUsed();
        break;
    case ITEMS_BEFORE_DISPLAY+2:
        ChangeProcessesViewed();
        break;
    case ITEMS_BEFORE_DISPLAY+3:
        ResetList();
        break;
    case ITEMS_BEFORE_DISPLAY+4:
        MonitorBandWidth();
        break;
    case ITEMS_BEFORE_TIMER:
        ToggleTimer();
        break;
    case ITEMS_BEFORE_TIMER+1:
        ChangeRefreshRate();
        break;
    case ITEMS_BEFORE_LOG:
        wsReport(proc[selproc].pid);
        break;
    case ITEMS_BEFORE_LOG+1:
        wsReport(0);     /* Log all Window server clients */
        break;
    case ITEMS_BEFORE_SPECIAL:
        CompressAllHeaps();
        break;
    case ITEMS_BEFORE_SPECIAL+1:
        WalkAllHeapsAgain();
        break;
    case ITEMS_BEFORE_SPECIAL+2:
        GotoCurrentProcess();
        break;
    case ITEMS_BEFORE_SPECIAL+3:
        TryResetLeastStack();
        break;
    case ITEMS_BEFORE_SPECIAL+4:
        TryKillCurrentProcess();
        break;
    case ITEMS_BEFORE_SPECIAL+5:
        p_exit(0);
        }
    }

LOCAL_C VOID TryExecuteCommand(INT keycode)
    {
    keycode=uLocateCommand(keycode);
    if (keycode>=0)
        ManageCommand(keycode);
    }

LOCAL_C VOID DoScroll(INT dely)
    {
    P_POINT offset;
    P_RECT src;

    offset.x=0;
    offset.y=dely*TEXT_HEIGHT;
    src.tl.x=LAY_LEFT_BORDER;
    src.br.x=X_TO_END;
    if (dely>0)
        {
        src.tl.y=Y_TO_FIRST_PROCESS;
        src.br.y=Y_TO_FIRST_PROCESS+(6-dely)*TEXT_HEIGHT;
        }
    else
        {
        src.tl.y=Y_TO_FIRST_PROCESS-dely*TEXT_HEIGHT;
        src.br.y=Y_TO_FIRST_PROCESS+6*TEXT_HEIGHT;
        }
    wScrollRect(MainWid,&src,&offset);
    }

LOCAL_C VOID MakeHighlight(INT newselproc)
    {
    INT toscroll;

    if (newselproc==selproc)
        return;
    InvertLine(selproc);
    toscroll=topproc-newselproc;
    if (toscroll>0)
        {
        topproc-=toscroll;      
        if (toscroll<6)
            DoScroll(toscroll);
        else
            toscroll=6;
        while (toscroll)
            DrawLine(--toscroll);
        }
    else
        {
        toscroll=-5-toscroll;
        if (toscroll>0)
            {
            topproc+=toscroll;      
            if (toscroll<6)
                DoScroll(-toscroll);
            else
                toscroll=6;
            while (toscroll)
                DrawLine(6-toscroll--);
            }
        }
    selproc=newselproc;
    InvertLine(selproc);
    }

LOCAL_C VOID ScrollPageBy(INT delta)
    {
    if (delta<(-5))
        delta=(-5);
    else if (delta>5)
        delta=5;
    topproc+=delta;
    selproc+=delta;
    DrawList();
    }

LOCAL_C VOID TryMatchFirstLetter(INT upper)
    {
    INT k;
    INT i;

    k=numproc;
    i=selproc;
    while (k--)
        {
        if (++i==numproc)
            i=0;
        if (proc[i].name[0]==upper)
            {
            MakeHighlight(i);
            return;
            }
        }
    p_sound(-5,320);
    }

LOCAL_C VOID CheckBorderArrows(VOID)
    {
    INT bflags;

    if (topproc)
        bflags=W_BORD_CORNER_4|W_BORD_TOP_ON;
    else
        bflags=W_BORD_CORNER_4|W_BORD_TOP_OFF;
    if (topproc+6<numproc)
        bflags|=W_BORD_BOT_ON;
    else
        bflags|=W_BORD_BOT_OFF;
    if (bflags!=lastbflags)
        gBorder(lastbflags=bflags);
    }

LOCAL_C VOID SystemPauseLoop(VOID)
    {
    WS_EV event;
    UINT key,oldPri,pid;
    UWORD procBuf[WS_MAX_CLIENTS+1];

    pid=p_getpid();
    wGetProcessList(&procBuf[0]);
    if (procBuf[0]==pid)
        return;
    oldPri=p_getpri(pid);
    p_setpri(pid,E_PRIORITY_WSERV-1);
    DisableReads(TRUE);
    wGetEvent(&event);
    FOREVER
        {
        if (event.type!=E_FILE_PENDING)
            {
            p_iowait();
            if (event.type==WM_FOREGROUND || event.type==WM_BACKGROUND)
                break;
            if (event.type==WM_KEY)
                {
                key=p_tolower(event.p.key.keycode&(~W_SPECIAL_KEY));
                if (key=='s' || key==W_KEY_ESCAPE)
                    break;
                }
            wGetEvent(&event);
            }
        }
    DisableReads(FALSE);
    p_setpri(pid,oldPri);
    }

LOCAL_C VOID MainLoop(VOID)
    {
    INT ret;
    WMSG_KEY key;
    WORD keystat;
    WORD keyactive;

    keyactive=FALSE;
    FOREVER
        {
        CheckBorderArrows();
        if (keyactive)
            wFlush();
        else
            {
            uGetKeyA(&keystat,&key);
            keyactive=TRUE;
            }
        p_iowait();
        if (keystat==E_FILE_PENDING)
            {
            UpdateList();
            QueueTimer();
            continue;
            }
        keyactive=FALSE;
        if (key.keycode&W_EVENT_KEY)
            {
            switch (key.keycode)
                {
            case CONS_EVENT_COMMAND:
                p_exit(0);
            case CONS_EVENT_FOREGROUND:
                ResetList();
                }
            }
        else if ((key.modifiers&W_CTRL_MODIFIER|W_PSION_MODIFIER)==
                  (W_CTRL_MODIFIER|W_PSION_MODIFIER) &&
                  key.keycode==(W_SPECIAL_KEY|'n'))
            wsReport(-1);   /* Report foreground client (no dialogs) */
        else if ((key.modifiers&W_CTRL_MODIFIER|W_PSION_MODIFIER)==
                  (W_CTRL_MODIFIER|W_PSION_MODIFIER) &&
                  key.keycode==(W_SPECIAL_KEY|'p'))
            wClientPosition(0,0);
        else if ((key.modifiers&W_CTRL_MODIFIER|W_PSION_MODIFIER)==
                  (W_CTRL_MODIFIER|W_PSION_MODIFIER) &&
                  key.keycode==(W_SPECIAL_KEY|'s'))
            SystemPauseLoop();
        else if (key.keycode&W_SPECIAL_KEY)
            TryExecuteCommand(key.keycode&(~W_SPECIAL_KEY));
        else 
            {
            switch (key.keycode)
                {
            case W_KEY_MENU:
                if (!(key.modifiers&W_CTRL_MODIFIER))
                    {
                    ret=uPresentMenus();
                    if (ret>0)
                        TryExecuteCommand(ret);
                    }
                break;
            case W_KEY_UP:
                if (selproc)
                    MakeHighlight(selproc-1);
                break;
            case W_KEY_DOWN:
                if (selproc<numproc-1)
                    MakeHighlight(selproc+1);
                break;
            case W_KEY_PAGE_UP:
                if (key.modifiers&W_CTRL_MODIFIER)
                    MakeHighlight(0);
                else if (!topproc)
                    MakeHighlight(0);
                else
                    ScrollPageBy(-topproc);
                break;
            case W_KEY_PAGE_DOWN:
                if (key.modifiers&W_CTRL_MODIFIER)
                    MakeHighlight(numproc-1);
                else if (topproc+6>=numproc)
                    MakeHighlight(numproc-1);
                else
                    ScrollPageBy(numproc-topproc-6);
                break;
            default:
                if (p_isprint(key.keycode))
                    TryMatchFirstLetter(p_toupper(key.keycode));
                }
            }
        }
    }

LOCAL_C VOID CreateGC(VOID)
    {
    INT hgc;

    hgc=gCreateGC0(MainWid);
    if (hgc<0)
        p_exit(hgc);
    }

LOCAL_C VOID SpecificInit(VOID)
    {
    MainWid=uFindMainWid();
    CreateGC();
    hCrackCommandLine();
    p_unmarka();
    DrawTitles();
    UpdateList();
    p_open(&timcb,"TIM:",-1);
    QueueTimer();
    if (wCaptureKey('n'|W_SPECIAL_KEY,W_CTRL_MODIFIER|W_PSION_MODIFIER,
                                      W_CTRL_MODIFIER|W_PSION_MODIFIER)<0)
        wInfoMsg("'n' key capture failed");
    if (wCaptureKey('p'|W_SPECIAL_KEY,W_CTRL_MODIFIER|W_PSION_MODIFIER,
                                      W_CTRL_MODIFIER|W_PSION_MODIFIER)<0)
        wInfoMsg("'p' key capture failed");
    if (wCaptureKey('s'|W_SPECIAL_KEY,W_CTRL_MODIFIER|W_PSION_MODIFIER,
                                      W_CTRL_MODIFIER|W_PSION_MODIFIER)<0)
        wInfoMsg("'s' key capture failed");
    }

GLDEF_C VOID main(VOID)
    {
    uCommonInit();
    SpecificInit();
    MainLoop();
    }
