Re-structure projects. Introduce WPF/Avalonia chat windows. Record session from browser.

This commit is contained in:
Jared Goodwin 2020-08-01 16:53:59 -07:00
parent 7f043d0a84
commit bae1d1d628
232 changed files with 16921 additions and 10865 deletions

2
.gitignore vendored
View File

@ -279,3 +279,5 @@ Server/wwwroot/Downloads/Win-x86/Remotely_Desktop.exe
/Server/ScaffoldingReadMe.txt
/Server/Remotely.db
/Desktop.Win.Wrapper/Remotely_Desktop.zip
/Server/Remotely.db-wal
/Server/Remotely.db-shm

View File

@ -25,7 +25,6 @@ namespace Remotely.Agent.Installer.Win.Services
public event EventHandler<string> ProgressMessageChanged;
public event EventHandler<int> ProgressValueChanged;
public static string CoreRuntimeVersion => "3.1.3";
private string InstallPath => Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Program Files", "Remotely");
private string Platform => Environment.Is64BitOperatingSystem ? "x64" : "x86";
private JavaScriptSerializer Serializer { get; } = new JavaScriptSerializer();
@ -44,8 +43,6 @@ namespace Remotely.Agent.Installer.Win.Services
return false;
}
//await InstallDesktpRuntimeIfNeeded();
StopService();
await StopProcesses();
@ -120,7 +117,7 @@ namespace Remotely.Agent.Installer.Win.Services
ClearInstallDirectory();
ProcessEx.StartHidden("cmd.exe", $"/c timeout 5 & rd /s /q \"{InstallPath}\"");
ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely ScreenCast\"").WaitForExit();
ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely Desktop\"").WaitForExit();
GetRegistryBaseKey().DeleteSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Remotely", false);
@ -135,9 +132,9 @@ namespace Remotely.Agent.Installer.Win.Services
private void AddFirewallRule()
{
var screenCastPath = Path.Combine(InstallPath, "ScreenCast", "Remotely_ScreenCast.exe");
ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely ScreenCast\"").WaitForExit();
ProcessEx.StartHidden("netsh", $"advfirewall firewall add rule name=\"Remotely ScreenCast\" program=\"{screenCastPath}\" protocol=any dir=in enable=yes action=allow profile=Private,Domain description=\"The agent that allows screen sharing and remote control for Remotely.\"").WaitForExit();
var screenCastPath = Path.Combine(InstallPath, "Desktop", "Remotely_Desktop.exe");
ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely Desktop\"").WaitForExit();
ProcessEx.StartHidden("netsh", $"advfirewall firewall add rule name=\"Remotely Desktop\" program=\"{screenCastPath}\" protocol=any dir=in enable=yes action=allow profile=Private,Domain description=\"The agent that allows screen sharing and remote control for Remotely.\"").WaitForExit();
}
private void BackupDirectory()
@ -328,55 +325,6 @@ namespace Remotely.Agent.Installer.Win.Services
}
}
private async Task InstallDesktpRuntimeIfNeeded()
{
Logger.Write("Checking for .NET Core runtime.");
var uninstallKeys = new List<RegistryKey>();
var runtimeInstalled = false;
foreach (var subkeyName in GetRegistryBaseKey().OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\", false).GetSubKeyNames())
{
var subkey = GetRegistryBaseKey().OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + subkeyName, false);
if (subkey?.GetValue("DisplayName")?.ToString()?.Contains($"Microsoft Windows Desktop Runtime - {CoreRuntimeVersion}") == true)
{
runtimeInstalled = true;
break;
}
}
if (!runtimeInstalled)
{
Logger.Write("Downloading .NET Core runtime.");
ProgressMessageChanged.Invoke(this, "Downloading the .NET Core runtime.");
var client = new WebClient();
client.DownloadProgressChanged += (sender, args) =>
{
ProgressValueChanged?.Invoke(this, args.ProgressPercentage);
};
var downloadUrl = string.Empty;
if (Environment.Is64BitOperatingSystem)
{
downloadUrl = "https://download.visualstudio.microsoft.com/download/pr/5954c748-86a1-4823-9e7d-d35f6039317a/169e82cbf6fdeb678c5558c5d0a83834/windowsdesktop-runtime-3.1.3-win-x64.exe";
}
else
{
downloadUrl = "https://download.visualstudio.microsoft.com/download/pr/7cd5c874-5d11-4e72-81f0-4a005d956708/0eb310169770c893407169fc3abaac4f/windowsdesktop-runtime-3.1.3-win-x86.exe";
}
var targetFile = Path.Combine(Path.GetTempPath(), "windowsdesktop-runtime.exe");
await client.DownloadFileTaskAsync(downloadUrl, targetFile);
Logger.Write("Installing .NET Core runtime.");
ProgressMessageChanged?.Invoke(this, "Installing the .NET Core runtime.");
ProgressValueChanged?.Invoke(this, 0);
await Task.Run(() => { ProcessEx.StartHidden(targetFile, "/install /quiet /norestart").WaitForExit(); });
}
else
{
Logger.Write(".NET Core runtime already installed.");
}
}
private void InstallService()
{
Logger.Write("Installing service.");
@ -437,7 +385,7 @@ namespace Remotely.Agent.Installer.Win.Services
private async Task StopProcesses()
{
ProgressMessageChanged?.Invoke(this, "Stopping Remotely processes.");
var procs = Process.GetProcessesByName("Remotely_Agent").Concat(Process.GetProcessesByName("Remotely_ScreenCast"));
var procs = Process.GetProcessesByName("Remotely_Agent").Concat(Process.GetProcessesByName("Remotely_Desktop"));
foreach (var proc in procs)
{

View File

@ -23,22 +23,22 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="3.1.6" />
<PackageReference Include="Microsoft.Management.Infrastructure" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.4" />
<PackageReference Include="Microsoft.PowerShell.Commands.Diagnostics" Version="7.0.1" />
<PackageReference Include="Microsoft.PowerShell.Commands.Management" Version="7.0.1" />
<PackageReference Include="Microsoft.PowerShell.Commands.Utility" Version="7.0.1" />
<PackageReference Include="Microsoft.PowerShell.CoreCLR.Eventing" Version="7.0.1" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.1" />
<PackageReference Include="Microsoft.PowerShell.Security" Version="7.0.1" />
<PackageReference Include="Microsoft.WSMan.Management" Version="7.0.1" />
<PackageReference Include="Microsoft.WSMan.Runtime" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.6" />
<PackageReference Include="Microsoft.PowerShell.Commands.Diagnostics" Version="7.0.3" />
<PackageReference Include="Microsoft.PowerShell.Commands.Management" Version="7.0.3" />
<PackageReference Include="Microsoft.PowerShell.Commands.Utility" Version="7.0.3" />
<PackageReference Include="Microsoft.PowerShell.CoreCLR.Eventing" Version="7.0.3" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.3" />
<PackageReference Include="Microsoft.PowerShell.Security" Version="7.0.3" />
<PackageReference Include="Microsoft.WSMan.Management" Version="7.0.3" />
<PackageReference Include="Microsoft.WSMan.Runtime" Version="7.0.3" />
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="System.Management.Automation" Version="7.0.1" />
<PackageReference Include="System.Management.Automation" Version="7.0.3" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.7.0" />
</ItemGroup>
@ -52,9 +52,7 @@
<ItemGroup>
<Compile Update="Services\WindowsService.cs">
<SubType>Component</SubType>
</Compile>
<Compile Update="Services\WindowsService.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -39,7 +39,7 @@ namespace Remotely.Agent
{
builder.AddConsole().AddDebug();
});
serviceCollection.AddSingleton<DeviceSocket>();
serviceCollection.AddSingleton<AgentSocket>();
serviceCollection.AddScoped<ChatClientService>();
serviceCollection.AddTransient<Bash>();
serviceCollection.AddTransient<CMD>();
@ -79,11 +79,11 @@ namespace Remotely.Agent
await Services.GetRequiredService<Updater>().BeginChecking();
await Services.GetRequiredService<DeviceSocket>().Connect();
await Services.GetRequiredService<AgentSocket>().Connect();
}
finally
{
await Services.GetRequiredService<DeviceSocket>().HandleConnection();
await Services.GetRequiredService<AgentSocket>().HandleConnection();
}
}

View File

@ -18,9 +18,9 @@ using Remotely.Shared.Enums;
namespace Remotely.Agent.Services
{
public class DeviceSocket
public class AgentSocket
{
public DeviceSocket(ConfigService configService,
public AgentSocket(ConfigService configService,
Uninstaller uninstaller,
CommandExecutor commandExecutor,
ScriptRunner scriptRunner,
@ -52,7 +52,7 @@ namespace Remotely.Agent.Services
ConnectionInfo = ConfigService.GetConnectionInfo();
HubConnection = new HubConnectionBuilder()
.WithUrl(ConnectionInfo.Host + "/DeviceHub")
.WithUrl(ConnectionInfo.Host + "/AgentHub")
.Build();
RegisterMessageHandlers();
@ -121,7 +121,7 @@ namespace Remotely.Agent.Services
var waitTime = new Random().Next(1000, 30000);
Logger.Write($"Websocket closed. Reconnecting in {waitTime / 1000} seconds...");
await Task.Delay(waitTime);
await Program.Services.GetRequiredService<DeviceSocket>().Connect();
await Program.Services.GetRequiredService<AgentSocket>().Connect();
await Program.Services.GetRequiredService<Updater>().CheckForUpdates();
}
}
@ -149,8 +149,8 @@ namespace Remotely.Agent.Services
// TODO: Remove possibility for circular dependencies in the future
// by emitting these events so other services can listen for them.
HubConnection.On("Chat", async (string message, string orgName, string senderConnectionID) => {
await ChatService.SendMessage(message, orgName, senderConnectionID, HubConnection);
HubConnection.On("Chat", async (string senderName, string message, string orgName, bool disconnected, string senderConnectionID) => {
await ChatService.SendMessage(senderName, message, orgName, disconnected, senderConnectionID, HubConnection);
});
HubConnection.On("DownloadFile", async (string filePath, string senderConnectionID) =>
{

View File

@ -24,14 +24,14 @@ namespace Remotely.Agent.Services
{
try
{
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", EnvironmentHelper.ScreenCastExecutableFileName);
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName);
if (!File.Exists(rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage", "Chat executable not found on target device.", "Executable not found on device.", requesterID);
}
// Start ScreenCast.
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", $"Starting chat service...", "Starting chat service.", requesterID);
if (EnvironmentHelper.IsWindows)
{
@ -50,7 +50,7 @@ namespace Remotely.Agent.Services
out var procInfo);
if (!result)
{
await hubConnection.SendAsync("DisplayMessage", "Remote control failed to start on target device.", "Failed to start remote control.", requesterID);
await hubConnection.SendAsync("DisplayMessage", "Chat service failed to start on target device.", "Failed to start chat service.", requesterID);
}
else
{
@ -60,14 +60,14 @@ namespace Remotely.Agent.Services
}
else if (EnvironmentHelper.IsLinux)
{
var args = $"xterm -e {rcBinaryPath} -mode Chat -requester \"{requesterID}\" -organization \"{orgName}\" & disown";
return StartLinuxScreenCaster(args);
var args = $"{rcBinaryPath} -mode Chat -requester \"{requesterID}\" -organization \"{orgName}\" & disown";
return StartLinuxDesktopApp(args);
}
}
catch (Exception ex)
{
Logger.Write(ex);
await hubConnection.SendAsync("DisplayMessage", "Remote control failed to start on target device.", "Failed to start remote control.", requesterID);
await hubConnection.SendAsync("DisplayMessage", "Chat service failed to start on target device.", "Failed to start chat service.", requesterID);
}
return -1;
}
@ -76,7 +76,7 @@ namespace Remotely.Agent.Services
{
try
{
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", EnvironmentHelper.ScreenCastExecutableFileName);
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName);
if (!File.Exists(rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage", "Remote control executable not found on target device.", "Executable not found on device.", requesterID);
@ -84,13 +84,15 @@ namespace Remotely.Agent.Services
}
// Start ScreenCast.
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", $"Starting remote control...", "Starting remote control.", requesterID);
if (EnvironmentHelper.IsWindows)
{
if (EnvironmentHelper.IsDebug)
{
// SignalR Connection IDs might start with a hyphen. We surround them
// with quotes so the command line will be parsed correctly.
Process.Start(rcBinaryPath, $"-mode Unattended -requester \"{requesterID}\" -serviceid \"{serviceID}\" -deviceid {ConnectionInfo.DeviceID} -host {ConnectionInfo.Host}");
}
else
@ -110,7 +112,7 @@ namespace Remotely.Agent.Services
else if (EnvironmentHelper.IsLinux)
{
var args = $"{rcBinaryPath} -mode Unattended -requester \"{requesterID}\" -serviceid \"{serviceID}\" -deviceid {ConnectionInfo.DeviceID} -host {ConnectionInfo.Host} & disown";
StartLinuxScreenCaster(args);
StartLinuxDesktopApp(args);
}
}
catch (Exception ex)
@ -123,13 +125,15 @@ namespace Remotely.Agent.Services
{
try
{
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", EnvironmentHelper.ScreenCastExecutableFileName);
// Start ScreenCast.
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName);
// Start Desktop app.
if (EnvironmentHelper.IsWindows)
{
Logger.Write("Restarting screen caster.");
if (EnvironmentHelper.IsDebug)
{
// SignalR Connection IDs might start with a hyphen. We surround them
// with quotes so the command line will be parsed correctly.
Process.Start(rcBinaryPath, $"-mode Unattended -requester \"{requesterID}\" -serviceid \"{serviceID}\" -deviceid {ConnectionInfo.DeviceID} -host {ConnectionInfo.Host} -relaunch true -viewers {String.Join(",", viewerIDs)}");
}
else
@ -156,7 +160,7 @@ namespace Remotely.Agent.Services
else if (EnvironmentHelper.IsLinux)
{
var args = $"{rcBinaryPath} -mode Unattended -requester \"{requesterID}\" -serviceid \"{serviceID}\" -deviceid {ConnectionInfo.DeviceID} -host {ConnectionInfo.Host} -relaunch true -viewers {string.Join(",", viewerIDs)} & disown";
StartLinuxScreenCaster(args);
StartLinuxDesktopApp(args);
}
}
catch (Exception ex)
@ -167,7 +171,7 @@ namespace Remotely.Agent.Services
}
}
private int StartLinuxScreenCaster(string args)
private int StartLinuxDesktopApp(string args)
{
var xauthority = string.Empty;

View File

@ -1,11 +1,13 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely.Agent.Models;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Runtime.Caching;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -32,44 +34,58 @@ namespace Remotely.Agent.Services
};
private MemoryCache ChatClients { get; } = new MemoryCache("ChatClients");
public async Task SendMessage(string message, string orgName, string senderConnectionID, HubConnection hubConnection)
public async Task SendMessage(string senderName,
string message,
string orgName,
bool disconnected,
string senderConnectionID,
HubConnection hubConnection)
{
if (!await MessageLock.WaitAsync(30000))
{
Logger.Write("Timed out waiting for chat message lock.", Shared.Enums.EventType.Warning);
return;
}
try
{
if (await MessageLock.WaitAsync(30000))
ChatSession chatSession;
if (!ChatClients.Contains(senderConnectionID))
{
ChatSession chatSession;
if (!ChatClients.Contains(senderConnectionID))
if (disconnected)
{
var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", EnvironmentHelper.ScreenCastExecutableFileName);
var procID = await AppLauncher.LaunchChatService(orgName, senderConnectionID, hubConnection);
var clientPipe = new NamedPipeClientStream(".", "Remotely_Chat" + senderConnectionID, PipeDirection.InOut, PipeOptions.Asynchronous);
clientPipe.Connect(15000);
if (!clientPipe.IsConnected)
{
Logger.Write("Failed to connect to chat host.");
return;
}
chatSession = new ChatSession() { PipeStream = clientPipe, ProcessID = procID };
_ = Task.Run(async () => { await ReadFromStream(chatSession.PipeStream, senderConnectionID, hubConnection); });
ChatClients.Add(senderConnectionID, chatSession, CacheItemPolicy);
}
chatSession = (ChatSession)ChatClients.Get(senderConnectionID);
if (!chatSession.PipeStream.IsConnected)
{
ChatClients.Remove(senderConnectionID);
await hubConnection.SendAsync("DisplayMessage", "Chat disconnected. Please try again.", "Chat disconnected.");
// Don't start a new session just to show a disconnected message.
return;
}
using (var sw = new StreamWriter(chatSession.PipeStream, leaveOpen: true))
var procID = await AppLauncher.LaunchChatService(orgName, senderConnectionID, hubConnection);
var clientPipe = new NamedPipeClientStream(".", "Remotely_Chat" + senderConnectionID, PipeDirection.InOut, PipeOptions.Asynchronous);
clientPipe.Connect(15000);
if (!clientPipe.IsConnected)
{
await sw.WriteLineAsync(message);
await sw.FlushAsync();
Logger.Write("Failed to connect to chat host.");
return;
}
chatSession = new ChatSession() { PipeStream = clientPipe, ProcessID = procID };
_ = Task.Run(async () => { await ReadFromStream(chatSession.PipeStream, senderConnectionID, hubConnection); });
ChatClients.Add(senderConnectionID, chatSession, CacheItemPolicy);
}
chatSession = (ChatSession)ChatClients.Get(senderConnectionID);
if (!chatSession.PipeStream.IsConnected)
{
ChatClients.Remove(senderConnectionID);
await hubConnection.SendAsync("DisplayMessage", "Chat disconnected. Please try again.", "Chat disconnected.", senderConnectionID);
return;
}
using (var sw = new StreamWriter(chatSession.PipeStream, leaveOpen: true))
{
var chatMessage = new ChatMessage(senderName, message, disconnected);
await sw.WriteLineAsync(JsonSerializer.Serialize(chatMessage));
await sw.FlushAsync();
}
}
catch (Exception ex)
@ -88,10 +104,15 @@ namespace Remotely.Agent.Services
{
using (var sr = new StreamReader(clientPipe, leaveOpen: true))
{
var message = await sr.ReadLineAsync();
await hubConnection.SendAsync("Chat", message, senderConnectionID);
var messageJson = await sr.ReadLineAsync();
if (!string.IsNullOrWhiteSpace(messageJson))
{
var chatMessage = JsonSerializer.Deserialize<ChatMessage>(messageJson);
await hubConnection.SendAsync("Chat", chatMessage.Message, false, senderConnectionID);
}
}
}
await hubConnection.SendAsync("Chat", string.Empty, true, senderConnectionID);
ChatClients.Remove(senderConnectionID);
}
}

View File

@ -22,7 +22,7 @@ namespace Remotely.Agent.Services
changeDescription.Reason == SessionChangeReason.RemoteDisconnect)
{
foreach (var screenCaster in Process.GetProcessesByName("Remotely_ScreenCast"))
foreach (var screenCaster in Process.GetProcessesByName("Remotely_Desktop"))
{
if (screenCaster.SessionId == changeDescription.SessionId)
{

View File

@ -1,15 +1,14 @@
using Remotely.Shared.Models;
using Remotely.ScreenCast.Core.Enums;
using Remotely.ScreenCast.Core.Models;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Services;
using Remotely.Desktop.Core.Enums;
using Remotely.Desktop.Core.Models;
using Remotely.Desktop.Core.Services;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Remotely.Shared.Utilities;
namespace Remotely.ScreenCast.Core
namespace Remotely.Desktop.Core
{
public class Conductor
{
@ -62,29 +61,14 @@ namespace Remotely.ScreenCast.Core
{
if (!key.Contains("-"))
{
Logger.Write("Command line arguments are invalid.");
Logger.Write($"Command line arguments are invalid. Key: {key}");
i -= 1;
continue;
}
key = key.Trim().Replace("-", "").ToLower();
if (i + 1 == args.Length)
{
ArgDict.Add(key, "true");
continue;
}
var value = args[i + 1];
if (value != null)
{
if (value.StartsWith("-"))
{
ArgDict.Add(key, "true");
i -= 1;
}
else
{
ArgDict.Add(key, args[i + 1].Trim());
}
}
ArgDict.Add(key, args[i + 1].Trim());
}
}
catch (Exception ex)
@ -94,7 +78,14 @@ namespace Remotely.ScreenCast.Core
}
Mode = (AppMode)Enum.Parse(typeof(AppMode), ArgDict["mode"], true);
if (ArgDict.TryGetValue("mode", out var mode))
{
Mode = (AppMode)Enum.Parse(typeof(AppMode), mode, true);
}
else
{
Mode = AppMode.Normal;
}
if (ArgDict.TryGetValue("host", out var host))
{
@ -102,11 +93,11 @@ namespace Remotely.ScreenCast.Core
}
if (ArgDict.TryGetValue("requester", out var requester))
{
RequesterID = requester.Trim('"');
RequesterID = requester;
}
if (ArgDict.TryGetValue("serviceid", out var serviceID))
{
ServiceID = serviceID.Trim('"');
ServiceID = serviceID;
}
if (ArgDict.TryGetValue("deviceid", out var deviceID))
{

View File

@ -2,8 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Remotely.ScreenCast.Core</RootNamespace>
<AssemblyName>Remotely_ScreenCast.Core</AssemblyName>
<RootNamespace>Remotely.Desktop.Core</RootNamespace>
<AssemblyName>Remotely_Desktop.Core</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>
@ -32,21 +32,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.1.6" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="3.1.6" />
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="1.0.3" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>

View File

@ -1,4 +1,4 @@
namespace Remotely.ScreenCast.Core.Enums
namespace Remotely.Desktop.Core.Enums
{
public enum AppMode
{

View File

@ -1,6 +1,6 @@
using System;
namespace Remotely.ScreenCast.Core.Interfaces
namespace Remotely.Desktop.Core.Interfaces
{
public interface IAudioCapturer
{

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.Core.Interfaces
{
public interface IChatHostService
{
Task StartChat(string requesterID, string organizationName);
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Remotely.ScreenCast.Core.Interfaces
namespace Remotely.Desktop.Core.Interfaces
{
public interface IClipboardService
{

View File

@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Remotely.ScreenCast.Core.Interfaces
namespace Remotely.Desktop.Core.Interfaces
{
public interface ICursorIconWatcher
{

View File

@ -1,6 +1,6 @@
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Models;
namespace Remotely.ScreenCast.Core.Interfaces
namespace Remotely.Desktop.Core.Interfaces
{
public interface IKeyboardMouseInput
{

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Drawing;
namespace Remotely.ScreenCast.Core.Interfaces
namespace Remotely.Desktop.Core.Interfaces
{
public interface IScreenCapturer : IDisposable
{

View File

@ -1,7 +1,7 @@
using Remotely.Shared.Models;
using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Interfaces
namespace Remotely.Desktop.Core.Interfaces
{
public interface IScreenCaster
{

View File

@ -1,20 +1,23 @@
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Helpers;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Drawing.Imaging;
using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Models
namespace Remotely.Desktop.Core.Models
{
public class Viewer : IDisposable
{
private int imageQuality;
private DateTimeOffset lastQualityAdjustment;
private readonly int defaultImageQuality = 60;
public Viewer(CasterSocket casterSocket,
IScreenCapturer screenCapturer,
@ -26,7 +29,7 @@ namespace Remotely.ScreenCast.Core.Models
CasterSocket = casterSocket;
WebRtcSessionFactory = webRtcSessionFactory;
EncoderParams = new EncoderParameters();
ImageQuality = 60;
ImageQuality = defaultImageQuality;
ClipboardService = clipboardService;
ClipboardService.ClipboardTextChanged += ClipboardService_ClipboardTextChanged;
AudioCapturer = audioCapturer;
@ -66,21 +69,13 @@ namespace Remotely.ScreenCast.Core.Models
public bool IsConnected => CasterSocket.IsConnected;
public string Name { get; set; }
public ConcurrentQueue<DateTimeOffset> PendingSentFrames { get; } = new ConcurrentQueue<DateTimeOffset>();
public WebRtcSession RtcSession { get; set; }
public string ViewerConnectionID { get; set; }
public int WebSocketBuffer { get; set; }
private IAudioCapturer AudioCapturer { get; }
private CasterSocket CasterSocket { get; }
private IClipboardService ClipboardService { get; }
private int CurrentBuffer
{
get
{
return IsUsingWebRtc() ?
(int)RtcSession.CurrentBuffer :
WebSocketBuffer;
}
}
private IWebRtcSessionFactory WebRtcSessionFactory { get; }
public void Dispose()
{
@ -114,7 +109,7 @@ namespace Remotely.ScreenCast.Core.Models
public bool IsStalled()
{
return RtcSession?.CurrentBuffer > 1_000_000 || WebSocketBuffer > 1_000_000;
return PendingSentFrames.TryPeek(out var result) && DateTimeOffset.Now - result > TimeSpan.FromSeconds(15);
}
public bool IsUsingWebRtc()
@ -147,14 +142,14 @@ namespace Remotely.ScreenCast.Core.Models
public async Task SendScreenCapture(byte[] encodedImageBytes, int left, int top, int width, int height)
{
PendingSentFrames.Enqueue(DateTimeOffset.Now);
await SendToViewer(() =>
{
RtcSession.SendCaptureFrame(left, top, width, height, encodedImageBytes, ImageQuality);
WebSocketBuffer = 0;
}, async () =>
{
await CasterSocket.SendScreenCapture(encodedImageBytes, ViewerConnectionID, left, top, width, height, ImageQuality);
WebSocketBuffer += encodedImageBytes.Length;
});
}
@ -181,22 +176,25 @@ namespace Remotely.ScreenCast.Core.Models
public async Task ThrottleIfNeeded()
{
if (CurrentBuffer > 200_000)
if (AutoAdjustQuality && DateTimeOffset.Now - lastQualityAdjustment > TimeSpan.FromSeconds(2))
{
if (AutoAdjustQuality)
lastQualityAdjustment = DateTimeOffset.Now;
if (PendingSentFrames.TryPeek(out var result) && DateTimeOffset.Now - result > TimeSpan.FromMilliseconds(200))
{
var imageAdjust = (double)CurrentBuffer / 200_000 * 5;
ImageQuality = (int)Math.Max(ImageQuality - imageAdjust, 0);
Logger.Write($"Auto-adjusting image quality. Quality: {ImageQuality}");
var latency = (DateTimeOffset.Now - result).TotalMilliseconds;
ImageQuality = (int)(200 / latency * defaultImageQuality);
}
else
{
ImageQuality = defaultImageQuality;
}
}
Logger.Write($"Throttling output due to buffer size. Size: {CurrentBuffer}.");
await TaskHelper.DelayUntil(() => CurrentBuffer < 200_000, TimeSpan.FromSeconds(1));
}
else if (AutoAdjustQuality)
{
ImageQuality = Math.Min(ImageQuality + 1, 60);
}
await TaskHelper.DelayUntil(() => PendingSentFrames.Count < 5 &&
(
!PendingSentFrames.TryPeek(out var result) || DateTimeOffset.Now - result < TimeSpan.FromSeconds(1)
),
TimeSpan.MaxValue);
}
private async void AudioCapturer_AudioSampleReady(object sender, byte[] sample)

View File

@ -1,6 +1,6 @@
using System;
namespace Remotely.ScreenCast.Core
namespace Remotely.Desktop.Core
{
public class ServiceContainer
{

View File

@ -7,12 +7,12 @@ using System.Threading.Tasks;
using System.Diagnostics;
using System.IO;
using System.Net;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.Desktop.Core.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Remotely.Shared.Utilities;
using System.Threading;
namespace Remotely.ScreenCast.Core.Communication
namespace Remotely.Desktop.Core.Services
{
public class CasterSocket
{
@ -47,7 +47,7 @@ namespace Remotely.ScreenCast.Core.Communication
await Connection.DisposeAsync();
}
Connection = new HubConnectionBuilder()
.WithUrl($"{host}/RCDeviceHub")
.WithUrl($"{host}/CasterHub")
.AddMessagePackProtocol()
.WithAutomaticReconnect()
.Build();
@ -377,11 +377,17 @@ namespace Remotely.ScreenCast.Core.Communication
conductor.InvokeViewerRemoved(viewerID);
});
Connection.On("FrameReceived", (int bytesReceived, string viewerID) =>
Connection.On("FrameReceived", (string viewerID) =>
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.WebSocketBuffer = Math.Max(0, viewer.WebSocketBuffer - bytesReceived);
for (int i = 0; i < 5; i++)
{
if (viewer.PendingSentFrames.TryDequeue(out _))
{
break;
}
}
}
});

View File

@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using Remotely.Shared.Win32;
namespace Remotely.ScreenCast.Core.Services
namespace Remotely.Desktop.Core.Services
{
public interface IFileTransferService
{

View File

@ -1,10 +1,10 @@
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Models;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Concurrent;
using System.Timers;
namespace Remotely.ScreenCast.Core.Services
namespace Remotely.Desktop.Core.Services
{
public class IdleTimer
{

View File

@ -1,7 +1,6 @@
using MessagePack;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Models;
using Remotely.Shared.Enums;
using Remotely.Shared.Models.RtcDtos;
using Remotely.Shared.Utilities;
@ -11,9 +10,10 @@ using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Services
namespace Remotely.Desktop.Core.Services
{
public interface IRtcMessageHandler
{
@ -129,6 +129,9 @@ namespace Remotely.ScreenCast.Core.Services
case BinaryDtoType.SetKeyStatesUp:
SetKeyStatesUp();
break;
case BinaryDtoType.FrameReceived:
HandleFrameReceived();
break;
default:
break;
}
@ -167,6 +170,16 @@ namespace Remotely.ScreenCast.Core.Services
await Viewer.SendWindowsSessions();
}
private void HandleFrameReceived()
{
for (int i = 0; i < 5; i++)
{
if (Viewer.PendingSentFrames.TryDequeue(out _))
{
break;
}
}
}
private void KeyDown(byte[] message)
{
var dto = MessagePackSerializer.Deserialize<KeyDownDto>(message);

View File

@ -1,21 +1,20 @@
using Remotely.ScreenCast.Core.Models;
using Remotely.ScreenCast.Core.Communication;
using Remotely.Desktop.Core.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.Desktop.Core.Interfaces;
using System.Diagnostics;
using System.Drawing.Imaging;
using Microsoft.Extensions.DependencyInjection;
using Remotely.ScreenCast.Core.Utilities;
using Remotely.Desktop.Core.Utilities;
using Remotely.Shared.Utilities;
using System.Collections.Concurrent;
using Remotely.ScreenCast.Core.Enums;
using Remotely.Desktop.Core.Enums;
using Remotely.Shared.Models;
using Remotely.Shared.Win32;
namespace Remotely.ScreenCast.Core.Services
namespace Remotely.Desktop.Core.Services
{
public class ScreenCaster : IScreenCaster
{
@ -126,7 +125,6 @@ namespace Remotely.ScreenCast.Core.Services
if (encodedImageBytes?.Length > 0)
{
await viewer.SendScreenCapture(encodedImageBytes, diffArea.Left, diffArea.Top, diffArea.Width, diffArea.Height);
}
}

View File

@ -1,6 +1,5 @@
using MessagePack;
using Microsoft.MixedReality.WebRTC;
using Remotely.ScreenCast.Core.Services;
using Remotely.Shared.Models;
using Remotely.Shared.Models.RtcDtos;
using Remotely.Shared.Utilities;
@ -9,7 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Communication
namespace Remotely.Desktop.Core.Services
{
public class WebRtcSession : IDisposable
{

View File

@ -1,11 +1,10 @@
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace Remotely.ScreenCast.Core.Services
namespace Remotely.Desktop.Core.Services
{
public interface IWebRtcSessionFactory
{

View File

@ -5,7 +5,7 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Remotely.ScreenCast.Core.Utilities
namespace Remotely.Desktop.Core.Utilities
{
public class ImageUtils
{

View File

@ -1,6 +1,8 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Remotely.Desktop.Core;
using Remotely.Desktop.Linux.ViewModels;
using Remotely.Desktop.Linux.Views;
@ -15,14 +17,18 @@ namespace Remotely.Desktop.Linux
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
//if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
//{
// var conductor = ServiceContainer.Instance.GetRequiredService<Conductor>();
// if (conductor.Mode == Core.Enums.AppMode.Normal)
// {
// desktop.MainWindow = new MainWindow
// {
// DataContext = new MainWindowViewModel(),
// };
// }
//}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -5,7 +5,10 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Remotely.Desktop.Linux.Controls.HostNamePrompt"
xmlns:ViewModels="clr-namespace:Remotely.Desktop.Linux.ViewModels;assembly=Remotely_Desktop"
Title="Remotely Host Name" Height="150" Width="350" WindowStartupLocation="CenterScreen">
Title="Remotely Host Name"
Icon="/Assets/favicon.ico"
Height="150" Width="350"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<ViewModels:HostNamePromptViewModel/>
</Window.DataContext>

View File

@ -18,7 +18,6 @@ namespace Remotely.Desktop.Linux.Controls
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.Icon = MainWindow.Current?.Icon;
}
}
}

View File

@ -5,7 +5,8 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:vm="clr-namespace:Remotely.Desktop.Linux.ViewModels;assembly=Remotely_Desktop"
x:Class="Remotely.Desktop.Linux.Controls.MessageBox"
Title="{Binding Caption}" SizeToContent="WidthAndHeight" MinWidth="200" MinHeight="100" WindowStartupLocation="CenterScreen">
Icon="/Assets/favicon.ico"
Title="{Binding Caption}" SizeToContent="WidthAndHeight" MinWidth="300" MinHeight="100" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MessageBoxViewModel></vm:MessageBoxViewModel>
</Window.DataContext>

View File

@ -12,7 +12,7 @@ namespace Remotely.Desktop.Linux.Controls
public static async Task<MessageBoxResult> Show(string message, string caption, MessageBoxType type)
{
var messageBox = new MessageBox();
var viewModel = new MessageBoxViewModel();
var viewModel = messageBox.DataContext as MessageBoxViewModel;
viewModel.Caption = caption;
viewModel.Message = message;
@ -28,8 +28,6 @@ namespace Remotely.Desktop.Linux.Controls
break;
}
messageBox.DataContext = viewModel;
await messageBox.ShowDialog(MainWindow.Current);
return viewModel.Result;
@ -47,7 +45,6 @@ namespace Remotely.Desktop.Linux.Controls
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.Icon = MainWindow.Current?.Icon;
}
}

View File

@ -7,6 +7,21 @@
<RootNamespace>Remotely.Desktop.Linux</RootNamespace>
<Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
@ -19,29 +34,25 @@
<Compile Remove="Models\**" />
<EmbeddedResource Remove="Models\**" />
<None Remove="Models\**" />
<AvaloniaResource Remove="Controls\HostNamePrompt.xaml" />
<AvaloniaResource Remove="Controls\MessageBox.xaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\HostNamePrompt.xaml" />
<None Remove="Controls\MessageBox.xaml" />
<None Remove="Assets\Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Controls\HostNamePrompt.xaml">
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
<EmbeddedResource Include="Controls\MessageBox.xaml">
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
<PackageReference Include="Avalonia" Version="0.9.11" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.11" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.11" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.9.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScreenCast.Core\ScreenCast.Core.csproj" />
<ProjectReference Include="..\ScreenCast.Linux\ScreenCast.Linux.csproj" />
<ProjectReference Include="..\Desktop.Core\Desktop.Core.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Controls\HostNamePrompt.axaml.cs">
<DependentUpon>HostNamePrompt.axaml</DependentUpon>
</Compile>
<Compile Update="Controls\MessageBox.axaml.cs">
<DependentUpon>MessageBox.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@ -1,22 +1,121 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Models;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Linux.Services;
using Remotely.Desktop.Linux.Views;
using Remotely.Shared.Utilities;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Desktop.Linux
{
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToDebug()
.UseReactiveUI();
public static Conductor Conductor { get; private set; }
public static IServiceProvider Services => ServiceContainer.Instance;
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
BuildServices();
Conductor = Services.GetRequiredService<Conductor>();
Logger.Write("Processing Args: " + string.Join(", ", Environment.GetCommandLineArgs()));
Conductor.ProcessArgs(Environment.GetCommandLineArgs().SkipWhile(x => !x.StartsWith("-")).ToArray());
Task.Run(() =>
{
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
});
while (App.Current is null)
{
Thread.Sleep(100);
}
if (Conductor.Mode == Core.Enums.AppMode.Chat)
{
Services.GetRequiredService<IChatHostService>().StartChat(Conductor.RequesterID, Conductor.OrganizationName).Wait();
}
else if (Conductor.Mode == Core.Enums.AppMode.Unattended)
{
var casterSocket = Services.GetRequiredService<CasterSocket>();
casterSocket.Connect(Conductor.Host).ContinueWith(async (task) =>
{
await casterSocket.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName, Conductor.DeviceID);
await casterSocket.NotifyRequesterUnattendedReady(Conductor.RequesterID);
Services.GetRequiredService<IdleTimer>().Start();
Services.GetRequiredService<IClipboardService>().BeginWatching();
});
}
else
{
Dispatcher.UIThread.Post(() =>
{
App.Current.RunWithMainWindow<MainWindow>();
});
}
Thread.Sleep(Timeout.Infinite);
}
catch (Exception ex)
{
Logger.Write(ex);
throw;
}
}
private static void BuildServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
{
builder.AddConsole().AddDebug();
});
serviceCollection.AddSingleton<IScreenCaster, ScreenCaster>();
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputLinux>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceLinux>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerLinux>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<IChatHostService, ChatHostServiceLinux>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerLinux>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IFileTransferService, FileTransferService>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddSingleton<ICursorIconWatcher, CursorIconWatcherLinux>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
}
}

View File

@ -6,12 +6,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<Platform>x64</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\linux-x64\publish\ScreenCast</PublishDir>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\linux-x64\publish\Desktop</PublishDir>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>True</SelfContained>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishTrimmed>True</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,7 @@
{
"profiles": {
"Desktop.Linux": {
"commandName": "Project"
}
}
}

View File

@ -1,7 +1,7 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.Desktop.Core.Interfaces;
using System;
namespace Remotely.ScreenCast.Linux.Services
namespace Remotely.Desktop.Linux.Services
{
public class AudioCapturerLinux : IAudioCapturer
{

View File

@ -0,0 +1,99 @@
using Avalonia.Controls;
using Avalonia.Threading;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Linux.Controls;
using Remotely.Desktop.Linux.ViewModels;
using Remotely.Desktop.Linux.Views;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Desktop.Linux.Services
{
public class ChatHostServiceLinux : IChatHostService
{
private ChatWindowViewModel ChatViewModel { get; set; }
private NamedPipeServerStream NamedPipeStream { get; set; }
private StreamReader Reader { get; set; }
private StreamWriter Writer { get; set; }
public async Task StartChat(string requesterID, string organizationName)
{
NamedPipeStream = new NamedPipeServerStream("Remotely_Chat" + requesterID, PipeDirection.InOut, 10, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Writer = new StreamWriter(NamedPipeStream);
Reader = new StreamReader(NamedPipeStream);
var cts = new CancellationTokenSource(10000);
try
{
await NamedPipeStream.WaitForConnectionAsync(cts.Token);
}
catch (TaskCanceledException)
{
Logger.Write("A chat session was attempted, but the client failed to connect in time.", Shared.Enums.EventType.Warning);
Environment.Exit(0);
}
Dispatcher.UIThread.Post(() =>
{
var chatWindow = new ChatWindow();
chatWindow.Closing += ChatWindow_Closing; ;
ChatViewModel = chatWindow.DataContext as ChatWindowViewModel;
ChatViewModel.PipeStreamWriter = Writer;
ChatViewModel.OrganizationName = organizationName;
App.Current.Run(chatWindow);
});
_ = Task.Run(ReadFromStream);
}
private void ChatWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
try
{
NamedPipeStream?.Disconnect();
NamedPipeStream?.Dispose();
}
catch { }
}
private async Task ReadFromStream()
{
while (NamedPipeStream.IsConnected)
{
try
{
var messageJson = await Reader.ReadLineAsync();
if (!string.IsNullOrWhiteSpace(messageJson))
{
var chatMessage = JsonSerializer.Deserialize<ChatMessage>(messageJson);
await Dispatcher.UIThread.InvokeAsync(async () =>
{
if (chatMessage.Disconnected)
{
await MessageBox.Show("The partner has disconnected.", "Partner Disconnected", MessageBoxType.OK);
Environment.Exit(0);
return;
}
ChatViewModel.SenderName = chatMessage.SenderName;
ChatViewModel.ChatMessages.Add(chatMessage);
});
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}
}

View File

@ -0,0 +1,81 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Utilities;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Desktop.Linux.Services
{
public class ClipboardServiceLinux : IClipboardService
{
private CancellationTokenSource cancelTokenSource;
public event EventHandler<string> ClipboardTextChanged;
private string ClipboardText { get; set; }
public void BeginWatching()
{
try
{
StopWatching();
}
finally
{
cancelTokenSource = new CancellationTokenSource();
_ = Task.Run(() => WatchClipboard(cancelTokenSource.Token));
}
}
public void SetText(string clipboardText)
{
try
{
if (string.IsNullOrWhiteSpace(clipboardText))
{
App.Current.Clipboard.ClearAsync().Wait();
}
else
{
App.Current.Clipboard.SetTextAsync(clipboardText).Wait();
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void StopWatching()
{
cancelTokenSource?.Cancel();
cancelTokenSource?.Dispose();
}
private void WatchClipboard(CancellationToken cancelToken)
{
while (!cancelToken.IsCancellationRequested &&
!Environment.HasShutdownStarted)
{
try
{
var currentText = EnvironmentHelper.StartProcessWithResults("xclip", "-o");
// TODO: Switch back when fixed.
//var currentText = await App.Current.Clipboard.GetTextAsync();
if (!string.IsNullOrEmpty(currentText) && currentText != ClipboardText)
{
ClipboardText = currentText;
ClipboardTextChanged?.Invoke(this, ClipboardText);
}
}
finally
{
Thread.Sleep(500);
}
}
}
}
}

View File

@ -1,11 +1,11 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace Remotely.ScreenCast.Linux.Services
namespace Remotely.Desktop.Linux.Services
{
public class CursorIconWatcherLinux : ICursorIconWatcher
{

View File

@ -1,11 +1,11 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Linux.X11Interop;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Linux.X11Interop;
using System;
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Models;
using Remotely.Shared.Utilities;
namespace Remotely.ScreenCast.Linux.Services
namespace Remotely.Desktop.Linux.Services
{
public class KeyboardMouseInputLinux : IKeyboardMouseInput
{

View File

@ -1,6 +1,6 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Linux.X11Interop;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Linux.X11Interop;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
@ -9,7 +9,7 @@ using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
namespace Remotely.ScreenCast.Linux.Services
namespace Remotely.Desktop.Linux.Services
{
public class ScreenCapturerLinux : IScreenCapturer
{

View File

@ -0,0 +1,73 @@
using Avalonia.Controls;
using ReactiveUI;
using Remotely.Desktop.Linux.Services;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Remotely.Desktop.Linux.ViewModels
{
public class ChatWindowViewModel : ViewModelBase
{
private string inputText;
private string organizationName = "your IT provider";
private string senderName = "a technician";
public ObservableCollection<ChatMessage> ChatMessages { get; } = new ObservableCollection<ChatMessage>();
public string ChatSessionHeader => $"Chat session with {OrganizationName}";
public ICommand CloseCommand => new Executor((param) =>
{
(param as Window)?.Close();
});
public string InputText
{
get => inputText;
set => this.RaiseAndSetIfChanged(ref inputText, value);
}
public ICommand MinimizeCommand => new Executor((param) =>
{
(param as Window).WindowState = WindowState.Minimized;
});
public string OrganizationName
{
get => organizationName;
set
{
this.RaiseAndSetIfChanged(ref organizationName, value);
this.RaisePropertyChanged(nameof(ChatSessionHeader));
}
}
public StreamWriter PipeStreamWriter { get; set; }
public string SenderName
{
get => senderName;
set => this.RaiseAndSetIfChanged(ref senderName, value);
}
public async Task SendChatMessage()
{
if (string.IsNullOrWhiteSpace(InputText))
{
return;
}
var chatMessage = new ChatMessage(string.Empty, InputText);
InputText = string.Empty;
await PipeStreamWriter.WriteLineAsync(JsonSerializer.Serialize(chatMessage));
await PipeStreamWriter.FlushAsync();
chatMessage.SenderName = "You";
ChatMessages.Add(chatMessage);
}
}
}

View File

@ -7,13 +7,15 @@ namespace Remotely.Desktop.Linux.ViewModels
{
public class HostNamePromptViewModel : ViewModelBase
{
public static HostNamePromptViewModel Current { get; private set; }
public string host;
public HostNamePromptViewModel()
{
Current = this;
}
public string host;
public static HostNamePromptViewModel Current { get; private set; }
public string Host
{
get => host;

View File

@ -5,12 +5,10 @@ using ReactiveUI;
using Remotely.Desktop.Linux.Controls;
using Remotely.Desktop.Linux.Services;
using Remotely.Desktop.Linux.Views;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Models;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Linux.Services;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Models;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
@ -41,8 +39,6 @@ namespace Remotely.Desktop.Linux.ViewModels
return;
}
BuildServices();
Conductor = Services.GetRequiredService<Conductor>();
CasterSocket = Services.GetRequiredService<CasterSocket>();
@ -64,6 +60,7 @@ namespace Remotely.Desktop.Linux.ViewModels
public ICommand CloseCommand => new Executor((param) =>
{
(param as Window)?.Close();
Environment.Exit(0);
});
public ICommand CopyLinkCommand => new Executor(async (param) =>
@ -143,9 +140,10 @@ namespace Remotely.Desktop.Linux.ViewModels
try
{
SessionID = "Retrieving...";
await CheckDependencies();
SessionID = "Retrieving...";
Host = Config.GetConfig().Host;
@ -186,6 +184,7 @@ namespace Remotely.Desktop.Linux.ViewModels
catch (Exception ex)
{
Logger.Write(ex);
sessionID = "Failed";
await MessageBox.Show("Failed to connect to server.", "Connection Failed", MessageBoxType.OK);
return;
}
@ -201,6 +200,12 @@ namespace Remotely.Desktop.Linux.ViewModels
prompt.Owner = MainWindow.Current;
await prompt.ShowDialog(MainWindow.Current);
var result = HostNamePromptViewModel.Current.Host;
if (result is null)
{
return;
}
if (!result.StartsWith("https://") && !result.StartsWith("http://"))
{
result = $"https://{result}";
@ -214,30 +219,6 @@ namespace Remotely.Desktop.Linux.ViewModels
}
}
private void BuildServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
{
builder.AddConsole().AddDebug();
});
serviceCollection.AddSingleton<IScreenCaster, ScreenCaster>();
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputLinux>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceLinux>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerLinux>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerLinux>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IFileTransferService, FileTransferService>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddSingleton<ICursorIconWatcher, CursorIconWatcherLinux>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
private async Task CheckDependencies()
{

View File

@ -1,4 +1,5 @@
using Avalonia.Controls;
using ReactiveUI;
using Remotely.Desktop.Linux.Controls;
using Remotely.Desktop.Linux.Services;
using System.Windows.Input;
@ -7,28 +8,50 @@ namespace Remotely.Desktop.Linux.ViewModels
{
public class MessageBoxViewModel : ViewModelBase
{
public string Caption { get; set; }
public string Message { get; set; }
private bool areYesNoButtonsVisible;
private string caption;
private bool isOkButtonVisible;
private string message;
public bool AreYesNoButtonsVisible
{
get => areYesNoButtonsVisible;
set => this.RaiseAndSetIfChanged(ref areYesNoButtonsVisible, value);
}
public bool IsOkButtonVisible { get; set; }
public bool AreYesNoButtonsVisible { get; set; }
public string Caption
{
get => caption;
set => this.RaiseAndSetIfChanged(ref caption, value);
}
public MessageBoxResult Result { get; set; } = MessageBoxResult.Cancel;
public bool IsOkButtonVisible
{
get => isOkButtonVisible;
set => this.RaiseAndSetIfChanged(ref isOkButtonVisible, value);
}
public string Message
{
get => message;
set => this.RaiseAndSetIfChanged(ref message, value);
}
public ICommand NoCommand => new Executor((param) =>
{
Result = MessageBoxResult.No;
(param as Window).Close();
});
public ICommand OKCommand => new Executor((param) =>
{
Result = MessageBoxResult.OK;
(param as Window).Close();
});
public MessageBoxResult Result { get; set; } = MessageBoxResult.Cancel;
public ICommand YesCommand => new Executor((param) =>
{
Result = MessageBoxResult.Yes;
(param as Window).Close();
});
public ICommand NoCommand => new Executor((param) =>
{
Result = MessageBoxResult.No;
(param as Window).Close();
});
}
}

View File

@ -0,0 +1,93 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Remotely.Desktop.Linux.ViewModels;assembly=Remotely_Desktop"
xmlns:Models="clr-namespace:Remotely.Shared.Models;assembly=Remotely_Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Remotely.Desktop.Linux.Views.ChatWindow"
Icon="/Assets/favicon.ico"
HasSystemDecorations="False"
BorderBrush="DimGray"
BorderThickness="1"
MinHeight="250"
MinWidth="200"
Title="Remotely Chat" Height="450" Width="350">
<Window.DataContext>
<vm:ChatWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Name="TitleBanner" Height="50" Background="#FF464646">
<DockPanel Margin="10,4,0,0">
<DockPanel>
<Image Height="50" Width="50" Source="/Assets/Remotely_Icon.png" Margin="0,0,10,0"></Image>
<TextBlock Foreground="#FF1D90F1" FontWeight="Bold" FontSize="20" VerticalAlignment="Center">
Remotely Chat
</TextBlock>
</DockPanel>
<Button Classes="TitlebarButton" Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="X" />
<Button Classes="TitlebarButton" Command="{Binding MinimizeCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="_"/>
</DockPanel>
</Border>
<TextBlock Grid.Row="1"
Text="{Binding ChatSessionHeader}"
FontWeight="Bold"
Foreground="DimGray"
Margin="10,10,10,0"
TextWrapping="Wrap">
</TextBlock>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="5" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Border BorderBrush="Gray" BorderThickness="1" Margin="5">
<ScrollViewer x:Name="MessagesScrollViewer">
<ItemsControl x:Name="MessagesListBox" Items="{Binding ChatMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type Models:ChatMessage}">
<!-- TODO: Replace with Run when implemented. -->
<DockPanel Margin="5">
<TextBlock Text="{Binding SenderName}" FontWeight="Bold" TextWrapping="Wrap" FontSize="14">
<TextBlock.Styles>
<Style Selector="TextBlock[Text=You]">
<Style.Setters>
<Setter Property="Foreground" Value="SteelBlue"></Setter>
</Style.Setters>
</Style>
</TextBlock.Styles>
</TextBlock>
<TextBlock TextWrapping="Wrap" FontSize="14" Margin="0,0,5,0">: </TextBlock>
<TextBlock Text="{Binding Message}" TextWrapping="Wrap" FontSize="14"></TextBlock>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
<GridSplitter Grid.Row="1" Height="5" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
<TextBox Grid.Row="2"
x:Name="InputTextBox"
FontSize="14"
Margin="5"
Text="{Binding InputText, Mode=TwoWay}"
TextWrapping="Wrap" />
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,70 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using ReactiveUI;
using Remotely.Desktop.Linux.ViewModels;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Remotely.Desktop.Linux.Views
{
public class ChatWindow : Window
{
public ChatWindow()
{
this.InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private ChatWindowViewModel ViewModel => DataContext as ChatWindowViewModel;
private void ChatWindow_Closed(object sender, System.EventArgs e)
{
Environment.Exit(0);
}
private async void ChatWindow_KeyUp(object sender, Avalonia.Input.KeyEventArgs e)
{
if (e.Key == Avalonia.Input.Key.Enter)
{
await ViewModel.SendChatMessage();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.Closed += ChatWindow_Closed;
this.FindControl<Border>("TitleBanner").PointerPressed += TitleBanner_PointerPressed;
this.FindControl<TextBox>("InputTextBox").KeyUp += ChatWindow_KeyUp;
this.FindControl<ItemsControl>("MessagesListBox").ItemContainerGenerator.Materialized += ItemContainerGenerator_Materialized;
}
private async void ItemContainerGenerator_Materialized(object sender, Avalonia.Controls.Generators.ItemContainerEventArgs e)
{
// Allows listbox height to adjust to content before scrolling the scrollviewer.
await Task.Delay(1);
// TODO: Replace with ScrollToEnd when implemented.
var scrollViewer = this.FindControl<ScrollViewer>("MessagesScrollViewer");
var listBox = this.FindControl<ItemsControl>("MessagesListBox");
scrollViewer.Offset = new Vector(0, listBox.Bounds.Height);
}
private void TitleBanner_PointerPressed(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == Avalonia.Input.PointerUpdateKind.LeftButtonPressed)
{
this.BeginMoveDrag(e);
}
}
}
}

View File

@ -11,8 +11,9 @@ namespace Remotely.Desktop.Linux.Views
public MainWindow()
{
Current = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif

View File

@ -27,7 +27,7 @@ in this Software without prior written authorization from The Open Group.
using System;
using System.Runtime.InteropServices;
namespace Remotely.ScreenCast.Linux.X11Interop
namespace Remotely.Desktop.Linux.X11Interop
{
public static unsafe class LibX11
{

View File

@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace Remotely.ScreenCast.Linux.X11Interop
namespace Remotely.Desktop.Linux.X11Interop
{
public class LibXtst
{

View File

@ -143,7 +143,6 @@
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<EmbeddedResource Include="Install.ps1" />
<EmbeddedResource Include="Remotely_Desktop.zip" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +0,0 @@
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13
[System.IO.Directory]::CreateDirectory("$env:AppData\Remotely")
Invoke-WebRequest -Uri "https://dot.net/v1/dotnet-install.ps1" -OutFile "$env:AppData\Remotely\dotnet-install.ps1" -UseBasicParsing
&"$env:AppData\Remotely\dotnet-install.ps1" -Runtime dotnet
&"$env:AppData\Remotely\dotnet-install.ps1" -Runtime windowsdesktop
Start-Process -FilePath "dotnet.exe" -ArgumentList "$PSScriptRoot\Remotely_Desktop.dll" -WindowStyle Hidden

View File

@ -1,23 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using PathIO = System.IO.Path;
namespace Remotely.Desktop.Win.Wrapper
{
@ -26,48 +14,22 @@ namespace Remotely.Desktop.Win.Wrapper
/// </summary>
public partial class MainWindow : Window
{
private static readonly string baseDir = Directory.CreateDirectory(PathIO.Combine(PathIO.GetTempPath(), "Remotely_Desktop")).FullName;
private string tempDir;
private static readonly string baseDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "Remotely_Desktop")).FullName;
private readonly string currentVersionDir = Path.Combine(baseDir, "Current");
private readonly string remotelyDesktopFilename = "Remotely_Desktop.exe";
private readonly string tempDir = Directory.CreateDirectory(Path.Combine(baseDir, Guid.NewGuid().ToString())).FullName;
public MainWindow()
{
InitializeComponent();
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
StatusText.Text = "Extracting files...";
await Task.Run(CleanupOldFiles);
tempDir = Directory.CreateDirectory(PathIO.Combine(baseDir, Guid.NewGuid().ToString())).FullName;
await Task.Run(ExtractRemotely);
await Task.Run(ExtractInstallScript);
StatusText.Text = "Updating .NET Core runtime...";
await Task.Run(RunInstallScript);
Close();
}
private void CleanupOldFiles()
{
foreach (var fse in Directory.GetFileSystemEntries(baseDir))
{
try
{
if (Directory.Exists(fse) && fse != tempDir)
if (Directory.Exists(fse) && fse != currentVersionDir)
{
Directory.Delete(fse, true);
}
@ -77,15 +39,22 @@ namespace Remotely.Desktop.Win.Wrapper
}
}
catch { }
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void ExtractRemotely()
{
try
{
var zipPath = PathIO.Combine(tempDir, "Remotely_Desktop.zip");
var zipPath = Path.Combine(tempDir, "Remotely_Desktop.zip");
var tempExePath = Path.Combine(tempDir, remotelyDesktopFilename);
using (var mrs = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Remotely.Desktop.Win.Wrapper.Remotely_Desktop.zip"))
{
@ -94,57 +63,89 @@ namespace Remotely.Desktop.Win.Wrapper
mrs.CopyTo(fs);
}
}
ZipFile.ExtractToDirectory(zipPath, tempDir);
}
catch (Exception ex)
{
MessageBox.Show("An error occured while extracting files. Error: " +
Environment.NewLine + Environment.NewLine + ex.Message, "Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
private void RunInstallScript()
{
try
{
var installPath = PathIO.Combine(tempDir, "Install.ps1");
Process.Start(new ProcessStartInfo()
using (var zipArchive = ZipFile.OpenRead(zipPath))
{
FileName = "powershell.exe",
Arguments = $"-executionpolicy bypass -f \"{installPath}\"",
WindowStyle = ProcessWindowStyle.Hidden
}).WaitForExit();
}
catch (Exception ex)
{
MessageBox.Show("An error occured while updating .NET Core. Error: " +
Environment.NewLine + Environment.NewLine + ex.Message, "Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
zipArchive.GetEntry(remotelyDesktopFilename).ExtractToFile(tempExePath);
var fileVersionInfo = FileVersionInfo.GetVersionInfo(tempExePath);
private void ExtractInstallScript()
{
try
{
var installPath = PathIO.Combine(tempDir, "Install.ps1");
using (var mrs = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Remotely.Desktop.Win.Wrapper.Install.ps1"))
{
using (var fs = new FileStream(installPath, FileMode.Create))
var targetExePath = Path.Combine(currentVersionDir, remotelyDesktopFilename);
if (File.Exists(targetExePath) &&
FileVersionInfo.GetVersionInfo(targetExePath).FileVersion == fileVersionInfo.FileVersion)
{
mrs.CopyTo(fs);
return;
}
try
{
var currentVersionDir = Path.GetDirectoryName(targetExePath);
Directory.Delete(currentVersionDir, true);
Directory.CreateDirectory(currentVersionDir);
ZipFile.ExtractToDirectory(zipPath, currentVersionDir);
}
catch { }
}
}
catch (Exception ex)
{
MessageBox.Show("An error occured while extracting files. Error: " +
Environment.NewLine + Environment.NewLine + ex.Message);
Environment.NewLine + Environment.NewLine + ex.Message, "Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void StartRemotely()
{
try
{
Process.Start(Path.Combine(currentVersionDir, remotelyDesktopFilename));
}
catch (Exception ex)
{
MessageBox.Show("An error occured while starting Remotely. Error: " +
Environment.NewLine + Environment.NewLine + ex.Message, "Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
StatusText.Text = "Extracting files...";
await Task.Run(ExtractRemotely);
await Task.Run(StartRemotely);
await Task.Run(CleanupOldFiles);
await Task.Run(AddFirewallRule);
Close();
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
private void AddFirewallRule()
{
var psi = new ProcessStartInfo()
{
FileName = "netsh",
Arguments = "advfirewall firewall delete rule name=\"Remotely Desktop\"",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true
};
Process.Start(psi).WaitForExit();
psi.Arguments = $"advfirewall firewall add rule name=\"Remotely Desktop\" program=\"{Path.Combine(currentVersionDir, remotelyDesktopFilename)}\" protocol=any dir=in enable=yes action=allow profile=Private,Domain description=\"The agent that allows screen sharing and remote control for Remotely.\"";
Process.Start(psi).WaitForExit();
}
}
}

View File

@ -7,12 +7,12 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Desktop.Win.Wrapper")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyTitle("Remotely Desktop")]
[assembly: AssemblyDescription("Desktop client for allowing remote control via the Remotely server.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Desktop.Win.Wrapper")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyCompany("Translucency Software")]
[assembly: AssemblyProduct("Remotely Desktop")]
[assembly: AssemblyCopyright("Copyright © 2020 Translucency Software")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Remotely.Desktop.Win"
StartupUri="MainWindow.xaml" DispatcherUnhandledException="Application_DispatcherUnhandledException" Startup="Application_Startup">
DispatcherUnhandledException="Application_DispatcherUnhandledException" Startup="Application_Startup" Exit="Application_Exit">
<Application.Resources>
<Style x:Key="TitlebarButton" TargetType="Button">
<Setter Property="Background" Value="#FF464646"></Setter>

View File

@ -1,6 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Services;
using Remotely.Desktop.Core;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
@ -40,5 +39,21 @@ namespace Remotely.Desktop.Win
Environment.Exit(0);
}
}
private void Application_Exit(object sender, ExitEventArgs e)
{
System.Windows.Forms.Application.Exit();
var conductor = ServiceContainer.Instance.GetRequiredService<Conductor>();
foreach (var viewer in conductor.Viewers.Values)
{
try
{
viewer.Dispose();
}
catch { }
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -4,6 +4,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AssemblyName>Remotely_Desktop</AssemblyName>
<RootNamespace>Remotely.Desktop.Win</RootNamespace>
<ApplicationIcon>favicon.ico</ApplicationIcon>
@ -16,23 +17,41 @@
<Product>Remotely Desktop</Product>
<PackageProjectUrl>https://remotely.one</PackageProjectUrl>
<Platforms>AnyCPU;x86;x64</Platforms>
<StartupObject>Remotely.Desktop.Win.Program</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\favicon.ico" />
<None Remove="Assets\Remotely_Icon.png" />
<None Remove="favicon.ico" />
<None Remove="Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="1.0.3" />
<PackageReference Include="NAudio" Version="1.10.0" />
<PackageReference Include="SharpDX" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.DXGI" Version="4.2.0" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScreenCast.Win\ScreenCast.Win.csproj" />
<ProjectReference Include="..\Desktop.Core\Desktop.Core.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\favicon.ico" />
<Resource Include="Assets\Remotely_Icon.png" />
<Resource Include="favicon.ico" />
<Resource Include="Remotely_Icon.png" />
</ItemGroup>
@ -52,7 +71,7 @@
<Compile Update="Controls\HostNamePrompt.xaml.cs">
<DependentUpon>HostNamePrompt.xaml</DependentUpon>
</Compile>
<Compile Update="MainWindow.xaml.cs">
<Compile Update="Views\MainWindow.xaml.cs">
<SubType>Code</SubType>
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
@ -62,10 +81,13 @@
<Page Update="Controls\HostNamePrompt.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="MainWindow.xaml">
<Page Update="Views\MainWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="if $(ConfigurationName) == Debug (&#xD;&#xA; if $(PlatformName) == Any CPU (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x64 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x64\Debug\netcoreapp3.1\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x64\Debug\netcoreapp3.1\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x86 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x86\Debug\netcoreapp3.1\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x86\Debug\netcoreapp3.1\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA;)" />
</Target>
</Project>

View File

@ -2,7 +2,7 @@
using SharpDX.DXGI;
using System;
namespace Remotely.ScreenCast.Win.Models
namespace Remotely.Desktop.Win.Models
{
public class DirectXOutput : IDisposable
{

View File

@ -1,25 +1,26 @@
using Remotely.Shared.Models;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Services;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using Remotely.Shared.Win32;
using System.Threading;
using Remotely.ScreenCast.Win.Services;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Communication;
using Remotely.Desktop.Win.Services;
using Remotely.Desktop.Core.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Models;
using Remotely.Shared.Utilities;
using System.Windows;
using Remotely.Desktop.Win.Views;
namespace Remotely.ScreenCast.Win
namespace Remotely.Desktop.Win
{
public class Program
{
private static Conductor Conductor { get; set; }
private static CasterSocket CasterSocket { get; set; }
private static Conductor Conductor { get; set; }
private static ICursorIconWatcher CursorIconWatcher { get; set; }
private static IServiceProvider Services => ServiceContainer.Instance;
public static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
@ -32,7 +33,7 @@ namespace Remotely.ScreenCast.Win
}
}
}
public static void Main(string[] args)
{
try
@ -47,14 +48,27 @@ namespace Remotely.ScreenCast.Win
if (Conductor.Mode == Core.Enums.AppMode.Chat)
{
Services.GetRequiredService<ChatHostService>().StartChat(Conductor.RequesterID, Conductor.OrganizationName).Wait();
StartUiThread(null);
Services.GetRequiredService<IChatHostService>().StartChat(Conductor.RequesterID, Conductor.OrganizationName).Wait();
}
else if (Conductor.Mode == Core.Enums.AppMode.Unattended)
{
StartUiThread(null);
App.Current.Dispatcher.Invoke(() =>
{
App.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
});
Task.Run(StartScreenCasting);
}
else
{
Task.Run(StartScreenCasting);
StartUiThread(() => new MainWindow());
}
System.Windows.Forms.Application.Run();
Environment.Exit(0);
Thread.Sleep(Timeout.Infinite);
}
catch (Exception ex)
{
@ -79,7 +93,7 @@ namespace Remotely.ScreenCast.Win
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<ChatHostService>();
serviceCollection.AddSingleton<IChatHostService, ChatHostServiceWin>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
@ -108,6 +122,7 @@ namespace Remotely.ScreenCast.Win
await CasterSocket.NotifyRequesterUnattendedReady(Conductor.RequesterID);
}
}
private static async Task StartScreenCasting()
{
CursorIconWatcher = Services.GetRequiredService<ICursorIconWatcher>();
@ -131,8 +146,29 @@ namespace Remotely.ScreenCast.Win
Services.GetRequiredService<IdleTimer>().Start();
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
Services.GetRequiredService<IClipboardService>().BeginWatching();
}
Thread.Sleep(Timeout.Infinite);
private static void StartUiThread(Func<Window> createWindowFunc)
{
var uiThread = new Thread(() =>
{
var app = new App();
app.InitializeComponent();
if (createWindowFunc is null)
{
app.Run();
}
else
{
app.Run(createWindowFunc());
}
});
uiThread.TrySetApartmentState(ApartmentState.STA);
uiThread.Start();
while (App.Current is null)
{
Thread.Sleep(100);
}
}
}
}

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\ScreenCast</PublishDir>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\Desktop</PublishDir>
<SelfContained>True</SelfContained>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>True</PublishSingleFile>

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Release</Configuration>
<Platform>x64</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\ScreenCast</PublishDir>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\win10-x64\publish\Desktop</PublishDir>
<SelfContained>True</SelfContained>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>False</PublishSingleFile>

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Release</Configuration>
<Platform>x86</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\win10-x86\publish\ScreenCast</PublishDir>
<PublishDir>..\Agent\bin\Release\netcoreapp3.1\win10-x86\publish\Desktop</PublishDir>
<RuntimeIdentifier>win10-x86</RuntimeIdentifier>
<SelfContained>True</SelfContained>
<PublishSingleFile>False</PublishSingleFile>

View File

@ -9,9 +9,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>x64</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>bin\Release\win-x64\publish\</PublishDir>
<SelfContained>false</SelfContained>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishTrimmed>True</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@ -9,9 +9,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>x86</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>bin\Release\win-x86\publish\</PublishDir>
<SelfContained>false</SelfContained>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win10-x86</RuntimeIdentifier>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishTrimmed>True</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@ -5,10 +5,10 @@ using System.IO;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using NAudio.Wave;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
namespace Remotely.ScreenCast.Win.Services
namespace Remotely.Desktop.Win.Services
{
public class AudioCapturerWin : IAudioCapturer
{

View File

@ -0,0 +1,95 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Win.ViewModels;
using Remotely.Desktop.Win.Views;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Remotely.Desktop.Win.Services
{
public class ChatHostServiceWin : IChatHostService
{
private ChatWindowViewModel ChatViewModel { get; set; }
private NamedPipeServerStream NamedPipeStream { get; set; }
private StreamReader Reader { get; set; }
private StreamWriter Writer { get; set; }
public async Task StartChat(string requesterID, string organizationName)
{
NamedPipeStream = new NamedPipeServerStream("Remotely_Chat" + requesterID, PipeDirection.InOut, 10, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Writer = new StreamWriter(NamedPipeStream);
Reader = new StreamReader(NamedPipeStream);
var cts = new CancellationTokenSource(10000);
try
{
await NamedPipeStream.WaitForConnectionAsync(cts.Token);
}
catch (TaskCanceledException)
{
Logger.Write("A chat session was attempted, but the client failed to connect in time.", Shared.Enums.EventType.Warning);
Environment.Exit(0);
}
App.Current.Dispatcher.Invoke(() =>
{
var chatWindow = new ChatWindow();
chatWindow.Closing += ChatWindow_Closing;
ChatViewModel = chatWindow.DataContext as ChatWindowViewModel;
ChatViewModel.PipeStreamWriter = Writer;
ChatViewModel.OrganizationName = organizationName;
chatWindow.Show();
});
_ = Task.Run(ReadFromStream);
}
private void ChatWindow_Closing(object sender, EventArgs e)
{
try
{
NamedPipeStream?.Disconnect();
NamedPipeStream?.Dispose();
}
catch { }
}
private async Task ReadFromStream()
{
while (NamedPipeStream.IsConnected)
{
try
{
var messageJson = await Reader.ReadLineAsync();
if (!string.IsNullOrWhiteSpace(messageJson))
{
var chatMessage = JsonSerializer.Deserialize<ChatMessage>(messageJson);
App.Current.Dispatcher.Invoke(() =>
{
if (chatMessage.Disconnected)
{
MessageBox.Show("Your partner has disconnected.", "Partner Disconnected", MessageBoxButton.OK, MessageBoxImage.Information);
App.Current.Shutdown();
return;
}
ChatViewModel.SenderName = chatMessage.SenderName;
ChatViewModel.ChatMessages.Add(chatMessage);
});
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}
}

View File

@ -0,0 +1,96 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Remotely.Desktop.Win.Services
{
public class ClipboardServiceWin : IClipboardService
{
private CancellationTokenSource cancelTokenSource;
public event EventHandler<string> ClipboardTextChanged;
private string ClipboardText { get; set; }
public void BeginWatching()
{
try
{
StopWatching();
}
finally
{
cancelTokenSource = new CancellationTokenSource();
_ = Task.Run(() => WatchClipboard(cancelTokenSource.Token));
}
}
public void SetText(string clipboardText)
{
var thread = new Thread(() =>
{
try
{
if (string.IsNullOrWhiteSpace(clipboardText))
{
Clipboard.Clear();
}
else
{
Clipboard.SetText(clipboardText);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
public void StopWatching()
{
try
{
cancelTokenSource?.Cancel();
cancelTokenSource?.Dispose();
}
catch { }
}
private void WatchClipboard(CancellationToken cancelToken)
{
while (!cancelToken.IsCancellationRequested &&
!Environment.HasShutdownStarted)
{
var thread = new Thread(() =>
{
try
{
Win32Interop.SwitchToInputDesktop();
if (Clipboard.ContainsText() && Clipboard.GetText() != ClipboardText)
{
ClipboardText = Clipboard.GetText();
ClipboardTextChanged?.Invoke(this, ClipboardText);
}
}
catch { }
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Thread.Sleep(500);
}
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using Remotely.Shared.Utilities;
using System;
using System.IO;
using System.Text.Json;
@ -37,8 +38,9 @@ namespace Remotely.Desktop.Win.Services
Directory.CreateDirectory(ConfigFolder);
File.WriteAllText(ConfigFile, JsonSerializer.Serialize(this));
}
catch
catch (Exception ex)
{
Logger.Write(ex);
}
}
}

View File

@ -7,9 +7,9 @@ using System.Timers;
using System.Windows.Forms;
using Remotely.Shared.Win32;
using Remotely.Shared.Models;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.Desktop.Core.Interfaces;
namespace Remotely.ScreenCast.Win.Services
namespace Remotely.Desktop.Win.Services
{
/// <summary>
/// A class that can be used to watch for cursor icon changes.

View File

@ -1,35 +1,31 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Models;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Models;
using System;
using Remotely.Shared.Win32;
using static Remotely.Shared.Win32.User32;
using System.Windows.Forms;
using System.Threading;
using Remotely.ScreenCast.Core.Services;
using Remotely.Desktop.Core.Services;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Remotely.ScreenCast.Core;
using Remotely.Desktop.Core;
using System.Runtime.InteropServices;
using Remotely.Shared.Utilities;
namespace Remotely.ScreenCast.Win.Services
namespace Remotely.Desktop.Win.Services
{
public class KeyboardMouseInputWin : IKeyboardMouseInput
{
private volatile bool inputBlocked;
public KeyboardMouseInputWin()
{
Application.ApplicationExit += Application_ApplicationExit;
StartInputActionTask();
StartInputProcessingThread();
}
private CancellationTokenSource CancelTokenSource { get; set; }
private CancellationToken CancelToken { get; set; }
private ConcurrentQueue<Action> InputActions { get; } = new ConcurrentQueue<Action>();
private Task InputActionsTask { get; set; }
private bool ShutdownStarted { get; set; }
public Tuple<double, double> GetAbsolutePercentFromRelativePercent(double percentX, double percentY, IScreenCapturer capturer)
{
var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left - capturer.GetVirtualScreenBounds().Left;
@ -54,7 +50,7 @@ namespace Remotely.ScreenCast.Win.Services
ki = new KEYBDINPUT()
{
wVk = keyCode,
wScan = 0,
wScan = (ScanCodeShort)MapVirtualKeyEx((uint)keyCode, VkMapType.MAPVK_VK_TO_VSC, GetKeyboardLayout()),
time = 0,
dwExtraInfo = GetMessageExtraInfo()
}
@ -74,7 +70,7 @@ namespace Remotely.ScreenCast.Win.Services
ki = new KEYBDINPUT()
{
wVk = keyCode,
wScan = 0,
wScan = (ScanCodeShort)MapVirtualKeyEx((uint)keyCode, VkMapType.MAPVK_VK_TO_VSC, GetKeyboardLayout()),
time = 0,
dwFlags = KEYEVENTF.KEYUP,
dwExtraInfo = GetMessageExtraInfo()
@ -221,27 +217,31 @@ namespace Remotely.ScreenCast.Win.Services
{
InputActions.Enqueue(() =>
{
inputBlocked = toggleOn;
var result = BlockInput(toggleOn);
Logger.Write($"Result of ToggleBlockInput set to {toggleOn}: {result}");
});
}
private void Application_ApplicationExit(object sender, EventArgs e)
private void CheckQueue(CancellationToken cancelToken)
{
ShutdownStarted = true;
}
private void CheckQueue()
{
while (!ShutdownStarted &&
!Environment.HasShutdownStarted &&
!CancelToken.IsCancellationRequested)
while (!Environment.HasShutdownStarted &&
!cancelToken.IsCancellationRequested)
{
if (InputActions.TryDequeue(out var action))
try
{
action();
if (InputActions.TryDequeue(out var action))
{
action();
}
}
finally
{
Thread.Sleep(1);
}
Thread.Sleep(1);
}
Logger.Write($"Stopping input processing on thread {Thread.CurrentThread.ManagedThreadId}.");
}
private VirtualKey ConvertJavaScriptKeyToVirtualKey(string key)
@ -368,27 +368,47 @@ namespace Remotely.ScreenCast.Win.Services
}
return keyCode;
}
private void StartInputActionTask()
private void StartInputProcessingThread()
{
CancelTokenSource?.Cancel();
CancelTokenSource = new CancellationTokenSource();
CancelToken = CancelTokenSource.Token;
InputActionsTask = Task.Run(CheckQueue, CancelTokenSource.Token);
CancelTokenSource?.Dispose();
var newThread = new Thread(() =>
{
Logger.Write($"New input processing thread started on thread {Thread.CurrentThread.ManagedThreadId}.");
CancelTokenSource = new CancellationTokenSource();
if (inputBlocked)
{
ToggleBlockInput(true);
}
CheckQueue(CancelTokenSource.Token);
});
newThread.SetApartmentState(ApartmentState.STA);
newThread.Start();
}
private void TryOnInputDesktop(Action inputAction)
{
if (InputActionsTask.Status != TaskStatus.Running)
{
StartInputActionTask();
}
InputActions.Enqueue(() =>
{
if (!Win32Interop.SwitchToInputDesktop())
try
{
Logger.Write("Switch failed. Last Error: " + Marshal.GetLastWin32Error().ToString());
if (!Win32Interop.SwitchToInputDesktop())
{
Logger.Write("Desktop switch failed during input processing.");
// Thread likely has hooks in current desktop. SendKeys will create one with no way to unhook it.
// Start a new thread for processing input.
StartInputProcessingThread();
return;
}
inputAction();
}
catch (Exception ex)
{
Logger.Write(ex);
}
inputAction();
});
}
}

View File

@ -22,9 +22,9 @@
// THE SOFTWARE.
using Microsoft.Win32;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.ScreenCast.Win.Models;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Win.Models;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using SharpDX;
@ -37,12 +37,13 @@ using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Forms;
namespace Remotely.ScreenCast.Win.Services
namespace Remotely.Desktop.Win.Services
{
public class ScreenCapturerWin : IScreenCapturer
{
private readonly Dictionary<string, int> bitBltScreens = new Dictionary<string, int>();
private readonly Dictionary<string, DirectXOutput> directxScreens = new Dictionary<string, DirectXOutput>();
public ScreenCapturerWin()
{
Init();
@ -59,7 +60,6 @@ namespace Remotely.ScreenCast.Win.Services
public Bitmap PreviousFrame { get; set; }
public string SelectedScreen { get; private set; } = Screen.PrimaryScreen.DeviceName;
private Graphics Graphic { get; set; }
public void Dispose()
{
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
@ -208,12 +208,8 @@ namespace Remotely.ScreenCast.Win.Services
catch { }
return;
}
// TODO: Get dirty rects.
//RawRectangle[] dirtyRectsBuffer = new RawRectangle[duplicateFrameInformation.TotalMetadataBufferSize];
//duplicatedOutput.GetFrameDirtyRects(duplicateFrameInformation.TotalMetadataBufferSize, dirtyRectsBuffer, out var dirtyRectsSizeRequired);
// copy resource into memory that can be accessed by the CPU
// Copy resource into memory that can be accessed by the CPU
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
{
device.ImmediateContext.CopyResource(screenTexture2D, texture2D);

View File

@ -0,0 +1,87 @@
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Remotely.Desktop.Win.ViewModels
{
public class ChatWindowViewModel : ViewModelBase
{
private string inputText;
private string organizationName = "your IT provider";
private string senderName = "a technician";
public ObservableCollection<ChatMessage> ChatMessages { get; } = new ObservableCollection<ChatMessage>();
public string InputText
{
get
{
return inputText;
}
set
{
inputText = value;
FirePropertyChanged(nameof(InputText));
}
}
public string OrganizationName
{
get
{
return organizationName;
}
set
{
if (string.IsNullOrWhiteSpace(value) ||
value == organizationName)
{
return;
}
organizationName = value;
FirePropertyChanged(nameof(OrganizationName));
}
}
public StreamWriter PipeStreamWriter { get; set; }
public string SenderName
{
get
{
return senderName;
}
set
{
if (value == senderName)
{
return;
}
senderName = value;
FirePropertyChanged(nameof(SenderName));
}
}
public async Task SendChatMessage()
{
if (string.IsNullOrWhiteSpace(InputText))
{
return;
}
var chatMessage = new ChatMessage(string.Empty, InputText);
InputText = string.Empty;
await PipeStreamWriter.WriteLineAsync(JsonSerializer.Serialize(chatMessage));
await PipeStreamWriter.FlushAsync();
chatMessage.SenderName = "You";
ChatMessages.Add(chatMessage);
}
}
}

View File

@ -1,9 +1,9 @@
using Remotely.Desktop.Win.Controls;
using Remotely.Desktop.Win.Services;
using Remotely.Shared.Models;
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Models;
using Remotely.ScreenCast.Core.Services;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Models;
using Remotely.Desktop.Core.Services;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -13,9 +13,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Security.Principal;
using System.Windows.Input;
using Remotely.ScreenCast.Win.Services;
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Communication;
using Remotely.Desktop.Core.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Win32;
@ -27,26 +25,14 @@ namespace Remotely.Desktop.Win.ViewModels
{
private string host;
private string sessionID;
public static MainWindowViewModel Current { get; private set; }
public MainWindowViewModel()
{
Application.Current.Exit += Application_Exit;
Current = this;
BuildServices();
CursorIconWatcher = Services.GetRequiredService<ICursorIconWatcher>();
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
Services.GetRequiredService<IClipboardService>().BeginWatching();
Conductor = Services.GetRequiredService<Conductor>();
CasterSocket = Services.GetRequiredService<CasterSocket>();
Conductor.SessionIDChanged += SessionIDChanged;
Conductor.ViewerRemoved += ViewerRemoved;
Conductor.ViewerAdded += ViewerAdded;
Conductor.ScreenCastRequested += ScreenCastRequested;
}
public static MainWindowViewModel Current { get; private set; }
public static IServiceProvider Services => ServiceContainer.Instance;
public ICommand ChangeServerCommand
@ -61,8 +47,8 @@ namespace Remotely.Desktop.Win.ViewModels
}
}
private Conductor Conductor { get; }
private CasterSocket CasterSocket { get; }
private Conductor Conductor { get; set; }
private CasterSocket CasterSocket { get; set; }
private ICursorIconWatcher CursorIconWatcher { get; set; }
public ICommand ElevateToAdminCommand
@ -189,6 +175,18 @@ namespace Remotely.Desktop.Win.ViewModels
public async Task Init()
{
Application.Current.Exit += Application_Exit;
CursorIconWatcher = Services?.GetRequiredService<ICursorIconWatcher>();
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
Services?.GetRequiredService<IClipboardService>().BeginWatching();
Conductor = Services?.GetRequiredService<Conductor>();
CasterSocket = Services?.GetRequiredService<CasterSocket>();
Conductor.SessionIDChanged += SessionIDChanged;
Conductor.ViewerRemoved += ViewerRemoved;
Conductor.ViewerAdded += ViewerAdded;
Conductor.ScreenCastRequested += ScreenCastRequested;
SessionID = "Retrieving...";
Host = Config.GetConfig().Host;
@ -233,6 +231,7 @@ namespace Remotely.Desktop.Win.ViewModels
catch (Exception ex)
{
Logger.Write(ex);
SessionID = "Failed";
MessageBox.Show(Application.Current.MainWindow, "Failed to connect to server.", "Connection Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
@ -272,30 +271,6 @@ namespace Remotely.Desktop.Win.ViewModels
Viewers.Clear();
});
}
private void BuildServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
{
builder.AddConsole().AddDebug().AddEventLog();
});
serviceCollection.AddSingleton<ICursorIconWatcher, CursorIconWatcherWin>();
serviceCollection.AddSingleton<IScreenCaster, ScreenCaster>();
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputWin>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceWin>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerWin>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddScoped<IFileTransferService, FileTransferService>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
private async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
{

View File

@ -0,0 +1,98 @@
<Window x:Class="Remotely.Desktop.Win.Views.ChatWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Remotely.Desktop.Win.Views"
xmlns:ViewModels="clr-namespace:Remotely.Desktop.Win.ViewModels"
xmlns:Models="clr-namespace:Remotely.Shared.Models;assembly=Remotely_Shared"
mc:Ignorable="d"
WindowStyle="None"
ResizeMode="CanResizeWithGrip"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
BorderBrush="DimGray"
BorderThickness="1"
MinHeight="250"
MinWidth="200"
Topmost="True"
Title="Remotely Chat" Height="450" Width="350" Icon="/Assets/favicon.ico"
Loaded="Window_Loaded"
ContentRendered="Window_ContentRendered">
<Window.DataContext>
<ViewModels:ChatWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Height="50" Background="#FF464646">
<DockPanel Margin="10,0,0,0">
<DockPanel>
<Image Height="50" Width="50" Source="/Assets/Remotely_Icon.png" Margin="0,0,10,0"></Image>
<TextBlock Foreground="#FF1D90F1" FontWeight="Bold" FontSize="20" VerticalAlignment="Center">
Remotely Chat
</TextBlock>
</DockPanel>
<Button Style="{StaticResource TitlebarButton}" Click="CloseButton_Click" Content="X" />
<Button Style="{StaticResource TitlebarButton}" Click="MinimizeButton_Click" Content="_"/>
</DockPanel>
</Border>
<TextBlock Grid.Row="1" FontWeight="Bold" Foreground="DimGray" Margin="10,10,10,0" TextWrapping="Wrap">
<Run>Chat session with</Run>
<Run Text="{Binding OrganizationName}"></Run>
</TextBlock>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="5" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Border BorderBrush="Gray" BorderThickness="1" Margin="5">
<ScrollViewer x:Name="MessagesScrollViewer">
<ItemsControl x:Name="MessagesItemsControl" ItemsSource="{Binding ChatMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type Models:ChatMessage}">
<TextBlock TextWrapping="Wrap" Margin="5" FontSize="14">
<Run Text="{Binding SenderName}" FontWeight="Bold">
<Run.Style>
<Style TargetType="Run">
<Style.Triggers>
<Trigger Property="Text" Value="You">
<Setter Property="Foreground" Value="SteelBlue"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
<Run>: </Run>
<Run Text="{Binding Message}"></Run>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
<GridSplitter Grid.Row="1" Height="5" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
<TextBox Grid.Row="2"
FontSize="14"
Margin="5"
Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
PreviewKeyUp="ChatInputBox_PreviewKeyUp" />
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,67 @@
using Remotely.Desktop.Win.ViewModels;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Remotely.Desktop.Win.Views
{
/// <summary>
/// Interaction logic for ChatWindow.xaml
/// </summary>
public partial class ChatWindow : Window
{
public ChatWindow()
{
InitializeComponent();
}
private ChatWindowViewModel ViewModel => DataContext as ChatWindowViewModel;
private async void ChatInputBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
await ViewModel.SendChatMessage();
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
{
MessagesScrollViewer.ScrollToEnd();
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
private void Window_ContentRendered(object sender, EventArgs e)
{
Topmost = false;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
MessagesItemsControl.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
}
}

View File

@ -1,16 +1,16 @@
<Window x:Class="Remotely.Desktop.Win.MainWindow"
<Window x:Class="Remotely.Desktop.Win.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Remotely.Desktop.Win"
xmlns:local="clr-namespace:Remotely.Desktop.Win.Views"
xmlns:ViewModels="clr-namespace:Remotely.Desktop.Win.ViewModels"
mc:Ignorable="d"
Title="Remotely" Height="250" Width="350"
WindowStyle="None"
ResizeMode="NoResize"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
Loaded="Window_Loaded" Icon="Remotely_Icon.png">
Loaded="Window_Loaded" Icon="/Remotely_Icon.png">
<Window.Resources>
<DrawingBrush x:Key="GearBrush">
<DrawingBrush.Drawing>
@ -24,9 +24,11 @@
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Window.DataContext>
<ViewModels:MainWindowViewModel/>
<ViewModels:MainWindowViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<Border Height="50" Background="#FF464646">

View File

@ -1,4 +1,5 @@
using Remotely.Desktop.Win.ViewModels;
using Remotely.Desktop.Win;
using Remotely.Desktop.Win.ViewModels;
using System;
using System.Threading.Tasks;
using System.Windows;
@ -7,7 +8,7 @@ using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace Remotely.Desktop.Win
namespace Remotely.Desktop.Win.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
@ -22,7 +23,7 @@ namespace Remotely.Desktop.Win
public MainWindowViewModel ViewModel => DataContext as MainWindowViewModel;
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
App.Current.Shutdown();
Close();
}
private async void CopyLinkButton_Click(object sender, RoutedEventArgs e)

View File

@ -7,23 +7,6 @@ A remote control and remote scripting solution, built with .NET Core, SignalR Co
Website: https://remotely.one
Multi-Tenant Demo Server: https://app.remotely.one
> **From the Developer**:
>
>Remotely has become a second full-time job, one for which I don't get paid. The total donations I've received over the last 3 years, while greatly appreciated, only amount to what I earn in 3 days at a regular job.
>
>Every time I try to get back into it developing this project, or try to keep up with support questions and feature requests, I burn out quickly.
>
>I've accomplished everything I personally set out to achieve with Remotely, plus a huge amount of features requested by others. There's not enough incentive to continue working on it. I feel like the project is done, and I can set it down.
>
>For that reason, I will no longer be responding to emails, offering free support, or accepting feature requests. Development will be, for the most part, indefinitely suspended.
>
>Of course, anyone is free to fork the repo and continue development, so long as the original license is respected. I won't, however, be taking any pull requests, as I wish to maintain full ownership of the codebase.
>
> Thank you for understanding, and best wishes to everyone.
>
> \- Jared
## Client Prerequisites:
* Endpoint devices require the .NET Core runtime to be installed.
* For Windows, the Desktop Runtime is required.
@ -99,14 +82,6 @@ The following steps will configure your Windows 10 machine for building the Remo
* Windows 2016/2019 should work as well, but isn't tested regularly.
* Linux: Only Ubuntu 18.04+ is tested.
## Session Recording
* You can turn on session recording in appsettings.json.
* The following requirements must be met for it to work:
* On Linux, libgdiplus and libc6-dev must be installed (sudo apt-get install libgdiplus libc6-dev).
* The process running the app must have access to create and modify a folder name "Recordings" in the site's root content folder.
* FFmpeg must be executable from the process running the Remotely server.
* Link: https://www.ffmpeg.org/download.html
## Remote Control on Mobile
Ideally, you'd be doing remote control from an actual computer or laptop. However, I've tried to make the remote control at least somewhat usable from a mobile device. Here are the controls:
* Left-click: Single tap
@ -128,7 +103,6 @@ Note: To retain your settings between upgrades, copy your settings to appsetting
* DBProvider: Determines which of the three connection strings (at the top) will be used. The appropriate DB provider for the database type is automatically loaded in code.
* MaxOrganizationCount: By default, one organization can exist on the server, which is created automatically when the first account is registered. Afterward, self-registration will be disabled.
* Set this to -1 or increase it to a specific number to allow multi-tenancy.
* RecordRemoteControlSessions: Whether or not to record remote control sessions.
* RedirectToHttps: Whether ASP.NET Core will redirect all traffic from HTTP to HTTPS. This is independent of Nginx and IIS configurations that do the same.
* UseHsts: Whether ASP.NET Core will use HTTP Strict Transport Security.
* DataRetentionInDays: How long event logs and remote command logs will be kept.
@ -142,7 +116,6 @@ Note: To retain your settings between upgrades, copy your settings to appsetting
* Theme: The color theme to use for the site. Values are "Light" or "Dark". This can also be configured per-user in Account - Options.
* UseWebRtc: Attempt to create a peer-to-peer connection via WebRTC for screen sharing.
* Only works on Windows agents.
* Session recording will not work if a WebRTC connection is made.
## .NET Core Deployments

View File

@ -27,9 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Assets", "Assets", "{D96B47
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{3E835099-C417-4D82-8D5C-13DC09AF48AC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCast.Linux", "ScreenCast.Linux\ScreenCast.Linux.csproj", "{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCast.Core", "ScreenCast.Core\ScreenCast.Core.csproj", "{B04A1728-2E87-491E-BC7F-F575A1754DEF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.Core", "Desktop.Core\Desktop.Core.csproj", "{B04A1728-2E87-491E-BC7F-F575A1754DEF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{3B1B36AE-7A60-4974-A868-1E24894D4149}"
EndProject
@ -41,8 +39,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Folder", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCast.Win", "ScreenCast.Win\ScreenCast.Win.csproj", "{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.Win", "Desktop.Win\Desktop.Win.csproj", "{6B726FC4-A907-4813-BF38-3342E02AA8D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agent.Installer.Win", "Agent.Installer.Win\Agent.Installer.Win.csproj", "{A3D0368C-0850-4614-B5B5-41B9D5135AA9}"
@ -51,7 +47,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop.Win.Wrapper", "Desktop.Win.Wrapper\Desktop.Win.Wrapper.csproj", "{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.LoadTester", "Tests.LoadTester\Tests.LoadTester.csproj", "{6C25240C-613D-4A86-A04E-784BA6726094}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.LoadTester", "Tests.LoadTester\Tests.LoadTester.csproj", "{6C25240C-613D-4A86-A04E-784BA6726094}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -87,18 +83,6 @@ Global
{3E835099-C417-4D82-8D5C-13DC09AF48AC}.Release|x64.Build.0 = Release|x64
{3E835099-C417-4D82-8D5C-13DC09AF48AC}.Release|x86.ActiveCfg = Release|x86
{3E835099-C417-4D82-8D5C-13DC09AF48AC}.Release|x86.Build.0 = Release|x86
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x64.ActiveCfg = Debug|x64
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x64.Build.0 = Debug|x64
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x86.ActiveCfg = Debug|x86
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Debug|x86.Build.0 = Debug|x86
{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|x64
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x64.Build.0 = Release|x64
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x86.ActiveCfg = Release|x86
{E46F11D0-3C88-4D43-8CCC-EE7182CAD5C1}.Release|x86.Build.0 = Release|x86
{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|x64
@ -135,18 +119,6 @@ Global
{FF7FD66A-B3E2-4D39-A457-A00C94EDE9E6}.Release|x64.Build.0 = Release|x64
{FF7FD66A-B3E2-4D39-A457-A00C94EDE9E6}.Release|x86.ActiveCfg = Release|x86
{FF7FD66A-B3E2-4D39-A457-A00C94EDE9E6}.Release|x86.Build.0 = Release|x86
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Debug|x64.ActiveCfg = Debug|x64
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Debug|x64.Build.0 = Debug|x64
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Debug|x86.ActiveCfg = Debug|x86
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Debug|x86.Build.0 = Debug|x86
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Release|Any CPU.ActiveCfg = Release|x64
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Release|Any CPU.Build.0 = Release|x64
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Release|x64.ActiveCfg = Release|x64
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Release|x64.Build.0 = Release|x64
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Release|x86.ActiveCfg = Release|x86
{8D9EA0C3-EC7F-48AC-A2ED-9C60DD5A3987}.Release|x86.Build.0 = Release|x86
{6B726FC4-A907-4813-BF38-3342E02AA8D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B726FC4-A907-4813-BF38-3342E02AA8D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B726FC4-A907-4813-BF38-3342E02AA8D2}.Debug|x64.ActiveCfg = Debug|x64
@ -161,16 +133,16 @@ Global
{6B726FC4-A907-4813-BF38-3342E02AA8D2}.Release|x86.Build.0 = Release|x86
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x64.ActiveCfg = Debug|x64
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x64.Build.0 = Debug|x64
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x86.ActiveCfg = Debug|x86
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x86.Build.0 = Debug|x86
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x64.ActiveCfg = Debug|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x64.Build.0 = Debug|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x86.ActiveCfg = Debug|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Debug|x86.Build.0 = Debug|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|Any CPU.Build.0 = Release|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x64.ActiveCfg = Release|x64
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x64.Build.0 = Release|x64
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x86.ActiveCfg = Release|x86
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x86.Build.0 = Release|x86
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x64.ActiveCfg = Release|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x64.Build.0 = Release|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x86.ActiveCfg = Release|Any CPU
{A3D0368C-0850-4614-B5B5-41B9D5135AA9}.Release|x86.Build.0 = Release|Any CPU
{48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Debug|x64.ActiveCfg = Debug|x64
@ -185,16 +157,16 @@ Global
{48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x86.Build.0 = Release|x86
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x64.ActiveCfg = Debug|x64
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x64.Build.0 = Debug|x64
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x86.ActiveCfg = Debug|x86
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x86.Build.0 = Debug|x86
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x64.ActiveCfg = Debug|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x64.Build.0 = Debug|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x86.ActiveCfg = Debug|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Debug|x86.Build.0 = Debug|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|Any CPU.Build.0 = Release|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x64.ActiveCfg = Release|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x64.Build.0 = Release|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x86.ActiveCfg = Release|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x86.Build.0 = Release|Any CPU
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x64.ActiveCfg = Release|x64
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x64.Build.0 = Release|x64
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x86.ActiveCfg = Release|x86
{A9967E61-B1BE-4AA5-A33A-99B0B8B5FC2A}.Release|x86.Build.0 = Release|x86
{6C25240C-613D-4A86-A04E-784BA6726094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C25240C-613D-4A86-A04E-784BA6726094}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C25240C-613D-4A86-A04E-784BA6726094}.Debug|x64.ActiveCfg = Debug|Any CPU

View File

@ -1,153 +0,0 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.ScreenCast.Core.Services
{
public class ChatHostService
{
private string AsciiLogo => @"
_____ _ _
| __ \ | | | |
| |__) |___ _ __ ___ ___ | |_ ___| |_ _
| _ // _ \ '_ ` _ \ / _ \| __/ _ \ | | | |
| | \ \ __/ | | | | | (_) | || __/ | |_| |
|_| \_\___|_| |_| |_|\___/ \__\___|_|\__, |
__/ |
|___/
";
private int MaxCursorTop { get; set; }
private NamedPipeServerStream NamedPipeStream { get; set; }
private StreamReader Reader { get; set; }
private StreamWriter Writer { get; set; }
public async Task StartChat(string requesterID, string organizationName)
{
NamedPipeStream = new NamedPipeServerStream("Remotely_Chat" + requesterID, PipeDirection.InOut, 10, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Writer = new StreamWriter(NamedPipeStream);
Reader = new StreamReader(NamedPipeStream);
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Title = "Remotely Chat";
if (string.IsNullOrWhiteSpace(organizationName))
{
Console.Write(AsciiLogo);
}
else
{
WriteOrgnanizationName(organizationName);
}
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
Console.WriteLine("Your IT administrator would like to chat!");
Console.WriteLine();
Console.WriteLine("Connecting...");
Console.WriteLine();
var cts = new CancellationTokenSource(10000);
try
{
await NamedPipeStream.WaitForConnectionAsync(cts.Token);
}
catch (TaskCanceledException)
{
await Close();
return;
}
_ = Task.Run(ReadFromStream);
Console.WriteLine("You're now connected with a technician.");
Console.WriteLine();
Console.WriteLine("Type your responses below and hit Enter to send.");
Console.WriteLine("Press Ctrl + C to exit.");
while (NamedPipeStream.IsConnected)
{
SetPrompt();
var message = Console.ReadLine();
await Writer.WriteLineAsync(message);
await Writer.FlushAsync();
}
await Close();
}
private async Task Close()
{
Console.WriteLine("Connection failed. Closing...");
await Task.Delay(3000);
Environment.Exit(0);
}
private async Task ReadFromStream()
{
while (NamedPipeStream.IsConnected)
{
var message = await Reader.ReadLineAsync();
Console.WriteLine();
Console.WriteLine();
var split = message.Split(":", 2);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"{split[0]}: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(split[1]);
SetPrompt();
}
}
private void SetPrompt()
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("You: ");
Console.ForegroundColor = ConsoleColor.White;
}
private void WriteOrgnanizationName(string organizationName)
{
Console.WriteLine();
Console.WriteLine();
Console.Write(" ");
for (var i = 0; i < organizationName.Length + 10; i++)
{
Console.Write("/");
}
Console.WriteLine();
Console.Write(" ///");
for (var i = 0; i < organizationName.Length + 4; i++)
{
Console.Write(" ");
}
Console.Write("///");
Console.WriteLine();
Console.WriteLine($" /// {organizationName} ///");
Console.Write(" ///");
for (var i = 0; i < organizationName.Length + 4; i++)
{
Console.Write(" ");
}
Console.Write("///");
Console.WriteLine();
Console.Write(" ");
for (var i = 0; i < organizationName.Length + 10; i++)
{
Console.Write("/");
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
}
}
}

View File

@ -1,89 +0,0 @@
using Remotely.ScreenCast.Core;
using Remotely.ScreenCast.Core.Services;
using System;
using System.Threading;
using Remotely.ScreenCast.Linux.Services;
using Remotely.ScreenCast.Core.Communication;
using Remotely.ScreenCast.Core.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Linq;
using Remotely.ScreenCast.Core.Models;
using Remotely.Shared.Utilities;
namespace Remotely.ScreenCast.Linux
{
public class Program
{
public static Conductor Conductor { get; private set; }
public static IServiceProvider Services => ServiceContainer.Instance;
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
BuildServices();
Conductor = Services.GetRequiredService<Conductor>();
Logger.Write("Processing Args: " + string.Join(", ", Environment.GetCommandLineArgs()));
Conductor.ProcessArgs(Environment.GetCommandLineArgs().SkipWhile(x => !x.StartsWith("-")).ToArray());
if (Conductor.Mode == Core.Enums.AppMode.Chat)
{
Services.GetRequiredService<ChatHostService>().StartChat(Conductor.RequesterID, Conductor.OrganizationName).Wait();
}
else
{
var casterSocket = Services.GetRequiredService<CasterSocket>();
casterSocket.Connect(Conductor.Host).ContinueWith(async (task) =>
{
await casterSocket.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName, Conductor.DeviceID);
await casterSocket.NotifyRequesterUnattendedReady(Conductor.RequesterID);
Services.GetRequiredService<IdleTimer>().Start();
});
}
Thread.Sleep(Timeout.Infinite);
}
catch (Exception ex)
{
Logger.Write(ex);
throw;
}
}
private static void BuildServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
{
builder.AddConsole().AddDebug();
});
serviceCollection.AddSingleton<IScreenCaster, ScreenCaster>();
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputLinux>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceLinux>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerLinux>();
serviceCollection.AddSingleton<CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<ChatHostService>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerLinux>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IFileTransferService, FileTransferService>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddSingleton<ICursorIconWatcher, CursorIconWatcherLinux>();
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
}
}

View File

@ -1,61 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Description>Allows unattended remote control via the Remotely server.</Description>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssemblyName>Remotely_ScreenCast.Linux</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms>
<RootNamespace>Remotely.ScreenCast.Linux</RootNamespace>
<ApplicationIcon>favicon.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Input\**" />
<EmbeddedResource Remove="Input\**" />
<None Remove="Input\**" />
</ItemGroup>
<ItemGroup>
<None Remove="favicon.ico" />
</ItemGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScreenCast.Core\ScreenCast.Core.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -1,45 +0,0 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.Shared.Utilities;
using System;
using System.Diagnostics;
using System.IO;
namespace Remotely.ScreenCast.Linux.Services
{
public class ClipboardServiceLinux : IClipboardService
{
#pragma warning disable
public event EventHandler<string> ClipboardTextChanged;
#pragma warning restore
public void BeginWatching()
{
// Not implemented.
}
public void SetText(string clipboardText)
{
var tempPath = Path.GetTempFileName();
File.WriteAllText(tempPath, clipboardText);
try
{
var psi = new ProcessStartInfo("bash", $"-c \"cat {tempPath} | xclip -i -selection clipboard\"")
{
WindowStyle = ProcessWindowStyle.Hidden
};
var proc = Process.Start(psi);
proc.WaitForExit();
}
catch (Exception ex)
{
Logger.Write(ex);
}
finally
{
File.Delete(tempPath);
}
}
}
}

View File

@ -1,49 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>Remotely.ScreenCast.Win</RootNamespace>
<AssemblyName>Remotely_ScreenCast</AssemblyName>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<Authors>Jared Goodwin</Authors>
<Company>Translucency Software</Company>
<Product>Remotely ScreenCast</Product>
<Description>Allows unattended remote control via the Remotely server.</Description>
<Copyright>Copyright © 2020 Translucency Software</Copyright>
<PackageProjectUrl>https://remotely.one</PackageProjectUrl>
<Platforms>AnyCPU;x86;x64</Platforms>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<None Remove="favicon.ico" />
</ItemGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="1.0.3" />
<PackageReference Include="Microsoft.Win32.SystemEvents" Version="4.7.0" />
<PackageReference Include="NAudio" Version="1.10.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.1.4" />
<PackageReference Include="SharpDX" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.DXGI" Version="4.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScreenCast.Core\ScreenCast.Core.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="if $(ConfigurationName) == Debug (&#xD;&#xA; if $(PlatformName) == Any CPU (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\Debug\netcoreapp3.1\ScreenCast\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x64 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x64\Debug\netcoreapp3.1\ScreenCast\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x64\Debug\netcoreapp3.1\ScreenCast\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x86 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x86\Debug\netcoreapp3.1\ScreenCast\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x86\Debug\netcoreapp3.1\ScreenCast\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA;)" />
</Target>
</Project>

View File

@ -1,81 +0,0 @@
using Remotely.ScreenCast.Core.Interfaces;
using Remotely.ScreenCast.Core.Services;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
using System.Threading;
using System.Windows.Forms;
namespace Remotely.ScreenCast.Win.Services
{
public class ClipboardServiceWin : IClipboardService
{
public event EventHandler<string> ClipboardTextChanged;
private string ClipboardText { get; set; }
private System.Timers.Timer ClipboardWatcher { get; set; }
public void BeginWatching()
{
ClipboardWatcher?.Dispose();
ClipboardWatcher = new System.Timers.Timer(500);
ClipboardWatcher.Elapsed += ClipboardWatcher_Elapsed;
ClipboardWatcher.Start();
}
public void SetText(string clipboardText)
{
try
{
var thread = new Thread(() =>
{
try
{
if (string.IsNullOrWhiteSpace(clipboardText))
{
Clipboard.Clear();
}
else
{
Clipboard.SetText(clipboardText);
}
}
catch { }
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void StopWatching()
{
ClipboardWatcher?.Stop();
}
private void ClipboardWatcher_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var thread = new Thread(() =>
{
try
{
Win32Interop.SwitchToInputDesktop();
if (Clipboard.ContainsText() && Clipboard.GetText() != ClipboardText)
{
ClipboardText = Clipboard.GetText();
ClipboardTextChanged?.Invoke(this, ClipboardText);
}
}
catch { }
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
}

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View File

@ -64,10 +64,11 @@ namespace Remotely.Server.API
}
var waitTime = DateTimeOffset.Now - startWait;
downloadingAgents.Set(downloadId, string.Empty, TimeSpan.FromMinutes(10));
DataService.WriteEvent($"Download started after wait time of {waitTime}. " + "" +
$"Current Downloads: {downloadingAgents.Count}. Max Allowed: {AppConfig.MaxConcurrentUpdates}", EventType.Debug, null);
downloadingAgents.Set(downloadId, string.Empty, TimeSpan.FromMinutes(10));
byte[] fileBytes;
string filePath;

View File

@ -6,6 +6,7 @@ using Remotely.Shared.Models;
using Remotely.Server.Models;
using Remotely.Server.Services;
using Microsoft.AspNetCore.SignalR;
using Remotely.Server.Hubs;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
@ -18,21 +19,21 @@ namespace Remotely.Server.API
public LoginController(SignInManager<RemotelyUser> signInManager,
DataService dataService,
ApplicationConfig appConfig,
IHubContext<RCDeviceHub> rcDeviceHub,
IHubContext<CasterHub> casterHub,
IHubContext<RCBrowserHub> rcBrowserHub)
{
SignInManager = signInManager;
DataService = dataService;
AppConfig = appConfig;
RCDeviceHub = rcDeviceHub;
RCBrowserHub = rcBrowserHub;
CasterHubContext = casterHub;
RCBrowserHubContext = rcBrowserHub;
}
private SignInManager<RemotelyUser> SignInManager { get; }
private DataService DataService { get; }
public ApplicationConfig AppConfig { get; }
private IHubContext<RCDeviceHub> RCDeviceHub { get; }
private IHubContext<RCBrowserHub> RCBrowserHub { get; }
private IHubContext<CasterHub> CasterHubContext { get; }
private IHubContext<RCBrowserHub> RCBrowserHubContext { get; }
[HttpPost]
public async Task<IActionResult> Post([FromBody]ApiLogin login)
@ -72,11 +73,11 @@ namespace Remotely.Server.API
if (HttpContext?.User?.Identity?.IsAuthenticated == true)
{
orgId = DataService.GetUserByName(HttpContext.User.Identity.Name)?.OrganizationID;
var activeSessions = Services.RCDeviceHub.SessionInfoList.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
var activeSessions = CasterHub.SessionInfoList.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
foreach (var session in activeSessions.ToList())
{
await RCDeviceHub.Clients.Client(session.Value.RCDeviceSocketID).SendAsync("Disconnect", "User logged out.");
await RCBrowserHub.Clients.Client(session.Value.RequesterSocketID).SendAsync("ConnectionFailed");
await CasterHubContext.Clients.Client(session.Value.CasterSocketID).SendAsync("Disconnect", "User logged out.");
await RCBrowserHubContext.Clients.Client(session.Value.RequesterSocketID).SendAsync("ConnectionFailed");
}
}
await SignInManager.SignOutAsync();

View File

@ -10,6 +10,7 @@ using Remotely.Server.Models;
using Remotely.Server.Services;
using Remotely.Server.Attributes;
using Remotely.Shared.Helpers;
using Remotely.Server.Hubs;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
@ -20,18 +21,18 @@ namespace Remotely.Server.API
public class RemoteControlController : ControllerBase
{
public RemoteControlController(DataService dataService,
IHubContext<DeviceHub> deviceHub,
IHubContext<AgentHub> agentHub,
ApplicationConfig appConfig,
SignInManager<RemotelyUser> signInManager)
{
DataService = dataService;
DeviceHub = deviceHub;
AgentHubContext = agentHub;
AppConfig = appConfig;
SignInManager = signInManager;
}
public DataService DataService { get; }
public IHubContext<DeviceHub> DeviceHub { get; }
public IHubContext<AgentHub> AgentHubContext { get; }
public ApplicationConfig AppConfig { get; }
public SignInManager<RemotelyUser> SignInManager { get; }
@ -76,7 +77,7 @@ namespace Remotely.Server.API
private async Task<IActionResult> InitiateRemoteControl(string deviceID, string orgID)
{
var targetDevice = Services.DeviceHub.ServiceConnections.FirstOrDefault(x =>
var targetDevice = Hubs.AgentHub.ServiceConnections.FirstOrDefault(x =>
x.Value.OrganizationID == orgID &&
x.Value.ID.ToLower() == deviceID.ToLower());
@ -89,24 +90,24 @@ namespace Remotely.Server.API
}
var currentUsers = RCDeviceHub.SessionInfoList.Count(x => x.Value.OrganizationID == orgID);
var currentUsers = CasterHub.SessionInfoList.Count(x => x.Value.OrganizationID == orgID);
if (currentUsers >= AppConfig.RemoteControlSessionLimit)
{
return BadRequest("There are already the maximum amount of active remote control sessions for your organization.");
}
var existingSessions = RCDeviceHub.SessionInfoList
var existingSessions = CasterHub.SessionInfoList
.Where(x => x.Value.DeviceID == targetDevice.Value.ID)
.Select(x => x.Key)
.ToList();
await DeviceHub.Clients.Client(targetDevice.Key).SendAsync("RemoteControl", Request.HttpContext.Connection.Id, targetDevice.Key);
await AgentHubContext.Clients.Client(targetDevice.Key).SendAsync("RemoteControl", Request.HttpContext.Connection.Id, targetDevice.Key);
bool remoteControlStarted()
{
return !RCDeviceHub.SessionInfoList.Values
return !CasterHub.SessionInfoList.Values
.Where(x => x.DeviceID == targetDevice.Value.ID)
.All(x => existingSessions.Contains(x.RCDeviceSocketID));
.All(x => existingSessions.Contains(x.CasterSocketID));
};
if (!await TaskHelper.DelayUntil(remoteControlStarted, TimeSpan.FromSeconds(30)))
@ -115,9 +116,9 @@ namespace Remotely.Server.API
}
else
{
var rcSession = RCDeviceHub.SessionInfoList.Values.LastOrDefault(x => x.DeviceID == targetDevice.Value.ID && !existingSessions.Contains(x.RCDeviceSocketID));
var rcSession = CasterHub.SessionInfoList.Values.LastOrDefault(x => x.DeviceID == targetDevice.Value.ID && !existingSessions.Contains(x.CasterSocketID));
var otp = RemoteControlFilterAttribute.GetOtp(targetDevice.Value.ID);
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl?clientID={rcSession.RCDeviceSocketID}&serviceID={targetDevice.Key}&fromApi=true&otp={Uri.EscapeDataString(otp)}");
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl?clientID={rcSession.CasterSocketID}&serviceID={targetDevice.Key}&fromApi=true&otp={Uri.EscapeDataString(otp)}");
}
}
else

View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Remotely.Shared.Helpers;
using System.IO;
using Remotely.Server.Attributes;
using Remotely.Server.Hubs;
namespace Remotely.Server.API
{
@ -19,15 +20,15 @@ namespace Remotely.Server.API
{
public ScriptingController(DataService dataService,
UserManager<RemotelyUser> userManager,
IHubContext<DeviceHub> deviceHub)
IHubContext<AgentHub> agentHub)
{
DataService = dataService;
UserManager = userManager;
DeviceHub = deviceHub;
AgentHubContext = agentHub;
}
private DataService DataService { get; }
private IHubContext<DeviceHub> DeviceHub { get; }
private IHubContext<AgentHub> AgentHubContext { get; }
private UserManager<RemotelyUser> UserManager { get; }
[ServiceFilter(typeof(ApiAuthorizationFilter))]
@ -55,7 +56,7 @@ namespace Remotely.Server.API
Request.Headers.TryGetValue("OrganizationID", out var orgID);
KeyValuePair<string, Device> connection = Services.DeviceHub.ServiceConnections.FirstOrDefault(x =>
KeyValuePair<string, Device> connection = AgentHub.ServiceConnections.FirstOrDefault(x =>
x.Value.OrganizationID == orgID &&
x.Value.ID == deviceID);
@ -75,14 +76,14 @@ namespace Remotely.Server.API
};
DataService.AddOrUpdateCommandResult(commandResult);
var requestID = Guid.NewGuid().ToString();
await DeviceHub.Clients.Client(connection.Key).SendAsync("ExecuteCommandFromApi", mode, requestID, command, commandResult.ID, Guid.NewGuid().ToString());
var success = await TaskHelper.DelayUntil(() => Services.DeviceHub.ApiScriptResults.TryGetValue(requestID, out _), TimeSpan.FromSeconds(30));
await AgentHubContext.Clients.Client(connection.Key).SendAsync("ExecuteCommandFromApi", mode, requestID, command, commandResult.ID, Guid.NewGuid().ToString());
var success = await TaskHelper.DelayUntil(() => AgentHub.ApiScriptResults.TryGetValue(requestID, out _), TimeSpan.FromSeconds(30));
if (!success)
{
return commandResult;
}
Services.DeviceHub.ApiScriptResults.TryGetValue(requestID, out var commandID);
Services.DeviceHub.ApiScriptResults.Remove(requestID);
AgentHub.ApiScriptResults.TryGetValue(requestID, out var commandID);
AgentHub.ApiScriptResults.Remove(requestID);
DataService.DetachEntity(commandResult);
var result = DataService.GetCommandResult(commandID.ToString(), orgID);
return result;

Some files were not shown because too many files have changed in this diff Show More