Refactor BitBlt and DirectX capturer into one service.

This commit is contained in:
Jared Goodwin 2020-04-03 10:52:17 -07:00
parent 0970b2e27d
commit 7cc2820086
27 changed files with 283 additions and 351 deletions

View File

@ -6,12 +6,10 @@ using Remotely.Desktop.Linux.Controls;
using Remotely.Desktop.Linux.Services;
using Remotely.Desktop.Linux.Views;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Capture;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Models;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Linux.Capture;
using Remotely.ScreenCast.Linux.Services;
using Remotely.Shared.Models;
using Remotely.Shared.Services;
@ -225,14 +223,14 @@ namespace Remotely.Desktop.Linux.ViewModels
builder.AddConsole().AddEventLog();
});
serviceCollection.AddSingleton<IScreenCaster, LinuxScreenCaster>();
serviceCollection.AddSingleton<IScreenCaster, ScreenCasterLinux>();
serviceCollection.AddSingleton<IKeyboardMouseInput, X11Input>();
serviceCollection.AddSingleton<IClipboardService, LinuxClipboardService>();
serviceCollection.AddSingleton<IAudioCapturer, LinuxAudioCapturer>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceLinux>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerLinux>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddTransient<ICapturer, X11Capture>();
serviceCollection.AddTransient<IScreenCapturer, X11Capture>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();

View File

@ -2,10 +2,8 @@
using Remotely.Desktop.Win.Services;
using Remotely.Shared.Models;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Capture;
using Remotely.ScreenCast.Core.Models;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Win.Capture;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -255,34 +253,14 @@ namespace Remotely.Desktop.Win.ViewModels
});
serviceCollection.AddSingleton<CursorIconWatcher>();
serviceCollection.AddSingleton<IScreenCaster, WinScreenCaster>();
serviceCollection.AddSingleton<IKeyboardMouseInput, WinInput>();
serviceCollection.AddSingleton<IClipboardService, WinClipboardService>();
serviceCollection.AddSingleton<IAudioCapturer, WinAudioCapturer>();
serviceCollection.AddSingleton<IScreenCaster, ScreenCasterWin>();
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputWin>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceWin>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerWin>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddTransient<ICapturer>(provider =>
{
try
{
var dxCapture = new DXCapture();
if (dxCapture.GetScreenCount() == Screen.AllScreens.Length)
{
return dxCapture;
}
else
{
Logger.Write("DX screen count doesn't match. Using CPU capturer instead.");
dxCapture.Dispose();
return new BitBltCapture();
}
}
catch
{
return new BitBltCapture();
}
});
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();

View File

@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Remotely.ScreenCast.Core.Capture;
using System.Diagnostics;
using System.IO;
using System.Net;
@ -129,9 +128,9 @@ namespace Remotely.ScreenCast.Core.Communication
await Connection.SendAsync("SendScreenCapture", captureBytes, viewerID, left, top, width, height, captureTime);
}
public async Task SendScreenCount(int primaryScreenIndex, int screenCount, string viewerID)
public async Task SendScreenData(string selectedScreen, string[] displayNames, string viewerID)
{
await Connection.SendAsync("SendScreenCountToBrowser", primaryScreenIndex, screenCount, viewerID);
await Connection.SendAsync("SendScreenDataToBrowser", selectedScreen, displayNames, viewerID);
}
public async Task SendScreenSize(int width, int height, string viewerID)
@ -332,11 +331,11 @@ namespace Remotely.ScreenCast.Core.Communication
}
});
Connection.On("SelectScreen", (int screenIndex, string viewerID) =>
Connection.On("SelectScreen", (string displayName, string viewerID) =>
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.Capturer.SetSelectedScreen(screenIndex);
viewer.Capturer.SetSelectedScreen(displayName);
}
});

View File

@ -7,19 +7,27 @@ using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Interfaces
{
public interface ICapturer : IDisposable
public interface IScreenCapturer : IDisposable
{
event EventHandler<Rectangle> ScreenChanged;
bool CaptureFullscreen { get; set; }
Bitmap CurrentFrame { get; set; }
Rectangle CurrentScreenBounds { get; }
Bitmap PreviousFrame { get; set; }
event EventHandler<Rectangle> ScreenChanged;
int SelectedScreen { get; }
void SetSelectedScreen(int screenNumber);
int GetScreenCount();
Rectangle GetVirtualScreenBounds();
string SelectedScreen { get; }
IEnumerable<string> GetDisplayNames();
void GetNextFrame();
int GetScreenCount();
int GetSelectedScreenIndex();
Rectangle GetVirtualScreenBounds();
void Init();
void SetSelectedScreen(string displayName);
}
}

View File

@ -1,5 +1,4 @@
using Remotely.ScreenCast.Core.Capture;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using System;
@ -20,7 +19,7 @@ namespace Remotely.ScreenCast.Core.Models
ImageQuality = 75;
}
public bool AutoAdjustQuality { get; internal set; } = true;
public ICapturer Capturer { get; set; }
public IScreenCapturer Capturer { get; set; }
public bool DisconnectRequested { get; set; }
public EncoderParameters EncoderParams { get; private set; }
public bool FullScreenRefreshNeeded { get; internal set; }

View File

@ -17,14 +17,15 @@ using System.Diagnostics;
using System.Threading;
using System.Drawing.Imaging;
using Microsoft.Extensions.DependencyInjection;
using Remotely.ScreenCast.Core.Utilities;
namespace Remotely.ScreenCast.Core.Capture
namespace Remotely.ScreenCast.Core.Services
{
public class ScreenCasterBase
{
public async Task BeginScreenCasting(string viewerID,
string requesterName,
ICapturer capturer)
IScreenCapturer capturer)
{
var conductor = ServiceContainer.Instance.GetRequiredService<Conductor>();
var viewers = conductor.Viewers;
@ -59,9 +60,9 @@ namespace Remotely.ScreenCast.Core.Capture
await casterSocket.SendMachineName(Environment.MachineName, viewerID);
await casterSocket.SendScreenCount(
await casterSocket.SendScreenData(
capturer.SelectedScreen,
capturer.GetScreenCount(),
capturer.GetDisplayNames().ToArray(),
viewerID);
await casterSocket.SendScreenSize(capturer.CurrentScreenBounds.Width, capturer.CurrentScreenBounds.Height, viewerID);

View File

@ -10,7 +10,7 @@ using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Capture
namespace Remotely.ScreenCast.Core.Utilities
{
public class ImageUtils
{

View File

@ -3,7 +3,6 @@ using Remotely.ScreenCast.Core.Services;
using System;
using System.Threading;
using Remotely.ScreenCast.Linux.Services;
using Remotely.ScreenCast.Linux.Capture;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Interfaces;
using Microsoft.Extensions.DependencyInjection;
@ -60,15 +59,15 @@ namespace Remotely.ScreenCast.Linux
builder.AddConsole().AddEventLog();
});
serviceCollection.AddSingleton<IScreenCaster, LinuxScreenCaster>();
serviceCollection.AddSingleton<IScreenCaster, ScreenCasterLinux>();
serviceCollection.AddSingleton<IKeyboardMouseInput, X11Input>();
serviceCollection.AddSingleton<IClipboardService, LinuxClipboardService>();
serviceCollection.AddSingleton<IAudioCapturer, LinuxAudioCapturer>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceLinux>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerLinux>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<ChatHostService>();
serviceCollection.AddTransient<ICapturer, X11Capture>();
serviceCollection.AddTransient<IScreenCapturer, X11Capture>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}

View File

@ -5,7 +5,7 @@ using System.Text;
namespace Remotely.ScreenCast.Linux.Services
{
public class LinuxAudioCapturer : IAudioCapturer
public class AudioCapturerLinux : IAudioCapturer
{
public void ToggleAudio(bool toggleOn)
{

View File

@ -8,7 +8,7 @@ using System.Text;
namespace Remotely.ScreenCast.Linux.Services
{
public class LinuxClipboardService : IClipboardService
public class ClipboardServiceLinux : IClipboardService
{
public event EventHandler<string> ClipboardTextChanged;

View File

@ -1,9 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Capture;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Linux.Capture;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
@ -13,7 +11,7 @@ using System.Threading.Tasks;
namespace Remotely.ScreenCast.Linux.Services
{
public class LinuxScreenCaster : ScreenCasterBase, IScreenCaster
public class ScreenCasterLinux : ScreenCasterBase, IScreenCaster
{
public async Task BeginScreenCasting(ScreenCastRequest screenCastRequest)
{
@ -21,7 +19,7 @@ namespace Remotely.ScreenCast.Linux.Services
{
var conductor = ServiceContainer.Instance.GetRequiredService<Conductor>();
await conductor.CasterSocket.SendCursorChange(new CursorInfo(null, Point.Empty, "default"), new List<string>() { screenCastRequest.ViewerID });
_ = BeginScreenCasting(screenCastRequest.ViewerID, screenCastRequest.RequesterName, ServiceContainer.Instance.GetRequiredService<ICapturer>());
_ = BeginScreenCasting(screenCastRequest.ViewerID, screenCastRequest.RequesterName, ServiceContainer.Instance.GetRequiredService<IScreenCapturer>());
}
catch (Exception ex)
{

View File

@ -1,31 +1,42 @@
using Remotely.ScreenCast.Core.Capture;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Linux.X11Interop;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Remotely.ScreenCast.Linux.Capture
namespace Remotely.ScreenCast.Linux.Services
{
public class X11Capture : ICapturer
public class X11Capture : IScreenCapturer
{
private readonly Dictionary<string, int> x11Screens = new Dictionary<string, int>();
public X11Capture()
{
Display = LibX11.XOpenDisplay(null);
Init();
}
public event EventHandler<Rectangle> ScreenChanged;
public bool CaptureFullscreen { get; set; }
public Bitmap CurrentFrame { get; set; }
public Rectangle CurrentScreenBounds { get; private set; }
public IntPtr Display { get; private set; }
public Bitmap PreviousFrame { get; set; }
public event EventHandler<Rectangle> ScreenChanged;
public int SelectedScreen { get; private set; } = -1;
public string SelectedScreen { get; private set; } = "0";
public void Dispose()
{
//Graphic.Dispose();
CurrentFrame.Dispose();
PreviousFrame.Dispose();
}
public IEnumerable<string> GetDisplayNames() => x11Screens.Keys;
public void GetNextFrame()
{
try
@ -40,19 +51,13 @@ namespace Remotely.ScreenCast.Linux.Capture
Init();
}
}
public void Dispose()
{
//Graphic.Dispose();
CurrentFrame.Dispose();
PreviousFrame.Dispose();
}
public int GetScreenCount()
{
return LibX11.XScreenCount(Display);
}
public int GetSelectedScreenIndex() => x11Screens[SelectedScreen];
public Rectangle GetVirtualScreenBounds()
{
int width = 0;
@ -72,8 +77,14 @@ namespace Remotely.ScreenCast.Linux.Capture
{
try
{
x11Screens.Clear();
for (var i = 0; i < GetScreenCount(); i++)
{
x11Screens.Add(i.ToString(), i);
}
var defaultScreen = LibX11.XDefaultScreen(Display);
SetSelectedScreen(defaultScreen);
SetSelectedScreen(defaultScreen.ToString());
CurrentFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb);
PreviousFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb);
}
@ -82,25 +93,24 @@ namespace Remotely.ScreenCast.Linux.Capture
Logger.Write(ex);
}
}
public void SetSelectedScreen(int screenNumber)
public void SetSelectedScreen(string displayName)
{
if (screenNumber == SelectedScreen)
if (displayName == SelectedScreen)
{
return;
}
try
{
if (GetScreenCount() >= screenNumber + 1)
if (x11Screens.ContainsKey(displayName))
{
SelectedScreen = screenNumber;
SelectedScreen = displayName;
}
else
{
SelectedScreen = 0;
SelectedScreen = x11Screens.Keys.First();
}
var width = LibX11.XDisplayWidth(Display, SelectedScreen);
var height = LibX11.XDisplayHeight(Display, SelectedScreen);
var width = LibX11.XDisplayWidth(Display, x11Screens[SelectedScreen]);
var height = LibX11.XDisplayHeight(Display, x11Screens[SelectedScreen]);
CurrentScreenBounds = new Rectangle(0, 0, width, height);
CaptureFullscreen = true;
Init();
@ -115,7 +125,7 @@ namespace Remotely.ScreenCast.Linux.Capture
private void RefreshCurrentFrame()
{
var window = LibX11.XRootWindow(Display, SelectedScreen);
var window = LibX11.XRootWindow(Display, x11Screens[SelectedScreen]);
var imagePointer = LibX11.XGetImage(Display, window, 0, 0, CurrentScreenBounds.Width, CurrentScreenBounds.Height, ~0, 2);
var image = Marshal.PtrToStructure<LibX11.XImage>(imagePointer);

View File

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Remotely.ScreenCast.Linux.Capture;
using Remotely.ScreenCast.Core.Models;
namespace Remotely.ScreenCast.Linux.Services
@ -98,7 +97,7 @@ namespace Remotely.ScreenCast.Linux.Services
try
{
LibXtst.XTestFakeMotionEvent(Display,
viewer.Capturer.SelectedScreen,
viewer.Capturer.GetSelectedScreenIndex(),
(int)(viewer.Capturer.CurrentScreenBounds.Width * percentX),
(int)(viewer.Capturer.CurrentScreenBounds.Height * percentY),
0);

View File

@ -1,99 +0,0 @@
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;
using Remotely.Shared.Win32;
namespace Remotely.ScreenCast.Win.Capture
{
public class BitBltCapture : ICapturer
{
public BitBltCapture()
{
Init();
}
public event EventHandler<Rectangle> ScreenChanged;
public bool CaptureFullscreen { get; set; } = true;
public Bitmap CurrentFrame { get; set; }
public Rectangle CurrentScreenBounds { get; set; } = Screen.PrimaryScreen.Bounds;
public Bitmap PreviousFrame { get; set; }
public int SelectedScreen { get; private set; } = Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen);
private Graphics Graphic { get; set; }
public void Dispose()
{
Graphic.Dispose();
CurrentFrame.Dispose();
PreviousFrame.Dispose();
}
public void GetNextFrame()
{
try
{
Win32Interop.SwitchToInputDesktop();
PreviousFrame.Dispose();
PreviousFrame = (Bitmap)CurrentFrame.Clone();
Graphic.CopyFromScreen(CurrentScreenBounds.Left, CurrentScreenBounds.Top, 0, 0, new Size(CurrentScreenBounds.Width, CurrentScreenBounds.Height));
}
catch (Exception ex)
{
Logger.Write(ex);
Logger.Write("Capturer error. Trying to switch desktops in BitBltCapture.");
if (Win32Interop.SwitchToInputDesktop())
{
Win32Interop.GetCurrentDesktop(out var desktopName);
Logger.Write($"Switch to desktop {desktopName} after capture error in BitBltCapture.");
}
Init();
}
}
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;
}
if (GetScreenCount() >= screenNumber + 1)
{
SelectedScreen = screenNumber;
}
else
{
SelectedScreen = 0;
}
CurrentScreenBounds = Screen.AllScreens[SelectedScreen].Bounds;
CaptureFullscreen = true;
Init();
ScreenChanged?.Invoke(this, CurrentScreenBounds);
}
}
}

View File

@ -9,7 +9,6 @@ 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.Communication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -70,36 +69,15 @@ namespace Remotely.ScreenCast.Win
});
serviceCollection.AddSingleton<CursorIconWatcher>();
serviceCollection.AddSingleton<IScreenCaster, WinScreenCaster>();
serviceCollection.AddSingleton<IKeyboardMouseInput, WinInput>();
serviceCollection.AddSingleton<IClipboardService, WinClipboardService>();
serviceCollection.AddSingleton<IAudioCapturer, WinAudioCapturer>();
serviceCollection.AddSingleton<IScreenCaster, ScreenCasterWin>();
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputWin>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceWin>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerWin>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<ChatHostService>();
serviceCollection.AddTransient<ICapturer>(provider =>
{
try
{
var dxCapture = new DXCapture();
if (dxCapture.GetScreenCount() == Screen.AllScreens.Length)
{
return dxCapture;
}
else
{
Logger.Write("DX screen count doesn't match. Using CPU capturer instead.");
dxCapture.Dispose();
return new BitBltCapture();
}
}
catch (Exception ex)
{
Logger.Write(ex);
return new BitBltCapture();
}
});
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}

View File

@ -36,6 +36,10 @@
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="if $(ConfigurationName) == Debug (&#xD;&#xA; if $(PlatformName) == Any CPU (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x64 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x64\Debug\netcoreapp3.1\ScreenCast\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x64\Debug\netcoreapp3.1\ScreenCast\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x86 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x86\Debug\netcoreapp3.1\ScreenCast\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x86\Debug\netcoreapp3.1\ScreenCast\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA;)" />

View File

@ -13,7 +13,7 @@ using Remotely.ScreenCast.Core.Interfaces;
namespace Remotely.ScreenCast.Win.Services
{
public class WinAudioCapturer : IAudioCapturer
public class AudioCapturerWin : IAudioCapturer
{
private WasapiLoopbackCapture Capturer { get; set; }
private Stopwatch SendTimer { get; set; }

View File

@ -12,7 +12,7 @@ using System.Windows.Forms;
namespace Remotely.ScreenCast.Win.Services
{
public class WinClipboardService : IClipboardService
public class ClipboardServiceWin : IClipboardService
{
public event EventHandler<string> ClipboardTextChanged;

View File

@ -12,9 +12,9 @@ using Remotely.ScreenCast.Core;
namespace Remotely.ScreenCast.Win.Services
{
public class WinInput : IKeyboardMouseInput
public class KeyboardMouseInputWin : IKeyboardMouseInput
{
public WinInput()
public KeyboardMouseInputWin()
{
StartInputActionThread();
Application.ApplicationExit += Application_ApplicationExit;
@ -30,14 +30,14 @@ namespace Remotely.ScreenCast.Win.Services
private bool ShutdownStarted { get; set; }
public Tuple<double, double> GetAbsolutePercentFromRelativePercent(double percentX, double percentY, ICapturer capturer)
public Tuple<double, double> GetAbsolutePercentFromRelativePercent(double percentX, double percentY, IScreenCapturer 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<double, double>(absoluteX / capturer.GetVirtualScreenBounds().Width, absoluteY / capturer.GetVirtualScreenBounds().Height);
}
public Tuple<double, double> GetAbsolutePointFromRelativePercent(double percentX, double percentY, ICapturer capturer)
public Tuple<double, double> GetAbsolutePointFromRelativePercent(double percentX, double percentY, IScreenCapturer capturer)
{
var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left;
var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top;

View File

@ -1,4 +1,5 @@
// Much of this class was taken from the SharpDX samples.
// The DirectX capture code is based off examples from the
// SharpDX Samples at https://github.com/sharpdx/SharpDX.
// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
//
@ -29,6 +30,7 @@ using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
@ -36,13 +38,14 @@ using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace Remotely.ScreenCast.Win.Capture
namespace Remotely.ScreenCast.Win.Services
{
public class DXCapture : ICapturer
public class ScreenCapturerWin : IScreenCapturer
{
private readonly Dictionary<string, int> bitBltScreens = new Dictionary<string, int>();
private readonly Dictionary<string, OutputDuplication> directxScreens = new Dictionary<string, OutputDuplication>();
private Adapter1 adapter;
private SharpDX.Direct3D11.Device device;
private OutputDuplication duplicatedOutput;
private Factory1 factory;
private FeatureLevel[] featureLevels = new FeatureLevel[]
{
@ -57,24 +60,39 @@ namespace Remotely.ScreenCast.Win.Capture
FeatureLevel.Level_12_1
};
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<Rectangle> ScreenChanged;
public int SelectedScreen { get; private set; } = 0;
public DXCapture()
public ScreenCapturerWin()
{
Init();
}
public event EventHandler<Rectangle> ScreenChanged;
public bool CaptureFullscreen { get; set; } = true;
public Bitmap CurrentFrame { get; set; }
public Rectangle CurrentScreenBounds { get; private set; } = Screen.PrimaryScreen.Bounds;
public bool NeedsInit { get; set; } = true;
public Bitmap PreviousFrame { get; set; }
public string SelectedScreen { get; private set; } = Screen.PrimaryScreen.DeviceName;
private Graphics Graphic { get; set; }
public void Dispose()
{
foreach (var output in directxScreens.Values)
{
output.Dispose();
}
directxScreens.Clear();
device?.Dispose();
adapter?.Dispose();
factory?.Dispose();
CurrentFrame?.Dispose();
PreviousFrame?.Dispose();
}
public IEnumerable<string> GetDisplayNames() => Screen.AllScreens.Select(x => x.DeviceName);
public void GetNextFrame()
{
try
@ -90,12 +108,96 @@ namespace Remotely.ScreenCast.Win.Capture
Init();
}
Win32Interop.SwitchToInputDesktop();
PreviousFrame.Dispose();
PreviousFrame = (Bitmap)CurrentFrame.Clone();
if (directxScreens.ContainsKey(SelectedScreen))
{
try
{
GetDirectXFrame();
}
catch
{
GetBitBltFrame();
}
}
}
catch (Exception e)
{
Logger.Write(e);
NeedsInit = true;
}
}
public int GetScreenCount() => Screen.AllScreens.Length;
public int GetSelectedScreenIndex() => bitBltScreens[SelectedScreen];
public Rectangle GetVirtualScreenBounds() => SystemInformation.VirtualScreen;
public void Init()
{
CurrentFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb);
PreviousFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb);
InitBitBlt();
InitDirectX();
}
public void SetSelectedScreen(string displayName)
{
if (displayName == SelectedScreen)
{
return;
}
if (bitBltScreens.ContainsKey(displayName))
{
SelectedScreen = displayName;
}
else
{
SelectedScreen = bitBltScreens.Keys.First();
}
CurrentScreenBounds = Screen.AllScreens[bitBltScreens[SelectedScreen]].Bounds;
CaptureFullscreen = true;
NeedsInit = true;
ScreenChanged?.Invoke(this, CurrentScreenBounds);
}
private void GetBitBltFrame()
{
try
{
Win32Interop.SwitchToInputDesktop();
PreviousFrame.Dispose();
PreviousFrame = (Bitmap)CurrentFrame.Clone();
Graphic.CopyFromScreen(CurrentScreenBounds.Left, CurrentScreenBounds.Top, 0, 0, new Size(CurrentScreenBounds.Width, CurrentScreenBounds.Height));
}
catch (Exception ex)
{
Logger.Write(ex);
Logger.Write("Capturer error. Trying to switch desktops in BitBltCapture.");
if (Win32Interop.SwitchToInputDesktop())
{
Win32Interop.GetCurrentDesktop(out var desktopName);
Logger.Write($"Switch to desktop {desktopName} after capture error in BitBltCapture.");
}
NeedsInit = true;
}
}
private void GetDirectXFrame()
{
try
{
SharpDX.DXGI.Resource screenResource;
OutputDuplicateFrameInformation duplicateFrameInformation;
var duplicatedOutput = directxScreens[SelectedScreen];
// Try to get duplicated frame within given time is ms
var result = duplicatedOutput.TryAcquireNextFrame(100, out duplicateFrameInformation, out screenResource);
@ -121,16 +223,16 @@ namespace Remotely.ScreenCast.Win.Capture
//RawRectangle[] dirtyRectsBuffer = new RawRectangle[duplicateFrameInformation.TotalMetadataBufferSize];
//duplicatedOutput.GetFrameDirtyRects(duplicateFrameInformation.TotalMetadataBufferSize, dirtyRectsBuffer, out var dirtyRectsSizeRequired);
// copy resource into memory that can be accessed by the CPU
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
{
device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);
}
// Get the desktop capture texture
var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None);
var boundsRect = new Rectangle(0, 0, width, height);
// Copy pixels from screen capture Texture to GDI bitmap
@ -162,36 +264,17 @@ namespace Remotely.ScreenCast.Win.Capture
NeedsInit = true;
}
}
catch (Exception e)
}
private void InitBitBlt()
{
Graphic = Graphics.FromImage(CurrentFrame);
for (var i = 0; i < Screen.AllScreens.Length; i++)
{
Logger.Write(e);
NeedsInit = true;
bitBltScreens.Add(Screen.AllScreens[i].DeviceName, i);
}
}
public void Dispose()
{
duplicatedOutput?.Dispose();
output1?.Dispose();
output?.Dispose();
device?.Dispose();
adapter?.Dispose();
factory?.Dispose();
CurrentFrame?.Dispose();
PreviousFrame?.Dispose();
}
public int GetScreenCount()
{
return adapter.GetOutputCount();
}
public Rectangle GetVirtualScreenBounds()
{
return SystemInformation.VirtualScreen;
}
public void Init()
private void InitDirectX()
{
Dispose();
@ -201,72 +284,47 @@ namespace Remotely.ScreenCast.Win.Capture
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)
for (var i = 0; i < adapter.GetOutputCount(); i++)
{
SelectedScreen = 0;
using (var output = adapter.GetOutput(i))
using (var output1 = output.QueryInterface<Output1>())
{
// 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);
directxScreens.Add(output1.Description.DeviceName, output1.DuplicateOutput(device));
}
}
output = adapter.GetOutput(SelectedScreen);
output1 = output.QueryInterface<Output1>();
// 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;
}
}
}

View File

@ -6,20 +6,18 @@ 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;
using Remotely.Shared.Win32;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
namespace Remotely.ScreenCast.Win.Services
{
public class WinScreenCaster : ScreenCasterBase, IScreenCaster
public class ScreenCasterWin : ScreenCasterBase, IScreenCaster
{
public WinScreenCaster(CursorIconWatcher cursorIconWatcher)
public ScreenCasterWin(CursorIconWatcher cursorIconWatcher)
{
CursorIconWatcher = cursorIconWatcher;
}
@ -40,7 +38,7 @@ namespace Remotely.ScreenCast.Win.Services
var conductor = ServiceContainer.Instance.GetRequiredService<Conductor>();
await conductor.CasterSocket.SendCursorChange(CursorIconWatcher.GetCurrentCursor(), new List<string>() { screenCastRequest.ViewerID });
_ = BeginScreenCasting(screenCastRequest.ViewerID, screenCastRequest.RequesterName, ServiceContainer.Instance.GetRequiredService<ICapturer>());
_ = BeginScreenCasting(screenCastRequest.ViewerID, screenCastRequest.RequesterName, ServiceContainer.Instance.GetRequiredService<IScreenCapturer>());
}
}
}

View File

@ -135,6 +135,10 @@ namespace Remotely.Server.Services
public Task RemoteControl(string deviceID)
{
var targetDevice = DeviceSocketHub.ServiceConnections.FirstOrDefault(x => x.Value.ID == deviceID);
if (targetDevice.Value is null)
{
return Clients.Caller.SendAsync("DisplayMessage", $"The selected device is not online.", "Device is not online."); ;
}
if (DataService.DoesUserHaveAccessToDevice(deviceID, RemotelyUser))
{
var currentUsers = RCDeviceSocketHub.SessionInfoList.Count(x => x.Value.OrganizationID == RemotelyUser.OrganizationID);

View File

@ -132,9 +132,9 @@ namespace Remotely.Server.Services
return base.OnDisconnectedAsync(exception);
}
public Task SelectScreen(int screenIndex)
public Task SelectScreen(string displayName)
{
return RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("SelectScreen", screenIndex, Context.ConnectionId);
return RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("SelectScreen", displayName, Context.ConnectionId);
}
public Task SendClipboardTransfer(string transferText, bool typeText)

View File

@ -210,13 +210,13 @@ namespace Remotely.Server.Services
return RCBrowserHub.Clients.Client(rcBrowserHubConnectionID).SendAsync("ScreenCapture", captureBytes, left, top, width, height, captureTime);
}
public Task SendScreenCountToBrowser(int primaryScreenIndex, int screenCount, string rcBrowserHubConnectionID)
public Task SendScreenDataToBrowser(string selectedDisplay, string[] displayNames, string browserHubConnectionId)
{
lock (ViewerList)
{
ViewerList.Add(rcBrowserHubConnectionID);
ViewerList.Add(browserHubConnectionId);
}
return RCBrowserHub.Clients.Client(rcBrowserHubConnectionID).SendAsync("ScreenCount", primaryScreenIndex, screenCount);
return RCBrowserHub.Clients.Client(browserHubConnectionId).SendAsync("ScreenData", selectedDisplay, displayNames);
}
public Task SendScreenSize(int width, int height, string rcBrowserHubConnectionID)

View File

@ -54,8 +54,8 @@ export class RCBrowserSockets {
SendLatencyUpdate(sentTime, bytesReceived) {
this.Connection.invoke("SendLatencyUpdate", sentTime, bytesReceived);
}
SendSelectScreen(index) {
this.Connection.invoke("SelectScreen", index);
SendSelectScreen(displayName) {
this.Connection.invoke("SelectScreen", displayName);
}
SendMouseMove(percentX, percentY) {
this.Connection.invoke("MouseMove", percentX, percentY);
@ -120,18 +120,18 @@ export class RCBrowserSockets {
Remotely.ClipboardWatcher.SetClipboardText(clipboardText);
PopupMessage("Clipboard updated.");
});
hubConnection.on("ScreenCount", (primaryScreenIndex, screenCount) => {
hubConnection.on("ScreenData", (selectedDisplay, displayNames) => {
document.querySelector("#screenSelectBar").innerHTML = "";
for (let i = 0; i < screenCount; i++) {
for (let i = 0; i < displayNames.length; i++) {
var button = document.createElement("button");
button.innerHTML = `Monitor ${i}`;
button.classList.add("horizontal-bar-button");
if (i == primaryScreenIndex) {
if (displayNames[i] == selectedDisplay) {
button.classList.add("toggled");
}
document.querySelector("#screenSelectBar").appendChild(button);
button.onclick = (ev) => {
this.SendSelectScreen(i);
this.SendSelectScreen(displayNames[i]);
document.querySelectorAll("#screenSelectBar .horizontal-bar-button").forEach(button => {
button.classList.remove("toggled");
});

File diff suppressed because one or more lines are too long

View File

@ -68,8 +68,8 @@ export class RCBrowserSockets {
SendLatencyUpdate(sentTime: Date, bytesReceived: number) {
this.Connection.invoke("SendLatencyUpdate", sentTime, bytesReceived);
}
SendSelectScreen(index: number) {
this.Connection.invoke("SelectScreen", index);
SendSelectScreen(displayName: string) {
this.Connection.invoke("SelectScreen", displayName);
}
SendMouseMove(percentX: number, percentY: number): any {
this.Connection.invoke("MouseMove", percentX, percentY);
@ -134,18 +134,18 @@ export class RCBrowserSockets {
Remotely.ClipboardWatcher.SetClipboardText(clipboardText);
PopupMessage("Clipboard updated.");
});
hubConnection.on("ScreenCount", (primaryScreenIndex: number, screenCount: number) => {
hubConnection.on("ScreenData", (selectedDisplay: string, displayNames: string[]) => {
document.querySelector("#screenSelectBar").innerHTML = "";
for (let i = 0; i < screenCount; i++) {
for (let i = 0; i < displayNames.length; i++) {
var button = document.createElement("button");
button.innerHTML = `Monitor ${i}`;
button.classList.add("horizontal-bar-button");
if (i == primaryScreenIndex) {
if (displayNames[i] == selectedDisplay) {
button.classList.add("toggled");
}
document.querySelector("#screenSelectBar").appendChild(button);
button.onclick = (ev: MouseEvent) => {
this.SendSelectScreen(i);
this.SendSelectScreen(displayNames[i]);
document.querySelectorAll("#screenSelectBar .horizontal-bar-button").forEach(button => {
button.classList.remove("toggled");
});