Click here to Skip to main content
15,170,505 members
Articles / Programming Languages / C#
Article
Posted 29 Sep 2009

Tagged as

Stats

44.7K views
2.1K downloads
43 bookmarked

Screen Cast Server with Control

Rate me:
Please Sign up or sign in to vote.
4.64/5 (18 votes)
29 Sep 2009CPOL2 min read
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)

Share

About the Author

Pieter Alec Myburgh
Software Developer (Senior) EOH Microsoft Coastal
South Africa South Africa
Senior Software Engineer & Team Lead.

I reject your code and substitute my own!

Comments and Discussions

 
Questionchanging code for working via internet Pin
Member 1111991027-Sep-19 4:39
MemberMember 1111991027-Sep-19 4:39 
QuestionAny thoughts on how I might adjust the size of the screen? Pin
Sean_C_Roberts1-Jan-17 16:14
MemberSean_C_Roberts1-Jan-17 16:14 
Greetings, and first of all, I LOVE this code!

You offered in an earlier comment to send updated code by e-mail? May I please request that code?
I will PM you with my e-mail address.

I already programmed a remote-support app for the government agency for which I work (using mostly WMI), but what it lacks is remote control.
One of the problems with sharing desktops using OCS or Lync is that whenever something administrative is done, remote computers use "Secure Desktop", which in OCS or Lync, prevents the person sharing from seeing anything (and being able to provide admin credentials).
I have yet to test this on our domain, but if I can connect, and then actually type credentials into Secure Desktop, then your code is perfect! Smile | :)

** One question, though, in two parts: - there are lots of different desktop sizes around, and I would like to either:

1) Add scroll bars the sides of the PictureBox so that, if the tech or admin has a smaller screen, they can scroll to all of a user's screen - any thoughts on this? and\or...

2)Might there be a way to scale the size of the screen so that it fits in a sizable PictureBox? I guess then I would have to take the mouse input locally and find a way to calculate positions from the reduced size screen to the larger remote screen. May I please ask your thoughts about that?
QuestionServer Memory Leak Pin
mwechter9-Dec-11 8:58
Membermwechter9-Dec-11 8:58 
GeneralMy vote of 1 Pin
Shargon_8526-Jul-11 3:17
MemberShargon_8526-Jul-11 3:17 
QuestionChanging network way Pin
Zananok13-Apr-11 9:54
MemberZananok13-Apr-11 9:54 
AnswerRe: Changing network way Pin
Pieter Alec Myburgh4-May-11 22:38
MemberPieter Alec Myburgh4-May-11 22:38 
GeneralRe: Changing network way Pin
Zananok7-May-11 5:09
MemberZananok7-May-11 5:09 
GeneralRe: Changing network way Pin
Pieter Alec Myburgh18-Aug-11 1:35
MemberPieter Alec Myburgh18-Aug-11 1:35 
GeneralMy vote of 5 Pin
saransaki0814-Oct-10 4:31
Membersaransaki0814-Oct-10 4:31 
GeneralWorking on it slowly. Pin
Pieter Alec Myburgh14-Dec-09 20:02
MemberPieter Alec Myburgh14-Dec-09 20:02 
GeneralVery good but a bit buggy Pin
thewampek20-Oct-09 2:42
Memberthewampek20-Oct-09 2:42 
QuestionOptions to improve performance? Pin
Bill Seddon6-Oct-09 2:38
MemberBill Seddon6-Oct-09 2:38 
AnswerRe: Options to improve performance? Pin
Pieter Alec Myburgh12-Oct-09 21:53
MemberPieter Alec Myburgh12-Oct-09 21:53 
GeneralRe: Options to improve performance? Pin
Pieter Alec Myburgh12-Oct-09 22:19
MemberPieter Alec Myburgh12-Oct-09 22:19 
GeneralRe: Options to improve performance? Pin
Bill Seddon12-Oct-09 23:56
MemberBill Seddon12-Oct-09 23:56 
GeneralDead Average Pin
Jonathan C Dickinson5-Oct-09 23:16
MemberJonathan C Dickinson5-Oct-09 23:16 
GeneralMy vote of 1 Pin
Dave Kreskowiak30-Sep-09 2:52
mveDave Kreskowiak30-Sep-09 2:52 
GeneralRe: My vote of 1 Pin
Stephan Johnson30-Sep-09 8:53
MemberStephan Johnson30-Sep-09 8:53 
GeneralRe: My vote of 1 Pin
Stephan Johnson30-Sep-09 8:56
MemberStephan Johnson30-Sep-09 8:56 
GeneralRe: My vote of 1 Pin
Pieter Alec Myburgh30-Sep-09 21:14
MemberPieter Alec Myburgh30-Sep-09 21: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.