U0 InputFilterTask()
{
    CJob *tmpc, *tmpc1;
    Bool  old_filter;
    I64   old_flags = RFlagsGet;

    Fs->win_inhibit = WIG_USER_TASK_DEFAULT;
    LBts(&Fs->task_flags, TASKf_INPUT_FILTER_TASK);
    old_filter = LBts(&Fs->last_input_filter_task->task_flags, TASKf_FILTER_INPUT);
    LBEqual(&Fs->task_flags, TASKf_FILTER_INPUT, old_filter);
    while (TRUE)
    {
        CLI
        JobsHandler(old_flags);
        tmpc1 = &Fs->server_ctrl.next_waiting;
        tmpc  = tmpc1->next;
        if (tmpc == tmpc1)
            break;
        else
        {
            if (tmpc->job_code == JOBT_TEXT_INPUT)
            {
                QueueRemove(tmpc);
                RFlagsSet(old_flags);
                try
                    ExePrint("%s", tmpc->aux_str);
                catch
                    Fs->catch_except = TRUE;
                JobDel(tmpc);
            }
            else
                break;
        }
    }
    Fs->next_input_filter_task->last_input_filter_task = Fs->last_input_filter_task;
    Fs->last_input_filter_task->next_input_filter_task = Fs->next_input_filter_task;
    if (!old_filter)
        LBtr(&Fs->last_input_filter_task->task_flags, TASKf_FILTER_INPUT);
    RFlagsSet(old_flags);
}

I64 MessageScan(I64 *_arg1=NULL, I64 *_arg2=NULL, I64 mask=~1, CTask *task=NULL)
{/*Check for a message of type specified by a one in the mask.
Throw-out messages not in mask.
If no message fit mask, return NULL immediately.
Remove desired message, return message_code.
Note: This delivers messages from parent down to pop-up.
*/
    I64   res, old_flags;
    CJob *tmpc, *tmpc1;

    if (!task)
        task = Fs;
    old_flags = RFlagsGet;
    tmpc1 =&task->server_ctrl.next_waiting;
    while (TRUE)
    {
        CLI
        if (task == Fs)
            JobsHandler(old_flags);
        tmpc = tmpc1->next;
        if (tmpc == tmpc1)
            break;
        else
        {
            if (tmpc->job_code == JOBT_MESSAGE)
            {
                QueueRemove(tmpc);
                RFlagsSet(old_flags);
                res = tmpc->message_code;
                if (_arg1)
                    *_arg1 = tmpc->aux1;
                if (_arg2)
                    *_arg2 = tmpc->aux2;
                JobDel(tmpc);
                if ((res != MESSAGE_KEY_DOWN || !(tmpc->aux2 & SCF_KEY_DESC) || !Bt(&task->win_inhibit, WIf_SELF_KEY_DESC)) &&
                        Bt(&mask, res))
                    goto sm_done;
            }
        }
        RFlagsSet(old_flags);
    }
    res = MESSAGE_NULL;
    if (_arg1)
        *_arg1 = 0;
    if (_arg2)
        *_arg2 = 0;
    if (task->parent_task && task->parent_task->popup_task == task)
    {
        RFlagsSet(old_flags);
        return MessageScan(_arg1, _arg2, mask, task->parent_task);
    }
sm_done:
    RFlagsSet(old_flags);

    return res;
}

I64 FlushMessages(CTask *task=NULL)
{//Throw away all messages. Return count.
    I64 res = 0, arg1, arg2;

    while (MessageScan(&arg1, &arg2, ~1, task))
        res++;

    return res;
}

I64 MessageGet(I64 *_arg1=NULL, I64 *_arg2=NULL, I64 mask=~1, CTask *task=NULL)
{//Wait for a message of type specified by a one in the mask.
//Throw-out all messages not in mask.
    //Returns message_code. See ::/Demo/MessageLoop.ZC.
    I64 res;

    if (!task)
        task = Fs;
    LBtr(&task->task_flags, TASKf_IDLE);
    while (!(res = MessageScan(_arg1, _arg2, mask, task)))
    {
        LBts(&task->task_flags, TASKf_IDLE);
        Yield;
    }
    LBtr(&task->task_flags, TASKf_IDLE);

    return res;
}

I64 CharScan()
{//Checks for MESSAGE_KEY_DOWN and returns 0 immediately if no key.
//Waits for MESSAGE_KEY_UP of non-zero ASCII key and returns ASCII if key.
    //MessageScan() throws away other message types.
    I64 arg1a, arg2a, arg1b, arg2b;

    if (!MessageScan(&arg1a, &arg2a, 1 << MESSAGE_KEY_DOWN) || !arg1a)
        return 0;
    else
        do MessageGet(&arg1b, &arg2b, 1 << MESSAGE_KEY_UP);
        while (!arg1b); //Be careful of SC_SHIFT and SC_CTRL, etc.

    return arg1a;
}

Bool KeyScan(I64 *_ch=NULL, I64 *_scan_code=NULL, Bool echo=FALSE)
{//Checks for MESSAGE_KEY_DOWN and returns FALSE immediately if no key.
//Sets ASCII and scan_code.
    //Removes key message and returns TRUE.
    //MessageScan() throws away other message types.
    I64 ch = 0, sc = 0;

    if (MessageScan(&ch, &sc, 1 << MESSAGE_KEY_DOWN))
    {
        if (_ch)
            *_ch = ch;
        if (_scan_code)
            *_scan_code = sc;
        if (echo)
            PutKey(ch, sc);
        return TRUE;
    }
    else
    {
        if (_ch)
            *_ch = 0;
        if (_scan_code)
            *_scan_code = 0;
        return FALSE;
    }
}

I64 KeyGet(I64 *_scan_code=NULL, Bool echo=FALSE, Bool raw_cursor=FALSE)
{//Waits for MESSAGE_KEY_DOWN message and returns ASCII.
//Sets scan_code.
    //KeyScan() throws away other message types.
    I64  ch, sc;
    Bool cursor_on = FALSE;

    while (!KeyScan(&ch, &sc, FALSE))
    {
        if (IsRaw && raw_cursor)
        {
            if (!cursor_on && ToI64(TSCGet * 5 / counts.time_stamp_freq) & 1)
            {
                '.';
                cursor_on = TRUE;
            }
            else if (cursor_on && !(ToI64(TSCGet * 5 / counts.time_stamp_freq) & 1))
            {
                '' CH_BACKSPACE;
                cursor_on = FALSE;
            }
        }
        LBts(&Fs->task_flags, TASKf_IDLE);
        if (IsDebugMode)
        {
//We don't want interrupt-driven keyboard when in debugger
            //because that could have side-effects or crash, so we poll
            //keyboard when in debugger with interrupts off.
            PUSHFD
            CLI
            KbdMouseHandler(TRUE, FALSE);
            KbdMessagesQueue;
            POPFD
        }
        else
        {
            LBts(&Fs->task_flags, TASKf_AWAITING_MESSAGE);
            Yield;
        }
        LBtr(&Fs->task_flags, TASKf_IDLE);
    }
    if (IsRaw && raw_cursor && cursor_on)
        '' CH_BACKSPACE;
    if (echo)
        PutKey(ch, sc);
    if (_scan_code)
        *_scan_code = sc;
    return ch;
}

I64 CharGet(I64 *_scan_code=NULL, Bool echo=TRUE, Bool raw_cursor=FALSE)
{//Waits for non-zero ASCII key.
//Sets scan_code.
    I64 ch1;

    do ch1 = KeyGet(_scan_code, FALSE, raw_cursor);
    while (!ch1);
    if (echo)
    {
        if (!IsRaw)
            "$PT$%c$FG$", ch1;
        else
            "%c", ch1;
    }

    return ch1;
}

U8 *StrGet(U8 *message=NULL, U8 *default=NULL, I64 flags=NONE)
{//Returns a MAlloc()ed prompted string. See Flags.
    U8 *st;

    if (message)
        "" message, default;
    st = (*fp_getstr2)(flags);
    if (!*st)
    {
        Free(st);
        if (default)
            return StrNew(default);
        else
            return StrNew("");
    }
    return st;
}

I64 StrNGet(U8 *buf, I64 size, Bool allow_ext=TRUE)
{//Prompt into fixed length string. Size must include terminator.
    U8 *st;
    I64 ch, i = 0;

    if (!size || !buf)
        return 0;
    if (allow_ext)
    {
        st = StrGet;
        if (StrLen(st) > size - 1)
        {
            MemCopy(buf, st, size - 1);
            buf[size - 1] = 0;
        }
        else
            StrCopy(buf, st);
        i = StrLen(buf);
        Free(st);
    }
    else
    {
        while (TRUE)
        {
            ch = CharGet(, FALSE, IsDebugMode);
            if (ch == '\n')
            {
                '' ch;
                break;
            }
            else if (ch == CH_BACKSPACE)
            {
                if (i > 0)
                {
                    i--;
                    '' ch;
                }
            }
            else
            {
                if (i < size - 1)
                {
                    buf[i++] = ch;
                    '' ch;
                }
            }
        }
        buf[i] = 0;
    }
    return i;
}