/* * USER DCE functions * * Copyright 1993, 2005 Alexandre Julliard * Copyright 1996, 1997 Alex Korobka * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include "win.h" #include "windef.h" #include "wingdi.h" #include "wownt32.h" #include "ntstatus.h" #include "x11drv.h" #include "wine/winbase16.h" #include "wine/wingdi16.h" #include "wine/server.h" #include "wine/list.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(dc); struct dce { struct list entry; /* entry in global DCE list */ HDC hdc; HWND hwnd; HRGN clip_rgn; DWORD flags; void *class_ptr; /* ptr to identify window class for class DCEs */ ULONG count; /* usage count; 0 or 1 for cache DCEs, always 1 for window DCEs, always >= 1 for class DCEs */ }; static struct list dce_list = LIST_INIT(dce_list); static BOOL16 CALLBACK dc_hook( HDC16 hDC, WORD code, DWORD data, LPARAM lParam ); static CRITICAL_SECTION dce_section; static CRITICAL_SECTION_DEBUG critsect_debug = { 0, 0, &dce_section, { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, 0, 0, { 0, (DWORD)(__FILE__ ": dce_section") } }; static CRITICAL_SECTION dce_section = { &critsect_debug, -1, 0, 0, 0, 0 }; static const WCHAR displayW[] = { 'D','I','S','P','L','A','Y',0 }; /*********************************************************************** * dump_cache */ static void dump_cache(void) { struct dce *dce; EnterCriticalSection( &dce_section ); LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry ) { TRACE("%p: hwnd %p dcx %08lx %s %s\n", dce, dce->hwnd, dce->flags, (dce->flags & DCX_CACHE) ? "Cache" : "Owned", dce->count ? "InUse" : "" ); } LeaveCriticalSection( &dce_section ); } /*********************************************************************** * update_visible_region * * Set the visible region and X11 drawable for the DC associated to * a given window. */ static void update_visible_region( struct dce *dce ) { NTSTATUS status; HRGN vis_rgn = 0; HWND top = 0; struct x11drv_escape_set_drawable escape; struct x11drv_win_data *data; DWORD flags = dce->flags; size_t size = 256; /* don't clip siblings if using parent clip region */ if (flags & DCX_PARENTCLIP) flags &= ~DCX_CLIPSIBLINGS; /* fetch the visible region from the server */ do { RGNDATA *data = HeapAlloc( GetProcessHeap(), 0, sizeof(*data) + size - 1 ); if (!data) return; SERVER_START_REQ( get_visible_region ) { req->window = dce->hwnd; req->flags = flags; wine_server_set_reply( req, data->Buffer, size ); if (!(status = wine_server_call( req ))) { size_t reply_size = wine_server_reply_size( reply ); data->rdh.dwSize = sizeof(data->rdh); data->rdh.iType = RDH_RECTANGLES; data->rdh.nCount = reply_size / sizeof(RECT); data->rdh.nRgnSize = reply_size; vis_rgn = ExtCreateRegion( NULL, size, data ); top = reply->top_win; escape.org.x = reply->win_org_x - reply->top_org_x; escape.org.y = reply->win_org_y - reply->top_org_y; escape.drawable_org.x = reply->top_org_x; escape.drawable_org.y = reply->top_org_y; } else size = reply->total_size; } SERVER_END_REQ; HeapFree( GetProcessHeap(), 0, data ); } while (status == STATUS_BUFFER_OVERFLOW); if (status || !vis_rgn) return; if (dce->clip_rgn) CombineRgn( vis_rgn, vis_rgn, dce->clip_rgn, (flags & DCX_INTERSECTRGN) ? RGN_AND : RGN_DIFF ); if (top == dce->hwnd && ((data = X11DRV_get_win_data( dce->hwnd )) != NULL) && IsIconic( dce->hwnd ) && data->icon_window) escape.drawable = data->icon_window; else escape.drawable = X11DRV_get_whole_window( top ); escape.code = X11DRV_SET_DRAWABLE; escape.mode = IncludeInferiors; ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL ); /* map region to DC coordinates */ OffsetRgn( vis_rgn, -(escape.drawable_org.x + escape.org.x), -(escape.drawable_org.y + escape.org.y) ); SelectVisRgn16( HDC_16(dce->hdc), HRGN_16(vis_rgn) ); DeleteObject( vis_rgn ); } /*********************************************************************** * release_dce */ static void release_dce( struct dce *dce ) { struct x11drv_escape_set_drawable escape; if (!dce->hwnd) return; /* already released */ if (dce->clip_rgn) DeleteObject( dce->clip_rgn ); dce->clip_rgn = 0; dce->hwnd = 0; dce->flags &= DCX_CACHE; escape.code = X11DRV_SET_DRAWABLE; escape.drawable = root_window; escape.mode = IncludeInferiors; escape.org.x = escape.org.y = 0; escape.drawable_org.x = escape.drawable_org.y = 0; ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL ); } /*********************************************************************** * delete_clip_rgn */ static void delete_clip_rgn( struct dce *dce ) { if (!dce->clip_rgn) return; /* nothing to do */ dce->flags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN); DeleteObject( dce->clip_rgn ); dce->clip_rgn = 0; /* make it dirty so that the vis rgn gets recomputed next time */ SetHookFlags16( HDC_16(dce->hdc), DCHF_INVALIDATEVISRGN ); } /*********************************************************************** * alloc_cache_dce * * Allocate a new cache DCE. */ static struct dce *alloc_cache_dce(void) { struct x11drv_escape_set_dce escape; struct dce *dce; if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(*dce) ))) return NULL; if (!(dce->hdc = CreateDCW( displayW, NULL, NULL, NULL ))) { HeapFree( GetProcessHeap(), 0, dce ); return 0; } SaveDC( dce->hdc ); /* store DCE handle in DC hook data field */ SetDCHook( dce->hdc, dc_hook, (DWORD)dce ); dce->hwnd = 0; dce->clip_rgn = 0; dce->flags = DCX_CACHE; dce->class_ptr = NULL; dce->count = 1; EnterCriticalSection( &dce_section ); list_add_head( &dce_list, &dce->entry ); LeaveCriticalSection( &dce_section ); escape.code = X11DRV_SET_DCE; escape.dce = dce; ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape, 0, NULL ); return dce; } /*********************************************************************** * alloc_window_dce * * Allocate a DCE for a newly created window if necessary. */ void alloc_window_dce( struct x11drv_win_data *data ) { struct x11drv_escape_set_dce escape; struct dce *dce; void *class_ptr = NULL; LONG style = GetClassLongW( data->hwnd, GCL_STYLE ); if (!(style & (CS_CLASSDC|CS_OWNDC))) return; /* nothing to do */ if (!(style & CS_OWNDC)) /* class dc */ { /* hack: get the class pointer from the window structure */ WND *win = WIN_GetPtr( data->hwnd ); class_ptr = win->class; WIN_ReleasePtr( win ); EnterCriticalSection( &dce_section ); LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry ) { if (dce->class_ptr == class_ptr) { dce->count++; data->dce = dce; LeaveCriticalSection( &dce_section ); return; } } LeaveCriticalSection( &dce_section ); } /* now allocate a new one */ if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(*dce) ))) return; if (!(dce->hdc = CreateDCW( displayW, NULL, NULL, NULL ))) { HeapFree( GetProcessHeap(), 0, dce ); return; } /* store DCE handle in DC hook data field */ SetDCHook( dce->hdc, dc_hook, (DWORD)dce ); dce->hwnd = data->hwnd; dce->clip_rgn = 0; dce->flags = 0; dce->class_ptr = class_ptr; dce->count = 1; if (style & CS_OWNDC) { LONG win_style = GetWindowLongW( data->hwnd, GWL_STYLE ); if (win_style & WS_CLIPCHILDREN) dce->flags |= DCX_CLIPCHILDREN; if (win_style & WS_CLIPSIBLINGS) dce->flags |= DCX_CLIPSIBLINGS; } SetHookFlags16( HDC_16(dce->hdc), DCHF_INVALIDATEVISRGN ); EnterCriticalSection( &dce_section ); list_add_tail( &dce_list, &dce->entry ); LeaveCriticalSection( &dce_section ); data->dce = dce; escape.code = X11DRV_SET_DCE; escape.dce = dce; ExtEscape( dce->hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape, 0, NULL ); } /*********************************************************************** * free_window_dce * * Free a class or window DCE. */ void free_window_dce( struct x11drv_win_data *data ) { struct dce *dce = data->dce; if (dce) { EnterCriticalSection( &dce_section ); if (!--dce->count) { list_remove( &dce->entry ); SetDCHook(dce->hdc, NULL, 0L); DeleteDC( dce->hdc ); if (dce->clip_rgn) DeleteObject( dce->clip_rgn ); HeapFree( GetProcessHeap(), 0, dce ); } else if (dce->hwnd == data->hwnd) { release_dce( dce ); } LeaveCriticalSection( &dce_section ); data->dce = NULL; } /* now check for cache DCEs */ EnterCriticalSection( &dce_section ); LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry ) { if (dce->hwnd != data->hwnd) continue; if (!(dce->flags & DCX_CACHE)) continue; if (dce->count) WARN( "GetDC() without ReleaseDC() for window %p\n", data->hwnd ); release_dce( dce ); dce->count = 0; } LeaveCriticalSection( &dce_section ); } /*********************************************************************** * invalidate_dce * * It is called from SetWindowPos() - we have to * mark as dirty all busy DCEs for windows that have pWnd->parent as * an ancestor and whose client rect intersects with specified update * rectangle. In addition, pWnd->parent DCEs may need to be updated if * DCX_CLIPCHILDREN flag is set. */ void invalidate_dce( HWND hwnd, const RECT *rect ) { HWND hwndScope = GetAncestor( hwnd, GA_PARENT ); if( hwndScope ) { struct dce *dce; TRACE("scope hwnd = %p %s\n", hwndScope, wine_dbgstr_rect(rect) ); if (TRACE_ON(dc)) dump_cache(); /* walk all DCEs and fixup non-empty entries */ LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry ) { if (!dce->hwnd) continue; if ((dce->hwnd == hwndScope) && !(dce->flags & DCX_CLIPCHILDREN)) continue; /* child window positions don't bother us */ /* check if DCE window is within the z-order scope */ if (hwndScope == dce->hwnd || IsChild( hwndScope, dce->hwnd )) { if (hwnd != dce->hwnd) { /* check if the window rectangle intersects this DCE window */ RECT tmp; GetWindowRect( dce->hwnd, &tmp ); MapWindowPoints( 0, hwndScope, (POINT *)&tmp, 2 ); if (!IntersectRect( &tmp, &tmp, rect )) continue; } if (!dce->count) { /* Don't bother with visible regions of unused DCEs */ TRACE("\tpurged %p dce [%p]\n", dce, dce->hwnd); release_dce( dce ); } else { /* Set dirty bits in the hDC and DCE structs */ TRACE("\tfixed up %p dce [%p]\n", dce, dce->hwnd); SetHookFlags16( HDC_16(dce->hdc), DCHF_INVALIDATEVISRGN ); } } } /* dce list */ } } /*********************************************************************** * X11DRV_GetDCEx (X11DRV.@) * * Unimplemented flags: DCX_LOCKWINDOWUPDATE */ HDC X11DRV_GetDCEx( HWND hwnd, HRGN hrgnClip, DWORD flags ) { static const DWORD clip_flags = DCX_PARENTCLIP | DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_WINDOW; struct x11drv_win_data *data = X11DRV_get_win_data( hwnd ); struct dce *dce; BOOL bUpdateVisRgn = TRUE; HWND parent; LONG window_style = GetWindowLongW( hwnd, GWL_STYLE ); TRACE("hwnd %p, hrgnClip %p, flags %08lx\n", hwnd, hrgnClip, flags); /* fixup flags */ if (flags & (DCX_WINDOW | DCX_PARENTCLIP)) flags |= DCX_CACHE; if (flags & DCX_USESTYLE) { flags &= ~(DCX_CLIPCHILDREN | DCX_CLIPSIBLINGS | DCX_PARENTCLIP); if (window_style & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS; if (!(flags & DCX_WINDOW)) { if (GetClassLongW( hwnd, GCL_STYLE ) & CS_PARENTDC) flags |= DCX_PARENTCLIP; if (window_style & WS_CLIPCHILDREN && !(window_style & WS_MINIMIZE)) flags |= DCX_CLIPCHILDREN; if (!data || !data->dce) flags |= DCX_CACHE; } } if (flags & DCX_WINDOW) flags &= ~DCX_CLIPCHILDREN; parent = GetAncestor( hwnd, GA_PARENT ); if (!parent || (parent == GetDesktopWindow())) flags = (flags & ~DCX_PARENTCLIP) | DCX_CLIPSIBLINGS; /* it seems parent clip is ignored when clipping siblings or children */ if (flags & (DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN)) flags &= ~DCX_PARENTCLIP; if( flags & DCX_PARENTCLIP ) { LONG parent_style = GetWindowLongW( parent, GWL_STYLE ); if( (window_style & WS_VISIBLE) && (parent_style & WS_VISIBLE) ) { flags &= ~DCX_CLIPCHILDREN; if (parent_style & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS; } } /* find a suitable DCE */ if (flags & DCX_CACHE) { struct dce *dceEmpty = NULL, *dceUnused = NULL; /* Strategy: First, we attempt to find a non-empty but unused DCE with * compatible flags. Next, we look for an empty entry. If the cache is * full we have to purge one of the unused entries. */ EnterCriticalSection( &dce_section ); LIST_FOR_EACH_ENTRY( dce, &dce_list, struct dce, entry ) { if ((dce->flags & DCX_CACHE) && !dce->count) { dceUnused = dce; if (!dce->hwnd) dceEmpty = dce; else if ((dce->hwnd == hwnd) && !((dce->flags ^ flags) & clip_flags)) { TRACE("\tfound valid %p dce [%p], flags %08lx\n", dce, hwnd, dce->flags ); bUpdateVisRgn = FALSE; break; } } } if (&dce->entry == &dce_list) /* nothing found */ dce = dceEmpty ? dceEmpty : dceUnused; if (dce) dce->count = 1; LeaveCriticalSection( &dce_section ); /* if there's no dce empty or unused, allocate a new one */ if (!dce) dce = alloc_cache_dce(); if (!dce) return 0; } else { flags |= DCX_NORESETATTRS; dce = data->dce; if (dce->hwnd == hwnd) { TRACE("\tskipping hVisRgn update\n"); bUpdateVisRgn = FALSE; /* updated automatically, via DCHook() */ } else { /* we should free dce->clip_rgn here, but Windows apparently doesn't */ dce->flags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN); dce->clip_rgn = 0; } } if (flags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)) { /* if the extra clip region has changed, get rid of the old one */ if (dce->clip_rgn != hrgnClip || ((flags ^ dce->flags) & (DCX_INTERSECTRGN | DCX_EXCLUDERGN))) delete_clip_rgn( dce ); dce->clip_rgn = hrgnClip; if (!dce->clip_rgn) dce->clip_rgn = CreateRectRgn( 0, 0, 0, 0 ); dce->flags |= flags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN); bUpdateVisRgn = TRUE; } dce->hwnd = hwnd; dce->flags = (dce->flags & ~clip_flags) | (flags & clip_flags); if (SetHookFlags16( HDC_16(dce->hdc), DCHF_VALIDATEVISRGN )) bUpdateVisRgn = TRUE; /* DC was dirty */ if (bUpdateVisRgn) update_visible_region( dce ); if (!(flags & DCX_NORESETATTRS)) { RestoreDC( dce->hdc, 1 ); /* initial save level is always 1 */ SaveDC( dce->hdc ); /* save the state again for next time */ } TRACE("(%p,%p,0x%lx): returning %p\n", hwnd, hrgnClip, flags, dce->hdc); return dce->hdc; } /*********************************************************************** * X11DRV_ReleaseDC (X11DRV.@) */ BOOL X11DRV_ReleaseDC( HWND hwnd, HDC hdc, BOOL end_paint ) { enum x11drv_escape_codes escape = X11DRV_GET_DCE; struct dce *dce; BOOL ret = FALSE; TRACE("%p %p\n", hwnd, hdc ); EnterCriticalSection( &dce_section ); if (!ExtEscape( hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape, sizeof(dce), (LPSTR)&dce )) dce = NULL; if (dce && dce->count) { if (end_paint || (dce->flags & DCX_CACHE)) delete_clip_rgn( dce ); if (dce->flags & DCX_CACHE) dce->count = 0; ret = TRUE; } LeaveCriticalSection( &dce_section ); return ret; } /*********************************************************************** * dc_hook * * See "Undoc. Windows" for hints (DC, SetDCHook, SetHookFlags).. */ static BOOL16 CALLBACK dc_hook( HDC16 hDC, WORD code, DWORD data, LPARAM lParam ) { BOOL retv = TRUE; struct dce *dce = (struct dce *)data; TRACE("hDC = %04x, %i\n", hDC, code); if (!dce) return 0; assert( HDC_16(dce->hdc) == hDC ); switch( code ) { case DCHC_INVALIDVISRGN: /* GDI code calls this when it detects that the * DC is dirty (usually after SetHookFlags()). This * means that we have to recompute the visible region. */ if (dce->count) update_visible_region( dce ); else /* non-fatal but shouldn't happen */ WARN("DC is not in use!\n"); break; case DCHC_DELETEDC: /* * Windows will not let you delete a DC that is busy * (between GetDC and ReleaseDC) */ if (dce->count) { WARN("Application trying to delete a busy DC %p\n", dce->hdc); retv = FALSE; } else { EnterCriticalSection( &dce_section ); list_remove( &dce->entry ); LeaveCriticalSection( &dce_section ); if (dce->clip_rgn) DeleteObject( dce->clip_rgn ); HeapFree( GetProcessHeap(), 0, dce ); } break; } return retv; } /********************************************************************** * WindowFromDC (X11DRV.@) */ HWND X11DRV_WindowFromDC( HDC hdc ) { enum x11drv_escape_codes escape = X11DRV_GET_DCE; struct dce *dce; HWND hwnd = 0; EnterCriticalSection( &dce_section ); if (!ExtEscape( hdc, X11DRV_ESCAPE, sizeof(escape), (LPCSTR)&escape, sizeof(dce), (LPSTR)&dce )) dce = NULL; if (dce) hwnd = dce->hwnd; LeaveCriticalSection( &dce_section ); return hwnd; }