Browse Source

Initial commit

coder 3 years ago
commit
60244088b5

+ 329 - 0
.gitignore

@@ -0,0 +1,329 @@
+# ---> C Sharp
+# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
+[Bb]in/
+[Oo]bj/
+
+# mstest test results
+TestResults
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Rr]elease/
+x64/
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.log
+*.vspscc
+*.vssscc
+.builds
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish
+
+# Publish Web Output
+*.Publish.xml
+
+# NuGet Packages Directory
+packages
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+[Bb]in
+[Oo]bj
+sql
+TestResults
+[Tt]est[Rr]esult*
+*.Cache
+ClientBin
+[Ss]tyle[Cc]op.*
+~$*
+*.dbmdl
+Generated_Code #added for RIA/Silverlight projects
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+
+# ---> VisualStudio
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings 
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+

+ 167 - 0
MobileDroid/ScrExtDroid/MainActivity.cs

@@ -0,0 +1,167 @@
+using Android.App;
+using Android.Graphics;
+using Android.OS;
+using Android.Views;
+using Android.Widget;
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace ScrExtDroid
+{
+    [Activity(Label = "ScrExtDroid", MainLauncher = true, Icon = "@drawable/icon")]
+    public class MainActivity : Activity
+    {
+        private FlushView _view;
+
+        private bool _isRunning;
+
+        private string _target;
+
+        //
+
+        private void AskAndConnect()
+        {
+            var cfgPath = System.IO.Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "sxd_config.txt");
+
+            EditText et = new EditText(this);
+
+            et.Text = File.Exists(cfgPath) ? File.ReadAllText(cfgPath) : "192.168.233.233:61234";
+
+            AlertDialog.Builder ad = new AlertDialog.Builder(this);
+            ad.SetTitle("Connect");
+            ad.SetView(et);
+            ad.SetPositiveButton("OK", delegate
+            {
+                _target = et.Text;
+                StartDisplay();
+            });
+            ad.SetNegativeButton("Cancel", delegate
+            {
+                Finish();
+            });
+            ad.Show();
+        }
+
+        private void StartDisplay()
+        {
+            _isRunning = true;
+            Task.Run(() =>
+            {
+                var client = new Socket(SocketType.Stream, ProtocolType.Tcp);
+                try
+                {
+                    var parts = _target.Split(':');
+                    var port = int.Parse(parts[1]);
+
+                    RunOnUiThread(delegate
+                    {
+                        Toast.MakeText(this, "Connecting...", ToastLength.Short).Show();
+                    });
+
+                    var wait = client.BeginConnect(parts[0], port, null, null);
+
+                    wait.AsyncWaitHandle.WaitOne(2000, true);  //等待2秒
+                    if (!wait.IsCompleted) throw new TimeoutException("Time out");
+
+                    RunOnUiThread(delegate
+                    {
+                        Toast.MakeText(this, "Connected", ToastLength.Short).Show();
+                    });
+
+                    var cfgPath = System.IO.Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "sxd_config.txt");
+
+                    File.WriteAllText(cfgPath, _target);
+                }
+                catch (Exception e)
+                {
+                    RunOnUiThread(delegate
+                    {
+                        Toast.MakeText(this, "Connect fail:" + e.Message, ToastLength.Short).Show();
+                    });
+
+                    RunOnUiThread(delegate { Finish(); });
+                    return;
+                }
+
+                client.Send(new byte[1]);
+                client.ReceiveTimeout = 1000;
+
+                var ns = new NetworkStream(client);
+                ns.ReadTimeout = 1000;
+
+                var r = new BinaryReader(ns);
+
+                do
+                {
+                    var l = r.ReadInt32();
+                    if (l == 0)
+                    {
+                        RunOnUiThread(() => Finish());
+                        break;
+                    }
+                    var b = r.ReadBytes(l);
+                    var ms = new MemoryStream(b);
+                    var bm = BitmapFactory.DecodeStream(ms);
+                    RunOnUiThread(() => { _view.DrawBitmap(bm); });
+                } while (_isRunning);
+
+                client.Close();
+            });
+        }
+
+        //
+
+        protected override void OnCreate(Bundle bundle)
+        {
+            base.OnCreate(bundle);
+            RequestWindowFeature(WindowFeatures.NoTitle);
+            Window.AddFlags(WindowManagerFlags.KeepScreenOn);
+
+            _view = new FlushView(this);
+            SetContentView(_view);
+        }
+
+        protected override void OnResume()
+        {
+            base.OnResume();
+            AskAndConnect();
+        }
+
+        protected override void OnPause()
+        {
+            base.OnPause();
+            _isRunning = false;
+        }
+
+        private class FlushView : View
+        {
+            private readonly MainActivity context;
+            private Bitmap _bmp;
+
+            public FlushView(MainActivity context)
+                : base(context)
+            {
+                this.context = context;
+            }
+
+            public void DrawBitmap(Bitmap bmp)
+            {
+                _bmp = bmp;
+                Invalidate();
+            }
+
+            public override void Draw(Canvas canvas)
+            {
+                var b = _bmp;
+                if (b != null)
+                {
+                    canvas.DrawBitmap(b, 0, 0, new Paint());
+                    _bmp = null;
+                    b.Dispose();
+                }
+            }
+        }
+    }
+}

+ 8 - 0
MobileDroid/ScrExtDroid/Properties/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ScrExtDroid.ScrExtDroid" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
+	<uses-sdk android:minSdkVersion="11" />
+	<uses-permission android:name="android.permission.INTERNET" />
+	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+	<application android:label="ScrExtDroid"></application>
+</manifest>

+ 30 - 0
MobileDroid/ScrExtDroid/Properties/AssemblyInfo.cs

@@ -0,0 +1,30 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Android.App;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ScrExtDroid")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ScrExtDroid")]
+[assembly: AssemblyCopyright("Copyright ©  2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 77 - 0
MobileDroid/ScrExtDroid/Resources/Resource.Designer.cs

@@ -0,0 +1,77 @@
+#pragma warning disable 1591
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+[assembly: global::Android.Runtime.ResourceDesignerAttribute("ScrExtDroid.Resource", IsApplication=true)]
+
+namespace ScrExtDroid
+{
+	
+	
+	[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
+	public partial class Resource
+	{
+		
+		static Resource()
+		{
+			global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+		}
+		
+		public static void UpdateIdValues()
+		{
+		}
+		
+		public partial class Attribute
+		{
+			
+			static Attribute()
+			{
+				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+			}
+			
+			private Attribute()
+			{
+			}
+		}
+		
+		public partial class Drawable
+		{
+			
+			// aapt resource value: 0x7f020000
+			public const int Icon = 2130837504;
+			
+			static Drawable()
+			{
+				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+			}
+			
+			private Drawable()
+			{
+			}
+		}
+		
+		public partial class String
+		{
+			
+			// aapt resource value: 0x7f030000
+			public const int ApplicationName = 2130903040;
+			
+			static String()
+			{
+				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+			}
+			
+			private String()
+			{
+			}
+		}
+	}
+}
+#pragma warning restore 1591

BIN
MobileDroid/ScrExtDroid/Resources/drawable/Icon.png


+ 4 - 0
MobileDroid/ScrExtDroid/Resources/values/Strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="ApplicationName">ScrExtDroid</string>
+</resources>

+ 89 - 0
MobileDroid/ScrExtDroid/ScrExtDroid.csproj

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}</ProjectGuid>
+    <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ScrExtDroid</RootNamespace>
+    <AssemblyName>ScrExtDroid</AssemblyName>
+    <FileAlignment>512</FileAlignment>
+    <AndroidApplication>true</AndroidApplication>
+    <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
+    <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
+    <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
+    <TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
+    <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>True</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
+    <AndroidLinkMode>None</AndroidLinkMode>
+    <EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
+    <BundleAssemblies>False</BundleAssemblies>
+    <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
+    <Debugger>Xamarin</Debugger>
+    <AotAssemblies>False</AotAssemblies>
+    <EnableLLVM>False</EnableLLVM>
+    <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
+    <EnableProguard>False</EnableProguard>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <DebugSymbols>False</DebugSymbols>
+    <AndroidManagedSymbols>true</AndroidManagedSymbols>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
+    <AndroidLinkMode>Full</AndroidLinkMode>
+    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
+    <BundleAssemblies>True</BundleAssemblies>
+    <AndroidCreatePackagePerAbi>True</AndroidCreatePackagePerAbi>
+    <Debugger>Microsoft</Debugger>
+    <AotAssemblies>False</AotAssemblies>
+    <EnableLLVM>False</EnableLLVM>
+    <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
+    <EnableProguard>False</EnableProguard>
+    <AndroidSupportedAbis>armeabi;armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Mono.Android" />
+    <Reference Include="mscorlib" />
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="MainActivity.cs" />
+    <Compile Include="Resources\Resource.Designer.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <AndroidResource Include="Resources\values\Strings.xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <AndroidResource Include="Resources\drawable\Icon.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Properties\AndroidManifest.xml" />
+  </ItemGroup>
+  <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+     Other similar extension points exist, see Microsoft.Common.targets.
+		<Target Name="BeforeBuild">
+		</Target>
+		<Target Name="AfterBuild">
+		</Target>
+ -->
+</Project>

+ 24 - 0
MobileDroid/ScreenExtenderDroid.sln

@@ -0,0 +1,24 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.23107.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrExtDroid", "ScrExtDroid\ScrExtDroid.csproj", "{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}.Release|Any CPU.Build.0 = Release|Any CPU
+		{646161A3-7C3A-47CC-BD49-5F67E6D5FC46}.Release|Any CPU.Deploy.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

+ 25 - 0
PcWin/ScreenExtender.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30804.86
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenExtender", "ScreenExtender\ScreenExtender.csproj", "{B778FF7E-15CA-467C-8161-0BB92438AF49}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{B778FF7E-15CA-467C-8161-0BB92438AF49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B778FF7E-15CA-467C-8161-0BB92438AF49}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B778FF7E-15CA-467C-8161-0BB92438AF49}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B778FF7E-15CA-467C-8161-0BB92438AF49}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {36CF335B-EF1C-4621-B38A-00810B73806B}
+	EndGlobalSection
+EndGlobal

+ 2 - 0
PcWin/ScreenExtender.sln.DotSettings

@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=mjpeg/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 12 - 0
PcWin/ScreenExtender/Models/WindowModel.cs

@@ -0,0 +1,12 @@
+using ScreenExtender.Utility.Spy;
+using System.Drawing;
+
+namespace ScreenExtender.Models
+{
+    internal class WindowModel
+    {
+        public SpiedWindow Window { get; set; }
+
+        public Point Location { get; set; }
+    }
+}

+ 36 - 0
PcWin/ScreenExtender/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("ScreenExtender")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ScreenExtender")]
+[assembly: AssemblyCopyright("Copyright ©  2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("b778ff7e-15ca-467c-8161-0bb92438af49")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 21 - 0
PcWin/ScreenExtender/Resources/ResourceFetcher.cs

@@ -0,0 +1,21 @@
+using System.IO;
+
+namespace ScreenExtender.Resources
+{
+    internal static class ResourceFetcher
+    {
+        public static byte[] IndexPage => GetStringResource("index.html");
+
+        private static byte[] GetStringResource(string name)
+        {
+#if DEBUG
+            return File.ReadAllBytes(System.AppDomain.CurrentDomain.BaseDirectory + "/../../Resources/" + name);
+#else
+            using var s = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(ResourceFetcher).Namespace + "." + name);
+            using var ms = new MemoryStream();
+            s.CopyTo(ms);
+            return ms.ToArray();
+#endif
+        }
+    }
+}

+ 47 - 0
PcWin/ScreenExtender/Resources/index.html

@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml" class="fill">
+<head>
+    <meta charset="utf-8" />
+    <title>Screen Extender</title>
+    <style>
+        .fill {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+        }
+    </style>
+</head>
+<body class="fill">
+    <a href="https://www.runoob.com/try/try.php?filename=tryhtml5_video_all">asdf</a>
+    <br />
+
+<video autoplay loop controls width="200" height="200">
+    <source type="video/3gpp" src="http://192.168.5.33/sample_640x360.3gp" >
+</video>
+
+    <br />
+
+    <canvas id="tar"></canvas>
+
+    <script>
+        var myCanvas = document.getElementById('tar');
+        var ctx = myCanvas.getContext('2d');
+        var img = new Image;
+        img.onload = function () {
+            myCanvas.width = img.width;
+            myCanvas.height = img.height;
+            ctx.drawImage(img, 0, 0); // Or at whatever offset you like
+            setTimeout(function () {
+                img.src = null;
+                img.src = "/frame?" + new Date();
+            },
+                500);
+        };
+        img.src = "/frame";
+
+
+
+    </script>
+</body>
+</html>

+ 75 - 0
PcWin/ScreenExtender/ScreenExtender.csproj

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{B778FF7E-15CA-467C-8161-0BB92438AF49}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>ScreenExtender</RootNamespace>
+    <AssemblyName>ScreenExtender</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Models\WindowModel.cs" />
+    <Compile Include="Resources\ResourceFetcher.cs" />
+    <Compile Include="ScreenExtenderMainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ScreenExtenderMainForm.Designer.cs">
+      <DependentUpon>ScreenExtenderMainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ScreenExtenderProgram.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ScreenExtenderServer.cs" />
+    <Compile Include="Utility\ExStringFormatter.cs" />
+    <Compile Include="Utility\Spy\SpyAgent.cs" />
+    <Compile Include="Utility\Spy\SpiedWindow.cs" />
+    <EmbeddedResource Include="ScreenExtenderMainForm.resx">
+      <DependentUpon>ScreenExtenderMainForm.cs</DependentUpon>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Resources\index.html" />
+    <Content Include="Utility\Spy\SOURCE.txt" />
+  </ItemGroup>
+  <ItemGroup />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 277 - 0
PcWin/ScreenExtender/ScreenExtenderMainForm.Designer.cs

@@ -0,0 +1,277 @@
+
+namespace ScreenExtender
+{
+    partial class ScreenExtenderMainForm
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            this.TitleColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.PosColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.ListenTextBox = new System.Windows.Forms.TextBox();
+            this.CaptureListView = new System.Windows.Forms.ListView();
+            this.StatusLabel = new System.Windows.Forms.Label();
+            this.PortNumericUpDown = new System.Windows.Forms.NumericUpDown();
+            this.AddButton = new System.Windows.Forms.Button();
+            this.RemoveButton = new System.Windows.Forms.Button();
+            this.MainSplitContainer = new System.Windows.Forms.SplitContainer();
+            this.ListViewManagePanel = new System.Windows.Forms.Panel();
+            this.RefreshButton = new System.Windows.Forms.Button();
+            this.PreviewPictureBox = new System.Windows.Forms.PictureBox();
+            this.HvCheckBox = new System.Windows.Forms.CheckBox();
+            this.UpdateTimer = new System.Windows.Forms.Timer(this.components);
+            this.ListenCheckButton = new System.Windows.Forms.CheckBox();
+            ((System.ComponentModel.ISupportInitialize)(this.PortNumericUpDown)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MainSplitContainer)).BeginInit();
+            this.MainSplitContainer.Panel1.SuspendLayout();
+            this.MainSplitContainer.Panel2.SuspendLayout();
+            this.MainSplitContainer.SuspendLayout();
+            this.ListViewManagePanel.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.PreviewPictureBox)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // TitleColumnHeader
+            // 
+            this.TitleColumnHeader.Text = "Title";
+            this.TitleColumnHeader.Width = 120;
+            // 
+            // PosColumnHeader
+            // 
+            this.PosColumnHeader.Text = "Pos";
+            this.PosColumnHeader.Width = 120;
+            // 
+            // ListenTextBox
+            // 
+            this.ListenTextBox.Location = new System.Drawing.Point(12, 12);
+            this.ListenTextBox.Name = "ListenTextBox";
+            this.ListenTextBox.Size = new System.Drawing.Size(100, 21);
+            this.ListenTextBox.TabIndex = 0;
+            this.ListenTextBox.Text = "000.000.000.000";
+            // 
+            // CaptureListView
+            // 
+            this.CaptureListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.TitleColumnHeader,
+            this.PosColumnHeader});
+            this.CaptureListView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.CaptureListView.FullRowSelect = true;
+            this.CaptureListView.GridLines = true;
+            this.CaptureListView.HideSelection = false;
+            this.CaptureListView.Location = new System.Drawing.Point(0, 36);
+            this.CaptureListView.Name = "CaptureListView";
+            this.CaptureListView.Size = new System.Drawing.Size(284, 611);
+            this.CaptureListView.TabIndex = 3;
+            this.CaptureListView.UseCompatibleStateImageBehavior = false;
+            this.CaptureListView.View = System.Windows.Forms.View.Details;
+            // 
+            // StatusLabel
+            // 
+            this.StatusLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.StatusLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+            this.StatusLabel.Location = new System.Drawing.Point(262, 13);
+            this.StatusLabel.Name = "StatusLabel";
+            this.StatusLabel.Size = new System.Drawing.Size(565, 20);
+            this.StatusLabel.TabIndex = 5;
+            this.StatusLabel.Text = "FooooBarrrrrrr";
+            // 
+            // PortNumericUpDown
+            // 
+            this.PortNumericUpDown.Location = new System.Drawing.Point(118, 12);
+            this.PortNumericUpDown.Maximum = new decimal(new int[] {
+            65535,
+            0,
+            0,
+            0});
+            this.PortNumericUpDown.Minimum = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            this.PortNumericUpDown.Name = "PortNumericUpDown";
+            this.PortNumericUpDown.Size = new System.Drawing.Size(54, 21);
+            this.PortNumericUpDown.TabIndex = 1;
+            this.PortNumericUpDown.Value = new decimal(new int[] {
+            61234,
+            0,
+            0,
+            0});
+            // 
+            // AddButton
+            // 
+            this.AddButton.Location = new System.Drawing.Point(3, 3);
+            this.AddButton.Name = "AddButton";
+            this.AddButton.Size = new System.Drawing.Size(75, 23);
+            this.AddButton.TabIndex = 2;
+            this.AddButton.Text = "Add";
+            this.AddButton.UseVisualStyleBackColor = true;
+            this.AddButton.Click += new System.EventHandler(this.AddButton_Click);
+            // 
+            // RemoveButton
+            // 
+            this.RemoveButton.Location = new System.Drawing.Point(84, 3);
+            this.RemoveButton.Name = "RemoveButton";
+            this.RemoveButton.Size = new System.Drawing.Size(75, 23);
+            this.RemoveButton.TabIndex = 2;
+            this.RemoveButton.Text = "Remove";
+            this.RemoveButton.UseVisualStyleBackColor = true;
+            this.RemoveButton.Click += new System.EventHandler(this.RemoveButton_Click);
+            // 
+            // MainSplitContainer
+            // 
+            this.MainSplitContainer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.MainSplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
+            this.MainSplitContainer.Location = new System.Drawing.Point(12, 41);
+            this.MainSplitContainer.Name = "MainSplitContainer";
+            // 
+            // MainSplitContainer.Panel1
+            // 
+            this.MainSplitContainer.Panel1.Controls.Add(this.CaptureListView);
+            this.MainSplitContainer.Panel1.Controls.Add(this.ListViewManagePanel);
+            // 
+            // MainSplitContainer.Panel2
+            // 
+            this.MainSplitContainer.Panel2.Controls.Add(this.PreviewPictureBox);
+            this.MainSplitContainer.Size = new System.Drawing.Size(854, 647);
+            this.MainSplitContainer.SplitterDistance = 284;
+            this.MainSplitContainer.TabIndex = 7;
+            // 
+            // ListViewManagePanel
+            // 
+            this.ListViewManagePanel.Controls.Add(this.RefreshButton);
+            this.ListViewManagePanel.Controls.Add(this.RemoveButton);
+            this.ListViewManagePanel.Controls.Add(this.AddButton);
+            this.ListViewManagePanel.Dock = System.Windows.Forms.DockStyle.Top;
+            this.ListViewManagePanel.Location = new System.Drawing.Point(0, 0);
+            this.ListViewManagePanel.Name = "ListViewManagePanel";
+            this.ListViewManagePanel.Size = new System.Drawing.Size(284, 36);
+            this.ListViewManagePanel.TabIndex = 4;
+            // 
+            // RefreshButton
+            // 
+            this.RefreshButton.Location = new System.Drawing.Point(169, 3);
+            this.RefreshButton.Name = "RefreshButton";
+            this.RefreshButton.Size = new System.Drawing.Size(75, 23);
+            this.RefreshButton.TabIndex = 2;
+            this.RefreshButton.Text = "Refresh";
+            this.RefreshButton.UseVisualStyleBackColor = true;
+            this.RefreshButton.Click += new System.EventHandler(this.RefreshButton_Click);
+            // 
+            // PreviewPictureBox
+            // 
+            this.PreviewPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+            this.PreviewPictureBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.PreviewPictureBox.Location = new System.Drawing.Point(0, 0);
+            this.PreviewPictureBox.Name = "PreviewPictureBox";
+            this.PreviewPictureBox.Size = new System.Drawing.Size(566, 647);
+            this.PreviewPictureBox.TabIndex = 0;
+            this.PreviewPictureBox.TabStop = false;
+            this.PreviewPictureBox.Paint += new System.Windows.Forms.PaintEventHandler(this.PreviewPictureBox_Paint);
+            this.PreviewPictureBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.PreviewPictureBox_MouseDown);
+            this.PreviewPictureBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.PreviewPictureBox_MouseMove);
+            this.PreviewPictureBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.PreviewPictureBox_MouseUp);
+            this.PreviewPictureBox.Resize += new System.EventHandler(this.PreviewPictureBox_Resize);
+            // 
+            // HvCheckBox
+            // 
+            this.HvCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.HvCheckBox.Appearance = System.Windows.Forms.Appearance.Button;
+            this.HvCheckBox.AutoSize = true;
+            this.HvCheckBox.Location = new System.Drawing.Point(833, 13);
+            this.HvCheckBox.Name = "HvCheckBox";
+            this.HvCheckBox.Size = new System.Drawing.Size(33, 22);
+            this.HvCheckBox.TabIndex = 6;
+            this.HvCheckBox.Text = "H/V";
+            this.HvCheckBox.UseVisualStyleBackColor = true;
+            this.HvCheckBox.CheckedChanged += new System.EventHandler(this.HvCheckBox_CheckedChanged);
+            // 
+            // UpdateTimer
+            // 
+            this.UpdateTimer.Enabled = true;
+            this.UpdateTimer.Interval = 125;
+            this.UpdateTimer.Tick += new System.EventHandler(this.UpdateTimer_Tick);
+            // 
+            // ListenCheckButton
+            // 
+            this.ListenCheckButton.Appearance = System.Windows.Forms.Appearance.Button;
+            this.ListenCheckButton.Location = new System.Drawing.Point(178, 12);
+            this.ListenCheckButton.Name = "ListenCheckButton";
+            this.ListenCheckButton.Size = new System.Drawing.Size(78, 21);
+            this.ListenCheckButton.TabIndex = 5;
+            this.ListenCheckButton.Text = "Listen";
+            this.ListenCheckButton.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+            this.ListenCheckButton.UseVisualStyleBackColor = true;
+            this.ListenCheckButton.CheckedChanged += new System.EventHandler(this.ListenCheckButton_CheckedChanged);
+            // 
+            // ScreenExtenderMainForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(878, 700);
+            this.Controls.Add(this.ListenCheckButton);
+            this.Controls.Add(this.MainSplitContainer);
+            this.Controls.Add(this.HvCheckBox);
+            this.Controls.Add(this.StatusLabel);
+            this.Controls.Add(this.PortNumericUpDown);
+            this.Controls.Add(this.ListenTextBox);
+            this.Name = "ScreenExtenderMainForm";
+            this.Text = "Screen Extender";
+            this.Shown += new System.EventHandler(this.ScreenExtenderMainForm_Shown);
+            ((System.ComponentModel.ISupportInitialize)(this.PortNumericUpDown)).EndInit();
+            this.MainSplitContainer.Panel1.ResumeLayout(false);
+            this.MainSplitContainer.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.MainSplitContainer)).EndInit();
+            this.MainSplitContainer.ResumeLayout(false);
+            this.ListViewManagePanel.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.PreviewPictureBox)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.TextBox ListenTextBox;
+        private System.Windows.Forms.ListView CaptureListView;
+        private System.Windows.Forms.Label StatusLabel;
+        private System.Windows.Forms.NumericUpDown PortNumericUpDown;
+        private System.Windows.Forms.Button AddButton;
+        private System.Windows.Forms.Button RemoveButton;
+        private System.Windows.Forms.SplitContainer MainSplitContainer;
+        private System.Windows.Forms.Panel ListViewManagePanel;
+        private System.Windows.Forms.CheckBox HvCheckBox;
+        private System.Windows.Forms.Button RefreshButton;
+        private System.Windows.Forms.Timer UpdateTimer;
+        private System.Windows.Forms.ColumnHeader TitleColumnHeader;
+        private System.Windows.Forms.ColumnHeader PosColumnHeader;
+        private System.Windows.Forms.PictureBox PreviewPictureBox;
+        private System.Windows.Forms.CheckBox ListenCheckButton;
+    }
+}
+

+ 191 - 0
PcWin/ScreenExtender/ScreenExtenderMainForm.cs

@@ -0,0 +1,191 @@
+using ScreenExtender.Models;
+using ScreenExtender.Utility;
+using ScreenExtender.Utility.Spy;
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Windows.Forms;
+
+namespace ScreenExtender
+{
+    public partial class ScreenExtenderMainForm : Form
+    {
+        public byte[] FrameBytes;
+        private Bitmap _frameBitmap;
+
+        private Point _origPoint;
+        private Point? _mouseDownPoint;
+
+
+        private ScreenExtenderServer _server;
+
+        public ScreenExtenderMainForm()
+        {
+            InitializeComponent();
+        }
+
+        /////////////////////////////////////////////
+
+        private void UpdateStatus()
+        {
+            var vw = PreviewPictureBox.ClientSize;
+            StatusLabel.Text = $"ViewSize:{vw.Width} x {vw.Height}";
+            //TODO: Long Connection Count
+        }
+
+        private void UpdateListView()
+        {
+            foreach (ListViewItem item in CaptureListView.Items)
+            {
+                UpdateListViewItem(item);
+            }
+        }
+
+        private void UpdateListViewItem(ListViewItem lvi)
+        {
+            var model = (WindowModel)lvi.Tag;
+            lvi.Text = model.Window.Caption;
+            if (lvi.SubItems.Count == 1) lvi.SubItems.Add(model.Location.ToCsvString());
+            else lvi.SubItems[1].Text = model.Location.ToCsvString();
+        }
+
+        private void UpdateFrame()
+        {
+            var bmp = _frameBitmap;
+            if (bmp == null) return;
+
+            using var graphics = Graphics.FromImage(bmp);
+            graphics.Clear(Color.Black);
+
+            var models = CaptureListView.Items.Cast<ListViewItem>().Select(p => (WindowModel)p.Tag).ToArray();
+
+            foreach (var item in models)
+            {
+                item.Window.PrintTo(graphics, item.Location);
+            }
+
+            graphics.Dispose();
+
+            using var ms = new MemoryStream();
+            bmp.Save(ms, ImageFormat.Png);
+            FrameBytes = ms.ToArray();
+
+            PreviewPictureBox.Invalidate();
+        }
+
+        ///////////////////////////////////////////////////////////
+
+        private void ScreenExtenderMainForm_Shown(object sender, EventArgs e)
+        {
+            PreviewPictureBox_Resize(null, null);
+            _server = new ScreenExtenderServer(this);
+        }
+
+
+        private void ListenCheckButton_CheckedChanged(object sender, EventArgs e)
+        {
+            if (ListenCheckButton.Checked) _server.Start(IPAddress.Parse(ListenTextBox.Text), (int)PortNumericUpDown.Value);
+            else _server.Stop();
+        }
+
+        private void HvCheckBox_CheckedChanged(object sender, EventArgs e)
+        {
+            MainSplitContainer.Orientation = HvCheckBox.Checked
+                    ? Orientation.Horizontal
+                    : Orientation.Vertical
+                ;
+        }
+
+        private void AddButton_Click(object sender, EventArgs e)
+        {
+            new SpyAgent(f =>
+            {
+                var model = new WindowModel
+                {
+                    Window = f
+                };
+                var lvi = new ListViewItem { Tag = model };
+                CaptureListView.Items.Add(lvi);
+                CaptureListView.SelectedIndices.Clear();
+                UpdateListViewItem(lvi);
+                UpdateListView();
+                lvi.Selected = true;
+            });
+        }
+
+        private void RemoveButton_Click(object sender, EventArgs e)
+        {
+            var selectedItem = CaptureListView.SelectedItems.Cast<ListViewItem>().FirstOrDefault();
+            selectedItem?.Remove();
+        }
+
+        private void RefreshButton_Click(object sender, EventArgs e)
+        {
+            UpdateListView();
+        }
+
+        private void UpdateTimer_Tick(object sender, EventArgs e)
+        {
+            UpdateFrame();
+        }
+
+        private void PreviewPictureBox_Resize(object sender, EventArgs e)
+        {
+            var old = _frameBitmap;
+            var sz = PreviewPictureBox.ClientSize;
+            _frameBitmap = new Bitmap(sz.Width, sz.Height, PixelFormat.Format32bppRgb);
+            old?.Dispose();
+
+            UpdateStatus();
+        }
+
+        private void PreviewPictureBox_Paint(object sender, PaintEventArgs e)
+        {
+            var bmp = _frameBitmap;
+            if (bmp != null) e.Graphics.DrawImage(bmp, Point.Empty);
+        }
+
+        private void PreviewPictureBox_MouseDown(object sender, MouseEventArgs e)
+        {
+            var selectedModel = (WindowModel)CaptureListView.SelectedItems.Cast<ListViewItem>().FirstOrDefault()?.Tag;
+            if (selectedModel == null) return;
+
+            _origPoint = selectedModel.Location;
+            _mouseDownPoint = MousePosition;
+        }
+
+        private void PreviewPictureBox_MouseMove(object sender, MouseEventArgs e)
+        {
+            if (_mouseDownPoint.HasValue)
+            {
+                var selectedItem = CaptureListView.SelectedItems.Cast<ListViewItem>().FirstOrDefault();
+                if (selectedItem == null) return;
+                var model = (WindowModel)selectedItem.Tag;
+
+                var mdp = _mouseDownPoint.Value;
+                var mmp = MousePosition;
+
+                var mx = mmp.X - mdp.X;
+                var my = mmp.Y - mdp.Y;
+
+                var fx = _origPoint.X + mx;
+                var fy = _origPoint.Y + my;
+
+                model.Location = new Point(fx, fy);
+
+                UpdateListViewItem(selectedItem);
+                UpdateFrame();
+            }
+        }
+
+        private void PreviewPictureBox_MouseUp(object sender, MouseEventArgs e)
+        {
+            _mouseDownPoint = null;
+        }
+
+
+    }
+}

+ 123 - 0
PcWin/ScreenExtender/ScreenExtenderMainForm.resx

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="UpdateTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>

+ 22 - 0
PcWin/ScreenExtender/ScreenExtenderProgram.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace ScreenExtender
+{
+    static class ScreenExtenderProgram
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new ScreenExtenderMainForm());
+        }
+    }
+}

+ 166 - 0
PcWin/ScreenExtender/ScreenExtenderServer.cs

@@ -0,0 +1,166 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using ScreenExtender.Resources;
+
+namespace ScreenExtender
+{
+    internal class ScreenExtenderServer
+    {
+        private readonly ScreenExtenderMainForm _mainForm;
+
+        private Socket _server;
+        private bool _isRunning;
+
+        public ScreenExtenderServer(ScreenExtenderMainForm mainForm)
+        {
+            _mainForm = mainForm;
+        }
+
+        public void Start(IPAddress listen, int port)
+        {
+            _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            _server.Bind(new IPEndPoint(listen, port));
+            _server.Listen(1);
+
+            _isRunning = true;
+            Task.Run(ServerProc);
+        }
+
+        public void Stop()
+        {
+            _isRunning = false;
+            _server?.Close();
+            _server?.Dispose();
+        }
+
+        private void ServerProc()
+        {
+            while (_isRunning)
+            {
+                var client = _server.Accept();
+                Task.Run(() => RequestProc(client));
+            }
+        }
+
+        private void RequestProc(Socket client)
+        {
+            string req = "";
+            {
+                var buf = new byte[1024];
+                var len = client.Receive(buf, 0, buf.Length, SocketFlags.None);
+                //detect request
+
+                var text = Encoding.ASCII.GetString(buf, 0, len);
+                var lines = text.Split('\r', '\n');
+                if (lines.Length > 0 && lines[0].StartsWith("GET"))
+                {
+                    var parts = lines[0].Split(new[] { ' ' }, 3);
+                    if (parts.Length > 1) req = parts[1];
+                }
+            }
+
+            var stream = new NetworkStream(client, true);
+
+            if (req == "")
+            {
+                var ns = new NetworkStream(client);
+                var w = new BinaryWriter(ns);
+                while (_isRunning)
+                {
+                    Thread.Sleep(100);
+                    var rBuf = _mainForm.FrameBytes;
+                    if (rBuf == null)  continue;
+
+                    try
+                    {
+                        w.Write(rBuf.Length);
+                        w.Write(rBuf);
+                    }
+                    catch (IOException)
+                    {
+                        break;
+                    }
+
+                }
+                client.Close();
+            }
+
+            var path = req.Split('?')[0];
+
+            switch (path)
+            {
+                case "/":
+                    {
+                        var rBuf = ResourceFetcher.IndexPage;
+                        var writer = new BinaryWriter(stream);
+                        writer.Write(Encoding.ASCII.GetBytes("HTTP / 1.1 200 OK\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("Content-Type: text/html\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("Content-Length: " + rBuf.Length + "\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("\r\n"));
+                        writer.Write(rBuf);
+                        writer.Close();
+                        client.Close();
+                    }
+
+                    break;
+                case "/mjpeg":
+                    {
+                        var writer = new BinaryWriter(stream);
+                        writer.Write(Encoding.ASCII.GetBytes("HTTP / 1.1 200 OK\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("Content-Type: multipart/x-mixed-replace; boundary=frame\r\n"));
+
+                        while (_isRunning)
+                        {
+                            var rBuf = _mainForm.FrameBytes;
+                            if (rBuf == null)
+                            {
+                                Thread.Sleep(100);
+                                continue;
+                            }
+
+                            writer.Write(Encoding.ASCII.GetBytes("\r\n"));
+                            writer.Write(Encoding.ASCII.GetBytes("--frame\r\n"));
+
+                            writer.Write(Encoding.ASCII.GetBytes("Content-Type: image/png\r\n"));
+                            writer.Write(Encoding.ASCII.GetBytes("Content-Length: " + rBuf.Length + "\r\n"));
+                            writer.Write(Encoding.ASCII.GetBytes("\r\n"));
+                            writer.Write(rBuf);
+                        }
+                        client.Shutdown(SocketShutdown.Both);
+                        client.Close();
+                        client.Dispose();
+                    }
+                    break;
+
+                case "/frame": //Single Picture Download,close
+                    {
+                        byte[] rBuf;
+                        do
+                        {
+                            rBuf = _mainForm.FrameBytes;
+                            if (rBuf != null) break;
+                            Thread.Sleep(100);
+                        } while (true);
+
+                        var writer = new BinaryWriter(stream);
+                        writer.Write(Encoding.ASCII.GetBytes("HTTP / 1.1 200 OK\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("Content-Type: image/png\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("Content-Length: " + rBuf.Length + "\r\n"));
+                        writer.Write(Encoding.ASCII.GetBytes("\r\n"));
+                        writer.Write(rBuf);
+                        writer.Close();
+                        client.Close();
+                    }
+                    break;
+                default:
+                    client.Close();
+                    break;
+            }
+        }
+    }
+}

+ 14 - 0
PcWin/ScreenExtender/Utility/ExStringFormatter.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScreenExtender.Utility
+{
+    internal static class ExStringFormatter
+    {
+        public static string ToCsvString(this Point point) => $"{point.X},{point.Y}";
+    }
+}

+ 1 - 0
PcWin/ScreenExtender/Utility/Spy/SOURCE.txt

@@ -0,0 +1 @@
+SOURCE FROM: https://gist.github.com/r1pper/5560944 With Modified

+ 277 - 0
PcWin/ScreenExtender/Utility/Spy/SpiedWindow.cs

@@ -0,0 +1,277 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace ScreenExtender.Utility.Spy
+{
+    public class SpiedWindow : EventArgs
+    {
+        public SpiedWindow(IntPtr handle)
+        {
+            Handle = handle;
+        }
+
+        public IntPtr Handle { get; }
+
+        public Rectangle Area
+        {
+            get
+            {
+                RECT rect;
+                GetWindowRect(Handle, out rect);
+                return rect;
+            }
+        }
+
+        public string Caption
+        {
+            get
+            {
+                var len = GetWindowTextLength(Handle);
+                var str = new StringBuilder(len + 1);
+                GetWindowText(Handle, str, len + 1);
+                return str.ToString();
+            }
+        }
+
+        public SpiedWindow GetParentWindow()
+        {
+            return new SpiedWindow(GetParent(Handle));
+        }
+
+        public void SeParentWindow(IntPtr parentHandle)
+        {
+            SetParent(Handle, parentHandle);
+        }
+
+        public IEnumerable<SpiedWindow> GetChildren()
+        {
+            var children = new List<SpiedWindow>();
+            EnumChildWindows(Handle, (hWnd, lp) =>
+            {
+                children.Add(new SpiedWindow(hWnd));
+                return true;
+            }, IntPtr.Zero);
+            return children;
+        }
+
+        public override string ToString()
+        {
+            return Caption;
+        }
+
+        public void PrintTo(Graphics graphics, Point itemLocation)
+        {
+            using var bmp = GetScreenShot();
+            if(bmp!=null) graphics.DrawImage(bmp, itemLocation);
+        }
+
+
+        private Bitmap GetScreenShot()
+        {
+            var sz = Area.Size;
+            if (sz == Size.Empty) return null;
+            var bmp = new Bitmap(sz.Width, sz.Height, PixelFormat.Format32bppArgb);
+            using var g = Graphics.FromImage(bmp);
+            var hdc = g.GetHdc();
+            PrintWindow(Handle, hdc, PW_RENDERFULLCONTENT);
+            g.ReleaseHdc(hdc);
+            return bmp;
+        }
+
+        #region Under the Hood
+
+        [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
+        private static extern IntPtr GetParent(IntPtr hWnd);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
+
+        [DllImport("user32.dll")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
+
+        [DllImport("user32.dll")]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        private static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
+
+        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        private static extern int GetWindowTextLength(IntPtr hWnd);
+
+        private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct RECT
+        {
+            public int Left, Top, Right, Bottom;
+
+            public RECT(int left, int top, int right, int bottom)
+            {
+                Left = left;
+                Top = top;
+                Right = right;
+                Bottom = bottom;
+            }
+
+            public RECT(Rectangle r)
+                : this(r.Left, r.Top, r.Right, r.Bottom)
+            {
+            }
+
+            public int X
+            {
+                get { return Left; }
+                set
+                {
+                    Right -= (Left - value);
+                    Left = value;
+                }
+            }
+
+            public int Y
+            {
+                get { return Top; }
+                set
+                {
+                    Bottom -= (Top - value);
+                    Top = value;
+                }
+            }
+
+            public int Height
+            {
+                get { return Bottom - Top; }
+                set { Bottom = value + Top; }
+            }
+
+            public int Width
+            {
+                get { return Right - Left; }
+                set { Right = value + Left; }
+            }
+
+            public Point Location
+            {
+                get { return new Point(Left, Top); }
+                set
+                {
+                    X = value.X;
+                    Y = value.Y;
+                }
+            }
+
+            public Size Size
+            {
+                get { return new Size(Width, Height); }
+                set
+                {
+                    Width = value.Width;
+                    Height = value.Height;
+                }
+            }
+
+            public static implicit operator Rectangle(RECT r)
+            {
+                return new Rectangle(r.Left, r.Top, r.Width, r.Height);
+            }
+
+            public static implicit operator RECT(Rectangle r)
+            {
+                return new RECT(r);
+            }
+
+            public static bool operator ==(RECT r1, RECT r2)
+            {
+                return r1.Equals(r2);
+            }
+
+            public static bool operator !=(RECT r1, RECT r2)
+            {
+                return !r1.Equals(r2);
+            }
+
+            public bool Equals(RECT r)
+            {
+                return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
+            }
+
+            public override bool Equals(object obj)
+            {
+                if (obj is RECT)
+                    return Equals((RECT)obj);
+                if (obj is Rectangle)
+                    return Equals(new RECT((Rectangle)obj));
+                return false;
+            }
+
+            public override int GetHashCode()
+            {
+                return ((Rectangle)this).GetHashCode();
+            }
+
+            public override string ToString()
+            {
+                return string.Format(CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top,
+                                     Right, Bottom);
+            }
+        }
+
+
+        private static uint PW_RENDERFULLCONTENT = 2;
+
+        [DllImport("user32.dll", SetLastError = true)]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        private static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
+
+        [DllImport("gdi32.dll")]
+        private static extern bool MoveToEx(IntPtr hdc, int X, int Y, IntPtr lpPoint);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern IntPtr GetDC(IntPtr hWnd);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern IntPtr GetWindowDC(IntPtr hWnd);
+
+        [DllImport("user32.dll")]
+        private static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
+
+        [DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        private static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
+
+        public enum TernaryRasterOperations : uint
+        {
+            SRCCOPY = 0x00CC0020,
+            SRCPAINT = 0x00EE0086,
+            SRCAND = 0x008800C6,
+            SRCINVERT = 0x00660046,
+            SRCERASE = 0x00440328,
+            NOTSRCCOPY = 0x00330008,
+            NOTSRCERASE = 0x001100A6,
+            MERGECOPY = 0x00C000CA,
+            MERGEPAINT = 0x00BB0226,
+            PATCOPY = 0x00F00021,
+            PATPAINT = 0x00FB0A09,
+            PATINVERT = 0x005A0049,
+            DSTINVERT = 0x00550009,
+            BLACKNESS = 0x00000042,
+            WHITENESS = 0x00FF0062,
+            CAPTUREBLT = 0x40000000 //only if WinVer >= 5.0.0 (see wingdi.h)
+        }
+
+
+        private const int WM_PRINT = 0x0317;
+
+        [DllImport("user32.dll")]
+        public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
+
+        #endregion Under the Hood
+    }
+}

+ 142 - 0
PcWin/ScreenExtender/Utility/Spy/SpyAgent.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace ScreenExtender.Utility.Spy
+{
+    public class SpyAgent
+    {
+        private readonly Action<SpiedWindow> _selectedAction;
+        private readonly Timer _timer;
+        private SpyAgentLocator _locator;
+
+        public SpyAgent(Action<SpiedWindow> selectedAction)
+        {
+            _selectedAction = selectedAction;
+            _timer = new Timer { Interval = 25, Enabled = false };
+            _timer.Tick += OnTimerTicked;
+            BeginSpying();
+        }
+
+        private void OnTimerTicked(object sender, EventArgs e)
+        {
+            ShowLocator();
+            if (!Control.MouseButtons.HasFlag(MouseButtons.Left)) return;
+            EndSpying();
+            _selectedAction(GetHoveredWindow());
+        }
+
+        private void ShowLocator()
+        {
+            var window = GetHoveredWindow();
+
+            if (window.Handle == IntPtr.Zero)
+            {
+                _locator.Hide();
+                return;
+            }
+
+            _locator.Location = window.Area.Location;
+            _locator.Size = window.Area.Size;
+            _locator.TopLevel = true;
+            _locator.TopMost = true;
+            _locator.Show();
+        }
+
+        public void BeginSpying()
+        {
+            if (_locator != null)
+            {
+                _locator.Close();
+                _locator.Dispose();
+            }
+
+            _locator = new SpyAgentLocator();
+            MakePassThrough(_locator.Handle);
+
+            _timer.Enabled = true;
+        }
+
+        public void EndSpying()
+        {
+            _timer.Enabled = false;
+
+            _locator?.Close();
+            _locator?.Dispose();
+            _locator = null;
+        }
+
+        private class SpyAgentLocator : Form
+        {
+            public SpyAgentLocator()
+            {
+                FormBorderStyle = FormBorderStyle.None;
+                BackColor = Color.OrangeRed;
+                Opacity = 0.25;
+                TopLevel = true;
+                TopMost = true;
+            }
+        }
+
+        #region Under the Hood
+
+        private const int GWL_EXSTYLE = -20;
+        private const int WS_EX_TRANSPARENT = 0x20;
+
+        [DllImport("user32.dll")]
+        private static extern IntPtr WindowFromPoint(POINT point);
+
+        [DllImport("user32.dll")]
+        private static extern IntPtr ChildWindowFromPoint(IntPtr hWndParent, POINT point);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
+
+        public static SpiedWindow GetHoveredWindow()
+        {
+            var handle = WindowFromPoint(Cursor.Position);
+            return new SpiedWindow(handle);
+        }
+
+        private static void MakePassThrough(IntPtr handle)
+        {
+            var exstyle = GetWindowLong(handle, GWL_EXSTYLE);
+            exstyle |= WS_EX_TRANSPARENT;
+            SetWindowLong(handle, GWL_EXSTYLE, exstyle);
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct POINT
+        {
+            public readonly int X;
+            public readonly int Y;
+
+            public POINT(int x, int y)
+            {
+                X = x;
+                Y = y;
+            }
+
+            public POINT(Point pt)
+                : this(pt.X, pt.Y)
+            {
+            }
+
+            public static implicit operator Point(POINT p)
+            {
+                return new Point(p.X, p.Y);
+            }
+
+            public static implicit operator POINT(Point p)
+            {
+                return new POINT(p.X, p.Y);
+            }
+        }
+
+        #endregion Under the Hood
+    }
+}

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# ScreenExtender
+
+Full C# Implemented Screen Extender.
+
+Use pad or phone as second screen to display background window