[comp.os.os2] Keyboard, mouse and threads

kulokari@cc.helsinki.fi (01/04/90)

In article <1020@tuminfo1.lan.informatik.tu-muenchen.dbp.de>, rommel@lan.informatik.tu-muenchen.dbp.de (Kai-Uwe Rommel) writes:
> I know about the busy loop problem. But I already tried to use to
> separate threads for keyboard and mouse input. I was surprised to see
> that the MouReadEventQueue() call apears to use busy wait too when the
> MOUSE_WAIT mode is used. When my mouse thread waited for an event, my
> other thread waiting for keyboard input was blocked !?! Any hints ?
> Currently I use the MOUSE_NOWAIT mode and make some DosSleep() calls
> between the MouReadEventQueue() calls.
> 

Mouse and keyboard handling and the use of threads therein, with special
reference to uEmacs, does seem to interest many of us. I have had no problems
in getting the system to work. Stopping it is a lot harder, as will be
demonstrated below.

This message is a bit lengthy, sorry.

The problem is, how to use OS/2 threads to read keyboard and mouse
input and merge them into one message queue (fifo), if possible without
busy loop polling. This much for the background. 

The basic solution, written in somewhat stylized and abridged C,
is the following:

--------------
int stop, threads;
unsigned long something;

kbd_thread() {
    threads++;
    while (!stop)
    {   
        /* read a key, translate to character string and put it in FIFO */

        ...
        /* indicate there is something in FIFO */
        DosSemClear(&something);
    }
    threads--;
    DosExit(0,0);
}

mou_thread() {

    /* analogous to kbd_thread */
}


get_char() {
    if (somethinginfifo())
        return (getfromfifo());

    DosSemSetWait(&something,-1L); /* wait until there is something
                                      again... */  
    return (getfromfifo());     
}


main() {
    char c;

    ...

    stop = 0;
    threads=0;

    /* start threads */
    ...

    /* main loop */

    while (!stop)
        c = get_char();
        ...
        /* something */
        ...
    }

    /* wait for threads to kill themselves */
    while (threads);
    ...
}
-----------------
    
This is simple and beautiful, and works well until you try to exit. Because
both threads are always waiting for the "next" keyin/mouse event, you must both
press a key and click the mouse before the threads get the change to notice
that they are expected to die. Of course, if you just brutally exit the
program, the threads will be gone too, but in many cases this is not what you
want. For example, you must be able to deactivate keyboard and mouse handlers
when you spawn subprocesses, and restore them afterwards.

So we must eliminate the read-ahead. Let us introduce another
semaphore, 'proceed', and modify the code:

---------------

int stop, threads;
unsigned long something, proceed;

kbd_thread() {
    threads++;
    while (1)
    {
        DosSemWait(&proceed,-1L);  /* wait for permission to continue */
        if (stop) {
            threads--;
            DosExit(0,0);
        }
        /* read a key, translate to character string and put it in FIFO */

        ...
        /* indicate there is something in FIFO */
        DosSemClear(&something);
        DosSemSet(&proceed); 
    }
}

mou_thread() {

    /* analogous to kbd_thread */
}


get_char() {
    if (somethinginfifo())
        return (getfromfifo());

    DosSemSet(&something);         /* indicate fifo is empty */
    DosSemClear(&proceed);         /* allow threads to continue */
    DosSemWait(&something,-1L);    /* wait until there is something
                                      again ... */
    return (getfromfifo());     
}


main() {
    ...

    stop = 0;
    threads=0;
    DosSemClear(&proceed);

    /* start threads */
    ...

    /* main loop */

    while (!stop)
        ...
        /* do something */
        ...
    }

    DosSemClear(&proceed);
    /* wait for threads to kill themselves */
    while (threads);
    ...
}


---------------------

But this is still not good enough. If the quit command comes from
keyboard, you must still click the mouse, and vice versa. I know no
*good* solution. What you need is the ability to kill threads from
the outside, and OS/2 does not allow that as far as I know.
DosSuspendThread is useless here. Another passable solution would
be to have a timeout in MouReadEventQue() and KbdCharIn(). No luck
there, either. What you *can* do is to simulate timeouts. This brings
back polling, tempered with judicious use of DosSleep().

If you know that the quit command always comes from keyboard (as in
uEmacs), then you need to poll only the mouse. This is fortunate,
because response time is not so critical with mouse, and because mouse
has its own event queue, events do not go unnoticed while we sleep, so
we can sleep longer.

Here is the "final" version of mou_thread():


#define AWHILE 333L /* third of a second */

mou_thread() {
    int nEvents;
    threads++;
    while (1)
    {
        DosSemWait(&proceed,-1L); /* wait for permission to continue */
        if (stop) {
            threads--;
            DosExit(0,0);
        }
            
        MouGetNumQueEl(...); /* set nEvents to number of events
                                in mouse queue */
        if (nEvents==0) {
            DosSleep(AWHILE);
            continue; /* restart loop */
        }
        /* translate all mouse events in queue to character strings,
           store them in FIFO */

          ...

        /* indicate there is something in FIFO */
        DosSemClear(&something);
        DosSemSet(&proceed);
    }
    threads--;
    DosExit(0,0);
}


Although this works well, THIS IS NOT A GOOD SOLUTION! OS/2 should
provide a combined keyboard / mouse event queue for text-mode
applications, too. PM has it, of course. And there should be a way to
kill threads, as there is a way to kill processes (sessions). Perhaps
in a future release...

Or perhaps somebody knows how to do it better with the present tools?


---


Hannu Kulokari
CC, U of Helsinki
kulokari@cc.helsinki.fi

ballard@cheddar.cc.ubc.ca (Alan Ballard) (01/05/90)

In article <1688.25a364a4@cc.helsinki.fi> kulokari@cc.helsinki.fi writes:
> ...    
>This is simple and beautiful, and works well until you try to exit. Because
>both threads are always waiting for the "next" keyin/mouse event, you must both
>press a key and click the mouse before the threads get the change to notice
>that they are expected to die. Of course, if you just brutally exit the
>program, the threads will be gone too, but in many cases this is not what you
>want.
 
You might try closing the mouse/keyboard handles from another thread, which
*may* have the effect of causing the waits in the key/mouse threads to abort.
That works in a similar situation for device monitors.  At least, it works
with OS/2 1.1; I've had some preliminary indication maybe it doesn't always
work with 1.2.   
 
An alternative, of course, is to spawn a separate process to contain the 
threads that wait on mouse and keyboard, and to have them communicate back
to the main thread via IPC or shared memory.  Then you can just kill this 
process and restart it when you want to wait again.
 


>...  And there should be a way to
>kill threads, as there is a way to kill processes (sessions). Perhaps
>in a future release...
 
I agree.  The lack of anyway to kill a thread or abort a thread's wait is 
one of the more glaring omissions in the OS/2 API. 
 



Alan Ballard                   | Internet: Alan_Ballard@mtsg.ubc.ca
University Computing Services  |   Bitnet: USERAB1@UBCMTSG
University of British Columbia |    Phone: 604-228-3074
Vancouver B.C. Canada V6R 1W5  |      Fax: 604-228-5116

kulokari@cc.helsinki.fi (01/06/90)

In article <6155@ubc-cs.UUCP>, ballard@cheddar.cc.ubc.ca (Alan Ballard) writes:
> You might try closing the mouse/keyboard handles from another thread, which
> *may* have the effect of causing the waits in the key/mouse threads to abort.
> That works in a similar situation for device monitors.  At least, it works
> with OS/2 1.1; I've had some preliminary indication maybe it doesn't always
> work with 1.2.   

I tried. Does not work. 
>  
> An alternative, of course, is to spawn a separate process to contain the 
> threads that wait on mouse and keyboard, and to have them communicate back
> to the main thread via IPC or shared memory.  Then you can just kill this 
> process and restart it when you want to wait again.

I thought that. Somehow I feel it is overkill for such a simple and basic
task. Separate exe file and all that.


Hannu Kulokari
CC, U of Helsinki

kulokari@cc.helsinki.fi (Internet)
kulokari@finuh (Bitnet)