// // HET.C // Hosted Entity Tracker // // Copyright(c) Microsoft 1997- // #include // // OSI and HET apis are the equivalent of the NT HOOK functionality. // HET_DD apis are the equivalent of the NT display driver apis. // ///// // // HOOK functionality // ///// BOOL WINAPI OSIIsWindowScreenSaver16(HWND hwnd) { BOOL fScreenSaver; DebugEntry(OSIIsWindowScreenSaver16); // // If there is no screen saver active, this window can't be one. // fScreenSaver = FALSE; SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &fScreenSaver, 0); if (fScreenSaver) { char szClassName[64]; // // Is the class name WindowsScreenSaverClass? This is what all // screen savers using the Win95 toolkit use. BOGUS BUGBUG // EXCEPT FOR THE IE4 CHANNEL SCREEN SAVER. // if (!GetClassName(hwnd, szClassName, sizeof(szClassName)) || lstrcmp(szClassName, HET_SCREEN_SAVER_CLASS)) { fScreenSaver = FALSE; } } DebugExitBOOL(OSIIsWindowScreenSaver16, fScreenSaver); return(fScreenSaver); } // // OSIStartWindowTracking16() // // This installs our global call window proc hook then watches windows // being created, destroyed, shown, hidden and looks for relationships via // process or related process info to the currently shared windows. // BOOL WINAPI OSIStartWindowTracking16(void) { BOOL rc = FALSE; DebugEntry(OSIStartWindowTracking16); ASSERT(!g_hetTrackHook); // // Install window/task tracking hook // g_hetTrackHook = SetWindowsHookEx(WH_CALLWNDPROC, HETTrackProc, g_hInstAs16, NULL); if (!g_hetTrackHook) { ERROR_OUT(("Can't install WH_CALLWNDPROC hook")); DC_QUIT; } // // Install event hook // g_hetEventHook = SetWindowsHookEx(WH_CBT, HETEventProc, g_hInstAs16, NULL); if (!g_hetEventHook) { ERROR_OUT(("Can't install WH_CBT hook")); DC_QUIT; } rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(OSIStartWindowTracking16, rc); return(rc); } // // OSIStopWindowTracking16() // void WINAPI OSIStopWindowTracking16(void) { DebugEntry(OSIStopWindowTracking16); // // Remove Graphic Output hooks // HETDDViewing(FALSE); // // Remove event hook // if (g_hetEventHook) { UnhookWindowsHookEx(g_hetEventHook); g_hetEventHook = NULL; } // // Remove window/task tracking hook // if (g_hetTrackHook) { UnhookWindowsHookEx(g_hetTrackHook); g_hetTrackHook = NULL; } DebugExitVOID(OSIStopWindowTracking16); } // // HETEventProc() // This is a global CBT hook that prevents the screensaver from kicking // in when sharing. // LRESULT CALLBACK HETEventProc ( int nCode, WPARAM wParam, LPARAM lParam ) { LRESULT lResult; DebugEntry(HETEventProc); if ((nCode == HCBT_SYSCOMMAND) && (wParam == SC_SCREENSAVE)) { // Prevent the screen saver from starting. NONZERO means disallow. WARNING_OUT(("Preventing screensaver from starting, we're currently sharing")); lResult = TRUE; } else { lResult = CallNextHookEx(g_hetEventHook, nCode, wParam, lParam); } DebugExitDWORD(HETEventProc, lResult); return(lResult); } // // HETTrackProc() // // This is the global hook that watches for windows coming & going, // showing & hiding to see if new ones related to shared ones should also // be shared. This covers related processes as well as related windows. // LRESULT CALLBACK HETTrackProc ( int nCode, WPARAM wParam, LPARAM lParam ) { LPCWPSTRUCT lpCwp; LPWINDOWPOS lpPos; LRESULT lResult; DebugEntry(HETTrackProc); // // wParam is a BOOL, TRUE if this is interthread // lParam is a pointer to a CWPSTRUCT // lpCwp = (LPCWPSTRUCT)lParam; ASSERT(!IsBadReadPtr(lpCwp, sizeof(*lpCwp))); // // We better be tracking still // ASSERT(g_hetTrackHook); // // Skip calls that happen in CONF itself. This is our implementation // of the SKIP_OWNPROCESS WinEvent option in NT's hook dll // if (GetCurrentTask() != g_hCoreTask) { switch (lpCwp->message) { case WM_NCCREATE: HETHandleCreate(lpCwp->hwnd); break; case WM_NCDESTROY: HETHandleDestroy(lpCwp->hwnd); break; case WM_NCPAINT: // // This will catch being shown before WINDOWPOSCHANGED does. // We still keep that for a catch all. // if (IsWindowVisible(lpCwp->hwnd)) { HETHandleShow(lpCwp->hwnd, FALSE); } break; case WM_WINDOWPOSCHANGED: lpPos = (LPWINDOWPOS)lpCwp->lParam; ASSERT(!IsBadReadPtr(lpPos, sizeof(WINDOWPOS))); if (!(lpPos->flags & SWP_NOMOVE)) HETCheckParentChange(lpCwp->hwnd); if (lpPos->flags & SWP_SHOWWINDOW) HETHandleShow(lpCwp->hwnd, TRUE); else if (lpPos->flags & SWP_HIDEWINDOW) HETHandleHide(lpCwp->hwnd); break; } } lResult = CallNextHookEx(g_hetTrackHook, nCode, wParam, lParam); DebugExitDWORD(HETTrackProc, lResult); return(lResult); } // // HETHandleCreate() // void HETHandleCreate(HWND hwnd) { HET_TRACK_INFO hti; UINT hostType; DebugEntry(HETHandleCreate); // // Ignore child windows // if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) { if (GetParent(hwnd) != g_osiDesktopWindow) { TRACE_OUT(("Skipping child window %04x create", hwnd)); DC_QUIT; } } hti.idThread = g_lpfnGetWindowThreadProcessId(hwnd, &hti.idProcess); // // Ignore special shell threads // if (HET_IsShellThread(hti.idThread)) { TRACE_OUT(("Skipping shell thread window %04x create", hwnd)); DC_QUIT; } // // We don't need to ignore menus. Only when first shared do we skip // menus. The cached one we never want to share. The others will // go away almost immediately. From now on, we treat them the same // as other windows. // // // Figure out what to do. // hti.hwndUs = hwnd; hti.cWndsApp = 0; hti.cWndsSharedThread = 0; hti.cWndsSharedProcess = 0; UpOneLevel: EnumWindows(HETShareEnum, (LPARAM)(LPHET_TRACK_INFO)&hti); if (hti.cWndsSharedThread) { TRACE_OUT(("New window %04x in shared thread %08lx", hwnd, hti.idThread)); hostType = HET_HOSTED_PERMANENT | HET_HOSTED_BYTHREAD; } else if (hti.cWndsSharedProcess) { TRACE_OUT(("New window %04x in shared process %08lx", hwnd, hti.idProcess)); hostType = HET_HOSTED_PERMANENT | HET_HOSTED_BYPROCESS; } else if (hti.cWndsApp) { // // There's another window in our app, but none are shared. So don't // share us either. // TRACE_OUT(("New window %04x in unshared process %08lx", hwnd, hti.idProcess)); DC_QUIT; } else { DWORD idParentProcess; // Loop through our ancestor processes (no thread info at this point) HETGetParentProcessID(hti.idProcess, &idParentProcess); if (!idParentProcess) { TRACE_OUT(("Can't get parent of process %08lx", hti.idProcess)); DC_QUIT; } // // We know if we got here that all our favorite fields are still // zero. So just loop! But NULL out idThread to avoid matching // anything while we look at our parent. // TRACE_OUT(("First window %04x in process %08lx, checking parent %08lx", hwnd, hti.idProcess, idParentProcess)); hti.idThread = 0; hti.idProcess = idParentProcess; goto UpOneLevel; } // // OK, we are going to share this. No need to repaint, all our // notifications are synchronous. // OSIShareWindow16(hwnd, hostType, FALSE, TRUE); DC_EXIT_POINT: DebugExitVOID(HETHandleCreate); } // // HETHandleDestroy() // Handles the destruction of a window // void HETHandleDestroy(HWND hwnd) { DebugEntry(HETHandleDestroy); // // Blow away our cache. Our cache holds the last window // drawing happened for, whether it was shared or not, // to let us more quickly decide whether we care. // OSIUnshareWindow16(hwnd, TRUE); if (hwnd == g_oeLastWindow) { TRACE_OUT(("Tossing oe cached window %04x", g_oeLastWindow)); g_oeLastWindow = NULL; } DebugExitVOID(HETHandleDestroy); } // // HETHandleShow() // void HETHandleShow ( HWND hwnd, BOOL fForceRepaint ) { UINT hostType; HET_TRACK_INFO hti; DebugEntry(HETHandleShow); hostType = HET_GetHosting(hwnd); // // If this window is a real child, clear the hosting property. Usually // one isn't there. But in the case of a top level window becoming // a child of another, we want to wipe out junk. // if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) { if (GetParent(hwnd) != g_osiDesktopWindow) { TRACE_OUT(("Skipping child window 0x%04x show", hwnd)); if (hostType) { WARNING_OUT(("Unsharing shared child window 0x%04 from SHOW", hwnd)); OSIUnshareWindow16(hwnd, TRUE); } DC_QUIT; } } // // Is this guy already shared? Nothing to do if so. Unlike NT, // we don't get async notifications. // if (hostType) { TRACE_OUT(("Window %04x already shared, ignoring show", hwnd)); DC_QUIT; } // // Here's where we also enumerate the top level windows and find a // match. But we DO not track across processes in this case. Instead // we look at the owner if there is one. // // This solves the create-as-a-child then change to a top level // window problem, like combo dropdowns. // hti.idThread = g_lpfnGetWindowThreadProcessId(hwnd, &hti.idProcess); // // Ignore special shell threads // if (HET_IsShellThread(hti.idThread)) { TRACE_OUT(("Skipping shell thread window 0x%04x show", hwnd)); DC_QUIT; } hti.hwndUs = hwnd; hti.cWndsApp = 0; hti.cWndsSharedThread = 0; hti.cWndsSharedProcess = 0; EnumWindows(HETShareEnum, (LPARAM)(LPHET_TRACK_INFO)&hti); // // These kinds of windows are always only temp shared. They don't // start out as top level windows that we saw from the beginning or // watched created. These are SetParent() or menu kinds of dudes, so // for a lot of reasons we're plain safer sharing these babies only // temporarily // // // Anything else shared on this thread/process, the decision is easy. // Otherwise, we look at the ownership trail. // if (!hti.cWndsSharedThread && !hti.cWndsSharedProcess) { HWND hwndOwner; // // Does it have an owner that is shared? // hwndOwner = hwnd; while (hwndOwner = GetWindow(hwndOwner, GW_OWNER)) { if (HET_GetHosting(hwndOwner)) { TRACE_OUT(("Found shared owner %04x of window %04x", hwndOwner, hwnd)); break; } } if (!hwndOwner) { DC_QUIT; } } // // We maybe getting this too late, like in the case of a menu coming up, // and it may have already painted/erased. So invalidate this baby. // That's what the fForceRepaint parameter is for. That is only true // when coming from WM_WINDOWPOSCHANGED after an explicit WM_SHOWWINDOW // call. Most of the time, we catch WM_NCPAINT though, for show. // TRACE_OUT(("Sharing temporary window %04x", hwnd)); OSIShareWindow16(hwnd, HET_HOSTED_BYWINDOW | HET_HOSTED_TEMPORARY, fForceRepaint, TRUE); DC_EXIT_POINT: DebugExitVOID(HETHandleShow); } // // HETHandleHide() // void HETHandleHide(HWND hwnd) { UINT hostType; DebugEntry(HETHandleHide); hostType = HET_GetHosting(hwnd); if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) { if (GetParent(hwnd) != GetDesktopWindow()) { TRACE_OUT(("Skipping child window %04x hide", hwnd)); if (hostType) { WARNING_OUT(("Unsharing shared child window 0x%04 from HIDE", hwnd)); OSIUnshareWindow16(hwnd, TRUE); } DC_QUIT; } } if (!hostType) { // // Unlike NT, we don't get hide notifications out of context, so // we don't need to recount the top level guys. // TRACE_OUT(("Window %04x not shared, ignoring hide", hwnd)); } else if (hostType & HET_HOSTED_TEMPORARY) { TRACE_OUT(("Unsharing temporary window %04x", hwnd)); OSIUnshareWindow16(hwnd, TRUE); } else { ASSERT(hostType & HET_HOSTED_PERMANENT); // Nothing to do TRACE_OUT(("Window %04x permanently shared, ignoring hide", hwnd)); } DC_EXIT_POINT: DebugExitVOID(HETHandleHide); } // // HETCheckParentChange() // // On a windowposchange with MOVE, we make sure that no child window has the // hosting property. When a window's parent changes, it is always moved, // so that's the best way I have to check for it. Since we only look at // top level windows, converted-to-children windows will stay shared forever // and won't show up in the share menu. // // This is NOT perfect. If the child is not moving to a different position // relative to the two parents, we won't see anything. But for the case // where one is switching to/from top level, its very likely we will come // through here. More likely than checking for hide/show. // void HETCheckParentChange(HWND hwnd) { DebugEntry(HETCheckParentChange); if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) { if (GetParent(hwnd) != GetDesktopWindow()) { UINT hostType; hostType = HET_GetHosting(hwnd); if (hostType) { WARNING_OUT(("Unsharing shared child window 0x%04x from MOVE", hwnd)); OSIUnshareWindow16(hwnd, TRUE); } } } DebugExitVOID(HETCheckParentChange); } // // HETShareEnum() // // This is the EnumWindows() callback. We stop when we find the first // matching shared window (thread or process). We keep a running tally // of the count of all top level windows in our process (not shared by // thread or process) at the same time. This lets us do tracking. // BOOL CALLBACK HETShareEnum(HWND hwnd, LPARAM lParam) { LPHET_TRACK_INFO lphti = (LPHET_TRACK_INFO)lParam; DWORD idProcess; DWORD idThread; UINT hostType; BOOL rc = TRUE; DebugEntry(HETShareEnum); // Skip ourself. if (hwnd == lphti->hwndUs) { DC_QUIT; } // Skip if window is gone. idThread = g_lpfnGetWindowThreadProcessId(hwnd, &idProcess); if (!idThread) { DC_QUIT; } // // Do the processes match? If not, easy amscray // if (idProcess != lphti->idProcess) { DC_QUIT; } lphti->cWndsApp++; hostType = HET_GetHosting(hwnd); if (!hostType) { DC_QUIT; } // // Now, if this window is shared by thread or process, do the right // thing. // if (hostType & HET_HOSTED_BYPROCESS) { // We have a match. We can return immediately. lphti->cWndsSharedProcess++; rc = FALSE; } else if (hostType & HET_HOSTED_BYTHREAD) { // // For WOW apps, we don't want this one, if in a separate thread, to // count. No matter what. // if (idThread == lphti->idThread) { lphti->cWndsSharedThread++; rc = FALSE; } } DC_EXIT_POINT: DebugExitBOOL(HETShareEnum, rc); return(rc); } // // HET_IsShellThread() // Returns TRUE if thread is one of shell's special threads // BOOL HET_IsShellThread(DWORD threadID) { BOOL rc; DebugEntry(HET_IsShellThread); if ((threadID == g_lpfnGetWindowThreadProcessId(HET_GetShellDesktop(), NULL)) || (threadID == g_lpfnGetWindowThreadProcessId(HET_GetShellTray(), NULL))) { rc = TRUE; } else { rc = FALSE; } DebugExitBOOL(HET_IsShellThread, rc); return(rc); } // // OSIShareWindow16() // This shares a window. This is called when // * An app is unshared // * A window is destroyed // * A temporarily shared window is hidden // // This returns TRUE if it shared a window // BOOL WINAPI OSIShareWindow16 ( HWND hwnd, UINT hostType, BOOL fRepaint, BOOL fUpdateCount ) { BOOL rc = FALSE; DebugEntry(OSIShareWindow16); // // Set the property // if (!HET_SetHosting(hwnd, hostType)) { ERROR_OUT(("Couldn't set shared property on window %04x", hwnd)); DC_QUIT; } // // Toss out our cache--it could have been a child of this one. // g_oeLastWindow = NULL; TRACE_OUT(("Shared window %04x of type %04x", hwnd, hostType)); // // Repaint it // if (fRepaint) { USR_RepaintWindow(hwnd); } if (fUpdateCount) { PostMessageNoFail(g_asMainWindow, DCS_NEWTOPLEVEL_MSG, TRUE, 0); } rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(OSIShareWindow16, rc); return(rc); } // // OSIUnshareWindow16() // This unshares a window. This is called when // * An app is unshared // * A window is destroyed // * A temporarily shared window is hidden // // This returns TRUE if it unshared a shared window. // BOOL WINAPI OSIUnshareWindow16 ( HWND hwnd, BOOL fUpdateCount ) { BOOL rc = FALSE; UINT hostType; DebugEntry(OSIUnshareWindow16); // // This gets the old property and clears it in one step. // hostType = HET_ClearHosting(hwnd); if (!hostType) { // // Unlike NT, all the destroy notifications we get are synchronous. // So we don't need to recalculate the total. // DC_QUIT; } TRACE_OUT(("Unsharing window %04x of type %04x", hwnd, hostType)); // // Toss our cache--the sharing status of some window has changed. // g_oeLastWindow = NULL; // // Update the top level count // if (fUpdateCount) { PostMessageNoFail(g_asMainWindow, DCS_NEWTOPLEVEL_MSG, FALSE, 0); } rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(OSI_UnshareWindow, rc); return(rc); } // // HET_WindowIsHosted() // Returns TRUE if a window is shared. This is used by the IM code in its // high level hooks. // BOOL HET_WindowIsHosted(HWND hwnd) { BOOL rc = FALSE; HWND hwndParent; DebugEntry(HETHookWindowIsHosted); if (!hwnd) DC_QUIT; // // Walk up to the top level window this one is inside of // while (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) { hwndParent = GetParent(hwnd); if (hwndParent == GetDesktopWindow()) break; hwnd = hwndParent; } rc = HET_GetHosting(hwnd); DC_EXIT_POINT: DebugExitBOOL(HET_WindowIsHosted, rc); return(rc); } // // HETGetParentProcessID() // Get parent process if this one // void HETGetParentProcessID ( DWORD processID, LPDWORD pParentProcessID ) { // // Get the ID of the parent process // ASSERT(processID); *pParentProcessID = GetProcessDword(processID, GPD_PARENT); } ///// // // DISPLAY DRIVER functionality // ///// // // HET_DDInit() // BOOL HET_DDInit(void) { return(TRUE); } // // HET_DDTerm() // void HET_DDTerm(void) { DebugEntry(HET_DDTerm); // // Make sure we stop hosting // g_hetDDDesktopIsShared = FALSE; OSIStopWindowTracking16(); DebugExitVOID(HET_DDTerm); } // // HET_DDProcessRequest() // Handles HET escapes // BOOL HET_DDProcessRequest ( UINT fnEscape, LPOSI_ESCAPE_HEADER pResult, DWORD cbResult ) { BOOL rc = TRUE; DebugEntry(HET_DDProcessRequest); switch (fnEscape) { // // NOTE: // Unlike NT, we have no need of keeping a duplicated list of // shared windows. We can make window calls directly, and can use // GetProp to find out. // case HET_ESC_UNSHARE_ALL: { // Nothing to do } break; case HET_ESC_SHARE_DESKTOP: { ASSERT(!g_hetDDDesktopIsShared); g_hetDDDesktopIsShared = TRUE; } break; case HET_ESC_UNSHARE_DESKTOP: { ASSERT(g_hetDDDesktopIsShared); g_hetDDDesktopIsShared = FALSE; HETDDViewing(FALSE); } break; case HET_ESC_VIEWER: { HETDDViewing(((LPHET_VIEWER)pResult)->viewersPresent != 0); break; } default: { ERROR_OUT(("Unrecognized HET escape")); rc = FALSE; } break; } DebugExitBOOL(HET_DDProcessRequest, rc); return(rc); } // // HETDDViewing() // // Called when viewing of our shared apps starts/stops. Naturally, no longer // sharing anything stops viewing also. // void HETDDViewing(BOOL fViewers) { DebugEntry(HETDDViewing); if (g_oeViewers != fViewers) { g_oeViewers = fViewers; OE_DDViewing(fViewers); } DebugExitVOID(HETDDViewing); }