Add support for winevent hooks.

diff --git a/dlls/ttydrv/wnd.c b/dlls/ttydrv/wnd.c
index a9f4e58..bc07de1 100644
--- a/dlls/ttydrv/wnd.c
+++ b/dlls/ttydrv/wnd.c
@@ -186,6 +186,7 @@
         ret = SendMessageA( hwnd, WM_NCCREATE, 0, (LPARAM)cs );
         if (ret) ret = (SendMessageA( hwnd, WM_CREATE, 0, (LPARAM)cs ) != -1);
     }
+    if (ret) NotifyWinEvent(EVENT_OBJECT_CREATE, hwnd, OBJID_WINDOW, 0);
     return ret;
 }
 
diff --git a/dlls/user/hook.c b/dlls/user/hook.c
index 847849f..d074305 100644
--- a/dlls/user/hook.c
+++ b/dlls/user/hook.c
@@ -2,6 +2,7 @@
  * Windows hook functions
  *
  * Copyright 2002 Alexandre Julliard
+ * Copyright 2005 Dmitry Timoshkov
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -65,6 +66,7 @@
 #include "wine/port.h"
 
 #include <stdarg.h>
+#include <assert.h>
 
 #include "windef.h"
 #include "winbase.h"
@@ -81,7 +83,17 @@
 WINE_DEFAULT_DEBUG_CHANNEL(hook);
 WINE_DECLARE_DEBUG_CHANNEL(relay);
 
-static const char * const hook_names[WH_MAXHOOK - WH_MINHOOK + 1] =
+struct hook_info
+{
+    FARPROC proc;
+    void *handle;
+    DWORD tid;
+    WCHAR module[MAX_PATH];
+};
+
+#define WH_WINEVENT (WH_MAXHOOK+1)
+
+static const char * const hook_names[WH_WINEVENT - WH_MINHOOK + 1] =
 {
     "WH_MSGFILTER",
     "WH_JOURNALRECORD",
@@ -98,7 +110,8 @@
     "WH_FOREGROUNDIDLE",
     "WH_CALLWNDPROCRET",
     "WH_KEYBOARD_LL",
-    "WH_MOUSE_LL"
+    "WH_MOUSE_LL",
+    "WH_WINEVENT"
 };
 
 
@@ -130,6 +143,13 @@
         return 0;
     }
 
+    if (!proc)
+    {
+        SetLastError( ERROR_INVALID_FILTER_PROC );
+        return 0;
+    }
+
+    /* FIXME: what if the tid belongs to another process? */
     if (tid)  /* thread-local hook */
     {
         if (id == WH_JOURNALRECORD ||
@@ -156,9 +176,13 @@
 
     SERVER_START_REQ( set_hook )
     {
-        req->id      = id;
-        req->tid     = tid;
-        req->unicode = unicode;
+        req->id        = id;
+        req->pid       = 0;
+        req->tid       = tid;
+        req->event_min = EVENT_MIN;
+        req->event_max = EVENT_MAX;
+        req->flags     = WINEVENT_INCONTEXT;
+        req->unicode   = unicode;
         if (inst) /* make proc relative to the module base */
         {
             req->proc = (void *)((char *)proc - (char *)inst);
@@ -281,7 +305,7 @@
  *
  * Retrieve the hook procedure real value for a module-relative proc
  */
-static HOOKPROC get_hook_proc( HOOKPROC proc, const WCHAR *module )
+static void *get_hook_proc( void *proc, const WCHAR *module )
 {
     HMODULE mod;
 
@@ -291,7 +315,7 @@
         /* FIXME: the library will never be freed */
         if (!(mod = LoadLibraryW(module))) return NULL;
     }
-    return (HOOKPROC)((char *)mod + (ULONG_PTR)proc);
+    return (char *)mod + (ULONG_PTR)proc;
 }
 
 
@@ -312,6 +336,7 @@
     SERVER_START_REQ( start_hook_chain )
     {
         req->id = id;
+        req->event = EVENT_MIN;
         wine_server_set_reply( req, module, sizeof(module)-sizeof(WCHAR) );
         if (!wine_server_call( req ))
         {
@@ -340,6 +365,10 @@
             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_MOUSE_LL_HOOK, wparam, lparam,
                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
             break;
+        default:
+            ERR("Unknown hook id %d\n", id);
+            assert(0);
+            break;
         }
     }
     else if (proc)
@@ -431,6 +460,7 @@
         ret = !wine_server_call_err( req );
     }
     SERVER_END_REQ;
+    if (!ret && GetLastError() == ERROR_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE );
     return ret;
 }
 
@@ -448,9 +478,11 @@
     SERVER_START_REQ( remove_hook )
     {
         req->handle = hhook;
+        req->id     = 0;
         ret = !wine_server_call_err( req );
     }
     SERVER_END_REQ;
+    if (!ret && GetLastError() == ERROR_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE );
     return ret;
 }
 
@@ -474,6 +506,7 @@
     SERVER_START_REQ( get_next_hook )
     {
         req->handle = queue->hook;
+        req->event = EVENT_MIN;
         wine_server_set_reply( req, module, sizeof(module)-sizeof(WCHAR) );
         if (!wine_server_call_err( req ))
         {
@@ -504,6 +537,10 @@
             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_MOUSE_LL_HOOK, wparam, lparam,
                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
             break;
+        default:
+            ERR("Unknown hook id %d\n", id);
+            assert(0);
+            break;
         }
     }
     else if (proc)
@@ -549,28 +586,73 @@
  * Set up an event hook for a set of events.
  *
  * PARAMS
- *  dwMin     [I] Lowest event handled by pfnProc
- *  dwMax     [I] Highest event handled by pfnProc
- *  hModule   [I] DLL containing pfnProc
- *  pfnProc   [I] Callback event hook function
- *  dwProcess [I] Process to get events from, or 0 for all processes
- *  dwThread  [I] Thread to get events from, or 0 for all threads
- *  dwFlags   [I] Flags indicating the status of pfnProc
+ *  event_min [I] Lowest event handled by pfnProc
+ *  event_max [I] Highest event handled by pfnProc
+ *  inst      [I] DLL containing pfnProc
+ *  proc      [I] Callback event hook function
+ *  pid       [I] Process to get events from, or 0 for all processes
+ *  tid       [I] Thread to get events from, or 0 for all threads
+ *  flags     [I] Flags indicating the status of pfnProc
  *
  * RETURNS
  *  Success: A handle representing the hook.
  *  Failure: A NULL handle.
- *
- * BUGS
- *  Not implemented.
  */
-HWINEVENTHOOK WINAPI SetWinEventHook(DWORD dwMin, DWORD dwMax, HMODULE hModule,
-                                     WINEVENTPROC pfnProc, DWORD dwProcess,
-                                     DWORD dwThread, DWORD dwFlags)
+HWINEVENTHOOK WINAPI SetWinEventHook(DWORD event_min, DWORD event_max,
+                                     HMODULE inst, WINEVENTPROC proc,
+                                     DWORD pid, DWORD tid, DWORD flags)
 {
-    FIXME("(%ld,%ld,%p,%p,%ld,%ld,0x%08lx)-stub!\n", dwMin, dwMax, hModule,
-          pfnProc, dwProcess, dwThread, dwFlags);
-    return 0;
+    HWINEVENTHOOK handle = 0;
+    WCHAR module[MAX_PATH];
+    DWORD len;
+
+    TRACE("%ld,%ld,%p,%p,%08lx,%04lx,%08lx\n", event_min, event_max, inst,
+          proc, pid, tid, flags);
+
+    if (inst)
+    {
+        if (!(len = GetModuleFileNameW(inst, module, MAX_PATH)) || len >= MAX_PATH)
+            inst = 0;
+    }
+
+    if ((flags & WINEVENT_INCONTEXT) && !inst)
+    {
+        SetLastError(ERROR_HOOK_NEEDS_HMOD);
+        return 0;
+    }
+
+    if (event_min > event_max)
+    {
+        SetLastError(ERROR_INVALID_HOOK_FILTER);
+        return 0;
+    }
+
+    /* FIXME: what if the tid or pid belongs to another process? */
+    if (tid)  /* thread-local hook */
+        inst = 0;
+
+    SERVER_START_REQ( set_hook )
+    {
+        req->id        = WH_WINEVENT;
+        req->pid       = pid;
+        req->tid       = tid;
+        req->event_min = event_min;
+        req->event_max = event_max;
+        req->flags     = flags;
+        req->unicode   = 1;
+        if (inst) /* make proc relative to the module base */
+        {
+            req->proc = (void *)((char *)proc - (char *)inst);
+            wine_server_add_data( req, module, strlenW(module) * sizeof(WCHAR) );
+        }
+        else req->proc = proc;
+
+        if (!wine_server_call_err( req )) handle = reply->handle;
+    }
+    SERVER_END_REQ;
+
+    TRACE("-> %p\n", handle);
+    return handle;
 }
 
 
@@ -585,17 +667,84 @@
  * RETURNS
  *  Success: TRUE. The event hook has been removed.
  *  Failure: FALSE, if hEventHook is invalid.
- *
- * BUGS
- *  Not implemented.
  */
 BOOL WINAPI UnhookWinEvent(HWINEVENTHOOK hEventHook)
 {
-    FIXME("(%p)-stub!\n", hEventHook);
+    BOOL ret;
 
-    return (hEventHook != 0);
+    TRACE( "%p\n", hEventHook );
+
+    SERVER_START_REQ( remove_hook )
+    {
+        req->handle = hEventHook;
+        req->id     = WH_WINEVENT;
+        ret = !wine_server_call_err( req );
+    }
+    SERVER_END_REQ;
+    return ret;
 }
 
+inline static BOOL find_first_hook(DWORD id, DWORD event, HWND hwnd, LONG object_id,
+                                   LONG child_id, struct hook_info *info)
+{
+    BOOL ret;
+
+    SERVER_START_REQ( start_hook_chain )
+    {
+        req->id = id;
+        req->event = event;
+        req->window = hwnd;
+        req->object_id = object_id;
+        req->child_id = child_id;
+        wine_server_set_reply( req, info->module, sizeof(info->module)-sizeof(WCHAR) );
+        ret = !wine_server_call( req );
+        if (ret)
+        {
+            info->module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
+            info->handle    = reply->handle;
+            info->proc      = reply->proc;
+            info->tid       = reply->tid;
+        }
+    }
+    SERVER_END_REQ;
+    return ret && (info->tid || info->proc);
+}
+
+inline static BOOL find_next_hook(DWORD event, HWND hwnd, LONG object_id,
+                                  LONG child_id, struct hook_info *info)
+{
+    BOOL ret;
+
+    SERVER_START_REQ( get_next_hook )
+    {
+        req->handle = info->handle;
+        req->event = event;
+        req->window = hwnd;
+        req->object_id = object_id;
+        req->child_id = child_id;
+        wine_server_set_reply( req, info->module, sizeof(info->module)-sizeof(WCHAR) );
+        ret = !wine_server_call( req );
+        if (ret)
+        {
+            info->module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
+            info->handle    = reply->next;
+            info->proc      = reply->proc;
+            info->tid       = reply->tid;
+        }
+    }
+    SERVER_END_REQ;
+    return ret;
+}
+
+inline static void find_hook_close(DWORD id)
+{
+    SERVER_START_REQ( finish_hook_chain )
+    {
+        req->id = id;
+        wine_server_call( req );
+    }
+    SERVER_END_REQ;
+}
 
 /***********************************************************************
  *           NotifyWinEvent                             [USER32.@]
@@ -603,20 +752,81 @@
  * Inform the OS that an event has occurred.
  *
  * PARAMS
- *  dwEvent  [I] Id of the event
- *  hWnd     [I] Window holding the object that created the event
- *  nId      [I] Type of object that created the event
- *  nChildId [I] Child object of nId, or CHILDID_SELF.
+ *  event     [I] Id of the event
+ *  hwnd      [I] Window holding the object that created the event
+ *  object_id [I] Type of object that created the event
+ *  child_id  [I] Child object of nId, or CHILDID_SELF.
  *
  * RETURNS
  *  Nothing.
- *
- * BUGS
- *  Not implemented.
  */
-void WINAPI NotifyWinEvent(DWORD dwEvent, HWND hWnd, LONG nId, LONG nChildId)
+void WINAPI NotifyWinEvent(DWORD event, HWND hwnd, LONG object_id, LONG child_id)
 {
-    FIXME("(%ld,%p,%ld,%ld)-stub!\n", dwEvent, hWnd, nId, nChildId);
+    struct hook_info info;
+
+    TRACE("%04lx,%p,%ld,%ld\n", event, hwnd, object_id, child_id);
+
+    if (!hwnd)
+    {
+        SetLastError(ERROR_INVALID_WINDOW_HANDLE);
+        return;
+    }
+
+#if 0
+    if (event & 0x80000000)
+    {
+        /* FIXME: on 64-bit platforms we need to invent some other way for
+         * passing parameters, nId and nChildId can't hold full [W|L]PARAM.
+         * struct call_hook *hook = (LRESULT *)hWnd;
+         * wparam = hook->wparam;
+         * lparam = hook->lparam;
+         */
+        LRESULT *ret = (LRESULT *)hwnd;
+        INT id, code, unicode;
+
+        id = (dwEvent & 0x7fff0000) >> 16;
+        code = event & 0x7fff;
+        unicode = event & 0x8000;
+        *ret = HOOK_CallHooks(id, code, object_id, child_id, unicode);
+        return;
+    }
+#endif
+
+    if (!find_first_hook(WH_WINEVENT, event, hwnd, object_id, child_id, &info)) return;
+
+    do
+    {
+        if (info.proc)
+        {
+            TRACE( "calling WH_WINEVENT hook %p event %lx hwnd %p %lx %lx module %s\n",
+                   info.proc, event, hwnd, object_id, child_id, debugstr_w(info.module) );
+
+            if (!info.module[0] || (info.proc = get_hook_proc( info.proc, info.module )) != NULL)
+            {
+                int locks = WIN_SuspendWndsLock();
+
+                if (TRACE_ON(relay))
+                    DPRINTF( "%04lx:Call winevent hook proc %p (hhook=%p,event=%lx,hwnd=%p,object_id=%lx,child_id=%lx,tid=%04lx,time=%lx)\n",
+                             GetCurrentThreadId(), info.proc, info.handle, event, hwnd, object_id,
+                             child_id, GetCurrentThreadId(), GetCurrentTime());
+
+                info.proc(info.handle, event, hwnd, object_id, child_id,
+                          GetCurrentThreadId(), GetCurrentTime());
+
+                if (TRACE_ON(relay))
+                    DPRINTF( "%04lx:Ret  winevent hook proc %p (hhook=%p,event=%lx,hwnd=%p,object_id=%lx,child_id=%lx,tid=%04lx,time=%lx)\n",
+                             GetCurrentThreadId(), info.proc, info.handle, event, hwnd, object_id,
+                             child_id, GetCurrentThreadId(), GetCurrentTime());
+
+                WIN_RestoreWndsLock( locks );
+            }
+        }
+        else
+            break;
+    }
+    while (find_next_hook(event, hwnd, object_id, child_id, &info));
+
+    find_hook_close(WH_WINEVENT);
 }
 
 
diff --git a/dlls/user/message.c b/dlls/user/message.c
index eb43c86..f74bc77 100644
--- a/dlls/user/message.c
+++ b/dlls/user/message.c
@@ -64,6 +64,8 @@
     enum message_type type;
     MSG               msg;
     UINT              flags;  /* InSendMessageEx return flags */
+    HWINEVENTHOOK     hook;   /* winevent hook handle */
+    WINEVENTPROC      hook_proc; /* winevent hook proc address */
 };
 
 /* structure to group all parameters for sent messages of the various kinds */
@@ -1542,6 +1544,25 @@
 
 
 /***********************************************************************
+ *		get_hook_proc
+ *
+ * Retrieve the hook procedure real value for a module-relative proc
+ */
+static void *get_hook_proc( void *proc, const WCHAR *module )
+{
+    HMODULE mod;
+
+    if (!(mod = GetModuleHandleW(module)))
+    {
+        TRACE( "loading %s\n", debugstr_w(module) );
+        /* FIXME: the library will never be freed */
+        if (!(mod = LoadLibraryW(module))) return NULL;
+    }
+    return (char *)mod + (ULONG_PTR)proc;
+}
+
+
+/***********************************************************************
  *           MSG_peek_message
  *
  * Peek for a message matching the given parameters. Return FALSE if none available.
@@ -1584,6 +1605,8 @@
                     info.msg.time    = reply->time;
                     info.msg.pt.x    = reply->x;
                     info.msg.pt.y    = reply->y;
+                    info.hook        = reply->hook;
+                    info.hook_proc   = reply->hook_proc;
                     extra_info       = reply->info;
                 }
                 else
@@ -1598,7 +1621,8 @@
         if (res) return FALSE;
 
         TRACE( "got type %d msg %x (%s) hwnd %p wp %x lp %lx\n",
-               info.type, info.msg.message, SPY_GetMsgName(info.msg.message, info.msg.hwnd),
+               info.type, info.msg.message,
+               (info.type == MSG_WINEVENT) ? "MSG_WINEVENT" : SPY_GetMsgName(info.msg.message, info.msg.hwnd),
                info.msg.hwnd, info.msg.wParam, info.msg.lParam );
 
         switch(info.type)
@@ -1617,6 +1641,34 @@
             call_sendmsg_callback( (SENDASYNCPROC)info.msg.wParam, info.msg.hwnd,
                                    info.msg.message, extra_info, info.msg.lParam );
             goto next;
+        case MSG_WINEVENT:
+            if (size)
+            {
+                WCHAR module[MAX_PATH];
+                size = min( size, (MAX_PATH - 1) * sizeof(WCHAR) );
+                memcpy( module, buffer, size );
+                module[size / sizeof(WCHAR)] = 0;
+                if (!(info.hook_proc = get_hook_proc( info.hook_proc, module )))
+                {
+                    ERR( "invalid winevent hook module name %s\n", debugstr_w(module) );
+                    goto next;
+                }
+            }
+            if (TRACE_ON(relay))
+                DPRINTF( "%04lx:Call winevent proc %p (hook=%p,event=%x,hwnd=%p,object_id=%x,child_id=%lx,tid=%04lx,time=%lx)\n",
+                         GetCurrentThreadId(), info.hook_proc,
+                         info.hook, info.msg.message, info.msg.hwnd, info.msg.wParam,
+                         info.msg.lParam, extra_info, info.msg.time);
+
+            info.hook_proc( info.hook, info.msg.message, info.msg.hwnd, info.msg.wParam,
+                            info.msg.lParam, extra_info, info.msg.time );
+
+            if (TRACE_ON(relay))
+                DPRINTF( "%04lx:Ret  winevent proc %p (hook=%p,event=%x,hwnd=%p,object_id=%x,child_id=%lx,tid=%04lx,time=%lx)\n",
+                         GetCurrentThreadId(), info.hook_proc,
+                         info.hook, info.msg.message, info.msg.hwnd, info.msg.wParam,
+                         info.msg.lParam, extra_info, info.msg.time);
+            goto next;
         case MSG_OTHER_PROCESS:
             info.flags = ISMEX_SEND;
             if (!unpack_message( info.msg.hwnd, info.msg.message, &info.msg.wParam,
diff --git a/dlls/user/tests/msg.c b/dlls/user/tests/msg.c
index 6f3c40c..3fa8d57 100644
--- a/dlls/user/tests/msg.c
+++ b/dlls/user/tests/msg.c
@@ -4978,8 +4978,8 @@
     HWINEVENTHOOK hhook;
     const struct message *events = WmWinEventsSeq;
     HMODULE user32 = GetModuleHandleA("user32.dll");
-    FARPROC pSetWinEventHook = 0;/*GetProcAddress(user32, "SetWinEventHook");*/
-    FARPROC pUnhookWinEvent = 0;/*GetProcAddress(user32, "UnhookWinEvent");*/
+    FARPROC pSetWinEventHook = GetProcAddress(user32, "SetWinEventHook");
+    FARPROC pUnhookWinEvent = GetProcAddress(user32, "UnhookWinEvent");
     FARPROC pNotifyWinEvent = GetProcAddress(user32, "NotifyWinEvent");
 
     hwnd = CreateWindowExA(0, "TestWindowClass", NULL,
@@ -5134,7 +5134,7 @@
     HHOOK hhook;
     HWINEVENTHOOK hwinevent_hook;
     HMODULE user32 = GetModuleHandleA("user32.dll");
-    FARPROC pSetWinEventHook = 0;/*GetProcAddress(user32, "SetWinEventHook");*/
+    FARPROC pSetWinEventHook = GetProcAddress(user32, "SetWinEventHook");
     FARPROC pUnhookWinEvent = GetProcAddress(user32, "UnhookWinEvent");
 
     hhook = SetWindowsHookExA(WH_CBT, cbt_hook_proc, GetModuleHandleA(0), GetCurrentThreadId());
@@ -5208,12 +5208,16 @@
     ok(GetLastError() == 0xdeadbeef, "unexpected error %ld\n", GetLastError());
     ok(pUnhookWinEvent(hwinevent_hook), "UnhookWinEvent error %ld\n", GetLastError());
 
+todo_wine {
+    /* This call succeeds under win2k SP4, but fails under Wine.
+       Does win2k test/use passed process id? */
     SetLastError(0xdeadbeef);
     hwinevent_hook = (HWINEVENTHOOK)pSetWinEventHook(EVENT_MIN, EVENT_MAX,
 	0, win_event_proc, 0xdeadbeef, 0, WINEVENT_OUTOFCONTEXT);
     ok(hwinevent_hook != 0, "SetWinEventHook error %ld\n", GetLastError());
     ok(GetLastError() == 0xdeadbeef, "unexpected error %ld\n", GetLastError());
     ok(pUnhookWinEvent(hwinevent_hook), "UnhookWinEvent error %ld\n", GetLastError());
+}
 
     SetLastError(0xdeadbeef);
     ok(!pUnhookWinEvent((HWINEVENTHOOK)0xdeadbeef), "UnhookWinEvent succeeded\n");
@@ -5225,9 +5229,9 @@
 START_TEST(msg)
 {
     HMODULE user32 = GetModuleHandleA("user32.dll");
-    FARPROC pSetWinEventHook = 0;/*GetProcAddress(user32, "SetWinEventHook");*/
-    FARPROC pUnhookWinEvent = 0;/*GetProcAddress(user32, "UnhookWinEvent");*/
-    FARPROC pIsWinEventHookInstalled = GetProcAddress(user32, "IsWinEventHookInstalled");
+    FARPROC pSetWinEventHook = GetProcAddress(user32, "SetWinEventHook");
+    FARPROC pUnhookWinEvent = GetProcAddress(user32, "UnhookWinEvent");
+    FARPROC pIsWinEventHookInstalled = 0;/*GetProcAddress(user32, "IsWinEventHookInstalled");*/
 
     if (!RegisterWindowClasses()) assert(0);
 
@@ -5247,17 +5251,19 @@
 	    for (event = EVENT_MIN; event <= EVENT_MAX; event++)
 		ok(pIsWinEventHookInstalled(event), "IsWinEventHookInstalled(%u) failed\n", event);
 	}
-
-        /* Fix message sequences before removing 3 lines below */
-        ok(pUnhookWinEvent(hEvent_hook), "UnhookWinEvent error %ld\n", GetLastError());
-        pUnhookWinEvent = 0;
-        hEvent_hook = 0;
     }
 
     cbt_hook_thread_id = GetCurrentThreadId();
     hCBT_hook = SetWindowsHookExA(WH_CBT, cbt_hook_proc, 0, GetCurrentThreadId());
     assert(hCBT_hook);
 
+    test_winevents();
+
+    /* Fix message sequences before removing 3 lines below */
+    ok(pUnhookWinEvent(hEvent_hook), "UnhookWinEvent error %ld\n", GetLastError());
+    pUnhookWinEvent = 0;
+    hEvent_hook = 0;
+
     test_messages();
     test_mdi_messages();
     test_button_messages();
@@ -5267,7 +5273,6 @@
     test_accelerators();
     test_timers();
     test_set_hook();
-    test_winevents();
 
     UnhookWindowsHookEx(hCBT_hook);
     if (pUnhookWinEvent)
diff --git a/dlls/x11drv/window.c b/dlls/x11drv/window.c
index 1c3dd8d..caaab28 100644
--- a/dlls/x11drv/window.c
+++ b/dlls/x11drv/window.c
@@ -1005,6 +1005,8 @@
         return FALSE;
     }
 
+    NotifyWinEvent(EVENT_OBJECT_CREATE, hwnd, OBJID_WINDOW, 0);
+
     /* Send the size messages */
 
     if (!(wndPtr = WIN_GetPtr(hwnd)) || wndPtr == WND_OTHER_PROCESS) return FALSE;
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h
index 9719654..b375e0b 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -2142,7 +2142,8 @@
     MSG_CALLBACK_RESULT,
     MSG_OTHER_PROCESS,
     MSG_POSTED,
-    MSG_HARDWARE
+    MSG_HARDWARE,
+    MSG_WINEVENT
 };
 #define SEND_MSG_ABORT_IF_HUNG  0x01
 
@@ -2166,6 +2167,8 @@
     unsigned int    lparam;
     int             x;
     int             y;
+    user_handle_t   hook;
+    void*           hook_proc;
     unsigned int    time;
     unsigned int    info;
     size_t          total;
@@ -2948,7 +2951,11 @@
 {
     struct request_header __header;
     int            id;
+    process_id_t   pid;
     thread_id_t    tid;
+    int            event_min;
+    int            event_max;
+    int            flags;
     void*          proc;
     int            unicode;
     /* VARARG(module,unicode_str); */
@@ -2979,6 +2986,10 @@
 {
     struct request_header __header;
     int            id;
+    int            event;
+    user_handle_t  window;
+    int            object_id;
+    int            child_id;
 };
 struct start_hook_chain_reply
 {
@@ -3009,6 +3020,10 @@
 {
     struct request_header __header;
     user_handle_t  handle;
+    int            event;
+    user_handle_t  window;
+    int            object_id;
+    int            child_id;
 };
 struct get_next_hook_reply
 {
@@ -3712,6 +3727,6 @@
     struct set_global_windows_reply set_global_windows_reply;
 };
 
-#define SERVER_PROTOCOL_VERSION 155
+#define SERVER_PROTOCOL_VERSION 156
 
 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */
diff --git a/server/hook.c b/server/hook.c
index b269114..293b20c 100644
--- a/server/hook.c
+++ b/server/hook.c
@@ -2,6 +2,7 @@
  * Server-side window hooks support
  *
  * Copyright (C) 2002 Alexandre Julliard
+ * Copyright (C) 2005 Dmitry Timoshkov
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -40,15 +41,22 @@
 {
     struct list         chain;    /* hook chain entry */
     user_handle_t       handle;   /* user handle for this hook */
-    struct thread      *thread;   /* thread owning the hook */
+    struct process     *process;  /* process the hook is set to */
+    struct thread      *thread;   /* thread the hook is set to */
+    struct thread      *owner;    /* owner of the out of context hook */
     int                 index;    /* hook table index */
+    int                 event_min;
+    int                 event_max;
+    int                 flags;
     void               *proc;     /* hook function */
     int                 unicode;  /* is it a unicode hook? */
     WCHAR              *module;   /* module name for global hooks */
     size_t              module_size;
 };
 
-#define NB_HOOKS (WH_MAXHOOK-WH_MINHOOK+1)
+#define WH_WINEVENT (WH_MAXHOOK+1)
+
+#define NB_HOOKS (WH_WINEVENT-WH_MINHOOK+1)
 #define HOOK_ENTRY(p)  LIST_ENTRY( (p), struct hook, chain )
 
 struct hook_table
@@ -124,6 +132,8 @@
     free_user_handle( hook->handle );
     if (hook->module) free( hook->module );
     if (hook->thread) release_object( hook->thread );
+    if (hook->process) release_object( hook->process );
+    release_object( hook->owner );
     list_remove( &hook->chain );
     free( hook );
 }
@@ -162,27 +172,68 @@
 }
 
 /* find the first non-deleted hook in the chain */
-inline static struct hook *get_first_valid_hook( struct hook_table *table, int index )
+inline static struct hook *get_first_valid_hook( struct hook_table *table, int index,
+                                                 int event, user_handle_t win,
+                                                 int object_id, int child_id )
 {
     struct hook *hook = get_first_hook( table, index );
-    while (hook && !hook->proc)
+
+    while (hook)
+    {
+        if ((!hook->process || hook->process == current->process) &&
+            (!(hook->flags & WINEVENT_SKIPOWNPROCESS) || hook->process != current->process))
+        {
+            if ((!hook->thread || hook->thread == current) &&
+                (!(hook->flags & WINEVENT_SKIPOWNTHREAD) || hook->thread != current))
+            {
+                if (hook->proc && event >= hook->event_min && event <= hook->event_max)
+                {
+                    if (hook->flags & WINEVENT_INCONTEXT) return hook;
+
+                    /* only winevent hooks may be out of context */
+                    assert(hook->index + WH_MINHOOK == WH_WINEVENT);
+                    post_win_event( hook->owner, event, win, object_id, child_id,
+                                    hook->proc, hook->module, hook->module_size,
+                                    hook->handle );
+                }
+            }
+        }
         hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) );
+    }
     return hook;
 }
 
 /* find the next hook in the chain, skipping the deleted ones */
-static struct hook *get_next_hook( struct hook *hook )
+static struct hook *get_next_hook( struct hook *hook, int event, user_handle_t win,
+                                   int object_id, int child_id )
 {
     struct hook_table *table = get_table( hook );
     int index = hook->index;
 
     while ((hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) )))
     {
-        if (hook->proc) return hook;
+        if ((!hook->process || hook->process == current->process) &&
+            (!(hook->flags & WINEVENT_SKIPOWNPROCESS) || hook->process != current->process))
+        {
+            if ((!hook->thread || hook->thread == current) &&
+                (!(hook->flags & WINEVENT_SKIPOWNTHREAD) || hook->thread != current))
+            {
+                if (hook->proc && event >= hook->event_min && event <= hook->event_max)
+                {
+                    if (hook->flags & WINEVENT_INCONTEXT) return hook;
+
+                    /* only winevent hooks may be out of context */
+                    assert(hook->index + WH_MINHOOK == WH_WINEVENT);
+                    post_win_event( hook->owner, event, win, object_id, child_id,
+                                    hook->proc, hook->module, hook->module_size,
+                                    hook->handle );
+                }
+            }
+        }
     }
     if (global_hooks && table != global_hooks)  /* now search through the global table */
     {
-        hook = get_first_valid_hook( global_hooks, index );
+        hook = get_first_valid_hook( global_hooks, index, event, win, object_id, child_id );
     }
     return hook;
 }
@@ -265,17 +316,37 @@
 /* set a window hook */
 DECL_HANDLER(set_hook)
 {
-    struct thread *thread;
+    struct process *process = NULL;
+    struct thread *thread = NULL;
     struct hook *hook;
     WCHAR *module;
     int global;
     size_t module_size = get_req_data_size();
 
-    if (!req->proc || req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+    if (!req->proc || req->id < WH_MINHOOK || req->id > WH_WINEVENT)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
     }
+
+    if (req->pid && !(process = get_process_from_id( req->pid ))) return;
+
+    if (req->tid)
+    {
+        if (!(thread = get_thread_from_id( req->tid )))
+        {
+            if (process) release_object( process );
+            return;
+        }
+        if (process && process != thread->process)
+        {
+            release_object( process );
+            release_object( thread );
+            set_error( STATUS_INVALID_PARAMETER );
+            return;
+        }
+    }
+
     if (req->id == WH_KEYBOARD_LL || req->id == WH_MOUSE_LL)
     {
         /* low-level hardware hooks are special: always global, but without a module */
@@ -285,11 +356,15 @@
     }
     else if (!req->tid)
     {
-        if (!module_size)
+        /* out of context hooks do not need a module handle */
+        if (!module_size && (req->flags & WINEVENT_INCONTEXT))
         {
+            if (process) release_object( process );
+            if (thread) release_object( thread );
             set_error( STATUS_INVALID_PARAMETER );
             return;
         }
+
         if (!(module = memdup( get_req_data(), module_size ))) return;
         thread = NULL;
         global = 1;
@@ -298,11 +373,15 @@
     {
         module = NULL;
         global = 0;
-        if (!(thread = get_thread_from_id( req->tid ))) return;
     }
 
     if ((hook = add_hook( thread, req->id - WH_MINHOOK, global )))
     {
+        hook->owner = (struct thread *)grab_object( current );
+        hook->process = process ? (struct process *)grab_object( process ) : NULL;
+        hook->event_min   = req->event_min;
+        hook->event_max   = req->event_max;
+        hook->flags       = req->flags;
         hook->proc        = req->proc;
         hook->unicode     = req->unicode;
         hook->module      = module;
@@ -311,6 +390,7 @@
     }
     else if (module) free( module );
 
+    if (process) release_object( process );
     if (thread) release_object( thread );
 }
 
@@ -324,13 +404,13 @@
     {
         if (!(hook = get_user_object( req->handle, USER_HOOK )))
         {
-            set_win32_error( ERROR_INVALID_HOOK_HANDLE );
+            set_error( STATUS_INVALID_HANDLE );
             return;
         }
     }
     else
     {
-        if (!req->proc || req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+        if (!req->proc || req->id < WH_MINHOOK || req->id > WH_WINEVENT)
         {
             set_error( STATUS_INVALID_PARAMETER );
             return;
@@ -351,32 +431,31 @@
     struct hook *hook;
     struct hook_table *table = get_queue_hooks( current );
 
-    if (req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+    if (req->id < WH_MINHOOK || req->id > WH_WINEVENT)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
     }
 
-    if (!table || !(hook = get_first_valid_hook( table, req->id - WH_MINHOOK )))
+    if (!table || !(hook = get_first_valid_hook( table, req->id - WH_MINHOOK, req->event, req->window, req->object_id, req->child_id )))
     {
         /* try global table */
         if (!(table = global_hooks) ||
-            !(hook = get_first_valid_hook( global_hooks, req->id - WH_MINHOOK )))
+            !(hook = get_first_valid_hook( global_hooks, req->id - WH_MINHOOK, req->event, req->window, req->object_id, req->child_id )))
             return;  /* no hook set */
     }
 
-    if (hook->thread && hook->thread != current)  /* must run in other thread */
+    if (hook->thread && hook->thread != current) /* must run in other thread */
     {
         reply->pid  = get_process_id( hook->thread->process );
         reply->tid  = get_thread_id( hook->thread );
-        reply->proc = 0;
     }
     else
     {
         reply->pid  = 0;
         reply->tid  = 0;
-        reply->proc = hook->proc;
     }
+    reply->proc    = hook->proc;
     reply->handle  = hook->handle;
     reply->unicode = hook->unicode;
     table->counts[hook->index]++;
@@ -390,7 +469,7 @@
     struct hook_table *table = get_queue_hooks( current );
     int index = req->id - WH_MINHOOK;
 
-    if (req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+    if (req->id < WH_MINHOOK || req->id > WH_WINEVENT)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
@@ -411,7 +490,7 @@
         set_error( STATUS_INVALID_HANDLE );
         return;
     }
-    if ((next = get_next_hook( hook )))
+    if ((next = get_next_hook( hook, req->event, req->window, req->object_id, req->child_id )))
     {
         reply->next = next->handle;
         reply->id   = next->index + WH_MINHOOK;
@@ -422,13 +501,12 @@
         {
             reply->pid  = get_process_id( next->thread->process );
             reply->tid  = get_thread_id( next->thread );
-            reply->proc = 0;
         }
         else
         {
             reply->pid  = 0;
             reply->tid  = 0;
-            reply->proc = next->proc;
         }
+        reply->proc = next->proc;
     }
 }
diff --git a/server/protocol.def b/server/protocol.def
index 3307e93..94af8ad 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -1519,7 +1519,8 @@
     MSG_CALLBACK_RESULT,/* result of a callback message */
     MSG_OTHER_PROCESS,  /* sent from other process, may include vararg data, always Unicode */
     MSG_POSTED,         /* posted message (from PostMessageW), always Unicode */
-    MSG_HARDWARE        /* hardware message */
+    MSG_HARDWARE,       /* hardware message */
+    MSG_WINEVENT        /* winevent message */
 };
 #define SEND_MSG_ABORT_IF_HUNG  0x01
 
@@ -1538,6 +1539,8 @@
     unsigned int    lparam;    /* parameters (result for MSG_CALLBACK_RESULT) */
     int             x;         /* x position */
     int             y;         /* y position */
+    user_handle_t   hook;      /* winevent hook handle */
+    void*           hook_proc; /* winevent hook proc address */
     unsigned int    time;      /* message time */
     unsigned int    info;      /* extra info (callback argument for MSG_CALLBACK_RESULT) */
     size_t          total;     /* total size of extra data */
@@ -2064,7 +2067,11 @@
 /* Set a window hook */
 @REQ(set_hook)
     int            id;             /* id of the hook */
+    process_id_t   pid;            /* id of process to set the hook into */
     thread_id_t    tid;            /* id of thread to set the hook into */
+    int            event_min;
+    int            event_max;
+    int            flags;
     void*          proc;           /* hook procedure */
     int            unicode;        /* is it a unicode hook? */
     VARARG(module,unicode_str);    /* module name */
@@ -2084,6 +2091,10 @@
 /* Start calling a hook chain */
 @REQ(start_hook_chain)
     int            id;             /* id of the hook */
+    int            event;          /* signalled event */
+    user_handle_t  window;         /* handle to the event window */
+    int            object_id;      /* object id for out of context winevent */
+    int            child_id;       /* child id for out of context winevent */
 @REPLY
     user_handle_t  handle;         /* handle to the next hook */
     process_id_t   pid;            /* process id for low-level keyboard/mouse hooks */
@@ -2103,6 +2114,10 @@
 /* Get the next hook to call */
 @REQ(get_next_hook)
     user_handle_t  handle;         /* handle to the current hook */
+    int            event;          /* signalled event */
+    user_handle_t  window;         /* handle to the event window */
+    int            object_id;      /* object id for out of context winevent */
+    int            child_id;       /* child id for out of context winevent */
 @REPLY
     user_handle_t  next;           /* handle to the next hook */
     int            id;             /* id of the next hook */
diff --git a/server/queue.c b/server/queue.c
index 0c9b23f..e5a5979 100644
--- a/server/queue.c
+++ b/server/queue.c
@@ -70,6 +70,8 @@
     int                    y;         /* y position */
     unsigned int           time;      /* message time */
     unsigned int           info;      /* extra info */
+    user_handle_t          hook;      /* winevent hook handle */
+    void                  *hook_proc; /* winevent hook proc address */
     void                  *data;      /* message data for sent messages */
     unsigned int           data_size; /* size of message data */
     struct message_result *result;    /* result in sender queue */
@@ -550,6 +552,8 @@
     reply->y      = msg->y;
     reply->time   = msg->time;
     reply->info   = msg->info;
+    reply->hook   = msg->hook;
+    reply->hook_proc = msg->hook_proc;
 
     if (msg->data) set_reply_data_ptr( msg->data, msg->data_size );
 
@@ -1296,6 +1300,45 @@
     release_object( thread );
 }
 
+/* post a win event */
+void post_win_event( struct thread *thread, unsigned int event,
+                     user_handle_t win, unsigned int object_id,
+                     unsigned int child_id, void *hook_proc,
+                     const WCHAR *module, size_t module_size,
+                     user_handle_t hook)
+{
+    struct message *msg;
+
+    if (thread->queue && (msg = mem_alloc( sizeof(*msg) )))
+    {
+        msg->type      = MSG_WINEVENT;
+        msg->win       = get_user_full_handle( win );
+        msg->msg       = event;
+        msg->wparam    = object_id;
+        msg->lparam    = child_id;
+        msg->time      = get_tick_count();
+        msg->x         = 0;
+        msg->y         = 0;
+        msg->info      = get_thread_id( current );
+        msg->result    = NULL;
+        msg->hook      = hook;
+        msg->hook_proc = hook_proc;
+
+        if ((msg->data = malloc( module_size )))
+        {
+            msg->data_size = module_size;
+            memcpy( msg->data, module, module_size );
+
+            if (debug_level > 1)
+                fprintf( stderr, "post_win_event: tid %04x event %04x win %p object_id %d child_id %d\n",
+                         get_thread_id(thread), event, win, object_id, child_id );
+            append_message( &thread->queue->msg_list[SEND_MESSAGE], msg );
+            set_queue_bits( thread->queue, QS_SENDMESSAGE );
+        }
+        else
+            free( msg );
+    }
+}
 
 /* get the message queue of the current thread */
 DECL_HANDLER(get_msg_queue)
diff --git a/server/thread.c b/server/thread.c
index 64e2ad5c..e2a02d8 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -275,7 +275,7 @@
     struct object *obj = get_ptid_entry( id );
 
     if (obj && obj->ops == &thread_ops) return (struct thread *)grab_object( obj );
-    set_error( STATUS_INVALID_PARAMETER );
+    set_win32_error( ERROR_INVALID_THREAD_ID );
     return NULL;
 }
 
diff --git a/server/trace.c b/server/trace.c
index 146d0f2..b499013 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -1866,6 +1866,8 @@
     fprintf( stderr, " lparam=%08x,", req->lparam );
     fprintf( stderr, " x=%d,", req->x );
     fprintf( stderr, " y=%d,", req->y );
+    fprintf( stderr, " hook=%p,", req->hook );
+    fprintf( stderr, " hook_proc=%p,", req->hook_proc );
     fprintf( stderr, " time=%08x,", req->time );
     fprintf( stderr, " info=%08x,", req->info );
     fprintf( stderr, " total=%d,", req->total );
@@ -2460,7 +2462,11 @@
 static void dump_set_hook_request( const struct set_hook_request *req )
 {
     fprintf( stderr, " id=%d,", req->id );
+    fprintf( stderr, " pid=%04x,", req->pid );
     fprintf( stderr, " tid=%04x,", req->tid );
+    fprintf( stderr, " event_min=%d,", req->event_min );
+    fprintf( stderr, " event_max=%d,", req->event_max );
+    fprintf( stderr, " flags=%d,", req->flags );
     fprintf( stderr, " proc=%p,", req->proc );
     fprintf( stderr, " unicode=%d,", req->unicode );
     fprintf( stderr, " module=" );
@@ -2481,7 +2487,11 @@
 
 static void dump_start_hook_chain_request( const struct start_hook_chain_request *req )
 {
-    fprintf( stderr, " id=%d", req->id );
+    fprintf( stderr, " id=%d,", req->id );
+    fprintf( stderr, " event=%d,", req->event );
+    fprintf( stderr, " window=%p,", req->window );
+    fprintf( stderr, " object_id=%d,", req->object_id );
+    fprintf( stderr, " child_id=%d", req->child_id );
 }
 
 static void dump_start_hook_chain_reply( const struct start_hook_chain_reply *req )
@@ -2502,7 +2512,11 @@
 
 static void dump_get_next_hook_request( const struct get_next_hook_request *req )
 {
-    fprintf( stderr, " handle=%p", req->handle );
+    fprintf( stderr, " handle=%p,", req->handle );
+    fprintf( stderr, " event=%d,", req->event );
+    fprintf( stderr, " window=%p,", req->window );
+    fprintf( stderr, " object_id=%d,", req->object_id );
+    fprintf( stderr, " child_id=%d", req->child_id );
 }
 
 static void dump_get_next_hook_reply( const struct get_next_hook_reply *req )
@@ -3212,8 +3226,8 @@
         NAME(TIMEOUT),
         NAME(USER_APC),
         NAME(WAS_LOCKED),
-        NAME_WIN32(ERROR_INVALID_HOOK_HANDLE),
         NAME_WIN32(ERROR_INVALID_INDEX),
+        NAME_WIN32(ERROR_INVALID_THREAD_ID),
         NAME_WIN32(ERROR_NEGATIVE_SEEK),
         NAME_WIN32(ERROR_SEEK),
         { NULL, 0 }  /* terminator */
diff --git a/server/user.h b/server/user.h
index dfeda2a..a68949c 100644
--- a/server/user.h
+++ b/server/user.h
@@ -64,6 +64,11 @@
 extern int attach_thread_input( struct thread *thread_from, struct thread *thread_to );
 extern void post_message( user_handle_t win, unsigned int message,
                           unsigned int wparam, unsigned int lparam );
+extern void post_win_event( struct thread *thread, unsigned int event,
+                            user_handle_t win, unsigned int object_id,
+                            unsigned int child_id, void *proc,
+                            const WCHAR *module, size_t module_size,
+                            user_handle_t handle );
 
 /* region functions */
 
diff --git a/windows/nonclient.c b/windows/nonclient.c
index 255421e..a3af69b 100644
--- a/windows/nonclient.c
+++ b/windows/nonclient.c
@@ -1561,7 +1561,7 @@
     case SC_ARRANGE:
     case SC_NEXTWINDOW:
     case SC_PREVWINDOW:
- 	FIXME("unimplemented!\n");
+ 	FIXME("unimplemented WM_SYSCOMMAND %04x!\n", wParam);
         break;
     }
     return 0;