|
|
/**************************************************************************\
* * Copyright (c) 1998-1999 Microsoft Corporation * * Abstract: * * Display/Palette notification routines for GDI+. * * Revision History: * * 7/19/99 ericvan * Created it. * 9/15/2000 agodfrey * #175866: Improved GDI+ startup, shutdown and event notification * \**************************************************************************/
#include "precomp.hpp"
#include "..\render\vgahash.hpp"
#include <winuser.h>
VOID DisplayNotify(); VOID PaletteNotify(); VOID SysColorNotify();
/////////////////////////////// MESSAGE HANDLERS ///////////////////////////////
/**************************************************************************\
* * Function Description: * * This routine receives a display notification request and appropriately * readjusts the size and resolution of DCI screen surface. * * History: * * 7/23/1999 ericvan * Created it. * \**************************************************************************/
VOID DisplayNotify() { GpDevice *device = Globals::DesktopDevice;
Devlock devlock(device);
// Check to see if we have switched to a Terminal Server Session
if (GetSystemMetrics(SM_REMOTESESSION)) { // it is a remote session
Globals::IsTerminalServer = TRUE; } else { // it isn't a remote session.
Globals::IsTerminalServer = FALSE; }
Globals::DesktopDriver->DesktopChangeNotification();
DWORD width, height;
width = GetSystemMetrics(SM_CXVIRTUALSCREEN); height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
if ((device != NULL) && (device->DeviceHdc != NULL) && (GetDeviceCaps(device->DeviceHdc, BITSPIXEL) <= 8)) { // <SystemPalette>
if (device->Palette == NULL) { device->Palette = (ColorPalette*)GpMalloc(sizeof(ColorPalette) + sizeof(ARGB) * 256); if (device->Palette == NULL) { return; } } INT numEntries; PALETTEENTRY palentry[256]; RGBQUAD rgb[256]; ColorPalette* palette;
palette = device->Palette;
// [agodfrey] On Win9x, GetSystemPaletteEntries(hdc, 0, 256, NULL)
// doesn't do what MSDN says it does. It seems to return the number
// of entries in the logical palette of the DC instead. So we have
// to make it up ourselves.
numEntries = (1 << (GetDeviceCaps(device->DeviceHdc, BITSPIXEL) * GetDeviceCaps(device->DeviceHdc, PLANES)));
GetSystemPaletteEntries(device->DeviceHdc, 0, 256, &palentry[0]);
palette->Count = numEntries; for (INT i=0; i<numEntries; i++) { palette->Entries[i] = GpColor::MakeARGB(0xFF, palentry[i].peRed, palentry[i].peGreen, palentry[i].peBlue); rgb[i].rgbRed = palentry[i].peRed; rgb[i].rgbGreen = palentry[i].peGreen; rgb[i].rgbBlue = palentry[i].peBlue; rgb[i].rgbReserved = 0; } if (device->DIBSectionBitmap != NULL) { SetDIBColorTable(device->DIBSectionHdc, 0, numEntries, &rgb[0]); }
Globals::PaletteChangeCount++; }
// Set BufferWidth to 0. This forces ::Start() to recreate the temporary
// BufferDIB at the correct bit depth next time we process any cached records.
// This needs to be done especially if the screen mode is not palettized
// any more since the BufferDIB shouldn't be 8bpp, but reformatted to 32bpp.
device->BufferWidth = 0;
// Recreate the DCI object. If the allocation fails, keep the old one
// so that we don't access violate 'ScanDci' (although we might quite
// happily draw wrong):
EpScanGdiDci *scanDci = new EpScanGdiDci(Globals::DesktopDevice, TRUE); if (scanDci != NULL) { delete Globals::DesktopDevice->ScanDci; Globals::DesktopDevice->ScanDci = scanDci; } // update width and height on desktop surface
// this copies the Device ScanDCI to Screen bitmap.
Globals::DesktopSurface->InitializeForGdiScreen( Globals::DesktopDevice, width, height ); // Give the driver an opportunity to adjust the surface.
Globals::DesktopDriver->UpdateSurfacePixelFormat( Globals::DesktopSurface ); }
/**************************************************************************\
* * Function Description: * * This routine receives a palette change notification request and appropriately * readjusts the system palette matching. * * History: * * 7/23/1999 ericvan * Created it. * \**************************************************************************/
VOID PaletteNotify() { Devlock devlock(Globals::DesktopDevice);
// update count to force lazy recomputation of translation vector
Globals::PaletteChangeCount++;
// update the system palette
Globals::DesktopDriver->PaletteChangeNotification(); }
/**************************************************************************\
* * Function Description: * * This routine receives a WM_SYSCOLORCHANGE notifications and updates the * system magic colors. * * History: * * 1/10/2K ericvan * Created it. * \**************************************************************************/
VOID SysColorNotify() { // [ericvan] There is no synchronization here. If a synchronization
// problem should occur, the worst side effect would be a bad
// color which would go away on a repaint. I think we can live with it.
Globals::SystemColors[16] = ::GetSysColor(COLOR_3DSHADOW); Globals::SystemColors[17] = ::GetSysColor(COLOR_3DFACE); Globals::SystemColors[18] = ::GetSysColor(COLOR_3DHIGHLIGHT); Globals::SystemColors[19] = ::GetSysColor(COLOR_DESKTOP); VGAHashRebuildTable(&Globals::SystemColors[16]); }
////////////////////////// MESSAGE/WINEVENT CALLBACKS //////////////////////////
/**************************************************************************\
* * Function Description: * * This routine is the GDI+ hidden window message pump. If the app doesn't * hook us directly, then we add a top-level window to intercept * WM_DISPLAYCHANGE and WM_PALETTECHANGED directly. * * History: * * 7/23/1999 ericvan * Created it. * \**************************************************************************/
LRESULT CALLBACK NotificationWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch (uMsg) { case WM_DISPLAYCHANGE: DisplayNotify(); break;
case WM_PALETTECHANGED: PaletteNotify(); break;
case WM_SYSCOLORCHANGE: SysColorNotify(); break;
case WM_WININICHANGE: if(lParam != 0 && lstrcmpiA((LPCSTR)(lParam), "intl") == 0) { Globals::UserDigitSubstituteInvalid = TRUE; } else if ((wParam == SPI_SETFONTSMOOTHING) || (wParam == SPI_SETFONTSMOOTHINGTYPE) || (wParam == SPI_SETFONTSMOOTHINGCONTRAST) || (wParam == SPI_SETFONTSMOOTHINGORIENTATION)) { Globals::CurrentSystemRenderingHintInvalid = TRUE; } break; default: if (Globals::g_nAccessibilityMessage == uMsg && uMsg >= WM_USER) { Globals::g_fAccessibilityPresent = TRUE; } else { return DefWindowProcA(hwnd, uMsg, wParam, lParam); } }
// return 0 if we processed it.
return 0; }
/**************************************************************************\
* * Function Description: * * This routine is the GDI+ win-event hook. It watches for full-drag * messages, to let the DCI renderer know when full-drag is being done. * * History: * * 3/21/2000 andrewgo * Created it. * \**************************************************************************/
VOID CALLBACK WinEventProcedure( HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime ) { ASSERT((event == EVENT_SYSTEM_MOVESIZESTART) || (event == EVENT_SYSTEM_MOVESIZEEND));
Globals::IsMoveSizeActive = (event == EVENT_SYSTEM_MOVESIZESTART); }
/////////////////////// MESSAGE/WINEVENT INITIALIZATION ////////////////////////
VOID InternalNotificationShutdown();
/**************************************************************************\
* * Function Description: * * Called by NotificationStartup and BackgroundThreadProc. * Initializes the hidden window and WinEvent hook. * * Preconditions: * * BackgroundThreadCriticalSection must be held. * * History: * * 9/15/2000 agodfrey * Created it. * \**************************************************************************/
BOOL InternalNotificationStartup() { // register a window class
// we force ANSI rep using GDI+ for benefit of Win9x
WNDCLASSA wndClass = { 0, &NotificationWndProc, 0, 0, DllInstance, NULL, NULL, NULL, "GDI+ Hook Window", "GDI+ Hook Window Class", }; Globals::WindowClassAtom = RegisterClassA(&wndClass);
if (!Globals::WindowClassAtom) { WARNING(("RegisterClass failed")); return FALSE; } // If this fails, we continue. It just means we won't work properly
// with accessibility software.
Globals::g_nAccessibilityMessage = RegisterWindowMessageA("GDI+ Accessibility"); Globals::HwndNotify = CreateWindowA((LPCSTR) Globals::WindowClassAtom, (LPCSTR) "GDI+ Window", WS_OVERLAPPED | WS_POPUP | WS_MINIMIZE, 0, 0, 1, 1, // x,y,width,height
NULL, // hWndParent
NULL, // hMenu
DllInstance, NULL); if (!Globals::HwndNotify) { WARNING(("CreateWindowA failed, the GDI+ hook window does not exist!")); InternalNotificationShutdown(); return FALSE; }
// [ericvan] This is BS, but must be done. We only receive palette
// messages if we have called SelectPalette at least once on our primary DC.
{ struct { LOGPALETTE logpal; PALETTEENTRY palEntry[256]; } lp; const ColorPalette* colorPal = GetDefaultColorPalette(PIXFMT_8BPP_INDEXED); lp.logpal.palVersion = 0x300; lp.logpal.palNumEntries = static_cast<WORD>(colorPal->Count); for (INT i=0; i<lp.logpal.palNumEntries; i++) { GpColor color(colorPal->Entries[i]); lp.logpal.palPalEntry[i].peRed = color.GetRed(); lp.logpal.palPalEntry[i].peGreen = color.GetGreen(); lp.logpal.palPalEntry[i].peBlue = color.GetBlue(); lp.logpal.palPalEntry[i].peFlags = 0; } HPALETTE hPal = CreatePalette(&lp.logpal); HDC hdc = GetDC(Globals::HwndNotify); SelectPalette(hdc, hPal, FALSE); ReleaseDC(Globals::HwndNotify, hdc); DeleteObject(hPal); } // [andrewgo] On NT, if a DCI lock is held while a window moves, NT is
// forced to redraw the whole screen. If "Show window contents while
// dragging" (AKA "Full-drag") is enabled (it's on by default),
// then this can result in repeated, excessive repaints
// of the whole screen while somone is dragging a window around.
//
// We work around this by disabling DCI rendering while we notice
// that window moves are happening.
if ((Globals::IsNt) && (Globals::SetWinEventHookFunction)) { Globals::WinEventHandle = (Globals::SetWinEventHookFunction)(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, NULL, WinEventProcedure, 0, 0, WINEVENT_OUTOFCONTEXT); ASSERT(Globals::WinEventHandle != NULL);
if (!Globals::WinEventHandle) { InternalNotificationShutdown(); return FALSE; } } return TRUE; }
/**************************************************************************\
* * Function Description: * * Called by NotificationStartup and BackgroundThreadProc. * (Also by InternalNotificationStartup, to clean up when there's an * error.) * * Destroys the hidden window and WinEvent hook. * * Keep this synchronized with SimulateInternalNotificationShutdown. * * Preconditions: * * BackgroundThreadSection must be held. * * History: * * 9/15/2000 agodfrey * Created it. * \**************************************************************************/
VOID InternalNotificationShutdown() { if (Globals::UnhookWinEventFunction && Globals::WinEventHandle) { (Globals::UnhookWinEventFunction)(Globals::WinEventHandle); Globals::WinEventHandle = NULL; } if (Globals::HwndNotify) { if (Globals::IsNt && (Globals::OsVer.dwMajorVersion == 4)) { // NT 4.0 has a problem in its DestroyWindow that will
// leave the application in a zombie state.
// Leak the window and rely on process cleanup.
} else { DestroyWindow(Globals::HwndNotify); } Globals::HwndNotify = NULL; } if (Globals::WindowClassAtom) { UnregisterClassA((LPCSTR)Globals::WindowClassAtom, DllInstance); Globals::WindowClassAtom = NULL; } }
/**************************************************************************\
* * Function Description: * * If the thread quits without cleaning up, this fixes our state * to avoid crashing later. * * "Cleans up" what it can - keeps the state consistent, but may leak. * * Preconditions: * * BackgroundThreadCriticalSection must be held. * * History: * * 9/16/2000 agodfrey * Created it. * \**************************************************************************/
VOID SimulateInternalNotificationShutdown() { // UnhookWinEvent can't be called from a different thread; so if this
// causes a leak, we can't help it.
Globals::WinEventHandle = NULL;
// DestroyWindow can't be called from a different thread; so if this
// causes a leak, we can't help it.
Globals::HwndNotify = NULL; // I don't know about UnregisterClass. I'm assuming we can't call it here.
// Anyway, the window may not have been destroyed, and MSDN says that must
// happen first. So, if need be, we'll leak this too.
Globals::WindowClassAtom = NULL; }
/**************************************************************************\
* * Function Description: * * Starts our top-level window, and sets up the WndProc and WinEventHook. * This must be called from a GUI thread - it's called from either our * own background thread, or by the app (via callback pointers returned * from GdiplusStartup). * * History: * * 9/15/2000 agodfrey * Created it. * \**************************************************************************/
GpStatus WINAPI NotificationStartup( OUT ULONG_PTR *token ) { GdiplusStartupCriticalSection critsec;
// Generate the first token, if necessary.
// Also handles wraparound.
if (Globals::NotificationInitToken == 0) { Globals::NotificationInitToken = GenerateInitToken(); // Make sure that the token isn't one of the "special" values.
if (Globals::NotificationInitToken <= NotificationModuleTokenMax) { Globals::NotificationInitToken = NotificationModuleTokenMax + 1; } } // If there's no hidden window yet, create one.
if (Globals::HiddenWindowOwnerToken == NotificationModuleTokenNobody) { // If there's a background thread, then the owner should be set to
// 'NotificationModuleTokenGdiplus'.
ASSERT (Globals::ThreadNotify == NULL);
{ // We take BackgroundThreadCriticalSection because that's a
// precondition for InternalNotificationStartup(). I know that we
// don't actually need to (there's no background thread at this
// point) - but code can change, so this is safer.
BackgroundThreadCriticalSection critsec;
if (!InternalNotificationStartup()) { return GenericError; } }
// Store the token of this calling module - when it calls
// NotificationShutdown, we must destroy the hidden window (and
// start up the background thread, if necessary).
Globals::HiddenWindowOwnerToken = Globals::NotificationInitToken; } *token = Globals::NotificationInitToken;
// Increment the token counter for the next module
Globals::NotificationInitToken++; return Ok; }
/**************************************************************************\
* * Function Description: * * Shuts down our top-level window, WndProc and WinEventHook. * This must be called from a GUI thread - it's called from either our * own background thread, or by the app (via callback pointers returned * from GdiplusStartup). * * History: * * 9/15/2000 agodfrey * Created it. * \**************************************************************************/
VOID WINAPI NotificationShutdown( ULONG_PTR token ) { GdiplusStartupCriticalSection critsec; // The token they pass us should be the one we gave them, so it shouldn't
// be one of the 'special values'.
if (token <= NotificationModuleTokenMax) { RIP(("Invalid token passed to NotificationShutdown")); // Ignore the call.
return; } if (token == Globals::HiddenWindowOwnerToken) { // The module that created the hidden window is shutting down.
// There shouldn't be a background thread.
ASSERT (Globals::ThreadNotify == NULL); { BackgroundThreadCriticalSection critsec;
InternalNotificationShutdown(); }
Globals::HiddenWindowOwnerToken = NotificationModuleTokenNobody;
// If this is not the final module to shut down, start up the
// background thread
if (Globals::LibraryInitRefCount > 1) { if (!BackgroundThreadStartup()) { // !!! [johnstep] Ack, what can we do now? Another client may
// be happily using GDI+ and now we've lost
// our message notifications.
WARNING(("Could not start background thread")); } } } }
////////////////////////////// BACKGROUND THREAD ///////////////////////////////
/**************************************************************************\
* * Function Description: * * Thread proc for our background GUI thread. Sets up a hidden window, * WndProc and WinEventHook, then starts the message loop. * * History: * * 7/23/1999 ericvan * Created it. * 9/15/2000 agodfrey * #175866: Improved GDI+ startup, shutdown and event notification * \**************************************************************************/
DWORD WINAPI BackgroundThreadProc( VOID* ) { BOOL error=FALSE; HANDLE threadQuitEvent; { BackgroundThreadCriticalSection critsec;
// Read threadQuitEvent under the critical section - ensures that
// we don't get the NULL that was there before the main thread
// initialized it. We can assume, though, that it won't change until
// this thread ends.
threadQuitEvent = Globals::ThreadQuitEvent;
if (!InternalNotificationStartup()) { error = TRUE; } }
if (error) { return 0; }
// [agodfrey] We used to have a call to "WaitForInputIdle" here,
// which caused problems. It was motivated by Shell and DDE -
// since calling GetMessage() signals user that "the app is
// ready to receive DDE messages", and we were doing
// it in PROCESS_ATTACH, long before the app was really ready.
//
// Now, we simply disallow initializing GDI+ in PROCESS_ATTACH.
// Process window messages
// We use MsgWaitForMultipleObjects, so that we can catch both messages
// and our "quit" event being signalled.
DWORD dwWake; MSG msg; BOOL quit = FALSE; while (!quit) { dwWake = MsgWaitForMultipleObjects( 1, &threadQuitEvent, FALSE, INFINITE, QS_ALLINPUT); if (dwWake == WAIT_OBJECT_0) { // Our "quit" event was signaled.
quit = TRUE; break; } else if (dwWake == WAIT_OBJECT_0 + 1) { // We received a message
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { quit = TRUE; break; } TranslateMessage(&msg); DispatchMessageA(&msg); } } else { RIP(("Unexpected return value from MsgQaitForMultipleObjects")); } }
// Clean up:
{ BackgroundThreadCriticalSection critsec; InternalNotificationShutdown(); } return 1; }
/**************************************************************************\
* * Function Description: * * Starts up the background thread. If the user doesn't ask us to piggyback * our hidden window onto their main GUI thread, we end up here, to create * our own. * * Preconditions: * * GdiplusStartupCriticalSection must be held. * * History: * * 7/23/1999 ericvan * Created it. * 9/15/2000 agodfrey * #175866: Improved GDI+ startup, shutdown and event notification * \**************************************************************************/
BOOL BackgroundThreadStartup() { ASSERT(Globals::HiddenWindowOwnerToken == NotificationModuleTokenNobody); // [agodfrey] Create an event object. We'll use this to tell the
// background thread to quit.
HANDLE threadQuitEvent = CreateEventA(NULL, TRUE, FALSE, NULL); if (threadQuitEvent == NULL) { WARNING(("CreateEvent failed: %d", GetLastError())); BackgroundThreadShutdown(); return FALSE; } { // Store threadQuitEvent while holding the correct critsec.
BackgroundThreadCriticalSection critsec; Globals::ThreadQuitEvent = threadQuitEvent; }
// Create the background thread.
Globals::ThreadNotify = CreateThread(NULL, // LPSECURITY_ATTRIBUTES
0, // same stack size
&BackgroundThreadProc, 0, // parameter to thread
0, // creation flags
&Globals::ThreadId);
if (Globals::ThreadNotify == NULL) { BackgroundThreadShutdown(); return FALSE; } // Record the fact that GDI+ has its own hidden window, and so
// NotificationStartup shouldn't create another one.
Globals::HiddenWindowOwnerToken = NotificationModuleTokenGdiplus; return TRUE; }
/**************************************************************************\
* * Function Description: * * Shuts down the background thread. * * Preconditions: * * GdiplusStartupCriticalSection must be held. * BackgroundThreadCriticalSection must *NOT* be held (we would deadlock). * * History: * * 7/23/1999 ericvan * Created it. * 9/15/2000 agodfrey * #175866: Improved GDI+ startup, shutdown and event notification. * Made it more robust by adding an event, and changing the thread's * message loop so that it quits when the event is signaled. * \**************************************************************************/
VOID BackgroundThreadShutdown() { // Stop the background thread
if (Globals::ThreadNotify != NULL) { ASSERT(Globals::HiddenWindowOwnerToken == NotificationModuleTokenGdiplus); // We want to be careful not to hold BackgroundThreadCriticalSection
// while we wait for the thread to terminate, since that could
// cause a deadlock situation (our wait would time out).
HANDLE threadQuitEvent; { BackgroundThreadCriticalSection critsec; threadQuitEvent = Globals::ThreadQuitEvent; } ASSERT(threadQuitEvent); // If it's NULL, ThreadNotify should be NULL.
SetEvent(threadQuitEvent);
DWORD ret = WaitForSingleObject(Globals::ThreadNotify, INFINITE); ASSERT(ret == WAIT_OBJECT_0); CloseHandle(Globals::ThreadNotify); Globals::ThreadNotify = NULL; Globals::ThreadId = 0; Globals::HiddenWindowOwnerToken = NotificationModuleTokenNobody; }
{ BackgroundThreadCriticalSection critsec; // [agodfrey] I discovered that, if InternalGdiplusShutdown is called
// from PROCESS_DETACH, the system will have terminated the thread
// already; WaitForSingleObject returns immediately because the
// thread has already stopped running.
//
// In this case, InternalNotificationShutdown() isn't called, i.e. the
// globals it cleans up are still non-NULL. I deem this "ok" because,
// if we're in PROCESS_DETACH, no-one's going to read those variables
// again.
//
// Still, I don't know if there are other legitimate ways for the
// thread to end without it cleaning up properly. So we call
// SimulateInternalNotificationShutdown() just to be safe - it's not
// very expensive.
SimulateInternalNotificationShutdown(); // Destroy the "quit" event
if (Globals::ThreadQuitEvent) { CloseHandle(Globals::ThreadQuitEvent); Globals::ThreadQuitEvent = NULL; } } }
|