/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
 
/*
 *   Portions Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

// #include "MemCheck:MemCheck.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "swis.h"
#include "LanMan98SWIs.h"
#include "kernel.h"
#include "LanMan98PGLib/PGwin.h"
#include "LanMan98PGLib/PGicn.h"
#include "LanMan98PGLib/PGmnu.h"
#include "LanMan98PGLib/PGerr.h"
#include "lm98enum.h"

/* What this task calls itself, and so how another copy is recognised */
#define TASK_NAME "LanMan98 Filer"

#define REG_SEARCH (50*1024)
#define MAX_LINE   (256)

static int ci_strcmp(char *s1, char *s2)
{
    while(*s1 && toupper(*s1) == toupper(*s2))
    {
        s1++;
        s2++;
    }
    return toupper(*s1) - toupper(*s2);
}

/* RISC OS pathnames do not distinguish case, so neither can this */
static int ci_strncmp(const char *s1, const char *s2, size_t n)
{
    while(n > 0 && *s1 && toupper(*s1) == toupper(*s2))
    {
        s1++;
        s2++;
        n--;
    }
    return (n == 0) ? 0 : (toupper(*s1) - toupper(*s2));
}

static char *find_reg(char *base)
{
    int i, j;
    char key[] = "unencrypted  reg";

    i = j = 0;
    while(i < REG_SEARCH && key[j] != 0)
        if(key[j++] != base[i++])
            j = 0;
    if(key[j] != 0)
        return NULL;
    else
        return base + i;
}

static void set_info_icons(win_window win)
{
    icn_icon *icon;
    _kernel_swi_regs regs;
    _kernel_oserror *err;
    char *help, buf[256], *cptr;

    icon = IcnButtons(win);
    regs.r[0] = 18;
    regs.r[1] = (int) "LanMan98";
    err = _kernel_swi(OS_Module, &regs, &regs);
    if(err != NULL)
        ErrorFatal("LanMan98 module not loaded");
//    MemCheck_RegisterMiscBlock((void *) regs.r[3], REG_SEARCH);
//    MemCheck_RegisterMiscBlock((void *) regs.r[4], REG_SEARCH);
    help = (char *) (regs.r[3] + *((int *)(regs.r[3] + 20)));
    cptr = strstr(help, "\t");
    if(cptr == NULL)
        Error("LanMan98 module has corrupted help string");
    strcpy(buf, cptr + 1);
    cptr = strstr(buf, ")");
    if(cptr == NULL)
        Error("LanMan98 module has corrupted help string");
    cptr[1] = '\0';
    IcnSetText(icon[6], buf);
    cptr = find_reg((char *) regs.r[4]);
    if(cptr == NULL)
        Error("LanMan98 module has corrupted help string");
    IcnSetText(icon[7], cptr);
//    MemCheck_UnRegisterMiscBlock((void *) regs.r[3]);
//    MemCheck_UnRegisterMiscBlock((void *) regs.r[4]);
}

static win_window info_window(void)
{
    win_template info_tplt;
    win_window info;

    info_tplt = WinLdTplt("<LanMan98$Dir>.Templates", "info");
    if(info_tplt == NULL)
        ErrorFatal("Can't find \"info\" in \"<LanMan98$Dir>.Templates\"");
    info = WinCreate(info_tplt);
    if(info == NULL)
        ErrorFatal("Can't create \"Info\" window");
    set_info_icons(info);
    return info;
}

typedef struct mount_rec_s *mount_rec_t;

struct mount_rec_s
{
    icn_icon icn;
    int mount;
    int notypes;
    int working;
    mount_rec_t next;
};

static mount_rec_t rec_list = NULL;

static mount_rec_t rec_construct(void)
{
    mount_rec_t rec;

    rec = malloc(sizeof(*rec));
    rec->next = rec_list;
    rec_list = rec;
    return rec;
}

static void rec_destruct(mount_rec_t rec)
{
    mount_rec_t *recp;

    for(recp = &rec_list; *recp; recp = &(*recp)->next)
    {
        if(*recp == rec)
        {
            *recp = (*recp)->next;
            free(rec);
            return;
        }
    }
    ErrorFatal("Attempt to destruct non-existant mount record");
}

static mount_rec_t rec_find(char *name)
{
    mount_rec_t rec;

    for(rec = rec_list; rec; rec = rec->next)
        if(ci_strcmp(IcnText(rec->icn), name) == 0)
            return rec;
    return NULL;
}

static mnu_menu menu;
static mnu_item item_free, item_dismount;
static win_window connect_window;
static char link_fname[256];

static icn_icon icn_local, icn_server, icn_share, icn_user,
                icn_password, icn_connect, icn_cancel, icn_notypes,
                icn_keep_pw;

static mount_rec_t menu_rec, null_rec = NULL;

static void show_discs(void)
{
    _kernel_oscli("Filer_OpenDir -LargeIcons LM98:Discs");
}

static void bar_icon_clickS(void)
{
    char buf[256];
    mount_rec_t rec;

    rec = IcnData();
    if(rec->mount)
    {
        sprintf(buf, "Filer_OpenDir LanMan98%s::%s.$",
                     rec->notypes ? "#notypes" : "",
                     IcnText(rec->icn));

        _kernel_oscli(buf);
    }
    else
    {
        show_discs();
    }
}

static void bar_icon_clickA(void)
{
    mount_rec_t rec;

    rec = IcnData();
    show_discs();
}

static void bar_icon_clickM(void)
{
    menu_rec = IcnData();
    MnuGrey(item_dismount, menu_rec->mount == 0);
    MnuGrey(item_free, menu_rec->mount == 0);
    MnuOpen(menu);
}

static void add_disc(char *name, int mount, int notypes)
{
    mount_rec_t rec;

    rec = rec_find(name);
    if(rec == NULL)
    {
        rec = rec_construct();
        rec->icn = IcnBarDisc("!lanman98", name);
        IcnSetData(rec->icn, rec);
        IcnClickS(rec->icn, bar_icon_clickS);
        IcnClickA(rec->icn, bar_icon_clickA);
        IcnClickM(rec->icn, bar_icon_clickM);
    }
    rec->mount = mount;
    rec->notypes = notypes;
    rec->working = 1;
    if(mount == 0)
        null_rec = rec;
}

static void new_disc(void)
{
    link_fname[0] = '\0';
    IcnSetText(icn_local, "");
    IcnSetText(icn_server, "");
    IcnSetText(icn_share, "");
    IcnSetText(icn_user, "");
    IcnSetText(icn_password, "");
    IcnSetSel(icn_notypes, 0);
    IcnSetSel(icn_keep_pw, 0);
    WinOpen(connect_window);
    IcnGiveFocus(icn_local);
}

static void dismount_disc(void)
{
    _kernel_oserror *err;

    if(menu_rec)
    {
        err = _swix(LanMan98_OmniOp, _IN(0)|_IN(1), 1, menu_rec->mount);
        if(err) WinError(err);
        IcnBarDelete(menu_rec->icn);
        rec_destruct(menu_rec);
        menu_rec = NULL;
        if(rec_list == NULL)
            add_disc("", 0, 0);
        MnuOpen(NULL);
    }
}

static void show_free(void)
{
    char buf[256];

    sprintf(buf, "ShowFree -fs LanMan98 %s", IcnText(menu_rec->icn));
    _kernel_oscli(buf);

}

/*
    Find what is out there and leave an icon for each share.

    The work is done by the module, in a task window so that whoever asked
    can watch it happen - it takes a few seconds and talks to every server
    that answers, which is worth showing rather than appearing to hang.
    What it leaves behind is a file per share in Discs, which is the same
    thing this application writes when a share is connected to by hand, so
    double clicking one opens the connect window with everything but the
    password already in it.
*/
static void discover_shares(void)
{
    /*
        Done here rather than in a task window of its own.  A window would
        have to be watched and then dismissed, and what it has to say is
        already said by the icons appearing: the directory opens when this
        finishes and whatever was found is in it.

        It takes a few seconds - every machine that answers is asked what
        it shares - so the pointer says so while it runs.
    */
    char buf[288];
    char *found;

    _swix(Hourglass_On, 0);
    _kernel_oscli("Discover98 { > null: }");
    _swix(Hourglass_Off, 0);

    /*
        What was found is shown from where the module put it, which is the
        scrap directory rather than Discs: a share that has merely been
        seen is not one the user has asked to keep.  It becomes a kept icon
        in Discs if it is double clicked and the connection succeeds.
    */
    found = getenv("LanMan98$Found");
    if(found && *found && strlen(found) + 24 < sizeof(buf))
    {
        sprintf(buf, "Filer_OpenDir -LargeIcons %s", found);
        _kernel_oscli(buf);
    }
    else
    {
        show_discs();
    }
}

/*
    Only one copy at a time.

    A second copy puts a second icon on the bar, a second of everything on
    its menu and a second of any alert, and it is easily done: installing a
    newer version means running it while the old one is still there.

    The task list is read before this copy announces itself to the Wimp, so
    everything found is somebody else and this does not have to know its own
    handle to avoid asking itself to go.  The message is then sent once
    there is a task to send it from.

    Nothing is lost by the older copy going.  The mounts belong to the
    module rather than to the front end, so they stay up, and this copy
    reads them back as it starts.
*/
static int other_copy[8];
static int other_copies = 0;

static void find_other_copies(void)
{
    _kernel_swi_regs r;
    int buf[64];
    int ctx;

    ctx = 0;
    do
    {
        r.r[0] = ctx;
        r.r[1] = (int) buf;
        r.r[2] = sizeof(buf);
        if(_kernel_swi(TaskManager_EnumerateTasks, &r, &r) != NULL)
            return;                 /* no task manager: never mind */
        ctx = r.r[0];
        {
            int *p = buf;
            int *end = (int *) r.r[1];

            while(p + 4 <= end)
            {
                char *nm = (char *) p[1];

                if(nm && (ci_strcmp(nm, TASK_NAME) == 0)
                      && (other_copies < 8))
                    other_copy[other_copies++] = p[0];
                p += 4;
            }
        }
    }
    while(ctx >= 0);
}

static void quit_other_copies(void)
{
    _kernel_swi_regs r;
    int msg[5];
    int i;

    for(i = 0; i < other_copies; i++)
    {
        msg[0] = sizeof(msg);       /* size */
        msg[1] = 0;                 /* sender, filled in by the Wimp */
        msg[2] = 0;                 /* my ref */
        msg[3] = 0;                 /* your ref: not a reply */
        msg[4] = 0;                 /* Message_Quit */
        r.r[0] = 17;                /* User_Message */
        r.r[1] = (int) msg;
        r.r[2] = other_copy[i];
        _kernel_swi(Wimp_SendMessage, &r, &r);
    }
}

static void mk_menu(void)
{
    mnu_item item[7];

    item[0] = MnuSubmenu("Info", MnuWindow(info_window()));
    item[1] = MnuAction("Show discs", show_discs);
    item[2] = MnuAction("Discover", discover_shares);
    item[3] = MnuAction("New...", new_disc);
    item[4] = item_dismount = MnuAction("Dismount", dismount_disc);
    item[5] = item_free = MnuAction("Free", show_free);
    item[6] = NULL;
    menu = MnuMenu("LanMan98", item);
}

static void win_error(char *s)
{
    _kernel_oserror err;

    err.errnum = 0;
    strcpy(err.errmess, s);
    WinError(&err);
}

static void save_share(void)
{
    static const char link_fname_fmt[]  = "LM98:Discs.%s";
    static const char settype_cmd_fmt[] = "SetType %s &069";
    char buf[256];
    FILE *f;

    /*
        Where the link gets written back.

        A share *Discover98 found is left in the scrap directory, and
        connecting to it is what promotes it into Discs: that is what makes
        a share which was merely seen into one that is kept.  So a link
        opened from there is written to Discs under its local name rather
        than back to where it came from, which would not outlive the next
        discovery anyway.

        Anything else is written back where it was.  This used to promote a
        link opened from anywhere that was not Discs, which meant a share
        the user had filed somewhere of their own - moved onto another disc,
        or into a directory with the rest of a group - reappeared in Discs
        every time it was used.  With a handful of shares that is untidy;
        with dozens it makes them impossible to organise at all.
    */
    {
        char *found = getenv("LanMan98$Found");

        if(found && *found && ci_strncmp(link_fname, found, strlen(found)) == 0)
            link_fname[0] = '\0';
    }

    if (link_fname[0] == '\0')
    {
        const char *icn_text = IcnText(icn_local);

        if(sizeof(link_fname_fmt) - 2 + strlen(icn_text) <= sizeof(link_fname))
            sprintf(link_fname, link_fname_fmt, IcnText(icn_local));
    }
    f = fopen(link_fname, "w");
    if(f)
    {
        fprintf(f, "Server: %s\n", IcnText(icn_server));
        fprintf(f, "Share: %s\n", IcnText(icn_share));
        if(*IcnText(icn_user))
            fprintf(f, "User: %s\n", IcnText(icn_user));
        if(*IcnText(icn_password))
        {
            if(IcnSel(icn_keep_pw))
                fprintf(f, "Password: %s\n", IcnText(icn_password));
            else
                fprintf(f, "Password: *****\n");
        }
        if(IcnSel(icn_notypes))
            fprintf(f, "Notypes: Y\n");
        fclose(f);
        if(sizeof(settype_cmd_fmt) - 2 + strlen(link_fname) <= sizeof(buf))
        {
            sprintf(buf, settype_cmd_fmt, link_fname);
            _kernel_oscli(buf);
        }
    }
}

static void str2up(char *s, char *t, int size)
{
    int len, i;

    len = strlen(t) + 1;
    if(len > size)
        len = size;
    for(i = 0; i < len; i++)
        s[i] = toupper(t[i]);
    s[size-1] = 0;
}

#define MAX_SHARE (64)
static void connect(void)
{
    _kernel_oserror *err;
    _kernel_swi_regs r;
    char buf[MAX_SHARE];

    if(*IcnText(icn_local) == 0)
        win_error("\"Local name\" not specified");
    else if(*IcnText(icn_server) == 0)
        win_error("\"Server\" not specified");
    else if(*IcnText(icn_share) == 0)
        win_error("\"Share\" not specified");
    else
    {
        str2up(buf, IcnText(icn_share), MAX_SHARE);
        r.r[0] = 0;
        r.r[1] = (int) IcnText(icn_server);
        r.r[2] = (int) IcnText(icn_user);
        r.r[3] = (int) IcnText(icn_password);
        r.r[4] = (int) IcnText(icn_local);
        r.r[5] = (int) buf;
        err = _kernel_swi(LanMan98_OmniOp, &r, &r);
        if(err)
        {
            /*
                A failure that is about the machine rather than about the
                user is only reported.

                Opening the connect window with the password ready to be
                retyped is the right answer to a refused logon and the
                wrong one to a server that is switched off: it says the
                password was the problem when the message says it was not,
                and invites the user to type it again to no purpose.
            */
            if(err->errnum != ERR_SERVER_UNREACHABLE)
            {
                WinOpen(connect_window);
                IcnGiveFocus(icn_password);
            }
            WinError(err);
        }
        else
        {
            save_share();
            add_disc(IcnText(icn_local), r.r[1], IcnSel(icn_notypes));
            if(null_rec)
            {
                IcnBarDelete(null_rec->icn);
                rec_destruct(null_rec);
                null_rec = NULL;
            }
            WinClose(connect_window);
        }
    }
}

static void cancel(void)
{
    WinClose(connect_window);
}

static int key_press(int k)
{
    if(k != '\r')
        return 0;
    connect();
    return 1;
}

static void mk_connect_window()
{
    win_template tplt;
    icn_icon *icn;

    tplt = WinLdTplt("<LanMan98$Dir>.Templates", "connect");
    if(tplt == NULL)
        ErrorFatal("Can't find \"connect\" in \"<LanMan98$Dir>.Templates\"");
    connect_window = WinCreate(tplt);
    if(connect_window == NULL)
        ErrorFatal("Can't create \"Connect\" window");
    WinPress(connect_window, key_press);
    icn = IcnButtons(connect_window);
    icn_local        = icn[5];
    icn_server	     = icn[6];
    icn_share	     = icn[7];
    icn_user	     = icn[8];
    icn_password     = icn[9];
    icn_connect	     = icn[10];
    icn_cancel	     = icn[11];
    icn_notypes	     = icn[13];
    icn_keep_pw	     = icn[15];
    IcnClickS(icn_connect, connect);
    IcnClickA(icn_connect, connect);
    IcnClickS(icn_cancel, cancel);
    IcnClickA(icn_cancel, cancel);
}

// static char *tag_read(char *tag, char *buf)
// {
//     return (memcmp(tag, buf, strlen(tag)) == 0)
//                ? strtok(buf + strlen(tag), " \t\n")
//                : NULL;
// }

static char *tag_read(char *tag, char *buf)
{
    char *ret;

    if(memcmp(tag, buf, strlen(tag)) != 0)
        return NULL;
    buf += strlen(tag);
    buf += strspn(buf, " \t");
    ret = strchr(buf, '\n');
    if(ret) *ret = 0;
    return buf;
}

static int data_open(int type, char *name)
{
    FILE *f;
    char buf[256], *s;
    int needs_password;

    if(type != 0x069)
        return 0;
    f = fopen(name, "r");
    if(f)
    {
        if(strlen(name) + 1 <= sizeof(link_fname))
            sprintf(link_fname, "%s", name);

        s = strrchr(name, '.');
        name = s ? s + 1 : name;
        s = strrchr(name, ':');
        name = s ? s + 1 : name;
        IcnSetText(icn_local, name);
        IcnSetText(icn_server, "");
        IcnSetText(icn_share, "");
        IcnSetText(icn_user, "");
        IcnSetText(icn_password, "");
        IcnSetSel(icn_notypes, 0);
        IcnSetSel(icn_keep_pw, 0);
        needs_password = 0;
        while(!feof(f))
        {
            if(fgets(buf, 256, f))
            {
                s = tag_read("Server:", buf);
                if(s) IcnSetText(icn_server, s);
                s = tag_read("Share:", buf);
                if(s) IcnSetText(icn_share, s);
                s = tag_read("User:", buf);
                if(s) IcnSetText(icn_user, s);
                s = tag_read("Password:", buf);
                if(s)
                {
                    needs_password = (strcmp(s, "*****") == 0);
                    IcnSetSel(icn_keep_pw, !needs_password);

                    if(!needs_password)
                        IcnSetText(icn_password, s);
                }
                s = tag_read("Notypes:", buf);
                if(s && (s[0] == 'y' || s[0] == 'Y'))
                    IcnSetSel(icn_notypes, 1);
            }
        }
        fclose(f);
        if(needs_password)
        {
            WinOpen(connect_window);
            IcnGiveFocus(icn_password);
        }
        else
        {
            connect();
        }
    }
    return 1;
}

static void update_mounts(void)
{
    mount_rec_t rec, *recp;

    for(rec = rec_list; rec; rec = rec->next)
        rec->working = 0;

    for(LM98EnumBegin(); LM98EnumGoing(); LM98EnumNext())
    {
        rec = rec_find(LM98EnumName());
        if(rec)
        {
            rec->working = 1;
            rec->mount = LM98EnumMount();
            // Probably has that value already, but if it doesn't then
            // it should have, and the old value is stale (need not be closed)
        }
        else
        {
            add_disc(LM98EnumName(), LM98EnumMount(), 0);
            if(null_rec)
            {
                IcnBarDelete(null_rec->icn);
                rec_destruct(null_rec);
                null_rec = NULL;
            }
        }
    }

    if(null_rec)
        null_rec->working = 1;

    recp = &rec_list;
    while(*recp)
    {
        if((*recp)->working)
        {
            recp = &(*recp)->next;
        }
        else
        {
            rec = *recp;
            *recp = rec->next;
            IcnBarDelete(rec->icn);
            free(rec);
        }
    }

    if(rec_list == NULL)
        add_disc("", 0, 0);
}

/*
    Saying that a server has appeared.

    The module asks the network what is on it on a timer and puts the name
    of anything answering for the first time in LanMan98$New.  That is read
    here on the same wake the mount list is already looked at on, and said
    in a window.

    Never an error box.  Wimp_ReportError is modal: it holds the whole
    desktop until somebody clicks it, and for something nobody asked for -
    arriving mid-drag, or while the machine is unattended - that is worse
    than saying nothing at all.  So this is an ordinary window that takes
    itself away again, either when clicked or after a while on screen.

    A !LanMan98 whose Templates predates this has no "alert" in it.  That
    must not stop the Filer running, so the window is built the first time
    there is something to say and, if it cannot be, the whole thing goes
    quiet for the rest of the session rather than complaining once per
    server for ever.
*/

#define ALERT_LINGER (1500)     /* centiseconds it stays on screen */
/*
    How much text the message icon will hold.

    Not arithmetic: the desktop draws in a proportional font whose size is
    the user's business, so the width of a string is not knowable from its
    length.  This is a deliberately cautious budget for the 1368 OS units
    the icon spans - about 34 OS units a character, which is roughly twice
    the fixed system font and leaves room for a wider one.  Beyond it the
    message counts the servers instead of naming them, which is short
    whatever the font.
*/
#define ALERT_TEXT_MAX (40)
#define ALERT_MARGIN (32)       /* gap from the top right of the screen */

static win_window alert_win = NULL;
static int alert_usable = 1;
static int alert_deadline = 0;  /* when to close; 0 while not showing */

static int monotonic(void)
{
    int t;

    t = 0;
    _swix(OS_ReadMonotonicTime, _OUT(0), &t);
    return t;
}

static void alert_hide(void)
{
    if(alert_deadline)
    {
        alert_deadline = 0;
        WinClose(alert_win);
    }
}

static void alert_click(int x, int y)
{
    (void) x;
    (void) y;
    alert_hide();
}

static void alert_icon_click(void)
{
    alert_hide();
}

static int alert_build(void)
{
    win_template tplt;
    icn_icon *icon;

    if(alert_win)
        return 1;
    if(!alert_usable)
        return 0;
    tplt = WinLdTplt("<LanMan98$Dir>.Templates", "alert");
    if(tplt != NULL)
        alert_win = WinCreate(tplt);
    if(alert_win == NULL)
    {
        alert_usable = 0;
        return 0;
    }
    WinClickS(alert_win, alert_click);
    icon = IcnButtons(alert_win);
    IcnClickS(icon[0], alert_icon_click);
    IcnClickS(icon[1], alert_icon_click);
    return 1;
}

/*
    Put it in the top right corner, which is where something nobody asked
    for belongs: away from where the pointer is likely to be, and clear of
    the icon bar.
*/
static void alert_place(void)
{
    int x0, y0, x1, y1;
    int xwl, ywl, xeig, yeig;

    x0 = y0 = x1 = y1 = 0;
    WinExtent(alert_win, &x0, &y0, &x1, &y1);
    xwl = ywl = 0;
    xeig = yeig = 1;
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 11, &xwl);
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 12, &ywl);
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 4, &xeig);
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 5, &yeig);
    WinPlace(alert_win,
             ((xwl + 1) << xeig) - (x1 - x0) - ALERT_MARGIN,
             ((ywl + 1) << yeig) - ALERT_MARGIN);
    /*
        WinPlace positions a window; it does not open one.  It opens the
        window to move it and then closes it again unless it was already
        open, so a window being shown for the first time has to be opened
        after being placed rather than by placing it.
    */
    WinOpen(alert_win);
}

static void alert_show(char *names)
{
    char buf[192];
    icn_icon *icon;

    if(!alert_build())
        return;
    icon = IcnButtons(alert_win);
    /*
        The module separates names with commas, so more than one name means
        more than one machine.

        Name them where they fit and count them where they do not: the
        window is a fixed width and the system font a fixed pitch, so a
        long enough list would otherwise be centred and clipped at both
        ends, which reads as neither a name nor a number.
    */
    {
        int several = (strchr(names, ',') != NULL);
        int n, i;

        n = 1;
        for(i = 0; names[i]; i++)
            if(names[i] == ',')
                n++;

        if((int) strlen(names) + (several ? 23 : 22) <= ALERT_TEXT_MAX)
            sprintf(buf, "%s %s now sharing files", names,
                    several ? "are" : "is");
        else
            sprintf(buf, "%d servers are now sharing files", n);
    }
    /*
        Close it first if it is already up.  Changing the text under an
        open window leaves the Wimp redrawing it at its own convenience,
        and a second server arriving while the first is still on screen
        would otherwise show the old message.
    */
    alert_hide();
    IcnSetText(icon[0], buf);
    alert_place();
    alert_deadline = monotonic() + ALERT_LINGER;
}

static void alert_poll(void)
{
    char *names;

    names = getenv("LanMan98$New");
    if(names != NULL && *names != 0)
    {
        alert_show(names);
        /*
            Taken, so the module can say it again for the next machine.
            Cleared even when there was no window to show it in, or an old
            Templates would leave it set and this would run every wake.
        */
        _kernel_oscli("Unset LanMan98$New");
    }
    else if(alert_deadline && monotonic() - alert_deadline >= 0)
    {
        alert_hide();
    }
}


int main()
{
    int *poll_word = NULL;
    _kernel_oserror *err;

//    MemCheck_Init();
//    MemCheck_RedirectToFilename("<LanMan98$Dir>.FilerMem");
//    MemCheck_InterceptSCLStringFunctions();
//    MemCheck_SetQuitting(0, 0);
//    MemCheck_OutputBlocksInfo();
    find_other_copies();        /* while this is not yet a task itself */
    WinInit(TASK_NAME);
    quit_other_copies();
    mk_menu();
    mk_connect_window();
    WinDataOpen(data_open);
    add_disc("", 0, 0);
    err = _swix(LanMan98_PollWord, _OUT(0), &poll_word);
    if(err) ErrorFatal("LamMan98 module not present");
//    MemCheck_RegisterMiscBlock(poll_word, sizeof(int));
    update_mounts();
    while(1)
    {
        if(*poll_word)
        {
            err = _swix(LanMan98_PollWord, _OUT(0), &poll_word);
            if(err) ErrorFatal("LamMan98 module not present");
            update_mounts();
        }
        alert_poll();
        WinSleep(50);
    }
    return 0;
}
