Beginning work on Linux remote control client.

This commit is contained in:
Jared Goodwin 2019-03-27 15:59:13 -07:00
parent aa05a9e049
commit a43e7cba27
66 changed files with 1449 additions and 1863 deletions

View File

@ -29,11 +29,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Remotely_Server", "Remotely
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Remotely_Library", "Remotely_Library\Remotely_Library.csproj", "{A9E1BA7A-6080-4DAC-9B29-6DC8437150EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Remotely_ScreenCast", "Remotely_ScreenCast\Remotely_ScreenCast.csproj", "{2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Remotely_ScreenCast.Win", "Remotely_ScreenCast.Win\Remotely_ScreenCast.Win.csproj", "{2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}"
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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Remotely_ScreenCast.Linux", "Remotely_ScreenCast.Linux\Remotely_ScreenCast.Linux.csproj", "{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Remotely_ScreenCast.Core", "Remotely_ScreenCast.Core\Remotely_ScreenCast.Core.csproj", "{B04A1728-2E87-491E-BC7F-F575A1754DEF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -105,18 +107,30 @@ 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
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x64.ActiveCfg = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x64.Build.0 = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x86.ActiveCfg = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x86.Build.0 = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|Any CPU.Build.0 = Release|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x64.ActiveCfg = Release|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x64.Build.0 = Release|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x86.ActiveCfg = Release|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x86.Build.0 = Release|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Debug|x64.ActiveCfg = Debug|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Debug|x64.Build.0 = Debug|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Debug|x86.ActiveCfg = Debug|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Debug|x86.Build.0 = Debug|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Release|Any CPU.Build.0 = Release|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Release|x64.ActiveCfg = Release|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Release|x64.Build.0 = Release|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Release|x86.ActiveCfg = Release|Any CPU
{B04A1728-2E87-491E-BC7F-F575A1754DEF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -258,13 +258,8 @@ namespace Remotely_Agent.Services
}
try
{
if (!OSUtils.IsWindows)
{
await hubConnection.InvokeAsync("DisplayConsoleMessage", $"Remote control is only supported on Windows at this time.", requesterID);
return;
}
if (!File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName)))
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName);
if (!File.Exists(rcBinaryPath))
{
await hubConnection.InvokeAsync("DisplayConsoleMessage", "Remote control executable not found on target device.", requesterID);
return;
@ -278,24 +273,24 @@ namespace Remotely_Agent.Services
if (Program.IsDebug)
{
Process.Start(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName), $"-mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -desktop default");
Process.Start(rcBinaryPath, $"-mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -desktop default");
}
else
{
var result = Win32Interop.OpenInteractiveProcess(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName) + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -desktop default", "default", true, out _);
var result = Win32Interop.OpenInteractiveProcess(rcBinaryPath + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -desktop default", "default", true, out _);
if (!result)
{
await hubConnection.InvokeAsync("DisplayConsoleMessage", "Remote control failed to start on target device.", requesterID);
}
}
}
//else if (OSUtils.IsLinux)
//{
// var users = OSUtils.StartProcessWithResults("users", "");
// var username = users?.Split()?.FirstOrDefault()?.Trim();
else if (OSUtils.IsLinux)
{
var users = OSUtils.StartProcessWithResults("users", "");
var username = users?.Split()?.FirstOrDefault()?.Trim();
// Process.Start("sudo", $"-u {username} {rcBinaryPath} -mode Unattended -requester {requesterID} -serviceid {serviceID} -desktop default -hostname {Utilities.GetConnectionInfo().Host.Split("//").Last()}");
//}
Process.Start("sudo", $"-u {username} mono {rcBinaryPath} -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -desktop default");
}
}
catch (Exception ex)
{
@ -314,17 +309,18 @@ namespace Remotely_Agent.Services
}
try
{
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName);
// Start ScreenCast.
if (OSUtils.IsWindows)
{
if (Program.IsDebug)
{
Process.Start(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName), $"-mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}");
Process.Start(rcBinaryPath, $"-mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}");
}
else
{
var result = Win32Interop.OpenInteractiveProcess(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName) + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}", "default", true, out _);
var result = Win32Interop.OpenInteractiveProcess(rcBinaryPath + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}", "default", true, out _);
if (!result)
{
Logger.Write("Failed to relaunch screen caster.");
@ -333,13 +329,13 @@ namespace Remotely_Agent.Services
}
}
}
//else if (OSUtils.IsLinux)
//{
// var users = OSUtils.StartProcessWithResults("users", "");
// var username = users?.Split()?.FirstOrDefault()?.Trim();
else if (OSUtils.IsLinux)
{
var users = OSUtils.StartProcessWithResults("users", "");
var username = users?.Split()?.FirstOrDefault()?.Trim();
// Process.Start("sudo", $"-u {username} {rcBinaryPath} -mode Unattended -requester {requesterID} -serviceid {serviceID} -desktop default -hostname {Utilities.GetConnectionInfo().Host.Split("//").Last()}");
//}
Process.Start("sudo", $"-u {username} mono {rcBinaryPath} -mode Unattended -requester {requesterID} -serviceid {serviceID} -hostname {Utilities.GetConnectionInfo().Host} -desktop default");
}
}
catch (Exception ex)
{

View File

@ -1,5 +1,5 @@
using Remotely_Desktop.ViewModels;
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Core.Models;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Costura.Fody.3.3.2\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.3.3.2\build\Costura.Fody.props')" />
<Import Project="..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props')" />
<Import Project="..\packages\PropertyChanged.Fody.2.6.0\build\PropertyChanged.Fody.props" Condition="Exists('..\packages\PropertyChanged.Fody.2.6.0\build\PropertyChanged.Fody.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -45,8 +45,8 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Costura, Version=3.3.2.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
<HintPath>..\packages\Costura.Fody.3.3.2\lib\net40\Costura.dll</HintPath>
<Reference Include="Costura, Version=3.3.3.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
<HintPath>..\packages\Costura.Fody.3.3.3\lib\net40\Costura.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
@ -135,20 +135,23 @@
<Resource Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Remotely_ScreenCast\Remotely_ScreenCast.csproj">
<ProjectReference Include="..\Remotely_ScreenCast.Core\Remotely_ScreenCast.Core.csproj">
<Project>{b04a1728-2e87-491e-bc7f-f575a1754def}</Project>
<Name>Remotely_ScreenCast.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Remotely_ScreenCast.Win\Remotely_ScreenCast.Win.csproj">
<Project>{2dcea1f5-9b64-4edb-9cd0-4d6675d96709}</Project>
<Name>Remotely_ScreenCast</Name>
<Name>Remotely_ScreenCast.Win</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\PropertyChanged.Fody.2.6.0\build\PropertyChanged.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\PropertyChanged.Fody.2.6.0\build\PropertyChanged.Fody.props'))" />
<Error Condition="!Exists('..\packages\Costura.Fody.3.3.2\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.3.3.2\build\Costura.Fody.props'))" />
<Error Condition="!Exists('..\packages\Fody.4.0.2\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.4.0.2\build\Fody.targets'))" />
<Error Condition="!Exists('..\packages\Fody.4.2.1\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.4.2.1\build\Fody.targets'))" />
<Error Condition="!Exists('..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props'))" />
</Target>
<Import Project="..\packages\Fody.4.0.2\build\Fody.targets" Condition="Exists('..\packages\Fody.4.0.2\build\Fody.targets')" />
<Import Project="..\packages\Fody.4.2.1\build\Fody.targets" Condition="Exists('..\packages\Fody.4.2.1\build\Fody.targets')" />
</Project>

View File

@ -1,8 +1,11 @@
using Remotely_Desktop.Controls;
using Remotely_Desktop.Services;
using Remotely_ScreenCast;
using Remotely_ScreenCast.Capture;
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Core;
using Remotely_ScreenCast.Core.Capture;
using Remotely_ScreenCast.Core.Models;
using Remotely_ScreenCast.Win;
using Remotely_ScreenCast.Win.Capture;
using Remotely_ScreenCast.Win.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -23,16 +26,24 @@ namespace Remotely_Desktop.ViewModels
public MainWindowViewModel()
{
Current = this;
Conductor = new Conductor();
Conductor.SessionIDChanged += SessionIDChanged;
Conductor.ViewerRemoved += ViewerRemoved;
Conductor.ViewerAdded += ViewerAdded;
Conductor.ScreenCastRequested += ScreenCastRequested;
CursorIconWatcher = new CursorIconWatcher(Conductor);
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
}
Program.SessionIDChanged += SessionIDChanged;
Program.ViewerRemoved += ViewerRemoved;
Program.ViewerAdded += ViewerAdded;
Program.ScreenCastRequested += ScreenCastRequested;
private async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
{
await Conductor.OutgoingMessages.SendCursorChange(cursor, Conductor.Viewers.Keys.ToList());
}
public event PropertyChangedEventHandler PropertyChanged;
public static MainWindowViewModel Current { get; private set; }
public Conductor Conductor { get; }
public Config Config { get; private set; }
public string ForceHost { get; }
public bool AllowHostChange
@ -52,6 +63,8 @@ namespace Remotely_Desktop.ViewModels
public string SessionID { get; set; }
public ObservableCollection<Viewer> Viewers { get; } = new ObservableCollection<Viewer>();
public CursorIconWatcher CursorIconWatcher { get; }
public void FirePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
@ -75,10 +88,10 @@ namespace Remotely_Desktop.ViewModels
}
Program.ProcessArgs(new string[] { "-mode", "Normal", "-host", Config.Host });
Conductor.ProcessArgs(new string[] { "-mode", "Normal", "-host", Config.Host });
try
{
await Program.Connect();
await Conductor.Connect();
}
catch (Exception ex)
{
@ -86,24 +99,43 @@ namespace Remotely_Desktop.ViewModels
MessageBox.Show("Failed to connect to server.", "Connection Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
Program.SetEventHandlers();
await Task.Run(async () =>
{
await Program.HandleConnection();
});
Conductor.SetMessageHandlers(new WinInput());
await Conductor.OutgoingMessages.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName);
await Conductor.OutgoingMessages.GetSessionID();
}
private void ScreenCastRequested(object sender, Tuple<string, string> args)
private void ScreenCastRequested(object sender, Tuple<string, string> viewerAndRequester)
{
App.Current.Dispatcher.Invoke(() =>
{
var result = MessageBox.Show($"You've received a connection request from {args.Item2}. Accept?", "Connection Request", MessageBoxButton.YesNo, MessageBoxImage.Question);
var result = MessageBox.Show($"You've received a connection request from {viewerAndRequester.Item2}. Accept?", "Connection Request", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
Task.Run(() =>
Task.Run(async() =>
{
ScreenCaster.BeginScreenCasting(args.Item1, args.Item2, Program.OutgoingMessages);
ICapturer capturer;
try
{
if (Conductor.Viewers.Count == 0)
{
capturer = new DXCapture();
capturer.Init();
}
else
{
capturer = new BitBltCapture();
}
}
catch (Exception ex)
{
Logger.Write(ex);
capturer = new BitBltCapture();
}
await Conductor.OutgoingMessages.SendCursorChange(CursorIconWatcher.GetCurrentCursor(), new List<string>() { viewerAndRequester.Item1 });
ScreenCaster.BeginScreenCasting(viewerAndRequester.Item1, viewerAndRequester.Item2, Conductor.OutgoingMessages, capturer, Conductor);
});
}
});
@ -114,7 +146,7 @@ namespace Remotely_Desktop.ViewModels
foreach (Viewer viewer in viewerList)
{
viewer.DisconnectRequested = true;
await Program.OutgoingMessages.SendViewerRemoved(viewer.ViewerConnectionID);
await Conductor.OutgoingMessages.SendViewerRemoved(viewer.ViewerConnectionID);
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Costura.Fody" version="3.3.2" targetFramework="net472" />
<package id="Fody" version="4.0.2" targetFramework="net472" developmentDependency="true" />
<package id="Costura.Fody" version="3.3.3" targetFramework="net472" />
<package id="Fody" version="4.2.1" targetFramework="net472" developmentDependency="true" />
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net472" />
<package id="PropertyChanged.Fody" version="2.6.0" targetFramework="net472" />
</packages>

View File

@ -47,7 +47,7 @@ namespace Remotely_Library.Services
}
else if (IsLinux)
{
return "Remotely_ScreenCast";
return "Remotely_ScreenCast.Mono.exe";
}
else
{

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Capture
namespace Remotely_ScreenCast.Core.Capture
{
public interface ICapturer : IDisposable
{
@ -15,6 +15,10 @@ namespace Remotely_ScreenCast.Capture
Bitmap PreviousFrame { get; set; }
EventHandler<Rectangle> ScreenChanged { get; set; }
int SelectedScreen { get; set; }
int ScreenCount { get; set; }
double VirtualScreenHeight { get; set; }
double VirtualScreenWidth { get; set; }
void Capture();
void Init();
}

View File

@ -1,4 +1,4 @@
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Core.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
@ -10,7 +10,7 @@ using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Capture
namespace Remotely_ScreenCast.Core.Capture
{
public class ImageUtils
{

View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Sockets;
using Remotely_ScreenCast.Utilities;
using Remotely_ScreenCast.Core.Models;
using Remotely_ScreenCast.Core.Sockets;
using Remotely_ScreenCast.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Drawing;
@ -9,44 +9,23 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Remotely_ScreenCast.Capture
namespace Remotely_ScreenCast.Core.Capture
{
public class ScreenCaster
{
public static async void BeginScreenCasting(string viewerID,
string requesterName,
OutgoingMessages outgoingMessages)
OutgoingMessages outgoingMessages,
ICapturer capturer,
Conductor conductor)
{
ICapturer capturer;
CaptureMode captureMode;
Viewer viewer;
byte[] encodedImageBytes;
var success = false;
try
{
if (Program.Viewers.Count == 0)
{
capturer = new DXCapture();
captureMode = CaptureMode.DirectX;
capturer.Init();
}
else
{
capturer = new BitBltCapture();
captureMode = CaptureMode.BitBtl;
}
}
catch (Exception ex)
{
Logger.Write(ex);
capturer = new BitBltCapture();
captureMode = CaptureMode.BitBtl;
}
Logger.Write($"Starting screen cast. Requester: {requesterName}. Viewer ID: {viewerID}. Capture Mode: {captureMode.ToString()}. App Mode: {Program.Mode} Desktop: {Program.CurrentDesktopName}");
Logger.Write($"Starting screen cast. Requester: {requesterName}. Viewer ID: {viewerID}. Capturer: {capturer.GetType().ToString()}. App Mode: {conductor.Mode} Desktop: {conductor.CurrentDesktopName}");
viewer = new Viewer()
{
@ -59,17 +38,17 @@ namespace Remotely_ScreenCast.Capture
while (!success)
{
success = Program.Viewers.TryAdd(viewerID, viewer);
success = conductor.Viewers.TryAdd(viewerID, viewer);
}
if (Program.Mode == Enums.AppMode.Normal)
if (conductor.Mode == Enums.AppMode.Normal)
{
Program.ViewerAdded?.Invoke(null, viewer);
conductor.ViewerAdded?.Invoke(null, viewer);
}
await outgoingMessages.SendScreenCount(
capturer.SelectedScreen,
Screen.AllScreens.Length,
capturer.ScreenCount,
viewerID);
await outgoingMessages.SendScreenSize(capturer.CurrentScreenBounds.Width, capturer.CurrentScreenBounds.Height, viewerID);
@ -79,8 +58,6 @@ namespace Remotely_ScreenCast.Capture
await outgoingMessages.SendScreenSize(bounds.Width, bounds.Height, viewerID);
};
await outgoingMessages.SendCursorChange(CursorIconWatcher.Current.GetCurrentCursor(), new List<string>() { viewerID });
// TODO: SetThradDesktop causes issues with input after switching.
//var desktopName = Win32Interop.GetCurrentDesktop();
while (!viewer.DisconnectRequested)
@ -164,13 +141,13 @@ namespace Remotely_ScreenCast.Capture
success = false;
while (!success)
{
success = Program.Viewers.TryRemove(viewerID, out _);
success = conductor.Viewers.TryRemove(viewerID, out _);
}
capturer.Dispose();
// Close if no one is viewing.
if (Program.Viewers.Count == 0 && Program.Mode == Enums.AppMode.Unattended)
if (conductor.Viewers.Count == 0 && conductor.Mode == Enums.AppMode.Unattended)
{
Environment.Exit(0);
}
@ -180,7 +157,7 @@ namespace Remotely_ScreenCast.Capture
{
var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left;
var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top;
return new Tuple<double, double>(absoluteX / SystemInformation.VirtualScreen.Width, absoluteY / SystemInformation.VirtualScreen.Height);
return new Tuple<double, double>(absoluteX / capturer.VirtualScreenWidth, absoluteY / capturer.VirtualScreenHeight);
}
public static Tuple<double, double> GetAbsolutePointFromRelativePercent(double percentX, double percentY, ICapturer capturer)
{

View File

@ -0,0 +1,98 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Remotely_ScreenCast.Core.Enums;
using Remotely_ScreenCast.Core.Input;
using Remotely_ScreenCast.Core.Models;
using Remotely_ScreenCast.Core.Sockets;
using Remotely_ScreenCast.Core.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Core
{
public class Conductor
{
public AppMode Mode { get; private set; }
public string RequesterID { get; private set; }
public string ServiceID { get; private set; }
public string Host { get; private set; }
public HubConnection Connection { get; private set; }
public OutgoingMessages OutgoingMessages { get; private set; }
public string CurrentDesktopName { get; set; }
public ConcurrentDictionary<string, Viewer> Viewers { get; } = new ConcurrentDictionary<string, Viewer>();
public Dictionary<string, string> ArgDict { get; set; }
public void SetMessageHandlers(IKeyboardMouseInput keyboardMouse)
{
OutgoingMessages = new OutgoingMessages(Connection);
MessageHandlers.ApplyConnectionHandlers(Connection, this, keyboardMouse);
}
public Task Connect()
{
Connection = new HubConnectionBuilder()
.WithUrl($"{Host}/RCDeviceHub")
.AddMessagePackProtocol()
.Build();
return Connection.StartAsync();
}
public 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();
}
public 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"];
CurrentDesktopName = ArgDict["desktop"];
ServiceID = ArgDict["serviceid"];
}
}
public EventHandler<string> SessionIDChanged { get; set; }
public EventHandler<string> ViewerRemoved { get; set; }
public EventHandler<Viewer> ViewerAdded { get; set; }
public EventHandler<Tuple<string, string>> ScreenCastRequested { get; set; }
public EventHandler<Tuple<string, string>> ScreenCastInitiated { get; set; }
}
}

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Enums
namespace Remotely_ScreenCast.Core.Enums
{
public enum AppMode
{

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Core.Input
{
public interface IKeyboardMouseInput
{
void SendKeyDown(string key);
void SendKeyUp(string key);
uint SendMouseMove(double percentX, double percentY);
uint SendLeftMouseDown(double percentX, double percentY);
uint SendLeftMouseUp(double percentX, double percentY);
uint SendRightMouseDown(double percentX, double percentY);
uint SendRightMouseUp(double percentX, double percentY);
uint SendMouseWheel(int deltaY);
}
}

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Models
namespace Remotely_ScreenCast.Core.Models
{
public class CursorInfo
{

View File

@ -1,4 +1,4 @@
using Remotely_ScreenCast.Capture;
using Remotely_ScreenCast.Core.Capture;
using System;
using System.Collections.Generic;
using System.Drawing;
@ -6,7 +6,7 @@ using System.Drawing.Imaging;
using System.Linq;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Models
namespace Remotely_ScreenCast.Core.Models
{
public class Viewer
{

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</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.Client.Core" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="1.1.0" />
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast_Linux.Capture;
using Remotely_ScreenCast_Linux.Utilities;
using Remotely_ScreenCast_Linux;
using Remotely_ScreenCast.Core.Capture;
using Remotely_ScreenCast.Core.Utilities;
using Remotely_ScreenCast.Core;
using System;
using System.Collections.Generic;
using System.Linq;
@ -10,17 +10,18 @@ using System.Threading.Tasks;
using System.Net;
using System.IO;
using System.Diagnostics;
using Remotely_ScreenCast_Linux.Models;
using Remotely_ScreenCast.Core.Models;
using Remotely_ScreenCast.Core.Input;
namespace Remotely_ScreenCast_Linux.Sockets
namespace Remotely_ScreenCast.Core.Sockets
{
public class MessageHandlers
{
public static void ApplyConnectionHandlers(HubConnection hubConnection, OutgoingMessages outgoingMessages)
public static void ApplyConnectionHandlers(HubConnection hubConnection, Conductor conductor, IKeyboardMouseInput keyboardMouse)
{
hubConnection.Closed += (ex) =>
{
Logger.Write($"Error: {ex.Message}");
Logger.Write($"Connection closed. Error: {ex.Message}");
Environment.Exit(1);
return Task.CompletedTask;
};
@ -29,7 +30,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
{
try
{
ScreenCaster.BeginScreenCasting(viewerID, requesterName, outgoingMessages);
conductor.ScreenCastInitiated?.Invoke(null, new Tuple<string, string>(viewerID, requesterName));
}
catch (Exception ex)
{
@ -39,100 +40,97 @@ namespace Remotely_ScreenCast_Linux.Sockets
hubConnection.On("RequestScreenCast", (string viewerID, string requesterName) =>
{
Program.ScreenCastRequested?.Invoke(null, new Tuple<string, string>(viewerID, requesterName));
conductor.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)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//var keyCode = Win32Interop.ConvertJavaScriptKeyToVirtualKey(key);
//Win32Interop.SendKeyDown((User32.VirtualKey)keyCode);
keyboardMouse.SendKeyDown(key);
}
});
hubConnection.On("KeyUp", (string key, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//var keyCode = Win32Interop.ConvertJavaScriptKeyToVirtualKey(key);
//Win32Interop.SendKeyUp((User32.VirtualKey)keyCode);
keyboardMouse.SendKeyUp(key);
}
});
hubConnection.On("KeyPress", async (string key, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.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);
keyboardMouse.SendKeyDown(key);
await Task.Delay(1);
keyboardMouse.SendKeyUp(key);
}
});
hubConnection.On("MouseMove", (double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
//Win32Interop.SendMouseMove(mousePoint.Item1, mousePoint.Item2);
var xyPercents = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
keyboardMouse.SendMouseMove(xyPercents.Item1, xyPercents.Item2);
}
});
hubConnection.On("MouseDown", (int button, double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
var xyPercents = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
if (button == 0)
{
//Win32Interop.SendLeftMouseDown((int)mousePoint.Item1, (int)mousePoint.Item2);
keyboardMouse.SendLeftMouseDown(xyPercents.Item1, xyPercents.Item2);
}
else if (button == 2)
{
//Win32Interop.SendRightMouseDown((int)mousePoint.Item1, (int)mousePoint.Item2);
keyboardMouse.SendRightMouseDown(xyPercents.Item1, xyPercents.Item2);
}
}
});
hubConnection.On("MouseUp", (int button, double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
var mousePoint = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
var xyPercents = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
if (button == 0)
{
//Win32Interop.SendLeftMouseUp((int)mousePoint.Item1, (int)mousePoint.Item2);
keyboardMouse.SendLeftMouseUp(xyPercents.Item1, xyPercents.Item2);
}
else if (button == 2)
{
//Win32Interop.SendRightMouseUp((int)mousePoint.Item1, (int)mousePoint.Item2);
keyboardMouse.SendRightMouseUp(xyPercents.Item1, xyPercents.Item2);
}
}
});
hubConnection.On("MouseWheel", (double deltaX, double deltaY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//Win32Interop.SendMouseWheel(-(int)deltaY);
keyboardMouse.SendMouseWheel(-(int)deltaY);
}
});
hubConnection.On("ViewerDisconnected", async (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer))
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.DisconnectRequested = true;
}
await hubConnection.InvokeAsync("ViewerDisconnected", viewerID);
Program.ViewerRemoved?.Invoke(null, viewerID);
conductor.ViewerRemoved?.Invoke(null, viewerID);
});
hubConnection.On("LatencyUpdate", (double latency, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer))
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.PendingFrames--;
viewer.Latency = latency;
@ -141,7 +139,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
hubConnection.On("SelectScreen", (int screenIndex, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer))
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.Capturer.SelectedScreen = screenIndex;
}
@ -149,7 +147,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
hubConnection.On("TouchDown", (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendLeftMouseDown(point.X, point.Y);
@ -157,7 +155,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
});
hubConnection.On("LongPress", (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendRightMouseDown(point.X, point.Y);
@ -166,7 +164,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
});
hubConnection.On("TouchMove", (double moveX, double moveY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendMouseMove(point.X + moveX, point.Y + moveY);
@ -174,7 +172,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
});
hubConnection.On("TouchUp", (string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
{
//User32.GetCursorPos(out var point);
//Win32Interop.SendLeftMouseUp(point.X, point.Y);
@ -182,17 +180,17 @@ namespace Remotely_ScreenCast_Linux.Sockets
});
hubConnection.On("Tap", (double percentX, double percentY, string viewerID) =>
{
if (Program.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.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);
var xyPercents = ScreenCaster.GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
keyboardMouse.SendLeftMouseDown(xyPercents.Item1, xyPercents.Item2);
keyboardMouse.SendLeftMouseUp(xyPercents.Item1, xyPercents.Item2);
}
});
hubConnection.On("SharedFileIDs", (List<string> fileIDs) => {
fileIDs.ForEach(id =>
{
var url = $"{Program.Host}/API/FileSharing/{id}";
var url = $"{conductor.Host}/API/FileSharing/{id}";
var webRequest = WebRequest.CreateHttp(url);
var response = webRequest.GetResponse();
var contentDisp = response.Headers["Content-Disposition"];
@ -220,7 +218,7 @@ namespace Remotely_ScreenCast_Linux.Sockets
hubConnection.On("SessionID", (string sessionID) =>
{
Program.SessionIDChanged?.Invoke(null, sessionID);
conductor.SessionIDChanged?.Invoke(null, sessionID);
});
}
}

View File

@ -1,5 +1,5 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Core.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -7,7 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Sockets
namespace Remotely_ScreenCast.Core.Sockets
{
public class OutgoingMessages
{
@ -27,7 +27,7 @@ namespace Remotely_ScreenCast.Sockets
await Connection.SendAsync("SendScreenCapture", captureBytes, viewerID, left, top, width, height, captureTime);
}
internal async Task SendScreenCount(int primaryScreenIndex, int screenCount, string viewerID)
public async Task SendScreenCount(int primaryScreenIndex, int screenCount, string viewerID)
{
await Connection.SendAsync("SendScreenCountToBrowser", primaryScreenIndex, screenCount, viewerID);
}
@ -42,22 +42,22 @@ namespace Remotely_ScreenCast.Sockets
await Connection.SendAsync("SendCursorChange", cursor, viewerIDs);
}
internal async Task NotifyViewersRelaunchedScreenCasterReady(string[] viewerIDs)
public async Task NotifyViewersRelaunchedScreenCasterReady(string[] viewerIDs)
{
await Connection.SendAsync("NotifyViewersRelaunchedScreenCasterReady", viewerIDs);
}
internal async Task SendDeviceInfo(string serviceID, string machineName)
public async Task SendDeviceInfo(string serviceID, string machineName)
{
await Connection.SendAsync("ReceiveDeviceInfo", serviceID, machineName);
}
internal async Task SendConnectionFailedToViewers(List<string> viewerIDs)
public async Task SendConnectionFailedToViewers(List<string> viewerIDs)
{
await Connection.SendAsync("SendConnectionFailedToViewers", viewerIDs);
}
internal async Task GetSessionID()
public async Task GetSessionID()
{
await Connection.SendAsync("GetSessionID");
}

View File

@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Utilities
namespace Remotely_ScreenCast.Core.Utilities
{
public static class Logger
{

View File

@ -0,0 +1,38 @@
using Remotely_ScreenCast.Core.Capture;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace Remotely_ScreenCast.Linux.Capture
{
public class X11Capture : ICapturer
{
public bool CaptureFullscreen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public System.Drawing.Bitmap CurrentFrame { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public Rectangle CurrentScreenBounds => throw new NotImplementedException();
public System.Drawing.Bitmap PreviousFrame { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public EventHandler<Rectangle> ScreenChanged { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public int SelectedScreen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public int ScreenCount { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public double VirtualScreenHeight { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public double VirtualScreenWidth { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public void Capture()
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
public void Init()
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,50 @@
using Remotely_ScreenCast.Core.Input;
using System;
using System.Collections.Generic;
using System.Text;
namespace Remotely_ScreenCast.Linux.Input
{
public class X11Input : IKeyboardMouseInput
{
public void SendKeyDown(string key)
{
throw new NotImplementedException();
}
public void SendKeyUp(string key)
{
throw new NotImplementedException();
}
public uint SendLeftMouseDown(double percentX, double percentY)
{
throw new NotImplementedException();
}
public uint SendLeftMouseUp(double percentX, double percentY)
{
throw new NotImplementedException();
}
public uint SendMouseMove(double percentX, double percentY)
{
throw new NotImplementedException();
}
public uint SendMouseWheel(int deltaY)
{
throw new NotImplementedException();
}
public uint SendRightMouseDown(double percentX, double percentY)
{
throw new NotImplementedException();
}
public uint SendRightMouseUp(double percentX, double percentY)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,88 @@
using Remotely_ScreenCast.Core;
using Remotely_ScreenCast.Core.Capture;
using Remotely_ScreenCast.Core.Utilities;
using Remotely_ScreenCast.Linux.Capture;
using Remotely_ScreenCast.Linux.Input;
using Remotely_ScreenCast.Linux.X11Interop;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Linux
{
public class Program
{
public static Conductor Conductor { get; private set; }
//public static CursorIconWatcher CursorIconWatcher { get; private set; }
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
//var display = Xlib.XOpenDisplay(null);
//Console.WriteLine($"Display is {display.ToString()}");
//var count = Xlib.XScreenCount(display);
//Console.WriteLine($"Count is {count}");
//using (var bitmap = new System.Drawing.Bitmap(800, 600))
//{
// using (var graphic = System.Drawing.Graphics.FromImage(bitmap))
// {
// graphic.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(800, 600));
// }
// bitmap.Save("Test.jpg");
//}
//var width = Xlib.XDisplayWidth(display, 0);
//var height = Xlib.XDisplayHeight(display, 0);
//Console.WriteLine($"Width: {width}, Height: {height}");
//Console.ReadLine();
Conductor = new Conductor();
Conductor.ProcessArgs(args);
Conductor.Connect().Wait();
Conductor.SetMessageHandlers(new X11Input());
Conductor.ScreenCastInitiated += ScreenCastInitiated;
//CursorIconWatcher = new CursorIconWatcher(Conductor);
//CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
Conductor.OutgoingMessages.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName).Wait();
Conductor.StartWaitForViewerTimer();
HandleConnection(Conductor).Wait();
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private static void ScreenCastInitiated(object sender, Tuple<string, string> viewerAndRequester)
{
ICapturer capturer;
try
{
capturer = new X11Capture();
//await Conductor.OutgoingMessages.SendCursorChange(CursorIconWatcher.GetCurrentCursor(), new List<string>() { viewerAndRequester.Item1 });
ScreenCaster.BeginScreenCasting(viewerAndRequester.Item1, viewerAndRequester.Item2, Conductor.OutgoingMessages, capturer, Conductor);
Conductor.OutgoingMessages.SendConnectionFailedToViewers(new List<string>() { viewerAndRequester.Item1 }).Wait();
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
//public static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
//{
// await Conductor.OutgoingMessages.SendCursorChange(cursor, Conductor.Viewers.Keys.ToList());
//}
public static async Task HandleConnection(Conductor conductor)
{
await Task.Delay(100);
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
}
}

View File

@ -3,7 +3,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AssemblyName>Remotely_ScreenCast</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -15,25 +14,12 @@
</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>
<ProjectReference Include="..\Remotely_ScreenCast.Core\Remotely_ScreenCast.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,154 @@
/*
Copyright 1985, 1986, 1987, 1991, 1998 The Open Group
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of The Open Group shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from The Open Group.
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
namespace Remotely_ScreenCast.Linux.X11Interop
{
public static unsafe class Xlib
{
#region Structs
public struct XImage
{
public int width;
public int height; /* size of image */
public int xoffset; /* number of pixels offset in X direction */
public int format; /* XYBitmap, XYPixmap, ZPixmap */
public char* data; /* pointer to image data */
public int byte_order; /* data byte order, LSBFirst, MSBFirst */
public int bitmap_unit; /* quant. of scanline 8, 16, 32 */
public int bitmap_bit_order; /* LSBFirst, MSBFirst */
public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */
public int depth; /* depth of image */
public int bytes_per_line; /* accelerator to next scanline */
public int bits_per_pixel; /* bits per pixel (ZPixmap) */
public ulong red_mask; /* bits in z arrangement */
public ulong green_mask;
public ulong blue_mask;
public IntPtr obdata; /* hook for the object routines to hang on */
public XImage create_image()
{
return new XImage();
}
public int destroy_image()
{
return 0;
}
public ulong get_pixel()
{
return 0;
}
public XImage sub_image()
{
return new XImage();
}
public int add_pixel()
{
return 0;
}
}
public struct Screen {
public IntPtr ext_data; /* hook for extension to hang data */
public IntPtr *display;/* back pointer to display structure */ // TODO: XDisplay struct?
public IntPtr root; /* Root window id. */
public int width;
public int height; /* width and height of screen */
public int mwidth;
public int mheight; /* width and height of in millimeters */
public int ndepths; /* number of depths possible */
public uint depths; /* list of allowable depths on the screen */ //TODO: Depth[]
public int root_depth; /* bits per pixel */
public IntPtr root_visual; /* root visual */
public IntPtr default_gc; /* GC for the root root visual */
public IntPtr cmap; /* default color map */ // TODO: Colormap struct?
public ulong white_pixel;
public ulong black_pixel; /* White and Black pixel values */
public int max_maps, min_maps; /* max and min color maps */
public int backing_store; /* Never, WhenMapped, Always */
public bool save_unders;
public long root_input_mask; /* initial root input mask */
}
public struct Depth {
public int depth; /* this depth (Z) of the depth */
public int nvisuals; /* number of Visual types at this depth */
public Visual[] visuals; /* list of visuals possible at this depth */
}
public struct Visual {
public IntPtr ext_data; /* hook for extension to hang data */
public int visualid; /* visual id of this visual */
public ulong red_mask, green_mask, blue_mask; /* mask values */
public int bits_per_rgb; /* log base 2 of distinct color values */
public int map_entries; /* color map entries */
}
public class XExtData
{
public int number; /* number returned by XRegisterExtension */
public XExtData next; /* next item on list of data for structure */
public IntPtr private_data; /* data private to this extension. */
}
#endregion Structs
#region Imports
[DllImport("libX11")]
public static extern XImage XGetImage(IntPtr display, IntPtr drawable, int x, int y, uint width, uint height, ulong plane_mask, int format);
[DllImport("libX11")]
public static extern int XScreenCount(IntPtr display);
[DllImport("libX11")]
public static extern int XDefaultScreen(IntPtr display);
[DllImport("libX11")]
public static extern IntPtr XOpenDisplay(string display_name);
[DllImport("libX11")]
public static extern IntPtr XRootWindow(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern XImage* XGetSubImage(IntPtr display, IntPtr drawable, int x, int y, uint width, uint height, ulong plane_mask, int format, XImage dest_image, int dest_x, int dest_y);
[DllImport("libX11")]
public static extern IntPtr XScreenOfDisplay(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XDisplayWidth(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XDisplayHeight(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XWidthOfScreen(IntPtr screen);
[DllImport("libX11")]
public static extern int XHeightOfScreen(IntPtr screen);
#endregion Imports
}
}

View File

@ -12,10 +12,11 @@ using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using Remotely_ScreenCast.Utilities;
using Remotely_ScreenCast.Core.Utilities;
using System.Threading;
using Remotely_ScreenCast.Core.Capture;
namespace Remotely_ScreenCast.Capture
namespace Remotely_ScreenCast.Win.Capture
{
public class BitBltCapture : ICapturer
{
@ -56,6 +57,10 @@ namespace Remotely_ScreenCast.Capture
}
}
public Rectangle CurrentScreenBounds { get; set; } = Screen.PrimaryScreen.Bounds;
public int ScreenCount { get; set; } = Screen.AllScreens.Length;
public double VirtualScreenHeight { get; set; } = SystemInformation.VirtualScreen.Width;
public double VirtualScreenWidth { get; set; } = SystemInformation.VirtualScreen.Height;
private int selectedScreen = Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen);
private Graphics graphic;

View File

@ -1,4 +1,5 @@
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Core;
using Remotely_ScreenCast.Core.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
@ -12,17 +13,22 @@ using System.Timers;
using System.Windows.Forms;
using Win32;
namespace Remotely_ScreenCast.Capture
namespace Remotely_ScreenCast.Win.Capture
{
/// <summary>
/// A class that can be used to watch for cursor icon changes.
/// </summary>
public class CursorIconWatcher
{
public static CursorIconWatcher Current { get; } = new CursorIconWatcher();
public CursorIconWatcher(Conductor conductor)
{
Conductor = conductor;
}
public event EventHandler<CursorInfo> OnChange;
private System.Timers.Timer ChangeTimer { get; set; }
private string PreviousCursorHandle { get; set; }
public Conductor Conductor { get; }
private User32.CursorInfo cursorInfo;

View File

@ -1,4 +1,5 @@
using Remotely_ScreenCast.Utilities;
using Remotely_ScreenCast.Core.Capture;
using Remotely_ScreenCast.Core.Utilities;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
@ -8,9 +9,10 @@ using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Win32;
namespace Remotely_ScreenCast.Capture
namespace Remotely_ScreenCast.Win.Capture
{
public class DXCapture : ICapturer
{
@ -63,6 +65,10 @@ namespace Remotely_ScreenCast.Capture
}
}
public int ScreenCount { get; set; } = Screen.AllScreens.Length;
public double VirtualScreenHeight { get; set; } = SystemInformation.VirtualScreen.Width;
public double VirtualScreenWidth { get; set; } = SystemInformation.VirtualScreen.Height;
public void Capture()
{
try

View File

@ -0,0 +1,224 @@
using Remotely_ScreenCast.Core.Input;
using System;
using Win32;
using static Win32.User32;
namespace Remotely_ScreenCast.Win.Input
{
public class WinInput : IKeyboardMouseInput
{
public uint SendLeftMouseDown(double percentX, double percentY)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = percentX * 65535D;
var normalizedY = percentY * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.LEFTDOWN | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public uint SendLeftMouseUp(double percentX, double percentY)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = percentX * 65535D;
var normalizedY = percentY * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.LEFTUP | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public uint SendRightMouseDown(double percentX, double percentY)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = percentX * 65535D;
var normalizedY = percentY * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.RIGHTDOWN | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public uint SendRightMouseUp(double percentX, double percentY)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = percentX * 65535D;
var normalizedY = percentY * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.RIGHTUP | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public uint SendMouseMove(double percentX, double percentY)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = percentX * 65535D;
var normalizedY = percentY * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.MOVE | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public uint SendMouseWheel(int deltaY)
{
if (deltaY < 0)
{
deltaY = -120;
}
else if (deltaY > 0)
{
deltaY = 120;
}
var union = new User32.InputUnion() { mi = new User32.MOUSEINPUT() { dwFlags = MOUSEEVENTF.WHEEL, dx = 0, dy = 0, time = 0, mouseData = deltaY, dwExtraInfo = GetMessageExtraInfo() } };
var input = new User32.INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new User32.INPUT[] { input }, INPUT.Size);
}
public void SendKeyDown(string key)
{
var keyCode = ConvertJavaScriptKeyToVirtualKey(key);
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = keyCode,
wScan = 0,
time = 0,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public void SendKeyUp(string key)
{
var keyCode = ConvertJavaScriptKeyToVirtualKey(key);
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = keyCode,
wScan = 0,
time = 0,
dwFlags = KEYEVENTF.KEYUP,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
private VirtualKey ConvertJavaScriptKeyToVirtualKey(string key)
{
VirtualKey keyCode;
switch (key)
{
case "Down":
case "ArrowDown":
keyCode = VirtualKey.DOWN;
break;
case "Up":
case "ArrowUp":
keyCode = VirtualKey.UP;
break;
case "Left":
case "ArrowLeft":
keyCode = VirtualKey.LEFT;
break;
case "Right":
case "ArrowRight":
keyCode = VirtualKey.RIGHT;
break;
case "Enter":
keyCode = VirtualKey.RETURN;
break;
case "Esc":
case "Escape":
keyCode = VirtualKey.ESCAPE;
break;
case "Alt":
keyCode = VirtualKey.MENU;
break;
case "Control":
keyCode = VirtualKey.CONTROL;
break;
case "Shift":
keyCode = VirtualKey.SHIFT;
break;
case "PAUSE":
keyCode = VirtualKey.PAUSE;
break;
case "BREAK":
keyCode = VirtualKey.PAUSE;
break;
case "Backspace":
keyCode = VirtualKey.BACK;
break;
case "Tab":
keyCode = VirtualKey.TAB;
break;
case "CapsLock":
keyCode = VirtualKey.CAPITAL;
break;
case "Delete":
keyCode = VirtualKey.DELETE;
break;
case "Home":
keyCode = VirtualKey.HOME;
break;
case "End":
keyCode = VirtualKey.END;
break;
case "PageUp":
keyCode = VirtualKey.PRIOR;
break;
case "PageDown":
keyCode = VirtualKey.NEXT;
break;
case "NumLock":
keyCode = VirtualKey.NUMLOCK;
break;
case "Insert":
keyCode = VirtualKey.INSERT;
break;
case "ScrollLock":
keyCode = VirtualKey.SCROLL;
break;
case "F1":
keyCode = VirtualKey.F1;
break;
case "F2":
keyCode = VirtualKey.F2;
break;
case "F3":
keyCode = VirtualKey.F3;
break;
case "F4":
keyCode = VirtualKey.F4;
break;
case "F5":
keyCode = VirtualKey.F5;
break;
case "F6":
keyCode = VirtualKey.F6;
break;
case "F7":
keyCode = VirtualKey.F7;
break;
case "F8":
keyCode = VirtualKey.F8;
break;
case "F9":
keyCode = VirtualKey.F9;
break;
case "F10":
keyCode = VirtualKey.F10;
break;
case "F11":
keyCode = VirtualKey.F11;
break;
case "F12":
keyCode = VirtualKey.F12;
break;
default:
keyCode = (VirtualKey)VkKeyScan(Convert.ToChar(key));
break;
}
return keyCode;
}
}
}

View File

@ -0,0 +1,150 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Remotely_ScreenCast.Core;
using Remotely_ScreenCast.Core.Capture;
using Remotely_ScreenCast.Core.Enums;
using Remotely_ScreenCast.Core.Models;
using Remotely_ScreenCast.Core.Sockets;
using Remotely_ScreenCast.Core.Utilities;
using Remotely_ScreenCast.Win.Capture;
using Remotely_ScreenCast.Win.Input;
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;
using System.Windows.Forms;
using Win32;
namespace Remotely_ScreenCast.Win
{
public class Program
{
public static Conductor Conductor { get; private set; }
public static CursorIconWatcher CursorIconWatcher { get; private set; }
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Conductor = new Conductor();
Conductor.ProcessArgs(args);
Conductor.Connect().Wait();
Conductor.SetMessageHandlers(new WinInput());
Conductor.ScreenCastInitiated += ScreenCastInitiated;
CursorIconWatcher = new CursorIconWatcher(Conductor);
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
Conductor.OutgoingMessages.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName).Wait();
CheckInitialDesktop();
CheckForRelaunch();
Conductor.StartWaitForViewerTimer();
HandleConnection(Conductor).Wait();
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private static void CheckForRelaunch()
{
if (Conductor.ArgDict.ContainsKey("relaunch"))
{
Logger.Write($"Resuming after relaunch in desktop {Conductor.CurrentDesktopName}.");
var viewersString = Conductor.ArgDict["viewers"];
var viewerIDs = viewersString.Split(",".ToCharArray());
Conductor.OutgoingMessages.NotifyViewersRelaunchedScreenCasterReady(viewerIDs).Wait();
}
else
{
Conductor.OutgoingMessages.NotifyRequesterUnattendedReady(Conductor.RequesterID).Wait();
}
}
private static async void ScreenCastInitiated(object sender, Tuple<string, string> viewerAndRequester)
{
ICapturer capturer;
try
{
if (Conductor.Viewers.Count == 0)
{
capturer = new DXCapture();
capturer.Init();
}
else
{
capturer = new BitBltCapture();
}
}
catch (Exception ex)
{
Logger.Write(ex);
capturer = new BitBltCapture();
}
await Conductor.OutgoingMessages.SendCursorChange(CursorIconWatcher.GetCurrentCursor(), new List<string>() { viewerAndRequester.Item1 });
ScreenCaster.BeginScreenCasting(viewerAndRequester.Item1, viewerAndRequester.Item2, Conductor.OutgoingMessages, capturer, Conductor);
}
public static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
{
await Conductor.OutgoingMessages.SendCursorChange(cursor, Conductor.Viewers.Keys.ToList());
}
public static async Task HandleConnection(Conductor conductor)
{
while (true)
{
var desktopName = Win32Interop.GetCurrentDesktop();
if (desktopName.ToLower() != conductor.CurrentDesktopName.ToLower() && conductor.Viewers.Count > 0)
{
conductor.CurrentDesktopName = desktopName;
Logger.Write($"Switching desktops to {desktopName}.");
// TODO: SetThradDesktop causes issues with input after switching.
//var inputDesktop = Win32Interop.OpenInputDesktop();
//User32.SetThreadDesktop(inputDesktop);
//User32.CloseDesktop(inputDesktop);
conductor.Connection.InvokeAsync("SwitchingDesktops", conductor.Viewers.Keys.ToList()).Wait();
var result = Win32Interop.OpenInteractiveProcess(Assembly.GetExecutingAssembly().Location + $" -mode {conductor.Mode.ToString()} -requester {conductor.RequesterID} -serviceid {conductor.ServiceID} -host {conductor.Host} -relaunch true -desktop {desktopName} -viewers {String.Join(",", conductor.Viewers.Keys.ToList())}", desktopName, true, out _);
if (!result)
{
Logger.Write($"Desktop switch to {desktopName} failed.");
conductor.OutgoingMessages.SendConnectionFailedToViewers(conductor.Viewers.Keys.ToList()).Wait();
}
}
await Task.Delay(100);
}
}
private static void CheckInitialDesktop()
{
var desktopName = Win32Interop.GetCurrentDesktop();
if (desktopName.ToLower() != Conductor.CurrentDesktopName.ToLower())
{
Conductor.CurrentDesktopName = desktopName;
Logger.Write($"Setting initial desktop to {desktopName}.");
Conductor.ArgDict["desktop"] = desktopName;
var openProcessString = Assembly.GetExecutingAssembly().Location;
foreach (var arg in Conductor.ArgDict)
{
openProcessString += $" -{arg.Key} {arg.Value}";
}
var result = Win32Interop.OpenInteractiveProcess(openProcessString, desktopName, true, out _);
if (!result)
{
Logger.Write($"Desktop relaunch to {desktopName} failed.");
}
Environment.Exit(0);
}
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
}
}

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2DCEA1F5-9B64-4EDB-9CD0-4D6675D96709}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Remotely_ScreenCast.Win</RootNamespace>
<AssemblyName>Remotely_ScreenCast</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Costura, Version=3.3.3.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
<HintPath>..\packages\Costura.Fody.3.3.3\lib\net40\Costura.dll</HintPath>
</Reference>
<Reference Include="MessagePack, Version=1.7.3.4, Culture=neutral, PublicKeyToken=b4a0369545f0a1be, processorArchitecture=MSIL">
<HintPath>..\packages\MessagePack.1.7.3.4\lib\net47\MessagePack.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.Connections.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Connections.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Http.Connections.Client, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.Http.Connections.Client.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Connections.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Http.Connections.Common, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.Http.Connections.Common.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Connections.Common.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Http.Features, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.Http.Features.2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Client, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.SignalR.Client.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Client.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.SignalR.Client.Core.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Client.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Common, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.SignalR.Common.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Common.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.Json, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.SignalR.Protocols.Json.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Protocols.Json.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Configuration.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Configuration.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Binder, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Configuration.Binder.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.2.2.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Logging.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Options, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Options.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Primitives, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Primitives.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="SharpDX, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll</HintPath>
</Reference>
<Reference Include="SharpDX.Direct3D11, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll</HintPath>
</Reference>
<Reference Include="SharpDX.DXGI, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Annotations, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Drawing.Common, Version=4.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Drawing.Common.4.5.1\lib\net461\System.Drawing.Common.dll</HintPath>
</Reference>
<Reference Include="System.IO.Pipelines, Version=4.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.4.5.3\lib\netstandard2.0\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Channels, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Channels.4.5.0\lib\netstandard2.0\System.Threading.Channels.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Capture\BitBltCapture.cs" />
<Compile Include="Capture\CursorIconWatcher.cs" />
<Compile Include="Capture\DXCapture.cs" />
<Compile Include="Input\WinInput.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Win32\ADVAPI32.cs" />
<Compile Include="Win32\GDI32.cs" />
<Compile Include="Win32\Kernel32.cs" />
<Compile Include="Win32\SECUR32.cs" />
<Compile Include="Win32\User32.cs" />
<Compile Include="Win32\Win32Interop.cs" />
<Compile Include="Win32\WTSAPI32.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="FodyWeavers.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Remotely_ScreenCast.Core\Remotely_ScreenCast.Core.csproj">
<Project>{b04a1728-2e87-491e-bc7f-f575a1754def}</Project>
<Name>Remotely_ScreenCast.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>if $(ConfigurationName) == Debug (
md "$(SolutionDir)Remotely_Agent\bin\Debug\netcoreapp2.2\ScreenCast\"
copy /y "$(TargetPath)" "$(SolutionDir)Remotely_Agent\bin\Debug\netcoreapp2.2\ScreenCast\"
)</PostBuildEvent>
</PropertyGroup>
<Import Project="..\packages\Fody.4.2.1\build\Fody.targets" Condition="Exists('..\packages\Fody.4.2.1\build\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.4.2.1\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.4.2.1\build\Fody.targets'))" />
<Error Condition="!Exists('..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.3.3.3\build\Costura.Fody.props'))" />
</Target>
</Project>

View File

@ -0,0 +1,140 @@
using Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using static Win32.ADVAPI32;
using static Win32.User32;
using System.Windows.Forms;
namespace Win32
{
public class Win32Interop
{
public static bool OpenInteractiveProcess(string applicationName, string desktopName, bool hiddenWindow, out PROCESS_INFORMATION procInfo)
{
uint winlogonPid = 0;
IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
procInfo = new PROCESS_INFORMATION();
// Obtain session ID for active session.
uint dwSessionId = Kernel32.WTSGetActiveConsoleSessionId();
// Check for RDP session. If active, use that session ID instead.
var rdpSessionID = GetRDPSession();
if (rdpSessionID > 0)
{
dwSessionId = rdpSessionID;
}
// Obtain the process ID of the winlogon process that is running within the currently active session.
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// Obtain a handle to the winlogon process.
hProcess = Kernel32.OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// Obtain a handle to the access token of the winlogon process.
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
Kernel32.CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// Copy the access token of the winlogon process; the newly created token will be a primary token.
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out hUserTokenDup))
{
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
return false;
}
// By default, CreateProcessAsUser creates a process on a non-interactive window station, meaning
// the window station has a desktop that is invisible and the process is incapable of receiving
// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
// interaction with the new process.
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = @"winsta0\" + desktopName;
// Flags that specify the priority and creation method of the process.
uint dwCreationFlags;
if (hiddenWindow)
{
dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | DETACHED_PROCESS;
}
else
{
dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
}
// Create a new process in the current user's logon session.
bool result = CreateProcessAsUser(hUserTokenDup, null, applicationName, ref sa, ref sa, false, dwCreationFlags, IntPtr.Zero, null, ref si, out procInfo);
// Invalidate the handles.
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
Kernel32.CloseHandle(hUserTokenDup);
return result;
}
public static uint GetRDPSession()
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSAPI32.WTSEnumerateSessions(WTSAPI32.WTS_CURRENT_SERVER_HANDLE, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTSAPI32.WTS_SESSION_INFO));
var sessList = new List<WTSAPI32.WTS_SESSION_INFO>();
Int64 current = (Int64)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTSAPI32.WTS_SESSION_INFO sessInf = (WTSAPI32.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTSAPI32.WTS_SESSION_INFO));
current += dataSize;
sessList.Add(sessInf);
}
}
uint retVal = 0;
var rdpSession = sessList.Find(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0);
if (sessList.Exists(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0))
{
retVal = (uint)rdpSession.SessionID;
}
return retVal;
}
public static IntPtr OpenInputDesktop()
{
return User32.OpenInputDesktop(0, false, ACCESS_MASK.GENERIC_ALL);
}
public static string GetCurrentDesktop()
{
var inputDesktop = OpenInputDesktop();
byte[] deskBytes = new byte[256];
uint lenNeeded;
var success = GetUserObjectInformationW(inputDesktop, UOI_NAME, deskBytes, 256, out lenNeeded);
if (!success)
{
CloseDesktop(inputDesktop);
return "Default";
}
var desktopName = Encoding.Unicode.GetString(deskBytes.Take((int)lenNeeded).ToArray()).Replace("\0", "");
CloseDesktop(inputDesktop);
return desktopName;
}
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Costura.Fody" version="3.3.2" targetFramework="net472" />
<package id="Fody" version="4.0.2" targetFramework="net472" developmentDependency="true" />
<package id="Costura.Fody" version="3.3.3" targetFramework="net472" />
<package id="Fody" version="4.2.1" targetFramework="net472" developmentDependency="true" />
<package id="MessagePack" version="1.7.3.4" targetFramework="net472" />
<package id="Microsoft.AspNetCore.Connections.Abstractions" version="2.2.0" targetFramework="net472" />
<package id="Microsoft.AspNetCore.Http.Connections.Client" version="1.1.0" targetFramework="net472" />
@ -27,6 +27,7 @@
<package id="SharpDX.DXGI" version="4.2.0" targetFramework="net472" />
<package id="System.Buffers" version="4.5.0" targetFramework="net472" />
<package id="System.ComponentModel.Annotations" version="4.5.0" targetFramework="net472" />
<package id="System.Drawing.Common" version="4.5.1" targetFramework="net472" />
<package id="System.IO.Pipelines" version="4.5.3" targetFramework="net472" />
<package id="System.Memory" version="4.5.2" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />

View File

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely_ScreenCast.Capture
{
public enum CaptureMode
{
BitBtl,
DirectX
}
}

View File

@ -1,206 +0,0 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Remotely_ScreenCast;
using Remotely_ScreenCast.Capture;
using Remotely_ScreenCast.Enums;
using Remotely_ScreenCast.Models;
using Remotely_ScreenCast.Sockets;
using Remotely_ScreenCast.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;
using System.Windows.Forms;
using Win32;
namespace Remotely_ScreenCast
{
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 string CurrentDesktopName { get; 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)
{
var desktopName = Win32Interop.GetCurrentDesktop();
if (desktopName.ToLower() != CurrentDesktopName.ToLower())
{
CurrentDesktopName = desktopName;
Logger.Write($"Setting initial desktop to {desktopName}.");
ArgDict["desktop"] = desktopName;
var openProcessString = Assembly.GetExecutingAssembly().Location;
foreach (var arg in ArgDict)
{
openProcessString += $" -{arg.Key} {arg.Value}";
}
var result = Win32Interop.OpenInteractiveProcess(openProcessString, desktopName, true, out _);
if (!result)
{
Logger.Write($"Desktop relaunch to {desktopName} failed.");
}
Environment.Exit(0);
}
if (ArgDict.ContainsKey("relaunch"))
{
Logger.Write($"Resuming after relaunch in desktop {CurrentDesktopName}.");
var viewersString = ArgDict["viewers"];
var viewerIDs = viewersString.Split(",".ToCharArray());
OutgoingMessages.NotifyViewersRelaunchedScreenCasterReady(viewerIDs).Wait();
}
else
{
OutgoingMessages.NotifyRequesterUnattendedReady(RequesterID).Wait();
}
StartWaitForViewerTimer();
}
else if (Mode == AppMode.Normal)
{
OutgoingMessages.GetSessionID().Wait();
}
while (true)
{
if (Mode == AppMode.Unattended)
{
var desktopName = Win32Interop.GetCurrentDesktop();
if (desktopName.ToLower() != CurrentDesktopName.ToLower() && Viewers.Count > 0)
{
CurrentDesktopName = desktopName;
Logger.Write($"Switching desktops to {desktopName}.");
// TODO: SetThradDesktop causes issues with input after switching.
//var inputDesktop = Win32Interop.OpenInputDesktop();
//User32.SetThreadDesktop(inputDesktop);
//User32.CloseDesktop(inputDesktop);
Connection.InvokeAsync("SwitchingDesktops", Viewers.Keys.ToList()).Wait();
var result = Win32Interop.OpenInteractiveProcess(Assembly.GetExecutingAssembly().Location + $" -mode {Mode.ToString()} -requester {RequesterID} -serviceid {ServiceID} -host {Host} -relaunch true -desktop {desktopName} -viewers {String.Join(",", Viewers.Keys.ToList())}", desktopName, true, out _);
if (!result)
{
Logger.Write($"Desktop switch to {desktopName} failed.");
OutgoingMessages.SendConnectionFailedToViewers(Viewers.Keys.ToList()).Wait();
}
}
}
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), Program.ArgDict["mode"]);
Host = Program.ArgDict["host"];
if (Mode == AppMode.Unattended)
{
RequesterID = Program.ArgDict["requester"];
CurrentDesktopName = Program.ArgDict["desktop"];
ServiceID = Program.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

@ -1,229 +0,0 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely_ScreenCast.Capture;
using Remotely_ScreenCast.Utilities;
using Remotely_ScreenCast;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Win32;
using System.Net;
using System.IO;
using System.Diagnostics;
using Remotely_ScreenCast.Models;
namespace Remotely_ScreenCast.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

@ -1,354 +0,0 @@
using Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using static Win32.ADVAPI32;
using static Win32.User32;
using System.Windows.Forms;
namespace Win32
{
public class Win32Interop
{
public static bool OpenInteractiveProcess(string applicationName, string desktopName, bool hiddenWindow, out PROCESS_INFORMATION procInfo)
{
uint winlogonPid = 0;
IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
procInfo = new PROCESS_INFORMATION();
// Obtain session ID for active session.
uint dwSessionId = Kernel32.WTSGetActiveConsoleSessionId();
// Check for RDP session. If active, use that session ID instead.
var rdpSessionID = GetRDPSession();
if (rdpSessionID > 0)
{
dwSessionId = rdpSessionID;
}
// Obtain the process ID of the winlogon process that is running within the currently active session.
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// Obtain a handle to the winlogon process.
hProcess = Kernel32.OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// Obtain a handle to the access token of the winlogon process.
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
Kernel32.CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// Copy the access token of the winlogon process; the newly created token will be a primary token.
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out hUserTokenDup))
{
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
return false;
}
// By default, CreateProcessAsUser creates a process on a non-interactive window station, meaning
// the window station has a desktop that is invisible and the process is incapable of receiving
// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
// interaction with the new process.
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
si.lpDesktop = @"winsta0\" + desktopName;
// Flags that specify the priority and creation method of the process.
uint dwCreationFlags;
if (hiddenWindow)
{
dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | DETACHED_PROCESS;
}
else
{
dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
}
// Create a new process in the current user's logon session.
bool result = CreateProcessAsUser(hUserTokenDup, null, applicationName, ref sa, ref sa, false, dwCreationFlags, IntPtr.Zero, null, ref si, out procInfo);
// Invalidate the handles.
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
Kernel32.CloseHandle(hUserTokenDup);
return result;
}
public static uint GetRDPSession()
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSAPI32.WTSEnumerateSessions(WTSAPI32.WTS_CURRENT_SERVER_HANDLE, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTSAPI32.WTS_SESSION_INFO));
var sessList = new List<WTSAPI32.WTS_SESSION_INFO>();
Int64 current = (Int64)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTSAPI32.WTS_SESSION_INFO sessInf = (WTSAPI32.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTSAPI32.WTS_SESSION_INFO));
current += dataSize;
sessList.Add(sessInf);
}
}
uint retVal = 0;
var rdpSession = sessList.Find(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0);
if (sessList.Exists(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0))
{
retVal = (uint)rdpSession.SessionID;
}
return retVal;
}
public static IntPtr OpenInputDesktop()
{
return User32.OpenInputDesktop(0, false, ACCESS_MASK.GENERIC_ALL);
}
public static uint SendLeftMouseDown(int x, int y)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = x * (double)65535;
var normalizedY = y * (double)65535;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.LEFTDOWN | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = (UIntPtr)GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public static uint SendLeftMouseUp(int x, int y)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = x * (double)65535;
var normalizedY = y * (double)65535;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.LEFTUP | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = (UIntPtr)GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public static uint SendRightMouseDown(int x, int y)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = x * (double)65535;
var normalizedY = y * (double)65535;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.RIGHTDOWN | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = (UIntPtr)GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public static uint SendRightMouseUp(int x, int y)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = x * (double)65535;
var normalizedY = y * (double)65535;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.RIGHTUP | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = (UIntPtr)GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
// Offsets are used in case there's a multi-monitor setup where the left-most or top-most edge of the virtual screen
// is not 0.
public static uint SendMouseMove(double x, double y)
{
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = x * (double)65535;
var normalizedY = y * (double)65535;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.MOVE | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = (UIntPtr)GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public static uint SendMouseWheel(int deltaY)
{
if (deltaY < 0)
{
deltaY = -120;
}
else if (deltaY > 0)
{
deltaY = 120;
}
var union = new User32.InputUnion() { mi = new User32.MOUSEINPUT() { dwFlags = MOUSEEVENTF.WHEEL, dx = 0, dy = 0, time = 0, mouseData = deltaY, dwExtraInfo = GetMessageExtraInfo() } };
var input = new User32.INPUT() { type = InputType.MOUSE, U = union };
return SendInput(1, new User32.INPUT[] { input }, INPUT.Size);
}
public static void SendKeyDown(VirtualKey key)
{
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = key,
wScan = 0,
time = 0,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public static void SendKeyUp(VirtualKey key)
{
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = key,
wScan = 0,
time = 0,
dwFlags = KEYEVENTF.KEYUP,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
public static string GetCurrentDesktop()
{
var inputDesktop = OpenInputDesktop();
byte[] deskBytes = new byte[256];
uint lenNeeded;
var success = GetUserObjectInformationW(inputDesktop, UOI_NAME, deskBytes, 256, out lenNeeded);
if (!success)
{
CloseDesktop(inputDesktop);
return "Default";
}
var desktopName = Encoding.Unicode.GetString(deskBytes.Take((int)lenNeeded).ToArray()).Replace("\0", "");
CloseDesktop(inputDesktop);
return desktopName;
}
public static short ConvertJavaScriptKeyToVirtualKey(string key)
{
short keyCode;
switch (key)
{
case "Down":
case "ArrowDown":
keyCode = (short)VirtualKey.DOWN;
break;
case "Up":
case "ArrowUp":
keyCode = (short)VirtualKey.UP;
break;
case "Left":
case "ArrowLeft":
keyCode = (short)VirtualKey.LEFT;
break;
case "Right":
case "ArrowRight":
keyCode = (short)VirtualKey.RIGHT;
break;
case "Enter":
keyCode = (short)VirtualKey.RETURN;
break;
case "Esc":
case "Escape":
keyCode = (short)VirtualKey.ESCAPE;
break;
case "Alt":
keyCode = (short)VirtualKey.MENU;
break;
case "Control":
keyCode = (short)VirtualKey.CONTROL;
break;
case "Shift":
keyCode = (short)VirtualKey.SHIFT;
break;
case "PAUSE":
keyCode = (short)VirtualKey.PAUSE;
break;
case "BREAK":
keyCode = (short)VirtualKey.PAUSE;
break;
case "Backspace":
keyCode = (short)VirtualKey.BACK;
break;
case "Tab":
keyCode = (short)VirtualKey.TAB;
break;
case "CapsLock":
keyCode = (short)VirtualKey.CAPITAL;
break;
case "Delete":
keyCode = (short)VirtualKey.DELETE;
break;
case "Home":
keyCode = (short)VirtualKey.HOME;
break;
case "End":
keyCode = (short)VirtualKey.END;
break;
case "PageUp":
keyCode = (short)VirtualKey.PRIOR;
break;
case "PageDown":
keyCode = (short)VirtualKey.NEXT;
break;
case "NumLock":
keyCode = (short)VirtualKey.NUMLOCK;
break;
case "Insert":
keyCode = (short)VirtualKey.INSERT;
break;
case "ScrollLock":
keyCode = (short)VirtualKey.SCROLL;
break;
case "F1":
keyCode = (short)VirtualKey.F1;
break;
case "F2":
keyCode = (short)VirtualKey.F2;
break;
case "F3":
keyCode = (short)VirtualKey.F3;
break;
case "F4":
keyCode = (short)VirtualKey.F4;
break;
case "F5":
keyCode = (short)VirtualKey.F5;
break;
case "F6":
keyCode = (short)VirtualKey.F6;
break;
case "F7":
keyCode = (short)VirtualKey.F7;
break;
case "F8":
keyCode = (short)VirtualKey.F8;
break;
case "F9":
keyCode = (short)VirtualKey.F9;
break;
case "F10":
keyCode = (short)VirtualKey.F10;
break;
case "F11":
keyCode = (short)VirtualKey.F11;
break;
case "F12":
keyCode = (short)VirtualKey.F12;
break;
default:
keyCode = User32.VkKeyScan(Convert.ToChar(key));
break;
}
return keyCode;
}
}
}

View File

@ -1,21 +0,0 @@
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

@ -1,198 +0,0 @@
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

@ -1,137 +0,0 @@
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

@ -1,96 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,24 +0,0 @@
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

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

View File

@ -1,41 +0,0 @@
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

@ -1,151 +0,0 @@
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

@ -1,70 +0,0 @@
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

@ -1,76 +0,0 @@
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

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

View File

@ -1 +1 @@
2019.03.25.1255
2019.03.26.2309

View File

@ -57,8 +57,8 @@
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="1.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="2.10.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.3" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" PrivateAssets="All" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.0" />
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>

View File

@ -11,7 +11,8 @@ if [ "$1" = "--uninstall" ]; then
exit
fi
apt-get install unzip
apt-get -y install unzip
apt-get -y install libgdiplus
mkdir -p /usr/local/bin/Remotely/
cd /usr/local/bin/Remotely/
@ -48,9 +49,11 @@ WorkingDirectory=/usr/local/bin/Remotely/
ExecStart=/usr/local/bin/Remotely/Remotely_Agent
Restart=always
RestartSec=10
Environment=DISPLAY=:0
Environment="XAUTHORITY=/home/$USER/.Xauthority"
[Install]
WantedBy=multi-user.target
WantedBy=graphical.target
EOL
systemctl enable remotely-agent

View File

@ -106,10 +106,12 @@ export function ApplyInputHandlers(sockets) {
ConnectionBar.classList.remove("open");
RemoteControl.RCBrowserSockets.SendCtrlAltDel();
});
document.querySelector("#sessionIDInput, #nameInput").addEventListener("keypress", (ev) => {
if (ev.key.toLowerCase() == "enter") {
ConnectToClient();
}
document.querySelectorAll("#sessionIDInput, #nameInput").forEach(x => {
x.addEventListener("keypress", (ev) => {
if (ev.key.toLowerCase() == "enter") {
ConnectToClient();
}
});
});
document.querySelector("#connectButton").addEventListener("click", (ev) => {
ConnectToClient();

File diff suppressed because one or more lines are too long

View File

@ -114,10 +114,12 @@ export function ApplyInputHandlers(sockets: RCBrowserSockets) {
ConnectionBar.classList.remove("open");
RemoteControl.RCBrowserSockets.SendCtrlAltDel();
});
document.querySelector("#sessionIDInput, #nameInput").addEventListener("keypress", (ev: KeyboardEvent) => {
if (ev.key.toLowerCase() == "enter") {
ConnectToClient();
}
document.querySelectorAll("#sessionIDInput, #nameInput").forEach(x => {
x.addEventListener("keypress", (ev: KeyboardEvent) => {
if (ev.key.toLowerCase() == "enter") {
ConnectToClient();
}
})
});
document.querySelector("#connectButton").addEventListener("click", (ev) => {
ConnectToClient();

View File

@ -69,7 +69,7 @@ if ($ArgList.Contains("c")) {
if ((Test-Path -Path ".\Remotely_Agent\bin\Release\netcoreapp2.2\linux-x64\publish") -eq $true) {
Get-ChildItem -Path ".\Remotely_Agent\bin\Release\netcoreapp2.2\linux-x64\publish" | Remove-Item -Force -Recurse
}
Push-Location -Path ".\Remotely_Agent"
# Publish Core clients.
@ -81,15 +81,24 @@ if ($ArgList.Contains("c")) {
New-Item -Path ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x64\publish\ScreenCast\" -ItemType Directory -Force
New-Item -Path ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x86\publish\ScreenCast\" -ItemType Directory -Force
New-Item -Path ".\Remotely_Agent\bin\Release\netcoreapp2.2\linux-x64\publish\ScreenCast\" -ItemType Directory -Force
# Copy .NET Framework ScreenCaster to Agent output.
if ((Test-Path -Path ".\Remotely_ScreenCast\bin\Release\Remotely_ScreenCast.exe") -eq $true) {
Copy-Item -Path ".\Remotely_ScreenCast\bin\Release\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x64\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
Copy-Item -Path ".\Remotely_ScreenCast\bin\Release\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x86\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
# Copy .NET Framework ScreenCaster to Agent output folder.
if ((Test-Path -Path ".\Remotely_ScreenCast.Win\bin\Release\Remotely_ScreenCast.exe") -eq $true) {
Copy-Item -Path ".\Remotely_ScreenCast.Win\bin\Release\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x64\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
Copy-Item -Path ".\Remotely_ScreenCast.Win\bin\Release\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x86\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
}
elseif ((Test-Path -Path ".\Remotely_ScreenCast\bin\Debug\Remotely_ScreenCast.exe") -eq $true) {
Copy-Item -Path ".\Remotely_ScreenCast\bin\Debug\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x64\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
Copy-Item -Path ".\Remotely_ScreenCast\bin\Debug\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x86\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
elseif ((Test-Path -Path ".\Remotely_ScreenCast.Win\bin\Debug\Remotely_ScreenCast.exe") -eq $true) {
Copy-Item -Path ".\Remotely_ScreenCast.Win\bin\Debug\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x64\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
Copy-Item -Path ".\Remotely_ScreenCast.Win\bin\Debug\Remotely_ScreenCast.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\win10-x86\publish\ScreenCast\Remotely_ScreenCast.exe" -Force
}
# Copy Mono ScreenCaster to Agent output folder.
if ((Test-Path -Path ".\Remotely_ScreenCast.Mono\bin\Release\Remotely_ScreenCast.Mono.exe") -eq $true) {
Copy-Item -Path ".\Remotely_ScreenCast.Mono\bin\Release\Remotely_ScreenCast.Mono.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\linux-x64\publish\ScreenCast\Remotely_ScreenCast.Mono.exe" -Force
}
elseif ((Test-Path -Path ".\Remotely_ScreenCast.Mono\bin\Debug\Remotely_ScreenCast.Mono.exe") -eq $true) {
Copy-Item -Path ".\Remotely_ScreenCast.Mono\bin\Debug\Remotely_ScreenCast.Mono.exe" -Destination ".\Remotely_Agent\bin\Release\netcoreapp2.2\linux-x64\publish\ScreenCast\Remotely_ScreenCast.Mono.exe" -Force
}
# Compress Core clients.