Introduction
Ever wanted to see what is on someone else's screen but you didn't want to install PC Anywhere or Configure VNC?
Well now, it's possible to just run the Screen Cast Server and view that person's screen from your Screen Cast Client.
Warning: This code is not optimized for Internet usage and does not imply that it is better than PC Anywhere, VNC, or any other 3rd party application on the market. It is also not encrypted in any way and is thus not secure.
Background
I am constantly bugged by end users that say "I did click the right button" or "I did select that first". I decided that I want to see what they see when they use my apps, so I developed this simple little client-server screen casting utility. The server part is so small that you can copy paste it into any of your apps and call it when that app loads.
Using the Code
Using this code is simple if you know a little bit of Remoting.
The server part uses Remoting to open a port and exposes the CastScreen
method:
ScreenHost.ScreenObject remoteObject = new ScreenHost.ScreenObject();
TcpChannel channel = new TcpChannel(8082);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ScreenHost.ScreenObject),
"CastScreen", WellKnownObjectMode.SingleCall);
The exposed method (CastScreen
) resides in a shared class that is used by both the client and the server:
public MemoryStream CastScreen(int formatType, bool showMouse)
{
MemoryStream ms;
Bitmap bitmap;
Bitmap outBmp;
Rectangle bounds = Screen.GetBounds(Screen.GetBounds(Point.Empty));
setImgFormat(formatType);
using (bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
ms = new MemoryStream();
if (showMouse)
{
outBmp = drawMouse(bitmap);
}
else
{
outBmp = bitmap;
}
outBmp.Save(ms, imgFormat);
return ms;
}
}
private Bitmap drawMouse(Bitmap value)
{
Point mPoint;
GetCursorPos(out mPoint);
for (int i = -3; i < 3; i++)
{
for (int j = -3; j < 3; j++)
{
value.SetPixel(mPoint.X + i, mPoint.Y + j, Color.Red);
}
}
return value;
}
The client application has a timer that acts as the refresh rate and it makes a BackGroundWorker
run the code to get the image from the server.
try
{
hostInstance =
(ScreenHost.ScreenObject)Activator.GetObject(typeof(ScreenHost.ScreenObject),
"tcp://" + hostIP + ":8082/CastScreen", null);
if (hostInstance != null)
{
ms = hostInstance.CastScreen(imgFormat);
img = Image.FromStream(ms);
updatePictureBox(picCast, img);
updateText(lblMessage, "Screen Updated - " + DateTime.Now);
}
}
catch (Exception exc)
{
if (bgWorker1.IsBusy)
{ bgWorker1.CancelAsync(); }
updateText(lblMessage, exc.Message);
}
Controlling the keyboard and mouse on the remote PC was tricky at first. This is until you discover windows 'built in' commands. When you expose the methods contained in Windows user32.dll controlling the keyboard and mouse becomes simple as well.
#region Mouse Methods
public void SetMouseLocation(Point mouseXY)
{
SetCursorPos(mouseXY.X, mouseXY.Y);
}
public void Left_Click_Down()
{
mouse_event(meLeftDown, 0, 0, 0, new System.IntPtr());
}
public void Left_Click_Up()
{
mouse_event(meLeftUp, 0, 0, 0, new System.IntPtr());
}
public void Right_Click_Down()
{
mouse_event(meRightDown, 0, 0, 0, new System.IntPtr());
}
public void Right_Click_Up()
{
mouse_event(meRightUp, 0, 0, 0, new System.IntPtr());
}
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out System.Drawing.Point lpPoint);
[DllImport("user32.dll")]
private static extern bool SetCursorPos(int X, int Y);
[DllImport("user32.dll")]
private static extern void mouse_event
(UInt32 dwFlags, UInt32 dx, UInt32 dy, UInt32 dwData, IntPtr dwExtraInfo);
#endregion
#region Keyboard Methods
public void keyboard_key_down(Byte KeyCode)
{
keybd_event(KeyCode, (byte)MapVirtualKey((int)KeyCode, 0), 0, 0);
}
public void keyboard_key_up(Byte KeyCode)
{
keybd_event((byte)KeyCode, (byte)MapVirtualKey((int)KeyCode, 0), 2, 0);
}
[DllImport("user32.dll")]
static extern short MapVirtualKey(int wCode, int wMapType);
[DllImport("user32.dll", EntryPoint = "keybd_event",
CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern void keybd_event(byte vk, byte scan, int flags, int extrainfo);
#endregion
Calling the control methods in the user32.dll is specific because it's Microsoft defined.
I found the following code to be the easiest way to do so.
void picCast_KeyDown(object sender, KeyEventArgs e)
{
if (chkKeyboard.Checked)
{
byte[] keyPressed = BitConverter.GetBytes(e.KeyValue);
hostInstance.keyboard_key_down(keyPressed[0]);
}
}
private void picCast_MouseMove(object sender, MouseEventArgs e)
{
try
{
mouseXY = e.Location;
if (connected)
{
if (chkMouse.Checked)
{
hostInstance.SetMouseLocation(mouseXY);
}
}
}
catch (Exception ex)
{
updateText(lblMessage, ex.Message);
}
}
private void picCast_MouseDown(object sender, MouseEventArgs e)
{
if (chkMouse.Checked)
{
if (e.Button == MouseButtons.Left)
{
hostInstance.Left_Click_Down();
}
if (e.Button == MouseButtons.Right)
{
hostInstance.Right_Click_Down();
}
}
}
When controlling the clipboard I found it easiest to use the .NET clipboard.get
and clipboard.set
commands. While doing this, I ran into some problems. I was getting STAThread
errors because I'm calling it on one thread and passing it to a Remote PC's server thread. To overcome this, I created a new thread that calls the method in its own method.
public string getClipboardText()
{
try
{
strValue = "";
th = new Thread(new ThreadStart(getSTAText));
th.SetApartmentState(ApartmentState.STA);
th.Start();
while (strValue == "")
{ }
return strValue;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return "";
}
}
private void getSTAText()
{
strValue = Clipboard.GetText();
if (strValue == "" || strValue == string.Empty)
{ strValue = "clipboard is empty"; }
}
Points of Interest
Like in version one of this app, there are so many applications for this code. It is simple to use because it mostly uses windows built in DLLs for control. To make this app even more simple, you can publish it to your company's website with Microsoft's one-click installer. For me, this is excellent because our company is a contracted company and we may not install apps on the main firm's computers unless its software we developed for them specifically. The pain of remote support has become a thing of the past.
History
This is version two of this app. The first app only does the screen casting. Please feel free to update and edit this code. As always... please share your updates.