windows-nt/Source/XPSP1/NT/enduser/netmeeting/as/as16/het.c
2020-09-26 16:20:57 +08:00

995 lines
22 KiB
C

//
// HET.C
// Hosted Entity Tracker
//
// Copyright(c) Microsoft 1997-
//
#include <as16.h>
//
// 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);
}