Extract remote control functionality into separate library. (#539)

* Convert server to new single-file startup model.

* Add remote control implementations.

* Implement IViewerAuthorizer.

* Update hub endpoints.

* Implement HubEventHandler.

* Implement ViewerHubDataProvider.

* Implement page data provider.

* Implement RCL and refactor.

* Update submodule.

* Replace submodule with NuGet.

* Update copy URL.

* Update NuGet.

* Remove deprecated WebRTC.

* Remove deprecated WebRTC.

* Update Immense.RemoteControl

* Building out desktop projects.

* Bring more services into submodule.

* Update submodule.

* Update submodule.

* Refactoring for module.

* Update submodule.

* Update submodule

* Got Windows desktop app running.

* Refactor for submodule changes.

* FIx unattended session start.

* Switch desktop app out of console mode.

* Fix tests.

* Update publishing.

* Remove ClickOnce middleware.

* Remove ClickOnce remnants.

* Update submodule

* Add some logging.

* Update Linux path.

* Update submodule.

* Add cleanup service for unattended sessions that failed to start.

* Update submodule.

* Fix chat.

* Add ValidateExecutableReferencesMatchSelfContained property.

* Add other submodule projects.  Align checkbox.

* Update submodule.  Reduce deserialization in the browser, resulting in faster renders.

* Update submodule.

* Update submodule.

* Update submodule.

* Update submodule.

* Add orgId back for branding.

* Get branding loading in desktop apps.

* Update submodule.

* Create log dir.

* Refactor version check on config page.

* Update submodule.

* Update submodule.

* Change submodule URL.

* Correct namespace.

* Update submodule.

* Checkout submodules recursively.
This commit is contained in:
Jared Goodwin 2022-12-23 06:39:12 -08:00 committed by GitHub
parent eae16cb3dc
commit 3ef4cdf81a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
320 changed files with 10543 additions and 29853 deletions

View File

@ -1,4 +1,4 @@
[*.cs]
[ "*.cs" ]
# CA1416: Validate platform compatibility
dotnet_diagnostic.CA1416.severity = none
@ -7,4 +7,108 @@ dotnet_diagnostic.CA1416.severity = none
dotnet_diagnostic.CS1591.severity = none
csharp_style_var_for_built_in_types=true:silent
csharp_style_var_when_type_is_apparent=true:silent
csharp_style_var_elsewhere=true:silent
csharp_style_var_elsewhere=true:silent
# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = none
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_indent_labels = one_less_than_current
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_prefer_simplified_interpolation = true:suggestion
# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = silent
[*.cs]
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_prefer_static_local_function = true:suggestion

View File

@ -40,6 +40,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install .NET Core
uses: actions/setup-dotnet@v1.9.0
@ -109,7 +111,8 @@ jobs:
with:
# Comment out the below 'repository' line if you want to build from
# your fork instead of the author's.
repository: lucent-sea/Remotely
repository: immense/Remotely
submodules: recursive
fetch-depth: 0
# Test the Server URL to make sure it's valid

View File

@ -27,6 +27,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
# Install the .NET Core workload

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "submodules/Immense.RemoteControl"]
path = submodules/Immense.RemoteControl
url = git@github.com:immense/RemoteControl.git

View File

@ -122,15 +122,15 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="..\Shared\AppConstants.cs">
<Link>AppConstants.cs</Link>
</Compile>
<Compile Include="..\Shared\Models\ConnectionInfo.cs">
<Link>Models\ConnectionInfo.cs</Link>
</Compile>
<Compile Include="..\Shared\Models\DeviceSetupOptions.cs">
<Link>Models\DeviceSetupOptions.cs</Link>
</Compile>
<Compile Include="..\Shared\Utilities\AppConstants.cs">
<Link>Utilities\AppConstants.cs</Link>
</Compile>
<Compile Include="Models\BrandingInfo.cs" />
<Compile Include="Utilities\CommandLineParser.cs" />
<Compile Include="Utilities\MessageBoxEx.cs" />

View File

@ -1,7 +1,6 @@
using Remotely.Agent.Installer.Win.Models;
using Remotely.Agent.Installer.Win.Services;
using Remotely.Agent.Installer.Win.Utilities;
using Remotely.Shared.Utilities;
using Remotely.Shared.Models;
using System;
using System.Diagnostics;
@ -18,6 +17,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Net;
using Remotely.Shared;
namespace Remotely.Agent.Installer.Win.ViewModels
{
@ -34,7 +34,7 @@ namespace Remotely.Agent.Installer.Win.ViewModels
private int _progress;
private string _serverUrl;
private string _serverUrl = AppConstants.ServerUrl;
private string _statusMessage;
public MainWindowViewModel()

View File

@ -23,16 +23,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.6" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.5" />
<PackageReference Include="Microsoft.WSMan.Management" Version="7.2.5" />
<PackageReference Include="Microsoft.WSMan.Runtime" Version="7.2.5" />
<PackageReference Include="System.Management.Automation" Version="7.2.5" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.9" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.6" />
<PackageReference Include="Microsoft.WSMan.Management" Version="7.2.6" />
<PackageReference Include="Microsoft.WSMan.Runtime" Version="7.2.6" />
<PackageReference Include="System.Management.Automation" Version="7.2.6" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
</ItemGroup>

View File

@ -6,8 +6,8 @@ namespace Remotely.Agent.Interfaces
{
public interface IAppLauncher
{
Task<int> LaunchChatService(string orgName, string requesterID, HubConnection hubConnection);
Task LaunchRemoteControl(int targetSessionId, string requesterID, string serviceID, HubConnection hubConnection);
Task RestartScreenCaster(List<string> viewerIDs, string serviceID, string requesterID, HubConnection hubConnection, int targetSessionID = -1);
Task<int> LaunchChatService(string pipeName, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection);
Task LaunchRemoteControl(int targetSessionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection);
Task RestartScreenCaster(List<string> viewerIDs, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection, int targetSessionID = -1);
}
}

View File

@ -11,6 +11,7 @@ using System.IO;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Versioning;
namespace Remotely.Agent
{
@ -97,20 +98,10 @@ namespace Remotely.Agent
SetWorkingDirectory();
if (EnvironmentHelper.IsWindows &&
if (OperatingSystem.IsWindows() &&
Process.GetCurrentProcess().SessionId == 0)
{
_ = Task.Run(() =>
{
try
{
ServiceBase.Run(new WindowsService());
}
catch (Exception ex)
{
Logger.Write(ex, "Failed to start service.", EventType.Warning);
}
});
_ = Task.Run(StartService);
}
await Services.GetRequiredService<IUpdater>().BeginChecking();
@ -129,5 +120,18 @@ namespace Remotely.Agent
var assemblyDir = Path.GetDirectoryName(assemblyPath);
Directory.SetCurrentDirectory(assemblyDir);
}
[SupportedOSPlatform("windows")]
private static void StartService()
{
try
{
ServiceBase.Run(new WindowsService());
}
catch (Exception ex)
{
Logger.Write(ex, "Failed to start service.", EventType.Warning);
}
}
}
}

View File

@ -65,7 +65,7 @@ namespace Remotely.Agent.Services
_connectionInfo = _configService.GetConnectionInfo();
_hubConnection = new HubConnectionBuilder()
.WithUrl(_connectionInfo.Host + "/AgentHub")
.WithUrl(_connectionInfo.Host + "/hubs/service")
.AddMessagePackProtocol()
.Build();
@ -178,10 +178,8 @@ namespace Remotely.Agent.Services
private void RegisterMessageHandlers()
{
// TODO: Remove possibility for circular dependencies in the future
// by emitting these events so other services can listen for them.
_hubConnection.On("ChangeWindowsSession", async (string serviceID, string viewerID, int targetSessionID) =>
_hubConnection.On("ChangeWindowsSession", async (string viewerConnectionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, int targetSessionID) =>
{
try
{
@ -191,7 +189,7 @@ namespace Remotely.Agent.Services
return;
}
await _appLauncher.RestartScreenCaster(new List<string>() { viewerID }, serviceID, viewerID, _hubConnection, targetSessionID);
await _appLauncher.RestartScreenCaster(new List<string>() { viewerConnectionId }, sessionId, accessKey, userConnectionId, requesterName, orgName, orgId, _hubConnection, targetSessionID);
}
catch (Exception ex)
{
@ -199,7 +197,7 @@ namespace Remotely.Agent.Services
}
});
_hubConnection.On("Chat", async (string senderName, string message, string orgName, bool disconnected, string senderConnectionID) =>
_hubConnection.On("Chat", async (string senderName, string message, string orgName, string orgId, bool disconnected, string senderConnectionID) =>
{
try
{
@ -209,7 +207,7 @@ namespace Remotely.Agent.Services
return;
}
await _chatService.SendMessage(senderName, message, orgName, disconnected, senderConnectionID, _hubConnection);
await _chatService.SendMessage(senderName, message, orgName, orgId, disconnected, senderConnectionID, _hubConnection);
}
catch (Exception ex)
{
@ -342,7 +340,7 @@ namespace Remotely.Agent.Services
}
});
_hubConnection.On("RemoteControl", async (string requesterID, string serviceID) =>
_hubConnection.On("RemoteControl", async (string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId) =>
{
try
{
@ -351,7 +349,7 @@ namespace Remotely.Agent.Services
Logger.Write("Remote control attempted before server was verified.", EventType.Warning);
return;
}
await _appLauncher.LaunchRemoteControl(-1, requesterID, serviceID, _hubConnection);
await _appLauncher.LaunchRemoteControl(-1, sessionId, accessKey, userConnectionId, requesterName, orgName, orgId, _hubConnection);
}
catch (Exception ex)
{
@ -359,7 +357,7 @@ namespace Remotely.Agent.Services
}
});
_hubConnection.On("RestartScreenCaster", async (List<string> viewerIDs, string serviceID, string requesterID) =>
_hubConnection.On("RestartScreenCaster", async (List<string> viewerIDs, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId) =>
{
try
{
@ -368,7 +366,7 @@ namespace Remotely.Agent.Services
Logger.Write("Remote control attempted before server was verified.", EventType.Warning);
return;
}
await _appLauncher.RestartScreenCaster(viewerIDs, serviceID, requesterID, _hubConnection);
await _appLauncher.RestartScreenCaster(viewerIDs, sessionId, accessKey, userConnectionId, requesterName, orgName, orgId, _hubConnection);
}
catch (Exception ex)
{

View File

@ -8,7 +8,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web.Services.Description;
namespace Remotely.Agent.Services
{
@ -26,94 +28,6 @@ namespace Remotely.Agent.Services
}
public async Task<int> LaunchChatService(string orgName, string requesterID, HubConnection hubConnection)
{
try
{
if (!File.Exists(_rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage",
"Chat executable not found on target device.",
"Executable not found on device.",
"bg-danger",
requesterID);
}
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", $"Starting chat service.", "Starting chat service.", "bg-success", requesterID);
var args = $"{_rcBinaryPath} " +
$"-mode Chat " +
$"-requester \"{requesterID}\" " +
$"-organization \"{orgName}\" " +
$"-host \"{_connectionInfo.Host}\" " +
$"-orgid \"{_connectionInfo.OrganizationID}\"";
return StartLinuxDesktopApp(args);
}
catch (Exception ex)
{
Logger.Write(ex);
await hubConnection.SendAsync("DisplayMessage", "Chat service failed to start on target device.", "Failed to start chat service.", "bg-danger", requesterID);
}
return -1;
}
public async Task LaunchRemoteControl(int targetSessionId, string requesterID, string serviceID, HubConnection hubConnection)
{
try
{
if (!File.Exists(_rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage",
"Remote control executable not found on target device.",
"Executable not found on device.",
"bg-danger",
requesterID);
return;
}
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", "Starting remote control.", "Starting remote control.", "bg-success", requesterID);
var args = $"{_rcBinaryPath} " +
$"-mode Unattended " +
$"-requester \"{requesterID}\" " +
$"-serviceid \"{serviceID}\" " +
$"-deviceid {_connectionInfo.DeviceID} " +
$"-host \"{_connectionInfo.Host}\" " +
$"-orgid \"{_connectionInfo.OrganizationID}\"";
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.", "bg-danger", requesterID);
}
}
public async Task RestartScreenCaster(List<string> viewerIDs, string serviceID, string requesterID, HubConnection hubConnection, int targetSessionID = -1)
{
try
{
// Start Desktop app.
var args = $"{_rcBinaryPath} " +
$"-mode Unattended " +
$"-requester \"{requesterID}\" " +
$"-serviceid \"{serviceID}\" " +
$"-deviceid {_connectionInfo.DeviceID} " +
$"-host \"{_connectionInfo.Host}\" " +
$"-orgid \"{_connectionInfo.OrganizationID}\" " +
$"-relaunch true " +
$"-viewers {string.Join(",", viewerIDs)}";
StartLinuxDesktopApp(args);
}
catch (Exception ex)
{
await hubConnection.SendAsync("SendConnectionFailedToViewers", viewerIDs);
Logger.Write(ex);
throw;
}
}
private int StartLinuxDesktopApp(string args)
{
var xauthority = GetXorgAuth();
@ -180,5 +94,97 @@ namespace Remotely.Agent.Services
catch { }
return string.Empty;
}
public async Task<int> LaunchChatService(string pipeName, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
try
{
if (!File.Exists(_rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage",
"Chat executable not found on target device.",
"Executable not found on device.",
"bg-danger",
userConnectionId);
}
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", $"Starting chat service.", "Starting chat service.", "bg-success", userConnectionId);
var args =
_rcBinaryPath +
$" --mode Chat" +
$" --host \"{_connectionInfo.Host}\"" +
$" --pipe-name {pipeName}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"";
return StartLinuxDesktopApp(args);
}
catch (Exception ex)
{
Logger.Write(ex);
await hubConnection.SendAsync("DisplayMessage", "Chat service failed to start on target device.", "Failed to start chat service.", "bg-danger", userConnectionId);
}
return -1;
}
public async Task LaunchRemoteControl(int targetSessionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
try
{
if (!File.Exists(_rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage",
"Remote control executable not found on target device.",
"Executable not found on device.",
"bg-danger",
userConnectionId);
return;
}
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", "Starting remote control.", "Starting remote control.", "bg-success", userConnectionId);
var args =
_rcBinaryPath +
$" --mode Unattended" +
$" --host {_connectionInfo.Host}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --session-id \"{sessionId}\"" +
$" --access-key \"{accessKey}\"";
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.", "bg-danger", userConnectionId);
}
}
public async Task RestartScreenCaster(List<string> viewerIDs, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection, int targetSessionID = -1)
{
try
{
var args =
_rcBinaryPath +
$" --mode Unattended" +
$" --host {_connectionInfo.Host}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --session-id \"{sessionId}\"" +
$" --access-key \"{accessKey}\"";
StartLinuxDesktopApp(args);
}
catch (Exception ex)
{
await hubConnection.SendAsync("SendConnectionFailedToViewers", viewerIDs);
Logger.Write(ex);
throw;
}
}
}
}

View File

@ -7,20 +7,21 @@ namespace Remotely.Agent.Services
{
public class AppLauncherMac : IAppLauncher
{
public async Task<int> LaunchChatService(string orgName, string requesterID, HubConnection hubConnection)
public async Task<int> LaunchChatService(string pipeName, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
await hubConnection.SendAsync("DisplayMessage", "Feature under development.", "Feature is under development.", "bg-warning", requesterID);
await hubConnection.SendAsync("DisplayMessage", "Feature under development.", "Feature is under development.", "bg-warning", userConnectionId);
return 0;
}
public async Task LaunchRemoteControl(int targetSessionId, string requesterID, string serviceID, HubConnection hubConnection)
public async Task LaunchRemoteControl(int targetSessionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
await hubConnection.SendAsync("DisplayMessage", "Feature under development.", "Feature is under development.", "bg-warning", requesterID);
await hubConnection.SendAsync("DisplayMessage", "Feature under development.", "Feature is under development.", "bg-warning", userConnectionId);
}
public async Task RestartScreenCaster(List<string> viewerIDs, string serviceID, string requesterID, HubConnection hubConnection, int targetSessionID = -1)
public async Task RestartScreenCaster(List<string> viewerIDs, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection, int targetSessionID = -1)
{
await hubConnection.SendAsync("DisplayMessage", "Feature under development.", "Feature is under development.", "bg-warning", requesterID);
await hubConnection.SendAsync("DisplayMessage", "Feature under development.", "Feature is under development.", "bg-warning", userConnectionId);
}
}
}

View File

@ -7,43 +7,46 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Threading.Tasks;
namespace Remotely.Agent.Services
{
[SupportedOSPlatform("windows")]
public class AppLauncherWin : IAppLauncher
{
private readonly ConnectionInfo _connectionInfo;
private readonly string _rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName);
public AppLauncherWin(ConfigService configService)
{
ConnectionInfo = configService.GetConnectionInfo();
_connectionInfo = configService.GetConnectionInfo();
}
private ConnectionInfo ConnectionInfo { get; }
public async Task<int> LaunchChatService(string orgName, string requesterID, HubConnection hubConnection)
public async Task<int> LaunchChatService(string pipeName, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
try
{
if (!File.Exists(_rcBinaryPath))
{
await hubConnection.SendAsync("DisplayMessage", "Chat executable not found on target device.", "Executable not found on device.", "bg-danger", requesterID);
await hubConnection.SendAsync("DisplayMessage", "Chat executable not found on target device.", "Executable not found on device.", "bg-danger", userConnectionId);
}
// Start Desktop app.
await hubConnection.SendAsync("DisplayMessage", $"Starting chat service.", "Starting chat service.", "bg-success", requesterID);
await hubConnection.SendAsync("DisplayMessage", $"Starting chat service.", "Starting chat service.", "bg-success", userConnectionId);
if (WindowsIdentity.GetCurrent().IsSystem)
{
var result = Win32Interop.OpenInteractiveProcess($"{_rcBinaryPath} " +
$"-mode Chat " +
$"-requester \"{requesterID}\" " +
$"-organization \"{orgName}\" " +
$"-host \"{ConnectionInfo.Host}\" " +
$"-orgid \"{ConnectionInfo.OrganizationID}\"",
var result = Win32Interop.OpenInteractiveProcess(
_rcBinaryPath +
$" --mode Chat" +
$" --host \"{_connectionInfo.Host}\"" +
$" --pipe-name {pipeName}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"",
targetSessionId: -1,
forceConsoleSession: false,
desktopName: "default",
@ -55,7 +58,7 @@ namespace Remotely.Agent.Services
"Chat service failed to start on target device.",
"Failed to start chat service.",
"bg-danger",
requesterID);
userConnectionId);
}
else
{
@ -64,12 +67,13 @@ namespace Remotely.Agent.Services
}
else
{
return Process.Start(_rcBinaryPath,
$"-mode Chat " +
$"-requester \"{requesterID}\" " +
$"-organization \"{orgName}\" " +
$"-host \"{ConnectionInfo.Host}\" " +
$"-orgid \"{ConnectionInfo.OrganizationID}\"").Id;
return Process.Start(_rcBinaryPath,
$" --mode Chat" +
$" --host \"{_connectionInfo.Host}\"" +
$" --requester-name \"{userConnectionId}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --pipe-name {pipeName}").Id;
}
}
catch (Exception ex)
@ -79,12 +83,12 @@ namespace Remotely.Agent.Services
"Chat service failed to start on target device.",
"Failed to start chat service.",
"bg-danger",
requesterID);
userConnectionId);
}
return -1;
}
public async Task LaunchRemoteControl(int targetSessionId, string requesterID, string serviceID, HubConnection hubConnection)
public async Task LaunchRemoteControl(int targetSessionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
try
{
@ -94,7 +98,7 @@ namespace Remotely.Agent.Services
"Remote control executable not found on target device.",
"Executable not found on device.",
"bg-danger",
requesterID);
userConnectionId);
return;
}
@ -104,16 +108,18 @@ namespace Remotely.Agent.Services
"Starting remote control.",
"Starting remote control.",
"bg-success",
requesterID);
userConnectionId);
if (WindowsIdentity.GetCurrent().IsSystem)
{
var result = Win32Interop.OpenInteractiveProcess(_rcBinaryPath +
$" -mode Unattended" +
$" -requester \"{requesterID}\"" +
$" -serviceid \"{serviceID}\"" +
$" -deviceid {ConnectionInfo.DeviceID}" +
$" -host {ConnectionInfo.Host}" +
$" -orgid \"{ConnectionInfo.OrganizationID}\"",
var result = Win32Interop.OpenInteractiveProcess(
_rcBinaryPath +
$" --mode Unattended" +
$" --host {_connectionInfo.Host}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --session-id \"{sessionId}\"" +
$" --access-key \"{accessKey}\"",
targetSessionId: targetSessionId,
forceConsoleSession: Shlwapi.IsOS(OsType.OS_ANYSERVER) && targetSessionId == -1,
desktopName: "default",
@ -125,19 +131,19 @@ namespace Remotely.Agent.Services
"Remote control failed to start on target device.",
"Failed to start remote control.",
"bg-danger",
requesterID);
userConnectionId);
}
}
else
{
// 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} " +
$"-orgid \"{ConnectionInfo.OrganizationID}\"");
Process.Start(_rcBinaryPath,
$" --mode Unattended" +
$" --host {_connectionInfo.Host}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --session-id \"{sessionId}\"" +
$" --access-key \"{accessKey}\"");
}
}
catch (Exception ex)
@ -147,10 +153,10 @@ namespace Remotely.Agent.Services
"Remote control failed to start on target device.",
"Failed to start remote control.",
"bg-danger",
requesterID);
userConnectionId);
}
}
public async Task RestartScreenCaster(List<string> viewerIDs, string serviceID, string requesterID, HubConnection hubConnection, int targetSessionID = -1)
public async Task RestartScreenCaster(List<string> viewerIDs, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection, int targetSessionID = -1)
{
try
{
@ -162,14 +168,15 @@ namespace Remotely.Agent.Services
await Task.Delay(1000);
var result = Win32Interop.OpenInteractiveProcess(_rcBinaryPath +
$" -mode Unattended" +
$" -requester \"{requesterID}\"" +
$" -serviceid \"{serviceID}\"" +
$" -deviceid {ConnectionInfo.DeviceID}" +
$" -host {ConnectionInfo.Host}" +
$" -orgid \"{ConnectionInfo.OrganizationID}\"" +
$" -relaunch true" +
$" -viewers {String.Join(",", viewerIDs)}",
$" --mode Unattended" +
$" --relaunch true" +
$" --host {_connectionInfo.Host}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --session-id \"{sessionId}\"" +
$" --access-key \"{accessKey}\"" +
$" --viewers {string.Join(",", viewerIDs)}",
targetSessionId: targetSessionID,
forceConsoleSession: Shlwapi.IsOS(OsType.OS_ANYSERVER) && targetSessionID == -1,
@ -185,22 +192,21 @@ namespace Remotely.Agent.Services
"Remote control failed to start on target device.",
"Failed to start remote control.",
"bg-danger",
requesterID);
userConnectionId);
}
}
else
{
// 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} " +
$" -orgid \"{ConnectionInfo.OrganizationID}\"" +
$"-relaunch true " +
$"-viewers {String.Join(",", viewerIDs)}");
Process.Start(_rcBinaryPath,
$" --mode Unattended" +
$" --relaunch true" +
$" --host {_connectionInfo.Host}" +
$" --requester-name \"{requesterName}\"" +
$" --org-name \"{orgName}\"" +
$" --org-id \"{orgId}\"" +
$" --session-id \"{sessionId}\"" +
$" --access-key \"{accessKey}\"" +
$" --viewers {string.Join(",", viewerIDs)}");
}
}
catch (Exception ex)

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR.Client;
using Immense.RemoteControl.Shared.Models;
using Microsoft.AspNetCore.SignalR.Client;
using Remotely.Agent.Interfaces;
using Remotely.Agent.Models;
using Remotely.Shared.Models;
@ -16,13 +17,15 @@ namespace Remotely.Agent.Services
{
public class ChatClientService
{
private readonly IAppLauncher _appLauncher;
private readonly MemoryCache _chatClients = new("ChatClients");
private readonly SemaphoreSlim _messageLock = new(1,1);
public ChatClientService(IAppLauncher appLauncher)
{
AppLauncher = appLauncher;
_appLauncher = appLauncher;
}
private SemaphoreSlim MessageLock { get; } = new(1,1);
private IAppLauncher AppLauncher { get; }
private CacheItemPolicy CacheItemPolicy { get; } = new()
{
SlidingExpiration = TimeSpan.FromMinutes(10),
@ -34,16 +37,17 @@ namespace Remotely.Agent.Services
})
};
private MemoryCache ChatClients { get; } = new("ChatClients");
public async Task SendMessage(string senderName,
public async Task SendMessage(
string senderName,
string message,
string orgName,
string orgId,
bool disconnected,
string senderConnectionID,
HubConnection hubConnection)
{
if (!await MessageLock.WaitAsync(30000))
if (!await _messageLock.WaitAsync(30000))
{
Logger.Write("Timed out waiting for chat message lock.", Shared.Enums.EventType.Warning);
return;
@ -52,7 +56,7 @@ namespace Remotely.Agent.Services
try
{
ChatSession chatSession;
if (!ChatClients.Contains(senderConnectionID))
if (!_chatClients.Contains(senderConnectionID))
{
if (disconnected)
{
@ -60,7 +64,8 @@ namespace Remotely.Agent.Services
return;
}
var procID = await AppLauncher.LaunchChatService(orgName, senderConnectionID, hubConnection);
var pipeName = Guid.NewGuid().ToString();
var procID = await _appLauncher.LaunchChatService(pipeName, senderConnectionID, senderName, orgName, orgId, hubConnection);
if (procID > 0)
{
@ -72,7 +77,7 @@ namespace Remotely.Agent.Services
return;
}
var clientPipe = new NamedPipeClientStream(".", "Remotely_Chat" + senderConnectionID, PipeDirection.InOut, PipeOptions.Asynchronous);
var clientPipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
clientPipe.Connect(15000);
if (!clientPipe.IsConnected)
{
@ -81,14 +86,14 @@ namespace Remotely.Agent.Services
}
chatSession = new ChatSession() { PipeStream = clientPipe, ProcessID = procID };
_ = Task.Run(async () => { await ReadFromStream(chatSession.PipeStream, senderConnectionID, hubConnection); });
ChatClients.Add(senderConnectionID, chatSession, CacheItemPolicy);
_chatClients.Add(senderConnectionID, chatSession, CacheItemPolicy);
}
chatSession = (ChatSession)ChatClients.Get(senderConnectionID);
chatSession = (ChatSession)_chatClients.Get(senderConnectionID);
if (!chatSession.PipeStream.IsConnected)
{
ChatClients.Remove(senderConnectionID);
_chatClients.Remove(senderConnectionID);
await hubConnection.SendAsync("DisplayMessage", "Chat disconnected. Please try again.", "Chat disconnected.", "bg-warning", senderConnectionID);
return;
}
@ -104,7 +109,7 @@ namespace Remotely.Agent.Services
}
finally
{
MessageLock.Release();
_messageLock.Release();
}
}
@ -121,7 +126,7 @@ namespace Remotely.Agent.Services
}
}
await hubConnection.SendAsync("Chat", string.Empty, true, senderConnectionID);
ChatClients.Remove(senderConnectionID);
_chatClients.Remove(senderConnectionID);
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR.Client;
using Remotely.Shared;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;

View File

@ -1,10 +1,12 @@
using Remotely.Shared.Utilities;
using System;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.ServiceProcess;
namespace Remotely.Agent.Services
{
[SupportedOSPlatform("windows")]
partial class WindowsService : ServiceBase
{
public WindowsService()

View File

@ -47,8 +47,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="6.0.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="2.0.2" />
<PackageReference Include="SkiaSharp" Version="2.88.0" />
<PackageReference Include="SkiaSharp" Version="2.88.1" />
<PackageReference Include="SkiaSharp.Views.Desktop.Common" Version="2.88.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
</ItemGroup>

View File

@ -19,7 +19,6 @@ namespace Remotely.Desktop.Core.Services
Task Disconnect();
Task DisconnectAllViewers();
Task DisconnectViewer(Viewer viewer, bool notifyViewer);
Task<IceServerModel[]> GetIceServers();
Task<string> GetSessionID();
Task NotifyRequesterUnattendedReady(string requesterID);
Task NotifyViewersRelaunchedScreenCasterReady(string[] viewerIDs);
@ -28,9 +27,7 @@ namespace Remotely.Desktop.Core.Services
Task SendCtrlAltDelToAgent();
Task SendDeviceInfo(string serviceID, string machineName, string deviceID);
Task SendDtoToViewer<T>(T dto, string viewerId);
Task SendIceCandidateToBrowser(string candidate, int sdpMlineIndex, string sdpMid, string viewerConnectionID);
Task SendMessageToViewer(string viewerID, string message);
Task SendRtcOfferToBrowser(string sdp, string viewerID, IceServerModel[] iceServers);
Task SendViewerConnected(string viewerConnectionId);
}
@ -69,7 +66,7 @@ namespace Remotely.Desktop.Core.Services
catch { }
}
Connection = new HubConnectionBuilder()
.WithUrl($"{host.Trim().TrimEnd('/')}/CasterHub")
.WithUrl($"{host.Trim().TrimEnd('/')}/hubs/desktop")
.AddMessagePackProtocol()
.WithAutomaticReconnect()
.Build();
@ -115,11 +112,6 @@ namespace Remotely.Desktop.Core.Services
return Connection.SendAsync("DisconnectViewer", viewer.ViewerConnectionID, notifyViewer);
}
public async Task<IceServerModel[]> GetIceServers()
{
return await Connection.InvokeAsync<IceServerModel[]>("GetIceServers");
}
public async Task<string> GetSessionID()
{
return await Connection.InvokeAsync<string>("GetSessionID");
@ -164,15 +156,7 @@ namespace Remotely.Desktop.Core.Services
var serializedDto = MessagePack.MessagePackSerializer.Serialize(dto);
return Connection.SendAsync("SendDtoToBrowser", serializedDto, viewerId);
}
public Task SendIceCandidateToBrowser(string candidate, int sdpMlineIndex, string sdpMid, string viewerConnectionID)
{
return Connection.SendAsync("SendIceCandidateToBrowser", candidate, sdpMlineIndex, sdpMid, viewerConnectionID);
}
public Task SendRtcOfferToBrowser(string sdp, string viewerID, IceServerModel[] iceServers)
{
return Connection.SendAsync("SendRtcOfferToBrowser", sdp, viewerID, iceServers);
}
public Task SendViewerConnected(string viewerConnectionId)
{
return Connection.SendAsync("ViewerConnected", viewerConnectionId);
@ -200,7 +184,6 @@ namespace Remotely.Desktop.Core.Services
string requesterName,
bool notifyUser,
bool enforceAttendedAccess,
bool useWebRtc,
string organizationName) =>
{
try
@ -224,8 +207,7 @@ namespace Remotely.Desktop.Core.Services
{
NotifyUser = notifyUser,
ViewerID = viewerID,
RequesterName = requesterName,
UseWebRtc = useWebRtc
RequesterName = requesterName
});
}
catch (Exception ex)
@ -235,45 +217,13 @@ namespace Remotely.Desktop.Core.Services
});
Connection.On("ReceiveIceCandidate", (string candidate, int sdpMlineIndex, string sdpMid, string viewerID) =>
{
try
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.RtcSession.AddIceCandidate(sdpMid, sdpMlineIndex, candidate);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
Connection.On("ReceiveRtcAnswer", async (string sdp, string viewerID) =>
{
try
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
await viewer.RtcSession.SetRemoteDescription("answer", sdp);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
Connection.On("RequestScreenCast", (string viewerID, string requesterName, bool notifyUser, bool useWebRtc) =>
Connection.On("RequestScreenCast", (string viewerID, string requesterName, bool notifyUser) =>
{
conductor.InvokeScreenCastRequested(new ScreenCastRequest()
{
NotifyUser = notifyUser,
ViewerID = viewerID,
RequesterName = requesterName,
UseWebRtc = useWebRtc
RequesterName = requesterName
});
});

View File

@ -18,15 +18,13 @@ namespace Remotely.Desktop.Core.Services
{
public interface IDeviceInitService
{
BrandingInfo BrandingInfo { get; }
Task GetInitParams();
Task<BrandingInfo> GetBrandingInfo();
void SetBrandingInfo(BrandingInfo branding);
}
public class DeviceInitService : IDeviceInitService
{
private static BrandingInfo _brandingInfo;
private static BrandingInfo _brandingInfo = new();
private readonly Conductor _conductor;
private readonly IConfigService _configService;
@ -36,14 +34,13 @@ namespace Remotely.Desktop.Core.Services
_configService = configService;
}
public BrandingInfo BrandingInfo => _brandingInfo;
public async Task GetInitParams()
public async Task<BrandingInfo> GetBrandingInfo()
{
try
{
if (_brandingInfo != null)
if (_brandingInfo is not null)
{
return;
return _brandingInfo;
}
using var httpClient = new HttpClient();
@ -81,7 +78,7 @@ namespace Remotely.Desktop.Core.Services
var brandingUrl = $"{config.Host.TrimEnd('/')}/api/branding/{config.OrganizationId}";
_brandingInfo = await httpClient.GetFromJsonAsync<BrandingInfo>(brandingUrl).ConfigureAwait(false);
return;
return _brandingInfo;
}
}
}
@ -100,6 +97,8 @@ namespace Remotely.Desktop.Core.Services
{
Logger.Write(ex, "Failed to resolve init params.", Shared.Enums.EventType.Warning);
}
return _brandingInfo;
}
public void SetBrandingInfo(BrandingInfo branding)

View File

@ -97,9 +97,6 @@ namespace Remotely.Desktop.Core.Services
case BaseDtoType.ToggleBlockInput:
ToggleBlockInput(message);
break;
case BaseDtoType.ToggleWebRtcVideo:
ToggleWebRtcVideo(message, viewer);
break;
case BaseDtoType.ClipboardTransfer:
await ClipboardTransfer(message);
break;
@ -259,11 +256,5 @@ namespace Remotely.Desktop.Core.Services
var dto = MessagePackSerializer.Deserialize<ToggleBlockInputDto>(message);
KeyboardMouseInput.ToggleBlockInput(dto.ToggleOn);
}
private void ToggleWebRtcVideo(byte[] message, Viewer viewer)
{
var dto = MessagePackSerializer.Deserialize<ToggleWebRtcVideoDto>(message);
viewer.ToggleWebRtcVideo(dto.ToggleOn);
}
}
}

View File

@ -104,12 +104,6 @@ namespace Remotely.Desktop.Core.Services
});
}
if (EnvironmentHelper.IsWindows && screenCastRequest.UseWebRtc)
{
await viewer.InitializeWebRtc();
}
// Wait until the first image is received.
if (!TaskHelper.DelayUntil(() => !viewer.PendingSentFrames.Any(), TimeSpan.FromSeconds(30)))
{
@ -136,12 +130,6 @@ namespace Remotely.Desktop.Core.Services
{
try
{
if (viewer.IsUsingWebRtcVideo)
{
Thread.Sleep(100);
continue;
}
if (viewer.IsStalled)
{
// Viewer isn't responding. Abort sending.

View File

@ -28,12 +28,10 @@ namespace Remotely.Desktop.Core.Services
public Viewer(ICasterSocket casterSocket,
IScreenCapturer screenCapturer,
IClipboardService clipboardService,
IWebRtcSessionFactory webRtcSessionFactory,
IAudioCapturer audioCapturer)
{
Capturer = screenCapturer;
CasterSocket = casterSocket;
WebRtcSessionFactory = webRtcSessionFactory;
ClipboardService = clipboardService;
ClipboardService.ClipboardTextChanged += ClipboardService_ClipboardTextChanged;
AudioCapturer = audioCapturer;
@ -55,26 +53,9 @@ namespace Remotely.Desktop.Core.Services
}
}
public bool IsUsingWebRtc
{
get
{
return RtcSession?.IsPeerConnected == true && RtcSession?.IsDataChannelOpen == true;
}
}
public bool IsUsingWebRtcVideo
{
get
{
return RtcSession?.IsPeerConnected == true && RtcSession?.IsVideoTrackConnected == true;
}
}
public string Name { get; set; }
public ConcurrentQueue<SentFrame> PendingSentFrames { get; } = new();
public TimeSpan RoundTripLatency { get; private set; }
public WebRtcSession RtcSession { get; set; }
public string ViewerConnectionID { get; set; }
private IAudioCapturer AudioCapturer { get; }
@ -84,8 +65,6 @@ namespace Remotely.Desktop.Core.Services
private IClipboardService ClipboardService { get; }
private IWebRtcSessionFactory WebRtcSessionFactory { get; }
public void ApplyAutoQuality()
{
if (ImageQuality < DefaultQuality)
@ -146,52 +125,20 @@ namespace Remotely.Desktop.Core.Services
public void Dispose()
{
DisconnectRequested = true;
Disposer.TryDisposeAll(RtcSession, Capturer);
Disposer.TryDisposeAll(Capturer);
GC.SuppressFinalize(this);
}
public async Task InitializeWebRtc()
{
try
{
var iceServers = await CasterSocket.GetIceServers();
RtcSession = WebRtcSessionFactory.GetNewSession(this);
if (RtcSession is null)
{
return;
}
RtcSession.LocalSdpReady += async (sender, sdp) =>
{
await CasterSocket.SendRtcOfferToBrowser(sdp.Content, ViewerConnectionID, iceServers);
};
RtcSession.IceCandidateReady += async (sender, candidate) =>
{
await CasterSocket.SendIceCandidateToBrowser(candidate.Content, candidate.SdpMlineIndex, candidate.SdpMid, ViewerConnectionID);
};
await RtcSession.Init(iceServers);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public async Task SendAudioSample(byte[] audioSample)
{
var dto = new AudioSampleDto(audioSample);
await SendToViewer(() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
public async Task SendClipboardText(string clipboardText)
{
var dto = new ClipboardTextDto(clipboardText);
await SendToViewer(() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
public async Task SendCtrlAltDel()
@ -207,8 +154,7 @@ namespace Remotely.Desktop.Core.Services
}
var dto = new CursorChangeDto(cursorInfo.ImageBytes, cursorInfo.HotSpot.X, cursorInfo.HotSpot.Y, cursorInfo.CssOverride);
await SendToViewer(() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
public async Task SendFile(FileUpload fileUpload, CancellationToken cancelToken, Action<double> progressUpdateCallback)
{
@ -223,8 +169,7 @@ namespace Remotely.Desktop.Core.Services
StartOfFile = true
};
await SendToViewer(async () => await RtcSession.SendDto(fileDto),
async () => await CasterSocket.SendDtoToViewer(fileDto, ViewerConnectionID));
await TrySendToViewer(async () => await CasterSocket.SendDtoToViewer(fileDto, ViewerConnectionID));
using var fs = File.OpenRead(fileUpload.FilePath);
using var br = new BinaryReader(fs);
@ -242,9 +187,7 @@ namespace Remotely.Desktop.Core.Services
MessageId = messageId
};
await SendToViewer(
async () => await RtcSession.SendDto(fileDto),
async () => await CasterSocket.SendDtoToViewer(fileDto, ViewerConnectionID));
await TrySendToViewer(async () => await CasterSocket.SendDtoToViewer(fileDto, ViewerConnectionID));
progressUpdateCallback((double)fs.Position / fs.Length);
}
@ -257,8 +200,7 @@ namespace Remotely.Desktop.Core.Services
StartOfFile = false
};
await SendToViewer(async () => await RtcSession.SendDto(fileDto),
async () => await CasterSocket.SendDtoToViewer(fileDto, ViewerConnectionID));
await TrySendToViewer(async () => await CasterSocket.SendDtoToViewer(fileDto, ViewerConnectionID));
progressUpdateCallback(1);
}
@ -296,9 +238,7 @@ namespace Remotely.Desktop.Core.Services
ImageBytes = chunk
};
await SendToViewer(
() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
}
@ -316,15 +256,13 @@ namespace Remotely.Desktop.Core.Services
ScreenWidth = screenWidth,
ScreenHeight = screenHeight
};
await SendToViewer(() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
public async Task SendScreenSize(int width, int height)
{
var dto = new ScreenSizeDto(width, height);
await SendToViewer(() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
public async Task SendViewerConnected()
@ -337,14 +275,9 @@ namespace Remotely.Desktop.Core.Services
if (EnvironmentHelper.IsWindows)
{
var dto = new WindowsSessionsDto(Win32Interop.GetActiveSessions());
await SendToViewer(() => RtcSession.SendDto(dto),
() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
await TrySendToViewer(() => CasterSocket.SendDtoToViewer(dto, ViewerConnectionID));
}
}
public void ToggleWebRtcVideo(bool toggleOn)
{
RtcSession.ToggleWebRtcVideo(toggleOn);
}
private async void AudioCapturer_AudioSampleReady(object sender, byte[] sample)
{
@ -356,18 +289,11 @@ namespace Remotely.Desktop.Core.Services
await SendClipboardText(clipboardText);
}
private Task SendToViewer(Func<Task> webRtcSend, Func<Task> websocketSend)
private Task TrySendToViewer(Func<Task> websocketSend)
{
try
{
if (IsUsingWebRtc)
{
return webRtcSend();
}
else
{
return websocketSend();
}
return websocketSend();
}
catch (Exception ex)
{

View File

@ -1,234 +0,0 @@
using MessagePack;
using Microsoft.MixedReality.WebRTC;
using Remotely.Shared.Utilities;
using Remotely.Shared.Models;
using Remotely.Shared.Models.RemoteControlDtos;
using System;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
namespace Remotely.Desktop.Core.Services
{
public class WebRtcSession : IDisposable
{
public WebRtcSession(Services.Viewer viewer, IDtoMessageHandler rtcMessageHandler)
{
Viewer = viewer;
RtcMessageHandler = rtcMessageHandler;
}
public event EventHandler<IceCandidate> IceCandidateReady;
public event EventHandler<SdpMessage> LocalSdpReady;
public ulong CurrentBuffer { get; private set; }
public bool IsDataChannelOpen => CaptureChannel?.State == DataChannel.ChannelState.Open;
public bool IsPeerConnected => PeerSession?.IsConnected == true;
public bool IsVideoTrackConnected
{
get
{
return Transceiver?.LocalVideoTrack?.Enabled == true;
}
}
private DataChannel CaptureChannel { get; set; }
private IceServerModel[] IceServers { get; set; }
private PeerConnection PeerSession { get; set; }
private IDtoMessageHandler RtcMessageHandler { get; }
private Transceiver Transceiver { get; set; }
private ExternalVideoTrackSource VideoSource { get; set; }
private Services.Viewer Viewer { get; }
public void AddIceCandidate(string sdpMid, int sdpMlineIndex, string candidate)
{
PeerSession.AddIceCandidate(new IceCandidate()
{
Content = candidate,
SdpMid = sdpMid,
SdpMlineIndex = sdpMlineIndex
});
}
public void Dispose()
{
try
{
// Unable to exit process until DataChannel is removed/disposed,
// and this throws internally (at least in 2.0 version).
PeerSession?.RemoveDataChannel(CaptureChannel);
}
catch { }
PeerSession.Transceivers.RemoveAll(x => true);
Disposer.TryDisposeAll(Transceiver?.LocalVideoTrack, VideoSource, PeerSession);
GC.SuppressFinalize(this);
}
public async Task Init(IceServerModel[] iceServers)
{
Logger.Write("Starting WebRTC connection.");
IceServers = iceServers;
PeerSession = new PeerConnection();
var iceList = IceServers.Select(x => new IceServer()
{
Urls = { x.Url },
TurnPassword = x.TurnPassword ?? string.Empty,
TurnUserName = x.TurnUsername ?? string.Empty
}).ToList();
var config = new PeerConnectionConfiguration()
{
IceServers = iceList
};
await PeerSession.InitializeAsync(config);
PeerSession.LocalSdpReadytoSend += PeerSession_LocalSdpReadytoSend;
PeerSession.Connected += PeerConnection_Connected;
PeerSession.IceStateChanged += PeerConnection_IceStateChanged;
PeerSession.IceCandidateReadytoSend += PeerSession_IceCandidateReadytoSend;
CaptureChannel = await PeerSession.AddDataChannelAsync("ScreenCapture", true, true);
CaptureChannel.BufferingChanged += DataChannel_BufferingChanged;
CaptureChannel.MessageReceived += CaptureChannel_MessageReceived;
CaptureChannel.StateChanged += CaptureChannel_StateChanged;
VideoSource = ExternalVideoTrackSource.CreateFromArgb32Callback(GetCaptureFrame);
Transceiver = PeerSession.AddTransceiver(MediaKind.Video);
PeerSession.CreateOffer();
}
public Task SendDto<T>(T dto) where T : BaseDto
{
CaptureChannel.SendMessage(MessagePackSerializer.Serialize(dto));
TaskHelper.DelayUntil(() => CurrentBuffer < 128_000, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public async Task SetRemoteDescription(string type, string sdp)
{
if (!Enum.TryParse<SdpMessageType>(type, true, out var sdpMessageType))
{
Logger.Write("Unable to parse remote WebRTC description type.");
return;
}
await PeerSession.SetRemoteDescriptionAsync(new SdpMessage()
{
Content = sdp,
Type = sdpMessageType
});
if (sdpMessageType == SdpMessageType.Offer)
{
PeerSession.CreateAnswer();
}
}
public void ToggleWebRtcVideo(bool toggleOn)
{
if (Transceiver?.LocalVideoTrack != null)
{
Transceiver.LocalVideoTrack.Dispose();
Transceiver.LocalVideoTrack = null;
}
if (toggleOn)
{
Transceiver.LocalVideoTrack = LocalVideoTrack.CreateFromSource(VideoSource, new LocalVideoTrackInitConfig()
{
trackName = "ScreenCapture"
});
}
}
private async void CaptureChannel_MessageReceived(byte[] obj)
{
await RtcMessageHandler.ParseMessage(Viewer, obj);
}
private async void CaptureChannel_StateChanged()
{
// Clear the queue when WebRTC state changes.
Viewer.PendingSentFrames.Clear();
Logger.Write($"DataChannel state changed. New State: {CaptureChannel.State}");
if (CaptureChannel.State == DataChannel.ChannelState.Closed)
{
await Init(IceServers);
}
}
private void DataChannel_BufferingChanged(ulong previous, ulong current, ulong limit)
{
CurrentBuffer = current;
}
private void GetCaptureFrame(in FrameRequest request)
{
try
{
if (!IsVideoTrackConnected)
{
return;
}
var result = Viewer.Capturer.GetNextFrame();
if (!result.IsSuccess || result.Value is null)
{
return;
}
using var currentFrame = result.Value;
if (currentFrame == null)
{
return;
}
var pixels = currentFrame.GetPixels();
var frame = new Argb32VideoFrame()
{
data = pixels,
height = (uint)currentFrame.Height,
width = (uint)currentFrame.Width,
stride = currentFrame.RowBytes
};
request.CompleteRequest(in frame);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private void PeerConnection_Connected()
{
Logger.Write("PeerConnection connected.");
}
private void PeerConnection_IceStateChanged(IceConnectionState newState)
{
// Clear the queue when WebRTC state changes.
Viewer.PendingSentFrames.Clear();
Logger.Write($"Ice state changed to {newState}.");
}
private void PeerSession_IceCandidateReadytoSend(IceCandidate candidate)
{
Logger.Write("Ice candidate ready to send.");
IceCandidateReady?.Invoke(this, candidate);
}
private void PeerSession_LocalSdpReadytoSend(SdpMessage message)
{
Logger.Write($"Local SDP ready.");
LocalSdpReady?.Invoke(this, message);
}
}
}

View File

@ -1,21 +0,0 @@
namespace Remotely.Desktop.Core.Services
{
public interface IWebRtcSessionFactory
{
WebRtcSession GetNewSession(Services.Viewer viewer);
}
public class WebRtcSessionFactory : IWebRtcSessionFactory
{
public WebRtcSessionFactory(IDtoMessageHandler messageHandler)
{
MessageHandler = messageHandler;
}
private IDtoMessageHandler MessageHandler { get; }
public WebRtcSession GetNewSession(Services.Viewer viewer)
{
return new WebRtcSession(viewer, MessageHandler);
}
}
}

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -7,6 +7,9 @@
<RootNamespace>Remotely.Desktop.XPlat</RootNamespace>
<Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>
<PropertyGroup>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@ -52,20 +55,14 @@
<EmbeddedResource Include="Assets\Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Desktop.Core\Desktop.Core.csproj" />
<ProjectReference Include="..\Desktop.Shared\Desktop.Shared.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\HostNamePrompt.axaml.cs">
<DependentUpon>HostNamePrompt.axaml</DependentUpon>
</Compile>
<Compile Update="Controls\MessageBox.axaml.cs">
<DependentUpon>MessageBox.axaml</DependentUpon>
</Compile>
<ProjectReference Include="..\submodules\Immense.RemoteControl\Desktop.Linux\Desktop.Linux.csproj" />
<ProjectReference Include="..\submodules\Immense.RemoteControl\Desktop.Shared\Desktop.Shared.csproj" />
</ItemGroup>
</Project>

66
Desktop.Linux/Program.cs Normal file
View File

@ -0,0 +1,66 @@
using Immense.RemoteControl.Desktop.Shared.Abstractions;
using System.Threading.Tasks;
using System.Threading;
using System;
using Immense.RemoteControl.Desktop.Windows;
using Remotely.Desktop.Shared.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Services;
using Immense.RemoteControl.Desktop.Shared.Services;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Immense.RemoteControl.Desktop.Shared.Enums;
using Immense.RemoteControl.Desktop.UI.Services;
using Remotely.Shared;
var provider = await Startup.UseRemoteControlClient(
args,
config =>
{
config.AddBrandingProvider<BrandingProvider>();
},
services =>
{
services.AddLogging(builder =>
{
#if DEBUG
builder.SetMinimumLevel(LogLevel.Debug);
#endif
builder.AddProvider(new FileLoggerProvider());
});
services.AddSingleton<IOrganizationIdProvider, OrganizationIdProvider>();
},
async services =>
{
var appState = services.GetRequiredService<IAppState>();
if (appState.ArgDict.TryGetValue("org-id", out var orgId))
{
var orgIdProvider = services.GetRequiredService<IOrganizationIdProvider>();
orgIdProvider.OrganizationId = orgId;
}
var brandingProvider = services.GetRequiredService<IBrandingProvider>();
if (brandingProvider is BrandingProvider branding)
{
await branding.TrySetFromApi();
}
},
AppConstants.ServerUrl);
Console.WriteLine("Press Ctrl + C to exit.");
var shutdownService = provider.GetRequiredService<IShutdownService>();
Console.CancelKeyPress += async (s, e) =>
{
await shutdownService.Shutdown();
};
var dispatcher = provider.GetRequiredService<IAvaloniaDispatcher>();
try
{
await Task.Delay(Timeout.InfiniteTimeSpan, dispatcher.AppCancellationToken);
}
catch (TaskCanceledException) { }

View File

@ -0,0 +1,8 @@
{
"profiles": {
"Desktop.Linux": {
"commandName": "Project",
"commandLineArgs": "-m Attended -s some-session-id -a vERyLonGAndCOMpleXKeY -o Immense -r Han"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\favicon.ico" />
<None Remove="Assets\Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\submodules\Immense.RemoteControl\Desktop.Shared\Desktop.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\favicon.ico" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,119 @@
using Immense.RemoteControl.Desktop.Shared.Abstractions;
using Immense.RemoteControl.Desktop.Shared.Services;
using Immense.RemoteControl.Shared.Models;
using Remotely.Shared;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.Shared.Services
{
public class BrandingProvider : IBrandingProvider
{
private readonly IAppState _appState;
private readonly IOrganizationIdProvider _orgIdProvider;
private BrandingInfoBase _brandingInfo = new()
{
Product = "Remote Control"
};
public BrandingProvider(IAppState appState, IOrganizationIdProvider orgIdProvider)
{
_appState = appState;
_orgIdProvider = orgIdProvider;
using var mrs = typeof(BrandingProvider).Assembly.GetManifestResourceStream("Desktop.Shared.Assets.Remotely_Icon.png");
using var ms = new MemoryStream();
mrs!.CopyTo(ms);
_brandingInfo.Icon = ms.ToArray();
}
public Task<BrandingInfoBase> GetBrandingInfo()
{
return Task.FromResult(_brandingInfo);
}
public void SetBrandingInfo(BrandingInfoBase brandingInfo)
{
_brandingInfo = brandingInfo;
}
public async Task TrySetFromApi()
{
try
{
if (string.IsNullOrWhiteSpace(_appState.Host))
{
return;
}
var host = _appState.Host;
using var httpClient = new HttpClient();
var fileName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess()?.MainModule?.FileName);
if (string.IsNullOrWhiteSpace(fileName))
{
return;
}
if (fileName.Contains('[') &&
fileName.Contains(']') &&
!string.IsNullOrWhiteSpace(host))
{
var codeLength = AppConstants.RelayCodeLength + 2;
for (var i = 0; i < fileName.Length; i++)
{
var codeSection = string.Join("", fileName.Skip(i).Take(codeLength));
if (codeSection.StartsWith("[") && codeSection.EndsWith("]"))
{
var relayCode = codeSection[1..5];
using var response = await httpClient.GetAsync($"{host.TrimEnd('/')}/api/Relay/{relayCode}").ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var organizationId = await response.Content.ReadAsStringAsync();
_orgIdProvider.OrganizationId = organizationId;
var brandingUrl = $"{host.TrimEnd('/')}/api/branding/{organizationId}";
var result = await httpClient.GetFromJsonAsync<BrandingInfo>(brandingUrl).ConfigureAwait(false);
if (result is not null)
{
_brandingInfo = result;
}
return;
}
}
}
}
if (!string.IsNullOrWhiteSpace(host) && !string.IsNullOrWhiteSpace(_orgIdProvider.OrganizationId))
{
var brandingUrl = $"{host.TrimEnd('/')}/api/branding/{_orgIdProvider.OrganizationId}";
var result = await httpClient.GetFromJsonAsync<BrandingInfo>(brandingUrl).ConfigureAwait(false);
if (result is not null)
{
_brandingInfo = result;
}
}
}
catch (Exception ex)
{
Logger.Write(ex, "Failed to resolve init params.", EventType.Warning);
}
}
}
}

View File

@ -0,0 +1,20 @@
using CommunityToolkit.Mvvm.Messaging;
using Immense.RemoteControl.Desktop.Shared.Services;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.Shared.Services
{
public interface IOrganizationIdProvider
{
string OrganizationId { get; set; }
}
public class OrganizationIdProvider : IOrganizationIdProvider
{
public string OrganizationId { get; set; } = string.Empty;
}
}

View File

@ -1,31 +0,0 @@
<Application x:Class="Remotely.Desktop.Win.App"
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"
DispatcherUnhandledException="Application_DispatcherUnhandledException"
Startup="Application_Startup">
<Application.Resources>
<Style x:Key="TitlebarButton" TargetType="Button">
<Setter Property="Background" Value="#FF464646"></Setter>
<Setter Property="DockPanel.Dock" Value="Right"></Setter>
<Setter Property="HorizontalAlignment" Value="Right"></Setter>
<Setter Property="BorderBrush" Value="{x:Null}"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
<Setter Property="Height" Value="30"></Setter>
<Setter Property="Width" Value="30"></Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
<Setter Property="Margin" Value="5,0,5,0"></Setter>
</Style>
<Style x:Key="SectionHeader" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"></Setter>
<Setter Property="FontSize" Value="18"></Setter>
</Style>
<Style x:Key="NormalButton" TargetType="Button">
<Setter Property="Background" Value="White"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="Padding" Value="6,4"></Setter>
</Style>
</Application.Resources>
</Application>

View File

@ -1,260 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Win.Services;
using Remotely.Desktop.Win.Views;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
using Form = System.Windows.Forms.Form;
namespace Remotely.Desktop.Win
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public Form BackgroundForm { get; private set; }
private ICasterSocket _casterSocket { get; set; }
private Conductor _conductor { get; set; }
private ICursorIconWatcher CursorIconWatcher { get; set; }
private IServiceProvider Services => ServiceContainer.Instance;
public async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
{
if (_conductor?.Viewers?.Count > 0)
{
foreach (var viewer in _conductor.Viewers.Values)
{
await viewer.SendCursorChange(cursor);
}
}
}
private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Logger.Write(e.Exception);
MessageBox.Show("There was an unhandled exception.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
e.Handled = true;
}
private void Application_Startup(object sender, StartupEventArgs e)
{
if (Environment.GetCommandLineArgs().Contains("-elevate"))
{
var commandLine = Win32Interop.GetCommandLine().Replace(" -elevate", "");
Logger.Write($"Elevating process {commandLine}.");
var result = Win32Interop.OpenInteractiveProcess(
commandLine,
-1,
false,
"default",
true,
out var procInfo);
Logger.Write($"Elevate result: {result}. Process ID: {procInfo.dwProcessId}.");
Environment.Exit(0);
}
_ = Task.Run(Initialize);
}
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<ICasterSocket, CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<IChatClientService, ChatHostService>();
serviceCollection.AddSingleton<IChatUiService, ChatUiServiceWin>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddScoped<IFileTransferService, FileTransferServiceWin>();
serviceCollection.AddSingleton<ISessionIndicator, SessionIndicatorWin>();
serviceCollection.AddSingleton<IShutdownService, ShutdownServiceWin>();
serviceCollection.AddScoped<IDtoMessageHandler, DtoMessageHandler>();
serviceCollection.AddScoped<IRemoteControlAccessService, RemoteControlAccessServiceWin>();
serviceCollection.AddScoped<IConfigService, ConfigServiceWin>();
serviceCollection.AddScoped<IDeviceInitService, DeviceInitService>();
serviceCollection.AddScoped<IClickOnceService, ClickOnceService>();
BackgroundForm = new Form()
{
Visible = false,
Opacity = 0,
ShowIcon = false,
ShowInTaskbar = false,
WindowState = System.Windows.Forms.FormWindowState.Minimized
};
serviceCollection.AddSingleton((serviceProvider) => BackgroundForm);
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
private async Task Initialize()
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
var args = Environment.GetCommandLineArgs()
.SkipWhile(x => !x.StartsWith("-"))
.ToArray();
BuildServices();
_conductor = Services.GetRequiredService<Conductor>();
_casterSocket = Services.GetRequiredService<ICasterSocket>();
_conductor.ProcessArgs(args);
SystemEvents.SessionEnding += async (s, e) =>
{
if (e.Reason == SessionEndReasons.SystemShutdown)
{
await _casterSocket.DisconnectAllViewers();
}
};
await Services.GetRequiredService<IClickOnceService>()
.TrySetBrandingFromActivationUri()
.ConfigureAwait(false);
var deviceInitService = Services.GetRequiredService<IDeviceInitService>();
await deviceInitService.GetInitParams().ConfigureAwait(false);
StartWinFormsThread();
if (_conductor.Mode == Core.Enums.AppMode.Chat)
{
var chatService = Services.GetRequiredService<IChatClientService>();
await chatService.StartChat(_conductor.RequesterID, _conductor.OrganizationName).ConfigureAwait(false);
}
else if (_conductor.Mode == Core.Enums.AppMode.Unattended)
{
Dispatcher.Invoke(() =>
{
ShutdownMode = ShutdownMode.OnExplicitShutdown;
});
await StartScreenCasting().ConfigureAwait(false);
}
else
{
Dispatcher.Invoke(() =>
{
MainWindow = new MainWindow();
MainWindow.Show();
});
}
WaitForAppExit();
}
catch (Exception ex)
{
Logger.Write(ex);
throw;
}
}
private async Task SendReadyNotificationToViewers()
{
if (_conductor.ArgDict.ContainsKey("relaunch"))
{
Logger.Write($"Resuming after relaunch.");
var viewersString = _conductor.ArgDict["viewers"];
var viewerIDs = viewersString.Split(",".ToCharArray());
await _casterSocket.NotifyViewersRelaunchedScreenCasterReady(viewerIDs);
}
else
{
await _casterSocket.NotifyRequesterUnattendedReady(_conductor.RequesterID);
}
}
private async Task StartScreenCasting()
{
CursorIconWatcher = Services.GetRequiredService<ICursorIconWatcher>();
await _casterSocket.Connect(_conductor.Host);
await _casterSocket.SendDeviceInfo(_conductor.ServiceID, Environment.MachineName, _conductor.DeviceID);
if (Win32Interop.GetCurrentDesktop(out var currentDesktopName))
{
Logger.Write($"Setting initial desktop to {currentDesktopName}.");
}
else
{
Logger.Write("Failed to get initial desktop name.");
}
if (!Win32Interop.SwitchToInputDesktop())
{
Logger.Write("Failed to set initial desktop.");
}
await SendReadyNotificationToViewers();
Services.GetRequiredService<IdleTimer>().Start();
CursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
Services.GetRequiredService<IClipboardService>().BeginWatching();
Services.GetRequiredService<IKeyboardMouseInput>().Init();
}
private void StartWinFormsThread()
{
var winformsThread = new Thread(() =>
{
System.Windows.Forms.Application.Run(BackgroundForm);
})
{
IsBackground = true
};
winformsThread.TrySetApartmentState(ApartmentState.STA);
winformsThread.Start();
Logger.Write("Background WinForms thread started.");
}
private void WaitForAppExit()
{
var appExitEvent = new ManualResetEventSlim();
Dispatcher.Invoke(() =>
{
Exit += (s, a) =>
{
appExitEvent.Set();
};
});
appExitEvent.Wait();
}
}
}

View File

@ -1,10 +0,0 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -16,7 +16,7 @@
<Product>Remotely Desktop</Product>
<PackageProjectUrl>https://remotely.one</PackageProjectUrl>
<Platforms>AnyCPU;x86;x64</Platforms>
<StartupObject>Remotely.Desktop.Win.App</StartupObject>
<StartupObject></StartupObject>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
@ -38,7 +38,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="2.0.2" />
<PackageReference Include="NAudio" Version="2.1.0" />
<PackageReference Include="SharpDX" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
@ -46,8 +45,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Desktop.Core\Desktop.Core.csproj" />
<ProjectReference Include="..\Desktop.Shared\Desktop.Shared.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\submodules\Immense.RemoteControl\Desktop.Windows\Desktop.Windows.csproj" />
</ItemGroup>
<ItemGroup>
@ -55,36 +55,6 @@
<EmbeddedResource Include="Assets\Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Update="App.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<Compile Update="App.xaml.cs">
<SubType>Code</SubType>
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Update="Views\HostNamePrompt.xaml.cs">
<DependentUpon>HostNamePrompt.xaml</DependentUpon>
</Compile>
<Compile Update="Views\MainWindow.xaml.cs">
<SubType>Code</SubType>
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Update="Views\HostNamePrompt.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Views\MainWindow.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="if $(SolutionDir) == *Undefined* (&#xD;&#xA; exit 0&#xD;&#xA;)&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA; if $(PlatformName) == AnyCPU (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\Debug\net6.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\Debug\net6.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x64 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x64\Debug\net6.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x64\Debug\net6.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x86 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x86\Debug\net6.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x86\Debug\net6.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA;)" />
</Target>

View File

@ -1,38 +0,0 @@
using Remotely.Shared.Utilities;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Drawing;
namespace Remotely.Desktop.Win.Models
{
public class DirectXOutput : IDisposable
{
public DirectXOutput(Adapter1 adapter,
SharpDX.Direct3D11.Device device,
OutputDuplication outputDuplication,
Texture2D texture2D,
DisplayModeRotation rotation)
{
Adapter = adapter;
Device = device;
OutputDuplication = outputDuplication;
Texture2D = texture2D;
Rotation = rotation;
Bounds = new Rectangle(0, 0, texture2D.Description.Width, texture2D.Description.Height);
}
public Adapter1 Adapter { get; }
public Rectangle Bounds { get; set; }
public SharpDX.Direct3D11.Device Device { get; }
public OutputDuplication OutputDuplication { get; }
public DisplayModeRotation Rotation { get; }
public Texture2D Texture2D { get; }
public void Dispose()
{
OutputDuplication?.ReleaseFrame();
Disposer.TryDisposeAll(OutputDuplication, Texture2D, Adapter, Device);
GC.SuppressFinalize(this);
}
}
}

57
Desktop.Win/Program.cs Normal file
View File

@ -0,0 +1,57 @@
using Immense.RemoteControl.Desktop.Shared.Abstractions;
using Immense.RemoteControl.Desktop.UI.WPF.Services;
using System.Threading.Tasks;
using System.Threading;
using System;
using Immense.RemoteControl.Desktop.Windows;
using Remotely.Desktop.Shared.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Services;
using Immense.RemoteControl.Desktop.Shared.Services;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Immense.RemoteControl.Desktop.Shared.Enums;
using Remotely.Shared;
var provider = await Startup.UseRemoteControlClient(
args,
config =>
{
config.AddBrandingProvider<BrandingProvider>();
},
services =>
{
services.AddLogging(builder =>
{
#if DEBUG
builder.SetMinimumLevel(LogLevel.Debug);
#endif
builder.AddProvider(new FileLoggerProvider());
});
services.AddSingleton<IOrganizationIdProvider, OrganizationIdProvider>();
},
async services =>
{
var appState = services.GetRequiredService<IAppState>();
if (appState.ArgDict.TryGetValue("org-id", out var orgId))
{
var orgIdProvider = services.GetRequiredService<IOrganizationIdProvider>();
orgIdProvider.OrganizationId = orgId;
}
var brandingProvider = services.GetRequiredService<IBrandingProvider>();
if (brandingProvider is BrandingProvider branding)
{
await branding.TrySetFromApi();
}
},
AppConstants.ServerUrl);
var dispatcher = provider.GetRequiredService<IWindowsUiDispatcher>();
try
{
await Task.Delay(Timeout.InfiniteTimeSpan, dispatcher.ApplicationExitingToken);
}
catch (TaskCanceledException) { }

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.0</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>
<ErrorReportUrl>https://remotely.one/Contact</ErrorReportUrl>
<ExcludeDeploymentUrl>False</ExcludeDeploymentUrl>
<GenerateManifests>True</GenerateManifests>
<Install>true</Install>
<InstallFrom>Web</InstallFrom>
<InstallUrl>http://127.0.0.1:5000/Content/Win-x64/ClickOnce/</InstallUrl>
<IsRevisionIncremented>False</IsRevisionIncremented>
<IsWebBootstrapper>True</IsWebBootstrapper>
<MapFileExtensions>True</MapFileExtensions>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<Platform>x64</Platform>
<ProductName>Remotely Desktop</ProductName>
<PublishDir>..\Server\wwwroot\Content\Win-x64\ClickOnce\</PublishDir>
<PublishUrl>..\Server\wwwroot\Content\Win-x64\ClickOnce\</PublishUrl>
<PublisherName>Translucency Software</PublisherName>
<PublishProtocol>ClickOnce</PublishProtocol>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishSingleFile>False</PublishSingleFile>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<SelfContained>False</SelfContained>
<SignatureAlgorithm>(none)</SignatureAlgorithm>
<SignManifests>False</SignManifests>
<SuiteName>Remotely</SuiteName>
<SupportUrl>https://remotely.one/Contact</SupportUrl>
<TargetFramework>net6.0-windows</TargetFramework>
<TrustUrlParameters>True</TrustUrlParameters>
<UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateRequired>False</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.DesktopRuntime.6.0.x64">
<Install>true</Install>
<ProductName>.NET Desktop Runtime 6.0.0 (x64)</ProductName>
</BootstrapperPackage>
</ItemGroup>
</Project>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.0</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>
<ErrorReportUrl>https://remotely.one/Contact</ErrorReportUrl>
<ExcludeDeploymentUrl>False</ExcludeDeploymentUrl>
<GenerateManifests>True</GenerateManifests>
<Install>true</Install>
<InstallFrom>Web</InstallFrom>
<InstallUrl>http://127.0.0.1:5000/Content/Win-x86/ClickOnce/</InstallUrl>
<IsRevisionIncremented>False</IsRevisionIncremented>
<IsWebBootstrapper>True</IsWebBootstrapper>
<MapFileExtensions>True</MapFileExtensions>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<Platform>x86</Platform>
<ProductName>Remotely Desktop</ProductName>
<PublishDir>..\Server\wwwroot\Content\Win-x86\ClickOnce\</PublishDir>
<PublishUrl>..\Server\wwwroot\Content\Win-x86\ClickOnce\</PublishUrl>
<PublisherName>Translucency Software</PublisherName>
<PublishProtocol>ClickOnce</PublishProtocol>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishSingleFile>False</PublishSingleFile>
<RuntimeIdentifier>win10-x86</RuntimeIdentifier>
<SelfContained>False</SelfContained>
<SignatureAlgorithm>(none)</SignatureAlgorithm>
<SignManifests>False</SignManifests>
<SuiteName>Remotely</SuiteName>
<SupportUrl>https://remotely.one/Contact</SupportUrl>
<TargetFramework>net6.0-windows</TargetFramework>
<TrustUrlParameters>True</TrustUrlParameters>
<UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateRequired>False</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.DesktopRuntime.6.0.x86">
<Install>true</Install>
<ProductName>.NET Desktop Runtime 6.0.0 (x86)</ProductName>
</BootstrapperPackage>
</ItemGroup>
</Project>

View File

@ -1,7 +1,8 @@
{
"profiles": {
"Desktop.Win": {
"commandName": "Project"
"commandName": "Project",
"commandLineArgs": "-m Attended -s some-session-id -a vERyLonGAndCOMpleXKeY -o Immense -r Han"
}
}
}

View File

@ -1,119 +0,0 @@
using NAudio.Wave;
using Remotely.Desktop.Core.Interfaces;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace Remotely.Desktop.Win.Services
{
public class AudioCapturerWin : IAudioCapturer
{
private readonly List<byte> _tempBuffer = new();
private WasapiLoopbackCapture _capturer;
private Stopwatch _sendTimer;
private WaveFormat _targetFormat;
public AudioCapturerWin()
{
_targetFormat = new WaveFormat(16000, 8, 1);
}
public event EventHandler<byte[]> AudioSampleReady;
public void ToggleAudio(bool toggleOn)
{
if (toggleOn)
{
Start();
}
else
{
Stop();
}
}
private void Capturer_DataAvailable(object sender, WaveInEventArgs args)
{
try
{
if (args.Buffer.All(x => x == 0))
{
return;
}
if (args.BytesRecorded > 0)
{
lock (_tempBuffer)
{
if (!_sendTimer.IsRunning)
{
_sendTimer.Restart();
}
_tempBuffer.AddRange(args.Buffer.Take(args.BytesRecorded));
if (_tempBuffer.Count > 50_000 ||
_sendTimer.Elapsed.TotalMilliseconds > 1000)
{
_sendTimer.Reset();
SendTempBuffer();
}
}
}
}
catch { }
}
private void SendTempBuffer()
{
if (_tempBuffer.Count == 0)
{
return;
}
using var ms1 = new MemoryStream();
using (var wfw = new WaveFileWriter(ms1, _capturer.WaveFormat))
{
wfw.Write(_tempBuffer.ToArray(), 0, _tempBuffer.Count);
}
_tempBuffer.Clear();
// Resample to 16-bit so Firefox will play it.
using var ms2 = new MemoryStream(ms1.ToArray());
using var wfr = new WaveFileReader(ms2);
using var ms3 = new MemoryStream();
using (var resampler = new MediaFoundationResampler(wfr, _targetFormat))
{
WaveFileWriter.WriteWavFileToStream(ms3, resampler);
}
AudioSampleReady?.Invoke(this, ms3.ToArray());
}
private void Start()
{
try
{
_capturer = new WasapiLoopbackCapture();
_sendTimer = Stopwatch.StartNew();
_capturer.DataAvailable += Capturer_DataAvailable;
_capturer.StartRecording();
}
catch { }
}
private void Stop()
{
if (_capturer is not null)
{
_capturer.DataAvailable -= Capturer_DataAvailable;
}
_capturer?.StopRecording();
_capturer.Dispose();
_sendTimer?.Stop();
}
}
}

View File

@ -1,59 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Win.ViewModels;
using Remotely.Desktop.Win.Views;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Remotely.Desktop.Win.Services
{
public class ChatUiServiceWin : IChatUiService
{
private ChatWindowViewModel ChatViewModel { get; set; }
public event EventHandler ChatWindowClosed;
public void ReceiveChat(ChatMessage chatMessage)
{
App.Current.Dispatcher.Invoke(() =>
{
if (chatMessage.Disconnected)
{
MessageBox.Show("Your partner has disconnected.", "Partner Disconnected", MessageBoxButton.OK, MessageBoxImage.Information);
App.Current.Shutdown();
return;
}
if (ChatViewModel != null)
{
ChatViewModel.SenderName = chatMessage.SenderName;
ChatViewModel.ChatMessages.Add(chatMessage);
}
});
}
public void ShowChatWindow(string organizationName, StreamWriter writer)
{
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();
});
}
private void ChatWindow_Closing(object sender, CancelEventArgs e)
{
ChatWindowClosed?.Invoke(this, null);
}
}
}

View File

@ -1,106 +0,0 @@
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
namespace Remotely.Desktop.Win.Services
{
public interface IClickOnceService
{
string GetActivationUri();
Task TrySetBrandingFromActivationUri();
}
public class ClickOnceService : IClickOnceService
{
private static string _activationUri;
private readonly IDeviceInitService _deviceInitService;
private readonly Conductor _conductor;
public ClickOnceService(Conductor conductor, IDeviceInitService deviceInitService)
{
_conductor = conductor;
_deviceInitService = deviceInitService;
}
public string GetActivationUri()
{
try
{
if (!string.IsNullOrWhiteSpace(_activationUri))
{
return _activationUri;
}
var appRoot = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent;
if (!Directory.Exists(Path.Combine(appRoot.FullName, "manifests")))
{
Logger.Write($"Manifests folder not found in root folder: {appRoot}", EventType.Debug);
return _activationUri;
}
var manifestFiles = appRoot.GetFiles("manifests\\*tion_*.manifest", SearchOption.AllDirectories);
var manifestFile = manifestFiles.FirstOrDefault();
if (manifestFile is null)
{
Logger.Write($"Manifest file not found.", EventType.Warning);
return _activationUri;
}
var manifest = new XmlDocument();
manifest.Load(manifestFile.FullName);
var node = manifest.GetElementsByTagName("deploymentProvider")[0];
_activationUri = node.Attributes["codebase"].Value;
Logger.Write($"Found ActivationUri: {_activationUri}");
}
catch (Exception ex)
{
Logger.Write(ex);
}
return _activationUri;
}
public async Task TrySetBrandingFromActivationUri()
{
var activationUri = GetActivationUri();
if (Uri.TryCreate(activationUri, UriKind.Absolute, out var result))
{
var host = $"{result.Scheme}://{result.Authority}";
if (!string.IsNullOrWhiteSpace(host))
{
_conductor.UpdateHost(host);
using var httpClient = new HttpClient();
try
{
var url = $"{host.TrimEnd('/')}/api/branding";
var query = HttpUtility.ParseQueryString(result.Query);
if (query?.AllKeys?.Contains("organizationid") == true)
{
url += $"?organizationId={query["organizationid"]}";
_conductor.UpdateOrganizationId(query["organizationid"]);
}
var branding = await httpClient.GetFromJsonAsync<BrandingInfo>(url).ConfigureAwait(false);
if (branding != null)
{
_deviceInitService.SetBrandingInfo(branding);
}
}
catch { }
}
}
}
}
}

View File

@ -1,103 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
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()
{
App.Current.Dispatcher.Invoke(() =>
{
App.Current.Exit -= App_Exit;
App.Current.Exit += App_Exit;
});
StopWatching();
_cancelTokenSource = new CancellationTokenSource();
WatchClipboard(_cancelTokenSource.Token);
}
public Task 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.IsBackground = true;
thread.Start();
return Task.CompletedTask;
}
public void StopWatching()
{
try
{
_cancelTokenSource?.Cancel();
_cancelTokenSource?.Dispose();
}
catch { }
}
private void App_Exit(object sender, System.Windows.ExitEventArgs e)
{
_cancelTokenSource?.Cancel();
}
private void WatchClipboard(CancellationToken cancelToken)
{
var thread = new Thread(() =>
{
while (!cancelToken.IsCancellationRequested)
{
try
{
Win32Interop.SwitchToInputDesktop();
if (Clipboard.ContainsText() && Clipboard.GetText() != ClipboardText)
{
ClipboardText = Clipboard.GetText();
ClipboardTextChanged?.Invoke(this, ClipboardText);
}
}
catch { }
Thread.Sleep(500);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
}
}

View File

@ -1,49 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.IO;
using System.Text.Json;
namespace Remotely.Desktop.Win.Services
{
public class ConfigServiceWin : IConfigService
{
private static readonly string _configFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Remotely");
private static readonly string _configFile = Path.Combine(_configFolder, "Config.json");
public DesktopAppConfig GetConfig()
{
var config = new DesktopAppConfig();
if (string.IsNullOrWhiteSpace(config.Host) &&
File.Exists(_configFile))
{
try
{
config = JsonSerializer.Deserialize<DesktopAppConfig>(File.ReadAllText(_configFile));
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
return config;
}
public void Save(DesktopAppConfig config)
{
try
{
Directory.CreateDirectory(_configFolder);
File.WriteAllText(_configFile, JsonSerializer.Serialize(config));
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}

View File

@ -1,109 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Shared.Models;
using Remotely.Shared.Win32;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows.Forms;
namespace Remotely.Desktop.Win.Services
{
/// <summary>
/// A class that can be used to watch for cursor icon changes.
/// </summary>
public class CursorIconWatcherWin : ICursorIconWatcher
{
public CursorIconWatcherWin()
{
ChangeTimer = new System.Timers.Timer(25);
ChangeTimer.Elapsed += ChangeTimer_Elapsed;
ChangeTimer.Start();
}
public event EventHandler<CursorInfo> OnChange;
private System.Timers.Timer ChangeTimer { get; set; }
private string PreviousCursorHandle { get; set; }
private User32.CursorInfo cursorInfo;
public CursorInfo GetCurrentCursor()
{
try
{
var ci = new User32.CursorInfo();
ci.cbSize = Marshal.SizeOf(ci);
User32.GetCursorInfo(out ci);
if (ci.flags == User32.CURSOR_SHOWING)
{
if (ci.hCursor.ToString() == Cursors.IBeam.Handle.ToString())
{
return new CursorInfo(Array.Empty<byte>(), Point.Empty, "text");
}
using var icon = Icon.FromHandle(ci.hCursor);
using var ms = new MemoryStream();
using var cursor = new Cursor(ci.hCursor);
var hotspot = cursor.HotSpot;
icon.ToBitmap().Save(ms, ImageFormat.Png);
return new CursorInfo(ms.ToArray(), hotspot);
}
else
{
return new CursorInfo(Array.Empty<byte>(), Point.Empty, "default");
}
}
catch
{
return new CursorInfo(Array.Empty<byte>(), Point.Empty, "default");
}
}
private void ChangeTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (OnChange == null)
{
return;
}
try
{
cursorInfo = new User32.CursorInfo();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
User32.GetCursorInfo(out cursorInfo);
if (cursorInfo.flags == User32.CURSOR_SHOWING)
{
var currentCursor = cursorInfo.hCursor.ToString();
if (currentCursor != PreviousCursorHandle)
{
if (currentCursor == Cursors.IBeam.Handle.ToString())
{
OnChange?.Invoke(this, new CursorInfo(Array.Empty<byte>(), Point.Empty, "text"));
}
else
{
using var icon = Icon.FromHandle(cursorInfo.hCursor);
using var ms = new MemoryStream();
using var cursor = new Cursor(cursorInfo.hCursor);
var hotspot = cursor.HotSpot;
icon.ToBitmap().Save(ms, ImageFormat.Png);
OnChange?.Invoke(this, new CursorInfo(ms.ToArray(), hotspot));
}
PreviousCursorHandle = currentCursor;
}
}
else if (PreviousCursorHandle != "0")
{
PreviousCursorHandle = "0";
OnChange?.Invoke(this, new CursorInfo(Array.Empty<byte>(), Point.Empty, "default"));
}
}
catch
{
OnChange?.Invoke(this, new CursorInfo(Array.Empty<byte>(), Point.Empty, "default"));
}
}
}
}

View File

@ -1,37 +0,0 @@
using System;
using System.Windows.Input;
namespace Remotely.Desktop.Win.Services
{
public class Executor : ICommand
{
public Executor(Action<object> executeAction, Predicate<object> isExecutable = null)
{
ExecuteAction = executeAction;
IsExecutable = isExecutable;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action<object> ExecuteAction { get; set; }
private Predicate<object> IsExecutable { get; set; }
public bool CanExecute(object parameter)
{
if (IsExecutable == null)
{
return true;
}
return IsExecutable.Invoke(parameter);
}
public void Execute(object parameter)
{
ExecuteAction.Invoke(parameter);
}
}
}

View File

@ -1,202 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Core.ViewModels;
using Remotely.Desktop.Win.ViewModels;
using Remotely.Desktop.Win.Views;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Remotely.Desktop.Win.Services
{
public class FileTransferServiceWin : IFileTransferService
{
private static readonly ConcurrentDictionary<string, FileStream> _partialTransfers =
new();
private static readonly ConcurrentDictionary<string, FileTransferWindow> _fileTransferWindows =
new();
private static readonly SemaphoreSlim _writeLock = new(1,1);
private static MessageBoxResult? _result;
public string GetBaseDirectory()
{
var programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
return Directory.CreateDirectory(Path.Combine(programDataPath, "Remotely", "Shared")).FullName;
}
public void OpenFileTransferWindow(Viewer viewer)
{
App.Current.Dispatcher.Invoke(() =>
{
if (_fileTransferWindows.TryGetValue(viewer.ViewerConnectionID, out var window))
{
window.Activate();
}
else
{
window = new FileTransferWindow
{
DataContext = new FileTransferWindowViewModel(viewer, this)
};
window.Closed += (sender, arg) =>
{
_fileTransferWindows.Remove(viewer.ViewerConnectionID, out _);
};
_fileTransferWindows.AddOrUpdate(viewer.ViewerConnectionID, window, (k, v) => window);
window.Show();
}
});
}
public async Task ReceiveFile(byte[] buffer, string fileName, string messageId, bool endOfFile, bool startOfFile)
{
try
{
await _writeLock.WaitAsync();
var baseDir = GetBaseDirectory();
SetFileOrFolderPermissions(baseDir);
if (startOfFile)
{
var filePath = Path.Combine(baseDir, fileName);
if (File.Exists(filePath))
{
var count = 0;
var ext = Path.GetExtension(fileName);
var fileWithoutExt = Path.GetFileNameWithoutExtension(fileName);
while (File.Exists(filePath))
{
filePath = Path.Combine(baseDir, $"{fileWithoutExt}-{count}{ext}");
count++;
}
}
File.Create(filePath).Close();
SetFileOrFolderPermissions(filePath);
var fs = new FileStream(filePath, FileMode.OpenOrCreate);
_partialTransfers.AddOrUpdate(messageId, fs, (k, v) => fs);
}
var fileStream = _partialTransfers[messageId];
if (buffer?.Length > 0)
{
await fileStream.WriteAsync(buffer, 0, buffer.Length);
}
if (endOfFile)
{
fileStream.Close();
_partialTransfers.Remove(messageId, out _);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
finally
{
_writeLock.Release();
if (endOfFile)
{
await Task.Run(ShowTransferComplete);
}
}
}
public async Task UploadFile(FileUpload fileUpload, Viewer viewer, CancellationToken cancelToken, Action<double> progressUpdateCallback)
{
try
{
await viewer.SendFile(fileUpload, cancelToken, progressUpdateCallback);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private void SetFileOrFolderPermissions(string path)
{
FileSystemSecurity ds;
var aclSections = AccessControlSections.Access | AccessControlSections.Group | AccessControlSections.Owner;
if (File.Exists(path))
{
ds = new FileSecurity(path, aclSections);
}
else if (Directory.Exists(path))
{
ds = new DirectorySecurity(path, aclSections);
}
else
{
return;
}
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
var account = (NTAccount)sid.Translate(typeof(NTAccount));
var accessAlreadySet = false;
foreach (FileSystemAccessRule rule in ds.GetAccessRules(true, true, typeof(NTAccount)))
{
if (rule.IdentityReference == account &&
rule.FileSystemRights.HasFlag(FileSystemRights.Modify) &&
rule.AccessControlType == AccessControlType.Allow)
{
accessAlreadySet = true;
break;
}
}
if (!accessAlreadySet)
{
ds.AddAccessRule(new FileSystemAccessRule(account, FileSystemRights.Modify, AccessControlType.Allow));
if (File.Exists(path))
{
new FileInfo(path).SetAccessControl((FileSecurity)ds);
}
else if (Directory.Exists(path))
{
new DirectoryInfo(path).SetAccessControl((DirectorySecurity)ds);
}
}
}
private void ShowTransferComplete()
{
// Prevent multiple dialogs from popping up.
if (_result is null)
{
_result = MessageBox.Show("File transfer complete. Show folder?",
"Transfer Complete",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.Yes,
MessageBoxOptions.ServiceNotification);
if (_result == MessageBoxResult.Yes)
{
Process.Start("explorer.exe", GetBaseDirectory());
}
_result = null;
}
}
}
}

View File

@ -1,410 +0,0 @@
using Remotely.Desktop.Core.Enums;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static Remotely.Shared.Win32.User32;
namespace Remotely.Desktop.Win.Services
{
public class KeyboardMouseInputWin : IKeyboardMouseInput
{
private readonly ConcurrentQueue<Action> _inputActions = new();
private CancellationTokenSource _cancelTokenSource;
private volatile bool _inputBlocked;
private Thread _inputProcessingThread;
public Tuple<double, double> GetAbsolutePercentFromRelativePercent(double percentX, double percentY, IScreenCapturer capturer)
{
var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left - capturer.GetVirtualScreenBounds().Left;
var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top - capturer.GetVirtualScreenBounds().Top;
return new Tuple<double, double>(absoluteX / capturer.GetVirtualScreenBounds().Width, absoluteY / capturer.GetVirtualScreenBounds().Height);
}
public Tuple<double, double> GetAbsolutePointFromRelativePercent(double percentX, double percentY, IScreenCapturer capturer)
{
var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left;
var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top;
return new Tuple<double, double>(absoluteX, absoluteY);
}
public void Init()
{
App.Current.Dispatcher.Invoke(() =>
{
App.Current.Exit -= App_Exit;
App.Current.Exit += App_Exit;
});
StartInputProcessingThread();
}
public void SendKeyDown(string key)
{
TryOnInputDesktop(() =>
{
try
{
if (!ConvertJavaScriptKeyToVirtualKey(key, out var keyCode) || keyCode is null)
{
return;
}
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = keyCode.Value,
wScan = (ScanCodeShort)MapVirtualKeyEx((uint)keyCode.Value, VkMapType.MAPVK_VK_TO_VSC, GetKeyboardLayout()),
time = 0,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
}
public void SendKeyUp(string key)
{
TryOnInputDesktop(() =>
{
try
{
if (!ConvertJavaScriptKeyToVirtualKey(key, out var keyCode) || keyCode is null)
{
return;
}
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = keyCode.Value,
wScan = (ScanCodeShort)MapVirtualKeyEx((uint)keyCode.Value, VkMapType.MAPVK_VK_TO_VSC, GetKeyboardLayout()),
time = 0,
dwFlags = KEYEVENTF.KEYUP,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
}
public void SendMouseButtonAction(int button, ButtonAction buttonAction, double percentX, double percentY, Viewer viewer)
{
TryOnInputDesktop(() =>
{
try
{
MOUSEEVENTF mouseEvent;
switch (button)
{
case 0:
switch (buttonAction)
{
case ButtonAction.Down:
mouseEvent = MOUSEEVENTF.LEFTDOWN;
break;
case ButtonAction.Up:
mouseEvent = MOUSEEVENTF.LEFTUP;
break;
default:
return;
}
break;
case 1:
switch (buttonAction)
{
case ButtonAction.Down:
mouseEvent = MOUSEEVENTF.MIDDLEDOWN;
break;
case ButtonAction.Up:
mouseEvent = MOUSEEVENTF.MIDDLEUP;
break;
default:
return;
}
break;
case 2:
switch (buttonAction)
{
case ButtonAction.Down:
mouseEvent = MOUSEEVENTF.RIGHTDOWN;
break;
case ButtonAction.Up:
mouseEvent = MOUSEEVENTF.RIGHTUP;
break;
default:
return;
}
break;
default:
return;
}
var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = xyPercent.Item1 * 65535D;
var normalizedY = xyPercent.Item2 * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | mouseEvent | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
}
public void SendMouseMove(double percentX, double percentY, Viewer viewer)
{
TryOnInputDesktop(() =>
{
try
{
var xyPercent = GetAbsolutePercentFromRelativePercent(percentX, percentY, viewer.Capturer);
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
var normalizedX = xyPercent.Item1 * 65535D;
var normalizedY = xyPercent.Item2 * 65535D;
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.MOVE | MOUSEEVENTF.VIRTUALDESK, dx = (int)normalizedX, dy = (int)normalizedY, time = 0, mouseData = 0, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
}
public void SendMouseWheel(int deltaY)
{
TryOnInputDesktop(() =>
{
try
{
if (deltaY < 0)
{
deltaY = -120;
}
else if (deltaY > 0)
{
deltaY = 120;
}
var union = new InputUnion() { mi = new MOUSEINPUT() { dwFlags = MOUSEEVENTF.WHEEL, dx = 0, dy = 0, time = 0, mouseData = deltaY, dwExtraInfo = GetMessageExtraInfo() } };
var input = new INPUT() { type = InputType.MOUSE, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
}
public void SendText(string transferText)
{
TryOnInputDesktop(() =>
{
try
{
SendKeys.SendWait(transferText);
}
catch (Exception ex)
{
Logger.Write(ex);
}
});
}
public void SetKeyStatesUp()
{
TryOnInputDesktop(() =>
{
var thread = new Thread(() =>
{
foreach (VirtualKey key in Enum.GetValues(typeof(VirtualKey)))
{
try
{
var state = GetKeyState(key);
if (state == -127)
{
var union = new InputUnion()
{
ki = new KEYBDINPUT()
{
wVk = key,
wScan = 0,
time = 0,
dwFlags = KEYEVENTF.KEYUP,
dwExtraInfo = GetMessageExtraInfo()
}
};
var input = new INPUT() { type = InputType.KEYBOARD, U = union };
SendInput(1, new INPUT[] { input }, INPUT.Size);
}
}
catch { }
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
});
}
public void ToggleBlockInput(bool toggleOn)
{
_inputActions.Enqueue(() =>
{
_inputBlocked = toggleOn;
var result = BlockInput(toggleOn);
Logger.Write($"Result of ToggleBlockInput set to {toggleOn}: {result}");
});
}
private void App_Exit(object sender, System.Windows.ExitEventArgs e)
{
_cancelTokenSource?.Cancel();
}
private void CheckQueue(CancellationToken cancelToken)
{
while (!cancelToken.IsCancellationRequested)
{
try
{
if (_inputActions.TryDequeue(out var action))
{
action();
}
}
finally
{
Thread.Sleep(1);
}
}
Logger.Write($"Stopping input processing on thread {Thread.CurrentThread.ManagedThreadId}.");
}
private bool ConvertJavaScriptKeyToVirtualKey(string key, out VirtualKey? result)
{
result = key switch
{
"Down" or "ArrowDown" => VirtualKey.DOWN,
"Up" or "ArrowUp" => VirtualKey.UP,
"Left" or "ArrowLeft" => VirtualKey.LEFT,
"Right" or "ArrowRight" => VirtualKey.RIGHT,
"Enter" => VirtualKey.RETURN,
"Esc" or "Escape" => VirtualKey.ESCAPE,
"Alt" => VirtualKey.MENU,
"Control" => VirtualKey.CONTROL,
"Shift" => VirtualKey.SHIFT,
"PAUSE" => VirtualKey.PAUSE,
"BREAK" => VirtualKey.PAUSE,
"Backspace" => VirtualKey.BACK,
"Tab" => VirtualKey.TAB,
"CapsLock" => VirtualKey.CAPITAL,
"Delete" => VirtualKey.DELETE,
"Home" => VirtualKey.HOME,
"End" => VirtualKey.END,
"PageUp" => VirtualKey.PRIOR,
"PageDown" => VirtualKey.NEXT,
"NumLock" => VirtualKey.NUMLOCK,
"Insert" => VirtualKey.INSERT,
"ScrollLock" => VirtualKey.SCROLL,
"F1" => VirtualKey.F1,
"F2" => VirtualKey.F2,
"F3" => VirtualKey.F3,
"F4" => VirtualKey.F4,
"F5" => VirtualKey.F5,
"F6" => VirtualKey.F6,
"F7" => VirtualKey.F7,
"F8" => VirtualKey.F8,
"F9" => VirtualKey.F9,
"F10" => VirtualKey.F10,
"F11" => VirtualKey.F11,
"F12" => VirtualKey.F12,
"Meta" => VirtualKey.LWIN,
"ContextMenu" => VirtualKey.MENU,
_ => key.Length == 1 ?
(VirtualKey)VkKeyScan(Convert.ToChar(key)) :
null
};
if (result is null)
{
Logger.Write($"Unable to parse key input: {key}.");
return false;
}
return true;
}
private void StartInputProcessingThread()
{
_cancelTokenSource?.Cancel();
_cancelTokenSource?.Dispose();
// After BlockInput is enabled, only simulated input coming from the same thread
// will work. So we have to start a new thread that runs continuously and
// processes a queue of input events.
_inputProcessingThread = new Thread(() =>
{
Logger.Write($"New input processing thread started on thread {Thread.CurrentThread.ManagedThreadId}.");
_cancelTokenSource = new CancellationTokenSource();
if (_inputBlocked)
{
ToggleBlockInput(true);
}
CheckQueue(_cancelTokenSource.Token);
});
_inputProcessingThread.SetApartmentState(ApartmentState.STA);
_inputProcessingThread.Start();
}
private void TryOnInputDesktop(Action inputAction)
{
_inputActions.Enqueue(() =>
{
try
{
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);
}
});
}
}
}

View File

@ -1,35 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Win.ViewModels;
using Remotely.Desktop.Win.Views;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.Win.Services
{
public class RemoteControlAccessServiceWin : IRemoteControlAccessService
{
public Task<bool> PromptForAccess(string requesterName, string organizationName)
{
var result = App.Current.Dispatcher.Invoke(() =>
{
var promptWindow = new PromptForAccessWindow();
var viewModel = promptWindow.DataContext as PromptForAccessWindowViewModel;
if (!string.IsNullOrWhiteSpace(requesterName))
{
viewModel.RequesterName = requesterName;
}
if (!string.IsNullOrWhiteSpace(organizationName))
{
viewModel.OrganizationName = organizationName;
}
promptWindow.ShowDialog();
return viewModel.PromptResult;
});
return Task.FromResult(result);
}
}
}

View File

@ -1,446 +0,0 @@
// The DirectX capture code is based off examples from the
// SharpDX Samples at https://github.com/sharpdx/SharpDX.
// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using Microsoft.Win32;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Utilities;
using Remotely.Desktop.Win.Models;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Remotely.Shared;
using Result = Remotely.Shared.Result;
using SkiaSharp;
using SkiaSharp.Views.Desktop;
using Remotely.Desktop.Core.Extensions;
using System.Runtime.InteropServices;
namespace Remotely.Desktop.Win.Services
{
public class ScreenCapturerWin : IScreenCapturer
{
private readonly Dictionary<string, int> _bitBltScreens = new();
private readonly Dictionary<string, DirectXOutput> _directxScreens = new();
private readonly object _screenBoundsLock = new();
private SKBitmap _currentFrame;
private SKBitmap _previousFrame;
public ScreenCapturerWin()
{
Init();
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
}
public event EventHandler<Rectangle> ScreenChanged;
public bool CaptureFullscreen { get; set; } = true;
public Rectangle CurrentScreenBounds { get; private set; } = Screen.PrimaryScreen.Bounds;
public bool NeedsInit { get; set; } = true;
public string SelectedScreen { get; private set; } = Screen.PrimaryScreen.DeviceName;
public void Dispose()
{
try
{
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
ClearDirectXOutputs();
GC.SuppressFinalize(this);
}
catch { }
}
public IEnumerable<string> GetDisplayNames()
{
return Screen.AllScreens.Select(x => x.DeviceName);
}
public SKRect GetFrameDiffArea()
{
return ImageUtils.GetDiffArea(_currentFrame, _previousFrame, CaptureFullscreen);
}
public Result<SKBitmap> GetImageDiff()
{
return ImageUtils.GetImageDiff(_currentFrame, _previousFrame);
}
public Result<SKBitmap> GetNextFrame()
{
lock (_screenBoundsLock)
{
try
{
if (!Win32Interop.SwitchToInputDesktop())
{
// Something will occasionally prevent this from succeeding after active
// desktop has changed to/from WinLogon (err code 170). I'm guessing a hook
// is getting put in the desktop, which causes SetThreadDesktop to fail.
// The caller can start a new thread, which seems to resolve it.
var errCode = Marshal.GetLastWin32Error();
var errMessage = $"Failed to switch to input desktop. Last Win32 error code: {errCode}";
Logger.Write(errMessage);
return Result.Fail<SKBitmap>(errMessage);
}
if (NeedsInit)
{
Logger.Write("Init needed in GetNextFrame.");
Init();
}
SwapFrames();
var result = GetDirectXFrame();
if (!result.IsSuccess || result.Value is null || IsEmpty(result.Value))
{
result = GetBitBltFrame();
if (!result.IsSuccess || result.Value is null)
{
var ex = result.Exception ?? new("Unknown error.");
Logger.Write(ex);
return Result.Fail<SKBitmap>(ex);
}
}
_currentFrame = result.Value;
return result;
}
catch (Exception e)
{
Logger.Write(e);
NeedsInit = true;
return Result.Fail<SKBitmap>(e);
}
}
}
public int GetScreenCount()
{
return Screen.AllScreens.Length;
}
public int GetSelectedScreenIndex()
{
if (_bitBltScreens.TryGetValue(SelectedScreen, out var index))
{
return index;
}
return 0;
}
public Rectangle GetVirtualScreenBounds()
{
return SystemInformation.VirtualScreen;
}
public void Init()
{
Win32Interop.SwitchToInputDesktop();
CaptureFullscreen = true;
InitBitBlt();
InitDirectX();
ScreenChanged?.Invoke(this, CurrentScreenBounds);
NeedsInit = false;
}
public void SetSelectedScreen(string displayName)
{
lock (_screenBoundsLock)
{
if (displayName == SelectedScreen)
{
return;
}
if (_bitBltScreens.ContainsKey(displayName))
{
SelectedScreen = displayName;
}
else
{
SelectedScreen = _bitBltScreens.Keys.First();
}
RefreshCurrentScreenBounds();
}
}
internal Result<SKBitmap> GetBitBltFrame()
{
try
{
using var bitmap = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb);
using (var graphic = Graphics.FromImage(bitmap))
{
graphic.CopyFromScreen(CurrentScreenBounds.Left, CurrentScreenBounds.Top, 0, 0, new Size(CurrentScreenBounds.Width, CurrentScreenBounds.Height));
}
return Result.Ok(bitmap.ToSKBitmap());
}
catch (Exception ex)
{
Logger.Write(ex);
Logger.Write("Capturer error in BitBltCapture.");
NeedsInit = true;
return Result.Fail<SKBitmap>("Error while capturing BitBlt frame.");
}
}
internal Result<SKBitmap> GetDirectXFrame()
{
if (!_directxScreens.TryGetValue(SelectedScreen, out var dxOutput))
{
return Result.Fail<SKBitmap>("DirectX output not found.");
}
try
{
var outputDuplication = dxOutput.OutputDuplication;
var device = dxOutput.Device;
var texture2D = dxOutput.Texture2D;
var bounds = dxOutput.Bounds;
var result = outputDuplication.TryAcquireNextFrame(50, out var duplicateFrameInfo, out var screenResource);
if (!result.Success)
{
return Result.Fail<SKBitmap>("Next frame did not arrive.");
}
if (duplicateFrameInfo.AccumulatedFrames == 0)
{
try
{
outputDuplication.ReleaseFrame();
}
catch { }
return Result.Fail<SKBitmap>("No frames were accumulated.");
}
using Texture2D screenTexture2D = screenResource.QueryInterface<Texture2D>();
device.ImmediateContext.CopyResource(screenTexture2D, texture2D);
var dataBox = device.ImmediateContext.MapSubresource(texture2D, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None);
using var bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
var bitmapData = bitmap.LockBits(bounds, ImageLockMode.WriteOnly, bitmap.PixelFormat);
var dataBoxPointer = dataBox.DataPointer;
var bitmapDataPointer = bitmapData.Scan0;
for (var y = 0; y < bounds.Height; y++)
{
Utilities.CopyMemory(bitmapDataPointer, dataBoxPointer, bounds.Width * 4);
dataBoxPointer = IntPtr.Add(dataBoxPointer, dataBox.RowPitch);
bitmapDataPointer = IntPtr.Add(bitmapDataPointer, bitmapData.Stride);
}
bitmap.UnlockBits(bitmapData);
device.ImmediateContext.UnmapSubresource(texture2D, 0);
screenResource?.Dispose();
switch (dxOutput.Rotation)
{
case DisplayModeRotation.Unspecified:
case DisplayModeRotation.Identity:
break;
case DisplayModeRotation.Rotate90:
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
case DisplayModeRotation.Rotate180:
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case DisplayModeRotation.Rotate270:
bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
default:
break;
}
return Result.Ok(bitmap.ToSKBitmap());
}
catch (Exception ex)
{
Logger.Write(ex, "Error while getting DirectX frame.");
}
finally
{
try
{
dxOutput.OutputDuplication.ReleaseFrame();
}
catch { }
}
return Result.Fail<SKBitmap>("Failed to get DirectX frame.");
}
private void ClearDirectXOutputs()
{
foreach (var screen in _directxScreens.Values)
{
try
{
screen.Dispose();
}
catch { }
}
_directxScreens.Clear();
}
private void InitBitBlt()
{
_bitBltScreens.Clear();
for (var i = 0; i < Screen.AllScreens.Length; i++)
{
_bitBltScreens.Add(Screen.AllScreens[i].DeviceName, i);
}
}
private void InitDirectX()
{
try
{
ClearDirectXOutputs();
using var factory = new Factory1();
foreach (var adapter in factory.Adapters1.Where(x => (x.Outputs?.Length ?? 0) > 0))
{
foreach (var output in adapter.Outputs)
{
try
{
var device = new SharpDX.Direct3D11.Device(adapter);
var output1 = output.QueryInterface<Output1>();
var bounds = output1.Description.DesktopBounds;
var width = bounds.Right - bounds.Left;
var height = bounds.Bottom - bounds.Top;
// Create Staging texture CPU-accessible
var textureDesc = new Texture2DDescription
{
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
Width = width,
Height = height,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Staging
};
var texture2D = new Texture2D(device, textureDesc);
_directxScreens.Add(
output1.Description.DeviceName,
new DirectXOutput(adapter,
device,
output1.DuplicateOutput(device),
texture2D,
output1.Description.Rotation));
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private bool IsEmpty(SKBitmap bitmap)
{
if (bitmap is null)
{
return true;
}
var height = bitmap.Height;
var width = bitmap.Width;
var bytesPerPixel = bitmap.BytesPerPixel;
try
{
unsafe
{
byte* scan = (byte*)bitmap.GetPixels();
for (var row = 0; row < height; row++)
{
for (var column = 0; column < width; column++)
{
var index = (row * width * bytesPerPixel) + (column * bytesPerPixel);
byte* data = scan + index;
for (var i = 0; i < bytesPerPixel; i++)
{
if (data[i] != 0)
{
return false;
}
}
}
}
return true;
}
}
catch
{
return true;
}
}
private void RefreshCurrentScreenBounds()
{
CurrentScreenBounds = Screen.AllScreens[_bitBltScreens[SelectedScreen]].Bounds;
CaptureFullscreen = true;
NeedsInit = true;
ScreenChanged?.Invoke(this, CurrentScreenBounds);
}
private void SwapFrames()
{
if (_currentFrame != null)
{
_previousFrame?.Dispose();
_previousFrame = _currentFrame;
}
}
private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
RefreshCurrentScreenBounds();
}
}
}

View File

@ -1,98 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Core.Utilities;
using Remotely.Shared.Utilities;
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
namespace Remotely.Desktop.Win.Services
{
public class SessionIndicatorWin : ISessionIndicator
{
private readonly Form _backgroundForm;
private readonly IDeviceInitService _deviceInitService;
private Container _container;
private ContextMenuStrip _contextMenuStrip;
private NotifyIcon _notifyIcon;
public SessionIndicatorWin(Form backgroundForm, IDeviceInitService deviceInitService)
{
_backgroundForm = backgroundForm;
_deviceInitService = deviceInitService;
}
public void Show()
{
try
{
if (_notifyIcon != null)
{
return;
}
App.Current.Dispatcher.Invoke(() =>
{
App.Current.Exit += App_Exit;
});
_backgroundForm.Invoke(new Action(() =>
{
_container = new Container();
_contextMenuStrip = new ContextMenuStrip(_container);
_contextMenuStrip.Items.Add("Exit", null, ExitMenuItem_Click);
Icon icon;
if (_deviceInitService.BrandingInfo?.Icon?.Any() == true)
{
using var ms = new MemoryStream(_deviceInitService.BrandingInfo.Icon);
using var bitmap = new Bitmap(ms);
icon = Icon.FromHandle(bitmap.GetHicon());
}
else
{
icon = Icon.ExtractAssociatedIcon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, EnvironmentHelper.DesktopExecutableFileName));
}
_notifyIcon = new NotifyIcon(_container)
{
Icon = icon,
Text = "Remote Control Session",
BalloonTipIcon = ToolTipIcon.Info,
BalloonTipText = "A remote control session has started.",
BalloonTipTitle = "Remote Control Started",
ContextMenuStrip = _contextMenuStrip
};
_notifyIcon.Visible = true;
_notifyIcon.ShowBalloonTip(3000);
}));
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private void App_Exit(object sender, EventArgs e)
{
if (_notifyIcon != null)
{
_notifyIcon.Visible = false;
_notifyIcon?.Dispose();
_notifyIcon?.Icon?.Dispose();
}
}
private async void ExitMenuItem_Click(object sender, EventArgs e)
{
var casterSocket = ServiceContainer.Instance.GetRequiredService<ICasterSocket>();
await casterSocket.DisconnectAllViewers();
}
}
}

View File

@ -1,35 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Utilities;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
namespace Remotely.Desktop.Win.Services
{
public class ShutdownServiceWin : IShutdownService
{
public async Task Shutdown()
{
try
{
Logger.Write($"Exiting process ID {Environment.ProcessId}.");
var casterSocket = ServiceContainer.Instance.GetRequiredService<ICasterSocket>();
await casterSocket.DisconnectAllViewers();
await casterSocket.Disconnect();
System.Windows.Forms.Application.Exit();
App.Current.Dispatcher.Invoke(() =>
{
App.Current.Shutdown();
});
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}

View File

@ -1,97 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Core.ViewModels;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Remotely.Desktop.Win.ViewModels
{
public class BrandedViewModelBase : ViewModelBase
{
public BrandedViewModelBase()
{
DeviceInitService = ServiceContainer.Instance?.GetRequiredService<IDeviceInitService>();
ApplyBranding();
}
public void ApplyBranding()
{
try
{
var brandingInfo = DeviceInitService?.BrandingInfo ?? new BrandingInfo();
ProductName = "Remotely";
if (!string.IsNullOrWhiteSpace(brandingInfo?.Product))
{
ProductName = brandingInfo.Product;
}
TitleBackgroundColor = new SolidColorBrush(Color.FromRgb(
brandingInfo?.TitleBackgroundRed ?? 70,
brandingInfo?.TitleBackgroundGreen ?? 70,
brandingInfo?.TitleBackgroundBlue ?? 70));
TitleForegroundColor = new SolidColorBrush(Color.FromRgb(
brandingInfo?.TitleForegroundRed ?? 29,
brandingInfo?.TitleForegroundGreen ?? 144,
brandingInfo?.TitleForegroundBlue ?? 241));
TitleButtonForegroundColor = new SolidColorBrush(Color.FromRgb(
brandingInfo?.ButtonForegroundRed ?? 255,
brandingInfo?.ButtonForegroundGreen ?? 255,
brandingInfo?.ButtonForegroundBlue ?? 255));
Icon = GetBitmapImageIcon(brandingInfo);
FirePropertyChanged(nameof(ProductName));
FirePropertyChanged(nameof(TitleBackgroundColor));
FirePropertyChanged(nameof(TitleForegroundColor));
FirePropertyChanged(nameof(TitleButtonForegroundColor));
FirePropertyChanged(nameof(Icon));
}
catch (Exception ex)
{
Logger.Write(ex, "Error applying branding.");
}
}
public BitmapImage Icon { get; set; }
public string ProductName { get; set; }
public SolidColorBrush TitleBackgroundColor { get; set; }
public SolidColorBrush TitleButtonForegroundColor { get; set; }
public SolidColorBrush TitleForegroundColor { get; set; }
protected IDeviceInitService DeviceInitService { get; }
private BitmapImage GetBitmapImageIcon(BrandingInfo bi)
{
Stream imageStream;
if (bi.Icon?.Any() == true)
{
imageStream = new MemoryStream(bi.Icon);
}
else
{
imageStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Remotely.Desktop.Win.Assets.Remotely_Icon.png");
}
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = imageStream;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
imageStream.Close();
return bitmap;
}
}
}

View File

@ -1,84 +0,0 @@
using Remotely.Shared.Models;
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
namespace Remotely.Desktop.Win.ViewModels
{
public class ChatWindowViewModel : BrandedViewModelBase
{
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();
}
}
public string OrganizationName
{
get
{
return _organizationName;
}
set
{
if (string.IsNullOrWhiteSpace(value) ||
value == _organizationName)
{
return;
}
_organizationName = value;
FirePropertyChanged();
}
}
public StreamWriter PipeStreamWriter { get; set; }
public string SenderName
{
get
{
return _senderName;
}
set
{
if (value == _senderName)
{
return;
}
_senderName = value;
FirePropertyChanged();
}
}
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,129 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Core.ViewModels;
using Remotely.Desktop.Win.Services;
using Remotely.Shared.Win32;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Input;
namespace Remotely.Desktop.Win.ViewModels
{
public class FileTransferWindowViewModel : BrandedViewModelBase
{
private readonly IFileTransferService _fileTransferService;
private readonly Viewer _viewer;
private string _viewerConnectionId;
private string _viewerName;
public FileTransferWindowViewModel() { }
public FileTransferWindowViewModel(
Viewer viewer,
IFileTransferService fileTransferService)
{
_fileTransferService = fileTransferService;
_viewer = viewer;
ViewerName = viewer.Name;
ViewerConnectionId = viewer.ViewerConnectionID;
}
public ObservableCollection<FileUpload> FileUploads { get; } = new ObservableCollection<FileUpload>();
public ICommand OpenFileUploadDialog => new Executor(async (param) =>
{
// Change initial directory so it doesn't open in %userprofile% path
// for SYSTEM account.
var rootDir = Path.GetPathRoot(Environment.SystemDirectory);
var userDir = Path.Combine(rootDir,
"Users",
Win32Interop.GetUsernameFromSessionId((uint)Process.GetCurrentProcess().SessionId));
var ofd = new OpenFileDialog()
{
Title = "Upload File via Remotely",
Multiselect = true,
CheckFileExists = true,
InitialDirectory = Directory.Exists(userDir) ? userDir : rootDir
};
try
{
// The OpenFileDialog throws an error if SYSTEM doesn't have a Desktop folder.
var desktop = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Desktop");
Directory.CreateDirectory(desktop);
}
catch { }
var result = ofd.ShowDialog();
if (result == DialogResult.Cancel)
{
return;
}
foreach (var file in ofd.FileNames)
{
if (File.Exists(file))
{
await UploadFile(file);
}
}
});
public string ViewerConnectionId
{
get
{
return _viewerConnectionId;
}
set
{
_viewerConnectionId = value;
FirePropertyChanged();
}
}
public string ViewerName
{
get
{
return _viewerName;
}
set
{
_viewerName = value;
FirePropertyChanged();
}
}
public async Task UploadFile(string filePath)
{
var fileUpload = new FileUpload()
{
FilePath = filePath
};
App.Current.Dispatcher.Invoke(() =>
{
FileUploads.Add(fileUpload);
});
await _fileTransferService.UploadFile(fileUpload, _viewer, fileUpload.CancellationTokenSource.Token, (double progress) =>
{
App.Current.Dispatcher.Invoke(() => fileUpload.PercentProgress = progress);
});
}
public ICommand RemoveFileUpload => new Executor((param) =>
{
if (param is FileUpload fileUpload)
{
FileUploads.Remove(fileUpload);
fileUpload.CancellationTokenSource.Cancel();
}
});
}
}

View File

@ -1,19 +0,0 @@
using Remotely.Desktop.Core.ViewModels;
namespace Remotely.Desktop.Win.ViewModels
{
public class HostNamePromptViewModel : BrandedViewModelBase
{
private string _host = "https://";
public string Host
{
get => _host;
set
{
_host = value;
FirePropertyChanged();
}
}
}
}

View File

@ -1,355 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Win.Services;
using Remotely.Desktop.Win.Views;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Remotely.Desktop.Win.ViewModels
{
public class MainWindowViewModel : BrandedViewModelBase
{
private readonly ICasterSocket _casterSocket;
private readonly Conductor _conductor;
private readonly IConfigService _configService;
private readonly ICursorIconWatcher _cursorIconWatcher;
private string _host;
private string _sessionId;
private string _statusMessage;
public MainWindowViewModel()
{
Current = this;
if (Services is null)
{
return;
}
Application.Current.Exit += Application_Exit;
_configService = Services.GetRequiredService<IConfigService>();
_cursorIconWatcher = Services.GetRequiredService<ICursorIconWatcher>();
_cursorIconWatcher.OnChange += CursorIconWatcher_OnChange;
_conductor = Services.GetRequiredService<Conductor>();
_casterSocket = Services.GetRequiredService<ICasterSocket>();
Services.GetRequiredService<IClipboardService>().BeginWatching();
Services.GetRequiredService<IKeyboardMouseInput>().Init();
_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
{
get
{
return new Executor(async (param) =>
{
PromptForHostName();
await Init();
});
}
}
public ICommand ElevateToAdminCommand
{
get
{
return new Executor((param) =>
{
try
{
//var filePath = Process.GetCurrentProcess().MainModule.FileName;
var commandLine = Win32Interop.GetCommandLine().Replace(" -elevate", "");
var sections = commandLine.Split('"', StringSplitOptions.RemoveEmptyEntries);
var filePath = sections.First();
var arguments = string.Join('"', sections.Skip(1));
var psi = new ProcessStartInfo(filePath, arguments)
{
Verb = "RunAs",
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden
};
Process.Start(psi);
Environment.Exit(0);
}
// Exception can be thrown if UAC is dialog is cancelled.
catch { }
}, (param) =>
{
return !IsAdministrator;
});
}
}
public ICommand ElevateToServiceCommand
{
get
{
return new Executor((param) =>
{
try
{
var psi = new ProcessStartInfo("cmd.exe")
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true
};
//var filePath = Process.GetCurrentProcess().MainModule.FileName;
var commandLine = Win32Interop.GetCommandLine().Replace(" -elevate", "");
var sections = commandLine.Split('"', StringSplitOptions.RemoveEmptyEntries);
var filePath = sections.First();
var arguments = string.Join('"', sections.Skip(1));
Logger.Write($"Creating temporary service with file path {filePath} and arguments {arguments}.");
psi.Arguments = $"/c sc create Remotely_Temp binPath=\"{filePath} {arguments} -elevate\"";
Process.Start(psi).WaitForExit();
psi.Arguments = "/c sc start Remotely_Temp";
Process.Start(psi).WaitForExit();
psi.Arguments = "/c sc delete Remotely_Temp";
Process.Start(psi).WaitForExit();
App.Current.Shutdown();
}
catch { }
}, (param) =>
{
return IsAdministrator && !WindowsIdentity.GetCurrent().IsSystem;
});
}
}
public string Host
{
get => _host;
set
{
_host = value;
FirePropertyChanged();
}
}
public bool IsAdministrator => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
public ICommand RemoveViewersCommand
{
get
{
return new Executor(async (param) =>
{
foreach (Viewer viewer in (param as IList<object>).ToArray())
{
ViewerRemoved(this, viewer.ViewerConnectionID);
await _casterSocket.DisconnectViewer(viewer, true);
}
},
(param) =>
{
return (param as IList<object>)?.Count > 0;
});
}
}
public string StatusMessage
{
get => _statusMessage;
set
{
_statusMessage = value;
FirePropertyChanged();
}
}
public ObservableCollection<Viewer> Viewers { get; } = new ObservableCollection<Viewer>();
public void CopyLink()
{
Clipboard.SetText($"{Host}/RemoteControl?sessionID={StatusMessage?.Replace(" ", "")}");
}
public async Task GetSessionID()
{
await _casterSocket.SendDeviceInfo(_conductor.ServiceID, Environment.MachineName, _conductor.DeviceID);
var sessionId = await _casterSocket.GetSessionID();
var formattedSessionID = "";
for (var i = 0; i < sessionId.Length; i += 3)
{
formattedSessionID += sessionId.Substring(i, 3) + " ";
}
App.Current?.Dispatcher?.Invoke(() =>
{
_sessionId = formattedSessionID.Trim();
StatusMessage = _sessionId;
});
}
public async Task Init()
{
StatusMessage = "Retrieving...";
Host = _configService.GetConfig().Host;
while (string.IsNullOrWhiteSpace(Host))
{
Host = "https://";
PromptForHostName();
}
_conductor.ProcessArgs(new string[] { "-mode", "Normal", "-host", Host });
try
{
var result = await _casterSocket.Connect(_conductor.Host);
if (result)
{
_casterSocket.Connection.Closed += (ex) =>
{
App.Current?.Dispatcher?.Invoke(() =>
{
Viewers.Clear();
StatusMessage = "Disconnected";
});
return Task.CompletedTask;
};
_casterSocket.Connection.Reconnecting += (ex) =>
{
App.Current?.Dispatcher?.Invoke(() =>
{
Viewers.Clear();
StatusMessage = "Reconnecting";
});
return Task.CompletedTask;
};
_casterSocket.Connection.Reconnected += (id) =>
{
StatusMessage = _sessionId;
return Task.CompletedTask;
};
await DeviceInitService.GetInitParams();
ApplyBranding();
await GetSessionID();
return;
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
// If we got here, something went wrong.
StatusMessage = "Failed";
MessageBox.Show(Application.Current.MainWindow, "Failed to connect to server.", "Connection Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
}
public void PromptForHostName()
{
var prompt = new HostNamePrompt();
if (!string.IsNullOrWhiteSpace(Host))
{
prompt.ViewModel.Host = Host;
}
prompt.Owner = App.Current?.MainWindow;
prompt.ShowDialog();
var result = prompt.ViewModel.Host?.Trim()?.TrimEnd('/');
if (!Uri.TryCreate(result, UriKind.Absolute, out var serverUri) ||
(serverUri.Scheme != Uri.UriSchemeHttp && serverUri.Scheme != Uri.UriSchemeHttps))
{
Logger.Write("Server URL is not valid.");
MessageBox.Show("Server URL must be a valid Uri (e.g. https://app.remotely.one).", "Invalid Server URL", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
Host = result;
var config = _configService.GetConfig();
config.Host = Host;
_configService.Save(config);
}
public void ShutdownApp()
{
Services.GetRequiredService<IShutdownService>().Shutdown();
}
private void Application_Exit(object sender, ExitEventArgs e)
{
App.Current.Dispatcher.Invoke(() =>
{
Viewers.Clear();
});
}
private async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor)
{
if (_conductor?.Viewers?.Count > 0)
{
foreach (var viewer in _conductor.Viewers.Values)
{
await viewer.SendCursorChange(cursor);
}
}
}
private async void ScreenCastRequested(object sender, ScreenCastRequest screenCastRequest)
{
await App.Current.Dispatcher.InvokeAsync(async () =>
{
App.Current.MainWindow.Activate();
var result = MessageBox.Show(Application.Current.MainWindow, $"You've received a connection request from {screenCastRequest.RequesterName}. Accept?", "Connection Request", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
Services.GetRequiredService<IScreenCaster>().BeginScreenCasting(screenCastRequest);
}
else
{
await _casterSocket.SendConnectionRequestDenied(screenCastRequest.ViewerID);
}
});
}
private void ViewerAdded(object sender, Viewer viewer)
{
App.Current.Dispatcher.Invoke(() =>
{
Viewers.Add(viewer);
});
}
private void ViewerRemoved(object sender, string viewerID)
{
App.Current.Dispatcher.Invoke(() =>
{
var viewer = Viewers.FirstOrDefault(x => x.ViewerConnectionID == viewerID);
if (viewer != null)
{
Viewers.Remove(viewer);
}
});
}
}
}

View File

@ -1,55 +0,0 @@
using Remotely.Desktop.Core.ViewModels;
using Remotely.Desktop.Win.Services;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace Remotely.Desktop.Win.ViewModels
{
public class PromptForAccessWindowViewModel : BrandedViewModelBase
{
private string _organizationName = "your IT provider";
private string _requesterName = "a technician";
public string OrganizationName
{
get => _organizationName;
set
{
_organizationName = value;
FirePropertyChanged();
}
}
public bool PromptResult { get; set; }
public string RequesterName
{
get => _requesterName;
set
{
_requesterName = value;
FirePropertyChanged();
}
}
public ICommand SetResultNo => new Executor(param =>
{
if (param is Window promptWindow)
{
PromptResult = false;
promptWindow.Close();
}
});
public ICommand SetResultYes => new Executor(param =>
{
if (param is Window promptWindow)
{
PromptResult = true;
promptWindow.Close();
}
});
}
}

View File

@ -1,103 +0,0 @@
<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="Chat Session" Height="450" Width="450"
Icon="{Binding Icon}"
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="{Binding TitleBackgroundColor}">
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Height="50" Width="50" Margin="0,0,10,0" Source="{Binding Icon}"></Image>
<TextBlock Grid.Column="1" Foreground="{Binding TitleForegroundColor}" FontWeight="Bold" FontSize="20" VerticalAlignment="Center">
<Run Text="{Binding ProductName}"></Run>
<Run Text="Chat"></Run>
</TextBlock>
<Button Grid.Column="2" Style="{StaticResource TitlebarButton}" Click="MinimizeButton_Click" Content="____" Foreground="{Binding TitleButtonForegroundColor}" Background="{Binding TitleBackgroundColor}"/>
<Button Grid.Column="3" Style="{StaticResource TitlebarButton}" Click="CloseButton_Click" Content="X" Foreground="{Binding TitleButtonForegroundColor}" Background="{Binding TitleBackgroundColor}" />
</Grid>
</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

@ -1,59 +0,0 @@
using Remotely.Desktop.Win.ViewModels;
using System;
using System.Windows;
using System.Windows.Input;
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,68 +0,0 @@
<Window x:Class="Remotely.Desktop.Win.Views.FileTransferWindow"
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:vm="clr-namespace:Remotely.Desktop.Win.ViewModels"
mc:Ignorable="d"
Title="File Transfer"
Height="300" Width="400"
Topmost="True"
ContentRendered="Window_ContentRendered"
Icon="{Binding Icon}">
<Window.DataContext>
<vm:FileTransferWindowViewModel />
</Window.DataContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold" FontSize="16" Grid.Row="0">
<Run>Upload files to</Run>
<Run Text="{Binding ViewerName}"></Run>
</TextBlock>
<TextBlock Grid.Row="1" Margin="0,10,0,0">
Current Uploads:
</TextBlock>
<Border Grid.Row="2" Margin="0,5,0,0" BorderBrush="LightGray" BorderThickness="1">
<ScrollViewer >
<ItemsControl ItemsSource="{Binding FileUploads}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch" Margin="0,0,5,0">
<TextBlock Text="{Binding DisplayName, StringFormat={}{0}:}" Margin="0,0,0,2"></TextBlock>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar Value="{Binding PercentProgress}" Maximum="1" LargeChange="0.01" Height="18"></ProgressBar>
<Button Grid.Column="1"
VerticalAlignment="Top"
Command="{Binding DataContext.RemoveFileUpload, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:FileTransferWindow}}}"
CommandParameter="{Binding}"
Content="x"
Style="{StaticResource NormalButton}"
BorderThickness="0" />
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
<Button Grid.Row="3" Margin="0,10,0,0" Height="60" Style="{StaticResource NormalButton}" Command="{Binding OpenFileUploadDialog}">
Click here to upload files.
</Button>
</Grid>
</Window>

View File

@ -1,25 +0,0 @@
using System;
using System.Windows;
using System.Windows.Forms;
namespace Remotely.Desktop.Win.Views
{
/// <summary>
/// Interaction logic for FileTransferWindow.xaml
/// </summary>
public partial class FileTransferWindow : Window
{
public FileTransferWindow()
{
InitializeComponent();
Left = Screen.PrimaryScreen.WorkingArea.Right - Width;
Top = Screen.PrimaryScreen.WorkingArea.Bottom - Height;
}
private void Window_ContentRendered(object sender, EventArgs e)
{
Topmost = false;
}
}
}

View File

@ -1,20 +0,0 @@
<Window
x:Class="Remotely.Desktop.Win.Views.HostNamePrompt"
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" x:Name="PromptWindow"
mc:Ignorable="d"
Title="Host Name" Height="150" Width="350" WindowStartupLocation="CenterOwner"
Icon="{Binding Icon}">
<Window.DataContext>
<ViewModels:HostNamePromptViewModel/>
</Window.DataContext>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,5" Style="{StaticResource SectionHeader}"><Run Text="Enter the server URL:"/></TextBlock>
<TextBox x:Name="HostTextBox" Height="25" Text="{Binding Host, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Style="{StaticResource NormalButton}" HorizontalAlignment="Right" Width="40" Height="30" Margin="0,5,0,0" IsDefault="True" Click="OKButton_Click" Content="OK"/>
</StackPanel>
</Window>

View File

@ -1,23 +0,0 @@
using Remotely.Desktop.Win.ViewModels;
using System.Windows;
namespace Remotely.Desktop.Win.Views
{
/// <summary>
/// Interaction logic for HostNamePrompt.xaml
/// </summary>
public partial class HostNamePrompt : Window
{
public HostNamePrompt()
{
InitializeComponent();
}
public HostNamePromptViewModel ViewModel => DataContext as HostNamePromptViewModel;
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

View File

@ -1,116 +0,0 @@
<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.Views"
xmlns:ViewModels="clr-namespace:Remotely.Desktop.Win.ViewModels"
mc:Ignorable="d"
Title="{Binding ProductName}" Height="250" Width="350"
WindowStyle="None"
ResizeMode="NoResize"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
Closing="Window_Closing"
Loaded="Window_Loaded"
Icon="{Binding Icon}">
<Window.Resources>
<DrawingBrush x:Key="GearBrush">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M5.6385,-0.000199999999999534L5.3645,1.6368 4.0145,0.671800000000001 0.672499999999999,4.0128 1.6375,5.3648 -0.000500000000000611,5.6378 -0.000500000000000611,10.3628 1.6375,10.6358 0.6735,11.9858 4.0145,15.3268 5.3645,14.3628 5.6375,15.9998 10.3625,15.9998 10.6355,14.3618 11.9875,15.3278 15.3265,11.9858 14.3635,10.6358 16.0005,10.3628 16.0005,5.6378 14.3635,5.3648 15.3265,4.0138 11.9875,0.672800000000001 10.6355,1.6378 10.3625,-0.000199999999999534z" />
<GeometryDrawing Brush="#FF424242" Geometry="F1M8.0005,6.4623C7.1525,6.4623 6.4625,7.1523 6.4625,8.0003 6.4625,8.8483 7.1525,9.5373 8.0005,9.5373 8.8485,9.5373 9.5385,8.8483 9.5385,8.0003 9.5385,7.1523 8.8485,6.4623 8.0005,6.4623 M8.0005,10.5633C6.5875,10.5633 5.4375,9.4133 5.4375,8.0003 5.4375,6.5873 6.5875,5.4373 8.0005,5.4373 9.4135,5.4373 10.5635,6.5873 10.5635,8.0003 10.5635,9.4133 9.4135,10.5633 8.0005,10.5633 M7.3535,13.9753L8.6475,13.9753 8.9555,12.1263 9.2565,12.0323C9.5005,11.9563,9.7375,11.8583,9.9635,11.7393L10.2425,11.5933 11.7675,12.6823 12.6825,11.7673 11.5915,10.2403 11.7385,9.9613C11.7675,9.9043 11.7975,9.8543 11.8265,9.8053 11.8655,9.7373 11.8995,9.6783 11.9225,9.6243 11.9445,9.5703 11.9615,9.5043 11.9815,9.4283 11.9965,9.3743 12.0115,9.3173 12.0305,9.2573L12.1235,8.9553 13.9755,8.6473 13.9755,7.3533 12.1265,7.0453 12.0325,6.7443C11.9565,6.5003,11.8585,6.2633,11.7395,6.0373L11.5935,5.7583 12.6825,4.2333 11.7675,3.3183 10.2405,4.4083 9.9615,4.2623C9.9045,4.2323 9.8545,4.2033 9.8055,4.1743 9.7375,4.1353 9.6785,4.1003 9.6245,4.0783L9.6245,4.0783C9.5705,4.0563 9.5045,4.0383 9.4285,4.0183 9.3745,4.0043 9.3175,3.9893 9.2575,3.9703L8.9565,3.8763 8.6475,2.0253 7.3535,2.0253 7.0455,3.8743 6.7445,3.9683C6.5005,4.0443,6.2635,4.1423,6.0375,4.2603L5.7585,4.4073 4.2335,3.3173 3.3185,4.2323 4.4085,5.7593 4.2625,6.0393C4.2325,6.0953 4.2035,6.1463 4.1745,6.1953 4.1355,6.2633 4.1015,6.3213 4.0785,6.3753 4.0565,6.4303 4.0385,6.4953 4.0185,6.5723 4.0045,6.6263 3.9895,6.6823 3.9705,6.7433L3.8775,7.0443 2.0255,7.3533 2.0255,8.6473 3.8745,8.9553 3.9685,9.2563C4.0445,9.5003,4.1425,9.7363,4.2615,9.9633L4.4075,10.2423 3.3175,11.7673 4.2325,12.6823 5.7605,11.5913 6.0395,11.7383C6.0955,11.7673 6.1465,11.7973 6.1955,11.8253 6.2635,11.8653 6.3215,11.8993 6.3755,11.9213 6.4295,11.9443 6.4955,11.9613 6.5715,11.9813 6.6265,11.9963 6.6825,12.0103 6.7435,12.0293L7.0445,12.1233 7.3535,13.9753z M9.5155,15.0003L6.4845,15.0003 6.1385,12.9243C6.0865,12.9083 6.0345,12.8903 5.9835,12.8693 5.9325,12.8473 5.8835,12.8243 5.8345,12.7983L4.1225,14.0213 1.9795,11.8783 3.2135,10.1503C3.1705,10.0563,3.1315,9.9613,3.0945,9.8643L1.0005,9.5153 1.0005,6.4843 3.0755,6.1383C3.0925,6.0863 3.1105,6.0343 3.1315,5.9833 3.1525,5.9323 3.1765,5.8823 3.2025,5.8343L1.9795,4.1213 4.1225,1.9783 5.8505,3.2123C5.9445,3.1703,6.0395,3.1313,6.1365,3.0943L6.4855,1.0003 9.5165,1.0003 9.8615,3.0753C9.9115,3.0913 9.9635,3.1093 10.0175,3.1313 10.0685,3.1523 10.1175,3.1763 10.1665,3.2013L11.8785,1.9793 14.0215,4.1223 12.7875,5.8493C12.8305,5.9433,12.8695,6.0393,12.9065,6.1353L15.0005,6.4853 15.0005,9.5153 12.9255,9.8613C12.9085,9.9143 12.8905,9.9663 12.8695,10.0163 12.8485,10.0673 12.8245,10.1173 12.7985,10.1663L14.0215,11.8783 11.8785,14.0213 10.1505,12.7873C10.0565,12.8293,9.9615,12.8693,9.8645,12.9063L9.5155,15.0003z" />
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Window.DataContext>
<ViewModels:MainWindowViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<Border Height="50" Background="{Binding TitleBackgroundColor}">
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Height="45" Width="45" Margin="0,0,10,0" Source="{Binding Icon}"></Image>
<TextBlock Grid.Column="1" Text="{Binding ProductName}" Foreground="{Binding TitleForegroundColor}" FontWeight="Bold" FontSize="20" VerticalAlignment="Center" />
<Button Grid.Column="2" Style="{StaticResource TitlebarButton}" Click="MinimizeButton_Click" Content="____" Foreground="{Binding TitleButtonForegroundColor}" Background="{Binding TitleBackgroundColor}"/>
<Button Grid.Column="3" Style="{StaticResource TitlebarButton}" Click="CloseButton_Click" Content="X" Foreground="{Binding TitleButtonForegroundColor}" Background="{Binding TitleBackgroundColor}" />
</Grid>
</Border>
<Grid Margin="10,15,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="0,0,15,0">
<TextBlock Style="{StaticResource SectionHeader}"><Run Text="Share Screen"/></TextBlock>
<TextBlock FontSize="16" Margin="0,10,0,5"><Run Text="Your Session ID:"/></TextBlock>
<TextBox FontSize="16" Text="{Binding StatusMessage}" IsReadOnly="True"/>
<Grid Margin="0,10,0,0">
<TextBlock VerticalAlignment="Center">
Invite Link:
</TextBlock>
<Button HorizontalAlignment="Right" Style="{StaticResource NormalButton}" Click="CopyLinkButton_Click">
Copy
</Button>
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Margin="15,0,0,0">
<TextBlock Style="{StaticResource SectionHeader}"><Run Text="Viewers"/></TextBlock>
<Grid Margin="0,5,0,0">
<TextBlock FontSize="8" Margin="5,0,0,0">Name</TextBlock>
<TextBlock FontSize="8" Margin="0,0,5,0" HorizontalAlignment="Right">Has Control</TextBlock>
</Grid>
<ListBox x:Name="ViewerListBox" Height="90" HorizontalContentAlignment="Stretch" ItemsSource="{Binding Viewers}" SelectionMode="Extended">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<CheckBox IsChecked="{Binding HasControl}" HorizontalAlignment="Right"></CheckBox>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Style="{StaticResource NormalButton}"
HorizontalAlignment="Right"
Command="{Binding RemoveViewersCommand}"
CommandParameter="{Binding ElementName=ViewerListBox, Path=SelectedItems}"
Margin="0,5,0,0"
Content="Remove">
</Button>
</StackPanel>
</Grid>
</StackPanel>
<Button BorderThickness="0"
Background="Transparent"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Cursor="Hand"
Height="20"
Width="20"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10,5"
Click="OptionsButton_Click">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Change Server" Command="{Binding Path=ChangeServerCommand, Source={x:Static ViewModels:MainWindowViewModel.Current}}"></MenuItem>
<MenuItem Header="Elevate to Admin" Command="{Binding Path=ElevateToAdminCommand, Source={x:Static ViewModels:MainWindowViewModel.Current}}"></MenuItem>
<MenuItem Header="Elevate to Service" Command="{Binding Path=ElevateToServiceCommand, Source={x:Static ViewModels:MainWindowViewModel.Current}}"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
<Rectangle Fill="{StaticResource GearBrush}"></Rectangle>
</Button>
</Grid>
</Window>

View File

@ -1,77 +0,0 @@
using Remotely.Desktop.Win.ViewModels;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace Remotely.Desktop.Win.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MainWindowViewModel ViewModel => DataContext as MainWindowViewModel;
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
private async void CopyLinkButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.CopyLink();
var tooltip = new ToolTip
{
PlacementTarget = sender as Button,
Placement = PlacementMode.Bottom,
VerticalOffset = 5,
Content = "Copied to clipboard!",
HasDropShadow = true,
StaysOpen = false,
IsOpen = true
};
await Task.Delay(750);
var animation = new DoubleAnimation(0, TimeSpan.FromMilliseconds(750));
tooltip.BeginAnimation(OpacityProperty, animation);
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void OptionsButton_Click(object sender, RoutedEventArgs e)
{
(sender as Button).ContextMenu.IsOpen = true;
}
private void Window_Closing(object sender, CancelEventArgs e)
{
ViewModel?.ShutdownApp();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!DesignerProperties.GetIsInDesignMode(this) &&
ViewModel != null)
{
await ViewModel?.Init();
}
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
}
}

View File

@ -1,77 +0,0 @@
<Window x:Class="Remotely.Desktop.Win.Views.PromptForAccessWindow"
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"
mc:Ignorable="d"
WindowStyle="None"
xmlns:ViewModels="clr-namespace:Remotely.Desktop.Win.ViewModels"
ResizeMode="CanResizeWithGrip"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
BorderBrush="DimGray"
BorderThickness="1"
Topmost="True"
Title="Remote Control Request"
MinHeight="200"
MinWidth="250"
Height="275"
Width="450"
Icon="{Binding Icon}"
ContentRendered="Window_ContentRendered">
<Window.DataContext>
<ViewModels:PromptForAccessWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Height="50" Background="{Binding TitleBackgroundColor}">
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Height="50" Width="50" Margin="0,0,10,0" Source="{Binding Icon}"></Image>
<TextBlock Grid.Column="1" Text="Remote Control Request" Foreground="{Binding TitleForegroundColor}" FontWeight="Bold" FontSize="20" VerticalAlignment="Center" />
<Button Grid.Column="2" Style="{StaticResource TitlebarButton}" Click="MinimizeButton_Click" Content="____" Foreground="{Binding TitleButtonForegroundColor}" Background="{Binding TitleBackgroundColor}"/>
<Button Grid.Column="3" Style="{StaticResource TitlebarButton}" Click="CloseButton_Click" Content="X" Foreground="{Binding TitleButtonForegroundColor}" Background="{Binding TitleBackgroundColor}" />
</Grid>
</Border>
<StackPanel Grid.Row="1">
<TextBlock Style="{StaticResource SectionHeader}" FontWeight="Bold" FontSize="18" Foreground="DimGray" Margin="10" TextWrapping="Wrap">
A remote control session has been requested.
</TextBlock>
<TextBlock Style="{StaticResource SectionHeader}" FontWeight="Bold" FontSize="18" Foreground="DimGray" Margin="10" TextWrapping="Wrap">
<Run>Would you like to allow </Run>
<Run Text="{Binding RequesterName}"></Run>
<Run> from </Run>
<Run Text="{Binding OrganizationName}"></Run>
<Run> to control your computer?</Run>
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button Width="60" Height="40" Content="Yes" Margin="10"
Style="{StaticResource NormalButton}"
Command="{Binding SetResultYes}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PromptForAccessWindow}}}"></Button>
<Button Width="60" Height="40" Content="No" Margin="10"
Style="{StaticResource NormalButton}"
Command="{Binding SetResultNo}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PromptForAccessWindow}}}"></Button>
</StackPanel>
</Grid>
</Window>

View File

@ -1,45 +0,0 @@
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 PromptForAccessWindow.xaml
/// </summary>
public partial class PromptForAccessWindow : Window
{
public PromptForAccessWindow()
{
InitializeComponent();
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
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_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
}
}

View File

@ -1,44 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Remotely.Desktop.XPlat;assembly=Remotely_Desktop"
x:Class="Remotely.Desktop.XPlat.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<Style Selector="Button.TitlebarButton">
<Setter Property="Background" Value="#FF464646"></Setter>
<Setter Property="DockPanel.Dock" Value="Right"></Setter>
<Setter Property="HorizontalAlignment" Value="Right"></Setter>
<Setter Property="BorderBrush" Value="{x:Null}"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
<Setter Property="Height" Value="30"></Setter>
<Setter Property="Width" Value="30"></Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
<Setter Property="Margin" Value="5,0,5,0"></Setter>
</Style>
<Style Selector="TextBlock.SectionHeader">
<Setter Property="FontWeight" Value="Bold"></Setter>
<Setter Property="FontSize" Value="18"></Setter>
</Style>
<Style Selector="Button.NormalButton">
<Setter Property="Background" Value="White"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="Padding" Value="6,4"></Setter>
</Style>
<Style Selector="Button.HyperlinkButton">
<Setter Property="Foreground" Value="DodgerBlue"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="BorderBrush" Value="Transparent"></Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="Padding" Value="0"></Setter>
</Style>
</Application.Styles>
</Application>

View File

@ -1,130 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.XPlat.Services;
using Remotely.Desktop.XPlat.Views;
using Remotely.Shared.Utilities;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat
{
public class App : Application
{
private static IServiceProvider Services => ServiceContainer.Instance;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
//if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
//{
// desktop.MainWindow = new MainWindow
// {
// DataContext = new MainWindowViewModel(),
// };
//}
base.OnFrameworkInitializationCompleted();
_ = Task.Run(Startup);
}
private void BuildServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder =>
{
builder.AddConsole().AddDebug();
});
serviceCollection.AddSingleton<IScreenCaster, ScreenCaster>();
serviceCollection.AddSingleton<ICasterSocket, CasterSocket>();
serviceCollection.AddSingleton<IdleTimer>();
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddSingleton<IChatClientService, ChatHostService>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddScoped<IDtoMessageHandler, DtoMessageHandler>();
serviceCollection.AddScoped<IDeviceInitService, DeviceInitService>();
switch (EnvironmentHelper.Platform)
{
case Shared.Enums.Platform.Linux:
{
serviceCollection.AddSingleton<IKeyboardMouseInput, KeyboardMouseInputLinux>();
serviceCollection.AddSingleton<IClipboardService, ClipboardServiceLinux>();
serviceCollection.AddSingleton<IAudioCapturer, AudioCapturerLinux>();
serviceCollection.AddSingleton<IChatUiService, ChatUiServiceLinux>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerLinux>();
serviceCollection.AddScoped<IFileTransferService, FileTransferServiceLinux>();
serviceCollection.AddSingleton<ICursorIconWatcher, CursorIconWatcherLinux>();
serviceCollection.AddSingleton<ISessionIndicator, SessionIndicatorLinux>();
serviceCollection.AddSingleton<IShutdownService, ShutdownServiceLinux>();
serviceCollection.AddScoped<IRemoteControlAccessService, RemoteControlAccessServiceLinux>();
serviceCollection.AddScoped<IConfigService, ConfigServiceLinux>();
}
break;
case Shared.Enums.Platform.MacOS:
{
}
break;
default:
throw new PlatformNotSupportedException();
}
ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
private async Task Startup()
{
BuildServices();
var conductor = Services.GetRequiredService<Conductor>();
var args = Environment.GetCommandLineArgs().SkipWhile(x => !x.StartsWith("-"));
Logger.Write("Processing Args: " + string.Join(", ", args));
conductor.ProcessArgs(args.ToArray());
await Services.GetRequiredService<IDeviceInitService>().GetInitParams();
if (conductor.Mode == Core.Enums.AppMode.Chat)
{
await Services.GetRequiredService<IChatClientService>().StartChat(conductor.RequesterID, conductor.OrganizationName);
}
else if (conductor.Mode == Core.Enums.AppMode.Unattended)
{
var casterSocket = Services.GetRequiredService<ICasterSocket>();
await casterSocket.Connect(conductor.Host).ConfigureAwait(false);
await casterSocket.SendDeviceInfo(conductor.ServiceID, Environment.MachineName, conductor.DeviceID).ConfigureAwait(false);
await casterSocket.NotifyRequesterUnattendedReady(conductor.RequesterID).ConfigureAwait(false);
Services.GetRequiredService<IdleTimer>().Start();
Services.GetRequiredService<IClipboardService>().BeginWatching();
Services.GetRequiredService<IKeyboardMouseInput>().Init();
}
else
{
await Dispatcher.UIThread.InvokeAsync(() => {
this.RunWithMainWindow<MainWindow>();
});
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

View File

@ -1,40 +0,0 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:vm="clr-namespace:Remotely.Desktop.XPlat.ViewModels;assembly=Remotely_Desktop"
x:Class="Remotely.Desktop.XPlat.Controls.MessageBox"
Icon="{Binding WindowIcon}"
Title="{Binding Caption}"
SizeToContent="WidthAndHeight" MinWidth="300" MinHeight="100"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MessageBoxViewModel></vm:MessageBoxViewModel>
</Window.DataContext>
<StackPanel Margin="10, 20">
<TextBlock Text="{Binding Message}"></TextBlock>
<Grid Margin="0,20,0,0">
<Button Classes="NormalButton" HorizontalAlignment="Right" IsVisible="{Binding IsOkButtonVisible}"
Command="{Binding OKCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Content="OK">
</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Classes="NormalButton" HorizontalAlignment="Right" IsVisible="{Binding AreYesNoButtonsVisible}" Margin="5,0,5,0"
Command="{Binding YesCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Content="Yes">
</Button>
<Button Classes="NormalButton" HorizontalAlignment="Right" IsVisible="{Binding AreYesNoButtonsVisible}" Margin="5,0,5,0"
Command="{Binding NoCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Content="No">
</Button>
</StackPanel>
</Grid>
</StackPanel>
</Window>

View File

@ -1,80 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Remotely.Desktop.XPlat.ViewModels;
using Remotely.Shared.Utilities;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Controls
{
public class MessageBox : Window
{
public static async Task<MessageBoxResult> Show(string message, string caption, MessageBoxType type)
{
var messageBox = new MessageBox();
var viewModel = messageBox.DataContext as MessageBoxViewModel;
viewModel.Caption = caption;
viewModel.Message = message;
switch (type)
{
case MessageBoxType.OK:
viewModel.IsOkButtonVisible = true;
break;
case MessageBoxType.YesNo:
viewModel.AreYesNoButtonsVisible = true;
break;
default:
break;
}
if (App.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop &&
desktop.Windows.Count > 0)
{
await messageBox.ShowDialog(desktop.Windows.First());
}
else
{
var isClosed = false;
messageBox.Closed += (sender, args) =>
{
isClosed = true;
};
messageBox.Show();
await TaskHelper.DelayUntilAsync(() => isClosed, TimeSpan.MaxValue);
}
return viewModel.Result;
}
public MessageBox()
{
// This doesn't appear to work when set in XAML.
WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
public enum MessageBoxType
{
OK,
YesNo
}
public enum MessageBoxResult
{
Cancel,
OK,
Yes,
No
}
}

View File

@ -1,142 +0,0 @@
/*
Copyright 1985, 1986, 1987, 1991, 1998 The Open Group
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of The Open Group shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from The Open Group.
*/
using System;
using System.Runtime.InteropServices;
namespace Remotely.Desktop.XPlat.Native.Linux
{
public static unsafe class LibX11
{
[DllImport("libX11")]
public static extern IntPtr XGetImage(IntPtr display, IntPtr drawable, int x, int y, int width, int height, long plane_mask, int format);
[DllImport("libX11")]
public static extern IntPtr XDefaultVisual(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XScreenCount(IntPtr display);
[DllImport("libX11")]
public static extern int XDefaultScreen(IntPtr display);
[DllImport("libX11")]
public static extern IntPtr XOpenDisplay(string display_name);
[DllImport("libX11")]
public static extern void XCloseDisplay(IntPtr display);
[DllImport("libX11")]
public static extern IntPtr XRootWindow(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern IntPtr XGetSubImage(IntPtr display, IntPtr drawable, int x, int y, uint width, uint height, ulong plane_mask, int format, IntPtr dest_image, int dest_x, int dest_y);
[DllImport("libX11")]
public static extern IntPtr XScreenOfDisplay(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XDisplayWidth(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XDisplayHeight(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern int XWidthOfScreen(IntPtr screen);
[DllImport("libX11")]
public static extern int XHeightOfScreen(IntPtr screen);
[DllImport("libX11")]
public static extern IntPtr XDefaultGC(IntPtr display, int screen_number);
[DllImport("libX11")]
public static extern IntPtr XDefaultRootWindow(IntPtr display);
[DllImport("libX11")]
public static extern void XGetInputFocus(IntPtr display, out IntPtr focus_return, out int revert_to_return);
[DllImport("libX11")]
public static extern IntPtr XStringToKeysym(string key);
[DllImport("libX11")]
public static extern uint XKeysymToKeycode(IntPtr display, IntPtr keysym);
[DllImport("libX11")]
public static extern IntPtr XRootWindowOfScreen(IntPtr screen);
[DllImport("libX11")]
public static extern ulong XNextRequest(IntPtr display);
[DllImport("libX11")]
public static extern void XForceScreenSaver(IntPtr display, int mode);
[DllImport("libX11")]
public static extern void XSync(IntPtr display, bool discard);
[DllImport("libX11")]
public static extern void XDestroyImage(IntPtr ximage);
[DllImport("libX11")]
public static extern void XNoOp(IntPtr display);
[DllImport("libX11")]
public static extern void XFree(IntPtr data);
[DllImport("libX11")]
public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, out XWindowAttributes windowAttributes);
public struct XImage
{
public int width;
public int height; /* size of image */
public int xoffset; /* number of pixels offset in X direction */
public int format; /* XYBitmap, XYPixmap, ZPixmap */
//public char* data; /* pointer to image data */
public IntPtr data; /* pointer to image data */
public int byte_order; /* data byte order, LSBFirst, MSBFirst */
public int bitmap_unit; /* quant. of scanline 8, 16, 32 */
public int bitmap_bit_order; /* LSBFirst, MSBFirst */
public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */
public int depth; /* depth of image */
public int bytes_per_line; /* accelerator to next scanline */
public int bits_per_pixel; /* bits per pixel (ZPixmap) */
public ulong red_mask; /* bits in z arrangement */
public ulong green_mask;
public ulong blue_mask;
public IntPtr obdata; /* hook for the object routines to hang on */
}
public struct XWindowAttributes
{
public int x;
public int y;
public int width;
public int height;
public int border_width;
public int depth;
public IntPtr visual;
public IntPtr root;
public int @class;
public int bit_gravity;
public int win_gravity;
public int backing_store;
public ulong backing_planes;
public ulong backing_pixel;
public bool save_under;
public IntPtr colormap;
public bool map_installed;
public int map_state;
public long all_event_masks;
public long your_event_mask;
public long do_not_propagate_mask;
public bool override_redirect;
public IntPtr screen;
}
}
}

View File

@ -1,17 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Remotely.Desktop.XPlat.Native.Linux
{
public class LibXtst
{
[DllImport("libXtst")]
public static extern bool XTestQueryExtension(IntPtr display, out int event_base, out int error_base, out int major_version, out int minor_version);
[DllImport("libXtst")]
public static extern void XTestFakeKeyEvent(IntPtr display, uint keycode, bool is_press, ulong delay);
[DllImport("libXtst")]
public static extern void XTestFakeButtonEvent(IntPtr display, uint button, bool is_press, ulong delay);
[DllImport("libXtst")]
public static extern void XTestFakeMotionEvent(IntPtr display, int screen_number, int x, int y, ulong delay);
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Native.Linux
{
public class Libc
{
[DllImport("libc", SetLastError = true)]
public static extern uint geteuid();
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright © 2000 Compaq Computer Corporation, Inc.
* Copyright © 2002 Hewlett-Packard Company, Inc.
* Copyright © 2006 Intel Corporation
* Copyright © 2008 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting documentation, and
* that the name of the copyright holders not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no representations
* about the suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE.
*
* Author: Jim Gettys, HP Labs, Hewlett-Packard, Inc.
* Keith Packard, Intel Corporation
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Native.Linux
{
public static class LibXrandr
{
[StructLayout(LayoutKind.Sequential)]
public struct XRRMonitorInfo
{
// Atom
public IntPtr name;
public bool primary;
public bool automatic;
public int noutput;
public int x;
public int y;
public int width;
public int height;
public int mwidth;
public int mheight;
// RROutput*
public IntPtr outputs;
}
[DllImport("libXrandr")]
public static extern IntPtr XRRGetMonitors(IntPtr display, IntPtr window, bool get_active, out int monitors);
[DllImport("libXrandr")]
public static extern void XRRFreeMonitors(IntPtr monitors);
[DllImport("libXrandr")]
public static extern IntPtr XRRAllocateMonitor(IntPtr display, int output);
}
}

View File

@ -1,56 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using Remotely.Desktop.Core;
using Remotely.Shared.Utilities;
using System;
using System.Threading;
namespace Remotely.Desktop.XPlat
{
class Program
{
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
public static Conductor Conductor { get; private set; }
public static IServiceProvider Services => ServiceContainer.Instance;
public static CancellationTokenSource AppCts { get; private set; }
public static CancellationToken AppCancellationToken { get; private set; }
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
BuildAvaloniaApp().Start(AppMain, args);
}
catch (Exception ex)
{
Logger.Write(ex);
throw;
}
}
private static void AppMain(Application app, string[] args)
{
AppCts = new CancellationTokenSource();
AppCancellationToken = AppCts.Token;
app.Run(AppCancellationToken);
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Write((Exception)e.ExceptionObject);
}
}
}

View File

@ -1,8 +0,0 @@
{
"profiles": {
"Desktop.Linux": {
"commandName": "Project",
"commandLineArgs": "-orgid \"e42c65b4-deb2-4dca-b4fb-ec8dcbe8f84c\" -host \"https://localhost:5001\""
}
}
}

View File

@ -1,17 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using System;
namespace Remotely.Desktop.XPlat.Services
{
public class AudioCapturerLinux : IAudioCapturer
{
#pragma warning disable
public event EventHandler<byte[]> AudioSampleReady;
#pragma warning restore
public void ToggleAudio(bool toggleOn)
{
// Not implemented.
}
}
}

View File

@ -1,62 +0,0 @@
using Avalonia.Controls;
using Avalonia.Threading;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.XPlat.Controls;
using Remotely.Desktop.XPlat.ViewModels;
using Remotely.Desktop.XPlat.Views;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Services
{
public class ChatUiServiceLinux : IChatUiService
{
private ChatWindowViewModel ChatViewModel { get; set; }
public event EventHandler ChatWindowClosed;
public void ReceiveChat(ChatMessage chatMessage)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
if (chatMessage.Disconnected)
{
await MessageBox.Show("The partner has disconnected.", "Partner Disconnected", MessageBoxType.OK);
Environment.Exit(0);
return;
}
if (ChatViewModel != null)
{
ChatViewModel.SenderName = chatMessage.SenderName;
ChatViewModel.ChatMessages.Add(chatMessage);
}
});
}
public void ShowChatWindow(string organizationName, StreamWriter writer)
{
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);
});
}
private void ChatWindow_Closing(object sender, CancelEventArgs e)
{
ChatWindowClosed?.Invoke(this, null);
}
}
}

View File

@ -1,76 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Shared.Utilities;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.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(async () => await WatchClipboard(cancelTokenSource.Token));
}
}
public async Task SetText(string clipboardText)
{
try
{
if (string.IsNullOrWhiteSpace(clipboardText))
{
await App.Current.Clipboard.ClearAsync();
}
else
{
await App.Current.Clipboard.SetTextAsync(clipboardText);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void StopWatching()
{
cancelTokenSource?.Cancel();
cancelTokenSource?.Dispose();
}
private async Task WatchClipboard(CancellationToken cancelToken)
{
while (!cancelToken.IsCancellationRequested &&
!Environment.HasShutdownStarted)
{
try
{
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,48 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using System;
using System.IO;
using System.Text.Json;
namespace Remotely.Desktop.XPlat.Services
{
public class ConfigServiceLinux : IConfigService
{
private static string ConfigFile => Path.Combine(ConfigFolder, "Config.json");
private static string ConfigFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "remotely.json");
public DesktopAppConfig GetConfig()
{
var config = new DesktopAppConfig();
if (string.IsNullOrWhiteSpace(config.Host) &&
File.Exists(ConfigFile))
{
try
{
config = JsonSerializer.Deserialize<DesktopAppConfig>(File.ReadAllText(ConfigFile));
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
return config;
}
public void Save(DesktopAppConfig config)
{
try
{
Directory.CreateDirectory(ConfigFolder);
File.WriteAllText(ConfigFile, JsonSerializer.Serialize(config));
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}

View File

@ -1,16 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Shared.Models;
using System;
using System.Drawing;
namespace Remotely.Desktop.XPlat.Services
{
public class CursorIconWatcherLinux : ICursorIconWatcher
{
#pragma warning disable
public event EventHandler<CursorInfo> OnChange;
#pragma warning restore
public CursorInfo GetCurrentCursor() => new(null, Point.Empty, "default");
}
}

View File

@ -1,35 +0,0 @@
using System;
using System.Windows.Input;
namespace Remotely.Desktop.XPlat.Services
{
public class Executor : ICommand
{
public Executor(Action<object> executeAction, Predicate<object> isExecutable = null)
{
ExecuteAction = executeAction;
IsExecutable = isExecutable;
}
#pragma warning disable
public event EventHandler CanExecuteChanged;
#pragma warning restore
private Action<object> ExecuteAction { get; set; }
private Predicate<object> IsExecutable { get; set; }
public bool CanExecute(object parameter)
{
if (IsExecutable == null)
{
return true;
}
return IsExecutable.Invoke(parameter);
}
public void Execute(object parameter)
{
ExecuteAction.Invoke(parameter);
}
}
}

View File

@ -1,147 +0,0 @@
using Avalonia.Threading;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.Core.ViewModels;
using Remotely.Desktop.XPlat.Controls;
using Remotely.Desktop.XPlat.ViewModels;
using Remotely.Desktop.XPlat.Views;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Services
{
public class FileTransferServiceLinux : IFileTransferService
{
private static readonly SemaphoreSlim _writeLock = new(1,1);
private static readonly ConcurrentDictionary<string, FileStream> _partialTransfers =
new();
private static readonly ConcurrentDictionary<string, FileTransferWindow> _fileTransferWindows =
new();
private static volatile bool _messageBoxPending;
public string GetBaseDirectory()
{
var desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
if (Directory.Exists(desktopDir))
{
return desktopDir;
}
return Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "Remotely_Shared")).FullName;
}
public void OpenFileTransferWindow(Viewer viewer)
{
Dispatcher.UIThread.Post(() =>
{
if (_fileTransferWindows.TryGetValue(viewer.ViewerConnectionID, out var window))
{
window.Activate();
}
else
{
window = new FileTransferWindow
{
DataContext = new FileTransferWindowViewModel(viewer, this)
};
window.Closed += (sender, arg) =>
{
_fileTransferWindows.Remove(viewer.ViewerConnectionID, out _);
};
_fileTransferWindows.AddOrUpdate(viewer.ViewerConnectionID, window, (k, v) => window);
window.Show();
}
});
}
public async Task ReceiveFile(byte[] buffer, string fileName, string messageId, bool endOfFile, bool startOfFile)
{
try
{
await _writeLock.WaitAsync();
var baseDir = GetBaseDirectory();
if (startOfFile)
{
var filePath = Path.Combine(baseDir, fileName);
if (File.Exists(filePath))
{
var count = 0;
var ext = Path.GetExtension(fileName);
var fileWithoutExt = Path.GetFileNameWithoutExtension(fileName);
while (File.Exists(filePath))
{
filePath = Path.Combine(baseDir, $"{fileWithoutExt}-{count}{ext}");
count++;
}
}
File.Create(filePath).Close();
var fs = new FileStream(filePath, FileMode.OpenOrCreate);
_partialTransfers.AddOrUpdate(messageId, fs, (k, v) => fs);
}
var fileStream = _partialTransfers[messageId];
if (buffer?.Length > 0)
{
await fileStream.WriteAsync(buffer, 0, buffer.Length);
}
if (endOfFile)
{
fileStream.Close();
_partialTransfers.Remove(messageId, out _);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
finally
{
_writeLock.Release();
if (endOfFile)
{
await Task.Run(ShowTransferComplete);
}
}
}
public async Task UploadFile(FileUpload fileUpload, Viewer viewer, CancellationToken cancelToken, Action<double> progressUpdateCallback)
{
try
{
await viewer.SendFile(fileUpload, cancelToken, progressUpdateCallback);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private async Task ShowTransferComplete()
{
// Prevent multiple dialogs from popping up.
if (!_messageBoxPending)
{
_messageBoxPending = true;
await MessageBox.Show($"File tranfer complete. Files saved to directory:\n\n{GetBaseDirectory()}",
"Tranfer Complete",
MessageBoxType.OK);
_messageBoxPending = false;
}
}
}
}

View File

@ -1,255 +0,0 @@
using Remotely.Desktop.Core.Enums;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Desktop.XPlat.Native.Linux;
using Remotely.Shared.Utilities;
using System;
namespace Remotely.Desktop.XPlat.Services
{
public class KeyboardMouseInputLinux : IKeyboardMouseInput
{
private IntPtr Display { get; set; }
public void Init()
{
// Nothing to do here. The Windows implementation needs to start
// a processing queue to keep all input simulation on the same
// thread. Linux doesn't.
}
public void SendKeyDown(string key)
{
try
{
InitDisplay();
key = ConvertJavaScriptKeyToX11Key(key);
var keySim = LibX11.XStringToKeysym(key);
if (keySim == IntPtr.Zero)
{
Logger.Write($"Key not mapped: {key}");
return;
}
var keyCode = LibX11.XKeysymToKeycode(Display, keySim);
LibXtst.XTestFakeKeyEvent(Display, keyCode, true, 0);
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendKeyUp(string key)
{
try
{
InitDisplay();
key = ConvertJavaScriptKeyToX11Key(key);
var keySim = LibX11.XStringToKeysym(key);
if (keySim == IntPtr.Zero)
{
Logger.Write($"Key not mapped: {key}");
return;
}
var keyCode = LibX11.XKeysymToKeycode(Display, keySim);
LibXtst.XTestFakeKeyEvent(Display, keyCode, false, 0);
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendMouseButtonAction(int button, ButtonAction buttonAction, double percentX, double percentY, Viewer viewer)
{
try
{
var isPressed = buttonAction == ButtonAction.Down;
// Browser buttons start at 0. XTest starts at 1.
var mouseButton = (uint)(button + 1);
InitDisplay();
SendMouseMove(percentX, percentY, viewer);
LibXtst.XTestFakeButtonEvent(Display, mouseButton, isPressed, 0);
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendMouseMove(double percentX, double percentY, Viewer viewer)
{
try
{
InitDisplay();
var screenBounds = viewer.Capturer.CurrentScreenBounds;
LibXtst.XTestFakeMotionEvent(Display,
LibX11.XDefaultScreen(Display),
screenBounds.X + (int)(screenBounds.Width * percentX),
screenBounds.Y + (int)(screenBounds.Height * percentY),
0);
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendMouseWheel(int deltaY)
{
try
{
InitDisplay();
if (deltaY > 0)
{
LibXtst.XTestFakeButtonEvent(Display, 4, true, 0);
LibXtst.XTestFakeButtonEvent(Display, 4, false, 0);
}
else
{
LibXtst.XTestFakeButtonEvent(Display, 5, true, 0);
LibXtst.XTestFakeButtonEvent(Display, 5, false, 0);
}
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendRightMouseDown(double percentX, double percentY, Viewer viewer)
{
try
{
InitDisplay();
SendMouseMove(percentX, percentY, viewer);
LibXtst.XTestFakeButtonEvent(Display, 3, true, 0);
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendRightMouseUp(double percentX, double percentY, Viewer viewer)
{
try
{
InitDisplay();
SendMouseMove(percentX, percentY, viewer);
LibXtst.XTestFakeButtonEvent(Display, 3, false, 0);
LibX11.XSync(Display, false);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SendText(string transferText)
{
foreach (var key in transferText)
{
SendKeyDown(key.ToString());
SendKeyUp(key.ToString());
}
}
public void SetKeyStatesUp()
{
// Not implemented.
}
public void ToggleBlockInput(bool toggleOn)
{
// Not implemented.
}
private string ConvertJavaScriptKeyToX11Key(string key)
{
string keySym = key switch
{
"ArrowDown" => "Down",
"ArrowUp" => "Up",
"ArrowLeft" => "Left",
"ArrowRight" => "Right",
"Enter" => "Return",
"Esc" => "Escape",
"Alt" => "Alt_L",
"Control" => "Control_L",
"Shift" => "Shift_L",
"PAUSE" => "Pause",
"BREAK" => "Break",
"Backspace" => "BackSpace",
"Tab" => "Tab",
"CapsLock" => "Caps_Lock",
"Delete" => "Delete",
"PageUp" => "Page_Up",
"PageDown" => "Page_Down",
"NumLock" => "Num_Lock",
"ScrollLock" => "Scroll_Lock",
"ContextMenu" => "Menu",
" " => "space",
"!" => "exclam",
"\"" => "quotedbl",
"#" => "numbersign",
"$" => "dollar",
"%" => "percent",
"&" => "ampersand",
"'" => "apostrophe",
"(" => "parenleft",
")" => "parenright",
"*" => "asterisk",
"+" => "plus",
"," => "comma",
"-" => "minus",
"." => "period",
"/" => "slash",
":" => "colon",
";" => "semicolon",
"<" => "less",
"=" => "equal",
">" => "greater",
"?" => "question",
"@" => "at",
"[" => "bracketleft",
"\\" => "backslash",
"]" => "bracketright",
"_" => "underscore",
"`" => "grave",
"{" => "braceleft",
"|" => "bar",
"}" => "braceright",
"~" => "asciitilde",
_ => key,
};
return keySym;
}
private void InitDisplay()
{
try
{
if (Display == IntPtr.Zero)
{
Display = LibX11.XOpenDisplay(null);
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}

View File

@ -1,46 +0,0 @@
using Avalonia.Threading;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.XPlat.ViewModels;
using Remotely.Desktop.XPlat.Views;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Services
{
public class RemoteControlAccessServiceLinux : IRemoteControlAccessService
{
public async Task<bool> PromptForAccess(string requesterName, string organizationName)
{
return await Dispatcher.UIThread.InvokeAsync(async () =>
{
var promptWindow = new PromptForAccessWindow();
var viewModel = promptWindow.DataContext as PromptForAccessWindowViewModel;
if (!string.IsNullOrWhiteSpace(requesterName))
{
viewModel.RequesterName = requesterName;
}
if (!string.IsNullOrWhiteSpace(organizationName))
{
viewModel.OrganizationName = organizationName;
}
var isOpen = true;
promptWindow.Closed += (sender, arg) =>
{
isOpen = false;
};
promptWindow.Show();
while (isOpen)
{
await Task.Delay(100);
}
return viewModel.PromptResult;
});
}
}
}

View File

@ -1,231 +0,0 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Utilities;
using Remotely.Desktop.XPlat.Native.Linux;
using Remotely.Shared;
using Remotely.Shared.Utilities;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
namespace Remotely.Desktop.XPlat.Services
{
public class ScreenCapturerLinux : IScreenCapturer
{
private readonly object _screenBoundsLock = new();
private readonly Dictionary<string, LibXrandr.XRRMonitorInfo> _x11Screens = new();
private SKBitmap _currentFrame;
private SKBitmap _previousFrame;
public ScreenCapturerLinux()
{
Display = LibX11.XOpenDisplay(null);
Init();
}
public event EventHandler<Rectangle> ScreenChanged;
public bool CaptureFullscreen { get; set; } = true;
public Rectangle CurrentScreenBounds { get; private set; }
public IntPtr Display { get; private set; }
public string SelectedScreen { get; private set; }
public void Dispose()
{
LibX11.XCloseDisplay(Display);
GC.SuppressFinalize(this);
}
public IEnumerable<string> GetDisplayNames()
{
return _x11Screens.Keys.Select(x => x.ToString());
}
public SKRect GetFrameDiffArea()
{
return ImageUtils.GetDiffArea(_currentFrame, _previousFrame, CaptureFullscreen);
}
public Result<SKBitmap> GetImageDiff()
{
return ImageUtils.GetImageDiff(_currentFrame, _previousFrame);
}
public Result<SKBitmap> GetNextFrame()
{
lock (_screenBoundsLock)
{
try
{
if (_currentFrame != null)
{
_previousFrame?.Dispose();
try
{
_previousFrame = _currentFrame;
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
_currentFrame = GetX11Capture();
return Result.Ok(_currentFrame);
}
catch (Exception ex)
{
Logger.Write(ex);
Init();
return Result.Fail<SKBitmap>(ex);
}
}
}
public int GetScreenCount()
{
return _x11Screens.Count;
}
public int GetSelectedScreenIndex()
{
return int.Parse(SelectedScreen ?? "0");
}
public Rectangle GetVirtualScreenBounds()
{
int width = 0;
for (var i = 0; i < GetScreenCount(); i++)
{
width += LibX11.XWidthOfScreen(LibX11.XScreenOfDisplay(Display, i));
}
int height = 0;
for (var i = 0; i < GetScreenCount(); i++)
{
height += LibX11.XHeightOfScreen(LibX11.XScreenOfDisplay(Display, i));
}
return new Rectangle(0, 0, width, height);
}
public void Init()
{
try
{
CaptureFullscreen = true;
_x11Screens.Clear();
var monitorsPtr = LibXrandr.XRRGetMonitors(Display, LibX11.XDefaultRootWindow(Display), true, out var monitorCount);
var monitorInfoSize = Marshal.SizeOf<LibXrandr.XRRMonitorInfo>();
for (var i = 0; i < monitorCount; i++)
{
var monitorPtr = new IntPtr(monitorsPtr.ToInt64() + i * monitorInfoSize);
var monitorInfo = Marshal.PtrToStructure<LibXrandr.XRRMonitorInfo>(monitorPtr);
Logger.Write($"Found monitor: " +
$"{monitorInfo.width}," +
$"{monitorInfo.height}," +
$"{monitorInfo.x}, " +
$"{monitorInfo.y}");
_x11Screens.Add(i.ToString(), monitorInfo);
}
LibXrandr.XRRFreeMonitors(monitorsPtr);
if (string.IsNullOrWhiteSpace(SelectedScreen) ||
!_x11Screens.ContainsKey(SelectedScreen))
{
SelectedScreen = _x11Screens.Keys.First();
RefreshCurrentScreenBounds();
}
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
public void SetSelectedScreen(string displayName)
{
lock (_screenBoundsLock)
{
try
{
Logger.Write($"Setting display to {displayName}.");
if (displayName == SelectedScreen)
{
return;
}
if (_x11Screens.ContainsKey(displayName))
{
SelectedScreen = displayName;
}
else
{
SelectedScreen = _x11Screens.Keys.First();
}
RefreshCurrentScreenBounds();
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
private SKBitmap GetX11Capture()
{
var currentFrame = new SKBitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height);
var window = LibX11.XDefaultRootWindow(Display);
var imagePointer = LibX11.XGetImage(Display,
window,
CurrentScreenBounds.X,
CurrentScreenBounds.Y,
CurrentScreenBounds.Width,
CurrentScreenBounds.Height,
~0,
2);
var image = Marshal.PtrToStructure<LibX11.XImage>(imagePointer);
var pixels = currentFrame.GetPixels();
unsafe
{
byte* scan1 = (byte*)pixels.ToPointer();
byte* scan2 = (byte*)image.data.ToPointer();
var bytesPerPixel = currentFrame.BytesPerPixel;
var totalSize = currentFrame.Height * currentFrame.Width * bytesPerPixel;
for (int counter = 0; counter < totalSize - bytesPerPixel; counter++)
{
scan1[counter] = scan2[counter];
}
}
Marshal.DestroyStructure<LibX11.XImage>(imagePointer);
LibX11.XDestroyImage(imagePointer);
return currentFrame;
}
private void RefreshCurrentScreenBounds()
{
var screen = _x11Screens[SelectedScreen];
Logger.Write($"Setting new screen bounds: " +
$"{screen.width}," +
$"{screen.height}," +
$"{screen.x}, " +
$"{screen.y}");
CurrentScreenBounds = new Rectangle(screen.x, screen.y, screen.width, screen.height);
CaptureFullscreen = true;
ScreenChanged?.Invoke(this, CurrentScreenBounds);
}
}
}

View File

@ -1,19 +0,0 @@
using Avalonia.Controls;
using Avalonia.Threading;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.XPlat.Views;
namespace Remotely.Desktop.XPlat.Services
{
public class SessionIndicatorLinux : ISessionIndicator
{
public void Show()
{
Dispatcher.UIThread.Post(() =>
{
var indicatorWindow = new SessionIndicatorWindow();
indicatorWindow.Show();
});
}
}
}

View File

@ -1,22 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Remotely.Desktop.Core;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Core.Services;
using Remotely.Shared.Utilities;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Remotely.Desktop.XPlat.Services
{
public class ShutdownServiceLinux : IShutdownService
{
public async Task Shutdown()
{
Logger.Debug($"Exiting process ID {Environment.ProcessId}.");
var casterSocket = ServiceContainer.Instance.GetRequiredService<ICasterSocket>();
await casterSocket.DisconnectAllViewers();
Environment.Exit(0);
}
}
}

View File

@ -1,32 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Remotely.Desktop.XPlat.ViewModels;
using System;
namespace Remotely.Desktop.XPlat
{
public class ViewLocator : IDataTemplate
{
public bool SupportsRecycling => false;
public IControl Build(object data)
{
var name = data.GetType().FullName.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type);
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
public bool Match(object data)
{
return data is ReactiveViewModel;
}
}
}

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