Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#

Screen Cast Server with Control

Rate me:
Please Sign up or sign in to vote.
4.66/5 (19 votes)
29 Sep 2009CPOL2 min read 49.6K   2.1K   44   20
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)


Written By
Architect Backbase
Netherlands Netherlands
Senior Solutions Architect.

I reject your code and substitute my own Smile | :) !

Comments and Discussions

 
Questionchanging code for working via internet Pin
Member 1111991027-Sep-19 3:39
Member 1111991027-Sep-19 3:39 
QuestionAny thoughts on how I might adjust the size of the screen? Pin
Sean_C_Roberts1-Jan-17 15:14
Sean_C_Roberts1-Jan-17 15:14 
QuestionServer Memory Leak Pin
mwechter9-Dec-11 7:58
mwechter9-Dec-11 7:58 
GeneralMy vote of 1 Pin
Shargon_8526-Jul-11 2:17
Shargon_8526-Jul-11 2:17 
QuestionChanging network way Pin
Zananok13-Apr-11 8:54
Zananok13-Apr-11 8:54 
AnswerRe: Changing network way Pin
Pieter Alec Myburgh4-May-11 21:38
Pieter Alec Myburgh4-May-11 21:38 
GeneralRe: Changing network way Pin
Zananok7-May-11 4:09
Zananok7-May-11 4:09 
GeneralRe: Changing network way Pin
Pieter Alec Myburgh18-Aug-11 0:35
Pieter Alec Myburgh18-Aug-11 0:35 
GeneralMy vote of 5 Pin
saransaki0814-Oct-10 3:31
saransaki0814-Oct-10 3:31 
GeneralWorking on it slowly. Pin
Pieter Alec Myburgh14-Dec-09 19:02
Pieter Alec Myburgh14-Dec-09 19:02 
GeneralVery good but a bit buggy Pin
thewampek20-Oct-09 1:42
thewampek20-Oct-09 1:42 
QuestionOptions to improve performance? Pin
Bill Seddon6-Oct-09 1:38
Bill Seddon6-Oct-09 1:38 
AnswerRe: Options to improve performance? Pin
Pieter Alec Myburgh12-Oct-09 20:53
Pieter Alec Myburgh12-Oct-09 20:53 
GeneralRe: Options to improve performance? Pin
Pieter Alec Myburgh12-Oct-09 21:19
Pieter Alec Myburgh12-Oct-09 21:19 
GeneralRe: Options to improve performance? Pin
Bill Seddon12-Oct-09 22:56
Bill Seddon12-Oct-09 22:56 
GeneralDead Average Pin
Jonathan C Dickinson5-Oct-09 22:16
Jonathan C Dickinson5-Oct-09 22:16 
I am going to vote dead average because the code itself is definitely something valuable... A better explanation would have helped (especially what MapVirtualKey does Smile | :) - looks really interesting). Overall good work; I'm impressed.

Go bokke!!!

He who asks a question is a fool for five minutes. He who does not ask a question remains a fool forever. [Chineese Proverb]

Jonathan C Dickinson (C# Software Engineer)

GeneralMy vote of 1 Pin
Dave Kreskowiak30-Sep-09 1:52
mveDave Kreskowiak30-Sep-09 1:52 
GeneralRe: My vote of 1 Pin
Stephan Johnson30-Sep-09 7:53
Stephan Johnson30-Sep-09 7:53 
GeneralRe: My vote of 1 Pin
Stephan Johnson30-Sep-09 7:56
Stephan Johnson30-Sep-09 7:56 
GeneralRe: My vote of 1 Pin
Pieter Alec Myburgh30-Sep-09 20:14
Pieter Alec Myburgh30-Sep-09 20:14 

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

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