You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1322 lines
33 KiB
1322 lines
33 KiB
#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)-1);
|
|
szExeName[sizeof(szExeName) -1] = 0;
|
|
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);
|
|
}
|
|
|