Delphi Programming

  1. Home
  2. Computing & Technology
  3. Delphi Programming
ScreenThief - stealing screen shots over the Network
Page 2: ScreenThief Server and Client: basic settings
 Win prizes by sharing code!
Do you have some Delphi code you want to share? Are you interested in winning a prize for your work?
Delphi Programming Quickies Contest
 More of this Feature
• Page 1: The idea
• Page 3: Server and Client: querying and retrieving Client screen shot images

• DOWNLOAD FULL SOURCE
 Join the Discussion
"Post your views, comments, questions and doubts to this article."
Discuss!
 Related Resources
• Exchanging Data over the Network using Delphi - simple CHAT application
• Introduction to INDY
• Network programming with Delphi
• Indy related articles

Ok, once you know what the main purpose of ScreenThief is, let's see how to create the application(s) using Delphi.

Before we delve into the ScreenThief project's GUI and code, I suggest you to download the full source code. ScreenThief consists of two separate applications (server and client) both added to a Delphi project group - when loading the projects in Delphi, you should open the ScreenThiefProject.bpg (Borland Project Group) file.
Note: code examples below represent only the portions of the code from the project.

ScreenThief SERVER
The server part of the ScreenThief project is build arround the TIdTCPServer component. TIdTCPServer Implements a multi-threaded TCP (Transmission Control Protocol) server. TIdTCPServer uses one or more threads to listen for client connections, and in conjunction with a TIdThreadMgr, allocates a separate thread to handle each client connection to the server.

To successfully start a TCP server, using the TIdTCPServer component, we need to setup the Bindings property - - the IP and the Port where the Server listens for clients. For the sake of simplicity the IP is set to '127.0.0.1' (localhost) and Port is set to 7676 (no particular reason). In a real world situation the IP would be set to an IP address of the "Server" computer (in a local network that would most probably be '198.162.0.1').

Here's how the ScreenThief server looks at design time:

ScreenThief SERVER at design time

Course of action
Remember than, in general, the (TCP) server cannot, by default, send a command (data) to the client without having the client specifically asking for something. In our situation, when we want to command a Client to send the image (screen shot of the Desktop), we need a mechanism of sending a query from the Server to a Client, something like "send me your screen shot". On the other hand, the (TCP) client is not designed to listen to commands from the Server.
In other words, we have a "problem" of being able to reverse the standard command processing from "Client -> Server" to "Server -> Client". There are many techniques of enabling the Client to listen to Server's commands. For example, we could create a separate thread on the Client that would look for any commands from the Server. The idTcpDemo Client demo application uses this technique. Another example comprises adding a TTimer component and handling the eventual command from the server in an OnTimer event handler. The Indy chat demo implements this.

To enable the ScreenThief Server application to "send" command to ScreenThief Client I've used another approach. I'll explaing it at the end of this page (when describing the ScreenThief Client application).

The ScreenThief server allows clients (using TIdTCPClient) to connect to it and maintains a list of connected clients. The list is held in an instance of the TThreadList (thread-safe list object) class (named 'Clients'). An item of the list is a record holding some *interesting* data of a connected client. This is the Client record declaration:

type
  PClient   = ^TClient;
  TClient   = record
    HostName    : String[20];  { Hostname }
    PeerIP      : string[15];  { Cleint IP address }
    TakeShot    : boolean;     { explained later }
    Connected,                 { Time of connect }
    LastAction  : TDateTime;   { Time of last transaction }
    Thread      : Pointer;     { Pointer to thread }
  end;
  
...
var
  Clients : TThreadList; 

When the Client connects to the Server (the OnConnect event of the TIdTcpServer component) a new instance of the TClient record is created and added to the list of connected clients. The AThread parameter (a thread that is created for every connection made to the TIdTCPServer) of the OnConnect event is used to populate the fields of the PClient variable (an item of the thread-safe 'Clients' list).

procedure TMainFormServer.TCPServerConnect(AThread: TIdPeerThread);
var
  NewClient: PClient;
begin
  GetMem(NewClient, SizeOf(TClient));

  NewClient.PeerIP     := AThread.Connection.Socket.Binding.PeerIP;
  NewClient.HostName   := GStack.WSGetHostByAddr(NewClient.PeerIP);
  NewClient.TakeShot   := False;
  NewClient.Connected  := Now;
  NewClient.LastAction := NewClient.Connected;
  NewClient.Thread     := AThread;

  AThread.Data := TObject(NewClient);

  try
    Clients.LockList.Add(NewClient);
  finally
    Clients.UnlockList;
  end;
end; (* TCPServer Connect *)

Note that before a Client can connect to the Server, the Server must be active. The Server is started (and bind to an IP/Port) in the OnCreate event for the form:

const
  DefaultServerIP = '127.0.0.1';
  DefaultServerPort = 7676;
...
procedure TMainFormServer.FormCreate(Sender: TObject);
var
  Bindings: TIdSocketHandles;
begin
  //setup and start TCPServer
  Bindings := TIdSocketHandles.Create(TCPServer);
  try
    with Bindings.Add do
    begin
      IP := DefaultServerIP;
      Port := DefaultServerPort;
    end;
    try
      TCPServer.Bindings := Bindings;
      TCPServer.Active := True;
    except on E:Exception do
      ShowMessage(E.Message);
    end;
  finally
    Bindings.Free;
  end;
  //setup TCPServer

  //other startup settings
  Clients := TThreadList.Create;
  Clients.Duplicates := dupAccept;
end; (* Form Create *)

Note: there are many more event handlers in the Server part of the project, like processing the OnClose event for the form, refreshing the Clients list box, etc...

The most important (and interesting) code is added in the OnExecute event handler method of the TIdTCPServer. OnExecute occurs when a TIdPeerThread attempts to perform the TIdPeerThread.Run method. Or, in other words, the OnExecute is called every time the Client sends commands to the Server. As you might suppose the OnExecute holds the code to retrieve the screen shot of the Client and show the image in the TImage component.

But, before we explore the OnExecute event's code, we need to take a look at the Client project:

ScreenThief CLIENT
The client part of the ScreenThief project is build around the TIdTCPClient component. The TIdTCPClient encapsulates a complete TCP (Transmission Control Protocol) client including socks support.

To successfully connect to a TCP server, using the TIdTCPClient component, we need to setup the Host and Port properties of the TIdTCPClient (the IP and the Port where the Server listens for clients). The OnCreate event for the Client form takes care of this.

procedure TMainFormClient.FormCreate(Sender: TObject);
begin
  try
    TCPClient.Host := DefaultServerIP;
    TCPClient.Port := DefaultServerPort;
    TCPClient.Connect;
  except
    on E: Exception do 
      MessageDlg('Error while connecting to 
                  ScreenThiefSERVER:'+#13+E.Message, mtError, [mbOk], 0);
  end;
end;

ScreenThief CLIENT at design time

The beauty and the beast - or how to make the Server command a Client
As explained above, we need a way of sending commands from the Server to a Client - but the problem lies in the fact that Client (TIdTCPClient) does not implement a standard "listen" event. To overcome this, I've added a TTimer component to the Client that fires its OnTimer event every 30 seconds.

The Client asks the Server "are you waiting for my screen shot", is the Server responds "yes", the Client takes the screen shot, converts it from the BMP file to JPG (since JPG images are smaller than BMP - and we want to reduce the traffic as much as possible), and sends the image as a stream to the Server.

But, how to make the Server know whether this particualar client is the one we (server) are asking for a screen shot? The answer lies in the TakeShot boolean parameter of the TClient record - an item of the (connected) Clients list on the server.
When the user of the Server application pushes the "Screen capture NOW" button, the TakeShot parameter of the selected Client (displayed in the "Connected clients" list box - and stored as an Object inside the TStringList property of the list box) is set to True. The "next time" the Client asks whther it should send its screen shot, if the TakeShot parameter is true, the Client takes its screen shot and sends it to the Server (and TakeShot for that Client is set to False).

This might sound complicated but, as you will see, works great.

Next page > Server and Client: querying and retrieving Client screen shot images > Page 1, 2, 3

Explore Delphi Programming

About.com Special Features

Build Your Own Website

Step-by-step advice on how to do everything from choosing a Web host to promoting your content. More >

Connect Your Home Computers

Easy ways to connect two computers for networking purposes. More >

Delphi Programming

  1. Home
  2. Computing & Technology
  3. Delphi Programming

©2009 About.com, a part of The New York Times Company.

All rights reserved.