1322 lines
32 KiB
C++
1322 lines
32 KiB
C++
#include "precomp.h"
|
||
|
||
|
||
//
|
||
// HET.CPP
|
||
// Window, task tracking hooks
|
||
//
|
||
// Copyright(c) Microsoft 1997-
|
||
//
|
||
|
||
|
||
|
||
//
|
||
// Entry Point
|
||
//
|
||
int APIENTRY DllMain (HINSTANCE hInstance, DWORD reason, LPVOID plReserved)
|
||
{
|
||
//
|
||
// DONT ADD ANY TRACING TO THIS FUNCTION OR ANY FUNCTIONS CALLED FROM
|
||
// HERE - WE CANNOT GUARANTEE THAT THE TRACE DLL IS IN A FIT STATE TO
|
||
// DO ANYTHING FROM HERE.
|
||
//
|
||
|
||
switch (reason)
|
||
{
|
||
case DLL_PROCESS_ATTACH:
|
||
{
|
||
#ifdef _DEBUG
|
||
InitDebugModule(TEXT("MNMHOOK"));
|
||
#endif // _DEBUG
|
||
|
||
DBG_INIT_MEMORY_TRACKING(hInstance);
|
||
|
||
HOOK_Load(hInstance);
|
||
}
|
||
break;
|
||
|
||
case DLL_PROCESS_DETACH:
|
||
{
|
||
TRACE_OUT(("HOOK unloaded for app %s", GetCommandLine()));
|
||
|
||
DBG_CHECK_MEMORY_TRACKING(hInstance);
|
||
|
||
#ifdef _DEBUG
|
||
//
|
||
// NULL this out in debug to see if our hooks get called on
|
||
// this process while we are exiting.
|
||
//
|
||
g_hookInstance = NULL;
|
||
|
||
ExitDebugModule();
|
||
#endif
|
||
}
|
||
break;
|
||
|
||
case DLL_THREAD_ATTACH:
|
||
{
|
||
HOOK_NewThread();
|
||
}
|
||
break;
|
||
}
|
||
|
||
return(TRUE);
|
||
}
|
||
|
||
|
||
//
|
||
// HOOK_Load()
|
||
// This saves our instance handle and gets hold of various routines we
|
||
// need for window tracking. We can not link to these functions directly
|
||
// since some of them only exist in NT 4.0 SP-3, but we want you to be able
|
||
// to view and control without it.
|
||
//
|
||
void HOOK_Load(HINSTANCE hInst)
|
||
{
|
||
DWORD dwExeType;
|
||
LPSTR lpT;
|
||
LPSTR lpNext;
|
||
LPSTR lpLastPart;
|
||
char szExeName[MAX_PATH+1];
|
||
|
||
DebugEntry(HOOK_Load);
|
||
|
||
//
|
||
// Save our instance
|
||
//
|
||
g_hookInstance = hInst;
|
||
|
||
//
|
||
// (1) NtQueryInformationProcess() in NTDLL
|
||
// (2) SetWinEventHook() in USER32
|
||
// (3) UnhookWinEventHook() in USER32
|
||
//
|
||
|
||
// Get hold of NtQueryInformationProcess
|
||
hInst = GetModuleHandle(NTDLL_DLL);
|
||
g_hetNtQIP = (NTQIP) GetProcAddress(hInst, "NtQueryInformationProcess");
|
||
|
||
// Get hold of the WinEvent routines
|
||
hInst = GetModuleHandle(TEXT("USER32.DLL"));
|
||
g_hetSetWinEventHook = (SETWINEVENTHOOK)GetProcAddress(hInst, "SetWinEventHook");
|
||
g_hetUnhookWinEvent = (UNHOOKWINEVENT)GetProcAddress(hInst, "UnhookWinEvent");
|
||
|
||
//
|
||
// Figure out what type of app we are. We want to treat separate groupware
|
||
// process applets and WOW16 apps specially.
|
||
//
|
||
GetModuleFileName(NULL, szExeName, sizeof(szExeName));
|
||
ASSERT(*szExeName);
|
||
|
||
TRACE_OUT(("HOOK loaded for app %s", szExeName));
|
||
|
||
//
|
||
// Start at the beginning, and work our way to the last part after the
|
||
// last slash, if there is one. We know the path is fully qualified.
|
||
//
|
||
lpT = szExeName;
|
||
lpLastPart = szExeName;
|
||
|
||
while (*lpT)
|
||
{
|
||
lpNext = AnsiNext(lpT);
|
||
|
||
if (*lpT == '\\')
|
||
{
|
||
//
|
||
// This points to the next character AFTER the backwhack.
|
||
// If we're at the end of the string somehow, *lpLastPart will
|
||
// be zero, and worst that can happen is that our lstrcmpis fail.
|
||
//
|
||
lpLastPart = lpNext;
|
||
}
|
||
|
||
lpT = lpNext;
|
||
}
|
||
|
||
ASSERT(*lpLastPart);
|
||
|
||
//
|
||
// NOTE:
|
||
// GetModuleFileName() dies sometimes for a WOW app--it doesn't always
|
||
// NULL terminate. So we will do this on our own.
|
||
//
|
||
lpT = lpLastPart;
|
||
|
||
//
|
||
// Get to the '.' part of the 8.3 final file name
|
||
//
|
||
while (*lpT && (*lpT != '.'))
|
||
{
|
||
lpT = AnsiNext(lpT);
|
||
}
|
||
|
||
//
|
||
// Skip past the next three chars
|
||
//
|
||
if (*lpT == '.')
|
||
{
|
||
lpT = AnsiNext(lpT);
|
||
if (lpT && *lpT)
|
||
lpT = AnsiNext(lpT);
|
||
if (lpT && *lpT)
|
||
lpT = AnsiNext(lpT);
|
||
if (lpT && *lpT)
|
||
lpT = AnsiNext(lpT);
|
||
|
||
//
|
||
// And null terminate after the 3rd char past the '.' extension.
|
||
// This isn't great, but it covers .COM, .DLL, etc. dudes. The
|
||
// worst that will happen is GetBinaryType() will fail and we won't
|
||
// recognize a WOW app with some strange extension (not 3 chars)
|
||
// starting up.
|
||
//
|
||
if (lpT)
|
||
{
|
||
if (*lpT != 0)
|
||
{
|
||
WARNING_OUT(("WOW GetModuleFileName() bug--didn't NULL terminate string"));
|
||
}
|
||
|
||
*lpT = 0;
|
||
}
|
||
}
|
||
|
||
if (!lstrcmpi(lpLastPart, "WOWEXEC.EXE"))
|
||
{
|
||
TRACE_OUT(("New WOW VDM starting up"));
|
||
|
||
//
|
||
// A new WOW VDM is starting up. We don't want to share anything
|
||
// in the first thread, the WOW service thread, because those windows
|
||
// never go away.
|
||
//
|
||
g_appType = HET_WOWVDM_APP;
|
||
}
|
||
else if (!GetBinaryType(szExeName, &dwExeType))
|
||
{
|
||
ERROR_OUT(("Unable to determine binary type for %s", szExeName));
|
||
}
|
||
else if (dwExeType == SCS_WOW_BINARY)
|
||
{
|
||
TRACE_OUT(("New WOW APP in existing VDM starting up"));
|
||
|
||
//
|
||
// A new 16-bit app thread is starting in an existing WOW vdm.
|
||
//
|
||
g_idWOWApp = GetCurrentThreadId();
|
||
g_fShareWOWApp = (BOOL)HET_GetHosting(GetForegroundWindow());
|
||
|
||
TRACE_OUT(("For new WOW app %08ld, foreground is %s",
|
||
g_idWOWApp, (g_fShareWOWApp ? "SHARED" : "not SHARED")));
|
||
|
||
//
|
||
// Remember who was really active when this WOW dude was started
|
||
// up. On the first window create, we'll share him based on the
|
||
// status of it.
|
||
//
|
||
}
|
||
|
||
DebugExitVOID(HOOK_ProcessAttach);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// HOOK_NewThread()
|
||
// For WOW apps, each app is really a thread. The first thread created
|
||
// in NTVDM is the WOW service thread. We don't want to share any windows
|
||
// in it. Unfortunately, the first window created is a console window, so
|
||
// that happens in CONF's context and we can't get any info. The next window
|
||
// created in this thread is a WOW window (WOWEXEC.EXE). When that happens,
|
||
// we want to go back and unshare the console window.
|
||
//
|
||
// If the WOW VDM is already running when another 16-bit app starts up,
|
||
// we don't have these troubles.
|
||
//
|
||
void HOOK_NewThread(void)
|
||
{
|
||
DebugEntry(HOOK_NewThread);
|
||
|
||
TRACE_OUT(("App thread %08ld starting", GetCurrentThreadId()));
|
||
|
||
if (g_appType == HET_WOWVDM_APP)
|
||
{
|
||
TRACE_OUT(("Unsharing WOW service thread windows"));
|
||
|
||
//
|
||
// We want to go unshare the previously created WOW windows. We
|
||
// never want to keep shared the dudes in the WOW service thread.
|
||
//
|
||
g_appType = 0;
|
||
EnumWindows(HETUnshareWOWServiceWnds, GetCurrentProcessId());
|
||
}
|
||
|
||
// Update our "share windows on this thread" state.
|
||
g_idWOWApp = GetCurrentThreadId();
|
||
g_fShareWOWApp = (BOOL)HET_GetHosting(GetForegroundWindow());
|
||
|
||
TRACE_OUT(("For new app thread %08ld, foreground is %s",
|
||
g_idWOWApp, (g_fShareWOWApp ? "SHARED" : "not SHARED")));
|
||
|
||
DebugExitVOID(HOOK_NewThread);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// HETUnshareWOWServiceWnds()
|
||
// This unshares any windows that accidentally got shared in the first
|
||
// service thread in a WOW VDM. This can happen if a WOW app is launched
|
||
// by a 32-bit app, and it's the first WOW app ever. The first window
|
||
// created is a console window, and the notification happens in CONF's
|
||
// process without the right styles that tell us it's in a WOW process.
|
||
//
|
||
BOOL CALLBACK HETUnshareWOWServiceWnds(HWND hwnd, LPARAM lParam)
|
||
{
|
||
DWORD idProcess;
|
||
|
||
DebugEntry(HETUnshareWOWServiceWnds);
|
||
|
||
if (GetWindowThreadProcessId(hwnd, &idProcess) &&
|
||
(idProcess == (DWORD)lParam))
|
||
{
|
||
TRACE_OUT(("Unsharing WOW service window %08lx", hwnd));
|
||
OSI_UnshareWindow(hwnd, TRUE);
|
||
}
|
||
|
||
DebugExitVOID(HETUnshareWOWServiceWnds);
|
||
return(TRUE);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// HOOK_Init()
|
||
// This saves away the core window and atom used in the high level input
|
||
// hooks and when sharing.
|
||
//
|
||
void WINAPI HOOK_Init(HWND hwndCore, ATOM atomTrack)
|
||
{
|
||
DebugEntry(HOOK_Init);
|
||
|
||
g_asMainWindow = hwndCore;
|
||
g_asHostProp = atomTrack;
|
||
|
||
DebugExitVOID(HOOK_Init);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// OSI_StartWindowTracking()
|
||
// This installs our WinEvent hook so we can watch windows coming and going.
|
||
//
|
||
BOOL WINAPI OSI_StartWindowTracking(void)
|
||
{
|
||
BOOL rc = FALSE;
|
||
|
||
DebugEntry(OSI_StartWindowTracking);
|
||
|
||
ASSERT(!g_hetTrackHook);
|
||
|
||
//
|
||
// If we can't find the NTDLL + 2 USER32 routines we need, we can't
|
||
// let you share.
|
||
//
|
||
if (!g_hetNtQIP || !g_hetSetWinEventHook || !g_hetUnhookWinEvent)
|
||
{
|
||
ERROR_OUT(("Wrong version of NT; missing NTDLL and USER32 routines needed to share"));
|
||
DC_QUIT;
|
||
}
|
||
|
||
|
||
//
|
||
// Install our hook.
|
||
//
|
||
g_hetTrackHook = g_hetSetWinEventHook(HET_MIN_WINEVENT, HET_MAX_WINEVENT,
|
||
g_hookInstance, HETTrackProc, 0, 0,
|
||
WINEVENT_INCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
||
|
||
if (!g_hetTrackHook)
|
||
{
|
||
ERROR_OUT(("SetWinEventHook failed"));
|
||
DC_QUIT;
|
||
}
|
||
|
||
rc = TRUE;
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitBOOL(OSI_StartWindowTracking, rc);
|
||
return(rc);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// OSI_StopWindowTracking()
|
||
// Removes our hooks for window/task spying, if installed.
|
||
//
|
||
void WINAPI OSI_StopWindowTracking(void)
|
||
{
|
||
DebugEntry(OSI_StopWindowTracking);
|
||
|
||
if (g_hetTrackHook)
|
||
{
|
||
// Uninstall the WinEvent hook
|
||
ASSERT((g_hetUnhookWinEvent != NULL));
|
||
g_hetUnhookWinEvent(g_hetTrackHook);
|
||
|
||
g_hetTrackHook = NULL;
|
||
|
||
}
|
||
|
||
DebugExitVOID(OSI_StopWindowTracking);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// OSI_IsWindowScreenSaver()
|
||
//
|
||
// On NT the screensaver runs in a different desktop. We'll never get
|
||
// an HWND for it.
|
||
//
|
||
BOOL WINAPI OSI_IsWindowScreenSaver(HWND hwnd)
|
||
{
|
||
#ifdef _DEBUG
|
||
char className[HET_CLASS_NAME_SIZE];
|
||
|
||
if (GetClassName(hwnd, className, sizeof(className)) > 0)
|
||
{
|
||
ASSERT(lstrcmp(className, HET_SCREEN_SAVER_CLASS));
|
||
}
|
||
#endif // _DEBUG
|
||
|
||
return(FALSE);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// OSI_IsWOWWindow()
|
||
// Returns TRUE if the window is from a WOW (emulated 16-bit) application
|
||
//
|
||
BOOL WINAPI OSI_IsWOWWindow(HWND hwnd)
|
||
{
|
||
BOOL rc = FALSE;
|
||
DWORD_PTR* pWOWWords;
|
||
|
||
DebugEntry(OSI_IsWOWWindow);
|
||
|
||
//
|
||
// Get a pointer to the potential WOW words. We make use of an
|
||
// undocumented field which is only valid for NT4.0.
|
||
//
|
||
pWOWWords = (DWORD_PTR*) GetClassLongPtr(hwnd, GCL_WOWWORDS);
|
||
|
||
//
|
||
// Check that we can use this as a pointer.
|
||
//
|
||
if (!pWOWWords || IsBadReadPtr(pWOWWords, sizeof(DWORD)))
|
||
{
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// This is a valid pointer so try to dereference it.
|
||
//
|
||
if (0 == *pWOWWords)
|
||
{
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// The value pointed at by <pWOWWords> is non-zero so this must be a
|
||
// WOW app.
|
||
//
|
||
rc = TRUE;
|
||
|
||
DC_EXIT_POINT:
|
||
//
|
||
// Let the world know what we've found.
|
||
//
|
||
TRACE_OUT(( "Window %#x is a %s window", hwnd, rc ? "WOW" : "Win32"));
|
||
|
||
DebugExitBOOL(OSI_IsWOWWindow, rc);
|
||
return(rc);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// HETTrackProc()
|
||
// Used to spy on window events
|
||
// CREATE
|
||
// DESTROY
|
||
// SHOW
|
||
// HIDE
|
||
//
|
||
void CALLBACK HETTrackProc
|
||
(
|
||
HWINEVENTHOOK hEvent,
|
||
DWORD eventNotification,
|
||
HWND hwnd,
|
||
LONG idObject,
|
||
LONG idChild,
|
||
DWORD dwThreadId,
|
||
DWORD dwmsEventTime
|
||
)
|
||
{
|
||
DebugEntry(HETTrackProc);
|
||
|
||
if ((idObject != OBJID_WINDOW) || (idChild != 0))
|
||
{
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// Work around a bug in SP3 with ring transition callbacks, where this
|
||
// proc gets called before the LoadLibrary is completed.
|
||
//
|
||
if (!g_hookInstance)
|
||
{
|
||
ERROR_OUT(( "WinEvent hook called before LoadLibrary completed!"));
|
||
DC_QUIT;
|
||
}
|
||
|
||
switch (eventNotification)
|
||
{
|
||
case EVENT_OBJECT_CREATE:
|
||
HETHandleCreate(hwnd);
|
||
break;
|
||
|
||
case EVENT_OBJECT_DESTROY:
|
||
OSI_UnshareWindow(hwnd, TRUE);
|
||
break;
|
||
|
||
case EVENT_OBJECT_SHOW:
|
||
// Only if this is a console window do we want to force a repaint.
|
||
//
|
||
// Only console apps cause events to occur in CONF's process (the one
|
||
// that installed the hook)
|
||
//
|
||
HETHandleShow(hwnd, (g_hetTrackHook != NULL));
|
||
break;
|
||
|
||
case EVENT_OBJECT_HIDE:
|
||
HETHandleHide(hwnd);
|
||
break;
|
||
|
||
case EVENT_OBJECT_PARENTCHANGE:
|
||
HETCheckParentChange(hwnd);
|
||
break;
|
||
}
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitVOID(HETTrackProc);
|
||
}
|
||
|
||
//
|
||
// HETHandleCreate()
|
||
//
|
||
// If the window isn't a real top level dude (not CHILD style or parent is
|
||
// desktop) or is a menu, ignore it.
|
||
//
|
||
// Otherwise enum the top level windows and decide what to do:
|
||
// * If at least one other in the thread/process is shared in a perm.
|
||
// way, mark this the same
|
||
//
|
||
// * If this is the only one in the process, follow the ancestor chain
|
||
// up.
|
||
//
|
||
void HETHandleCreate(HWND hwnd)
|
||
{
|
||
HET_TRACK_INFO hti;
|
||
UINT hostType;
|
||
#ifdef _DEBUG
|
||
char szClass[HET_CLASS_NAME_SIZE];
|
||
|
||
GetClassName(hwnd, szClass, sizeof(szClass));
|
||
#endif
|
||
|
||
DebugEntry(HETHandleCreate);
|
||
|
||
//
|
||
// Ignore child windows
|
||
//
|
||
if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD)
|
||
{
|
||
if (GetParent(hwnd) != GetDesktopWindow())
|
||
{
|
||
TRACE_OUT(("Skipping child window %08lx create", hwnd));
|
||
DC_QUIT;
|
||
}
|
||
}
|
||
|
||
hti.idThread = GetWindowThreadProcessId(hwnd, &hti.idProcess);
|
||
if (!hti.idThread)
|
||
{
|
||
TRACE_OUT(("Window %08lx gone", hwnd));
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// Ignore special threads
|
||
//
|
||
if (HET_IsShellThread(hti.idThread))
|
||
{
|
||
TRACE_OUT(("Skipping shell thread window %08lx 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.
|
||
// NOTE:
|
||
// We don't want to inadvertently share the other windows WOW creates.
|
||
// The first thread in the WOW process has special classes, which aren't
|
||
// WOW wrappers.
|
||
//
|
||
hti.hwndUs = hwnd;
|
||
hti.fWOW = OSI_IsWOWWindow(hwnd);
|
||
hti.cWndsApp = 0;
|
||
hti.cWndsSharedThread = 0;
|
||
hti.cWndsSharedProcess = 0;
|
||
|
||
TRACE_OUT(("Create for %s window %08lx class %s process %08ld thread %08ld",
|
||
(hti.fWOW ? "WOW" : "32-bit"), hwnd, szClass, hti.idProcess, hti.idThread));
|
||
|
||
UpOneLevel:
|
||
EnumWindows(HETShareEnum, (LPARAM)(LPHET_TRACK_INFO)&hti);
|
||
|
||
if (hti.cWndsSharedThread)
|
||
{
|
||
TRACE_OUT(("Sharing window %08lx class %s by thread %08ld in process %08ld",
|
||
hwnd, szClass, hti.idThread, hti.idProcess));
|
||
hostType = HET_HOSTED_PERMANENT | HET_HOSTED_BYTHREAD;
|
||
}
|
||
else if (hti.cWndsSharedProcess)
|
||
{
|
||
TRACE_OUT(("Sharing window %08lx class %s by process %08ld in thread %08ld",
|
||
hwnd, szClass, hti.idProcess, hti.idThread));
|
||
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(("Not sharing window %08lx class %s; other unshared windows in thread %08ld process %08ld",
|
||
hwnd, szClass, hti.idThread, hti.idProcess));
|
||
DC_QUIT;
|
||
}
|
||
else if (hti.fWOW)
|
||
{
|
||
//
|
||
// Task tracking code for WOW apps, which are really threads.
|
||
//
|
||
BOOL fShare;
|
||
|
||
//
|
||
// WOW apps are different. They are threads in the NTVDM process.
|
||
// Therefore parent/child relationships aren't useful. Instead,
|
||
// the best thing we can come up with is to use the status of the
|
||
// foreground window. We assume that the currently active app at
|
||
// the time the WOW app started up is the one that launched us.
|
||
//
|
||
// We can't just call GetForegroundWindow() here, because it is too
|
||
// late.
|
||
//
|
||
if (hti.idThread == g_idWOWApp)
|
||
{
|
||
fShare = g_fShareWOWApp;
|
||
|
||
g_fShareWOWApp = FALSE;
|
||
g_idWOWApp = 0;
|
||
}
|
||
else
|
||
{
|
||
fShare = FALSE;
|
||
}
|
||
|
||
if (!fShare)
|
||
{
|
||
TRACE_OUT(("THREAD window %08lx class %s in thread %08ld not shared",
|
||
hwnd, szClass, hti.idThread));
|
||
DC_QUIT;
|
||
}
|
||
|
||
TRACE_OUT(("First window %08lx class %s of WOW app %08ld, shared since foreground is",
|
||
hwnd, szClass, hti.idThread));
|
||
hostType = HET_HOSTED_PERMANENT | HET_HOSTED_BYTHREAD;
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// Task tracking code for 32-bit apps.
|
||
//
|
||
DWORD idParentProcess;
|
||
|
||
//
|
||
// First window of a WIN32 app.
|
||
//
|
||
|
||
// 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 %08ld", 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 %08lx class %s in process %08ld %s, checking parent %08ld",
|
||
hwnd, szClass, hti.idProcess, GetCommandLine(), idParentProcess));
|
||
|
||
hti.idThread = 0;
|
||
hti.idProcess = idParentProcess;
|
||
goto UpOneLevel;
|
||
}
|
||
|
||
//
|
||
// OK, we are going to share this. We do have to repaint console
|
||
// windows--we get the notifications asynchronously. If the window isn't
|
||
// visible yet, redrawing will do nothing. After this, the property is
|
||
// set, and we will catch all ouput. If it has already become visible,
|
||
// invalidating it now will still work, and we will ignore the queued
|
||
// up show notification because the property is set.
|
||
//
|
||
OSI_ShareWindow(hwnd, hostType, (g_hetTrackHook != NULL), TRUE);
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitVOID(HETHandleCreate);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// HETHandleShow()
|
||
//
|
||
void HETHandleShow
|
||
(
|
||
HWND hwnd,
|
||
BOOL fForceRepaint
|
||
)
|
||
{
|
||
UINT hostType;
|
||
HET_TRACK_INFO hti;
|
||
|
||
DebugEntry(HETHandleShow);
|
||
|
||
hostType = (UINT)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) != GetDesktopWindow())
|
||
{
|
||
TRACE_OUT(("Skipping child window %08lx show", hwnd));
|
||
if (hostType)
|
||
{
|
||
WARNING_OUT(("Unsharing shared child window 0x%08x from SHOW", hwnd));
|
||
OSI_UnshareWindow(hwnd, TRUE);
|
||
}
|
||
DC_QUIT;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Is this window already shared? Nothing to do if so. If it's a
|
||
// console guy, we've seen it already on create.
|
||
//
|
||
if (hostType)
|
||
{
|
||
TRACE_OUT(("Window %08lx 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 = GetWindowThreadProcessId(hwnd, &hti.idProcess);
|
||
if (!hti.idThread)
|
||
{
|
||
TRACE_OUT(("Window %08lx gone", hwnd));
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// Ignore special shell threads
|
||
//
|
||
if (HET_IsShellThread(hti.idThread))
|
||
{
|
||
TRACE_OUT(("Skipping shell thread window %08lx show", hwnd));
|
||
DC_QUIT;
|
||
}
|
||
|
||
hti.hwndUs = hwnd;
|
||
hti.fWOW = OSI_IsWOWWindow(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 %08lx of window %08lx", hwndOwner, hwnd));
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!hwndOwner)
|
||
{
|
||
DC_QUIT;
|
||
}
|
||
}
|
||
|
||
//
|
||
// For console apps, we get notifications asynchronously posted to us,
|
||
// in NM's process. The window may have painted already without our
|
||
// seeing it. So force it to repaint just in case. The g_hetTrackHook
|
||
// variable is only around when this is NM.
|
||
//
|
||
TRACE_OUT(("Sharing temporary window %08lx", hwnd));
|
||
|
||
OSI_ShareWindow(hwnd, HET_HOSTED_BYWINDOW | HET_HOSTED_TEMPORARY,
|
||
fForceRepaint, TRUE);
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitVOID(HETHandleShow);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// HETHandleHide()
|
||
// This handles a window being hidden. If it was temporary, it is unshared.
|
||
// If it is permanent, it is marked as hidden.
|
||
//
|
||
void HETHandleHide(HWND hwnd)
|
||
{
|
||
UINT hostType;
|
||
|
||
DebugEntry(HETHandleHide);
|
||
|
||
hostType = (UINT)HET_GetHosting(hwnd);
|
||
|
||
if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD)
|
||
{
|
||
if (GetParent(hwnd) != GetDesktopWindow())
|
||
{
|
||
TRACE_OUT(("Skipping child window %08lx hide", hwnd));
|
||
if (hostType)
|
||
{
|
||
WARNING_OUT(("Unsharing shared child window 0x%08x from HIDE", hwnd));
|
||
OSI_UnshareWindow(hwnd, TRUE);
|
||
}
|
||
DC_QUIT;
|
||
}
|
||
}
|
||
|
||
if (!hostType)
|
||
{
|
||
//
|
||
// Console apps give us notifications out of context. Make
|
||
// sure the count is up to date.
|
||
//
|
||
if (g_hetTrackHook)
|
||
{
|
||
HETNewTopLevelCount();
|
||
}
|
||
else
|
||
{
|
||
TRACE_OUT(("Window %08lx not shared, ignoring hide", hwnd));
|
||
}
|
||
}
|
||
else if (hostType & HET_HOSTED_TEMPORARY)
|
||
{
|
||
//
|
||
// Temporarily shared window are only shared when visible.
|
||
//
|
||
TRACE_OUT(("Unsharing temporary window %08lx", hwnd));
|
||
OSI_UnshareWindow(hwnd, TRUE);
|
||
}
|
||
else
|
||
{
|
||
ASSERT(hostType & HET_HOSTED_PERMANENT);
|
||
|
||
// Nothing to do.
|
||
TRACE_OUT(("Window %08lx permanently shared, ignoring hide", hwnd));
|
||
}
|
||
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitVOID(HETHandleHide);
|
||
}
|
||
|
||
|
||
//
|
||
// HETCheckParentChange()
|
||
//
|
||
// PARENTCHANGE is 100% reliable, compared to Win9x stuff.
|
||
//
|
||
void HETCheckParentChange(HWND hwnd)
|
||
{
|
||
DebugEntry(HETCheckParentChange);
|
||
|
||
WARNING_OUT(("Got PARENTCHANGE for hwnd 0x%08x", hwnd));
|
||
|
||
if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD)
|
||
{
|
||
if (GetParent(hwnd) != GetDesktopWindow())
|
||
{
|
||
UINT hostType;
|
||
|
||
hostType = (UINT)HET_GetHosting(hwnd);
|
||
if (hostType)
|
||
{
|
||
WARNING_OUT(("Unsharing shared child window 0x%08x from MOVE", hwnd));
|
||
OSI_UnshareWindow(hwnd, TRUE);
|
||
}
|
||
}
|
||
}
|
||
|
||
DebugExitVOID(HETCheckParentChange);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// OSI_ShareWindow
|
||
// This shares a window, calling the display driver to add it to the visrgn
|
||
// list. It is called when
|
||
// * An app is shared
|
||
// * A new window in a shared app is created
|
||
// * A temporary window with a relationship to a shared window is shown
|
||
//
|
||
// This returns TRUE if it shared a window.
|
||
//
|
||
BOOL OSI_ShareWindow
|
||
(
|
||
HWND hwnd,
|
||
UINT hostType,
|
||
BOOL fRepaint,
|
||
BOOL fUpdateCount
|
||
)
|
||
{
|
||
BOOL rc = FALSE;
|
||
HET_SHARE_WINDOW req;
|
||
|
||
DebugEntry(OSI_ShareWindow);
|
||
|
||
//
|
||
// Set the property
|
||
//
|
||
if (!HET_SetHosting(hwnd, hostType))
|
||
{
|
||
ERROR_OUT(("Couldn't set shared property on window %08lx", hwnd));
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// Tell the display driver
|
||
//
|
||
req.winID = HandleToUlong(hwnd);
|
||
req.result = 0;
|
||
if (!OSI_FunctionRequest(HET_ESC_SHARE_WINDOW, (LPOSI_ESCAPE_HEADER)&req,
|
||
sizeof(req)) ||
|
||
!req.result)
|
||
{
|
||
ERROR_OUT(("Driver couldn't add window %08lx to list", hwnd));
|
||
|
||
HET_ClearHosting(hwnd);
|
||
DC_QUIT;
|
||
}
|
||
|
||
TRACE_OUT(("Shared window %08lx of type %08lx", hwnd, hostType));
|
||
|
||
//
|
||
// Repaint it
|
||
//
|
||
if (fRepaint)
|
||
{
|
||
USR_RepaintWindow(hwnd);
|
||
}
|
||
|
||
if (fUpdateCount)
|
||
{
|
||
PostMessage(g_asMainWindow, DCS_NEWTOPLEVEL_MSG, TRUE, 0);
|
||
}
|
||
|
||
rc = TRUE;
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitBOOL(OSI_ShareWindow, rc);
|
||
return(rc);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// OSI_UnshareWindow()
|
||
// This unshares a window. This is called when
|
||
// * An app is unshared
|
||
// * A window is destroyed
|
||
// * A temporarily shared window is hidden
|
||
//
|
||
// It returns TRUE if a shared window has been unshared.
|
||
//
|
||
BOOL OSI_UnshareWindow
|
||
(
|
||
HWND hwnd,
|
||
BOOL fUpdateCount
|
||
)
|
||
{
|
||
BOOL rc = FALSE;
|
||
UINT hostType;
|
||
HET_UNSHARE_WINDOW req;
|
||
|
||
DebugEntry(OSI_UnshareWindow);
|
||
|
||
//
|
||
// This gets the old property and clears it in one step.
|
||
//
|
||
hostType = (UINT)HET_ClearHosting(hwnd);
|
||
if (!hostType)
|
||
{
|
||
if (fUpdateCount && g_hetTrackHook)
|
||
{
|
||
//
|
||
// We always get async notifications for console apps. In that
|
||
// case, the window is really gone before this comes to us.
|
||
// So redetermine the count now.
|
||
//
|
||
HETNewTopLevelCount();
|
||
}
|
||
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// OK, stuff to do.
|
||
//
|
||
TRACE_OUT(("Unsharing window %08lx of type %08lx", hwnd, hostType));
|
||
|
||
//
|
||
// Tell the display driver
|
||
//
|
||
req.winID = HandleToUlong(hwnd);
|
||
OSI_FunctionRequest(HET_ESC_UNSHARE_WINDOW, (LPOSI_ESCAPE_HEADER)&req, sizeof(req));
|
||
|
||
//
|
||
// Update the top level count
|
||
//
|
||
if (fUpdateCount)
|
||
{
|
||
PostMessage(g_asMainWindow, DCS_NEWTOPLEVEL_MSG, FALSE, 0);
|
||
}
|
||
|
||
rc = TRUE;
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitBOOL(OSI_UnshareWindow, rc);
|
||
return(rc);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// 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 = GetWindowThreadProcessId(hwnd, &idProcess);
|
||
if (!idThread)
|
||
{
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// Do the apps match? If not, ignore this window.
|
||
//
|
||
if ((idProcess != lphti->idProcess) ||
|
||
((lphti->fWOW) && (idThread != lphti->idThread)))
|
||
{
|
||
DC_QUIT;
|
||
}
|
||
|
||
lphti->cWndsApp++;
|
||
|
||
hostType = (UINT)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);
|
||
}
|
||
|
||
|
||
//
|
||
// HETNewTopLevelCount()
|
||
// This does a quick new tally of the shared top level visible count
|
||
//
|
||
void HETNewTopLevelCount(void)
|
||
{
|
||
UINT newCount;
|
||
|
||
DebugEntry(HETNewTopLevelCount);
|
||
|
||
newCount = 0;
|
||
EnumWindows(HETCountTopLevel, (LPARAM)&newCount);
|
||
|
||
PostMessage(g_asMainWindow, DCS_RECOUNTTOPLEVEL_MSG, newCount, 0);
|
||
|
||
DebugExitVOID(HETNewTopLevelCount);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// HETCountTopLevel()
|
||
// This counts shared windows
|
||
//
|
||
BOOL CALLBACK HETCountTopLevel(HWND hwnd, LPARAM lParam)
|
||
{
|
||
DebugEntry(HETCountTopLevel);
|
||
|
||
if (HET_GetHosting(hwnd))
|
||
{
|
||
(*(LPUINT)lParam)++;
|
||
}
|
||
|
||
DebugExitBOOL(HETCountTopLevel, TRUE);
|
||
return(TRUE);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// 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 == GetWindowThreadProcessId(HET_GetShellDesktop(), NULL)) ||
|
||
(threadID == GetWindowThreadProcessId(HET_GetShellTray(), NULL)))
|
||
{
|
||
rc = TRUE;
|
||
}
|
||
else
|
||
{
|
||
rc = FALSE;
|
||
}
|
||
|
||
DebugExitBOOL(HET_IsShellThread, rc);
|
||
return(rc);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// HET_WindowIsHosted()
|
||
// This is called by the high level mouse hook. Unlike the version in
|
||
// MNMCPI32, it doesn't check (or know) if the whole desktop is shared.
|
||
//
|
||
// LAURABU BOGUS
|
||
// Note that this may need to be revised. The high level hooks are handy
|
||
// in desktop sharing also. For the keyboard, we track the toggle key
|
||
// states. For the mouse, we block messages to non-shared windows.
|
||
//
|
||
BOOL HET_WindowIsHosted(HWND hwnd)
|
||
{
|
||
BOOL rc = FALSE;
|
||
HWND hwndParent;
|
||
|
||
DebugEntry(HET_WindowIsHosted);
|
||
|
||
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 = (BOOL)HET_GetHosting(hwnd);
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitBOOL(HET_WindowIsHosted, rc);
|
||
return(rc);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// HETGetParentProcessID()
|
||
// This gets the ID of the process which created the passed in one. Used
|
||
// for task tracking
|
||
//
|
||
void HETGetParentProcessID
|
||
(
|
||
DWORD processID,
|
||
LPDWORD pParentProcessID
|
||
)
|
||
{
|
||
HANDLE hProcess;
|
||
UINT intRC;
|
||
PROCESS_BASIC_INFORMATION basicInfo;
|
||
|
||
DebugEntry(HETGetParentProcessID);
|
||
|
||
*pParentProcessID = 0;
|
||
|
||
//
|
||
// Open a handle to the process. If we don't have security privileges,
|
||
// or it is gone, this will fail.
|
||
//
|
||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
||
FALSE, processID);
|
||
if (NULL == hProcess)
|
||
{
|
||
WARNING_OUT(("Can't get process handle for ID %08lx", processID));
|
||
DC_QUIT;
|
||
}
|
||
|
||
//
|
||
// Get back an information block for this process, one item of which is
|
||
// the parent.
|
||
//
|
||
ASSERT(g_hetNtQIP);
|
||
|
||
intRC = g_hetNtQIP(hProcess, ProcessBasicInformation, &basicInfo,
|
||
sizeof(basicInfo), NULL);
|
||
|
||
if (!NT_SUCCESS(intRC))
|
||
{
|
||
ERROR_OUT(("Can't get info for process ID %08lx, handle %08lx -- error %u",
|
||
processID, hProcess, intRC));
|
||
}
|
||
else
|
||
{
|
||
*pParentProcessID = basicInfo.InheritedFromUniqueProcessId;
|
||
}
|
||
|
||
//
|
||
// Close the process handle
|
||
//
|
||
CloseHandle(hProcess);
|
||
|
||
DC_EXIT_POINT:
|
||
DebugExitVOID(HETGetParentProcessID);
|
||
}
|
||
|
||
|