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