/****************************** Module Header ******************************\ * Module Name: createw.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * Contains xxxCreateWindow, xxxDestroyWindow, and a few close friends. * * Note that during creation or deletion, the window is locked so that * it can't be deleted recursively. * * History: * 19-Oct-1990 DarrinM Created. * 11-Feb-1991 JimA Added access checks. * 19-Feb-1991 MikeKe Added Revalidation code * 20-Jan-1992 IanJa ANSI/UNICODE neutralization \***************************************************************************/ #include "precomp.h" #pragma hdrstop BOOL WantImeWindow(PWND pwndParent, PWND pwnd); #if DBG VOID VerifyWindowLink(PWND pwnd, PWND pwndParent, BOOL fLink); #endif /***************************************************************************\ * xxxCreateWindowEx (API) * * History: * 10-18-90 darrinm Ported from Win 3.0 sources. * 02-07-91 DavidPe Added Win 3.1 WH_CBT support. * 02-11-91 JimA Added access checks. * 04-11-92 ChandanC Added initialization of WOW words \***************************************************************************/ PWND xxxCreateWindowEx( DWORD dwExStyle, PLARGE_STRING cczpstrNVClass, PLARGE_STRING cczpstrClass, PLARGE_STRING cczpstrName, DWORD style, int x, int y, int cx, int cy, PWND pwndParent, PMENU pMenu, HANDLE hInstance, LPVOID lpCreateParams, DWORD dwExpWinVerAndFlags, PACTIVATION_CONTEXT pActCtx) { /* * The buffers for Class and Name may be client memory, and access * to those buffers must be protected. */ UINT mask = 0; BOOL fChild; BOOL fDefPos = FALSE; BOOL fStartup = FALSE; PCLS pcls; PPCLS ppcls; RECT rc; int dx, dy; SIZERECT src; int sw = SW_SHOW; PWND pwnd; PWND pwndZOrder, pwndHardError; CREATESTRUCTEX csex; PDESKTOP pdesk; ATOM atomT; PTHREADINFO ptiCurrent = PtiCurrent(); TL tlpwnd; TL tlpwndParent; TL tlpwndParentT; BOOL fLockParent = FALSE; WORD wWFAnsiCreator = 0; DWORD dw; DWORD dwMinMax; PMONITOR pMonitor; BOOL fTiled; CheckLock(pwndParent); UserAssert(IsWinEventNotifyDeferredOK()); #ifdef LAZY_CLASS_INIT if ((ptiCurrent->ppi->W32PF_Flags & W32PF_CLASSESREGISTERED) == 0) { if (!LW_RegisterWindows()) { RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "LW_RegisterWindows failed."); return NULL; } } #endif /* * For Edit Controls (including those in comboboxes), we must know whether * the App used an ANSI or a Unicode CreateWindow call. This is passed in * with the private WS_EX_ANSICREATOR dwExStyle bit, but we MUST NOT leave * out this bit in the window's dwExStyle! Transfer to the internal window * flag WFANSICREATOR immediately. */ if (dwExStyle & WS_EX_ANSICREATOR) { wWFAnsiCreator = WFANSICREATOR; dwExStyle &= ~WS_EX_ANSICREATOR; } /* * After grocking any bits, we should no longer be using any private bits. */ UserAssert((dwExStyle & WS_EXP_PRIVATE) == 0); /* * If this thread has already been in xxxDestroyThreadInfo, then this window * is probably going to end up with a bogus pti. */ UserAssert(!(ptiCurrent->TIF_flags & TIF_INCLEANUP)); pdesk = ptiCurrent->rpdesk; /* * If a parent window is specified, make sure it's on the same desktop. */ if (pwndParent != NULL && pwndParent->head.rpdesk != pdesk) { RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, ""); return NULL; } /* * Set a flag indicating whether it is a child window. */ fChild = ((HIWORD(style) & MaskWF(WFTYPEMASK)) == MaskWF(WFCHILD)); /* * The WS_EX_LAYOUT_RTL flag is set if, * * (1) WS_EX_LAYOUT_RTL set in dwExStyle parameter of the CreateWindow call. * * (2) If the window is created from DialogBox class, then it can't inherit * from its parent and it has to specify WS_EX_LAYOUTRTL explicitly to * enable mirroring on it. * * (3) If the window is an owned window then the window is left to right * layout and the algorithm terminates. (An owned window is one created * with an HWND passed in the hWndParent paremeter to CreateWindow(Ex), * but without the WS_CHILD flag present in it's styles. * * (4) If the window is a child window, and it's parent is right to left * layout, and it's parent does not have the WS_EX_NOINHERIT_LAYOUT flag * set in it's extended styles, then the window is right to left layout * and the algorithm terminates. * * (5) If the hWndParent parameter to Createwindow(Ex) was NULL, and the * process calling CreateWindow(Ex) has called * SetProcessDefaultLayout(LAYOUT_RTL), then the window is right to left * layout and the algorithm terminates. * * (6) In all other cases, the layout is left to right. */ if (!(dwExStyle & WS_EX_LAYOUTRTL)) { if (pwndParent != NULL) { if (fChild && TestWF(pwndParent, WEFLAYOUTRTL) && !TestWF(pwndParent, WEFNOINHERITLAYOUT)) { dwExStyle |= WS_EX_LAYOUTRTL; } } else if (!(!IS_PTR(cczpstrNVClass) && (PTR_TO_ID(cczpstrNVClass) == PTR_TO_ID(DIALOGCLASS)))) { if ((PpiCurrent()->dwLayout & LAYOUT_RTL)) { dwExStyle |= WS_EX_LAYOUTRTL; } } } /* * Ensure that we can create the window. If there is no desktop * yet, assume that this will be the root desktop window and allow * the creation. */ if (ptiCurrent->hdesk) { RETURN_IF_ACCESS_DENIED( ptiCurrent->amdesk, DESKTOP_CREATEWINDOW, NULL); } if (fChild) { /* * Don't allow child windows without a parent handle. */ if (pwndParent == NULL) { RIPERR0(ERROR_TLW_WITH_WSCHILD, RIP_WARNING, ""); return NULL; } if (!ValidateParentDepth(NULL, pwndParent)) { RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "Exceeded nested children limit"); return NULL; } } /* * Make sure we can get the window class. */ if (IS_PTR(cczpstrClass)) { /* * UserFindAtom protects access of the string. */ atomT = UserFindAtom(cczpstrClass->Buffer); } else { atomT = PTR_TO_ID(cczpstrClass); } if (atomT == 0) { CantFindClassMessageAndFail: #if DBG if (IS_PTR(cczpstrNVClass)) { try { RIPMSG1(RIP_VERBOSE, "Couldn't find class string %ws", cczpstrNVClass->Buffer); } except (W32ExceptionHandler(FALSE, RIP_WARNING)) { } } else { RIPMSG1(RIP_VERBOSE, "Couldn't find class atom 0x%x", cczpstrNVClass); } #endif RIPERR0(ERROR_CANNOT_FIND_WND_CLASS, RIP_VERBOSE, ""); return NULL; } /* * First scan the private classes. If we don't find the class there scan * the public classes. If we don't find it there, fail. */ ppcls = GetClassPtr(atomT, ptiCurrent->ppi, hInstance); if (ppcls == NULL) { goto CantFindClassMessageAndFail; } pcls = *ppcls; if (NeedsWindowEdge(style, dwExStyle, Is400Compat(dwExpWinVerAndFlags))) { dwExStyle |= WS_EX_WINDOWEDGE; } else { dwExStyle &= ~WS_EX_WINDOWEDGE; } /* * Allocate memory for regular windows. */ pwnd = HMAllocObject( ptiCurrent, pdesk, TYPE_WINDOW, sizeof(WND) + pcls->cbwndExtra); if (pwnd == NULL) { RIPERR0(ERROR_OUTOFMEMORY, RIP_WARNING, "Out of pool in xxxCreateWindowEx"); return NULL; } /* * Stuff in the pq, class pointer, and window style. */ pwnd->pcls = pcls; pwnd->style = style & ~WS_VISIBLE; pwnd->ExStyle = dwExStyle & ~(WS_EX_LAYERED | WS_EX_COMPOSITED); pwnd->cbwndExtra = pcls->cbwndExtra; /* * Increment the window reference count in the class structure. * Because xxxFreeWindow() decrements the count, incrementing has * to be done now. In the case of error, xxxFreeWindow() will decrement it. */ if (!ReferenceClass(pcls, pwnd)) { HMFreeObject(pwnd); goto CantFindClassMessageAndFail; } /* * Set the window active App context to be activated whenever we call * the user WndProc. */ pwnd->pActCtx = pActCtx; /* * Button control doesn't need input context. Other windows * will associate with the default input context. * N.b. this comparison needs to be performed on the NV class * name. */ if (pcls->atomNVClassName == gpsi->atomSysClass[ICLS_BUTTON]) { pwnd->hImc = NULL_HIMC; } else { pwnd->hImc = (HIMC)PtoH(ptiCurrent->spDefaultImc); } /* * Update the window count. Doing this now will ensure that if * the creation fails, xxxFreeWindow will keep the window count * correct. */ ptiCurrent->cWindows++; /* * Get the class from the window because ReferenceClass may have * cloned the class. */ pcls = pwnd->pcls; /* * This is a replacement for the &lpCreateParams stuff that used to * pass a pointer directly to the parameters on the stack. This * step must be done AFTER referencing the class because we * may use the ANSI class name. */ RtlZeroMemory(&csex, sizeof(csex)); csex.cs.dwExStyle = dwExStyle; csex.cs.hInstance = hInstance; if (!IS_PTR(cczpstrNVClass)) { csex.cs.lpszClass = (LPWSTR)cczpstrNVClass; } else { if (wWFAnsiCreator) { csex.cs.lpszClass = (LPWSTR)pcls->lpszAnsiClassName; if (IS_PTR(csex.cs.lpszClass)) { RtlInitLargeAnsiString( (PLARGE_ANSI_STRING)&csex.strClass, (LPSTR)csex.cs.lpszClass, (UINT)-1); } } else { csex.cs.lpszClass = cczpstrNVClass->Buffer; csex.strClass = *cczpstrNVClass; } } if (cczpstrName != NULL) { csex.cs.lpszName = cczpstrName->Buffer; csex.strName = *cczpstrName; } csex.cs.style = style; csex.cs.x = x; csex.cs.y = y; csex.cs.cx = cx; csex.cs.cy = cy; csex.cs.hwndParent = HW(pwndParent); /* * If pMenu is non-NULL and the window is not a child, pMenu must * be a menu. * Child windows get their UIState bits from their parent. Top level ones * remain with the default cleared bits. * * The below test is equivalent to TestwndChild(). */ if (fChild) { csex.cs.hMenu = (HMENU)pMenu; pwnd->ExStyle |= pwndParent->ExStyle & WS_EXP_UIVALID; #if WS_EXP_UIACCELHIDDEN != 0x40000000 #error Fix UISTATE bits copying if you moved the UISTATE bits from ExStyle #endif } else { csex.cs.hMenu = PtoH(pMenu); } csex.cs.lpCreateParams = lpCreateParams; /* * ThreadLock: we are going to be doing multiple callbacks here. */ ThreadLockAlwaysWithPti(ptiCurrent, pwnd, &tlpwnd); /* * set the parent to be the desktop window (if exists) * before any callback. This way we'll always have a * pointer on spwndParent */ if (pwnd->head.rpdesk) { Lock(&(pwnd->spwndParent), PWNDMESSAGE(pwnd)); } /* * Create the class small icon if there isn't one since we are in context * and we are creating a window from this class... */ if (pcls->spicn && !pcls->spicnSm) { xxxCreateClassSmIcon(pcls); } /* * Store the instance handle and window proc address. We do this earlier * than Windows because they have a bug were a message can be sent * but lpfnWndProc is not set (3986 CBT WM_CREATE not allowed.) */ pwnd->hModule = hInstance; /* * Get rid of EditWndProc plain. */ pwnd->lpfnWndProc = (WNDPROC_PWND)MapClientNeuterToClientPfn(pcls, 0, wWFAnsiCreator); /* * If this window class has a server-side window procedure, mark * it as such. If the app subclasses it later with an app-side proc * then this mark will be removed. */ if (pcls->CSF_flags & CSF_SERVERSIDEPROC) { SetWF(pwnd, WFSERVERSIDEPROC); UserAssert(!(pcls->CSF_flags & CSF_ANSIPROC)); } /* * If this window was created with an ANSI CreateWindow*() call, mark * it as such so edit controls will be created correctly. (A combobox * will be able to pass the WFANSICREATOR bit on to its edit control) */ SetWF(pwnd, wWFAnsiCreator); /* * If this window belongs to an ANSI class or it is a WFANSICREATOR * control, then mark it as an ANSI window */ if ((pcls->CSF_flags & CSF_ANSIPROC) || (wWFAnsiCreator && ((atomT == gpsi->atomSysClass[ICLS_BUTTON]) || (atomT == gpsi->atomSysClass[ICLS_COMBOBOX]) || (atomT == gpsi->atomSysClass[ICLS_COMBOLISTBOX]) || (atomT == gpsi->atomSysClass[ICLS_DIALOG]) || (atomT == gpsi->atomSysClass[ICLS_EDIT]) || (atomT == gpsi->atomSysClass[ICLS_LISTBOX]) || (atomT == gpsi->atomSysClass[ICLS_MDICLIENT]) || (atomT == gpsi->atomSysClass[ICLS_IME]) || (atomT == gpsi->atomSysClass[ICLS_STATIC])))) { SetWF(pwnd, WFANSIPROC); } /* * If a 3.1-compatible application is creating the window, set this * bit to enable various backward-compatibility hacks. * * If it's not 3.1 compatible, see if we need to turn on the PixieHack * (see wmupdate.c for more info on this) */ dw = GetAppCompatFlags(ptiCurrent); if (dw & GACF_RANDOM3XUI) { SetWF(pwnd, WFOLDUI); dwExStyle &= 0x0000003f; csex.cs.dwExStyle &= 0x0000003f; } pwnd->hMod16 = ((ptiCurrent->TIF_flags & TIF_16BIT) && !TestWF(pwnd, WFSERVERSIDEPROC))? xxxClientWOWGetProcModule(pwnd->lpfnWndProc):0; if (Is310Compat(dwExpWinVerAndFlags)) { SetWF(pwnd, WFWIN31COMPAT); if (Is400Compat(dwExpWinVerAndFlags)) { SetWF(pwnd, WFWIN40COMPAT); if (Is500Compat(dwExpWinVerAndFlags)) { SetWF(pwnd, WFWIN50COMPAT); } } } else if (dw & GACF_ALWAYSSENDNCPAINT) { SetWF(pwnd, WFALWAYSSENDNCPAINT); } /* * If we've got a registered DefWindowProc handler, make sure it's DLL * is loaded for this process. */ if (IsInsideUserApiHook()) { xxxLoadUserApiHook(); } /* * Inform the CBT hook that a window is being created. Pass it the * CreateParams and the window handle that the new one will be inserted * after. The CBT hook handler returns TRUE to prevent the window * from being created. It can also modify the CREATESTRUCT info, which * will affect the size, parent, and position of the window. * Defaultly position non-child windows at the top of their list. */ if (IsHooked(ptiCurrent, WHF_CBT)) { CBT_CREATEWND cbt; /* * Use the extended createstruct so the hook thunk can * handle the strings correctly. */ cbt.lpcs = (LPCREATESTRUCT)&csex; cbt.hwndInsertAfter = HWND_TOP; if ((BOOL)xxxCallHook(HCBT_CREATEWND, (WPARAM)HWq(pwnd), (LPARAM)&cbt, WH_CBT)) { goto MemError; } else { /* * The CreateHook may have modified some parameters so write them * out (in Windows 3.1 we used to write directly to the variables * on the stack). */ x = csex.cs.x; y = csex.cs.y; cx = csex.cs.cx; cy = csex.cs.cy; pwndZOrder = PWInsertAfter(cbt.hwndInsertAfter); } } else { pwndZOrder = (PWND)HWND_TOP; } if (!(fTiled = TestwndTiled(pwnd))) { /* * CW_USEDEFAULT is only valid for tiled and overlapped windows. * Don't let it be used. */ if (x == CW_USEDEFAULT || x == CW2_USEDEFAULT) { x = 0; y = 0; } if (cx == CW_USEDEFAULT || cx == CW2_USEDEFAULT) { cx = 0; cy = 0; } } /* * Make local copies of these parameters. */ src.x = x; src.y = y; src.cx = cx; src.cy = cy; /* * Position Child Windows */ if (fChild = (BOOL)TestwndChild(pwnd)) { /* * Child windows are offset from the parent's origin. */ UserAssert(pwndParent); if (pwndParent != PWNDDESKTOP(pwnd)) { src.x += pwndParent->rcClient.left; src.y += pwndParent->rcClient.top; } /* * Defaultly position child windows at bottom of their list. */ pwndZOrder = PWND_BOTTOM; } /* * Position Tiled Windows */ /* * Is this a Tiled/Overlapping window? */ if (fTiled) { /* * Force the WS_CLIPSIBLINGS window style and add a caption and * a border. */ SetWF(pwnd, WFCLIPSIBLINGS); mask = MaskWF(WFCAPTION) | MaskWF(WFBORDER); // // We add on a raised edge since IF the person had passed in WS_CAPTION, // and didn't specify any 3D borders, we would've added it on to the // style bits above. // if (TestWF(pwnd, WFWIN40COMPAT)) { SetWF(pwnd, WEFWINDOWEDGE); } /* * Set bit that will force size message to be sent at SHOW time. */ SetWF(pwnd, WFSENDSIZEMOVE); /* * Here is how the "tiled" window initial positioning works... * If the app is a 1.0x app, then we use our standard "stair step" * default positioning scheme. Otherwise, we check the x & cx * parameters. If either of these == CW_USEDEFAULT then use the * default position/size, otherwise use the position/size they * specified. If not using default position, use SW_SHOW for the * xxxShowWindow() parameter, otherwise use the y parameter given. * * In 32-bit world, CW_USEDEFAULT is 0x80000000, but apps still * store word-oriented values either in dialog templates or * in their own structures. So CreateWindow still recognizes the * 16 bit equivalent, which is 0x8000, CW2_USEDEFAULT. The original * is changed because parameters to CreateWindow() are 32 bit * values, which can cause sign extention, or weird results if * 16 bit math assumptions are being made, etc. */ /* * Default to passing the y parameter to xxxShowWindow(). */ if (x == CW_USEDEFAULT || x == CW2_USEDEFAULT) { /* * If the y value is not CW_USEDEFAULT, use it as a SW_* command. */ if (src.y != CW_USEDEFAULT && src.y != CW2_USEDEFAULT) { sw = src.y; } } /* * Allow the shell to tell us what monitor to run this app on */ pMonitor = NULL; if ( x == CW_USEDEFAULT || x == CW2_USEDEFAULT || cx == CW_USEDEFAULT || cx == CW2_USEDEFAULT) { if (ptiCurrent->ppi->hMonitor) { pMonitor = ValidateHmonitor(ptiCurrent->ppi->hMonitor); } else if (pwndParent) { pMonitor = _MonitorFromWindow(pwndParent, MONITOR_DEFAULTTONEAREST); } } if (!pMonitor) { pMonitor = GetPrimaryMonitor(); } SetTiledRect(pwnd, &rc, pMonitor); /* * Did the app ask for default positioning? */ if (x == CW_USEDEFAULT || x == CW2_USEDEFAULT) { /* * Use default positioning. */ if (ptiCurrent->ppi->usi.dwFlags & STARTF_USEPOSITION ) { fStartup = TRUE; x = src.x = ptiCurrent->ppi->usi.dwX; y = src.y = ptiCurrent->ppi->usi.dwY; } else { x = src.x = rc.left; y = src.y = rc.top; } fDefPos = TRUE; } else { /* * Use the apps specified positioning. Undo the "stacking" * effect caused by SetTiledRect(). */ if (pMonitor->cWndStack) { pMonitor->cWndStack--; } } /* * Did the app ask for default sizing? */ if (src.cx == CW_USEDEFAULT || src.cx == CW2_USEDEFAULT) { /* * Use default sizing. */ if (ptiCurrent->ppi->usi.dwFlags & STARTF_USESIZE) { fStartup = TRUE; src.cx = ptiCurrent->ppi->usi.dwXSize; src.cy = ptiCurrent->ppi->usi.dwYSize; } else { src.cx = rc.right - x; src.cy = rc.bottom - y; } fDefPos = TRUE; } else if (fDefPos) { /* * The app wants default positioning but not default sizing. * Make sure that it's still entirely visible by moving the * window. */ dx = (src.x + src.cx) - pMonitor->rcMonitor.right; dy = (src.y + src.cy) - pMonitor->rcMonitor.bottom; if (dx > 0) { x -= dx; src.x = x; if (src.x < pMonitor->rcMonitor.left) { src.x = x = pMonitor->rcMonitor.left; } } if (dy > 0) { y -= dy; src.y = y; if (src.y < pMonitor->rcMonitor.top) { src.y = y = pMonitor->rcMonitor.top; } } } } /* * If we have used any startup postitions, turn off the startup * info so we don't use it again. */ if (fStartup) { ptiCurrent->ppi->usi.dwFlags &= ~(STARTF_USESIZE | STARTF_USEPOSITION); } if (TestwndPopup(pwnd)) { /* * Force the clipsiblings/overlap style. */ SetWF(pwnd, WFCLIPSIBLINGS); } /* * Shove in those default style bits. */ *(((WORD *)&pwnd->style) + 1) |= mask; /* * Menu/SysMenu Stuff */ /* * If there is no menu handle given and it's not a child window but * there is a class menu, use the class menu. */ if (pMenu == NULL && !fChild && (pcls->lpszMenuName != NULL)) { UNICODE_STRING strMenuName; RtlInitUnicodeStringOrId(&strMenuName, pcls->lpszMenuName); pMenu = xxxClientLoadMenu(pcls->hModule, &strMenuName); csex.cs.hMenu = PtoH(pMenu); /* * This load fails if the caller does not have DESKTOP_CREATEMENU * permission but that's ok they will just get a window without a menu */ } /* * Store the menu handle. */ if (TestwndChild(pwnd)) { /* * It's an id in this case. */ pwnd->spmenu = pMenu; } else { /* * It's a real handle in this case. */ LockWndMenu(pwnd, &pwnd->spmenu, pMenu); } /* * Parent/Owner Stuff */ /* * If this isn't a child window, reset the Owner/Parent info. */ if (!fChild) { Lock(&(pwnd->spwndLastActive), pwnd); if ((pwndParent != NULL) && (pwndParent != pwndParent->head.rpdesk->spwndMessage) && (pwndParent != pwndParent->head.rpdesk->pDeskInfo->spwnd)) { PWND pwndOwner = GetTopLevelWindow(pwndParent); if (!ValidateOwnerDepth(pwnd, pwndOwner)) { RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "Exceeded nested owner limit for pwnd %#p", pwnd); goto MemError; } #if DBG if (pwnd->pcls->atomClassName == gpsi->atomSysClass[ICLS_IME]) { UserAssert(!TestCF(pwndOwner, CFIME)); } #endif Lock(&(pwnd->spwndOwner), pwndOwner); if (pwnd->spwndOwner && TestWF(pwnd->spwndOwner, WEFTOPMOST)) { /* * If this window's owner is a topmost window, then it has to * be one also since a window must be above its owner. */ SetWF(pwnd, WEFTOPMOST); } /* * If this is a owner window on another thread, share input * state so this window gets z-ordered correctly. */ if (atomT != gpsi->atomSysClass[ICLS_IME] && pwnd->spwndOwner != NULL && GETPTI(pwnd->spwndOwner) != ptiCurrent) { /* * No need to DeferWinEventNotify() here: pwnd and pwndParent * are locked and because we called ReferenceClass(pcls, pwnd), * pcls is safe until xxxFreeWindow(pwnd). (IanJa) */ zzzAttachThreadInput(ptiCurrent, GETPTI(pwnd->spwndOwner), TRUE); } } else { pwnd->spwndOwner = NULL; } #if DBG if (ptiCurrent->rpdesk != NULL) { UserAssert(!(ptiCurrent->rpdesk->dwDTFlags & (DF_DESTROYED | DF_DESKWNDDESTROYED | DF_DYING))); } #endif if ((pwndParent == NULL) || (pwndParent != pwndParent->head.rpdesk->spwndMessage)) { pwndParent = _GetDesktopWindow(); ThreadLockWithPti(ptiCurrent, pwndParent, &tlpwndParent); fLockParent = TRUE; } } /* * Store backpointer to parent. */ if ((pwnd->spwndNext != NULL) || (pwnd->spwndPrev != NULL)) { RIPMSG1(RIP_WARNING, "Window %#p linked in too early (in a hook callback)", pwnd); UnlinkWindow(pwnd, pwnd->spwndParent); } Lock(&(pwnd->spwndParent), pwndParent); /* * Final Window Positioning */ if (!TestWF(pwnd, WFWIN31COMPAT)) { /* * BACKWARD COMPATIBILITY HACK * * In 3.0, CS_PARENTDC overrides WS_CLIPCHILDREN and WS_CLIPSIBLINGS, * but only if the parent is not WS_CLIPCHILDREN. * This behavior is required by PowerPoint and Charisma, among others. */ if ((pcls->style & CS_PARENTDC) && !TestWF(pwndParent, WFCLIPCHILDREN)) { #if DBG if (TestWF(pwnd, WFCLIPCHILDREN)) RIPMSG0(RIP_WARNING, "WS_CLIPCHILDREN overridden by CS_PARENTDC"); if (TestWF(pwnd, WFCLIPSIBLINGS)) RIPMSG0(RIP_WARNING, "WS_CLIPSIBLINGS overridden by CS_PARENTDC"); #endif ClrWF(pwnd, (WFCLIPCHILDREN | WFCLIPSIBLINGS)); } } /* * If this is a child window being created in a parent window * of a different thread, but not on the desktop, attach their * input streams together. [windows with WS_CHILD can be created * on the desktop, that's why we check both the style bits * and the parent window.] */ if (TestwndChild(pwnd) && (pwndParent != PWNDDESKTOP(pwnd)) && (ptiCurrent != GETPTI(pwndParent))) { /* * No need to DeferWinEventNotify() - there is an xxx call just below */ zzzAttachThreadInput(ptiCurrent, GETPTI(pwndParent), TRUE); } /* * Make sure the window is between the minimum and maximum sizes. */ /* * HACK ALERT! * This sends WM_GETMINMAXINFO to a (tiled or sizable) window before * it has been created (before it is sent WM_NCCREATE). * Maybe some app expects this, so we mustn't reorder the messages. */ xxxAdjustSize(pwnd, &src.cx, &src.cy); /* * Check for a window being created full screen. * * Note the check for a non-NULL pdeskParent -- this is important for CreateWindowStation */ if (pwnd->head.rpdesk != NULL && !TestWF(pwnd, WFCHILD) && !TestWF(pwnd, WEFTOOLWINDOW)) { xxxCheckFullScreen(pwnd, &src); } if (src.cx < 0) { RIPMSG1(RIP_WARNING, "xxxCreateWindowEx: adjusted cx in pwnd %#p", pwnd); src.cx = 0; } if (src.cy < 0) { RIPMSG1(RIP_WARNING, "xxxCreateWindowEx: adjusted cy in pwnd %#p", pwnd); src.cy = 0; } /* * Calculate final window dimensions... */ RECTFromSIZERECT(&pwnd->rcWindow, &src); if (TestCF2(pcls, CFOWNDC) || (TestCF2(pcls, CFCLASSDC) && pcls->pdce == NULL)) { if (NULL == CreateCacheDC(pwnd, DCX_OWNDC, NULL)) { RIPMSG1(RIP_WARNING, "xxxCreateWindowEx: pwnd %#p failed to create cached DC", pwnd); goto MemError; } } /* * Setup Layered and Composited windows. * * NOTE: This MUST be done AFTER CreateCacheDC() has built any DC's used for * OWNDC windows, since the redirection functions need to convert these DC's * to use redirection. In Windows 2000, this was done before calling * CreateCacheDC(), and would Assert inside ResetOrg() when because a new * DC was built that was not setup for redirection. */ if (dwExStyle & WS_EX_LAYERED) { if (!xxxSetLayeredWindow(pwnd, FALSE)) { RIPMSG1(RIP_WARNING, "xxxCreateWindowEx: pwnd %#p failed to setup layered window", pwnd); goto MemError; } } if (dwExStyle & WS_EX_COMPOSITED) { /* * We only want to turn WS_EX_COMPOSITED on if the parent-chain doesn't * already have WS_EX_COMPOSITED turned on. */ if (GetStyleWindow(pwnd->spwndParent, WEFCOMPOSITED) == NULL) { if (!SetRedirectedWindow(pwnd, REDIRECT_COMPOSITED)) { RIPMSG1(RIP_WARNING, "xxxCreateWindowEx: pwnd %#p failed to setup composited window", pwnd); goto MemError; } SetWF(pwnd, WEFCOMPOSITED); } } /* * Update the create struct now that we've modified some passed in * parameters. */ csex.cs.x = x; csex.cs.y = y; csex.cs.cx = cx; csex.cs.cy = cy; /* * Send a NCCREATE message to the window. */ if (!xxxSendMessage(pwnd, WM_NCCREATE, 0L, (LPARAM)&csex)) { MemError: #if DBG if (!IS_PTR(cczpstrNVClass)) { RIPMSG2(RIP_WARNING, (pwndParent) ? "xxxCreateWindowEx failed, Class=%#.4x, ID=%d" : "xxxCreateWindowEx failed, Class=%#.4x", PTR_TO_ID(cczpstrNVClass), (LONG_PTR) pMenu); } else { RIPMSG2(RIP_WARNING, (pwndParent) ? "xxxCreateWindowEx failed, Class=\"%s\", ID=%d" : "xxxCreateWindowEx failed, Class=\"%s\"", pcls->lpszAnsiClassName, (LONG_PTR) pMenu); } #endif if (fLockParent) ThreadUnlock(&tlpwndParent); /* * Set the state as destroyed so any z-ordering events will be ignored. * We cannot NULL out the owner field until WM_NCDESTROY is sent or * apps like Rumba fault (they call GetParent after every message). */ SetWF(pwnd, WFDESTROYED); /* * Unset the visible flag so we don't think in xxxDestroyWindow that * this window is visible. */ if (TestWF(pwnd, WFVISIBLE)) { SetVisible(pwnd, SV_UNSET); } /* * FreeWindow performs a ThreadUnlock. */ xxxFreeWindow(pwnd, &tlpwnd); return NULL; } /* * We need to set the lame button flag before doing the CFNOCLOSE stuff * below or the app won't get the lame button menu item in its sysmenu. */ #ifdef LAME_BUTTON if (NeedsLameButton(pwnd, pwndParent)) { SetWF(pwnd, WEFLAMEBUTTON); } #endif // LAME_BUTTON /* * Delete the Close menu item if directed. */ if (TestCF(pwnd, CFNOCLOSE)) { /* * Do this by position since the separator does not have an ID. */ pMenu = xxxGetSystemMenu(pwnd, FALSE); if (pMenu != NULL) { TL tlpMenu; ThreadLock(pMenu, &tlpMenu); xxxDeleteMenu(pMenu, 5 #ifdef LAME_BUTTON + !!TestWF(pwnd, WEFLAMEBUTTON) #endif // LAME_BUTTON , MF_BYPOSITION); xxxDeleteMenu(pMenu, 5 #ifdef LAME_BUTTON + !!TestWF(pwnd, WEFLAMEBUTTON) #endif // LAME_BUTTON , MF_BYPOSITION); ThreadUnlock(&tlpMenu); } } /* * WM_NCCREATE processing may have changed the window text. Change * the CREATESTRUCT to point to the real window text. * * MSMoney needs this because it clears the window and we need to * reflect the new name back into the cs structure. * A better thing to do would be to have a pointer to the CREATESTRUCT * within the window itself so that DefWindowProc can change the * the window name in the CREATESTRUCT to point to the real name and * this funky check is no longer needed. * * DefSetText converts a pointer to NULL to a NULL title so * we don't want to over-write cs.lpszName if it was a pointer to * a NULL string and pName is NULL. Approach Database for Windows creates * windows with a pointer to NULL and then accesses the pointer later * during WM_CREATE */ if (TestWF(pwnd, WFTITLESET)) if (!(csex.strName.Buffer != NULL && csex.strName.Length == 0 && pwnd->strName.Buffer == NULL)) { csex.cs.lpszName = pwnd->strName.Buffer; RtlCopyMemory(&csex.strName, &pwnd->strName, sizeof(LARGE_STRING)); } /* * The Window is now officially "created." Change the relevant global * stuff. */ /* * Create per thread default IME window. */ if (IS_IME_ENABLED() && ptiCurrent->spwndDefaultIme == NULL) { /* * Avoid creating the default IME window to any of message only windows * or windows on no I/O desktop. */ if (WantImeWindow(pwndParent, pwnd)) { BOOL bReinit; // // Make sure we are not creating a window for Ole, // for it does not pump messages even though // they creates a window. // UserAssert(gaOleMainThreadWndClass != atomT); Lock(&(ptiCurrent->spwndDefaultIme), xxxCreateDefaultImeWindow(pwnd, atomT, hInstance)); /* * If keybaord layout is switched but Imm activation was skipped * while spwndDefaultIme was gone, do the activation now. */ #if _DBG if (ptiCurrent->spDefaultImc == NULL) { RIPMSG1(RIP_WARNING, "xxxCreateWindowEx: ptiCurrent(%08p)->spDefaultImc is NULL.", ptiCurrent); } ASSERT(ptiCurrent->pClientInfo); #endif #ifdef CUAS_ENABLE /* * Load IME and Activate TIM for CUAS. * We can do this after ptiCurrent->spwndDefaultIME is valid. */ if (ptiCurrent->spwndDefaultIme) { TL tlpwndIme; ThreadLockAlways(ptiCurrent->spwndDefaultIme, &tlpwndIme); xxxSendMessage(ptiCurrent->spwndDefaultIme, WM_IME_SYSTEM, (WPARAM)IMS_LOADTHREADLAYOUT, (LPARAM)0L); ThreadUnlock(&tlpwndIme); } #endif try { bReinit = ((ptiCurrent->pClientInfo->CI_flags & CI_INPUTCONTEXT_REINIT) != 0); } except (W32ExceptionHandler(TRUE, RIP_WARNING)) { goto MemError2; } if (ptiCurrent->spwndDefaultIme && bReinit) { TL tlpwndIme; TAGMSG1(DBGTAG_IMM, "xxxCreateDefaultImeWindow: ptiCurrent(%08p)->spDefaultImc->fNeedClientImcActivate is set.", ptiCurrent); /* * Make this client side callback to force the input context * to be reinitialized appropriately (keyboard layout has * changed since this thread was taking a nap without a * window but still a GUI thread). * * Windows NT Bug #294964. */ ThreadLock(ptiCurrent->spwndDefaultIme, &tlpwndIme); xxxSendMessage(ptiCurrent->spwndDefaultIme, WM_IME_SYSTEM, (WPARAM)IMS_ACTIVATETHREADLAYOUT, (LPARAM)ptiCurrent->spklActive->hkl); // Reset the flag. try { ptiCurrent->pClientInfo->CI_flags &= ~CI_INPUTCONTEXT_REINIT; } except (W32ExceptionHandler(TRUE, RIP_WARNING)) { ThreadUnlock(&tlpwndIme); goto MemError2; } ThreadUnlock(&tlpwndIme); } } else { TAGMSG0(DBGTAG_IMM, "xxxCreateWindowEx: default IME window not created."); } } /* * Update the Parent/Child linked list. Don't (re)link the window if a * diffrent parent was set during a callback (e.g., WM_GETMINMAXINFO). */ if (pwndParent != NULL && pwnd->spwndParent == pwndParent) { /* * Unlink this window first, it might have been linked already by * calling SetParent() in any of the messages that we had previously * sent (e.g. WM_GETMINMAXINFO). */ UnlinkWindow(pwnd, pwnd->spwndParent); if (!fChild && (pwndParent != pwndParent->head.rpdesk->spwndMessage)) { /* * If this is a top-level window, and it's not part of the * topmost pile of windows, then we have to make sure it * doesn't go on top of any of the topmost windows. * * If he's trying to put the window on the top, or trying * to insert it after one of the topmost windows, insert * it after the last topmost window in the pile. */ if (!TestWF(pwnd, WEFTOPMOST)) { if (pwndZOrder == PWND_TOP || (IS_PTR(pwndZOrder) && TestWF(pwndZOrder, WEFTOPMOST))) { pwndZOrder = CalcForegroundInsertAfter(pwnd); } } else { pwndHardError = GETTOPMOSTINSERTAFTER(pwnd); if (pwndHardError != NULL) { pwndZOrder = pwndHardError; } } } LinkWindow(pwnd, pwndZOrder, pwndParent); } #if DBG if (pwndParent != NULL) { VerifyWindowLink (pwnd, pwnd->spwndParent, TRUE); if (pwnd->spwndParent != pwndParent) { RIPMSGF1(RIP_WARNING, "Window 0x%p re-parented during callback", pwnd); } } #endif /* * Send a NCCALCSIZE message to the window and have it return the official * size of its client area. */ if (fChild && TestWF(pwndParent, WEFLAYOUTRTL)) { cx = pwnd->rcWindow.right - pwnd->rcWindow.left; pwnd->rcWindow.right = pwndParent->rcClient.right - (pwnd->rcWindow.left - pwndParent->rcClient.left); pwnd->rcWindow.left = pwnd->rcWindow.right - cx; } CopyRect(&rc, &pwnd->rcWindow); xxxSendMessage(pwnd, WM_NCCALCSIZE, 0L, (LPARAM)&rc); pwnd->rcClient = rc; /* * Send a CREATE message to the window. */ if (xxxSendMessage(pwnd, WM_CREATE, 0L, (LPARAM)&csex) == -1L) { #if DBG if (!IS_PTR(cczpstrNVClass)) { RIPMSG1(RIP_WARNING, "CreateWindow() send of WM_CREATE failed, Class = 0x%x", PTR_TO_ID(cczpstrNVClass)); } else { RIPMSG1(RIP_WARNING, "CreateWindow() send of WM_CREATE failed, Class = \"%s\"", pcls->lpszAnsiClassName); } #endif MemError2: if (fLockParent) { ThreadUnlock(&tlpwndParent); } if (ThreadUnlock(&tlpwnd)) { xxxDestroyWindow(pwnd); } return NULL; } /* * Flag that the window is created. WoW uses this bit to determine that * an fnid of 0 really means 0. */ SetWF(pwnd, WFISINITIALIZED); /* * Notify anyone who is listening that the window is created. Do this * before we size/move/max/min/show it so that event observers can count * on getting notifications for those things also. * * But do this AFTER WM_CREATE is sent. The window and its data will not * be fully initialized until then. Since the purpose of an event is to * let watchers turn around and do querying, we want their queries to * succeed and not fault. */ xxxWindowEvent(EVENT_OBJECT_CREATE, pwnd, OBJID_WINDOW, INDEXID_OBJECT, 0); /* * If this is a Tiled/Overlapped window, don't send size or move msgs yet. */ if (!TestWF(pwnd, WFSENDSIZEMOVE)) { xxxSendSizeMessage(pwnd, SIZENORMAL); if (pwndParent != NULL && PWNDDESKTOP(pwnd) != pwndParent) { rc.left -= pwndParent->rcClient.left; rc.top -= pwndParent->rcClient.top; } xxxSendMessage(pwnd, WM_MOVE, 0L, MAKELONG(rc.left, rc.top)); } /* * Min/Max Stuff */ /* * If app specified either min/max style, then we must call our minmax * code to get it all set up correctly so that when the show is done, * the window is displayed right. */ dwMinMax = MINMAX_KEEPHIDDEN | TEST_PUDF(PUDF_ANIMATE); if (TestWF(pwnd, WFMINIMIZED)) { SetMinimize(pwnd, SMIN_CLEAR); xxxMinMaximize(pwnd, SW_SHOWMINNOACTIVE, dwMinMax); } else if (TestWF(pwnd, WFMAXIMIZED)) { ClrWF(pwnd, WFMAXIMIZED); xxxMinMaximize(pwnd, SW_SHOWMAXIMIZED, dwMinMax); } /* * Send notification if it's a child. */ if (fChild && !TestWF(pwnd, WEFNOPARENTNOTIFY) && (pwnd->spwndParent != NULL)) { ThreadLockAlwaysWithPti(ptiCurrent, pwnd->spwndParent, &tlpwndParentT); xxxSendMessage(pwnd->spwndParent, WM_PARENTNOTIFY, MAKELONG(WM_CREATE, PTR_TO_ID(pwnd->spmenu)), (LPARAM)HWq(pwnd)); ThreadUnlock(&tlpwndParentT); } /* * Show the Window */ if (style & WS_VISIBLE) { xxxShowWindow(pwnd, sw | TEST_PUDF(PUDF_ANIMATE)); } /* * Try and set the application's hot key. Use the Win95 logic of * looking for the first tiled and/or APPWINDOW to be created by * this process. */ if (TestwndTiled(pwnd) || TestWF(pwnd, WEFAPPWINDOW)) { if (ptiCurrent->ppi->dwHotkey) { /* * Ignore hot keys for WowExe the first thread of a wow process. */ if (!(ptiCurrent->TIF_flags & TIF_16BIT) || (ptiCurrent->ppi->cThreads > 1)) { #ifdef LATER /* * Win95 sets the hot key directly, we on the other hand send * a WM_SETHOTKEY message to the app. Which is right? */ DWP_SetHotKey(pwnd, ptiCurrent->ppi->dwHotkey); #else xxxSendMessage(pwnd, WM_SETHOTKEY, ptiCurrent->ppi->dwHotkey, 0); #endif ptiCurrent->ppi->dwHotkey = 0; } } } if (fLockParent) ThreadUnlock(&tlpwndParent); return ThreadUnlock(&tlpwnd); } BOOL WantImeWindow( PWND pwndParent, PWND pwnd) { PDESKTOP pdesk; UserAssert(pwnd); if (PtiCurrent()->TIF_flags & TIF_DISABLEIME) { return FALSE; } if (TestWF(pwnd, WFSERVERSIDEPROC)) { return FALSE; } pdesk = pwnd->head.rpdesk; if (pdesk == NULL || pdesk->rpwinstaParent == NULL) { return FALSE; } // Check whether pwnd's desktop has I/O. if (pdesk->rpwinstaParent->dwWSF_Flags & WSF_NOIO) { return FALSE; } // Check if the owner window is message-only window. if (pwndParent) { PWND pwndT = pwndParent; while (pwndT && pdesk == pwndT->head.rpdesk) { if (pwndT == pdesk->spwndMessage) { return FALSE; } pwndT = pwndT->spwndParent; } } return TRUE; } /***************************************************************************\ * SetTiledRect * * History: * 10-19-90 darrinm Ported from Win 3.0 sources. \***************************************************************************/ VOID SetTiledRect( PWND pwnd, LPRECT lprc, PMONITOR pMonitor) { POINT pt; RECT rcT; UserAssert(pMonitor->cWndStack >= 0); /* * Get available desktop area, minus minimized spacing area. */ GetRealClientRect(PWNDDESKTOP(pwnd), &rcT, GRC_MINWNDS, pMonitor); /* * Increment the count of stacked windows. */ pMonitor->cWndStack++; /* * We want the left edge of the new window to align with the * right edge of the old window's system menu. And we want the * top edge of the new window to align with the bottom edge of the * selected caption area (caption height - cyBorder) of the old * window. */ #define X_TILED (SYSMET(CXSIZEFRAME) + SYSMET(CXSIZE)) #define Y_TILED (SYSMET(CYSIZEFRAME) + SYSMET(CYSIZE)) pt.x = pMonitor->cWndStack * X_TILED; pt.y = pMonitor->cWndStack * Y_TILED; /* * If below upper top left 1/4 of free area, reset. */ if ( (pt.x > ((rcT.right-rcT.left) / 4)) || (pt.y > ((rcT.bottom-rcT.top) / 4)) ) { pMonitor->cWndStack = 0; pt.x = X_TILED; pt.y = Y_TILED; } #undef X_TILED #undef Y_TILED /* * Get starting position */ pt.x += rcT.left; pt.y += rcT.top; lprc->left = pt.x; lprc->top = pt.y; lprc->right = pt.x + MultDiv(rcT.right-rcT.left, 3, 4); lprc->bottom = pt.y + MultDiv(rcT.bottom-rcT.top, 3, 4); } /***************************************************************************\ * xxxAdjustSize * * Make sure that *lpcx and *lpcy are within the legal limits. * * History: * 10-19-90 darrinm Ported from Win 3.0 sources. \***************************************************************************/ VOID xxxAdjustSize( PWND pwnd, LPINT lpcx, LPINT lpcy) { POINT ptmin, ptmax; MINMAXINFO mmi; CheckLock(pwnd); /* * If this window is sizeable or if this window is tiled, check size. */ if (TestwndTiled(pwnd) || TestWF(pwnd, WFSIZEBOX)) { /* * Get size info from pwnd. */ xxxInitSendValidateMinMaxInfo(pwnd, &mmi); if (TestWF(pwnd, WFMINIMIZED)) { ptmin = mmi.ptReserved; ptmax = mmi.ptMaxSize; } else { ptmin = mmi.ptMinTrackSize; ptmax = mmi.ptMaxTrackSize; } // // Make sure we're less than the max, and greater than the min // *lpcx = max(ptmin.x, min(*lpcx, ptmax.x)); *lpcy = max(ptmin.y, min(*lpcy, ptmax.y)); } } #if DBG /***************************************************************************\ * VerifyWindowLink * * History: * 10/28/96 GerardoB Added \***************************************************************************/ VOID VerifyWindowLink( PWND pwnd, PWND pwndParent, BOOL fLink) { BOOL fFirstFound = FALSE; BOOL fInFound = FALSE; PWND pwndNext = pwndParent->spwndChild; PWND pwndFirst = pwndNext; while (pwndNext != NULL) { if (pwndFirst == pwndNext) { if (fFirstFound) { RIPMSG1(RIP_ERROR, "Loop in %#p spwndNext chain", pwnd); return; } else { fFirstFound = TRUE; } } if (pwndNext == pwnd) fInFound = TRUE; pwndNext = pwndNext->spwndNext; } if (fLink && !fInFound) { RIPMSG1(RIP_ERROR, "pwnd 0x%p not found in spwndNext chain", pwnd); } } #endif /***************************************************************************\ * LinkWindow * * History: \***************************************************************************/ VOID LinkWindow( PWND pwnd, PWND pwndInsert, PWND pwndParent) { if (pwndParent->spwndChild == pwnd) { RIPMSG0(RIP_WARNING, "Attempting to link a window to itself"); return; } UserAssert(pwnd != pwndInsert); UserAssert((pwnd->spwndParent == NULL) || (pwnd->spwndParent == pwndParent)); if (pwndInsert == PWND_TOP) { /* * We are at the top of the list. */ LinkTop: #if DBG /* * If the first child is topmost, so must be pwnd, but only for * top-level windows. * * IME or IME related windows are the exceptions, because ImeSetTopmost * and its friends do most of the relinking on its own: When LinkWindow * is called, it's possible TOPMOST flags are left in intermediate * state. By the time the all window relinking finishes, TOPMOST flags * have been taken care of and they are just fine. */ if (pwndParent == PWNDDESKTOP(pwndParent) && pwndParent->spwndChild && FSwpTopmost(pwndParent->spwndChild) && pwndParent != PWNDMESSAGE(pwndParent) && // Check if the target is IME related window !TestCF(pwnd, CFIME) && pwnd->pcls->atomClassName != gpsi->atomSysClass[ICLS_IME]) { /* * There are few cases that cause the z-ordering code to leave the * WFTOGGLETOPMOST bit set. One is when SWP_NOOWNERZORDER is used * when changing the topmost state of a window; in this case, * ZOrderByOwner2 doesn't add ownees to the psmwp list, still * SetTopMost sets the bit on all the ownees. * * Another case is when SetWindowPos gets re-entered on the same * window. It's too late to attempt to fix this ancient behavior * (2/24/99) so let's turn off the assert for now. */ if (!FSwpTopmost(pwnd)) { RIPMSG1(RIP_WARNING, "LinkWindow pwnd:%p is not FSwpTopmost", pwnd); } } #endif // DBG if (pwndParent->spwndChild != NULL) { Lock(&pwndParent->spwndChild->spwndPrev, pwnd); Lock(&pwnd->spwndNext, pwndParent->spwndChild); } Lock(&(pwndParent->spwndChild), pwnd); UserAssert(pwnd->spwndPrev == NULL); } else { if (pwndInsert == PWND_BOTTOM) { /* * Find bottom-most window. */ if (((pwndInsert = pwndParent->spwndChild) == NULL) || TestWF(pwndInsert, WFBOTTOMMOST)) goto LinkTop; /* * Since we know (ahem) that there's only one bottommost window, * we can't possibly insert after it. Either we're inserting * the bottomost window, in which case it's not in the linked * list currently, or we're inserting some other window. */ while (pwndInsert->spwndNext != NULL) { if (TestWF(pwndInsert->spwndNext, WFBOTTOMMOST)) { #if DBG UserAssert(pwnd != pwndInsert->spwndNext); if (TestWF(pwnd, WFBOTTOMMOST)) UserAssert(FALSE); #endif break; } pwndInsert = pwndInsert->spwndNext; } } UserAssert(pwnd != pwndInsert); UserAssert(pwnd != pwndInsert->spwndNext); UserAssert(!TestWF(pwndInsert, WFDESTROYED)); UserAssert(TestWF(pwnd, WFCHILD) || !TestWF(pwnd, WEFTOPMOST) || TestWF(pwndInsert, WEFTOPMOST) || TestWF(pwnd, WFTOGGLETOPMOST) || (pwndParent != PWNDDESKTOP(pwndInsert))); UserAssert(pwnd->spwndParent == pwndInsert->spwndParent); if (pwndInsert->spwndNext != NULL) { Lock(&pwndInsert->spwndNext->spwndPrev, pwnd); Lock(&pwnd->spwndNext, pwndInsert->spwndNext); } Lock(&pwnd->spwndPrev, pwndInsert); Lock(&pwndInsert->spwndNext, pwnd); } if (TestWF(pwnd, WEFLAYERED)) TrackLayeredZorder(pwnd); #if DBG VerifyWindowLink (pwnd, pwndParent, TRUE); #endif } /***************************************************************************\ * xxxDestroyWindow (API) * * Destroy the specified window. The window passed in is not thread locked. * * History: * 10-20-90 darrinm Ported from Win 3.0 sources. * 02-07-91 DavidPe Added Win 3.1 WH_CBT support. * 02-11-91 JimA Added access checks. \***************************************************************************/ BOOL xxxDestroyWindow( PWND pwnd) { PMENUSTATE pMenuState, pmnsEnd; PTHREADINFO pti = PtiCurrent(); TL tlpwnd, tlpwndFocus, tlpwndParent; PWND pwndFocus; BOOL fAlreadyDestroyed; DWORD dwDisableHooks; dwDisableHooks = 0; ThreadLockWithPti(pti, pwnd, &tlpwnd); /* * First, if this handle has been marked for destruction, that means it * is possible that the current thread is not its owner! (meaning we're * being called from a handle unlock call). In this case, set the owner * to be the current thread so inter-thread send messages occur. */ fAlreadyDestroyed = HMIsMarkDestroy(pwnd); if (fAlreadyDestroyed) { /* * UserAssert(dwInAtomicOperation > 0); * This Assert ensures that we are here only because of an unlock * on a previously destroyed window. We BEGIN/ENDATOMICHCHECK in * HMDestroyUnlockedObject to ensure we don't leave the crit sect * unexpectedly, which gives us dwInAtomicCheck > 0. We set * TIF_DISABLEHOOKS to prevent a callback in Unlock * However, it is currently possible destroy the same window handle * twice, since we don't (yet) fail to revalidate zombie handles: * GerardoB may change this, at which time we should probably restore * this Assert, and test #76902 (close winmsd.exe) again. (preventing * hooks in a second destroy of a zombie window should be OK) - IanJa */ // UserAssert(dwInAtomicOperation > 0); if (HMPheFromObject(pwnd)->pOwner != pti) { UserAssert(PsGetCurrentThreadWin32Thread()); HMChangeOwnerThread(pwnd, pti); } dwDisableHooks = pti->TIF_flags & TIF_DISABLEHOOKS; pti->TIF_flags |= TIF_DISABLEHOOKS; } else { /* * Ensure that we can destroy the window. JIMA: no other process or thread * should be able to destroy any other process or thread's window. */ if (pti != GETPTI(pwnd)) { RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Access denied in xxxDestroyWindow"); goto FalseReturn; } } /* * First ask the CBT hook if we can destroy this window. * If this object has already been destroyed OR this thread is currently * in cleanup mode, *do not* make any callbacks via hooks to the client * process. */ if (!fAlreadyDestroyed && !(pti->TIF_flags & TIF_INCLEANUP) && IsHooked(pti, WHF_CBT)) { if (xxxCallHook(HCBT_DESTROYWND, (WPARAM)HWq(pwnd), 0, WH_CBT)) { goto FalseReturn; } } /* * If the window we are destroying is in menu mode, end the menu */ pMenuState = GetpMenuState(pwnd); if ((pMenuState != NULL) && (pwnd == pMenuState->pGlobalPopupMenu->spwndNotify)) { MNEndMenuStateNotify(pMenuState); /* * Signal all states to end. The window(s) will be unlocked when * the menu exits; we cannot unlock it now because the menu * code could fault. */ pmnsEnd = pMenuState; do { UserAssert(pwnd == pMenuState->pGlobalPopupMenu->spwndNotify); pMenuState->fInsideMenuLoop = FALSE; pMenuState = pMenuState->pmnsPrev; } while (pMenuState != NULL) ; /* * All states have been signaled to exit, so once we callback * we cannot count on pmnsEnd->pmnsPrev to be valid. Thus * we simply end the current menu here and let the others go * on their own. No state points to pwnd anymore so that * should be OK. */ if (!pmnsEnd->fModelessMenu) { xxxEndMenu(pmnsEnd); } } if (ghwndSwitch == HWq(pwnd)) { ghwndSwitch = NULL; } if (!TestWF(pwnd, WFCHILD) && (pwnd->spwndOwner == NULL)) { if (TestWF(pwnd, WFHASPALETTE)) { xxxFlushPalette(pwnd); } } /* * Disassociate thread state if this is top level and owned by a different * thread. This is done to begin with so these windows z-order together. */ if (pwnd->pcls->atomClassName != gpsi->atomSysClass[ICLS_IME] && !TestwndChild(pwnd) && pwnd->spwndOwner != NULL && GETPTI(pwnd->spwndOwner) != GETPTI(pwnd)) { /* * No need to zzzDeferWinEventNotify() - there is an xxx call just below */ zzzAttachThreadInput(GETPTI(pwnd), GETPTI(pwnd->spwndOwner), FALSE); } /* * If we are a child window without the WS_NOPARENTNOTIFY style, send * the appropriate notification message. * * NOTE: Although it would appear that we are illegally cramming a * a WORD (WM_DESTROY) and a DWORD (pwnd->spmenu) into a single LONG * (wParam) this isn't really the case because we first test if this * is a child window. The pMenu field in a child window is really * the window's id and only the LOWORD is significant. */ if (TestWF(pwnd, WFCHILD) && !TestWF(pwnd, WEFNOPARENTNOTIFY) && pwnd->spwndParent != NULL) { ThreadLockAlwaysWithPti(pti, pwnd->spwndParent, &tlpwndParent); xxxSendMessage(pwnd->spwndParent, WM_PARENTNOTIFY, MAKELONG(WM_DESTROY, PTR_TO_ID(pwnd->spmenu)), (LPARAM)HWq(pwnd)); ThreadUnlock(&tlpwndParent); } /* * Mark this window as beginning the destroy process. This is necessary * to prevent window-management calls such as ShowWindow or SetWindowPos * from coming in and changing the visible-state of the window * once we hide it. Otherwise, if the app attempts to make it * visible, then we can get our vis-rgns messed up once we truely * destroy the window. * * Don't mark the mother desktop with this bit. The xxxSetWindowPos() * will fail for this window, and thus possibly cause an assertion * in the xxxFreeWindow() call when we check for the visible-bit. */ if (pwnd->spwndParent && (pwnd->spwndParent->head.rpdesk != NULL)) { SetWF(pwnd, WFINDESTROY); } /* * Hide the window. */ if (TestWF(pwnd, WFVISIBLE)) { if (TestWF(pwnd, WFCHILD)) { xxxShowWindow(pwnd, SW_HIDE | TEST_PUDF(PUDF_ANIMATE)); } else { /* * Hide this window without activating anyone else. */ xxxSetWindowPos(pwnd, NULL, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | (fAlreadyDestroyed ? SWP_DEFERDRAWING : 0)); } /* * Under low memory conditions, the above attempt to hide could fail. */ if (TestWF(pwnd, WFVISIBLE)) { RIPMSG0(RIP_WARNING, "xxxDestroyWindow: normal hide failed"); SetVisible(pwnd, SV_UNSET); /* * Invalidate windows below so they redraw properly. */ xxxRedrawWindow(NULL, &pwnd->rcWindow, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); } } else if (IsTrayWindow(pwnd)) { PostShellHookMessages(HSHELL_WINDOWDESTROYED, (LPARAM)PtoHq( pwnd )); } /* * Destroy any owned windows. * [msadek], the check for FNID_GHOST is to fix bug# 380208, 382758 * where we recieve QEVENT_HUNGTHREAD for the owner window first. * since we post the event multiple times (once for each owned window) * We are guranteed to go through xxxDestroyWindow for owned windows too. */ if (!TestWF(pwnd, WFCHILD) && (GETFNID(pwnd) != FNID_GHOST)) { xxxDW_DestroyOwnedWindows(pwnd); /* * And remove the window hot-key, if it has one */ DWP_SetHotKey(pwnd, 0); } /* * If the window has already been destroyed, don't muck with * activation because we may already be in the middle of * an activation event. Changing activation now may cause us * to leave our critical section while holding the display lock. * This will result in a deadlock if another thread gets the * critical section before we do and attempts to lock the * display. */ if (!fAlreadyDestroyed) { PWND pwndActivate = NULL; TL tlpwndActivate; UINT cmdActivate; /* * If hiding the active window, activate someone else. * This call is strategically located after DestroyOwnedWindows() so we * don't end up activating our owner window. * * If the window is a popup, try to activate his creator not the top * window in the Z list. */ if (pwnd == pti->pq->spwndActive) { if (TestWF(pwnd, WFPOPUP) && pwnd->spwndOwner) { pwndActivate = pwnd->spwndOwner; cmdActivate = AW_TRY; } else { pwndActivate = pwnd; cmdActivate = AW_SKIP; } } else if ((pti->pq->spwndActive == NULL) && (gpqForeground == pti->pq)) { pwndActivate = pwnd; cmdActivate = AW_SKIP; } if (pwndActivate) { ThreadLockAlwaysWithPti(pti, pwndActivate, &tlpwndActivate); if (!xxxActivateWindow(pwndActivate, cmdActivate) || ((cmdActivate == AW_SKIP) && (pwnd == pti->pq->spwndActive))) { if ((cmdActivate == AW_SKIP) || (pwnd == pti->pq->spwndActive)) { Unlock(&pti->pq->spwndActive); pwndFocus = Unlock(&pti->pq->spwndFocus); if (IS_IME_ENABLED() && pwndFocus != NULL) { ThreadLockAlwaysWithPti(pti, pwndFocus, &tlpwndFocus); xxxFocusSetInputContext(pwndFocus, FALSE, FALSE); ThreadUnlock(&tlpwndFocus); } if (pti->pq == gpqForeground) { xxxWindowEvent(EVENT_OBJECT_FOCUS, NULL, OBJID_CLIENT, INDEXID_CONTAINER, 0); xxxWindowEvent(EVENT_SYSTEM_FOREGROUND, NULL, OBJID_WINDOW, INDEXID_CONTAINER, WEF_USEPWNDTHREAD); } zzzInternalDestroyCaret(); } } ThreadUnlock(&tlpwndActivate); } } /* * Fix last active popup. */ { PWND pwndOwner = pwnd->spwndOwner; if (pwndOwner != NULL) { while (pwndOwner->spwndOwner != NULL) { pwndOwner = pwndOwner->spwndOwner; } if (pwnd == pwndOwner->spwndLastActive) { /* * If pwndOwner is marked for destruction, locking it here * will prevent it from ever being freed, thus preventing * the associated session memory from going away. Just * unlock pwndOwner->spwndLastActive in this case. * [msadek- 03/02/2002] */ if (HMIsMarkDestroy(pwndOwner)) { Unlock(&pwndOwner->spwndLastActive); } else { Lock(&(pwndOwner->spwndLastActive), pwnd->spwndOwner); } } } } if (!fAlreadyDestroyed) { /* * Note we do this BEFORE telling the app the window is dying. Note * also that we do NOT loop through the children generating DESTROY * events. DESTROY of a parent implies DESTROY of all children (see * Windows NT Bug #71846). */ if (!TestWF(pwnd, WFDESTROYED)) { xxxWindowEvent(EVENT_OBJECT_DESTROY, pwnd, OBJID_WINDOW, INDEXID_CONTAINER, 0); } /* * Send destroy messages before the WindowLockStart in case * he tries to destroy windows as a result. */ xxxDW_SendDestroyMessages(pwnd); } /* * Check the owner of IME window again. * If thread is destroying, don't bother to check. */ if (IS_IME_ENABLED() && !(pti->TIF_flags & TIF_INCLEANUP) && pti->spwndDefaultIme != NULL && !TestCF(pwnd, CFIME) && pwnd->pcls->atomClassName != gpsi->atomSysClass[ICLS_IME]) { if (fAlreadyDestroyed) { RIPMSG2(RIP_VERBOSE, "xxxDestroyWindow: in final destruction of 0x%p, ime=0x%p", pwnd, pti->spwndDefaultIme); } else { if (!TestWF(pwnd, WFCHILD)) { if (ImeCanDestroyDefIME(pti->spwndDefaultIme, pwnd)) { TAGMSG1(DBGTAG_IMM, "xxxDestroyWindow: destroying (1) the default IME window=%p", pti->spwndDefaultIme); xxxDestroyWindow(pti->spwndDefaultIme); } } else if (pwnd->spwndParent != NULL) { if (ImeCanDestroyDefIMEforChild(pti->spwndDefaultIme, pwnd)) { TAGMSG1(DBGTAG_IMM, "xxxDestroyWindow: destroying (2) the default IME window=%p", pti->spwndDefaultIme); xxxDestroyWindow(pti->spwndDefaultIme); } } } } if ((pwnd->spwndParent != NULL) && !fAlreadyDestroyed) { /* * TestwndChild() on checks to WFCHILD bit. Make sure this * window wasn't SetParent()'ed to the desktop as well. */ if (TestwndChild(pwnd) && (pwnd->spwndParent != PWNDDESKTOP(pwnd)) && (GETPTI(pwnd) != GETPTI(pwnd->spwndParent))) { /* * pwnd is threadlocked, so no need to DeferWinEventNotify() */ CheckLock(pwnd); zzzAttachThreadInput(GETPTI(pwnd), GETPTI(pwnd->spwndParent), FALSE); } UnlinkWindow(pwnd, pwnd->spwndParent); } /* * This in intended to check for a case where we destroy the window, * but it's still listed as the active-window in the queue. This * could cause problems in window-activation (see xxxActivateThisWindow) * where we attempt to activate another window and in the process, try * to deactivate this window (bad). */ #if DBG if (pwnd == pti->pq->spwndActive) { RIPMSG1(RIP_WARNING, "xxxDestroyWindow: pwnd == pti->pq->spwndActive (%#p)", pwnd); } #endif /* * Set the state as destroyed so any z-ordering events will be ignored. * We cannot NULL out the owner field until WM_NCDESTROY is send or * apps like Rumba fault (they call GetParent after every message) */ SetWF(pwnd, WFDESTROYED); /* * FreeWindow performs a ThreadUnlock. */ xxxFreeWindow(pwnd, &tlpwnd); if (fAlreadyDestroyed) { pti->TIF_flags = (pti->TIF_flags & ~TIF_DISABLEHOOKS) | dwDisableHooks; } return TRUE; FalseReturn: if (fAlreadyDestroyed) { pti->TIF_flags = (pti->TIF_flags & ~TIF_DISABLEHOOKS) | dwDisableHooks; } ThreadUnlock(&tlpwnd); return FALSE; } /***************************************************************************\ * xxxDW_DestroyOwnedWindows * * History: * 10-20-90 darrinm Ported from Win 3.0 sources. * 07-22-91 darrinm Re-ported from Win 3.1 sources. \***************************************************************************/ VOID xxxDW_DestroyOwnedWindows( PWND pwndParent) { PWND pwnd, pwndDesktop; PDESKTOP pdeskParent; PWND pwndDefaultIme = GETPTI(pwndParent)->spwndDefaultIme; CheckLock(pwndParent); if ((pdeskParent = pwndParent->head.rpdesk) == NULL) return; pwndDesktop = pdeskParent->pDeskInfo->spwnd; /* * During shutdown, the desktop owner window will be * destroyed. In this case, pwndDesktop will be NULL. */ if (pwndDesktop == NULL) return; pwnd = pwndDesktop->spwndChild; while (pwnd != NULL) { if (pwnd->spwndOwner == pwndParent) { /* * We don't destroy the IME window here * unless the thread is doing cleanup. */ if (IS_IME_ENABLED() && !(GETPTI(pwndParent)->TIF_flags & TIF_INCLEANUP) && pwnd == pwndDefaultIme) { Unlock(&pwnd->spwndOwner); pwnd = pwnd->spwndNext; continue; } /* * If the window doesn't get destroyed, set its owner to NULL. * A good example of this is trying to destroy a window created * by another thread or process, but there are other cases. */ if (!xxxDestroyWindow(pwnd)) { Unlock(&pwnd->spwndOwner); } /* * Start the search over from the beginning since the app could * have caused other windows to be created or activation/z-order * changes. */ pwnd = pwndDesktop->spwndChild; } else { pwnd = pwnd->spwndNext; } } } /***************************************************************************\ * xxxDW_SendDestroyMessages * * History: * 10-20-90 darrinm Ported from Win 3.0 sources. \***************************************************************************/ VOID xxxDW_SendDestroyMessages( PWND pwnd) { PWND pwndChild; PWND pwndNext; TL tlpwndNext; TL tlpwndChild; PWINDOWSTATION pwinsta; CheckLock(pwnd); /* * Be sure the window gets any resulting messages before being destroyed. */ xxxCheckFocus(pwnd); pwinsta = _GetProcessWindowStation(NULL); if (pwinsta != NULL && pwnd == pwinsta->spwndClipOwner) { /* * Pass along the pwnd which is the reason we are dismissing the * clipboard. We want to later make sure the owner is still this * window after we make callbacks and clear the owner */ xxxDisownClipboard(pwnd); } /* * Send the WM_DESTROY message. */ #if _DBG if (pwnd == PtiCurrent()->spwndDefaultIme) { TAGMSG2(DBGTAG_IMM, "xxxDW_SendDestroyMessages: sending WM_DESTROY message to def IME=%p, pti=%p", pwnd, PtiCurrent()); } #endif xxxSendMessage(pwnd, WM_DESTROY, 0L, 0L); /* * Now send destroy message to all children of pwnd. * Enumerate down (pwnd->spwndChild) and sideways (pwnd->spwndNext). * We do it this way because parents often assume that child windows still * exist during WM_DESTROY message processing. */ pwndChild = pwnd->spwndChild; while (pwndChild != NULL) { pwndNext = pwndChild->spwndNext; ThreadLock(pwndNext, &tlpwndNext); ThreadLockAlways(pwndChild, &tlpwndChild); xxxDW_SendDestroyMessages(pwndChild); ThreadUnlock(&tlpwndChild); pwndChild = pwndNext; /* * The unlock may nuke the next window. If so, get out. */ if (!ThreadUnlock(&tlpwndNext)) break; } xxxCheckFocus(pwnd); } /***************************************************************************\ * xxxFW_DestroyAllChildren * * History: * 11-06-90 darrinm Ported from Win 3.0 sources. \***************************************************************************/ VOID xxxFW_DestroyAllChildren( PWND pwnd) { PWND pwndChild; TL tlpwndChild; PTHREADINFO pti; PTHREADINFO ptiCurrent = PtiCurrent(); CheckLock(pwnd); while (pwnd->spwndChild != NULL) { pwndChild = pwnd->spwndChild; /* * ThreadLock prior to the unlink in case pwndChild * is already marked as destroyed. */ ThreadLockAlwaysWithPti(ptiCurrent, pwndChild, &tlpwndChild); /* * Propagate the VISIBLE flag. We need to do this so that * when a child window gets destroyed we don't try to hide it * if the WFVISIBLE flag is set. */ if (TestWF(pwndChild, WFVISIBLE)) { SetVisible(pwndChild, SV_UNSET); } UnlinkWindow(pwndChild, pwnd); /* * Set the state as destroyed so any z-ordering events will be ignored. * We cannot NULL out the owner field until WM_NCDESTROY is send or * apps like Rumba fault (they call GetParent after every message) */ SetWF(pwndChild, WFDESTROYED); /* * If the window belongs to another thread, post * an event to let it know it should be destroyed. * Otherwise, free the window. */ pti = GETPTI(pwndChild); if (pti != ptiCurrent) { PostEventMessage(pti, pti->pq, QEVENT_DESTROYWINDOW, NULL, 0, (WPARAM)HWq(pwndChild), 0); ThreadUnlock(&tlpwndChild); } else { /* * FreeWindow performs a ThreadUnlock. */ xxxFreeWindow(pwndChild, &tlpwndChild); } } } /***************************************************************************\ * UnlockNotifyWindow * * Walk down a menu and unlock all notify windows. * * History: * 18-May-1994 JimA Created. \***************************************************************************/ VOID UnlockNotifyWindow( PMENU pmenu) { PITEM pItem; int i; /* * Go down the item list and unlock submenus. */ pItem = pmenu->rgItems; for (i = pmenu->cItems; i--; ++pItem) { if (pItem->spSubMenu != NULL) UnlockNotifyWindow(pItem->spSubMenu); } Unlock(&pmenu->spwndNotify); } /***************************************************************************\ * xxxFreeWindow * * History: * 19-Oct-1990 DarrinM Ported from Win 3.0 sources. \***************************************************************************/ VOID xxxFreeWindow( PWND pwnd, PTL ptlpwndFree) { PDCE *ppdce; PDCE pdce; UINT uDCERelease; PMENU pmenu; PQMSG pqmsg; PPCLS ppcls; WORD fnid; TL tlpdesk; PWINDOWSTATION pwinsta = _GetProcessWindowStation(NULL); PTHREADINFO pti = PtiCurrent(); PPROCESSINFO ppi; PMONITOR pMonitor; TL tlpMonitor; UNREFERENCED_PARAMETER(ptlpwndFree); CheckLock(pwnd); /* * If the pwnd is any of the global shell-related windows, * then we need to unlock them from the deskinfo. */ if (pwnd->head.rpdesk != NULL) { if (pwnd == pwnd->head.rpdesk->pDeskInfo->spwndShell) Unlock(&pwnd->head.rpdesk->pDeskInfo->spwndShell); if (pwnd == pwnd->head.rpdesk->pDeskInfo->spwndBkGnd) Unlock(&pwnd->head.rpdesk->pDeskInfo->spwndBkGnd); if (pwnd == pwnd->head.rpdesk->pDeskInfo->spwndTaskman) Unlock(&pwnd->head.rpdesk->pDeskInfo->spwndTaskman); if (pwnd == pwnd->head.rpdesk->pDeskInfo->spwndProgman) Unlock(&pwnd->head.rpdesk->pDeskInfo->spwndProgman); if (TestWF(pwnd,WFSHELLHOOKWND)) { _DeregisterShellHookWindow(pwnd); } if (TestWF(pwnd, WFMSGBOX)) { pwnd->head.rpdesk->pDeskInfo->cntMBox--; ClrWF(pwnd, WFMSGBOX); } } /* * First, if this handle has been marked for destruction, that means it * is possible that the current thread is not its owner! (meaning we're * being called from a handle unlock call). In this case, set the owner * to be the current thread so inter-thread send messages don't occur. */ if (HMIsMarkDestroy(pwnd)) HMChangeOwnerThread(pwnd, pti); /* * Blow away the children. * * DestroyAllChildren() will still destroy windows created by other * threads! This needs to be looked at more closely: the ultimate * "right" thing to do is not to destroy these windows but just * unlink them. */ xxxFW_DestroyAllChildren(pwnd); xxxSendMessage(pwnd, WM_NCDESTROY, 0, 0L); pMonitor = _MonitorFromWindow(pwnd, MONITOR_DEFAULTTOPRIMARY); ThreadLockAlwaysWithPti(pti, pMonitor, &tlpMonitor); xxxRemoveFullScreen(pwnd, pMonitor); ThreadUnlock(&tlpMonitor); /* * If this is one of the built in controls which hasn't been cleaned * up yet, do it now. If it lives in the kernel, call the function * directly, otherwise call back to the client. Even if the control * is sub- or super-classed, use the window procs associated with * the function id. */ fnid = GETFNID(pwnd); if ((fnid >= FNID_WNDPROCSTART) && !(pwnd->fnid & FNID_CLEANEDUP_BIT)) { if (fnid <= FNID_WNDPROCEND) { FNID(fnid)(pwnd, WM_FINALDESTROY, 0, 0, 0); } else if (fnid <= FNID_CONTROLEND && !(pti->TIF_flags & TIF_INCLEANUP)) { CallClientWorkerProc(pwnd, WM_FINALDESTROY, 0, 0, (PROC)FNID_TO_CLIENT_PFNWORKER(fnid)); } pwnd->fnid |= FNID_CLEANEDUP_BIT; } pwnd->fnid |= FNID_DELETED_BIT; /* * Check to clear the most recently active window in owned list. */ if (pwnd->spwndOwner && (pwnd->spwndOwner->spwndLastActive == pwnd)) { Lock(&(pwnd->spwndOwner->spwndLastActive), pwnd->spwndOwner); } /* * The windowstation may be NULL if we are destroying a desktop * or windowstation. If this is the case, this thread will not * be using the clipboard. */ if (pwinsta != NULL) { if (pwnd == pwinsta->spwndClipOpen) { Unlock(&pwinsta->spwndClipOpen); pwinsta->ptiClipLock = NULL; } if (pwnd == pwinsta->spwndClipViewer) { Unlock(&pwinsta->spwndClipViewer); } } if (IS_IME_ENABLED() && pwnd == pti->spwndDefaultIme) Unlock(&pti->spwndDefaultIme); if (pwnd == pti->pq->spwndFocus) Unlock(&pti->pq->spwndFocus); if (pwnd == pti->pq->spwndActivePrev) Unlock(&pti->pq->spwndActivePrev); if (pwnd == gspwndActivate) Unlock(&gspwndActivate); if (pwnd->head.rpdesk != NULL) { if (pwnd == pwnd->head.rpdesk->spwndForeground) Unlock(&pwnd->head.rpdesk->spwndForeground); if (pwnd == pwnd->head.rpdesk->spwndTray) Unlock(&pwnd->head.rpdesk->spwndTray); if (pwnd == pwnd->head.rpdesk->spwndTrack) { /* * Remove tooltip, if any */ if (GETPDESK(pwnd)->dwDTFlags & DF_TOOLTIPSHOWING) { PWND pwndTooltip = GETPDESK(pwnd)->spwndTooltip; TL tlpwndTooltip; ThreadLockAlways(pwndTooltip, &tlpwndTooltip); xxxResetTooltip((PTOOLTIPWND)pwndTooltip); ThreadUnlock(&tlpwndTooltip); } Unlock(&pwnd->head.rpdesk->spwndTrack); pwnd->head.rpdesk->dwDTFlags &= ~DF_MOUSEMOVETRK; } } if (pwnd == pti->pq->spwndCapture) xxxReleaseCapture(); if (FAnyShadows()) { if (TestCF(pwnd, CFDROPSHADOW)) { xxxRemoveShadow(pwnd); } else if (pwnd->pcls->atomClassName == gatomShadow) { CleanupShadow(pwnd); } } /* * This window won't be needing any more input. */ if (pwnd == gspwndMouseOwner) Unlock(&gspwndMouseOwner); /* * It also won't have any mouse cursors over it. */ if (pwnd == gspwndCursor) Unlock(&gspwndCursor); DestroyWindowsTimers(pwnd); DestroyWindowsHotKeys(pwnd); /* * Make sure this window has no pending sent messages. */ ClearSendMessages(pwnd); /* * Remove the associated GDI sprite. */ if (TestWF(pwnd, WEFLAYERED)) { UnsetLayeredWindow(pwnd); } if (TestWF(pwnd, WEFCOMPOSITED)) { UnsetRedirectedWindow(pwnd, REDIRECT_COMPOSITED); } #ifdef REDIRECTION if (TestWF(pwnd, WEFEXTREDIRECTED)) { UnsetRedirectedWindow(pwnd, REDIRECT_EXTREDIRECTED); } #endif // REDIRECTION /* * Blow away any update region lying around. */ if (NEEDSPAINT(pwnd)) { DecPaintCount(pwnd); DeleteMaybeSpecialRgn(pwnd->hrgnUpdate); pwnd->hrgnUpdate = NULL; ClrWF(pwnd, WFINTERNALPAINT); } /* * Decrememt queue's syncpaint count if necessary. */ if (NEEDSSYNCPAINT(pwnd)) { ClrWF(pwnd, WFSENDNCPAINT); ClrWF(pwnd, WFSENDERASEBKGND); } /* * Clear both flags to ensure that the window is removed * from the hung redraw list. */ ClearHungFlag(pwnd, WFREDRAWIFHUNG); ClearHungFlag(pwnd, WFREDRAWFRAMEIFHUNG); /* * If there is a WM_QUIT message in this app's message queue, call * PostQuitMessage() (this happens if the app posts itself a quit message. * WinEdit2.0 posts a quit to a window while receiving the WM_DESTROY * for that window - it works because we need to do a PostQuitMessage() * automatically for this thread. */ if (pti->mlPost.pqmsgRead != NULL) { /* * try to get rid of WM_DDE_ACK too. */ if ((pqmsg = FindQMsg(pti, &(pti->mlPost), pwnd, WM_QUIT, WM_QUIT, TRUE)) != NULL) { _PostQuitMessage((int)pqmsg->msg.wParam); } } if (!TestwndChild(pwnd) && pwnd->spmenu != NULL) { pmenu = (PMENU)pwnd->spmenu; if (UnlockWndMenu(pwnd, &pwnd->spmenu)) _DestroyMenu(pmenu); } if (pwnd->spmenuSys != NULL) { pmenu = (PMENU)pwnd->spmenuSys; if (pmenu != pwnd->head.rpdesk->spmenuDialogSys) { if (UnlockWndMenu(pwnd, &pwnd->spmenuSys)) { _DestroyMenu(pmenu); } } else { UnlockWndMenu(pwnd, &pwnd->spmenuSys); } } /* * If it was using either of the desktop system menus, unlock it */ if (pwnd->head.rpdesk != NULL) { if (pwnd->head.rpdesk->spmenuSys != NULL && pwnd == pwnd->head.rpdesk->spmenuSys->spwndNotify) { UnlockNotifyWindow(pwnd->head.rpdesk->spmenuSys); } else if (pwnd->head.rpdesk->spmenuDialogSys != NULL && pwnd == pwnd->head.rpdesk->spmenuDialogSys->spwndNotify) { UnlockNotifyWindow(pwnd->head.rpdesk->spmenuDialogSys); } } /* * Tell Gdi that the window is going away. */ if (gcountPWO != 0) { PVOID pwo = InternalRemoveProp(pwnd, PROP_WNDOBJ, TRUE); if (pwo != NULL) { GreLockDisplay(gpDispInfo->hDev); GreDeleteWnd(pwo); gcountPWO--; GreUnlockDisplay(gpDispInfo->hDev); } } #ifdef HUNGAPP_GHOSTING /* * RemoveGhost handles the case when pwnd is the hung window that has a * corresponding ghost window and the case when pwnd is the ghost itself. */ RemoveGhost(pwnd); #endif // HUNGAPP_GHOSTING /* * Scan the DC cache to find any DC's for this window. If any are there, * then invalidate them. We don't need to worry about calling SpbCheckDC * because the window has been hidden by this time. */ for (ppdce = &gpDispInfo->pdceFirst; *ppdce != NULL; ) { pdce = *ppdce; if (pdce->DCX_flags & DCX_INVALID) { goto NextEntry; } if ((pdce->pwndOrg == pwnd) || (pdce->pwndClip == pwnd)) { if (!(pdce->DCX_flags & DCX_CACHE)) { if (TestCF(pwnd, CFCLASSDC)) { GreLockDisplay(gpDispInfo->hDev); if (pdce->DCX_flags & (DCX_EXCLUDERGN | DCX_INTERSECTRGN)) DeleteHrgnClip(pdce); MarkDCEInvalid(pdce); pdce->pwndOrg = NULL; pdce->pwndClip = NULL; pdce->hrgnClip = NULL; /* * Remove the vis rgn since it is still owned - if we did * not, gdi would not be able to clean up properly if the * app that owns this vis rgn exist while the vis rgn is * still selected. */ GreSelectVisRgn(pdce->hdc, NULL, SVR_DELETEOLD); GreUnlockDisplay(gpDispInfo->hDev); } else if (TestCF(pwnd, CFOWNDC)) { DestroyCacheDC(ppdce, pdce->hdc); } else { UserAssert(FALSE); } } else { /* * If the DC is checked out, release it before * we invalidate. Note, that if this process is exiting * and it has a dc checked out, gdi is going to destroy that * dc. We need to similarly remove that dc from the dc cache. * This is not done here, but in the exiting code. * * The return for ReleaseDC() could fail, which would * indicate a delayed-free (DCE_NUKE). */ uDCERelease = DCE_RELEASED; if (pdce->DCX_flags & DCX_INUSE) { uDCERelease = ReleaseCacheDC(pdce->hdc, FALSE); } else if (!GreSetDCOwner(pdce->hdc, OBJECT_OWNER_NONE)) { uDCERelease = DCE_NORELEASE; } if (uDCERelease != DCE_FREED) { if (uDCERelease == DCE_NORELEASE) { /* * We either could not release this dc or could not set * its owner. In either case it means some other thread * is actively using it. Since it is not too useful if * the window it is calculated for is gone, mark it as * INUSE (so we don't give it out again) and as * DESTROYTHIS (so we just get rid of it since it is * easier to do this than to release it back into the * cache). The W32PF_OWNERDCCLEANUP bit means "look for * DESTROYTHIS flags and destroy that dc", and the bit * gets looked at in various strategic execution paths. */ pdce->DCX_flags = DCX_DESTROYTHIS | DCX_INUSE | DCX_CACHE; pti->ppi->W32PF_Flags |= W32PF_OWNDCCLEANUP; } else { /* * We either released the DC or changed its owner * successfully. Mark the entry as invalid so it can * be given out again. */ MarkDCEInvalid(pdce); pdce->hrgnClip = NULL; } /* * We shouldn't reference this window anymore. Setting * these to NULL here will make sure that even if we were * not able to release the DC here, we won't return this * window from one of the DC matching functions. */ pdce->pwndOrg = NULL; pdce->pwndClip = NULL; /* * Remove the visrgn since it is still owned - if we did * not, gdi would not be able to clean up properly if the * app that owns this visrgn exist while the visrgn is * still selected. */ GreLockDisplay(gpDispInfo->hDev); GreSelectVisRgn(pdce->hdc, NULL, SVR_DELETEOLD); GreUnlockDisplay(gpDispInfo->hDev); } } } /* * Step to the next DC. If the DC was deleted, there * is no need to calculate address of the next entry. */ if (pdce == *ppdce) NextEntry: ppdce = &pdce->pdceNext; } /* * Clean up the spb that may still exist - like child window spb's. */ if (pwnd == gspwndLockUpdate) { FreeSpb(FindSpb(pwnd)); Unlock(&gspwndLockUpdate); gptiLockUpdate = NULL; } if (TestWF(pwnd, WFHASSPB)) { FreeSpb(FindSpb(pwnd)); } /* * Blow away the window clipping region. If the window is maximized, don't * blow away the monitor region. If the window is the desktop, don't blow * away the screen region. */ if ( pwnd->hrgnClip != NULL && !TestWF(pwnd, WFMAXFAKEREGIONAL) && GETFNID(pwnd) != FNID_DESKTOP) { GreDeleteObject(pwnd->hrgnClip); pwnd->hrgnClip = NULL; } /* * Clean up any memory allocated for scroll bars... */ if (pwnd->pSBInfo) { DesktopFree(pwnd->head.rpdesk, (HANDLE)(pwnd->pSBInfo)); pwnd->pSBInfo = NULL; } /* * Free any callback handles associated with this window. * This is done outside of DeleteProperties because of the special * nature of callback handles as opposed to normal memory handles * allocated for a thread. */ /* * Blow away the title */ if (pwnd->strName.Buffer != NULL) { DesktopFree(pwnd->head.rpdesk, pwnd->strName.Buffer); pwnd->strName.Buffer = NULL; pwnd->strName.Length = 0; } /* * Blow away any properties connected to the window. */ if (pwnd->ppropList != NULL) { TL tlpDdeConv; PDDECONV pDdeConv; PDDEIMP pddei; /* * Get rid of any icon properties. */ DestroyWindowSmIcon(pwnd); InternalRemoveProp(pwnd, MAKEINTATOM(gpsi->atomIconProp), PROPF_INTERNAL); pDdeConv = (PDDECONV)_GetProp(pwnd, PROP_DDETRACK, PROPF_INTERNAL); if (pDdeConv != NULL) { ThreadLockAlwaysWithPti(pti, pDdeConv, &tlpDdeConv); xxxDDETrackWindowDying(pwnd, pDdeConv); ThreadUnlock(&tlpDdeConv); } pddei = (PDDEIMP)InternalRemoveProp(pwnd, PROP_DDEIMP, PROPF_INTERNAL); if (pddei != NULL) { pddei->cRefInit = 0; if (pddei->cRefConv == 0) { /* * If this is not 0 it is referenced by one or more DdeConv * structures so DON'T free it yet! */ UserFreePool(pddei); } } } /* * Unlock everything that the window references. * After we have sent the WM_DESTROY and WM_NCDESTROY message we * can unlock & NULL the owner field so no other windows get z-ordered * relative to this window. Rhumba faults if we NULL it before the * destroy. (It calls GetParent after every message). * * We special-case the spwndParent window. In this case, if the * window being destroyed is a desktop window, unlock the parent. * Otherwise, we lock in the desktop-window as the parent so that * if we aren't freed in this function, we will ensure that we * won't fault when doing things like clipping-calculations. We'll * unlock this once we know we're truly going to free this window. */ if (pwnd->head.rpdesk != NULL && pwnd != pwnd->head.rpdesk->pDeskInfo->spwnd) { Lock(&pwnd->spwndParent, pwnd->head.rpdesk->pDeskInfo->spwnd); } else { Unlock(&pwnd->spwndParent); } Unlock(&pwnd->spwndChild); Unlock(&pwnd->spwndOwner); Unlock(&pwnd->spwndLastActive); /* * Decrement the Window Reference Count in the Class structure. */ DereferenceClass(pwnd); /* * Mark the object for destruction before this final unlock. This way * the WM_FINALDESTROY will get sent if this is the last thread lock. * We're currently destroying this window, so don't allow unlock recursion * at this point (this is what HANDLEF_INDESTROY will do for us). */ HMMarkObjectDestroy(pwnd); HMPheFromObject(pwnd)->bFlags |= HANDLEF_INDESTROY; /* * Unlock the window... This shouldn't return FALSE because HANDLEF_DESTROY * is set, but just in case... if it isn't around anymore, return because * pwnd is invalid. */ if (!ThreadUnlock(ptlpwndFree)) { return; } /* * Try to free the object. The object won't free if it is locked - but * it will be marked for destruction. If the window is locked, change * it's wndproc to xxxDefWindowProc(). * * HMMarkObjectDestroy() will clear the HANDLEF_INDESTROY flag if the * object isn't about to go away (so it can be destroyed again!) */ if (HMMarkObjectDestroy(pwnd)) { /* * Delete the window's property list. Wait until now in case some * thread keeps a property pointer around across a callback. */ if (pwnd->ppropList != NULL) { DeleteProperties(pwnd); } #if DBG /* * If we find the window is visible at the time we free it, then * somehow the app was made visible on a callback (we hide it * during xxxDestroyWindow(). This screws up our vis-window * count for the thread, so we need to assert it. */ if (TestWF(pwnd, WFINDESTROY) && TestWF(pwnd, WFVISIBLE)) RIPMSG1(RIP_WARNING, "xxxFreeWindow: Window should not be visible (pwnd == %#p)", pwnd); #endif pti->cWindows--; /* * Since we're freeing the memory for this window, we need * to unlock the parent (which is the desktop for zombie windows). */ Unlock(&pwnd->spwndParent); ThreadLockDesktop(pti, pwnd->head.rpdesk, &tlpdesk, LDLT_FN_FREEWINDOW); HMFreeObject(pwnd); ThreadUnlockDesktop(pti, &tlpdesk, LDUT_FN_FREEWINDOW); return; } /* * Turn this into an object that the app won't see again - turn * it into an icon title window - the window is still totally * valid and useable by any structures that has this window locked. */ pwnd->lpfnWndProc = xxxDefWindowProc; if (pwnd->head.rpdesk) ppi = pwnd->head.rpdesk->rpwinstaParent->pTerm->ptiDesktop->ppi; else ppi = PpiCurrent(); ppcls = GetClassPtr(gpsi->atomSysClass[ICLS_ICONTITLE], ppi, hModuleWin); UserAssert(ppcls); pwnd->pcls = *ppcls; /* * Since pwnd is marked as destroyed, there should be no client-side * code which can validate it. So we do not need to search for a clone * class of the right desktop -- just use the base class and bump the * WndReferenceCount. This also helps if we are in a low-memory situation * and cannot alloc another clone. */ pwnd->pcls->cWndReferenceCount++; SetWF(pwnd, WFSERVERSIDEPROC); /* * Clear the palette bit so that WM_PALETTECHANGED will not be sent * again when the window is finally destroyed. */ ClrWF(pwnd, WFHASPALETTE); /* * Clear its child bits so no code assumes that if the child bit * is set, it has a parent. Change spmenu to NULL - it is only * non-zero if this was child. */ ClrWF(pwnd, WFTYPEMASK); SetWF(pwnd, WFTILED); pwnd->spmenu = NULL; } /***************************************************************************\ * UnlinkWindow * * History: * 19-Oct-1990 DarrinM Ported from Win 3.0 sources. \***************************************************************************/ VOID UnlinkWindow( PWND pwnd, PWND pwndParent) { if (pwndParent->spwndChild == pwnd) { UserAssert(pwnd->spwndPrev == NULL); Lock(&pwndParent->spwndChild, pwnd->spwndNext); } else if (pwnd->spwndPrev != NULL) { Lock(&pwnd->spwndPrev->spwndNext, pwnd->spwndNext); } if (pwnd->spwndNext != NULL) { Lock(&pwnd->spwndNext->spwndPrev, pwnd->spwndPrev); Unlock(&pwnd->spwndNext); } Unlock(&pwnd->spwndPrev); #if DBG VerifyWindowLink(pwnd, pwndParent, FALSE); #endif } /***************************************************************************\ * DestroyCacheDCEntries * * Destroys all cache dc entries currently in use by this thread. * * 24-Feb-1992 ScottLu Created. \***************************************************************************/ VOID DestroyCacheDCEntries( PTHREADINFO pti) { PDCE *ppdce; PDCE pdce; /* * Before any window destruction occurs, we need to destroy any dcs * in use in the dc cache. When a dc is checked out, it is marked owned, * which makes gdi's process cleanup code delete it when a process * goes away. We need to similarly destroy the cache entry of any dcs * in use by the exiting process. */ for (ppdce = &gpDispInfo->pdceFirst; *ppdce != NULL; ) { /* * If the dc owned by this thread, remove it from the cache. Because * DestroyCacheEntry destroys gdi objects, it is important that * USER be called first in process destruction ordering. * * Only destroy this dc if it is a cache dc, because if it is either * an owndc or a classdc, it will be destroyed for us when we destroy * the window (for owndcs) or destroy the class (for classdcs). */ pdce = *ppdce; if (pti == pdce->ptiOwner) { if (pdce->DCX_flags & DCX_CACHE) DestroyCacheDC(ppdce, pdce->hdc); } /* * Step to the next DC. If the DC was deleted, there's no need to * calculate address of the next entry. */ if (pdce == *ppdce) { ppdce = &pdce->pdceNext; } } } /***************************************************************************\ * PatchThreadWindows * * This patches a thread's windows so that their window procs point to * server only windowprocs. This is used for cleanup so that app aren't * called back while the system is cleaning up after them. * * 24-Feb-1992 ScottLu Created. \***************************************************************************/ VOID PatchThreadWindows( PTHREADINFO pti) { PHE pheT; PHE pheMax; PWND pwnd; /* * First do any preparation work: windows need to be "patched" so that * their window procs point to server only windowprocs, for example. */ pheMax = &gSharedInfo.aheList[giheLast]; for (pheT = gSharedInfo.aheList; pheT <= pheMax; pheT++) { /* * Make sure this object is a window, it hasn't been marked for * destruction, and that it is owned by this thread. */ if (pheT->bType != TYPE_WINDOW) continue; if (pheT->bFlags & HANDLEF_DESTROY) { continue; } if ((PTHREADINFO)pheT->pOwner != pti) { continue; } /* * Don't patch the window based on the class it was created from - * because apps can sometimes sub-class a class - make a random class, * then call ButtonWndProc with windows of that class by using * the CallWindowProc() api. So patch the wndproc based on what * wndproc this window has been calling. */ pwnd = (PWND)pheT->phead; if ((pwnd->fnid >= (WORD)FNID_WNDPROCSTART) && (pwnd->fnid <= (WORD)FNID_WNDPROCEND)) { pwnd->lpfnWndProc = STOCID(pwnd->fnid); if (pwnd->lpfnWndProc == NULL) { pwnd->lpfnWndProc = xxxDefWindowProc; } } else { pwnd->lpfnWndProc = xxxDefWindowProc; } /* * This is a server side window now... */ SetWF(pwnd, WFSERVERSIDEPROC); ClrWF(pwnd, WFANSIPROC); } }