resources: Change Dutch sublanguage code to SUBLANG_NEUTRAL.
[wine] / dlls / winex11.drv / dce.c
1 /*
2 * USER DCE functions
3  *
4  * Copyright 1993, 2005 Alexandre Julliard
5  * Copyright 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23
24 #include <assert.h>
25 #include "ntstatus.h"
26 #define WIN32_NO_STATUS
27 #include "win.h"
28 #include "windef.h"
29 #include "wingdi.h"
30 #include "wownt32.h"
31 #include "x11drv.h"
32 #include "wine/winbase16.h"
33 #include "wine/wingdi16.h"
34 #include "wine/server.h"
35 #include "wine/list.h"
36 #include "wine/debug.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(dc);
39
40 struct dce
41 {
42     struct list entry;         /* entry in global DCE list */
43     HDC         hdc;
44     HWND        hwnd;
45     HRGN        clip_rgn;
46     DWORD       flags;
47     void       *class_ptr;     /* ptr to identify window class for class DCEs */
48     ULONG       count;         /* usage count; 0 or 1 for cache DCEs, always 1 for window DCEs,
49                                   always >= 1 for class DCEs */
50 };
51
52 static struct list dce_list = LIST_INIT(dce_list);
53
54 static BOOL16 CALLBACK dc_hook( HDC16 hDC, WORD code, DWORD data, LPARAM lParam );
55
56 static CRITICAL_SECTION dce_section;
57 static CRITICAL_SECTION_DEBUG critsect_debug =
58 {
59     0, 0, &dce_section,
60     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
61       0, 0, { (DWORD_PTR)(__FILE__ ": dce_section") }
62 };
63 static CRITICAL_SECTION dce_section = { &critsect_debug, -1, 0, 0, 0, 0 };
64
65 static const WCHAR displayW[] = { 'D','I','S','P','L','A','Y',0 };
66
67
68 /***********************************************************************
69  *           dump_cache
70  */
71 static void dump_cache(void)
72 {
73     struct dce *dce;
74
75     EnterCriticalSection( &dce_section );
76
77     LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry )
78     {
79         TRACE("%p: hwnd %p dcx %08x %s %s\n",
80               dce, dce->hwnd, dce->flags,
81               (dce->flags & DCX_CACHE) ? "Cache" : "Owned",
82               dce->count ? "InUse" : "" );
83     }
84
85     LeaveCriticalSection( &dce_section );
86 }
87
88
89 /***********************************************************************
90  *              update_visible_region
91  *
92  * Set the visible region and X11 drawable for the DC associated to
93  * a given window.
94  */
95 static void update_visible_region( struct dce *dce )
96 {
97     NTSTATUS status;
98     HRGN vis_rgn = 0;
99     HWND top = 0;
100     struct x11drv_escape_set_drawable escape;
101     struct x11drv_win_data *data;
102     DWORD flags = dce->flags;
103     size_t size = 256;
104
105     /* don't clip siblings if using parent clip region */
106     if (flags & DCX_PARENTCLIP) flags &= ~DCX_CLIPSIBLINGS;
107
108     /* fetch the visible region from the server */
109     do
110     {
111         RGNDATA *data = HeapAlloc( GetProcessHeap(), 0, sizeof(*data) + size - 1 );
112         if (!data) return;
113
114         SERVER_START_REQ( get_visible_region )
115         {
116             req->window  = dce->hwnd;
117             req->flags   = flags;
118             wine_server_set_reply( req, data->Buffer, size );
119             if (!(status = wine_server_call( req )))
120             {
121                 size_t reply_size = wine_server_reply_size( reply );
122                 data->rdh.dwSize   = sizeof(data->rdh);
123                 data->rdh.iType    = RDH_RECTANGLES;
124                 data->rdh.nCount   = reply_size / sizeof(RECT);
125                 data->rdh.nRgnSize = reply_size;
126                 vis_rgn = ExtCreateRegion( NULL, size, data );
127
128                 top = reply->top_win;
129                 escape.org.x = reply->win_org_x - reply->top_org_x;
130                 escape.org.y = reply->win_org_y - reply->top_org_y;
131                 escape.drawable_org.x = reply->top_org_x;
132                 escape.drawable_org.y = reply->top_org_y;
133             }
134             else size = reply->total_size;
135         }
136         SERVER_END_REQ;
137         HeapFree( GetProcessHeap(), 0, data );
138     } while (status == STATUS_BUFFER_OVERFLOW);
139
140     if (status || !vis_rgn) return;
141
142     if (dce->clip_rgn) CombineRgn( vis_rgn, vis_rgn, dce->clip_rgn,
143                                    (flags & DCX_INTERSECTRGN) ? RGN_AND : RGN_DIFF );
144
145     if (top == dce->hwnd && ((data = X11DRV_get_win_data( dce->hwnd )) != NULL) &&
146          IsIconic( dce->hwnd ) && data->icon_window)
147         escape.drawable = data->icon_window;
148     else
149         escape.drawable = X11DRV_get_whole_window( top );
150
151     escape.code = X11DRV_SET_DRAWABLE;
152     escape.mode = IncludeInferiors;
153     ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL );
154
155     /* map region to DC coordinates */
156     OffsetRgn( vis_rgn,
157                -(escape.drawable_org.x + escape.org.x),
158                -(escape.drawable_org.y + escape.org.y) );
159     SelectVisRgn16( HDC_16(dce->hdc), HRGN_16(vis_rgn) );
160     DeleteObject( vis_rgn );
161 }
162
163
164 /***********************************************************************
165  *              release_dce
166  */
167 static void release_dce( struct dce *dce )
168 {
169     struct x11drv_escape_set_drawable escape;
170
171     if (!dce->hwnd) return;  /* already released */
172
173     if (dce->clip_rgn) DeleteObject( dce->clip_rgn );
174     dce->clip_rgn = 0;
175     dce->hwnd     = 0;
176     dce->flags   &= DCX_CACHE;
177
178     escape.code = X11DRV_SET_DRAWABLE;
179     escape.drawable = root_window;
180     escape.mode = IncludeInferiors;
181     escape.org.x = escape.org.y = 0;
182     escape.drawable_org.x = escape.drawable_org.y = 0;
183     ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL );
184 }
185
186
187 /***********************************************************************
188  *   delete_clip_rgn
189  */
190 static void delete_clip_rgn( struct dce *dce )
191 {
192     if (!dce->clip_rgn) return;  /* nothing to do */
193
194     dce->flags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN);
195     DeleteObject( dce->clip_rgn );
196     dce->clip_rgn = 0;
197
198     /* make it dirty so that the vis rgn gets recomputed next time */
199     SetHookFlags16( HDC_16(dce->hdc), DCHF_INVALIDATEVISRGN );
200 }
201
202
203 /***********************************************************************
204  *           alloc_cache_dce
205  *
206  * Allocate a new cache DCE.
207  */
208 static struct dce *alloc_cache_dce(void)
209 {
210     struct x11drv_escape_set_dce escape;
211     struct dce *dce;
212
213     if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(*dce) ))) return NULL;
214     if (!(dce->hdc = CreateDCW( displayW, NULL, NULL, NULL )))
215     {
216         HeapFree( GetProcessHeap(), 0, dce );
217         return 0;
218     }
219     SaveDC( dce->hdc );
220
221     /* store DCE handle in DC hook data field */
222     SetDCHook( dce->hdc, dc_hook, (DWORD)dce );
223
224     dce->hwnd      = 0;
225     dce->clip_rgn  = 0;
226     dce->flags     = DCX_CACHE;
227     dce->class_ptr = NULL;
228     dce->count     = 1;
229
230     EnterCriticalSection( &dce_section );
231     list_add_head( &dce_list, &dce->entry );
232     LeaveCriticalSection( &dce_section );
233
234     escape.code = X11DRV_SET_DCE;
235     escape.dce  = dce;
236     ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape, 0, NULL );
237
238     return dce;
239 }
240
241
242 /***********************************************************************
243  *           alloc_window_dce
244  *
245  * Allocate a DCE for a newly created window if necessary.
246  */
247 void alloc_window_dce( struct x11drv_win_data *data )
248 {
249     struct x11drv_escape_set_dce escape;
250     struct dce *dce;
251     void *class_ptr = NULL;
252     LONG style = GetClassLongW( data->hwnd, GCL_STYLE );
253
254     if (!(style & (CS_CLASSDC|CS_OWNDC))) return;  /* nothing to do */
255
256     if (!(style & CS_OWNDC))  /* class dc */
257     {
258         /* hack: get the class pointer from the window structure */
259         WND *win = WIN_GetPtr( data->hwnd );
260         class_ptr = win->class;
261         WIN_ReleasePtr( win );
262
263         EnterCriticalSection( &dce_section );
264         LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry )
265         {
266             if (dce->class_ptr == class_ptr)
267             {
268                 dce->count++;
269                 data->dce = dce;
270                 LeaveCriticalSection( &dce_section );
271                 return;
272             }
273         }
274         LeaveCriticalSection( &dce_section );
275     }
276
277     /* now allocate a new one */
278
279     if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(*dce) ))) return;
280     if (!(dce->hdc = CreateDCW( displayW, NULL, NULL, NULL )))
281     {
282         HeapFree( GetProcessHeap(), 0, dce );
283         return;
284     }
285
286     /* store DCE handle in DC hook data field */
287
288     SetDCHook( dce->hdc, dc_hook, (DWORD)dce );
289
290     dce->hwnd      = data->hwnd;
291     dce->clip_rgn  = 0;
292     dce->flags     = 0;
293     dce->class_ptr = class_ptr;
294     dce->count     = 1;
295
296     if (style & CS_OWNDC)
297     {
298         LONG win_style = GetWindowLongW( data->hwnd, GWL_STYLE );
299         if (win_style & WS_CLIPCHILDREN) dce->flags |= DCX_CLIPCHILDREN;
300         if (win_style & WS_CLIPSIBLINGS) dce->flags |= DCX_CLIPSIBLINGS;
301     }
302     SetHookFlags16( HDC_16(dce->hdc), DCHF_INVALIDATEVISRGN );
303
304     EnterCriticalSection( &dce_section );
305     list_add_tail( &dce_list, &dce->entry );
306     LeaveCriticalSection( &dce_section );
307     data->dce = dce;
308
309     escape.code = X11DRV_SET_DCE;
310     escape.dce  = dce;
311     ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape, 0, NULL );
312 }
313
314
315 /***********************************************************************
316  *           free_window_dce
317  *
318  * Free a class or window DCE.
319  */
320 void free_window_dce( struct x11drv_win_data *data )
321 {
322     struct dce *dce = data->dce;
323
324     if (dce)
325     {
326         EnterCriticalSection( &dce_section );
327         if (!--dce->count)
328         {
329             list_remove( &dce->entry );
330             SetDCHook(dce->hdc, NULL, 0L);
331             DeleteDC( dce->hdc );
332             if (dce->clip_rgn) DeleteObject( dce->clip_rgn );
333             HeapFree( GetProcessHeap(), 0, dce );
334         }
335         else if (dce->hwnd == data->hwnd)
336         {
337             release_dce( dce );
338         }
339         LeaveCriticalSection( &dce_section );
340         data->dce = NULL;
341     }
342
343     /* now check for cache DCEs */
344
345     EnterCriticalSection( &dce_section );
346     LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry )
347     {
348         if (dce->hwnd != data->hwnd) continue;
349         if (!(dce->flags & DCX_CACHE)) continue;
350
351         if (dce->count) WARN( "GetDC() without ReleaseDC() for window %p\n", data->hwnd );
352         release_dce( dce );
353         dce->count = 0;
354     }
355     LeaveCriticalSection( &dce_section );
356 }
357
358
359 /***********************************************************************
360  *   invalidate_dce
361  *
362  * It is called from SetWindowPos() - we have to
363  * mark as dirty all busy DCEs for windows that have pWnd->parent as
364  * an ancestor and whose client rect intersects with specified update
365  * rectangle. In addition, pWnd->parent DCEs may need to be updated if
366  * DCX_CLIPCHILDREN flag is set.
367  */
368 void invalidate_dce( HWND hwnd, const RECT *rect )
369 {
370     HWND hwndScope = GetAncestor( hwnd, GA_PARENT );
371
372     if( hwndScope )
373     {
374         struct dce *dce;
375
376         TRACE("scope hwnd = %p %s\n", hwndScope, wine_dbgstr_rect(rect) );
377         if (TRACE_ON(dc)) dump_cache();
378
379         /* walk all DCEs and fixup non-empty entries */
380
381         LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry )
382         {
383             if (!dce->hwnd) continue;
384             if ((dce->hwnd == hwndScope) && !(dce->flags & DCX_CLIPCHILDREN))
385                 continue;  /* child window positions don't bother us */
386
387             /* check if DCE window is within the z-order scope */
388
389             if (hwndScope == dce->hwnd || IsChild( hwndScope, dce->hwnd ))
390             {
391                 if (hwnd != dce->hwnd)
392                 {
393                     /* check if the window rectangle intersects this DCE window */
394                     RECT tmp;
395                     GetWindowRect( dce->hwnd, &tmp );
396                     MapWindowPoints( 0, hwndScope, (POINT *)&tmp, 2 );
397                     if (!IntersectRect( &tmp, &tmp, rect )) continue;
398
399                 }
400                 if (!dce->count)
401                 {
402                     /* Don't bother with visible regions of unused DCEs */
403
404                     TRACE("\tpurged %p dce [%p]\n", dce, dce->hwnd);
405                     release_dce( dce );
406                 }
407                 else
408                 {
409                     /* Set dirty bits in the hDC and DCE structs */
410
411                     TRACE("\tfixed up %p dce [%p]\n", dce, dce->hwnd);
412                     SetHookFlags16( HDC_16(dce->hdc), DCHF_INVALIDATEVISRGN );
413                 }
414             }
415         } /* dce list */
416     }
417 }
418
419
420 /***********************************************************************
421  *              X11DRV_GetDCEx   (X11DRV.@)
422  *
423  * Unimplemented flags: DCX_LOCKWINDOWUPDATE
424  */
425 HDC X11DRV_GetDCEx( HWND hwnd, HRGN hrgnClip, DWORD flags )
426 {
427     static const DWORD clip_flags = DCX_PARENTCLIP | DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_WINDOW;
428
429     struct x11drv_win_data *data = X11DRV_get_win_data( hwnd );
430     struct dce *dce;
431     BOOL bUpdateVisRgn = TRUE;
432     HWND parent;
433     LONG window_style = GetWindowLongW( hwnd, GWL_STYLE );
434
435     TRACE("hwnd %p, hrgnClip %p, flags %08x\n", hwnd, hrgnClip, flags);
436
437     /* fixup flags */
438
439     if (flags & (DCX_WINDOW | DCX_PARENTCLIP)) flags |= DCX_CACHE;
440
441     if (flags & DCX_USESTYLE)
442     {
443         flags &= ~(DCX_CLIPCHILDREN | DCX_CLIPSIBLINGS | DCX_PARENTCLIP);
444
445         if (window_style & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS;
446
447         if (!(flags & DCX_WINDOW))
448         {
449             if (GetClassLongW( hwnd, GCL_STYLE ) & CS_PARENTDC) flags |= DCX_PARENTCLIP;
450
451             if (window_style & WS_CLIPCHILDREN && !(window_style & WS_MINIMIZE))
452                 flags |= DCX_CLIPCHILDREN;
453             if (!data || !data->dce) flags |= DCX_CACHE;
454         }
455     }
456
457     if (flags & DCX_WINDOW) flags &= ~DCX_CLIPCHILDREN;
458
459     parent = GetAncestor( hwnd, GA_PARENT );
460     if (!parent || (parent == GetDesktopWindow()))
461         flags = (flags & ~DCX_PARENTCLIP) | DCX_CLIPSIBLINGS;
462
463     /* it seems parent clip is ignored when clipping siblings or children */
464     if (flags & (DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN)) flags &= ~DCX_PARENTCLIP;
465
466     if( flags & DCX_PARENTCLIP )
467     {
468         LONG parent_style = GetWindowLongW( parent, GWL_STYLE );
469         if( (window_style & WS_VISIBLE) && (parent_style & WS_VISIBLE) )
470         {
471             flags &= ~DCX_CLIPCHILDREN;
472             if (parent_style & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS;
473         }
474     }
475
476     /* find a suitable DCE */
477
478     if (flags & DCX_CACHE)
479     {
480         struct dce *dceEmpty = NULL, *dceUnused = NULL;
481
482         /* Strategy: First, we attempt to find a non-empty but unused DCE with
483          * compatible flags. Next, we look for an empty entry. If the cache is
484          * full we have to purge one of the unused entries.
485          */
486         EnterCriticalSection( &dce_section );
487         LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry )
488         {
489             if ((dce->flags & DCX_CACHE) && !dce->count)
490             {
491                 dceUnused = dce;
492
493                 if (!dce->hwnd) dceEmpty = dce;
494                 else if ((dce->hwnd == hwnd) && !((dce->flags ^ flags) & clip_flags))
495                 {
496                     TRACE("\tfound valid %p dce [%p], flags %08x\n",
497                           dce, hwnd, dce->flags );
498                     bUpdateVisRgn = FALSE;
499                     break;
500                 }
501             }
502         }
503
504         if (&dce->entry == &dce_list)  /* nothing found */
505             dce = dceEmpty ? dceEmpty : dceUnused;
506
507         if (dce) dce->count = 1;
508
509         LeaveCriticalSection( &dce_section );
510
511         /* if there's no dce empty or unused, allocate a new one */
512         if (!dce) dce = alloc_cache_dce();
513         if (!dce) return 0;
514     }
515     else
516     {
517         flags |= DCX_NORESETATTRS;
518         dce = data->dce;
519         if (dce->hwnd == hwnd)
520         {
521             TRACE("\tskipping hVisRgn update\n");
522             bUpdateVisRgn = FALSE; /* updated automatically, via DCHook() */
523         }
524         else
525         {
526             /* we should free dce->clip_rgn here, but Windows apparently doesn't */
527             dce->flags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN);
528             dce->clip_rgn = 0;
529         }
530     }
531
532     if (flags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN))
533     {
534         /* if the extra clip region has changed, get rid of the old one */
535         if (dce->clip_rgn != hrgnClip || ((flags ^ dce->flags) & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)))
536             delete_clip_rgn( dce );
537         dce->clip_rgn = hrgnClip;
538         if (!dce->clip_rgn) dce->clip_rgn = CreateRectRgn( 0, 0, 0, 0 );
539         dce->flags |= flags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN);
540         bUpdateVisRgn = TRUE;
541     }
542
543     dce->hwnd = hwnd;
544     dce->flags = (dce->flags & ~clip_flags) | (flags & clip_flags);
545
546     if (SetHookFlags16( HDC_16(dce->hdc), DCHF_VALIDATEVISRGN ))
547         bUpdateVisRgn = TRUE;  /* DC was dirty */
548
549     if (bUpdateVisRgn) update_visible_region( dce );
550
551     if (!(flags & DCX_NORESETATTRS))
552     {
553         RestoreDC( dce->hdc, 1 );  /* initial save level is always 1 */
554         SaveDC( dce->hdc );  /* save the state again for next time */
555     }
556
557     TRACE("(%p,%p,0x%x): returning %p\n", hwnd, hrgnClip, flags, dce->hdc);
558     return dce->hdc;
559 }
560
561
562 /***********************************************************************
563  *              X11DRV_ReleaseDC  (X11DRV.@)
564  */
565 INT X11DRV_ReleaseDC( HWND hwnd, HDC hdc, BOOL end_paint )
566 {
567     enum x11drv_escape_codes escape = X11DRV_GET_DCE;
568     struct dce *dce;
569     BOOL ret = FALSE;
570
571     TRACE("%p %p\n", hwnd, hdc );
572
573     EnterCriticalSection( &dce_section );
574     if (!ExtEscape( hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape,
575                     sizeof(dce), (LPSTR)&dce )) dce = NULL;
576     if (dce && dce->count)
577     {
578         if (end_paint || (dce->flags & DCX_CACHE)) delete_clip_rgn( dce );
579         if (dce->flags & DCX_CACHE) dce->count = 0;
580         ret = TRUE;
581     }
582     LeaveCriticalSection( &dce_section );
583     return ret;
584 }
585
586 /***********************************************************************
587  *              dc_hook
588  *
589  * See "Undoc. Windows" for hints (DC, SetDCHook, SetHookFlags)..
590  */
591 static BOOL16 CALLBACK dc_hook( HDC16 hDC, WORD code, DWORD data, LPARAM lParam )
592 {
593     BOOL retv = TRUE;
594     struct dce *dce = (struct dce *)data;
595
596     TRACE("hDC = %04x, %i\n", hDC, code);
597
598     if (!dce) return 0;
599     assert( HDC_16(dce->hdc) == hDC );
600
601     switch( code )
602     {
603     case DCHC_INVALIDVISRGN:
604         /* GDI code calls this when it detects that the
605          * DC is dirty (usually after SetHookFlags()). This
606          * means that we have to recompute the visible region.
607          */
608         if (dce->count) update_visible_region( dce );
609         else /* non-fatal but shouldn't happen */
610             WARN("DC is not in use!\n");
611         break;
612     case DCHC_DELETEDC:
613         /*
614          * Windows will not let you delete a DC that is busy
615          * (between GetDC and ReleaseDC)
616          */
617         if (dce->count)
618         {
619             WARN("Application trying to delete a busy DC %p\n", dce->hdc);
620             retv = FALSE;
621         }
622         else
623         {
624             EnterCriticalSection( &dce_section );
625             list_remove( &dce->entry );
626             LeaveCriticalSection( &dce_section );
627             if (dce->clip_rgn) DeleteObject( dce->clip_rgn );
628             HeapFree( GetProcessHeap(), 0, dce );
629         }
630         break;
631     }
632     return retv;
633 }
634
635
636 /**********************************************************************
637  *              WindowFromDC   (X11DRV.@)
638  */
639 HWND X11DRV_WindowFromDC( HDC hdc )
640 {
641     enum x11drv_escape_codes escape = X11DRV_GET_DCE;
642     struct dce *dce;
643     HWND hwnd = 0;
644
645     EnterCriticalSection( &dce_section );
646     if (!ExtEscape( hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape,
647                     sizeof(dce), (LPSTR)&dce )) dce = NULL;
648     if (dce) hwnd = dce->hwnd;
649     LeaveCriticalSection( &dce_section );
650     return hwnd;
651 }