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