Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Screen Cast Server with Control

4.66/5 (19 votes)
29 Sep 2009CPOL2 min read 52.1K   2.1K  
A screen cast server with mouse, keyboard, and clipboard control
Image 1

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:

C#
ScreenHost.ScreenObject remoteObject = new ScreenHost.ScreenObject();

//************************************* TCP *************************************//
// using TCP protocol
TcpChannel channel = new TcpChannel(8082);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ScreenHost.ScreenObject),
                      "CastScreen", WellKnownObjectMode.SingleCall);
//************************************* TCP *************************************// 

The exposed method (CastScreen) resides in a shared class that is used by both the client and the server:

C#
public MemoryStream CastScreen(int formatType, bool showMouse)
{
   MemoryStream ms;
   Bitmap bitmap;
   Bitmap outBmp;

   //Get the Screen Bounds
   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);
      }

      //Convert the Image to the given format
      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.

C#
//Setup the Host Instance

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.

C#
#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());
}

//user32.dll Mouse Methods
[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);
}

//user32.dll Keyboard Methods
[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.

C#
//A sample Keyboard Event
void picCast_KeyDown(object sender, KeyEventArgs e)
{
  if (chkKeyboard.Checked)
  {
     byte[] keyPressed = BitConverter.GetBytes(e.KeyValue);
     hostInstance.keyboard_key_down(keyPressed[0]);
  }
}

//A sample Mouse Event
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.

C#
//Text
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)