diff --git a/Sources/Video/Video.csproj b/Sources/Video/Video.csproj
index 360d6de2..06a2c18a 100644
--- a/Sources/Video/Video.csproj
+++ b/Sources/Video/Video.csproj
@@ -72,6 +72,7 @@
+
diff --git a/Sources/Video/WindowCaptureStream.cs b/Sources/Video/WindowCaptureStream.cs
new file mode 100644
index 00000000..afac3efc
--- /dev/null
+++ b/Sources/Video/WindowCaptureStream.cs
@@ -0,0 +1,340 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace AForge.Video
+{
+ ///
+ /// Window capture video source.
+ ///
+ ///
+ /// The video source constantly captures the target window.
+ ///
+ /// Sample usage:
+ ///
+ /// // set NewFrame event handler
+ /// stream.NewFrame += new NewFrameEventHandler( video_NewFrame );
+ ///
+ /// // start the video source
+ /// stream.Start( );
+ ///
+ /// // ...
+ /// // signal to stop
+ /// stream.SignalToStop( );
+ /// // ...
+ ///
+ /// private void video_NewFrame( object sender, NewFrameEventArgs eventArgs )
+ /// {
+ /// // get new frame
+ /// Bitmap bitmap = eventArgs.Frame;
+ /// // process the frame
+ /// }
+ ///
+ ///
+ ///
+ public class WindowCaptureStream : IVideoSource
+ {
+ private IntPtr hwnd;
+ private Rectangle region;
+
+ // frame interval in milliseconds
+ private int frameInterval = 100;
+
+ private Thread thread = null;
+ private ManualResetEvent stopEvent = null;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Window to capture.
+ ///
+ public WindowCaptureStream(IntPtr hWnd)
+ {
+ hwnd = hWnd;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Window to capture.
+ /// Window's rectangle to capture
+ ///
+ public WindowCaptureStream(IntPtr hWnd, Rectangle region) : this(hWnd)
+ {
+ this.region = region;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Window to capture.
+ /// Window's rectangle to capture
+ /// Time interval between making screen shots, ms.
+ ///
+ public WindowCaptureStream(IntPtr hWnd, Rectangle region, int frameInterval) : this(hWnd, region)
+ {
+ this.frameInterval = frameInterval;
+ }
+
+ ///
+ /// Received bytes count.
+ ///
+ ///
+ /// The property is not implemented for this video source and always returns 0.
+ ///
+ public long BytesReceived { get { return 0; } }
+
+ private int framesReceived;
+ ///
+ /// Received frames count.
+ ///
+ ///
+ /// Number of frames the video source provided from the moment of the last
+ /// access to the property.
+ ///
+ ///
+ public int FramesReceived
+ {
+ get
+ {
+ int frames = framesReceived;
+ framesReceived = 0;
+ return frames;
+ }
+ }
+
+ ///
+ /// State of the video source.
+ ///
+ ///
+ /// Current state of video source object - running or not.
+ ///
+ public bool IsRunning
+ {
+ get
+ {
+ if (thread != null)
+ {
+ // check thread status
+ if (thread.Join(0) == false)
+ return true;
+
+ // the thread is not running, free resources
+ Free();
+ }
+ return false;
+ }
+ }
+
+ ///
+ /// Video source.
+ ///
+ ///
+ public string Source { get { return "Window Capture"; } }
+
+ ///
+ /// New frame event.
+ ///
+ ///
+ /// Notifies clients about new available frame from video source.
+ ///
+ /// Since video source may have multiple clients, each client is responsible for
+ /// making a copy (cloning) of the passed video frame, because the video source disposes its
+ /// own original copy after notifying of clients.
+ ///
+ ///
+ public event NewFrameEventHandler NewFrame;
+
+ ///
+ /// Video playing finished event.
+ ///
+ ///
+ /// This event is used to notify clients that the video playing has finished.
+ ///
+ ///
+ public event PlayingFinishedEventHandler PlayingFinished;
+
+ ///
+ /// Video source error event.
+ ///
+ ///
+ /// This event is used to notify clients about any type of errors occurred in
+ /// video source object, for example internal exceptions.
+ ///
+ public event VideoSourceErrorEventHandler VideoSourceError;
+
+ ///
+ /// Signal video source to stop its work.
+ ///
+ ///
+ /// Signals video source to stop its background thread, stop to
+ /// provide new frames and free resources.
+ ///
+ public void SignalToStop()
+ {
+ // stop thread
+ if (thread != null)
+ {
+ // signal to stop
+ stopEvent.Set();
+ }
+ }
+
+ ///
+ /// Start video source.
+ ///
+ ///
+ /// Starts video source and return execution to caller. Video source
+ /// object creates background thread and notifies about new frames with the
+ /// help of event.
+ ///
+ /// Video source is not specified.
+ ///
+ public void Start()
+ {
+ if (!IsRunning)
+ {
+ framesReceived = 0;
+
+ // create events
+ stopEvent = new ManualResetEvent(false);
+
+ // create and start new thread
+ thread = new Thread(new ThreadStart(WorkerThread));
+ thread.Name = Source; // mainly for debugging
+ thread.Start();
+ }
+ }
+
+ ///
+ /// Stop video source.
+ ///
+ ///
+ /// Stops video source aborting its thread.
+ ///
+ /// Since the method aborts background thread, its usage is highly not preferred
+ /// and should be done only if there are no other options. The correct way of stopping camera
+ /// is signaling it stop and then
+ /// waiting for background thread's completion.
+ ///
+ ///
+ public void Stop()
+ {
+ if (this.IsRunning)
+ {
+ //stopEvent.Set();
+ //thread.Abort();
+ SignalToStop();
+ WaitForStop();
+ }
+ }
+
+ ///
+ /// Wait for video source has stopped.
+ ///
+ ///
+ /// Waits for source stopping after it was signalled to stop using
+ /// method.
+ ///
+ public void WaitForStop()
+ {
+ if (thread != null)
+ {
+ // wait for thread stop
+ thread.Join();
+
+ Free();
+ }
+ }
+
+ ///
+ /// Free resource.
+ ///
+ ///
+ private void Free()
+ {
+ thread = null;
+
+ // release events
+ stopEvent.Close();
+ stopEvent = null;
+ }
+
+ private void WorkerThread()
+ {
+ int width = region.Width;
+ int height = region.Height;
+ int x = region.Location.X;
+ int y = region.Location.Y;
+
+ Bitmap screenshot = new Bitmap(width, height);
+
+ var fromHwnd = Graphics.FromHwnd(hwnd);
+ var fromImage = Graphics.FromImage(screenshot);
+
+ // download start time and duration
+ DateTime start;
+ TimeSpan span;
+
+ while (!stopEvent.WaitOne(0, false))
+ {
+ // set dowbload start time
+ start = DateTime.Now;
+
+ try
+ {
+ IntPtr hdc_screen = fromHwnd.GetHdc();
+ IntPtr hdc_screenshot = fromImage.GetHdc();
+
+ BitBlt(hdc_screenshot, 0, 0, width, height, hdc_screen, x, y, (int)CopyPixelOperation.SourceCopy);
+
+ fromImage.ReleaseHdc(hdc_screenshot);
+ fromHwnd.ReleaseHdc(hdc_screen);
+
+ NewFrame?.Invoke(this, new NewFrameEventArgs(screenshot));
+
+ // wait for a while ?
+ if (frameInterval > 0)
+ {
+ // get download duration
+ span = DateTime.Now.Subtract(start);
+
+ // miliseconds to sleep
+ int msec = frameInterval - (int)span.TotalMilliseconds;
+
+ if ((msec > 0) && (stopEvent.WaitOne(msec, false)))
+ break;
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ break;
+ }
+ catch (Exception exception)
+ {
+ // provide information to clients
+ VideoSourceError?.Invoke(this, new VideoSourceErrorEventArgs(exception.Message));
+ // wait for a while before the next try
+ Thread.Sleep(250);
+ }
+
+ // need to stop ?
+ if (stopEvent.WaitOne(0, false))
+ break;
+ }
+
+ screenshot.Dispose();
+ fromHwnd.Dispose();
+ fromImage.Dispose();
+
+ PlayingFinished?.Invoke(this, ReasonToFinishPlaying.StoppedByUser);
+ }
+
+ [DllImport("gdi32.dll")]
+ static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop);
+ }
+}