Click here to Skip to main content
Click here to Skip to main content

Screen Cast Server with Control

, 29 Sep 2009
Rate this:
Please Sign up or sign in to vote.
A screen cast server with mouse, keyboard, and clipboard control

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();

//************************************* 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:

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.

//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.

#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.

//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.

//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)

About the Author

Pieter Alec Myburgh
Software Developer (Senior) Process Computing Technology
South Africa South Africa
Senior Software Engineer & Manager.
 
I reject your code and substitute my own!
Follow on   Twitter

Comments and Discussions

 
QuestionServer Memory Leak Pinmembermwechter9-Dec-11 7:58 
GeneralMy vote of 1 PinmemberShargon_8526-Jul-11 2:17 
QuestionChanging network way PinmemberZananok13-Apr-11 8:54 
AnswerRe: Changing network way PinmemberLinoxxis4-May-11 21:38 
GeneralRe: Changing network way PinmemberZananok7-May-11 4:09 
GeneralRe: Changing network way PinmemberLinoxxis18-Aug-11 0:35 
GeneralMy vote of 5 Pinmembersaransaki0814-Oct-10 3:31 
GeneralWorking on it slowly. PinmemberLinoxxis14-Dec-09 19:02 
GeneralVery good but a bit buggy PinmemberMember 199871120-Oct-09 1:42 
QuestionOptions to improve performance? PinmemberBill Seddon6-Oct-09 1:38 
AnswerRe: Options to improve performance? PinmemberLinoxxis12-Oct-09 20:53 
GeneralRe: Options to improve performance? PinmemberLinoxxis12-Oct-09 21:19 
GeneralRe: Options to improve performance? PinmemberBill Seddon12-Oct-09 22:56 
GeneralDead Average PinmemberJonathan C Dickinson5-Oct-09 22:16 
GeneralMy vote of 1 PinmvpDave Kreskowiak30-Sep-09 1:52 
GeneralRe: My vote of 1 PinmemberStephan Johnson30-Sep-09 7:53 
GeneralRe: My vote of 1 PinmemberStephan Johnson30-Sep-09 7:56 
GeneralRe: My vote of 1 PinmemberLinoxxis30-Sep-09 20:14 
Well it seems like you can't.
My aim here was to create a small and simple screen cast server.
If you need help understanding it yourself I suggest buying this book
http://www.amazon.com/C-Bible-Jeff-Ferguson/dp/0764548344

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 30 Sep 2009
Article Copyright 2009 by Pieter Alec Myburgh
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid