From 21e670ca661d02bf68cac090550c5208211a08d9 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Sun, 22 Dec 2019 22:05:36 -0800 Subject: [PATCH] Convert ScreenCast.Win to .NET Core. --- Desktop.Linux/Desktop.Linux.csproj | 2 - .../PublishProfiles/linux-x64.pubxml | 17 + .../Properties/PublishProfiles/win-x86.pubxml | 17 + Remotely.sln | 33 +- .../PublishProfiles/linux-x64.pubxml | 17 + ScreenCast.Win/Capture/DXCapture.cs | 4 +- .../Properties/PublishProfiles/win-x64.pubxml | 17 + .../Properties/PublishProfiles/win-x86.pubxml | 17 + ScreenCast.Win/ScreenCast.Win.csproj | 290 +----------------- .../App.config | 0 ScreenCast.Win_Old/Capture/BitBltCapture.cs | 100 ++++++ ScreenCast.Win_Old/Capture/DXCapture.cs | 214 +++++++++++++ .../FodyWeavers.xml | 0 .../FodyWeavers.xsd | 0 ScreenCast.Win_Old/Program.cs | 122 ++++++++ .../Properties/AssemblyInfo.cs | 0 .../Properties/app.manifest | 0 ScreenCast.Win_Old/ScreenCast.Win.csproj | 287 +++++++++++++++++ .../Services/CursorIconWatcher.cs | 128 ++++++++ .../Services/WinAudioCapturer.cs | 107 +++++++ .../Services/WinClipboardService.cs | 86 ++++++ ScreenCast.Win_Old/Services/WinInput.cs | 261 ++++++++++++++++ .../Services/WinScreenCaster.cs | 55 ++++ .../packages.config | 0 Utilities/Publish.ps1 | 32 +- 25 files changed, 1485 insertions(+), 321 deletions(-) create mode 100644 Desktop.Linux/Properties/PublishProfiles/linux-x64.pubxml create mode 100644 Desktop.Win/Properties/PublishProfiles/win-x86.pubxml create mode 100644 ScreenCast.Linux/Properties/PublishProfiles/linux-x64.pubxml create mode 100644 ScreenCast.Win/Properties/PublishProfiles/win-x64.pubxml create mode 100644 ScreenCast.Win/Properties/PublishProfiles/win-x86.pubxml rename {ScreenCast.Win => ScreenCast.Win_Old}/App.config (100%) create mode 100644 ScreenCast.Win_Old/Capture/BitBltCapture.cs create mode 100644 ScreenCast.Win_Old/Capture/DXCapture.cs rename {ScreenCast.Win => ScreenCast.Win_Old}/FodyWeavers.xml (100%) rename {ScreenCast.Win => ScreenCast.Win_Old}/FodyWeavers.xsd (100%) create mode 100644 ScreenCast.Win_Old/Program.cs rename {ScreenCast.Win => ScreenCast.Win_Old}/Properties/AssemblyInfo.cs (100%) rename {ScreenCast.Win => ScreenCast.Win_Old}/Properties/app.manifest (100%) create mode 100644 ScreenCast.Win_Old/ScreenCast.Win.csproj create mode 100644 ScreenCast.Win_Old/Services/CursorIconWatcher.cs create mode 100644 ScreenCast.Win_Old/Services/WinAudioCapturer.cs create mode 100644 ScreenCast.Win_Old/Services/WinClipboardService.cs create mode 100644 ScreenCast.Win_Old/Services/WinInput.cs create mode 100644 ScreenCast.Win_Old/Services/WinScreenCaster.cs rename {ScreenCast.Win => ScreenCast.Win_Old}/packages.config (100%) diff --git a/Desktop.Linux/Desktop.Linux.csproj b/Desktop.Linux/Desktop.Linux.csproj index efc3e6cc..53b5bee8 100644 --- a/Desktop.Linux/Desktop.Linux.csproj +++ b/Desktop.Linux/Desktop.Linux.csproj @@ -6,8 +6,6 @@ Remotely_Desktop Remotely.Desktop.Linux AnyCPU;x64;x86 - true - linux-x64 diff --git a/Desktop.Linux/Properties/PublishProfiles/linux-x64.pubxml b/Desktop.Linux/Properties/PublishProfiles/linux-x64.pubxml new file mode 100644 index 00000000..d1c51966 --- /dev/null +++ b/Desktop.Linux/Properties/PublishProfiles/linux-x64.pubxml @@ -0,0 +1,17 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp3.1 + ..\Server\wwwroot\Downloads + linux-x64 + false + True + False + + \ No newline at end of file diff --git a/Desktop.Win/Properties/PublishProfiles/win-x86.pubxml b/Desktop.Win/Properties/PublishProfiles/win-x86.pubxml new file mode 100644 index 00000000..b45e09ac --- /dev/null +++ b/Desktop.Win/Properties/PublishProfiles/win-x86.pubxml @@ -0,0 +1,17 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp3.1 + ..\Server\wwwroot\Downloads + win-x86 + false + True + False + + \ No newline at end of file diff --git a/Remotely.sln b/Remotely.sln index 80828c63..27d50e76 100644 --- a/Remotely.sln +++ b/Remotely.sln @@ -27,11 +27,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Assets", "Assets", "{D96B47 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{3E835099-C417-4D82-8D5C-13DC09AF48AC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCast.Win", "ScreenCast.Win\ScreenCast.Win.csproj", "{2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}" - ProjectSection(ProjectDependencies) = postProject - {98A8DF30-0993-4B49-822E-244A70AB8C4D} = {98A8DF30-0993-4B49-822E-244A70AB8C4D} - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCast.Linux", "ScreenCast.Linux\ScreenCast.Linux.csproj", "{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCast.Core", "ScreenCast.Core\ScreenCast.Core.csproj", "{B04A1728-2E87-491E-BC7F-F575A1754DEF}" @@ -40,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.Linux", "Desktop.Linux\Desktop.Linux.csproj", "{FF7FD66A-B3E2-4D39-A457-A00C94EDE9E6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop.Win", "Desktop.Win\Desktop.Win.csproj", "{90530ADC-69C6-4230-A09B-376B34F66B45}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.Win", "Desktop.Win\Desktop.Win.csproj", "{90530ADC-69C6-4230-A09B-376B34F66B45}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCast.Win", "ScreenCast.Win\ScreenCast.Win.csproj", "{D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -76,18 +73,6 @@ Global {3E835099-C417-4D82-8D5C-13DC09AF48AC}.Release|x64.Build.0 = Release|x64 {3E835099-C417-4D82-8D5C-13DC09AF48AC}.Release|x86.ActiveCfg = Release|x86 {3E835099-C417-4D82-8D5C-13DC09AF48AC}.Release|x86.Build.0 = Release|x86 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Debug|x64.ActiveCfg = Debug|x64 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Debug|x64.Build.0 = Debug|x64 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Debug|x86.ActiveCfg = Debug|x86 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Debug|x86.Build.0 = Debug|x86 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Release|Any CPU.Build.0 = Release|Any CPU - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Release|x64.ActiveCfg = Release|x64 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Release|x64.Build.0 = Release|x64 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Release|x86.ActiveCfg = Release|x86 - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}.Release|x86.Build.0 = Release|x86 {E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x64.ActiveCfg = Debug|x64 @@ -148,6 +133,18 @@ Global {90530ADC-69C6-4230-A09B-376B34F66B45}.Release|x64.Build.0 = Release|Any CPU {90530ADC-69C6-4230-A09B-376B34F66B45}.Release|x86.ActiveCfg = Release|Any CPU {90530ADC-69C6-4230-A09B-376B34F66B45}.Release|x86.Build.0 = Release|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Debug|x64.Build.0 = Debug|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Debug|x86.Build.0 = Debug|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Release|Any CPU.Build.0 = Release|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Release|x64.ActiveCfg = Release|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Release|x64.Build.0 = Release|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Release|x86.ActiveCfg = Release|Any CPU + {D4DE7CAB-74BB-4565-98DF-67DEA2B735FB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ScreenCast.Linux/Properties/PublishProfiles/linux-x64.pubxml b/ScreenCast.Linux/Properties/PublishProfiles/linux-x64.pubxml new file mode 100644 index 00000000..c0d31222 --- /dev/null +++ b/ScreenCast.Linux/Properties/PublishProfiles/linux-x64.pubxml @@ -0,0 +1,17 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp3.1 + ..\Agent\bin\Release\netcoreapp3.1\linux-x64\publish\ScreenCast + linux-x64 + false + True + False + + \ No newline at end of file diff --git a/ScreenCast.Win/Capture/DXCapture.cs b/ScreenCast.Win/Capture/DXCapture.cs index 98899211..2e5ec444 100644 --- a/ScreenCast.Win/Capture/DXCapture.cs +++ b/ScreenCast.Win/Capture/DXCapture.cs @@ -54,12 +54,12 @@ namespace Remotely.ScreenCast.Win.Capture OutputDuplicateFrameInformation duplicateFrameInformation; // Try to get duplicated frame within given time is ms - duplicatedOutput.AcquireNextFrame(50, out duplicateFrameInformation, out screenResource); + duplicatedOutput.TryAcquireNextFrame(50, out duplicateFrameInformation, out screenResource); while (duplicateFrameInformation.AccumulatedFrames < 1) { duplicatedOutput.ReleaseFrame(); - duplicatedOutput.AcquireNextFrame(50, out duplicateFrameInformation, out screenResource); + duplicatedOutput.TryAcquireNextFrame(50, out duplicateFrameInformation, out screenResource); } // copy resource into memory that can be accessed by the CPU diff --git a/ScreenCast.Win/Properties/PublishProfiles/win-x64.pubxml b/ScreenCast.Win/Properties/PublishProfiles/win-x64.pubxml new file mode 100644 index 00000000..c413acfa --- /dev/null +++ b/ScreenCast.Win/Properties/PublishProfiles/win-x64.pubxml @@ -0,0 +1,17 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp3.1 + ..\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\ScreenCast + false + win-x64 + True + False + + \ No newline at end of file diff --git a/ScreenCast.Win/Properties/PublishProfiles/win-x86.pubxml b/ScreenCast.Win/Properties/PublishProfiles/win-x86.pubxml new file mode 100644 index 00000000..bfff9bca --- /dev/null +++ b/ScreenCast.Win/Properties/PublishProfiles/win-x86.pubxml @@ -0,0 +1,17 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp3.1 + ..\Agent\bin\Release\netcoreapp3.1\win10-x86\publish\ScreenCast + win-x86 + false + True + False + + \ No newline at end of file diff --git a/ScreenCast.Win/ScreenCast.Win.csproj b/ScreenCast.Win/ScreenCast.Win.csproj index be467bae..6281cdc5 100644 --- a/ScreenCast.Win/ScreenCast.Win.csproj +++ b/ScreenCast.Win/ScreenCast.Win.csproj @@ -1,287 +1,21 @@ - - - + + - Debug - AnyCPU - {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709} - Exe + WinExe + netcoreapp3.1 + true Remotely.ScreenCast.Win Remotely_ScreenCast - v4.8 - 512 - true - true - - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - true - bin\x64\Debug\ - DEBUG;TRACE - true - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x64\Release\ - TRACE - true - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - true - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE - true - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - LocalIntranet - - - true - - - Properties\app.manifest + - - ..\packages\MessagePack.1.8.80\lib\net47\MessagePack.dll - - - ..\packages\Microsoft.AspNetCore.Connections.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Connections.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Http.Connections.Client.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Connections.Client.dll - - - ..\packages\Microsoft.AspNetCore.Http.Connections.Common.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Connections.Common.dll - - - ..\packages\Microsoft.AspNetCore.Http.Features.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll - - - ..\packages\Microsoft.AspNetCore.SignalR.Client.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Client.dll - - - ..\packages\Microsoft.AspNetCore.SignalR.Client.Core.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Client.Core.dll - - - ..\packages\Microsoft.AspNetCore.SignalR.Common.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Common.dll - - - ..\packages\Microsoft.AspNetCore.SignalR.Protocols.Json.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Protocols.Json.dll - - - ..\packages\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.dll - - - ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - ..\packages\Microsoft.Extensions.Configuration.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll - - - ..\packages\Microsoft.Extensions.Configuration.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll - - - ..\packages\Microsoft.Extensions.Configuration.Binder.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.3.1.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\packages\Microsoft.Extensions.Logging.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll - - - ..\packages\Microsoft.Extensions.Logging.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll - - - ..\packages\Microsoft.Extensions.Options.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll - - - ..\packages\Microsoft.Extensions.Primitives.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll - - - ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll - - - ..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll - - - ..\packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll - - - ..\packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll - - - - ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll - - - ..\packages\System.ComponentModel.Annotations.4.7.0\lib\net461\System.ComponentModel.Annotations.dll - - - - - - ..\packages\System.Drawing.Common.4.7.0\lib\net461\System.Drawing.Common.dll - - - ..\packages\System.IO.Pipelines.4.7.0\lib\netstandard2.0\System.IO.Pipelines.dll - - - ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - ..\packages\System.Runtime.Serialization.Primitives.4.3.0\lib\net46\System.Runtime.Serialization.Primitives.dll - True - True - - - ..\packages\System.Text.Encodings.Web.4.7.0\lib\netstandard2.0\System.Text.Encodings.Web.dll - - - ..\packages\System.Text.Json.4.7.0\lib\net461\System.Text.Json.dll - - - ..\packages\System.Threading.Channels.4.7.0\lib\netstandard2.0\System.Threading.Channels.dll - - - ..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - - - - - - - - + + + + - - - - - - - - - + - - - - - - - - {b04a1728-2e87-491e-bc7f-f575a1754def} - ScreenCast.Core - - - {3b1b36ae-7a60-4974-a868-1e24894d4149} - Shared - - - - - False - Microsoft .NET Framework 4.7.2 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - - if $(ConfigurationName) == Debug ( - if $(PlatformName) == Any CPU ( - md "$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\" - copy /y "$(TargetDir)\*" "$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\" - ) -) - + \ No newline at end of file diff --git a/ScreenCast.Win/App.config b/ScreenCast.Win_Old/App.config similarity index 100% rename from ScreenCast.Win/App.config rename to ScreenCast.Win_Old/App.config diff --git a/ScreenCast.Win_Old/Capture/BitBltCapture.cs b/ScreenCast.Win_Old/Capture/BitBltCapture.cs new file mode 100644 index 00000000..adf6b593 --- /dev/null +++ b/ScreenCast.Win_Old/Capture/BitBltCapture.cs @@ -0,0 +1,100 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Forms; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Remotely.ScreenCast.Core.Services; +using System.Threading; +using Remotely.ScreenCast.Core.Interfaces; + +namespace Remotely.ScreenCast.Win.Capture +{ + public class BitBltCapture : ICapturer + { + public BitBltCapture() + { + Init(); + } + + public bool CaptureFullscreen { get; set; } = true; + public Bitmap CurrentFrame { get; set; } + public Rectangle CurrentScreenBounds { get; set; } = Screen.PrimaryScreen.Bounds; + public bool IsCapturing { get; set; } + public int PauseForMilliseconds { get; set; } + public Bitmap PreviousFrame { get; set; } + public event EventHandler ScreenChanged; + public int SelectedScreen { get; private set; } = Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen); + private Graphics Graphic { get; set; } + + private object ScreenLock { get; } = new object(); + + public void Capture() + { + try + { + lock (ScreenLock) + { + PreviousFrame = (Bitmap)CurrentFrame.Clone(); + Graphic.CopyFromScreen(CurrentScreenBounds.Left, CurrentScreenBounds.Top, 0, 0, new Size(CurrentScreenBounds.Width, CurrentScreenBounds.Height)); + } + } + catch (Exception ex) + { + Logger.Write(ex); + Init(); + } + } + + public void Dispose() + { + Graphic.Dispose(); + CurrentFrame.Dispose(); + PreviousFrame.Dispose(); + } + + public int GetScreenCount() + { + return Screen.AllScreens.Length; + } + + public Rectangle GetVirtualScreenBounds() + { + return SystemInformation.VirtualScreen; + } + + public void Init() + { + CurrentFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb); + PreviousFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb); + Graphic = Graphics.FromImage(CurrentFrame); + } + + public void SetSelectedScreen(int screenNumber) + { + if (screenNumber == SelectedScreen) + { + return; + } + lock (ScreenLock) + { + if (GetScreenCount() >= screenNumber + 1) + { + SelectedScreen = screenNumber; + } + else + { + SelectedScreen = 0; + } + CurrentScreenBounds = Screen.AllScreens[SelectedScreen].Bounds; + CaptureFullscreen = true; + Init(); + ScreenChanged?.Invoke(this, CurrentScreenBounds); + } + } + } +} diff --git a/ScreenCast.Win_Old/Capture/DXCapture.cs b/ScreenCast.Win_Old/Capture/DXCapture.cs new file mode 100644 index 00000000..98899211 --- /dev/null +++ b/ScreenCast.Win_Old/Capture/DXCapture.cs @@ -0,0 +1,214 @@ +using Remotely.ScreenCast.Core.Interfaces; +using Remotely.ScreenCast.Core.Services; +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Threading; +using System.Windows.Forms; + +namespace Remotely.ScreenCast.Win.Capture +{ + public class DXCapture : ICapturer + { + private Adapter1 adapter; + private SharpDX.Direct3D11.Device device; + private OutputDuplication duplicatedOutput; + private Factory1 factory; + private int height; + private Output output; + private Output1 output1; + private Texture2D screenTexture; + private Texture2DDescription textureDesc; + private int width; + public bool CaptureFullscreen { get; set; } = true; + public Bitmap CurrentFrame { get; set; } + public Rectangle CurrentScreenBounds { get; private set; } + public bool NeedsInit { get; set; } = true; + public Bitmap PreviousFrame { get; set; } + public event EventHandler ScreenChanged; + public int SelectedScreen { get; private set; } = 0; + + public DXCapture() + { + Init(); + } + + public void Capture() + { + try + { + if (NeedsInit) + { + duplicatedOutput?.Dispose(); + Init(); + } + + PreviousFrame = (Bitmap)CurrentFrame.Clone(); + + SharpDX.DXGI.Resource screenResource; + OutputDuplicateFrameInformation duplicateFrameInformation; + + // Try to get duplicated frame within given time is ms + duplicatedOutput.AcquireNextFrame(50, out duplicateFrameInformation, out screenResource); + + while (duplicateFrameInformation.AccumulatedFrames < 1) + { + duplicatedOutput.ReleaseFrame(); + duplicatedOutput.AcquireNextFrame(50, out duplicateFrameInformation, out screenResource); + } + + // copy resource into memory that can be accessed by the CPU + using (var screenTexture2D = screenResource.QueryInterface()) + device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); + + // Get the desktop capture texture + var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None); + + // Create Drawing.Bitmap + using (var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb)) + { + var boundsRect = new Rectangle(0, 0, width, height); + + // Copy pixels from screen capture Texture to GDI bitmap + var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat); + var sourcePtr = mapSource.DataPointer; + var destPtr = mapDest.Scan0; + for (int y = 0; y < height; y++) + { + // Copy a single line + SharpDX.Utilities.CopyMemory(destPtr, sourcePtr, width * 4); + + // Advance pointers + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + destPtr = IntPtr.Add(destPtr, mapDest.Stride); + } + + // Release source and dest locks + bitmap.UnlockBits(mapDest); + device.ImmediateContext.UnmapSubresource(screenTexture, 0); + + screenResource.Dispose(); + duplicatedOutput.ReleaseFrame(); + + CurrentFrame = (Bitmap)bitmap.Clone(); + } + } + catch (SharpDXException e) + { + if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code) + { + Logger.Write(e); + NeedsInit = true; + } + } + catch (Exception e) + { + Logger.Write(e); + NeedsInit = true; + } + } + + public void Dispose() + { + duplicatedOutput?.Dispose(); + output1?.Dispose(); + output?.Dispose(); + device?.Dispose(); + adapter?.Dispose(); + factory?.Dispose(); + CurrentFrame?.Dispose(); + PreviousFrame?.Dispose(); + } + + public int GetScreenCount() + { + return Screen.AllScreens.Length; + } + + public Rectangle GetVirtualScreenBounds() + { + return SystemInformation.VirtualScreen; + } + + public void Init() + { + factory = new Factory1(); + + //Get first adapter + adapter = factory.Adapters1.FirstOrDefault(x => x.Outputs.Length > 0); + //Get device from adapter + device = new SharpDX.Direct3D11.Device(adapter); + //Get front buffer of the adapter + if (adapter.GetOutputCount() < SelectedScreen + 1) + { + SelectedScreen = 0; + } + output = adapter.GetOutput(SelectedScreen); + output1 = output.QueryInterface(); + + // Width/Height of desktop to capture + var bounds = output1.Description.DesktopBounds; + var newWidth = bounds.Right - bounds.Left; + var newHeight = bounds.Bottom - bounds.Top; + CurrentScreenBounds = new Rectangle(bounds.Left, bounds.Top, newWidth, newHeight); + if (newWidth != width || newHeight != height) + { + ScreenChanged?.Invoke(this, CurrentScreenBounds); + } + width = newWidth; + height = newHeight; + + CurrentFrame = new Bitmap(width, height, PixelFormat.Format32bppArgb); + PreviousFrame = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + // Create Staging texture CPU-accessible + textureDesc = new Texture2DDescription + { + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = width, + Height = height, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; + screenTexture = new Texture2D(device, textureDesc); + duplicatedOutput = output1.DuplicateOutput(device); + + NeedsInit = false; + } + + public void SetSelectedScreen(int screenNumber) + { + if (screenNumber == SelectedScreen) + { + return; + } + if (adapter == null) + { + SelectedScreen = 0; + } + else + { + if (adapter.Outputs.Length >= screenNumber + 1) + { + SelectedScreen = screenNumber; + } + else + { + SelectedScreen = 0; + } + } + CaptureFullscreen = true; + NeedsInit = true; + } + } +} diff --git a/ScreenCast.Win/FodyWeavers.xml b/ScreenCast.Win_Old/FodyWeavers.xml similarity index 100% rename from ScreenCast.Win/FodyWeavers.xml rename to ScreenCast.Win_Old/FodyWeavers.xml diff --git a/ScreenCast.Win/FodyWeavers.xsd b/ScreenCast.Win_Old/FodyWeavers.xsd similarity index 100% rename from ScreenCast.Win/FodyWeavers.xsd rename to ScreenCast.Win_Old/FodyWeavers.xsd diff --git a/ScreenCast.Win_Old/Program.cs b/ScreenCast.Win_Old/Program.cs new file mode 100644 index 00000000..7b6e6d66 --- /dev/null +++ b/ScreenCast.Win_Old/Program.cs @@ -0,0 +1,122 @@ +using Remotely.Shared.Models; +using Remotely.ScreenCast.Core; +using Remotely.ScreenCast.Core.Services; +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Remotely.Shared.Win32; +using System.Threading; +using Remotely.ScreenCast.Win.Services; +using Remotely.ScreenCast.Core.Interfaces; +using Remotely.ScreenCast.Win.Capture; +using Remotely.ScreenCast.Core.Sockets; + +namespace Remotely.ScreenCast.Win +{ + public class Program + { + public static Conductor Conductor { get; private set; } + public static CursorIconWatcher CursorIconWatcher { get; private set; } + public static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor) + { + if (Conductor?.CasterSocket != null) + { + await Conductor.CasterSocket.SendCursorChange(cursor, Conductor.Viewers.Keys.ToList()); + } + } + + public static void Main(string[] args) + { + try + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + CursorIconWatcher = new CursorIconWatcher(Conductor); + var screenCaster = new WinScreenCaster(CursorIconWatcher); + var clipboardService = new WinClipboardService(); + clipboardService.BeginWatching(); + var casterSocket = new CasterSocket(new WinInput(), screenCaster, new WinAudioCapturer(), clipboardService); + Conductor = new Conductor(casterSocket, screenCaster); + Conductor.ProcessArgs(args); + + Conductor.Connect().ContinueWith(async (task) => + { + CursorIconWatcher.OnChange += CursorIconWatcher_OnChange; + await Conductor.CasterSocket.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName, Conductor.DeviceID); + CheckInitialDesktop(); + await CheckForRelaunch(); + Conductor.IdleTimer = new IdleTimer(Conductor.Viewers); + Conductor.IdleTimer.Start(); + + await HandleConnection(Conductor); + }); + + Thread.Sleep(Timeout.Infinite); + } + catch (Exception ex) + { + Logger.Write(ex); + throw; + } + } + + private static async Task CheckForRelaunch() + { + + if (Conductor.ArgDict.ContainsKey("relaunch")) + { + Logger.Write($"Resuming after relaunch in desktop {Conductor.CurrentDesktopName}."); + var viewersString = Conductor.ArgDict["viewers"]; + var viewerIDs = viewersString.Split(",".ToCharArray()); + await Conductor.CasterSocket.NotifyViewersRelaunchedScreenCasterReady(viewerIDs); + } + else + { + await Conductor.CasterSocket.NotifyRequesterUnattendedReady(Conductor.RequesterID); + } + } + + private static void CheckInitialDesktop() + { + var desktopName = Win32Interop.GetCurrentDesktop(); + if (desktopName.ToLower() != Conductor.CurrentDesktopName.ToLower()) + { + Conductor.CurrentDesktopName = desktopName; + Logger.Write($"Setting initial desktop to {desktopName}."); + Conductor.ArgDict["desktop"] = desktopName; + var openProcessString = Assembly.GetExecutingAssembly().Location; + foreach (var arg in Conductor.ArgDict) + { + openProcessString += $" -{arg.Key} {arg.Value}"; + } + var result = Win32Interop.OpenInteractiveProcess(openProcessString, desktopName, true, out _); + if (!result) + { + Logger.Write($"Desktop relaunch to {desktopName} failed."); + } + Environment.Exit(0); + } + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Logger.Write((Exception)e.ExceptionObject); + } + + + private static async Task HandleConnection(Conductor conductor) + { + while (true) + { + var desktopName = Win32Interop.GetCurrentDesktop(); + if (desktopName.ToLower() != conductor.CurrentDesktopName.ToLower() && conductor.Viewers.Count > 0) + { + conductor.CurrentDesktopName = desktopName; + Logger.Write($"Switching desktops to {desktopName}."); + Win32Interop.SwitchToInputDesktop(); + } + await Task.Delay(1000); + } + } + } +} diff --git a/ScreenCast.Win/Properties/AssemblyInfo.cs b/ScreenCast.Win_Old/Properties/AssemblyInfo.cs similarity index 100% rename from ScreenCast.Win/Properties/AssemblyInfo.cs rename to ScreenCast.Win_Old/Properties/AssemblyInfo.cs diff --git a/ScreenCast.Win/Properties/app.manifest b/ScreenCast.Win_Old/Properties/app.manifest similarity index 100% rename from ScreenCast.Win/Properties/app.manifest rename to ScreenCast.Win_Old/Properties/app.manifest diff --git a/ScreenCast.Win_Old/ScreenCast.Win.csproj b/ScreenCast.Win_Old/ScreenCast.Win.csproj new file mode 100644 index 00000000..be467bae --- /dev/null +++ b/ScreenCast.Win_Old/ScreenCast.Win.csproj @@ -0,0 +1,287 @@ + + + + + Debug + AnyCPU + {2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709} + Exe + Remotely.ScreenCast.Win + Remotely_ScreenCast + v4.8 + 512 + true + true + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + true + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x86\Release\ + TRACE + true + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + LocalIntranet + + + true + + + Properties\app.manifest + + + + ..\packages\MessagePack.1.8.80\lib\net47\MessagePack.dll + + + ..\packages\Microsoft.AspNetCore.Connections.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Connections.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Http.Connections.Client.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Connections.Client.dll + + + ..\packages\Microsoft.AspNetCore.Http.Connections.Common.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Connections.Common.dll + + + ..\packages\Microsoft.AspNetCore.Http.Features.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll + + + ..\packages\Microsoft.AspNetCore.SignalR.Client.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Client.dll + + + ..\packages\Microsoft.AspNetCore.SignalR.Client.Core.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Client.Core.dll + + + ..\packages\Microsoft.AspNetCore.SignalR.Common.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Common.dll + + + ..\packages\Microsoft.AspNetCore.SignalR.Protocols.Json.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Protocols.Json.dll + + + ..\packages\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.3.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.Extensions.Configuration.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll + + + ..\packages\Microsoft.Extensions.Configuration.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Configuration.Binder.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.3.1.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + + ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll + + + ..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll + + + ..\packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll + + + ..\packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll + + + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.ComponentModel.Annotations.4.7.0\lib\net461\System.ComponentModel.Annotations.dll + + + + + + ..\packages\System.Drawing.Common.4.7.0\lib\net461\System.Drawing.Common.dll + + + ..\packages\System.IO.Pipelines.4.7.0\lib\netstandard2.0\System.IO.Pipelines.dll + + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + + ..\packages\System.Runtime.Serialization.Primitives.4.3.0\lib\net46\System.Runtime.Serialization.Primitives.dll + True + True + + + ..\packages\System.Text.Encodings.Web.4.7.0\lib\netstandard2.0\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.4.7.0\lib\net461\System.Text.Json.dll + + + ..\packages\System.Threading.Channels.4.7.0\lib\netstandard2.0\System.Threading.Channels.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + {b04a1728-2e87-491e-bc7f-f575a1754def} + ScreenCast.Core + + + {3b1b36ae-7a60-4974-a868-1e24894d4149} + Shared + + + + + False + Microsoft .NET Framework 4.7.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + if $(ConfigurationName) == Debug ( + if $(PlatformName) == Any CPU ( + md "$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\" + copy /y "$(TargetDir)\*" "$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\" + ) +) + + \ No newline at end of file diff --git a/ScreenCast.Win_Old/Services/CursorIconWatcher.cs b/ScreenCast.Win_Old/Services/CursorIconWatcher.cs new file mode 100644 index 00000000..43d2709d --- /dev/null +++ b/ScreenCast.Win_Old/Services/CursorIconWatcher.cs @@ -0,0 +1,128 @@ +using Remotely.ScreenCast.Core; +using Remotely.ScreenCast.Core.Models; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Timers; +using System.Windows.Forms; +using Remotely.Shared.Win32; +using Remotely.Shared.Models; + +namespace Remotely.ScreenCast.Win.Services +{ + /// + /// A class that can be used to watch for cursor icon changes. + /// + public class CursorIconWatcher + { + public CursorIconWatcher(Conductor conductor) + { + Conductor = conductor; + ChangeTimer = new System.Timers.Timer(25); + ChangeTimer.Elapsed += ChangeTimer_Elapsed; + ChangeTimer.Start(); + } + public event EventHandler OnChange; + private System.Timers.Timer ChangeTimer { get; set; } + private string PreviousCursorHandle { get; set; } + public Conductor Conductor { get; } + + private User32.CursorInfo cursorInfo; + + + public CursorInfo GetCurrentCursor() + { + try + { + var ci = new User32.CursorInfo(); + ci.cbSize = Marshal.SizeOf(ci); + User32.GetCursorInfo(out ci); + if (ci.flags == User32.CURSOR_SHOWING) + { + if (ci.hCursor.ToString() == Cursors.IBeam.Handle.ToString()) + { + return new CursorInfo(new byte[0], Point.Empty, "text"); + } + + using (var icon = Icon.FromHandle(ci.hCursor)) + { + using (var ms = new MemoryStream()) + { + using (var cursor = new Cursor(ci.hCursor)) + { + var hotspot = cursor.HotSpot; + icon.ToBitmap().Save(ms, ImageFormat.Png); + return new CursorInfo(ms.ToArray(), hotspot); + } + } + } + } + else + { + return new CursorInfo(new byte[0], Point.Empty, "default"); + } + } + catch + { + return new CursorInfo(new byte[0], Point.Empty, "default"); + } + } + + private void ChangeTimer_Elapsed(object sender, ElapsedEventArgs e) + { + if (OnChange == null) + { + return; + } + try + { + cursorInfo = new User32.CursorInfo(); + cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); + User32.GetCursorInfo(out cursorInfo); + if (cursorInfo.flags == User32.CURSOR_SHOWING) + { + var currentCursor = cursorInfo.hCursor.ToString(); + if (currentCursor != PreviousCursorHandle) + { + if (currentCursor == Cursors.IBeam.Handle.ToString()) + { + OnChange?.Invoke(this, new CursorInfo(new byte[0], Point.Empty, "text")); + } + else + { + using (var icon = Icon.FromHandle(cursorInfo.hCursor)) + { + using (var ms = new MemoryStream()) + { + using (var cursor = new Cursor(cursorInfo.hCursor)) + { + var hotspot = cursor.HotSpot; + icon.ToBitmap().Save(ms, ImageFormat.Png); + OnChange?.Invoke(this, new CursorInfo(ms.ToArray(), hotspot)); + } + } + } + } + PreviousCursorHandle = currentCursor; + } + } + else if (PreviousCursorHandle != "0") + { + PreviousCursorHandle = "0"; + OnChange?.Invoke(this, new CursorInfo(new byte[0], Point.Empty, "default")); + } + } + catch + { + OnChange?.Invoke(this, new CursorInfo(new byte[0], Point.Empty, "default")); + } + } + + } +} diff --git a/ScreenCast.Win_Old/Services/WinAudioCapturer.cs b/ScreenCast.Win_Old/Services/WinAudioCapturer.cs new file mode 100644 index 00000000..f6dca2da --- /dev/null +++ b/ScreenCast.Win_Old/Services/WinAudioCapturer.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Timers; +using NAudio.Wave; +using Remotely.ScreenCast.Core; +using Remotely.ScreenCast.Core.Interfaces; + +namespace Remotely.ScreenCast.Win.Services +{ + public class WinAudioCapturer : IAudioCapturer + { + private WasapiLoopbackCapture Capturer { get; set; } + private Stopwatch SendTimer { get; set; } + private WaveFormat TargetFormat { get; set; } + private List TempBuffer { get; set; } = new List(); + public void ToggleAudio(bool toggleOn) + { + if (toggleOn) + { + Start(); + } + else + { + Stop(); + } + } + + private async void SendTempBuffer() + { + if (TempBuffer.Count == 0) + { + return; + } + + using (var ms1 = new MemoryStream()) + { + using (var wfw = new WaveFileWriter(ms1, Capturer.WaveFormat)) + { + wfw.Write(TempBuffer.ToArray(), 0, TempBuffer.Count); + } + TempBuffer.Clear(); + + // Resample to 16-bit so Firefox will play it. + using (var ms2 = new MemoryStream(ms1.ToArray())) + using (var wfr = new WaveFileReader(ms2)) + using (var ms3 = new MemoryStream()) + { + using (var resampler = new MediaFoundationResampler(wfr, TargetFormat)) + { + WaveFileWriter.WriteWavFileToStream(ms3, resampler); + } + await Conductor.Current.CasterSocket.SendAudioSample(ms3.ToArray(), Program.Conductor.Viewers.Keys.ToList()); + } + } + } + + private void Start() + { + try + { + Capturer = new WasapiLoopbackCapture(); + TargetFormat = new WaveFormat(16000, 8, 1); + SendTimer = Stopwatch.StartNew(); + Capturer.DataAvailable += (aud, args) => + { + try + { + if (args.BytesRecorded > 0) + { + lock (TempBuffer) + { + if (!SendTimer.IsRunning) + { + SendTimer.Restart(); + } + TempBuffer.AddRange(args.Buffer.Take(args.BytesRecorded)); + if (TempBuffer.Count > 200000) + { + SendTimer.Reset(); + SendTempBuffer(); + } + else if (SendTimer.Elapsed.TotalMilliseconds > 1000) + { + SendTimer.Reset(); + SendTempBuffer(); + } + } + } + } + catch { } + }; + Capturer.StartRecording(); + } + catch { } + } + private void Stop() + { + Capturer.StopRecording(); + SendTimer.Reset(); + } + } +} diff --git a/ScreenCast.Win_Old/Services/WinClipboardService.cs b/ScreenCast.Win_Old/Services/WinClipboardService.cs new file mode 100644 index 00000000..3c8c3552 --- /dev/null +++ b/ScreenCast.Win_Old/Services/WinClipboardService.cs @@ -0,0 +1,86 @@ +using Remotely.ScreenCast.Core.Interfaces; +using Remotely.ScreenCast.Core.Services; +using Remotely.ScreenCast.Core.Sockets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Remotely.ScreenCast.Win.Services +{ + public class WinClipboardService : IClipboardService + { + public event EventHandler ClipboardTextChanged; + + private string ClipboardText { get; set; } + + private System.Timers.Timer ClipboardWatcher { get; set; } + + public void BeginWatching() + { + try + { + if (ClipboardWatcher?.Enabled == true) + { + ClipboardWatcher.Stop(); + } + + if (Clipboard.ContainsText()) + { + ClipboardText = Clipboard.GetText(); + ClipboardTextChanged.Invoke(this, ClipboardText); + } + ClipboardWatcher = new System.Timers.Timer(500); + } + catch + { + return; + } + ClipboardWatcher.Elapsed += (sender, args) => + { + var thread = new Thread(() => + { + try + { + if (Clipboard.ContainsText() && Clipboard.GetText() != ClipboardText) + { + ClipboardText = Clipboard.GetText(); + ClipboardTextChanged.Invoke(this, ClipboardText); + } + } + catch (Exception ex) + { + Logger.Write(ex); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + }; + ClipboardWatcher.Start(); + } + + public void SetText(string clipboardText) + { + try + { + var thread = new Thread(() => + { + Clipboard.SetText(clipboardText); + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + catch (Exception ex) + { + Logger.Write(ex); + } + } + public void StopWatching() + { + ClipboardWatcher?.Stop(); + } + } +} diff --git a/ScreenCast.Win_Old/Services/WinInput.cs b/ScreenCast.Win_Old/Services/WinInput.cs new file mode 100644 index 00000000..48798fc5 --- /dev/null +++ b/ScreenCast.Win_Old/Services/WinInput.cs @@ -0,0 +1,261 @@ +using Remotely.ScreenCast.Core.Input; +using Remotely.ScreenCast.Core.Models; +using System; +using Remotely.Shared.Win32; +using static Remotely.Shared.Win32.User32; +using Remotely.ScreenCast.Core.Capture; +using Remotely.ScreenCast.Core.Interfaces; +using System.Windows.Forms; + +namespace Remotely.ScreenCast.Win.Services +{ + public class WinInput : IKeyboardMouseInput + { + public Tuple GetAbsolutePercentFromRelativePercent(double percentX, double percentY, ICapturer capturer) + { + var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left - capturer.GetVirtualScreenBounds().Left; + var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top - capturer.GetVirtualScreenBounds().Top; + return new Tuple(absoluteX / capturer.GetVirtualScreenBounds().Width, absoluteY / capturer.GetVirtualScreenBounds().Height); + } + + public Tuple GetAbsolutePointFromRelativePercent(double percentX, double percentY, ICapturer capturer) + { + var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left; + var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top; + return new Tuple(absoluteX, absoluteY); + } + + public void SendKeyDown(string key, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var keyCode = ConvertJavaScriptKeyToVirtualKey(key); + var union = new InputUnion() + { + ki = new KEYBDINPUT() + { + wVk = keyCode, + wScan = 0, + time = 0, + dwExtraInfo = GetMessageExtraInfo() + } + }; + var input = new INPUT() { type = InputType.KEYBOARD, U = union }; + SendInput(1, new INPUT[] { input }, INPUT.Size); + } + + public void SendKeyUp(string key, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var keyCode = ConvertJavaScriptKeyToVirtualKey(key); + var union = new InputUnion() + { + ki = new KEYBDINPUT() + { + wVk = keyCode, + wScan = 0, + time = 0, + dwFlags = KEYEVENTF.KEYUP, + dwExtraInfo = GetMessageExtraInfo() + } + }; + var input = new INPUT() { type = InputType.KEYBOARD, U = union }; + SendInput(1, new INPUT[] { input }, INPUT.Size); + } + + public uint SendLeftMouseDown(double percentX, double percentY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer); + // Coordinates must be normalized. The bottom-right coordinate is mapped to 65535. + var normalizedX = xyPercent.Item1 * 65535D; + var normalizedY = xyPercent.Item2 * 65535D; + var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.LEFTDOWN | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new INPUT[] { input }, INPUT.Size); + } + public uint SendLeftMouseUp(double percentX, double percentY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer); + // Coordinates must be normalized. The bottom-right coordinate is mapped to 65535. + var normalizedX = xyPercent.Item1 * 65535D; + var normalizedY = xyPercent.Item2 * 65535D; + var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.LEFTUP | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new INPUT[] { input }, INPUT.Size); + } + public uint SendMouseMove(double percentX, double percentY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer); + // Coordinates must be normalized. The bottom-right coordinate is mapped to 65535. + var normalizedX = xyPercent.Item1 * 65535D; + var normalizedY = xyPercent.Item2 * 65535D; + var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.MOVE | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new INPUT[] { input }, INPUT.Size); + } + + public uint SendMouseWheel(int deltaY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + if (deltaY < 0) + { + deltaY = -120; + } + else if (deltaY > 0) + { + deltaY = 120; + } + var union = new User32.InputUnion() { mi = new User32.MOUSEINPUT() { dwFlags = MOUSEEVENTF.WHEEL, dx = 0, dy = 0, time = 0, mouseData = deltaY, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new User32.INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new User32.INPUT[] { input }, INPUT.Size); + } + + public uint SendRightMouseDown(double percentX, double percentY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer); + // Coordinates must be normalized. The bottom-right coordinate is mapped to 65535. + var normalizedX = xyPercent.Item1 * 65535D; + var normalizedY = xyPercent.Item2 * 65535D; + var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.RIGHTDOWN | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new INPUT[] { input }, INPUT.Size); + } + public uint SendRightMouseUp(double percentX, double percentY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer); + // Coordinates must be normalized. The bottom-right coordinate is mapped to 65535. + var normalizedX = xyPercent.Item1 * 65535D; + var normalizedY = xyPercent.Item2 * 65535D; + var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.RIGHTUP | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new INPUT[] { input }, INPUT.Size); + } + public void SendText(string transferText, Viewer viewer) + { + SendKeys.SendWait(transferText); + } + + private VirtualKey ConvertJavaScriptKeyToVirtualKey(string key) + { + VirtualKey keyCode; + switch (key) + { + case "Down": + case "ArrowDown": + keyCode = VirtualKey.DOWN; + break; + case "Up": + case "ArrowUp": + keyCode = VirtualKey.UP; + break; + case "Left": + case "ArrowLeft": + keyCode = VirtualKey.LEFT; + break; + case "Right": + case "ArrowRight": + keyCode = VirtualKey.RIGHT; + break; + case "Enter": + keyCode = VirtualKey.RETURN; + break; + case "Esc": + case "Escape": + keyCode = VirtualKey.ESCAPE; + break; + case "Alt": + keyCode = VirtualKey.MENU; + break; + case "Control": + keyCode = VirtualKey.CONTROL; + break; + case "Shift": + keyCode = VirtualKey.SHIFT; + break; + case "PAUSE": + keyCode = VirtualKey.PAUSE; + break; + case "BREAK": + keyCode = VirtualKey.PAUSE; + break; + case "Backspace": + keyCode = VirtualKey.BACK; + break; + case "Tab": + keyCode = VirtualKey.TAB; + break; + case "CapsLock": + keyCode = VirtualKey.CAPITAL; + break; + case "Delete": + keyCode = VirtualKey.DELETE; + break; + case "Home": + keyCode = VirtualKey.HOME; + break; + case "End": + keyCode = VirtualKey.END; + break; + case "PageUp": + keyCode = VirtualKey.PRIOR; + break; + case "PageDown": + keyCode = VirtualKey.NEXT; + break; + case "NumLock": + keyCode = VirtualKey.NUMLOCK; + break; + case "Insert": + keyCode = VirtualKey.INSERT; + break; + case "ScrollLock": + keyCode = VirtualKey.SCROLL; + break; + case "F1": + keyCode = VirtualKey.F1; + break; + case "F2": + keyCode = VirtualKey.F2; + break; + case "F3": + keyCode = VirtualKey.F3; + break; + case "F4": + keyCode = VirtualKey.F4; + break; + case "F5": + keyCode = VirtualKey.F5; + break; + case "F6": + keyCode = VirtualKey.F6; + break; + case "F7": + keyCode = VirtualKey.F7; + break; + case "F8": + keyCode = VirtualKey.F8; + break; + case "F9": + keyCode = VirtualKey.F9; + break; + case "F10": + keyCode = VirtualKey.F10; + break; + case "F11": + keyCode = VirtualKey.F11; + break; + case "F12": + keyCode = VirtualKey.F12; + break; + default: + keyCode = (VirtualKey)VkKeyScan(Convert.ToChar(key)); + break; + } + return keyCode; + } + } +} diff --git a/ScreenCast.Win_Old/Services/WinScreenCaster.cs b/ScreenCast.Win_Old/Services/WinScreenCaster.cs new file mode 100644 index 00000000..7b831ed8 --- /dev/null +++ b/ScreenCast.Win_Old/Services/WinScreenCaster.cs @@ -0,0 +1,55 @@ +using Remotely.ScreenCast.Core.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Remotely.ScreenCast.Core.Enums; +using Remotely.ScreenCast.Core.Services; +using Remotely.ScreenCast.Core.Capture; +using Remotely.ScreenCast.Core; +using Remotely.ScreenCast.Core.Models; +using Remotely.Shared.Models; +using Remotely.ScreenCast.Win.Capture; + +namespace Remotely.ScreenCast.Win.Services +{ + public class WinScreenCaster : ScreenCasterBase, IScreenCaster + { + public WinScreenCaster(CursorIconWatcher cursorIconWatcher) + { + CursorIconWatcher = cursorIconWatcher; + } + + public CursorIconWatcher CursorIconWatcher { get; } + + public async Task BeginScreenCasting(ScreenCastRequest screenCastRequest) + { + await Conductor.Current.CasterSocket.SendCursorChange(CursorIconWatcher.GetCurrentCursor(), new List() { screenCastRequest.ViewerID }); + _ = BeginScreenCasting(screenCastRequest.ViewerID, screenCastRequest.RequesterName, GetCapturer()); + } + + private static ICapturer GetCapturer() + { + ICapturer capturer; + try + { + if (Conductor.Current.Viewers.Count == 0) + { + capturer = new DXCapture(); + } + else + { + capturer = new BitBltCapture(); + } + } + catch (Exception ex) + { + Logger.Write(ex); + capturer = new BitBltCapture(); + } + + return capturer; + } + } +} diff --git a/ScreenCast.Win/packages.config b/ScreenCast.Win_Old/packages.config similarity index 100% rename from ScreenCast.Win/packages.config rename to ScreenCast.Win_Old/packages.config diff --git a/Utilities/Publish.ps1 b/Utilities/Publish.ps1 index ca6041c8..affe5484 100644 --- a/Utilities/Publish.ps1 +++ b/Utilities/Publish.ps1 @@ -114,9 +114,9 @@ if ((Test-Path -Path "$Root\Agent\bin\Release\netcoreapp3.1\linux-x64\publish") # Publish Core clients. -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x64 --configuration Release --output "$Root\Agent\bin\Release\netcoreapp3.1\win10-x64\publish" "$Root\Agent" -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --configuration Release --output "$Root\Agent\bin\Release\netcoreapp3.1\linux-x64\publish" "$Root\Agent" -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x86 --configuration Release --output "$Root\Agent\bin\Release\netcoreapp3.1\win10-x86\publish" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x64 --configuration Release --no-self-contained --output "$Root\Agent\bin\Release\netcoreapp3.1\win10-x64\publish" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --configuration Release --no-self-contained --output "$Root\Agent\bin\Release\netcoreapp3.1\linux-x64\publish" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x86 --configuration Release --no-self-contained --output "$Root\Agent\bin\Release\netcoreapp3.1\win10-x86\publish" "$Root\Agent" New-Item -Path "$Root\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\ScreenCast\" -ItemType Directory -Force New-Item -Path "$Root\Agent\bin\Release\netcoreapp3.1\win10-x86\publish\ScreenCast\" -ItemType Directory -Force @@ -124,31 +124,21 @@ New-Item -Path "$Root\Agent\bin\Release\netcoreapp3.1\linux-x64\publish\ScreenCa # Publish Linux ScreenCaster -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --configuration Release --output "$Root\Agent\bin\Release\netcoreapp3.1\linux-x64\publish\ScreenCast\" "$Root\ScreenCast.Linux\" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion -p:PublishProfile=linux-x64 "$Root\ScreenCast.Linux\" # Publish Linux GUI App -$PublishDir = "$Root\Desktop.Linux\bin\Release\netcoreapp3.1\linux-x64\publish\" -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --configuration Release --output "$PublishDir" "$Root\Desktop.Linux\" -Move-Item -Path "$PublishDir\Remotely_Desktop" -Destination "$Root\Server\wwwroot\Downloads\Remotely_Desktop" -Force +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion -p:PublishProfile=linux-x64 "$Root\Desktop.Linux\" -# Build .NET Framework ScreenCaster (32-bit) -&"$MSBuildPath" "$Root\ScreenCast.Win" /t:Build /p:Configuration=Release /p:Platform=x86 +# Publish Windows ScreenCaster (32-bit) +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion -p:PublishProfile=win-x86 "$Root\ScreenCast.Win" -# Copy 32-bit .NET Framework ScreenCaster to Agent output folder. -Get-ChildItem -Path "$Root\ScreenCast.Win\bin\x86\Release\" -Exclude "*.xml" | Copy-Item -Destination ".\Agent\bin\Release\netcoreapp3.1\win10-x86\publish\ScreenCast\" -Force +# Publish Windows ScreenCaster (64-bit) +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion -p:PublishProfile=win-x64 "$Root\ScreenCast.Win" -# Build .NET Framework ScreenCaster (64-bit) -&"$MSBuildPath" "$Root\ScreenCast.Win" /t:Build /p:Configuration=Release /p:Platform=x64 +# Publish Windows GUI App +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion -p:PublishProfile=win-x64 "$Root\ScreenCast.Win" -# Copy 64-bit .NET Framework ScreenCaster to Agent output folder. -Get-ChildItem -Path "$Root\ScreenCast.Win\bin\x64\Release\" -Exclude "*.xml" | Copy-Item -Destination ".\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\ScreenCast\" -Force - - -# Build Windows GUI App - -&"$MSBuildPath" "$Root\Desktop.Win" /t:Build /p:Configuration=Release /p:Platform=AnyCPU -Move-Item -Path "$Root\Desktop.Win\bin\Release\Remotely_Desktop.exe" -Destination "$Root\Server\wwwroot\Downloads\Remotely_Desktop.exe" -Force if ($SignAssemblies) { &"$Root\Utilities\signtool.exe" sign /f "$CertificatePath" /p $CertificatePassword /t http://timestamp.digicert.com "$Root\Server\wwwroot\Downloads\Remotely_Desktop.exe" }