fdopen: don't rewind the file after creating the FILE* handle. Added
[wine] / dlls / winedos / dosvm.c
1 /*
2  * DOS Virtual Machine
3  *
4  * Copyright 1998 Ove Kåven
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Note: This code hasn't been completely cleaned up yet.
21  */
22
23 #include "config.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 #ifdef HAVE_SYS_TIME_H
35 # include <sys/time.h>
36 #endif
37 #include <sys/types.h>
38
39 #include "wine/winbase16.h"
40 #include "wine/exception.h"
41 #include "windef.h"
42 #include "winbase.h"
43 #include "wingdi.h"
44 #include "winuser.h"
45 #include "winnt.h"
46 #include "wincon.h"
47
48 #include "msdos.h"
49 #include "file.h"
50 #include "miscemu.h"
51 #include "dosexe.h"
52 #include "dosvm.h"
53 #include "stackframe.h"
54 #include "wine/debug.h"
55 #include "excpt.h"
56
57 WINE_DEFAULT_DEBUG_CHANNEL(int);
58 WINE_DECLARE_DEBUG_CHANNEL(module);
59 WINE_DECLARE_DEBUG_CHANNEL(relay);
60
61 WORD DOSVM_psp = 0;
62 WORD DOSVM_retval = 0;
63
64 #ifdef MZ_SUPPORTED
65
66 #ifdef HAVE_SYS_VM86_H
67 # include <sys/vm86.h>
68 #endif
69 #ifdef HAVE_SYS_MMAN_H
70 # include <sys/mman.h>
71 #endif
72
73 #define IF_CLR(ctx)     ((ctx)->EFlags &= ~VIF_MASK)
74 #define IF_SET(ctx)     ((ctx)->EFlags |= VIF_MASK)
75 #define IF_ENABLED(ctx) ((ctx)->EFlags & VIF_MASK)
76 #define SET_PEND(ctx)   ((ctx)->EFlags |= VIP_MASK)
77 #define CLR_PEND(ctx)   ((ctx)->EFlags &= ~VIP_MASK)
78 #define IS_PEND(ctx)    ((ctx)->EFlags & VIP_MASK)
79
80 #undef TRY_PICRETURN
81
82 typedef struct _DOSEVENT {
83   int irq,priority;
84   DOSRELAY relay;
85   void *data;
86   struct _DOSEVENT *next;
87 } DOSEVENT, *LPDOSEVENT;
88
89 static CRITICAL_SECTION qcrit = CRITICAL_SECTION_INIT("DOSVM");
90 static struct _DOSEVENT *pending_event, *current_event;
91 static int sig_sent;
92 static HANDLE event_notifier;
93 static CONTEXT86 *current_context;
94
95 static int DOSVM_SimulateInt( int vect, CONTEXT86 *context, BOOL inwine )
96 {
97   FARPROC16 handler=DOSVM_GetRMHandler(vect);
98
99   /* check for our real-mode hooks */
100   if (vect==0x31) {
101     if (context->SegCs==DOSVM_dpmi_segments->wrap_seg) {
102       /* exit from real-mode wrapper */
103       return -1;
104     }
105     /* we could probably move some other dodgy stuff here too from dpmi.c */
106   }
107   /* check if the call is from our fake BIOS interrupt stubs */
108   if ((context->SegCs==0xf000) && !inwine) {
109     if (vect != (context->Eip/4)) {
110       TRACE("something fishy going on here (interrupt stub is %02lx)\n", context->Eip/4);
111     }
112     TRACE("builtin interrupt %02x has been branched to\n", vect);
113     DOSVM_RealModeInterrupt(vect, context);
114   }
115   /* check if the call goes to an unhooked interrupt */
116   else if (SELECTOROF(handler)==0xf000) {
117     /* if so, call it directly */
118     TRACE("builtin interrupt %02x has been invoked (through vector %02x)\n", OFFSETOF(handler)/4, vect);
119     DOSVM_RealModeInterrupt(OFFSETOF(handler)/4, context);
120   }
121   /* the interrupt is hooked, simulate interrupt in DOS space */
122   else {
123     WORD*stack= PTR_REAL_TO_LIN( context->SegSs, context->Esp );
124     WORD flag=LOWORD(context->EFlags);
125
126     TRACE_(int)("invoking hooked interrupt %02x at %04x:%04x\n", vect,
127                 SELECTOROF(handler), OFFSETOF(handler));
128     if (IF_ENABLED(context)) flag|=IF_MASK;
129     else flag&=~IF_MASK;
130
131     *(--stack)=flag;
132     *(--stack)=context->SegCs;
133     *(--stack)=LOWORD(context->Eip);
134     context->Esp-=6;
135     context->SegCs=SELECTOROF(handler);
136     context->Eip=OFFSETOF(handler);
137     IF_CLR(context);
138   }
139   return 0;
140 }
141
142 #define SHOULD_PEND(x) \
143   (x && ((!current_event) || (x->priority < current_event->priority)))
144
145 static void DOSVM_SendQueuedEvent(CONTEXT86 *context)
146 {
147   LPDOSEVENT event = pending_event;
148
149   if (SHOULD_PEND(event)) {
150     /* remove from "pending" list */
151     pending_event = event->next;
152     /* process event */
153     if (event->irq>=0) {
154       /* it's an IRQ, move it to "current" list */
155       event->next = current_event;
156       current_event = event;
157       TRACE("dispatching IRQ %d\n",event->irq);
158       /* note that if DOSVM_SimulateInt calls an internal interrupt directly,
159        * current_event might be cleared (and event freed) in this very call! */
160       DOSVM_SimulateInt((event->irq<8)?(event->irq+8):(event->irq-8+0x70),context,TRUE);
161     } else {
162       /* callback event */
163       TRACE("dispatching callback event\n");
164       (*event->relay)(context,event->data);
165       free(event);
166     }
167   }
168   if (!SHOULD_PEND(pending_event)) {
169     TRACE("clearing Pending flag\n");
170     CLR_PEND(context);
171   }
172 }
173
174 static void DOSVM_SendQueuedEvents(CONTEXT86 *context)
175 {
176   /* we will send all queued events as long as interrupts are enabled,
177    * but IRQ events will disable interrupts again */
178   while (IS_PEND(context) && IF_ENABLED(context))
179     DOSVM_SendQueuedEvent(context);
180 }
181
182 /***********************************************************************
183  *              QueueEvent (WINEDOS.@)
184  */
185 void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
186 {
187   LPDOSEVENT event, cur, prev;
188
189   if (current_context) {
190     EnterCriticalSection(&qcrit);
191     event = malloc(sizeof(DOSEVENT));
192     if (!event) {
193       ERR("out of memory allocating event entry\n");
194       return;
195     }
196     event->irq = irq; event->priority = priority;
197     event->relay = relay; event->data = data;
198
199     /* insert event into linked list, in order *after*
200      * all earlier events of higher or equal priority */
201     cur = pending_event; prev = NULL;
202     while (cur && cur->priority<=priority) {
203       prev = cur;
204       cur = cur->next;
205     }
206     event->next = cur;
207     if (prev) prev->next = event;
208     else pending_event = event;
209
210     /* alert the vm86 about the new event */
211     if (!sig_sent) {
212       TRACE("new event queued, signalling (time=%ld)\n", GetTickCount());
213       kill(dosvm_pid,SIGUSR2);
214       sig_sent++;
215     } else {
216       TRACE("new event queued (time=%ld)\n", GetTickCount());
217     }
218
219     /* Wake up DOSVM_Wait so that it can serve pending events. */
220     SetEvent(event_notifier);
221
222     LeaveCriticalSection(&qcrit);
223   } else {
224     /* DOS subsystem not running */
225     /* (this probably means that we're running a win16 app
226      *  which uses DPMI to thunk down to DOS services) */
227     if (irq<0) {
228       /* callback event, perform it with dummy context */
229       CONTEXT86 context;
230       memset(&context,0,sizeof(context));
231       (*relay)(&context,data);
232     } else {
233       ERR("IRQ without DOS task: should not happen\n");
234     }
235   }
236 }
237
238 static void DOSVM_ProcessConsole(void)
239 {
240   INPUT_RECORD msg;
241   DWORD res;
242   BYTE scan, ascii;
243
244   if (ReadConsoleInputA(GetStdHandle(STD_INPUT_HANDLE),&msg,1,&res)) {
245     switch (msg.EventType) {
246     case KEY_EVENT:
247       scan = msg.Event.KeyEvent.wVirtualScanCode;
248       ascii = msg.Event.KeyEvent.uChar.AsciiChar;
249       TRACE("scan %02x, ascii %02x\n", scan, ascii);
250
251       /* set the "break" (release) flag if key released */
252       if (!msg.Event.KeyEvent.bKeyDown) scan |= 0x80;
253
254       /* check whether extended bit is set,
255        * and if so, queue the extension prefix */
256       if (msg.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) {
257         DOSVM_Int09SendScan(0xE0,0);
258       }
259       DOSVM_Int09SendScan(scan, ascii);
260       break;
261     case MOUSE_EVENT:
262       DOSVM_Int33Console(&msg.Event.MouseEvent);
263       break;
264     case WINDOW_BUFFER_SIZE_EVENT:
265       FIXME("unhandled WINDOW_BUFFER_SIZE_EVENT.\n");
266       break;
267     case MENU_EVENT:
268       FIXME("unhandled MENU_EVENT.\n");
269       break;
270     case FOCUS_EVENT:
271       FIXME("unhandled FOCUS_EVENT.\n");
272       break;
273     default:
274       FIXME("unknown console event: %d\n", msg.EventType);
275     }
276   }
277 }
278
279 static void DOSVM_ProcessMessage(MSG *msg)
280 {
281   BYTE scan = 0;
282
283   TRACE("got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
284   if ((msg->message>=WM_MOUSEFIRST)&&
285       (msg->message<=WM_MOUSELAST)) {
286     DOSVM_Int33Message(msg->message,msg->wParam,msg->lParam);
287   } else {
288     switch (msg->message) {
289     case WM_KEYUP:
290       scan = 0x80;
291     case WM_KEYDOWN:
292       scan |= (msg->lParam >> 16) & 0x7f;
293
294       /* check whether extended bit is set,
295        * and if so, queue the extension prefix */
296       if (msg->lParam & 0x1000000) {
297         /* FIXME: some keys (function keys) have
298          * extended bit set even when they shouldn't,
299          * should check for them */
300         DOSVM_Int09SendScan(0xE0,0);
301       }
302       DOSVM_Int09SendScan(scan,0);
303       break;
304     }
305   }
306 }
307
308 /***********************************************************************
309  *              Wait (WINEDOS.@)
310  */
311 void WINAPI DOSVM_Wait( INT read_pipe, HANDLE hObject )
312 {
313   MSG msg;
314   DWORD waitret;
315   HANDLE objs[3];
316   int objc;
317   BOOL got_msg = FALSE;
318
319   objs[0]=GetStdHandle(STD_INPUT_HANDLE);
320   objs[1]=event_notifier;
321   objs[2]=hObject;
322   objc=hObject?3:2;
323   do {
324     /* check for messages (waste time before the response check below) */
325     if (PeekMessageA)
326     {
327         while (PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) {
328             /* got a message */
329             DOSVM_ProcessMessage(&msg);
330             /* we don't need a TranslateMessage here */
331             DispatchMessageA(&msg);
332             got_msg = TRUE;
333         }
334     }
335 chk_console_input:
336     if (!got_msg) {
337       /* check for console input */
338       INPUT_RECORD msg;
339       DWORD num;
340       if (PeekConsoleInputA(objs[0],&msg,1,&num) && num) {
341         DOSVM_ProcessConsole();
342         got_msg = TRUE;
343       }
344     }
345     if (read_pipe == -1) {
346       /* dispatch pending events */
347       if (SHOULD_PEND(pending_event)) {
348         CONTEXT86 context = *current_context;
349         IF_SET(&context);
350         SET_PEND(&context);
351         DOSVM_SendQueuedEvents(&context);
352         got_msg = TRUE;
353       }
354       if (got_msg) break;
355     } else {
356       fd_set readfds;
357       struct timeval timeout={0,0};
358       /* quick check for response from dosmod
359        * (faster than doing the full blocking wait, if data already available) */
360       FD_ZERO(&readfds); FD_SET(read_pipe,&readfds);
361       if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0)
362         break;
363     }
364     /* nothing yet, block while waiting for something to do */
365     if (MsgWaitForMultipleObjects)
366         waitret = MsgWaitForMultipleObjects(objc,objs,FALSE,INFINITE,QS_ALLINPUT);
367     else
368         waitret = WaitForMultipleObjects(objc,objs,FALSE,INFINITE);
369
370     if (waitret==(DWORD)-1) {
371       ERR_(module)("dosvm wait error=%ld\n",GetLastError());
372     }
373     if ((read_pipe != -1) && hObject) {
374       if (waitret==(WAIT_OBJECT_0+2)) break;
375     }
376     if (waitret==WAIT_OBJECT_0)
377       goto chk_console_input;
378   } while (TRUE);
379 }
380
381 DWORD WINAPI DOSVM_Loop( HANDLE hThread )
382 {
383   HANDLE objs[2];
384   MSG msg;
385   DWORD waitret;
386
387   objs[0] = GetStdHandle(STD_INPUT_HANDLE);
388   objs[1] = hThread;
389
390   for(;;) {
391       TRACE_(int)("waiting for action\n");
392       waitret = MsgWaitForMultipleObjects(2, objs, FALSE, INFINITE, QS_ALLINPUT);
393       if (waitret == WAIT_OBJECT_0) {
394           DOSVM_ProcessConsole();
395       }
396       else if (waitret == WAIT_OBJECT_0 + 1) {
397          DWORD rv;
398          if(!GetExitCodeThread(hThread, &rv)) {
399              ERR("Failed to get thread exit code!\n");
400              rv = 0;
401          }
402          return rv;
403       }
404       else if (waitret == WAIT_OBJECT_0 + 2) {
405           while (PeekMessageA(&msg,0,0,0,PM_REMOVE)) {
406               if (msg.hwnd) {
407                   /* it's a window message */
408                   DOSVM_ProcessMessage(&msg);
409                   DispatchMessageA(&msg);
410               } else {
411                   /* it's a thread message */
412                   switch (msg.message) {
413                   case WM_QUIT:
414                       /* stop this madness!! */
415                       return 0;
416                   case WM_USER:
417                       /* run passed procedure in this thread */
418                       /* (sort of like APC, but we signal the completion) */
419                       {
420                           DOS_SPC *spc = (DOS_SPC *)msg.lParam;
421                           TRACE_(int)("calling %p with arg %08lx\n", spc->proc, spc->arg);
422                           (spc->proc)(spc->arg);
423                           TRACE_(int)("done, signalling event %x\n", msg.wParam);
424                           SetEvent( (HANDLE)msg.wParam );
425                       }
426                       break;
427                   }
428               }
429           }
430       }
431       else
432       {
433           ERR_(int)("MsgWaitForMultipleObjects returned unexpected value.\n");
434           return 0;
435       }
436   }
437 }
438
439 static WINE_EXCEPTION_FILTER(exception_handler)
440 {
441   EXCEPTION_RECORD *rec = GetExceptionInformation()->ExceptionRecord;
442   CONTEXT *context = GetExceptionInformation()->ContextRecord;
443   int ret, arg = rec->ExceptionInformation[0];
444
445   switch(rec->ExceptionCode) {
446   case EXCEPTION_VM86_INTx:
447     if (TRACE_ON(relay)) {
448       DPRINTF("Call DOS int 0x%02x ret=%04lx:%04lx\n",
449               arg, context->SegCs, context->Eip );
450       DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
451               context->Eax, context->Ebx, context->Ecx, context->Edx,
452               context->Esi, context->Edi );
453       DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
454               context->Ebp, context->Esp, context->SegDs, context->SegEs,
455               context->SegFs, context->SegGs, context->EFlags );
456       }
457     ret = DOSVM_SimulateInt(arg, context, FALSE);
458     if (TRACE_ON(relay)) {
459       DPRINTF("Ret  DOS int 0x%02x ret=%04lx:%04lx\n",
460               arg, context->SegCs, context->Eip );
461       DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
462               context->Eax, context->Ebx, context->Ecx, context->Edx,
463               context->Esi, context->Edi );
464       DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
465               context->Ebp, context->Esp, context->SegDs, context->SegEs,
466               context->SegFs, context->SegGs, context->EFlags );
467     }
468     return ret ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_EXECUTION;
469
470   case EXCEPTION_VM86_STI:
471   /* case EXCEPTION_VM86_PICRETURN: */
472     IF_SET(context);
473     EnterCriticalSection(&qcrit);
474     sig_sent++;
475     while (NtCurrentTeb()->alarms) {
476       DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
477       /* hmm, instead of relying on this signal counter, we should
478        * probably check how many ticks have *really* passed, probably using
479        * QueryPerformanceCounter() or something like that */
480       InterlockedDecrement(&(NtCurrentTeb()->alarms));
481     }
482     TRACE_(int)("context=%p, current=%p\n", context, current_context);
483     TRACE_(int)("cs:ip=%04lx:%04lx, ss:sp=%04lx:%04lx\n", context->SegCs, context->Eip, context->SegSs, context->Esp);
484     if (!ISV86(context)) {
485       ERR_(int)("@#&*%%, winedos signal handling is *still* messed up\n");
486     }
487     TRACE_(int)("DOS task enabled interrupts %s events pending, sending events (time=%ld)\n", IS_PEND(context)?"with":"without", GetTickCount());
488     DOSVM_SendQueuedEvents(context);
489     sig_sent=0;
490     LeaveCriticalSection(&qcrit);
491     return EXCEPTION_CONTINUE_EXECUTION;
492   }
493   return EXCEPTION_CONTINUE_SEARCH;
494 }
495
496 int WINAPI DOSVM_Enter( CONTEXT86 *context )
497 {
498   CONTEXT86 *old_context = current_context;
499
500   current_context = context;
501   __TRY
502   {
503     __wine_enter_vm86( context );
504     TRACE_(module)( "vm86 returned: %s\n", strerror(errno) );
505   }
506   __EXCEPT(exception_handler)
507   {
508     TRACE_(module)( "leaving vm86 mode\n" );
509   }
510   __ENDTRY
511   current_context = old_context;
512   return 0;
513 }
514
515 /***********************************************************************
516  *              OutPIC (WINEDOS.@)
517  */
518 void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val)
519 {
520     LPDOSEVENT event;
521
522     if ((port==0x20) && (val==0x20)) {
523       EnterCriticalSection(&qcrit);
524       if (current_event) {
525         /* EOI (End Of Interrupt) */
526         TRACE("received EOI for current IRQ, clearing\n");
527         event = current_event;
528         current_event = event->next;
529         if (event->relay)
530         (*event->relay)(NULL,event->data);
531         free(event);
532
533         if (pending_event) {
534           /* another event is pending, which we should probably
535            * be able to process now */
536           TRACE("another event pending, setting flag\n");
537           current_context->EFlags |= VIP_MASK;
538         }
539       } else {
540         WARN("EOI without active IRQ\n");
541       }
542       LeaveCriticalSection(&qcrit);
543     } else {
544       FIXME("unrecognized PIC command %02x\n",val);
545     }
546 }
547
548 /***********************************************************************
549  *              SetTimer (WINEDOS.@)
550  */
551 void WINAPI DOSVM_SetTimer( UINT ticks )
552 {
553   struct itimerval tim;
554
555   if (dosvm_pid) {
556     /* the PC clocks ticks at 1193180 Hz */
557     tim.it_interval.tv_sec=0;
558     tim.it_interval.tv_usec=MulDiv(ticks,1000000,1193180);
559     /* sanity check */
560     if (!tim.it_interval.tv_usec) tim.it_interval.tv_usec=1;
561     /* first tick value */
562     tim.it_value = tim.it_interval;
563     TRACE_(int)("setting timer tick delay to %ld us\n", tim.it_interval.tv_usec);
564     setitimer(ITIMER_REAL, &tim, NULL);
565   }
566 }
567
568 /***********************************************************************
569  *              GetTimer (WINEDOS.@)
570  */
571 UINT WINAPI DOSVM_GetTimer( void )
572 {
573   struct itimerval tim;
574
575   if (dosvm_pid) {
576     getitimer(ITIMER_REAL, &tim);
577     return MulDiv(tim.it_value.tv_usec,1193180,1000000);
578   }
579   return 0;
580 }
581
582 #else /* !MZ_SUPPORTED */
583
584 /***********************************************************************
585  *              Enter (WINEDOS.@)
586  */
587 INT WINAPI DOSVM_Enter( CONTEXT86 *context )
588 {
589  ERR_(module)("DOS realmode not supported on this architecture!\n");
590  return -1;
591 }
592
593 /***********************************************************************
594  *              Wait (WINEDOS.@)
595  */
596 void WINAPI DOSVM_Wait( INT read_pipe, HANDLE hObject) {}
597
598 /***********************************************************************
599  *              OutPIC (WINEDOS.@)
600  */
601 void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
602
603 /***********************************************************************
604  *              SetTimer (WINEDOS.@)
605  */
606 void WINAPI DOSVM_SetTimer( UINT ticks ) {}
607
608 /***********************************************************************
609  *              GetTimer (WINEDOS.@)
610  */
611 UINT WINAPI DOSVM_GetTimer( void ) { return 0; }
612
613 /***********************************************************************
614  *              QueueEvent (WINEDOS.@)
615  */
616 void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
617 {
618   if (irq<0) {
619     /* callback event, perform it with dummy context */
620     CONTEXT86 context;
621     memset(&context,0,sizeof(context));
622     (*relay)(&context,data);
623   } else {
624     ERR("IRQ without DOS task: should not happen\n");
625   }
626 }
627
628 #endif
629
630 /**********************************************************************
631  *          DOSVM_RealModeInterrupt
632  *
633  * Handle real mode interrupts
634  */
635 void DOSVM_RealModeInterrupt( BYTE intnum, CONTEXT86 *context )
636 {
637     INTPROC proc = DOSVM_GetBuiltinHandler( intnum );
638     proc(context);
639 }
640
641
642 /**********************************************************************
643  *          DllMain  (DOSVM.Init)
644  */
645 BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
646 {
647     TRACE_(module)("(%p,%ld,%p)\n", hinstDLL, fdwReason, lpvReserved);
648
649     if (fdwReason == DLL_PROCESS_ATTACH)
650     {
651         DOSVM_InitSegments();
652
653 #ifdef MZ_SUPPORTED
654         event_notifier = CreateEventA(NULL, FALSE, FALSE, NULL);
655         if(!event_notifier)
656           ERR("Failed to create event object!\n");
657 #endif
658
659     }
660     return TRUE;
661 }