mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Begin building out Linux screencaster.
This commit is contained in:
parent
b4fcee8c54
commit
7155a99e7b
14
Remotely.sln
14
Remotely.sln
@ -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
|
||||
|
||||
21
Remotely_ScreenCast_Linux/Capture/ICapturer.cs
Normal file
21
Remotely_ScreenCast_Linux/Capture/ICapturer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
198
Remotely_ScreenCast_Linux/Capture/ImageUtils.cs
Normal file
198
Remotely_ScreenCast_Linux/Capture/ImageUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Remotely_ScreenCast_Linux/Capture/ScreenCaster.cs
Normal file
137
Remotely_ScreenCast_Linux/Capture/ScreenCaster.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Remotely_ScreenCast_Linux/Capture/X11Capture.cs
Normal file
96
Remotely_ScreenCast_Linux/Capture/X11Capture.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Remotely_ScreenCast_Linux/Enums/AppMode.cs
Normal file
14
Remotely_ScreenCast_Linux/Enums/AppMode.cs
Normal 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
|
||||
}
|
||||
}
|
||||
24
Remotely_ScreenCast_Linux/Models/CursorInfo.cs
Normal file
24
Remotely_ScreenCast_Linux/Models/CursorInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
5
Remotely_ScreenCast_Linux/Models/CursorInfo.d.ts
vendored
Normal file
5
Remotely_ScreenCast_Linux/Models/CursorInfo.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
interface CursorInfo {
|
||||
ImageBytes: any[];
|
||||
HotSpot: any;
|
||||
CssOverride: string;
|
||||
}
|
||||
41
Remotely_ScreenCast_Linux/Models/Viewer.cs
Normal file
41
Remotely_ScreenCast_Linux/Models/Viewer.cs
Normal 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; }
|
||||
|
||||
}
|
||||
}
|
||||
151
Remotely_ScreenCast_Linux/Program.cs
Normal file
151
Remotely_ScreenCast_Linux/Program.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
39
Remotely_ScreenCast_Linux/Remotely_ScreenCast_Linux.csproj
Normal file
39
Remotely_ScreenCast_Linux/Remotely_ScreenCast_Linux.csproj
Normal 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>
|
||||
228
Remotely_ScreenCast_Linux/Sockets/MessageHandlers.cs
Normal file
228
Remotely_ScreenCast_Linux/Sockets/MessageHandlers.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Remotely_ScreenCast_Linux/Sockets/OutgoingMessages.cs
Normal file
70
Remotely_ScreenCast_Linux/Sockets/OutgoingMessages.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Remotely_ScreenCast_Linux/Utilities/Logger.cs
Normal file
76
Remotely_ScreenCast_Linux/Utilities/Logger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Remotely_ScreenCast_Linux/X11/X11Interop.cs
Normal file
10
Remotely_ScreenCast_Linux/X11/X11Interop.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Remotely_ScreenCast_Linux.X11
|
||||
{
|
||||
public class X11Interop
|
||||
{
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user