Begin building out Linux screencaster.

This commit is contained in:
Jared Goodwin 2019-03-26 07:57:45 -07:00
parent b4fcee8c54
commit 7155a99e7b
15 changed files with 1124 additions and 0 deletions

View File

@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Remotely_ScreenCast", "Remo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Remotely_Desktop", "Remotely_Desktop\Remotely_Desktop.csproj", "{486A238C-387B-49C5-A361-B86ACDB2572A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Remotely_ScreenCast_Linux", "Remotely_ScreenCast_Linux\Remotely_ScreenCast_Linux.csproj", "{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -103,6 +105,18 @@ Global
{486A238C-387B-49C5-A361-B86ACDB2572A}.Release|x64.Build.0 = Release|Any CPU
{486A238C-387B-49C5-A361-B86ACDB2572A}.Release|x86.ActiveCfg = Release|Any CPU
{486A238C-387B-49C5-A361-B86ACDB2572A}.Release|x86.Build.0 = Release|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Debug|x64.Build.0 = Debug|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Debug|x86.Build.0 = Debug|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Release|Any CPU.Build.0 = Release|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Release|x64.ActiveCfg = Release|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Release|x64.Build.0 = Release|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Release|x86.ActiveCfg = Release|Any CPU
{9DB9F02E-9952-4A61-860A-CE2C6CE2A7E1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Capture
{
public interface ICapturer : IDisposable
{
bool CaptureFullscreen { get; set; }
Bitmap CurrentFrame { get; set; }
Rectangle CurrentScreenBounds { get; }
Bitmap PreviousFrame { get; set; }
EventHandler<Rectangle> ScreenChanged { get; set; }
int SelectedScreen { get; set; }
void Capture();
void Init();
}
}

View File

@ -0,0 +1,198 @@
using Remotely_ScreenCast_Linux.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.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Capture
{
public class ImageUtils
{
public static byte[] EncodeBitmap(Bitmap bitmap)
{
using (var ms = new MemoryStream())
{
bitmap.Save(ms, ImageFormat.Jpeg);
return ms.ToArray();
}
}
public static Rectangle GetDiffArea(Bitmap currentFrame, Bitmap previousFrame, bool captureFullscreen)
{
if (captureFullscreen)
{
return new Rectangle(new Point(0, 0), currentFrame.Size);
}
if (currentFrame.Height != previousFrame.Height || currentFrame.Width != previousFrame.Width)
{
throw new Exception("Bitmaps are not of equal dimensions.");
}
if (!Bitmap.IsAlphaPixelFormat(currentFrame.PixelFormat) || !Bitmap.IsAlphaPixelFormat(previousFrame.PixelFormat) ||
!Bitmap.IsCanonicalPixelFormat(currentFrame.PixelFormat) || !Bitmap.IsCanonicalPixelFormat(previousFrame.PixelFormat))
{
throw new Exception("Bitmaps must be 32 bits per pixel and contain alpha channel.");
}
var width = currentFrame.Width;
var height = currentFrame.Height;
int left = int.MaxValue;
int top = int.MaxValue;
int right = int.MinValue;
int bottom = int.MinValue;
BitmapData bd1 = null;
BitmapData bd2 = null;
try
{
bd1 = previousFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, currentFrame.PixelFormat);
bd2 = currentFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, previousFrame.PixelFormat);
var bytesPerPixel = Bitmap.GetPixelFormatSize(currentFrame.PixelFormat) / 8;
var totalSize = bd1.Height * bd1.Width * bytesPerPixel;
unsafe
{
byte* scan1 = (byte*)bd1.Scan0.ToPointer();
byte* scan2 = (byte*)bd2.Scan0.ToPointer();
for (int counter = 0; counter < totalSize - bytesPerPixel; counter += bytesPerPixel)
{
byte* data1 = scan1 + counter;
byte* data2 = scan2 + counter;
if (data1[0] != data2[0] ||
data1[1] != data2[1] ||
data1[2] != data2[2] ||
data1[3] != data2[3])
{
// Change was found.
var pixel = counter / 4;
var row = (int)Math.Floor((double)pixel / bd1.Width);
var column = pixel % bd1.Width;
if (row < top)
{
top = row;
}
if (row > bottom)
{
bottom = row;
}
if (column < left)
{
left = column;
}
if (column > right)
{
right = column;
}
}
}
}
if (left < right && top < bottom)
{
// Bounding box is valid.
left = Math.Max(left - 20, 0);
top = Math.Max(top - 20, 0);
right = Math.Min(right + 20, width);
bottom = Math.Min(bottom + 20, height);
return new Rectangle(left, top, right - left, bottom - top);
}
else
{
return Rectangle.Empty;
}
}
catch
{
return Rectangle.Empty;
}
finally
{
try
{
currentFrame.UnlockBits(bd1);
previousFrame.UnlockBits(bd2);
bd1 = null;
bd2 = null;
}
catch { }
}
}
public static Bitmap GetImageDiff(Bitmap currentFrame, Bitmap previousFrame, bool captureFullscreen)
{
if (captureFullscreen)
{
return (Bitmap)currentFrame.Clone();
}
if (currentFrame.Height != previousFrame.Height || currentFrame.Width != previousFrame.Width)
{
throw new Exception("Bitmaps are not of equal dimensions.");
}
if (!Bitmap.IsAlphaPixelFormat(currentFrame.PixelFormat) || !Bitmap.IsAlphaPixelFormat(previousFrame.PixelFormat) ||
!Bitmap.IsCanonicalPixelFormat(currentFrame.PixelFormat) || !Bitmap.IsCanonicalPixelFormat(previousFrame.PixelFormat))
{
throw new Exception("Bitmaps must be 32 bits per pixel and contain alpha channel.");
}
var width = currentFrame.Width;
var height = currentFrame.Height;
var mergedFrame = new Bitmap(width, height);
var bd1 = previousFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, currentFrame.PixelFormat);
var bd2 = currentFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, previousFrame.PixelFormat);
var bd3 = mergedFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, currentFrame.PixelFormat);
// Get the address of the first line.
IntPtr ptr1 = bd1.Scan0;
IntPtr ptr2 = bd2.Scan0;
IntPtr ptr3 = bd3.Scan0;
// Declare an array to hold the bytes of the bitmap.
int arraySize = Math.Abs(bd1.Stride) * currentFrame.Height;
var rgbValues1 = new byte[arraySize];
var rgbValues2 = new byte[arraySize];
var rgbValues3 = new byte[arraySize];
// Copy the RGBA values into the array.
Marshal.Copy(ptr1, rgbValues1, 0, arraySize);
Marshal.Copy(ptr2, rgbValues2, 0, arraySize);
// Check RGBA value for each pixel.
for (int counter = 0; counter < rgbValues2.Length - 4; counter += 4)
{
if (rgbValues1[counter] != rgbValues2[counter] ||
rgbValues1[counter + 1] != rgbValues2[counter + 1] ||
rgbValues1[counter + 2] != rgbValues2[counter + 2] ||
rgbValues1[counter + 3] != rgbValues2[counter + 3])
{
// Change was found.
rgbValues3[counter] = rgbValues2[counter];
rgbValues3[counter + 1] = rgbValues2[counter + 1];
rgbValues3[counter + 2] = rgbValues2[counter + 2];
rgbValues3[counter + 3] = rgbValues2[counter + 3];
}
}
// Copy merged frame to bitmap.
Marshal.Copy(rgbValues3, 0, ptr3, rgbValues3.Length);
previousFrame.UnlockBits(bd1);
currentFrame.UnlockBits(bd2);
mergedFrame.UnlockBits(bd3);
return mergedFrame;
}
}
}

View File

@ -0,0 +1,137 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast_Linux;
using Remotely_ScreenCast_Linux.Capture;
using Remotely_ScreenCast_Linux.Models;
using Remotely_ScreenCast_Linux.Sockets;
using Remotely_ScreenCast_Linux.Utilities;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Capture
{
public class ScreenCaster
{
public static async void BeginScreenCasting(string viewerID,
string requesterName,
OutgoingMessages outgoingMessages)
{
ICapturer capturer;
capturer = new X11Capture();
capturer.Init();
Viewer viewer;
byte[] encodedImageBytes;
var success = false;
Logger.Write($"Starting screen cast. Requester: {requesterName}. Viewer ID: {viewerID}. App Mode: {Program.Mode}");
viewer = new Viewer()
{
Capturer = capturer,
DisconnectRequested = false,
Name = requesterName,
ViewerConnectionID = viewerID,
HasControl = true,
ImageQuality = 1
};
while (!success)
{
success = Program.Viewers.TryAdd(viewerID, viewer);
}
if (Program.Mode == Enums.AppMode.Normal)
{
Program.ViewerAdded?.Invoke(null, viewer);
}
await outgoingMessages.SendScreenCount(
capturer.SelectedScreen,
//Screen.AllScreens.Length,
1,
viewerID);
await outgoingMessages.SendScreenSize(capturer.CurrentScreenBounds.Width, capturer.CurrentScreenBounds.Height, viewerID);
capturer.ScreenChanged += async (sender, bounds) =>
{
await outgoingMessages.SendScreenSize(bounds.Width, bounds.Height, viewerID);
};
//await outgoingMessages.SendCursorChange(CursorIconWatcher.Current.GetCurrentCursor(), new List<string>() { viewerID });
while (!viewer.DisconnectRequested)
{
try
{
while (viewer.PendingFrames > 10)
{
await Task.Delay(1);
}
capturer.Capture();
var diffArea = ImageUtils.GetDiffArea(capturer.CurrentFrame, capturer.PreviousFrame, capturer.CaptureFullscreen);
if (diffArea.IsEmpty)
{
continue;
}
using (var newImage = capturer.CurrentFrame.Clone(diffArea, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
if (capturer.CaptureFullscreen)
{
capturer.CaptureFullscreen = false;
}
encodedImageBytes = ImageUtils.EncodeBitmap(newImage);
if (encodedImageBytes?.Length > 0)
{
await outgoingMessages.SendScreenCapture(encodedImageBytes, viewerID, diffArea.Left, diffArea.Top, diffArea.Width, diffArea.Height, DateTime.UtcNow);
viewer.PendingFrames++;
}
GC.Collect();
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
Logger.Write($"Ended screen cast. Requester: {requesterName}. Viewer ID: {viewerID}.");
success = false;
while (!success)
{
success = Program.Viewers.TryRemove(viewerID, out _);
}
capturer.Dispose();
// Close if no one is viewing.
if (Program.Viewers.Count == 0 && Program.Mode == Enums.AppMode.Unattended)
{
Environment.Exit(0);
}
}
public static Tuple<double, double> GetAbsolutePercentFromRelativePercent(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<double, double>(800, 600);
//return new Tuple<double, double>(absoluteX / SystemInformation.VirtualScreen.Width, absoluteY / SystemInformation.VirtualScreen.Height);
}
public static Tuple<double, double> 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<double, double>(absoluteX, absoluteY);
}
}
}

View File

@ -0,0 +1,96 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using Remotely_ScreenCast_Linux.Utilities;
using System.Threading;
namespace Remotely_ScreenCast_Linux.Capture
{
public class X11Capture : ICapturer
{
public Bitmap CurrentFrame { get; set; }
public Bitmap PreviousFrame { get; set; }
public bool IsCapturing { get; set; }
public bool CaptureFullscreen { get; set; } = true;
public int PauseForMilliseconds { get; set; }
public EventHandler<Rectangle> ScreenChanged { get; set; }
private object ScreenLock { get; } = new object();
public int SelectedScreen
{
get
{
return selectedScreen;
}
set
{
if (value == selectedScreen)
{
return;
}
lock (ScreenLock)
{
//if (Screen.AllScreens.Length >= value + 1)
//{
// selectedScreen = value;
//}
//else
//{
// selectedScreen = 0;
//}
//CurrentScreenBounds = Screen.AllScreens[selectedScreen].Bounds;
CaptureFullscreen = true;
Init();
ScreenChanged?.Invoke(this, CurrentScreenBounds);
}
}
}
//public Rectangle CurrentScreenBounds { get; set; } = Screen.PrimaryScreen.Bounds;
//private int selectedScreen = Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen);
public Rectangle CurrentScreenBounds { get; set; }
private int selectedScreen;
private Graphics graphic;
public X11Capture()
{
Init();
}
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 Capture()
{
try
{
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();
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Enums
{
public enum AppMode
{
Unattended,
Normal
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Models
{
public class CursorInfo
{
public CursorInfo(byte[] imageBytes, Point hotspot, string cssOverride = null)
{
ImageBytes = imageBytes;
HotSpot = hotspot;
CssOverride = cssOverride;
}
public byte[] ImageBytes { get; set; }
public Point HotSpot { get; set; }
public string CssOverride { get; set; }
}
}

View File

@ -0,0 +1,5 @@
interface CursorInfo {
ImageBytes: any[];
HotSpot: any;
CssOverride: string;
}

View File

@ -0,0 +1,41 @@
using Remotely_ScreenCast_Linux.Capture;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Models
{
public class Viewer
{
public string ViewerConnectionID { get; set; }
public string Name { get; set; }
public ICapturer Capturer { get; set; }
public bool DisconnectRequested { get; set; }
public bool HasControl { get; set; }
public double Latency { get; set; } = 1;
public int PendingFrames { get; set; }
private double imageQuality = 1;
public double ImageQuality
{
get
{
return imageQuality;
}
set
{
if (imageQuality > 1 || imageQuality < 0)
{
return;
}
imageQuality = value;
}
}
public bool FullScreenRefreshNeeded { get; internal set; }
}
}

View File

@ -0,0 +1,151 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Remotely_ScreenCast_Linux;
using Remotely_ScreenCast_Linux.Capture;
using Remotely_ScreenCast_Linux.Enums;
using Remotely_ScreenCast_Linux.Models;
using Remotely_ScreenCast_Linux.Sockets;
using Remotely_ScreenCast_Linux.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux
{
public class Program
{
public static AppMode Mode { get; private set; }
public static string RequesterID { get; private set; }
public static string ServiceID { get; private set; }
public static string Host { get; private set; }
public static HubConnection Connection { get; private set; }
public static OutgoingMessages OutgoingMessages { get; private set; }
public static ConcurrentDictionary<string, Viewer> Viewers { get; } = new ConcurrentDictionary<string, Viewer>();
public static Dictionary<string, string> ArgDict { get; set; }
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
ProcessArgs(args);
Connect().Wait();
SetEventHandlers();
HandleConnection().Wait();
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public static async Task HandleConnection()
{
OutgoingMessages.SendDeviceInfo(ServiceID, Environment.MachineName).Wait();
if (Mode == AppMode.Unattended)
{
StartWaitForViewerTimer();
}
else if (Mode == AppMode.Normal)
{
OutgoingMessages.GetSessionID().Wait();
}
while (true)
{
await Task.Delay(100);
}
}
public static void SetEventHandlers()
{
OutgoingMessages = new OutgoingMessages(Connection);
MessageHandlers.ApplyConnectionHandlers(Connection, OutgoingMessages);
//CursorIconWatcher.Current.OnChange += CursorIconWatcher_OnChange;
}
public static Task Connect()
{
Connection = new HubConnectionBuilder()
.WithUrl($"{Host}/RCDeviceHub")
.AddMessagePackProtocol()
.Build();
return Connection.StartAsync();
}
private static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
{
await OutgoingMessages.SendCursorChange(cursor, Viewers.Keys.ToList());
}
private static void StartWaitForViewerTimer()
{
var timer = new System.Timers.Timer(10000);
timer.AutoReset = false;
timer.Elapsed += (sender, arg) =>
{
// Shut down if no viewers have connected within 10 seconds.
if (Viewers.Count == 0)
{
Logger.Write("No viewers connected after 10 seconds. Shutting down.");
Environment.Exit(0);
}
};
timer.Start();
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
public static void ProcessArgs(string[] args)
{
ArgDict = new Dictionary<string, string>();
for (var i = 0; i < args.Length; i += 2)
{
var key = args?[i];
if (key != null)
{
key = key.Trim().Replace("-", "").ToLower();
var value = args?[i + 1];
if (value != null)
{
ArgDict[key] = args[i + 1].Trim();
}
}
}
Mode = (AppMode)Enum.Parse(typeof(AppMode), ArgDict["mode"]);
Host = ArgDict["host"];
if (Mode == AppMode.Unattended)
{
RequesterID = ArgDict["requester"];
ServiceID = ArgDict["serviceid"];
}
}
public static EventHandler<string> SessionIDChanged { get; set; }
public static EventHandler<string> ViewerRemoved { get; set; }
public static EventHandler<Viewer> ViewerAdded { get; set; }
public static EventHandler<Tuple<string, string>> ScreenCastRequested { get; set; }
}
}

View File

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AssemblyName>Remotely_ScreenCast</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="Models\CursorInfo.cs">
<LastGenOutput>CursorInfo.d.ts</LastGenOutput>
<Generator>DtsGenerator</Generator>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Models\CursorInfo.d.ts">
<DependentUpon>CursorInfo.cs</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,228 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast_Linux.Capture;
using Remotely_ScreenCast_Linux.Utilities;
using Remotely_ScreenCast_Linux;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using System.Diagnostics;
using Remotely_ScreenCast_Linux.Models;
using Remotely_ScreenCast_Linux.Capture;
namespace Remotely_ScreenCast_Linux.Sockets
{
public class MessageHandlers
{
public static void ApplyConnectionHandlers(HubConnection hubConnection, OutgoingMessages outgoingMessages)
{
hubConnection.Closed += (ex) =>
{
Logger.Write($"Error: {ex.Message}");
Environment.Exit(1);
return Task.CompletedTask;
};
hubConnection.On("GetScreenCast", (string viewerID, string requesterName) =>
{
try
{
ScreenCaster.BeginScreenCasting(viewerID, requesterName, outgoingMessages);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
hubConnection.On("RequestScreenCast", (string viewerID, string requesterName) =>
{
Program.ScreenCastRequested?.Invoke(null, new Tuple<string, string>(viewerID, requesterName));
});
hubConnection.On("KeyDown", (string key, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//var keyCode = Win32Interop.ConvertJavaScriptKeyToVirtualKey(key);
//Win32Interop.SendKeyDown((User32.VirtualKey)keyCode);
}
});
hubConnection.On("KeyUp", (string key, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//var keyCode = Win32Interop.ConvertJavaScriptKeyToVirtualKey(key);
//Win32Interop.SendKeyUp((User32.VirtualKey)keyCode);
}
});
hubConnection.On("KeyPress", async (string key, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//var keyCode = Win32Interop.ConvertJavaScriptKeyToVirtualKey(key);
//Win32Interop.SendKeyDown((User32.VirtualKey)keyCode);
//await Task.Delay(1);
//Win32Interop.SendKeyUp((User32.VirtualKey)keyCode);
}
});
hubConnection.On("MouseMove", (double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
//Win32Interop.SendMouseMove(mousePoint.Item1, mousePoint.Item2);
}
});
hubConnection.On("MouseDown", (int button, double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
if (button == 0)
{
//Win32Interop.SendLeftMouseDown((int)mousePoint.Item1, (int)mousePoint.Item2);
}
else if (button == 2)
{
//Win32Interop.SendRightMouseDown((int)mousePoint.Item1, (int)mousePoint.Item2);
}
}
});
hubConnection.On("MouseUp", (int button, double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
if (button == 0)
{
//Win32Interop.SendLeftMouseUp((int)mousePoint.Item1, (int)mousePoint.Item2);
}
else if (button == 2)
{
//Win32Interop.SendRightMouseUp((int)mousePoint.Item1, (int)mousePoint.Item2);
}
}
});
hubConnection.On("MouseWheel", (double deltaX, double deltaY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//Win32Interop.SendMouseWheel(-(int)deltaY);
}
});
hubConnection.On("ViewerDisconnected", async (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.DisconnectRequested = true;
}
await hubConnection.InvokeAsync("ViewerDisconnected", viewerID);
Program.ViewerRemoved?.Invoke(null, viewerID);
});
hubConnection.On("LatencyUpdate", (double latency, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.PendingFrames--;
viewer.Latency = latency;
}
});
hubConnection.On("SelectScreen", (int screenIndex, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.Capturer.SelectedScreen = screenIndex;
}
});
hubConnection.On("TouchDown", (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendLeftMouseDown(point.X, point.Y);
}
});
hubConnection.On("LongPress", (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendRightMouseDown(point.X, point.Y);
//Win32Interop.SendRightMouseUp(point.X, point.Y);
}
});
hubConnection.On("TouchMove", (double moveX, double moveY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendMouseMove(point.X + moveX, point.Y + moveY);
}
});
hubConnection.On("TouchUp", (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendLeftMouseUp(point.X, point.Y);
}
});
hubConnection.On("Tap", (double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
//Win32Interop.SendLeftMouseDown((int)mousePoint.Item1, (int)mousePoint.Item2);
//Win32Interop.SendLeftMouseUp((int)mousePoint.Item1, (int)mousePoint.Item2);
}
});
hubConnection.On("SharedFileIDs", (List<string> fileIDs) => {
fileIDs.ForEach(id =>
{
var url = $"{Program.Host}/API/FileSharing/{id}";
var webRequest = WebRequest.CreateHttp(url);
var response = webRequest.GetResponse();
var contentDisp = response.Headers["Content-Disposition"];
var fileName = contentDisp
.Split(";".ToCharArray())
.FirstOrDefault(x => x.Trim().StartsWith("filename"))
.Split("=".ToCharArray())[1];
var legalChars = fileName.ToCharArray().Where(x => !Path.GetInvalidFileNameChars().Any(y => x == y));
fileName = new string(legalChars.ToArray());
var dirPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "RemotelySharedFiles")).FullName;
var filePath = Path.Combine(dirPath, fileName);
using (var fs = new FileStream(filePath, FileMode.Create))
{
using (var rs = response.GetResponseStream())
{
rs.CopyTo(fs);
}
}
Process.Start("explorer.exe", dirPath);
});
});
hubConnection.On("SessionID", (string sessionID) =>
{
Program.SessionIDChanged?.Invoke(null, sessionID);
});
}
}
}

View File

@ -0,0 +1,70 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast_Linux.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Sockets
{
public class OutgoingMessages
{
public OutgoingMessages(HubConnection hubConnection)
{
Connection = hubConnection;
}
private HubConnection Connection { get; }
public async Task SendScreenSize(int width, int height, string viewerID)
{
await Connection.SendAsync("SendScreenSize", width, height, viewerID);
}
public async Task SendScreenCapture(byte[] captureBytes, string viewerID, int left, int top, int width, int height, DateTime captureTime)
{
await Connection.SendAsync("SendScreenCapture", captureBytes, viewerID, left, top, width, height, captureTime);
}
internal async Task SendScreenCount(int primaryScreenIndex, int screenCount, string viewerID)
{
await Connection.SendAsync("SendScreenCountToBrowser", primaryScreenIndex, screenCount, viewerID);
}
public async Task NotifyRequesterUnattendedReady(string requesterID)
{
await Connection.SendAsync("NotifyRequesterUnattendedReady", requesterID);
}
public async Task SendCursorChange(CursorInfo cursor, List<string> viewerIDs)
{
await Connection.SendAsync("SendCursorChange", cursor, viewerIDs);
}
internal async Task NotifyViewersRelaunchedScreenCasterReady(string[] viewerIDs)
{
await Connection.SendAsync("NotifyViewersRelaunchedScreenCasterReady", viewerIDs);
}
internal async Task SendDeviceInfo(string serviceID, string machineName)
{
await Connection.SendAsync("ReceiveDeviceInfo", serviceID, machineName);
}
internal async Task SendConnectionFailedToViewers(List<string> viewerIDs)
{
await Connection.SendAsync("SendConnectionFailedToViewers", viewerIDs);
}
internal async Task GetSessionID()
{
await Connection.SendAsync("GetSessionID");
}
public async Task SendViewerRemoved(string viewerID)
{
await Connection.SendAsync("SendViewerRemoved", viewerID);
}
}
}

View File

@ -0,0 +1,76 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast_Linux.Utilities
{
public static class Logger
{
public static void Write(string message)
{
var path = Path.Combine(Path.GetTempPath(), "Remotely_Logs.txt");
var jsoninfo = new
{
Type = "Info",
Timestamp = DateTime.Now.ToString(),
Message = message
};
if (File.Exists(path))
{
var fi = new FileInfo(path);
while (fi.Length > 1000000)
{
var content = File.ReadAllLines(path);
File.WriteAllLines(path, content.Skip(10));
fi = new FileInfo(path);
}
}
try
{
File.AppendAllText(path, JsonConvert.SerializeObject(jsoninfo) + Environment.NewLine);
}
catch
{
Task.Delay(1000).ContinueWith((Task task) =>
{
Write(message);
});
}
}
public static void Write(Exception ex)
{
var exception = ex;
var path = Path.Combine(Path.GetTempPath(), "Remotely_Logs.txt");
while (exception != null)
{
var jsonError = new
{
Type = "Error",
Timestamp = DateTime.Now.ToString(),
Message = exception?.Message,
Source = exception?.Source,
StackTrace = exception?.StackTrace,
};
if (File.Exists(path))
{
var fi = new FileInfo(path);
while (fi.Length > 1000000)
{
var content = File.ReadAllLines(path);
File.WriteAllLines(path, content.Skip(10));
fi = new FileInfo(path);
}
}
File.AppendAllText(path, JsonConvert.SerializeObject(jsonError) + Environment.NewLine);
exception = exception.InnerException;
}
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Remotely_ScreenCast_Linux.X11
{
public class X11Interop
{
}
}