Complete unicodification of the rebar common control.
[wine] / windows / dce.c
1 /*
2  * USER DCE functions
3  *
4  * Copyright 1993 Alexandre Julliard
5  *           1996,1997 Alex Korobka
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  *
22  * Note: Visible regions of CS_OWNDC/CS_CLASSDC window DCs
23  * have to be updated dynamically.
24  *
25  * Internal DCX flags:
26  *
27  * DCX_DCEEMPTY    - dce is uninitialized
28  * DCX_DCEBUSY     - dce is in use
29  * DCX_DCEDIRTY    - ReleaseDC() should wipe instead of caching
30  * DCX_WINDOWPAINT - BeginPaint() is in effect
31  */
32
33 #include <assert.h>
34 #include "dce.h"
35 #include "win.h"
36 #include "user_private.h"
37 #include "windef.h"
38 #include "wingdi.h"
39 #include "wownt32.h"
40 #include "wine/winbase16.h"
41 #include "wine/winuser16.h"
42 #include "wine/debug.h"
43 #include "wine/list.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(dc);
46
47 typedef struct tagDCE
48 {
49     struct list entry;
50     HDC         hDC;
51     HWND        hwndCurrent;
52     HRGN        hClipRgn;
53     DCE_TYPE    type;
54     DWORD       DCXflags;
55 } DCE;
56
57 static struct list dce_list = LIST_INIT(dce_list);
58
59 static void DCE_DeleteClipRgn( DCE* );
60 static INT DCE_ReleaseDC( DCE* );
61
62
63 /***********************************************************************
64  *           DCE_DumpCache
65  */
66 static void DCE_DumpCache(void)
67 {
68     DCE *dce;
69
70     USER_Lock();
71
72     LIST_FOR_EACH_ENTRY( dce, &dce_list, DCE, entry )
73     {
74         TRACE("\t[0x%08x] hWnd %p, dcx %08x, %s %s\n",
75               (unsigned)dce, dce->hwndCurrent, (unsigned)dce->DCXflags,
76               (dce->DCXflags & DCX_CACHE) ? "Cache" : "Owned",
77               (dce->DCXflags & DCX_DCEBUSY) ? "InUse" : "" );
78     }
79
80     USER_Unlock();
81 }
82
83 /***********************************************************************
84  *           DCE_AllocDCE
85  *
86  * Allocate a new DCE.
87  */
88 DCE *DCE_AllocDCE( HWND hWnd, DCE_TYPE type )
89 {
90     static const WCHAR szDisplayW[] = { 'D','I','S','P','L','A','Y','\0' };
91     DCE * dce;
92
93     TRACE("(%p,%d)\n", hWnd, type);
94
95     if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(DCE) ))) return NULL;
96     if (!(dce->hDC = CreateDCW( szDisplayW, NULL, NULL, NULL )))
97     {
98         HeapFree( GetProcessHeap(), 0, dce );
99         return 0;
100     }
101     SaveDC( dce->hDC );
102
103     /* store DCE handle in DC hook data field */
104
105     SetDCHook( dce->hDC, DCHook16, (DWORD)dce );
106
107     dce->hwndCurrent = hWnd;
108     dce->hClipRgn    = 0;
109     dce->type        = type;
110
111     if( type != DCE_CACHE_DC ) /* owned or class DC */
112     {
113         dce->DCXflags = DCX_DCEBUSY;
114         if( hWnd )
115         {
116             LONG style = GetWindowLongW( hWnd, GWL_STYLE );
117             if (style & WS_CLIPCHILDREN) dce->DCXflags |= DCX_CLIPCHILDREN;
118             if (style & WS_CLIPSIBLINGS) dce->DCXflags |= DCX_CLIPSIBLINGS;
119         }
120         SetHookFlags16( HDC_16(dce->hDC), DCHF_INVALIDATEVISRGN );
121     }
122     else dce->DCXflags = DCX_CACHE | DCX_DCEEMPTY;
123
124     USER_Lock();
125     list_add_head( &dce_list, &dce->entry );
126     USER_Unlock();
127     return dce;
128 }
129
130
131 /***********************************************************************
132  *           DCE_FreeDCE
133  */
134 void DCE_FreeDCE( DCE *dce )
135 {
136     if (!dce) return;
137
138     USER_Lock();
139     list_remove( &dce->entry );
140     USER_Unlock();
141
142     SetDCHook(dce->hDC, NULL, 0L);
143
144     DeleteDC( dce->hDC );
145     if (dce->hClipRgn) DeleteObject(dce->hClipRgn);
146     HeapFree( GetProcessHeap(), 0, dce );
147 }
148
149 /***********************************************************************
150  *           DCE_FreeWindowDCE
151  *
152  * Remove owned DCE and reset unreleased cache DCEs.
153  */
154 void DCE_FreeWindowDCE( HWND hwnd )
155 {
156     struct list *ptr, *next;
157     WND *pWnd = WIN_GetPtr( hwnd );
158
159     LIST_FOR_EACH_SAFE( ptr, next, &dce_list )
160     {
161         DCE *pDCE = LIST_ENTRY( ptr, DCE, entry );
162
163         if (pDCE->hwndCurrent != hwnd) continue;
164
165         switch( pDCE->type )
166         {
167         case DCE_WINDOW_DC:
168             DCE_FreeDCE( pDCE );
169             pWnd->dce = NULL;
170             break;
171         case DCE_CLASS_DC:
172             if( pDCE->DCXflags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN) )
173             {
174                 if (USER_Driver.pReleaseDC)
175                     USER_Driver.pReleaseDC( pDCE->hwndCurrent, pDCE->hDC );
176                 DCE_DeleteClipRgn( pDCE );
177                 pDCE->hwndCurrent = 0;
178             }
179             break;
180         case DCE_CACHE_DC:
181             if( pDCE->DCXflags & DCX_DCEBUSY ) /* shared cache DCE */
182             {
183                 WARN("[%p] GetDC() without ReleaseDC()!\n",hwnd);
184                 DCE_ReleaseDC( pDCE );
185             }
186             if (pDCE->hwndCurrent && USER_Driver.pReleaseDC)
187                 USER_Driver.pReleaseDC( pDCE->hwndCurrent, pDCE->hDC );
188             pDCE->DCXflags &= DCX_CACHE;
189             pDCE->DCXflags |= DCX_DCEEMPTY;
190             pDCE->hwndCurrent = 0;
191             break;
192         }
193     }
194     WIN_ReleasePtr( pWnd );
195 }
196
197
198 /***********************************************************************
199  *   DCE_DeleteClipRgn
200  */
201 static void DCE_DeleteClipRgn( DCE* dce )
202 {
203     dce->DCXflags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN | DCX_WINDOWPAINT);
204
205     if (dce->hClipRgn) DeleteObject( dce->hClipRgn );
206     dce->hClipRgn = 0;
207
208     /* make it dirty so that the vis rgn gets recomputed next time */
209     dce->DCXflags |= DCX_DCEDIRTY;
210     SetHookFlags16( HDC_16(dce->hDC), DCHF_INVALIDATEVISRGN );
211 }
212
213
214 /***********************************************************************
215  *   DCE_ReleaseDC
216  */
217 static INT DCE_ReleaseDC( DCE* dce )
218 {
219     if ((dce->DCXflags & (DCX_DCEEMPTY | DCX_DCEBUSY)) != DCX_DCEBUSY) return 0;
220
221     /* restore previous visible region */
222
223     if ((dce->DCXflags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)) &&
224         (dce->DCXflags & (DCX_CACHE | DCX_WINDOWPAINT)) )
225         DCE_DeleteClipRgn( dce );
226
227     if (dce->DCXflags & DCX_CACHE)
228     {
229         /* make the DC clean so that RestoreDC doesn't try to update the vis rgn */
230         SetHookFlags16( HDC_16(dce->hDC), DCHF_VALIDATEVISRGN );
231         RestoreDC( dce->hDC, 1 );  /* initial save level is always 1 */
232         SaveDC( dce->hDC );  /* save the state again for next time */
233         dce->DCXflags &= ~DCX_DCEBUSY;
234         if (dce->DCXflags & DCX_DCEDIRTY)
235         {
236             /* don't keep around invalidated entries
237              * because RestoreDC() disables hVisRgn updates
238              * by removing dirty bit. */
239             if (dce->hwndCurrent && USER_Driver.pReleaseDC)
240                 USER_Driver.pReleaseDC( dce->hwndCurrent, dce->hDC );
241             dce->hwndCurrent = 0;
242             dce->DCXflags &= DCX_CACHE;
243             dce->DCXflags |= DCX_DCEEMPTY;
244         }
245     }
246     return 1;
247 }
248
249
250 /***********************************************************************
251  *   DCE_InvalidateDCE
252  *
253  * It is called from SetWindowPos() and EVENT_MapNotify - we have to
254  * mark as dirty all busy DCEs for windows that have pWnd->parent as
255  * an ancestor and whose client rect intersects with specified update
256  * rectangle. In addition, pWnd->parent DCEs may need to be updated if
257  * DCX_CLIPCHILDREN flag is set.  */
258 BOOL DCE_InvalidateDCE(HWND hwnd, const RECT* pRectUpdate)
259 {
260     HWND hwndScope = GetAncestor( hwnd, GA_PARENT );
261     BOOL bRet = FALSE;
262
263     if( hwndScope )
264     {
265         DCE *dce;
266
267         TRACE("scope hwnd = %p, (%ld,%ld - %ld,%ld)\n",
268               hwndScope, pRectUpdate->left,pRectUpdate->top,
269               pRectUpdate->right,pRectUpdate->bottom);
270         if(TRACE_ON(dc))
271           DCE_DumpCache();
272
273         /* walk all DCEs and fixup non-empty entries */
274
275         LIST_FOR_EACH_ENTRY( dce, &dce_list, DCE, entry )
276         {
277             if (dce->DCXflags & DCX_DCEEMPTY) continue;
278             if ((dce->hwndCurrent == hwndScope) && !(dce->DCXflags & DCX_CLIPCHILDREN))
279                 continue;  /* child window positions don't bother us */
280
281             /* check if DCE window is within the z-order scope */
282
283             if (hwndScope == dce->hwndCurrent || IsChild( hwndScope, dce->hwndCurrent ))
284             {
285                 if (hwnd != dce->hwndCurrent)
286                 {
287                     /* check if the window rectangle intersects this DCE window */
288                     RECT rect;
289                     GetWindowRect( dce->hwndCurrent, &rect );
290                     MapWindowPoints( 0, hwndScope, (POINT *)&rect, 2 );
291                     if (!IntersectRect( &rect, &rect, pRectUpdate )) continue;
292
293                 }
294                 if( !(dce->DCXflags & DCX_DCEBUSY) )
295                 {
296                     /* Don't bother with visible regions of unused DCEs */
297
298                     TRACE("\tpurged %p dce [%p]\n", dce, dce->hwndCurrent);
299                     if (dce->hwndCurrent && USER_Driver.pReleaseDC)
300                         USER_Driver.pReleaseDC( dce->hwndCurrent, dce->hDC );
301                     dce->hwndCurrent = 0;
302                     dce->DCXflags &= DCX_CACHE;
303                     dce->DCXflags |= DCX_DCEEMPTY;
304                 }
305                 else
306                 {
307                     /* Set dirty bits in the hDC and DCE structs */
308
309                     TRACE("\tfixed up %p dce [%p]\n", dce, dce->hwndCurrent);
310                     dce->DCXflags |= DCX_DCEDIRTY;
311                     SetHookFlags16( HDC_16(dce->hDC), DCHF_INVALIDATEVISRGN );
312                     bRet = TRUE;
313                 }
314             }
315         } /* dce list */
316     }
317     return bRet;
318 }
319
320
321 /***********************************************************************
322  *              GetDCEx (USER32.@)
323  *
324  * Unimplemented flags: DCX_LOCKWINDOWUPDATE
325  *
326  * FIXME: Full support for hrgnClip == 1 (alias for entire window).
327  */
328 HDC WINAPI GetDCEx( HWND hwnd, HRGN hrgnClip, DWORD flags )
329 {
330     HDC         hdc = 0;
331     DCE *       dce;
332     WND *       wndPtr;
333     DWORD       dcxFlags = 0;
334     BOOL        bUpdateVisRgn = TRUE;
335     BOOL        bUpdateClipOrigin = FALSE;
336     HWND parent;
337     LONG window_style;
338
339     TRACE("hwnd %p, hrgnClip %p, flags %08lx\n", hwnd, hrgnClip, flags);
340
341     if (flags & (DCX_LOCKWINDOWUPDATE)) {
342        FIXME("not yet supported - see source\n");
343        /* See the comment in LockWindowUpdate for more explanation. This flag is not implemented
344         * by that patch, but we need LockWindowUpdate implemented correctly before this can be done.
345         */
346     }
347
348     if (!hwnd) hwnd = GetDesktopWindow();
349     else hwnd = WIN_GetFullHandle( hwnd );
350
351     if (!(wndPtr = WIN_GetPtr( hwnd ))) return 0;
352     if (wndPtr == WND_OTHER_PROCESS)
353     {
354         wndPtr = NULL;
355         USER_Lock();
356     }
357
358     window_style = GetWindowLongW( hwnd, GWL_STYLE );
359
360     /* fixup flags */
361
362     if (flags & (DCX_WINDOW | DCX_PARENTCLIP)) flags |= DCX_CACHE;
363
364     if (flags & DCX_USESTYLE)
365     {
366         flags &= ~( DCX_CLIPCHILDREN | DCX_CLIPSIBLINGS | DCX_PARENTCLIP);
367
368         if( window_style & WS_CLIPSIBLINGS )
369             flags |= DCX_CLIPSIBLINGS;
370
371         if ( !(flags & DCX_WINDOW) )
372         {
373             if (GetClassLongW( hwnd, GCL_STYLE ) & CS_PARENTDC) flags |= DCX_PARENTCLIP;
374
375             if (window_style & WS_CLIPCHILDREN && !(window_style & WS_MINIMIZE))
376                 flags |= DCX_CLIPCHILDREN;
377             if (!wndPtr || !wndPtr->dce) flags |= DCX_CACHE;
378         }
379     }
380
381     if (flags & DCX_WINDOW) flags &= ~DCX_CLIPCHILDREN;
382
383     parent = GetAncestor( hwnd, GA_PARENT );
384     if (!parent || (parent == GetDesktopWindow()))
385         flags = (flags & ~DCX_PARENTCLIP) | DCX_CLIPSIBLINGS;
386
387     /* it seems parent clip is ignored when clipping siblings or children */
388     if (flags & (DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN)) flags &= ~DCX_PARENTCLIP;
389
390     if( flags & DCX_PARENTCLIP )
391     {
392         LONG parent_style = GetWindowLongW( parent, GWL_STYLE );
393         if( (window_style & WS_VISIBLE) && (parent_style & WS_VISIBLE) )
394         {
395             flags &= ~DCX_CLIPCHILDREN;
396             if (parent_style & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS;
397         }
398     }
399
400     /* find a suitable DCE */
401
402     dcxFlags = flags & (DCX_PARENTCLIP | DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN |
403                         DCX_CACHE | DCX_WINDOW);
404
405     if (flags & DCX_CACHE)
406     {
407         DCE *dceEmpty = NULL, *dceUnused = NULL;
408
409         /* Strategy: First, we attempt to find a non-empty but unused DCE with
410          * compatible flags. Next, we look for an empty entry. If the cache is
411          * full we have to purge one of the unused entries.
412          */
413
414         LIST_FOR_EACH_ENTRY( dce, &dce_list, DCE, entry )
415         {
416             if ((dce->DCXflags & (DCX_CACHE | DCX_DCEBUSY)) == DCX_CACHE )
417             {
418                 dceUnused = dce;
419
420                 if (dce->DCXflags & DCX_DCEEMPTY)
421                     dceEmpty = dce;
422                 else
423                 if ((dce->hwndCurrent == hwnd) &&
424                    ((dce->DCXflags & (DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN |
425                                       DCX_CACHE | DCX_WINDOW | DCX_PARENTCLIP)) == dcxFlags))
426                 {
427                     TRACE("\tfound valid %p dce [%p], flags %08lx\n",
428                           dce, hwnd, dcxFlags );
429                     bUpdateVisRgn = FALSE;
430                     bUpdateClipOrigin = TRUE;
431                     break;
432                 }
433             }
434         }
435
436         if (&dce->entry == &dce_list)  /* nothing found */
437             dce = dceEmpty ? dceEmpty : dceUnused;
438
439         /* if there's no dce empty or unused, allocate a new one */
440         if (!dce)
441         {
442             dce = DCE_AllocDCE( 0, DCE_CACHE_DC );
443         }
444     }
445     else
446     {
447         dce = wndPtr->dce;
448         if (dce && dce->hwndCurrent == hwnd)
449         {
450             TRACE("\tskipping hVisRgn update\n");
451             bUpdateVisRgn = FALSE; /* updated automatically, via DCHook() */
452         }
453     }
454     if (!dce)
455     {
456         hdc = 0;
457         goto END;
458     }
459
460     if (!(flags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN))) hrgnClip = 0;
461
462     if (((flags ^ dce->DCXflags) & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)) &&
463         (dce->hClipRgn != hrgnClip))
464     {
465         /* if the extra clip region has changed, get rid of the old one */
466         DCE_DeleteClipRgn( dce );
467     }
468
469     dce->hwndCurrent = hwnd;
470     dce->hClipRgn = hrgnClip;
471     dce->DCXflags = flags & (DCX_PARENTCLIP | DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN |
472                              DCX_CACHE | DCX_WINDOW | DCX_WINDOWPAINT |
473                              DCX_INTERSECTRGN | DCX_EXCLUDERGN);
474     dce->DCXflags |= DCX_DCEBUSY;
475     dce->DCXflags &= ~DCX_DCEDIRTY;
476     hdc = dce->hDC;
477
478     if (bUpdateVisRgn) SetHookFlags16( HDC_16(hdc), DCHF_INVALIDATEVISRGN ); /* force update */
479
480     if (!USER_Driver.pGetDC || !USER_Driver.pGetDC( hwnd, hdc, hrgnClip, flags ))
481         hdc = 0;
482
483     TRACE("(%p,%p,0x%lx): returning %p\n", hwnd, hrgnClip, flags, hdc);
484 END:
485     if (wndPtr) WIN_ReleasePtr(wndPtr);
486     else USER_Unlock();
487     return hdc;
488 }
489
490
491 /***********************************************************************
492  *              GetDC (USER32.@)
493  *
494  * Get a device context.
495  *
496  * RETURNS
497  *      Success: Handle to the device context
498  *      Failure: NULL.
499  */
500 HDC WINAPI GetDC( HWND hwnd )
501 {
502     if (!hwnd)
503         return GetDCEx( 0, 0, DCX_CACHE | DCX_WINDOW );
504     return GetDCEx( hwnd, 0, DCX_USESTYLE );
505 }
506
507
508 /***********************************************************************
509  *              GetWindowDC (USER32.@)
510  */
511 HDC WINAPI GetWindowDC( HWND hwnd )
512 {
513     return GetDCEx( hwnd, 0, DCX_USESTYLE | DCX_WINDOW );
514 }
515
516
517 /***********************************************************************
518  *              ReleaseDC (USER32.@)
519  *
520  * Release a device context.
521  *
522  * RETURNS
523  *      Success: Non-zero. Resources used by hdc are released.
524  *      Failure: 0.
525  */
526 INT WINAPI ReleaseDC( HWND hwnd, HDC hdc )
527 {
528     DCE * dce;
529     INT nRet = 0;
530
531     TRACE("%p %p\n", hwnd, hdc );
532
533     USER_Lock();
534     LIST_FOR_EACH_ENTRY( dce, &dce_list, DCE, entry )
535     {
536         if (dce->hDC == hdc)
537         {
538             if ( dce->DCXflags & DCX_DCEBUSY )
539                 nRet = DCE_ReleaseDC( dce );
540             break;
541         }
542
543     }
544     USER_Unlock();
545
546     return nRet;
547 }
548
549 /***********************************************************************
550  *              DCHook (USER.362)
551  *
552  * See "Undoc. Windows" for hints (DC, SetDCHook, SetHookFlags)..
553  */
554 BOOL16 WINAPI DCHook16( HDC16 hDC, WORD code, DWORD data, LPARAM lParam )
555 {
556     BOOL retv = TRUE;
557     DCE *dce = (DCE *)data;
558
559     TRACE("hDC = %04x, %i\n", hDC, code);
560
561     if (!dce) return 0;
562     assert( HDC_16(dce->hDC) == hDC );
563
564     /* Grab the windows lock before doing anything else  */
565     USER_Lock();
566
567     switch( code )
568     {
569       case DCHC_INVALIDVISRGN:
570            /* GDI code calls this when it detects that the
571             * DC is dirty (usually after SetHookFlags()). This
572             * means that we have to recompute the visible region.
573             */
574            if( dce->DCXflags & DCX_DCEBUSY )
575            {
576                /* Dirty bit has been cleared by caller, set it again so that
577                 * pGetDC recomputes the visible region. */
578                SetHookFlags16( hDC, DCHF_INVALIDATEVISRGN );
579                if (USER_Driver.pGetDC)
580                     USER_Driver.pGetDC( dce->hwndCurrent, dce->hDC, dce->hClipRgn, dce->DCXflags );
581            }
582            else /* non-fatal but shouldn't happen */
583              WARN("DC is not in use!\n");
584            break;
585
586       case DCHC_DELETEDC:
587            /*
588             * Windows will not let you delete a DC that is busy
589             * (between GetDC and ReleaseDC)
590             */
591
592            if ( dce->DCXflags & DCX_DCEBUSY )
593            {
594                WARN("Application trying to delete a busy DC\n");
595                retv = FALSE;
596            }
597            else DCE_FreeDCE( dce );
598            break;
599
600       default:
601            FIXME("unknown code\n");
602     }
603
604   USER_Unlock();  /* Release the wnd lock */
605   return retv;
606 }
607
608
609 /**********************************************************************
610  *              WindowFromDC (USER32.@)
611  */
612 HWND WINAPI WindowFromDC( HDC hDC )
613 {
614     DCE *dce;
615     HWND hwnd = 0;
616
617     USER_Lock();
618     LIST_FOR_EACH_ENTRY( dce, &dce_list, DCE, entry )
619     {
620         if (dce->hDC == hDC)
621         {
622             hwnd = dce->hwndCurrent;
623             break;
624         }
625     }
626     USER_Unlock();
627
628     return hwnd;
629 }
630
631
632 /***********************************************************************
633  *              LockWindowUpdate (USER32.@)
634  */
635 BOOL WINAPI LockWindowUpdate( HWND hwnd )
636 {
637     static HWND lockedWnd;
638
639     /* This function is fully implemented by the following patch:
640      *
641      * http://www.winehq.org/hypermail/wine-patches/2004/01/0142.html
642      *
643      * but in order to work properly, it needs the ability to invalidate
644      * DCEs in other processes when the lock window is changed, which
645      * isn't possible yet.
646      * -mike
647      */
648
649     FIXME("(%p), partial stub!\n",hwnd);
650
651     USER_Lock();
652     if (lockedWnd)
653     {
654         if (!hwnd)
655         {
656             /* Unlock lockedWnd */
657             /* FIXME: Do something */
658         }
659         else
660         {
661             /* Attempted to lock a second window */
662             /* Return FALSE and do nothing */
663             USER_Unlock();
664             return FALSE;
665         }
666     }
667     lockedWnd = hwnd;
668     USER_Unlock();
669     return TRUE;
670 }