Click here to Skip to main content
15,897,163 members
Articles / Desktop Programming / MFC

Extending Microsoft's Terminal Services Client To Provide Seamless Windows

Rate me:
Please Sign up or sign in to vote.
4.93/5 (61 votes)
14 Apr 2005GPL38 min read 616.5K   14.3K   176  
An article that describes a possible approach to extending Microsoft's Terminal Services/Remote Desktop Client to use seamless windows.
//*********************************************************************************
//
//Title: Terminal Services Window Clipper
//
//Author: Martin Wickett
//
//Date: 2004
//
//*********************************************************************************

#include <windows.h>

#include "resource.h"
#include "hookdll/hook.h"

//
// some global data
//
HWND				ghWnd;
NOTIFYICONDATA		nid;
HINSTANCE			hAppInstance;

static const UINT WM_TRAY_NOTIFY	= (WM_APP + 1000);
static const char szAppName[]		= "Terminal Services Window Clipper";

//
// spawn a message box
//
void Message(const char* message)
{
	MessageBox(GetDesktopWindow(),message,"TS Window Clipper", MB_OK);
}

//
// manage the tray icon
//
bool InitTrayIcon()
{
	nid.cbSize				= sizeof(NOTIFYICONDATA) ;
	nid.hWnd				= ghWnd;
	nid.uID					= 0 ;
	nid.uFlags				= NIF_MESSAGE | NIF_ICON | NIF_TIP ;	
	nid.uCallbackMessage	= WM_TRAY_NOTIFY;  
	strcpy(nid.szTip,szAppName);
	nid.hIcon				= ::LoadIcon(hAppInstance,MAKEINTRESOURCE(IDI_TRAY)); 
	
	if (Shell_NotifyIcon (NIM_ADD,&nid) != TRUE)
	{
		Message("Unable to create tray icon.");
		return false;
	}

	return true;
}

//
// Remove tray icon
//
bool RemoveTrayIcon()
{
	if (Shell_NotifyIcon (NIM_DELETE,&nid) != TRUE)
	{
		Message("Unable to remove tray icon.");
		return false;
	}

	return true;

}

//
// manage the about dialog box
//
BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	if (uMsg == WM_COMMAND)
	{
		WORD wID		 = LOWORD(wParam);
		if (wID == IDOK) DestroyWindow(hwndDlg);
	}

	return 0;
}

void AboutDlg()
{
	DialogBox(hAppInstance,MAKEINTRESOURCE(IDD_ABOUT),NULL,DialogProc);
}

//
// manage the context menu
//
void DoContextMenu()
{
	HMENU hMenu =LoadMenu(hAppInstance,MAKEINTRESOURCE(IDR_TRAY));
	if (hMenu == NULL)
	{
		Message("Unable to load menu ressource.");
		return;
	}

	HMENU hSubMenu = GetSubMenu(hMenu,0);
	if (hSubMenu == NULL)
	{
		Message("Unable to find popup mennu.");
		return;
	}

	// get the cursor position
	POINT pt;
	GetCursorPos (&pt) ;

	SetForegroundWindow(ghWnd);
	int cmd = TrackPopupMenu(hSubMenu,TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON,
							pt.x,pt.y,0,ghWnd,NULL);				
	DeleteObject(hMenu);

	switch (cmd)
	{
		case ID_WMEXIT :
		{
			PostQuitMessage(0);
			break;						
		}
		case ID_WMABOUT :
		{
			AboutDlg();
			break;
		}
	}
}

//
// manage the main window
//
LONG WINAPI MainWndProc ( HWND    hWnd, UINT    uMsg, WPARAM  wParam, LPARAM  lParam) 
{ 
	switch (uMsg)
	{
		case WM_DESTROY :
		{
			PostQuitMessage (0);
			return 0;
		}
		case WM_TRAY_NOTIFY :
		{
			if (lParam == WM_RBUTTONDOWN) 
				DoContextMenu();
			return 0;
		}
	}
	
	return DefWindowProc (hWnd, uMsg, wParam, lParam); 
} 

//
//Init window
//
bool InitWindow()
{
	// register the frame class
	WNDCLASS wndclass;
    wndclass.style         = 0; 
    wndclass.lpfnWndProc   = (WNDPROC)MainWndProc; 
    wndclass.cbClsExtra    = 0; 
    wndclass.cbWndExtra    = 0; 
    wndclass.hInstance     = hAppInstance; 
    wndclass.hIcon         = 0; 
    wndclass.hCursor       = LoadCursor (NULL,IDC_ARROW); 
    wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 
    wndclass.lpszMenuName  = NULL; 
    wndclass.lpszClassName = szAppName;
 
    if (!RegisterClass (&wndclass))
	{
		Message("Unable to register the window class.");
		return false;
	}
 
    // create the frame 
    ghWnd = CreateWindow (szAppName, szAppName, 
         WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 
             CW_USEDEFAULT, 
             CW_USEDEFAULT, 
             640, 
             480, 
             NULL, 
             NULL, 
             hAppInstance, 
             NULL); 
 
    // make sure window was created
    if (!ghWnd) 
	{
		Message("Unable to create the window.");
		return false; 
	}

	return true;
}

//
// init
//
bool Init(LPSTR lpCmdLine)
{
	// try to load WTSWinClipper.dll
	if (!WTSWinClipper::Init())
	{
		Message("Application not installed correctly: Unable to init hookdll.dll.");
		return false;
	}

	// check number of instances
	if (WTSWinClipper::GetInstanceCount() == 1)
	{
		// hook in
		WTSWinClipper::SetCbtHook();	
		return true;
	} else 
	{
		// already hooked
		return false;
	}
}

// 
// our main loop
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
{
	hAppInstance = hInstance;
	if (!Init(lpCmdLine)) 
	{
		return 0;
	}

	// if we have been specified an app to launch, we will wait until the app has closed and use that for
	// our cue to exit
	if (strlen(lpCmdLine)> 0)
	{
		// Because we do not have a explorer.exe we need to make this application the replacement
		// shell. We do this by calling SystemParametersInfo. If we don't do this, we won't get the WH_SHELL notifications.
		
		// From MSDN:
		// Note that custom shell applications do not receive WH_SHELL messages. Therefore, any application that 
		// registers itself as the default shell must call the SystemParametersInfo function with SPI_SETMINIMIZEDMETRICS 
		// before it (or any other application) can receive WH_SHELL messages.

		MINIMIZEDMETRICS mmm;
		mmm.cbSize = sizeof( MINIMIZEDMETRICS);
		SystemParametersInfo(SPI_SETMINIMIZEDMETRICS ,sizeof( MINIMIZEDMETRICS),&mmm,0);

		//set the current directory to that of the requested app .exe location
		//tokenise lpCmdLine. first is the exe path. second (if exists) is the current directory to set.
		//SetCurrentDirectory ();

		//start process specified from command line arg.
		PROCESS_INFORMATION procInfo;
		STARTUPINFO startupInfo = {0}; 
		startupInfo.cb = sizeof(STARTUPINFO);
		char attr[] = "";
		LPTSTR process= lpCmdLine;
		DWORD dwExitCode;

		BOOL m_create  = CreateProcess( NULL,process,NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &procInfo);
		
		if (m_create != false)
		{		 
			// A loop to watch the process. 
			GetExitCodeProcess(procInfo.hProcess, &dwExitCode);

			while (dwExitCode == STILL_ACTIVE )
			{
				GetExitCodeProcess(procInfo.hProcess, &dwExitCode);
				Sleep(1000);
			}

			// Release handles
			CloseHandle(procInfo.hProcess);
			CloseHandle(procInfo.hThread);
		}
		else
		{
			// CreateProcess failed.
			Message("Unable to launch the requested application");
		}
	}
	else
	// we are launching without an app, therefore we will show the system tray app and wait for the user to close it
	{
		// create a dummy window to receive WM_QUIT message
		InitWindow();

		// create the tray icon
		InitTrayIcon();

		// just get and dispatch messages until we're killed
		MSG msg;
		while (GetMessage(&msg,0,0,0))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		};

		// remove our tray icon
		RemoveTrayIcon();
	}


	// remove hook before saying goodbye
	WTSWinClipper::RemoveCbtHook();
	
	WTSWinClipper::Done();

	return 1;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions