Keep track of the windows and hooks used by a thread to properly
refuse to change the thread desktop when it's in use.

diff --git a/dlls/user/tests/winstation.c b/dlls/user/tests/winstation.c
index 5ce2d72..9f23586 100644
--- a/dlls/user/tests/winstation.c
+++ b/dlls/user/tests/winstation.c
@@ -64,12 +64,9 @@
     trace( "created desktop %p\n", d2 );
     ok( d2 != 0, "CreateDesktop failed\n" );
 
-    todo_wine
-    {
-        SetLastError( 0xdeadbeef );
-        ok( !SetThreadDesktop( d2 ), "set thread desktop succeeded with existing window\n" );
-        ok( GetLastError() == ERROR_BUSY, "bad last error %ld\n", GetLastError() );
-    }
+    SetLastError( 0xdeadbeef );
+    ok( !SetThreadDesktop( d2 ), "set thread desktop succeeded with existing window\n" );
+    ok( GetLastError() == ERROR_BUSY, "bad last error %ld\n", GetLastError() );
 
     DestroyWindow( hwnd );
     ok( SetThreadDesktop( d2 ), "set thread desktop failed\n" );
diff --git a/server/hook.c b/server/hook.c
index c3a0244..0830450 100644
--- a/server/hook.c
+++ b/server/hook.c
@@ -125,6 +125,7 @@
     hook->thread = thread ? (struct thread *)grab_object( thread ) : NULL;
     hook->index  = index;
     list_add_head( &table->hooks[index], &hook->chain );
+    if (thread) thread->desktop_users++;
     return hook;
 }
 
@@ -133,7 +134,12 @@
 {
     free_user_handle( hook->handle );
     if (hook->module) free( hook->module );
-    if (hook->thread) release_object( hook->thread );
+    if (hook->thread)
+    {
+        assert( hook->thread->desktop_users > 0 );
+        hook->thread->desktop_users--;
+        release_object( hook->thread );
+    }
     if (hook->process) release_object( hook->process );
     release_object( hook->owner );
     list_remove( &hook->chain );
diff --git a/server/thread.c b/server/thread.c
index 819fd9f..46d1e6e 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -140,6 +140,7 @@
     thread->suspend         = 0;
     thread->creation_time   = time(NULL);
     thread->exit_time       = 0;
+    thread->desktop_users   = 0;
 
     list_init( &thread->mutex_list );
     list_init( &thread->system_apc );
diff --git a/server/thread.h b/server/thread.h
index 749b277..4a175e7 100644
--- a/server/thread.h
+++ b/server/thread.h
@@ -83,6 +83,7 @@
     int                    affinity;      /* affinity mask */
     int                    suspend;       /* suspend count */
     obj_handle_t           desktop;       /* desktop handle */
+    int                    desktop_users; /* number of objects using the thread desktop */
     time_t                 creation_time; /* Thread creation time */
     time_t                 exit_time;     /* Thread exit time */
     struct token          *token;         /* security token associated with this thread */
diff --git a/server/window.c b/server/window.c
index 20775f2..9904c39 100644
--- a/server/window.c
+++ b/server/window.c
@@ -311,6 +311,8 @@
     if (win == shell_listview) shell_listview = NULL;
     if (win == progman_window) progman_window = NULL;
     if (win == taskman_window) taskman_window = NULL;
+    assert( win->thread->desktop_users > 0 );
+    win->thread->desktop_users--;
     free_user_handle( win->handle );
     destroy_properties( win );
     list_remove( &win->entry );
@@ -376,6 +378,7 @@
     /* put it on parent unlinked list */
     if (parent) list_add_head( &parent->unlinked, &win->entry );
 
+    current->desktop_users++;
     return win;
 
 failed:
@@ -1303,6 +1306,7 @@
         if (!top_window)
         {
             if (!(top_window = create_window( NULL, NULL, req->atom, req->instance ))) return;
+            current->desktop_users--;
             top_window->thread = NULL;  /* no thread owns the desktop */
             top_window->style  = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
         }
diff --git a/server/winstation.c b/server/winstation.c
index d42ed66..ed61df9 100644
--- a/server/winstation.c
+++ b/server/winstation.c
@@ -175,6 +175,13 @@
     return full_name;
 }
 
+/* retrieve a pointer to a desktop object */
+inline static struct desktop *get_desktop_obj( struct process *process, obj_handle_t handle,
+                                               unsigned int access )
+{
+    return (struct desktop *)get_handle_obj( process, handle, access, &desktop_ops );
+}
+
 /* create a desktop object */
 static struct desktop *create_desktop( const WCHAR *name, size_t len, unsigned int flags,
                                        struct winstation *winstation )
@@ -426,14 +433,39 @@
 /* set the thread current desktop */
 DECL_HANDLER(set_thread_desktop)
 {
-    struct desktop *desktop;
+    struct desktop *old_desktop, *new_desktop;
+    struct winstation *winstation;
 
-    if ((desktop = (struct desktop *)get_handle_obj( current->process, req->handle, 0, &desktop_ops )))
+    if (!(winstation = get_process_winstation( current->process, 0 /* FIXME: access rights? */ )))
+        return;
+
+    if (!(new_desktop = get_desktop_obj( current->process, req->handle, 0 )))
     {
-        /* FIXME: should we close the old one? */
-        current->desktop = req->handle;
-        release_object( desktop );
+        release_object( winstation );
+        return;
     }
+    if (new_desktop->winstation != winstation)
+    {
+        set_error( STATUS_ACCESS_DENIED );
+        release_object( new_desktop );
+        release_object( winstation );
+        return;
+    }
+
+    /* check if we are changing to a new desktop */
+
+    if (!(old_desktop = get_desktop_obj( current->process, current->desktop, 0)))
+        clear_error();  /* ignore error */
+
+    /* when changing desktop, we can't have any users on the current one */
+    if (old_desktop != new_desktop && current->desktop_users > 0)
+        set_error( STATUS_DEVICE_BUSY );
+    else
+        current->desktop = req->handle;  /* FIXME: should we close the old one? */
+
+    if (old_desktop) release_object( old_desktop );
+    release_object( new_desktop );
+    release_object( winstation );
 }