From 1ea295f8cbc007d68b012a97a34cbb687358dc3c Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Thu, 27 Jun 2019 22:04:05 -0700 Subject: [PATCH] Clipboard transfer. Unattended remote session survives Windows signout. Removed HTML-based on-screen keyboard and instead invoke native mobile keyboard. Ubuntu server install script detects version for grabbing .NET Core runtime. --- Agent/Services/DeviceSocket.cs | 36 ++- Agent/Services/Updater.cs | 7 +- .../ViewModels/MainWindowViewModel.cs | 33 ++- Desktop.Win/ViewModels/MainWindowViewModel.cs | 80 ++++--- README.md | 6 +- ScreenCast.Core/Capture/ScreenCaster.cs | 2 +- ScreenCast.Core/Conductor.cs | 9 + ScreenCast.Core/Sockets/CasterSocket.cs | 126 ++++++----- ScreenCast.Linux/Program.cs | 26 +++ ScreenCast.Win/Program.cs | 132 ++++++----- Server/CurrentVersion.txt | 2 +- Server/Pages/RemoteControl.cshtml | 13 +- Server/Pages/_OSK.cshtml | 74 ------- Server/Services/DeviceSocketHub.cs | 48 ++-- Server/Services/RCBrowserSocketHub.cs | 4 + Server/Services/RCDeviceSocketHub.cs | 195 ++++++++-------- Server/wwwroot/Downloads/Install-Linux-x64.sh | 1 + Server/wwwroot/css/osk.css | 58 ----- Server/wwwroot/css/remote-control.css | 6 +- Server/wwwroot/scripts/BrowserSockets.js | 2 +- Server/wwwroot/scripts/BrowserSockets.js.map | 2 +- Server/wwwroot/scripts/BrowserSockets.ts | 4 +- Server/wwwroot/scripts/RemoteControl/OSK.js | 35 --- .../wwwroot/scripts/RemoteControl/OSK.js.map | 1 - Server/wwwroot/scripts/RemoteControl/OSK.ts | 35 --- .../scripts/RemoteControl/RCBrowserSockets.js | 4 + .../RemoteControl/RCBrowserSockets.js.map | 2 +- .../scripts/RemoteControl/RCBrowserSockets.ts | 3 + Server/wwwroot/scripts/RemoteControl/UI.js | 189 ++++++++++------ .../wwwroot/scripts/RemoteControl/UI.js.map | 2 +- Server/wwwroot/scripts/RemoteControl/UI.ts | 209 +++++++++++------- Utilities/Remotely_Server_Install.sh | 8 +- 32 files changed, 701 insertions(+), 653 deletions(-) delete mode 100644 Server/Pages/_OSK.cshtml delete mode 100644 Server/wwwroot/css/osk.css delete mode 100644 Server/wwwroot/scripts/RemoteControl/OSK.js delete mode 100644 Server/wwwroot/scripts/RemoteControl/OSK.js.map delete mode 100644 Server/wwwroot/scripts/RemoteControl/OSK.ts diff --git a/Agent/Services/DeviceSocket.cs b/Agent/Services/DeviceSocket.cs index d3aa03e8..426b06b3 100644 --- a/Agent/Services/DeviceSocket.cs +++ b/Agent/Services/DeviceSocket.cs @@ -304,15 +304,43 @@ namespace Remotely.Agent.Services var rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScreenCast", OSUtils.ScreenCastExecutableFileName); // Start ScreenCast. if (OSUtils.IsWindows) - { - + { + Logger.Write("Restarting screen caster."); if (Program.IsDebug) { - Process.Start(rcBinaryPath, $"-mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}"); + var proc = Process.Start(rcBinaryPath, $"-mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}"); + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.Elapsed.TotalSeconds < 10) + { + await Task.Delay(250); + if (Process.GetProcessesByName(Path.GetFileNameWithoutExtension(rcBinaryPath))?.Where(x => x.Id == proc.Id)?.Count() > 0 != true) + { + Logger.Write("Restarting screen caster after failed relaunch."); + } + } } else { - var result = Win32Interop.OpenInteractiveProcess(rcBinaryPath + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}", "default", true, out _); + var result = Win32Interop.OpenInteractiveProcess(rcBinaryPath + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}", "default", true, out var procInfo); + + if (result) + { + // This relaunch might have been prompted by a user logging out, which would close + // the screencaster process. In that scenario, the relaunched process can get closed again + // while the Windows sign-out process is still occurring. We'll wait a bit to make sure the + // relaunched process is still running. If not, launch again. + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.Elapsed.TotalSeconds < 10) + { + await Task.Delay(250); + if (Process.GetProcessesByName(Path.GetFileNameWithoutExtension(rcBinaryPath))?.Where(x=>x.Id == procInfo.dwProcessId)?.Count() > 0 != true) + { + Logger.Write("Restarting screen caster after failed relaunch."); + result = Win32Interop.OpenInteractiveProcess(rcBinaryPath + $" -mode Unattended -requester {requesterID} -serviceid {serviceID} -host {Utilities.GetConnectionInfo().Host} -relaunch true -desktop default -viewers {String.Join(",", viewerIDs)}", "default", true, out procInfo); + } + } + } + if (!result) { Logger.Write("Failed to relaunch screen caster."); diff --git a/Agent/Services/Updater.cs b/Agent/Services/Updater.cs index 1aaa076b..d014a1f8 100644 --- a/Agent/Services/Updater.cs +++ b/Agent/Services/Updater.cs @@ -14,7 +14,6 @@ namespace Remotely.Agent.Services { public class Updater { - internal static void CheckForCoreUpdates() { try @@ -88,8 +87,10 @@ namespace Remotely.Agent.Services foreach (var proc in Process.GetProcesses().Where(x => - x.ProcessName.Contains("Remotely_Agent") && - x.Id != Process.GetCurrentProcess().Id)) + (x.ProcessName.Contains("Remotely_Agent") || + x.ProcessName.Contains("Remotely_ScreenCast") || + x.ProcessName.Contains("Remotely_Desktop")) + && x.Id != Process.GetCurrentProcess().Id)) { proc.Kill(); } diff --git a/Desktop.Unix/ViewModels/MainWindowViewModel.cs b/Desktop.Unix/ViewModels/MainWindowViewModel.cs index 6831da01..77c4d9f2 100644 --- a/Desktop.Unix/ViewModels/MainWindowViewModel.cs +++ b/Desktop.Unix/ViewModels/MainWindowViewModel.cs @@ -15,7 +15,9 @@ using Remotely.Shared.Services; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Drawing; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -37,10 +39,10 @@ namespace Remotely.Desktop.Unix.ViewModels Conductor.SessionIDChanged += SessionIDChanged; Conductor.ViewerRemoved += ViewerRemoved; Conductor.ViewerAdded += ViewerAdded; + Conductor.ClipboardTransferred += Conductor_ClipboardTransferred; Conductor.ScreenCastRequested += ScreenCastRequested; } - public static MainWindowViewModel Current { get; private set; } public ICommand ChangeServerCommand => new Executor(async (param) => @@ -70,16 +72,19 @@ namespace Remotely.Desktop.Unix.ViewModels } IsCopyMessageVisible = false; }); + public double CopyMessageOpacity { get => copyMessageOpacity; set => this.RaiseAndSetIfChanged(ref copyMessageOpacity, value); } + public string Host { get => host; set => this.RaiseAndSetIfChanged(ref host, value); } + public bool IsCopyMessageVisible { get => isCopyMessageVisible; @@ -106,6 +111,7 @@ namespace Remotely.Desktop.Unix.ViewModels get => sessionID; set => this.RaiseAndSetIfChanged(ref sessionID, value); } + public ObservableCollection Viewers { get; } = new ObservableCollection(); public async Task Init() @@ -139,7 +145,7 @@ namespace Remotely.Desktop.Unix.ViewModels { return; } - + await Conductor.CasterSocket.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName); await Conductor.CasterSocket.GetSessionID(); } @@ -167,8 +173,29 @@ namespace Remotely.Desktop.Unix.ViewModels } } + private void Conductor_ClipboardTransferred(object sender, string transferredText) + { + var tempPath = Path.GetTempFileName(); + File.WriteAllText(tempPath, transferredText); + try + { + var psi = new ProcessStartInfo("bash", $"-c \"cat {tempPath} | xclip -i -selection clipboard\"") + { + WindowStyle = ProcessWindowStyle.Hidden + }; + var proc = Process.Start(psi); + proc.WaitForExit(); - + } + catch (Exception ex) + { + Logger.Write(ex); + } + finally + { + File.Delete(tempPath); + } + } private void ScreenCastRequested(object sender, ScreenCastRequest screenCastRequest) { Dispatcher.UIThread.InvokeAsync(async () => diff --git a/Desktop.Win/ViewModels/MainWindowViewModel.cs b/Desktop.Win/ViewModels/MainWindowViewModel.cs index 0cab8b8c..c2024cb2 100644 --- a/Desktop.Win/ViewModels/MainWindowViewModel.cs +++ b/Desktop.Win/ViewModels/MainWindowViewModel.cs @@ -40,26 +40,16 @@ namespace Remotely.Desktop.Win.ViewModels Conductor.ViewerAdded += ViewerAdded; Conductor.ScreenCastRequested += ScreenCastRequested; Conductor.AudioToggled += AudioToggled; + Conductor.ClipboardTransferred += Conductor_ClipboardTransferred; CursorIconWatcher = new CursorIconWatcher(Conductor); CursorIconWatcher.OnChange += CursorIconWatcher_OnChange; AudioCapturer = new AudioCapturer(Conductor); } - private void AudioToggled(object sender, bool toggleOn) - { - if (toggleOn) - { - AudioCapturer.Start(); - } - else - { - AudioCapturer.Stop(); - } - } - public static MainWindowViewModel Current { get; private set; } public AudioCapturer AudioCapturer { get; private set; } + public ICommand ChangeServerCommand { get @@ -73,7 +63,9 @@ namespace Remotely.Desktop.Win.ViewModels } public Conductor Conductor { get; } + public CursorIconWatcher CursorIconWatcher { get; private set; } + public string Host { get => host; @@ -85,6 +77,27 @@ namespace Remotely.Desktop.Win.ViewModels } 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)) + { + viewer.DisconnectRequested = true; + await Conductor.CasterSocket.SendViewerRemoved(viewer.ViewerConnectionID); + } + }, + (param) => + { + return (param as IList)?.Count > 0; + }); + } + + } + public ICommand RestartAsAdminCommand { get @@ -100,7 +113,8 @@ namespace Remotely.Desktop.Win.ViewModels } // Exception can be thrown if UAC is dialog is cancelled. catch { } - }, (param) => { + }, (param) => + { return !IsAdministrator; }); } @@ -117,6 +131,12 @@ namespace Remotely.Desktop.Win.ViewModels } public ObservableCollection Viewers { get; } = new ObservableCollection(); + + public void CopyLink() + { + Clipboard.SetText($"{Host}/RemoteControl?sessionID={SessionID.Replace(" ", "")}"); + } + public async Task Init() { SessionID = "Retrieving..."; @@ -146,6 +166,7 @@ namespace Remotely.Desktop.Win.ViewModels await Conductor.CasterSocket.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName); await Conductor.CasterSocket.GetSessionID(); } + public void PromptForHostName() { var prompt = new HostNamePrompt(); @@ -169,31 +190,24 @@ namespace Remotely.Desktop.Win.ViewModels } } - public void CopyLink() + private void AudioToggled(object sender, bool toggleOn) { - Clipboard.SetText($"{Host}/RemoteControl?sessionID={SessionID.Replace(" ", "")}"); - } - - public ICommand RemoveViewersCommand - { - get + if (toggleOn) { - return new Executor(async (param) => - { - foreach (Viewer viewer in (param as IList)) - { - viewer.DisconnectRequested = true; - await Conductor.CasterSocket.SendViewerRemoved(viewer.ViewerConnectionID); - } - }, - (param) => - { - return (param as IList)?.Count > 0; - }); + AudioCapturer.Start(); + } + else + { + AudioCapturer.Stop(); } - } + private void Conductor_ClipboardTransferred(object sender, string transferredText) + { + Application.Current.Dispatcher.Invoke(() => { + Clipboard.SetText(transferredText); + }); + } private async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor) { if (Conductor?.CasterSocket != null) diff --git a/README.md b/README.md index 448e708e..7279ea65 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,10 @@ The following steps will configure your Windows 10 machine for building the Remo * Documentation for hosting in IIS can be found here: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/index?view=aspnetcore-2.2 ## Hosting a Server (Ubuntu) -* Currently, only Ubuntu 18.04 is tested. The Linux server package will likely work with other distros after some alterations to the setup script. +* Ubuntu 18.04 and 19.04 have been tested. The Linux server package might work with other distros after some alterations to the setup script. * Run Remotely_Server_Setup.sh (with sudo), which is in the [Utilities folder in source control](https://raw.githubusercontent.com/Jay-Rad/Remotely/master/Utilities/Remotely_Server_Install.sh). * "App root" will be the directory in which the Remotely server files are placed (typically /var/www/remotely). - * This script is only for Ubuntu 18.04. + * This script is only for Ubuntu 18.04 and 19.04. * The script installs the .NET Core runtime, as well as other dependencies. * Certbot is used in this script and will install an SSL certificate for your site. Your server needs to have a public domain name that is accessible from the internet for this to work. * More information: https://letsencrypt.org/, https://certbot.eff.org/ @@ -67,7 +67,7 @@ The following steps will configure your Windows 10 machine for building the Remo * Windows: Only the latest version of Windows 10 is tested. * Requires .NET Framework 4.7.2. * Windows 2016/2019 should work as well, but isn't tested regularly. -* Linux: Only Lubuntu 18.10 is tested. +* Linux: Only Ubuntu 18.04+ is tested. * Your account must be set to auto login for unattended remote control to work. ## Session Recording diff --git a/ScreenCast.Core/Capture/ScreenCaster.cs b/ScreenCast.Core/Capture/ScreenCaster.cs index e730c7bc..76ec5070 100644 --- a/ScreenCast.Core/Capture/ScreenCaster.cs +++ b/ScreenCast.Core/Capture/ScreenCaster.cs @@ -76,7 +76,7 @@ namespace Remotely.ScreenCast.Core.Capture // continue; //} - while (viewer.PendingFrames > 10) + while (viewer.PendingFrames > 10 || conductor.IsSwitchingDesktops) { await Task.Delay(1); } diff --git a/ScreenCast.Core/Conductor.cs b/ScreenCast.Core/Conductor.cs index 255661d5..5d23f20c 100644 --- a/ScreenCast.Core/Conductor.cs +++ b/ScreenCast.Core/Conductor.cs @@ -19,6 +19,8 @@ namespace Remotely.ScreenCast.Core { public event EventHandler AudioToggled; + public event EventHandler ClipboardTransferred; + public event EventHandler ScreenCastInitiated; public event EventHandler ScreenCastRequested; @@ -37,6 +39,7 @@ namespace Remotely.ScreenCast.Core public string RequesterID { get; private set; } public string ServiceID { get; private set; } public ConcurrentDictionary Viewers { get; } = new ConcurrentDictionary(); + public bool IsSwitchingDesktops { get; set; } public Task Connect() { @@ -108,6 +111,12 @@ namespace Remotely.ScreenCast.Core { ScreenCastInitiated?.Invoke(null, viewerIdAndRequesterName); } + + internal void InvokeClipboardTransfer(string transferText) + { + ClipboardTransferred?.Invoke(null, transferText); + } + internal void InvokeScreenCastRequested(ScreenCastRequest viewerIdAndRequesterName) { ScreenCastRequested?.Invoke(null, viewerIdAndRequesterName); diff --git a/ScreenCast.Core/Sockets/CasterSocket.cs b/ScreenCast.Core/Sockets/CasterSocket.cs index c1f43c78..bed3a674 100644 --- a/ScreenCast.Core/Sockets/CasterSocket.cs +++ b/ScreenCast.Core/Sockets/CasterSocket.cs @@ -26,9 +26,63 @@ namespace Remotely.ScreenCast.Core.Sockets ApplyConnectionHandlers(); } - private HubConnection Connection { get; } public Conductor Conductor { get; } public IKeyboardMouseInput KeyboardMouseInput { get; } + private HubConnection Connection { get; } + public async Task GetSessionID() + { + await Connection.SendAsync("GetSessionID"); + } + + public async Task NotifyRequesterUnattendedReady(string requesterID) + { + await Connection.SendAsync("NotifyRequesterUnattendedReady", requesterID); + } + + public async Task NotifyViewersRelaunchedScreenCasterReady(string[] viewerIDs) + { + await Connection.SendAsync("NotifyViewersRelaunchedScreenCasterReady", viewerIDs); + } + + public async Task SendAudioSample(byte[] buffer, List viewerIDs) + { + await Connection.SendAsync("SendAudioSample", buffer, viewerIDs); + } + + public async Task SendConnectionFailedToViewers(List viewerIDs) + { + await Connection.SendAsync("SendConnectionFailedToViewers", viewerIDs); + } + + public async Task SendCursorChange(CursorInfo cursor, List viewerIDs) + { + await Connection.SendAsync("SendCursorChange", cursor, viewerIDs); + } + + public async Task SendDeviceInfo(string serviceID, string machineName) + { + await Connection.SendAsync("ReceiveDeviceInfo", serviceID, machineName); + } + + public async Task SendScreenCapture(byte[] captureBytes, string viewerID, int left, int top, int width, int height, DateTime captureTime) + { + await Connection.SendAsync("SendScreenCapture", captureBytes, viewerID, left, top, width, height, captureTime); + } + + public async Task SendScreenCount(int primaryScreenIndex, int screenCount, string viewerID) + { + await Connection.SendAsync("SendScreenCountToBrowser", primaryScreenIndex, screenCount, viewerID); + } + + public async Task SendScreenSize(int width, int height, string viewerID) + { + await Connection.SendAsync("SendScreenSize", width, height, viewerID); + } + + public async Task SendViewerRemoved(string viewerID) + { + await Connection.SendAsync("SendViewerRemoved", viewerID); + } private void ApplyConnectionHandlers() { @@ -39,6 +93,18 @@ namespace Remotely.ScreenCast.Core.Sockets return Task.CompletedTask; }; + Connection.On("ClipboardTransfer", (string transferText) => + { + try + { + Conductor.InvokeClipboardTransfer(transferText); + } + catch (Exception ex) + { + Logger.Write(ex); + } + }); + Connection.On("GetScreenCast", (string viewerID, string requesterName) => { try @@ -210,7 +276,8 @@ namespace Remotely.ScreenCast.Core.Sockets KeyboardMouseInput.SendLeftMouseUp(percentX, percentY, viewer); } }); - Connection.On("SharedFileIDs", (List fileIDs) => { + Connection.On("SharedFileIDs", (List fileIDs) => + { fileIDs.ForEach(id => { var url = $"{Conductor.Host}/API/FileSharing/{id}"; @@ -244,60 +311,5 @@ namespace Remotely.ScreenCast.Core.Sockets Conductor.InvokeSessionIDChanged(sessionID); }); } - - public async Task SendAudioSample(byte[] buffer, List viewerIDs) - { - await Connection.SendAsync("SendAudioSample", buffer, viewerIDs); - } - - public async Task SendScreenSize(int width, int height, string viewerID) - { - await Connection.SendAsync("SendScreenSize", width, height, viewerID); - } - - public async Task SendScreenCapture(byte[] captureBytes, string viewerID, int left, int top, int width, int height, DateTime captureTime) - { - await Connection.SendAsync("SendScreenCapture", captureBytes, viewerID, left, top, width, height, captureTime); - } - - public async Task SendScreenCount(int primaryScreenIndex, int screenCount, string viewerID) - { - await Connection.SendAsync("SendScreenCountToBrowser", primaryScreenIndex, screenCount, viewerID); - } - - public async Task NotifyRequesterUnattendedReady(string requesterID) - { - await Connection.SendAsync("NotifyRequesterUnattendedReady", requesterID); - } - - public async Task SendCursorChange(CursorInfo cursor, List viewerIDs) - { - await Connection.SendAsync("SendCursorChange", cursor, viewerIDs); - } - - public async Task NotifyViewersRelaunchedScreenCasterReady(string[] viewerIDs) - { - await Connection.SendAsync("NotifyViewersRelaunchedScreenCasterReady", viewerIDs); - } - - public async Task SendDeviceInfo(string serviceID, string machineName) - { - await Connection.SendAsync("ReceiveDeviceInfo", serviceID, machineName); - } - - public async Task SendConnectionFailedToViewers(List viewerIDs) - { - await Connection.SendAsync("SendConnectionFailedToViewers", viewerIDs); - } - - public async Task GetSessionID() - { - await Connection.SendAsync("GetSessionID"); - } - - public async Task SendViewerRemoved(string viewerID) - { - await Connection.SendAsync("SendViewerRemoved", viewerID); - } } } diff --git a/ScreenCast.Linux/Program.cs b/ScreenCast.Linux/Program.cs index 09ff00f2..b4f7ea98 100644 --- a/ScreenCast.Linux/Program.cs +++ b/ScreenCast.Linux/Program.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Threading.Tasks; +using System.IO; namespace Remotely.ScreenCast.Linux { @@ -26,6 +27,7 @@ namespace Remotely.ScreenCast.Linux Conductor.Connect().Wait(); Conductor.SetMessageHandlers(new X11Input()); Conductor.ScreenCastInitiated += ScreenCastInitiated; + Conductor.ClipboardTransferred += Conductor_ClipboardTransferred; Conductor.CasterSocket.SendDeviceInfo(Conductor.ServiceID, Environment.MachineName).Wait(); Conductor.CasterSocket.NotifyRequesterUnattendedReady(Conductor.RequesterID).Wait(); Conductor.StartWaitForViewerTimer(); @@ -41,6 +43,30 @@ namespace Remotely.ScreenCast.Linux } } + private static void Conductor_ClipboardTransferred(object sender, string transferredText) + { + var tempPath = Path.GetTempFileName(); + File.WriteAllText(tempPath, transferredText); + try + { + var psi = new ProcessStartInfo("bash", $"-c \"cat {tempPath} | xclip -i -selection clipboard\"") + { + WindowStyle = ProcessWindowStyle.Hidden + }; + var proc = Process.Start(psi); + proc.WaitForExit(); + + } + catch (Exception ex) + { + Logger.Write(ex); + } + finally + { + File.Delete(tempPath); + } + } + private static async void ScreenCastInitiated(object sender, ScreenCastRequest screenCastRequest) { try diff --git a/ScreenCast.Win/Program.cs b/ScreenCast.Win/Program.cs index fb3d6296..1af1954d 100644 --- a/ScreenCast.Win/Program.cs +++ b/ScreenCast.Win/Program.cs @@ -22,14 +22,50 @@ using System.Threading.Tasks; using System.Windows.Forms; using Remotely.Shared.Win32; using NAudio.Wave; +using Remotely.Shared.Services; +using System.Runtime.InteropServices; +using System.Threading; namespace Remotely.ScreenCast.Win { public class Program { + public static AudioCapturer AudioCapturer { get; private set; } public static Conductor Conductor { get; private set; } public static CursorIconWatcher CursorIconWatcher { get; private set; } - public static AudioCapturer AudioCapturer { get; private set; } + public static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor) + { + if (Conductor?.CasterSocket != null) + { + await Conductor.CasterSocket.SendCursorChange(cursor, Conductor.Viewers.Keys.ToList()); + } + } + + public static async Task HandleConnection(Conductor conductor) + { + while (true) + { + var desktopName = Win32Interop.GetCurrentDesktop(); + if (desktopName.ToLower() != conductor.CurrentDesktopName.ToLower() && conductor.Viewers.Count > 0) + { + conductor.CurrentDesktopName = desktopName; + Logger.Write($"Switching desktops to {desktopName}."); + conductor.IsSwitchingDesktops = true; + // TODO: SetThreadDesktop causes issues with input after switching. + //var inputDesktop = Win32Interop.OpenInputDesktop(); + //User32.SetThreadDesktop(inputDesktop); + //User32.CloseDesktop(inputDesktop); + conductor.Connection.InvokeAsync("SwitchingDesktops", conductor.Viewers.Keys.ToList()).Wait(); + var result = Win32Interop.OpenInteractiveProcess(Assembly.GetExecutingAssembly().Location + $" -mode {conductor.Mode.ToString()} -requester {conductor.RequesterID} -serviceid {conductor.ServiceID} -host {conductor.Host} -relaunch true -desktop {desktopName} -viewers {String.Join(",", conductor.Viewers.Keys.ToList())}", desktopName, true, out _); + if (!result) + { + Logger.Write($"Desktop switch to {desktopName} failed."); + conductor.CasterSocket.SendConnectionFailedToViewers(conductor.Viewers.Keys.ToList()).Wait(); + } + } + await Task.Delay(100); + } + } public static void Main(string[] args) { @@ -42,6 +78,7 @@ namespace Remotely.ScreenCast.Win Conductor.SetMessageHandlers(new WinInput()); Conductor.ScreenCastInitiated += ScreenCastInitiated; Conductor.AudioToggled += AudioToggled; + Conductor.ClipboardTransferred += Conductor_ClipboardTransferred; AudioCapturer = new AudioCapturer(Conductor); CursorIconWatcher = new CursorIconWatcher(Conductor); CursorIconWatcher.OnChange += CursorIconWatcher_OnChange; @@ -86,6 +123,41 @@ namespace Remotely.ScreenCast.Win } } + private static void CheckInitialDesktop() + { + var desktopName = Win32Interop.GetCurrentDesktop(); + if (desktopName.ToLower() != Conductor.CurrentDesktopName.ToLower()) + { + Conductor.CurrentDesktopName = desktopName; + Logger.Write($"Setting initial desktop to {desktopName}."); + Conductor.ArgDict["desktop"] = desktopName; + var openProcessString = Assembly.GetExecutingAssembly().Location; + foreach (var arg in Conductor.ArgDict) + { + openProcessString += $" -{arg.Key} {arg.Value}"; + } + var result = Win32Interop.OpenInteractiveProcess(openProcessString, desktopName, true, out _); + if (!result) + { + Logger.Write($"Desktop relaunch to {desktopName} failed."); + } + Environment.Exit(0); + } + } + + private static void Conductor_ClipboardTransferred(object sender, string transferredText) + { + var thread = new Thread(() => { + Clipboard.SetText(transferredText); + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Logger.Write((Exception)e.ExceptionObject); + } + private static async void ScreenCastInitiated(object sender, ScreenCastRequest screenCastRequest) { ICapturer capturer; @@ -108,63 +180,5 @@ namespace Remotely.ScreenCast.Win await Conductor.CasterSocket.SendCursorChange(CursorIconWatcher.GetCurrentCursor(), new List() { screenCastRequest.ViewerID }); ScreenCaster.BeginScreenCasting(screenCastRequest.ViewerID, screenCastRequest.RequesterName, capturer, Conductor); } - - public static async void CursorIconWatcher_OnChange(object sender, CursorInfo cursor) - { - if (Conductor?.CasterSocket != null) - { - await Conductor.CasterSocket.SendCursorChange(cursor, Conductor.Viewers.Keys.ToList()); - } - } - - public static async Task HandleConnection(Conductor conductor) - { - while (true) - { - var desktopName = Win32Interop.GetCurrentDesktop(); - if (desktopName.ToLower() != conductor.CurrentDesktopName.ToLower() && conductor.Viewers.Count > 0) - { - conductor.CurrentDesktopName = desktopName; - Logger.Write($"Switching desktops to {desktopName}."); - // TODO: SetThreadDesktop causes issues with input after switching. - //var inputDesktop = Win32Interop.OpenInputDesktop(); - //User32.SetThreadDesktop(inputDesktop); - //User32.CloseDesktop(inputDesktop); - conductor.Connection.InvokeAsync("SwitchingDesktops", conductor.Viewers.Keys.ToList()).Wait(); - var result = Win32Interop.OpenInteractiveProcess(Assembly.GetExecutingAssembly().Location + $" -mode {conductor.Mode.ToString()} -requester {conductor.RequesterID} -serviceid {conductor.ServiceID} -host {conductor.Host} -relaunch true -desktop {desktopName} -viewers {String.Join(",", conductor.Viewers.Keys.ToList())}", desktopName, true, out _); - if (!result) - { - Logger.Write($"Desktop switch to {desktopName} failed."); - conductor.CasterSocket.SendConnectionFailedToViewers(conductor.Viewers.Keys.ToList()).Wait(); - } - } - await Task.Delay(100); - } - } - private static void CheckInitialDesktop() - { - var desktopName = Win32Interop.GetCurrentDesktop(); - if (desktopName.ToLower() != Conductor.CurrentDesktopName.ToLower()) - { - Conductor.CurrentDesktopName = desktopName; - Logger.Write($"Setting initial desktop to {desktopName}."); - Conductor.ArgDict["desktop"] = desktopName; - var openProcessString = Assembly.GetExecutingAssembly().Location; - foreach (var arg in Conductor.ArgDict) - { - openProcessString += $" -{arg.Key} {arg.Value}"; - } - var result = Win32Interop.OpenInteractiveProcess(openProcessString, desktopName, true, out _); - if (!result) - { - Logger.Write($"Desktop relaunch to {desktopName} failed."); - } - Environment.Exit(0); - } - } - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Logger.Write((Exception)e.ExceptionObject); - } } } diff --git a/Server/CurrentVersion.txt b/Server/CurrentVersion.txt index cb729d78..5f0ec0a3 100644 --- a/Server/CurrentVersion.txt +++ b/Server/CurrentVersion.txt @@ -1 +1 @@ -2019.06.23.1129 +2019.06.27.2150 diff --git a/Server/Pages/RemoteControl.cshtml b/Server/Pages/RemoteControl.cshtml index 7a84377f..99f296b7 100644 --- a/Server/Pages/RemoteControl.cshtml +++ b/Server/Pages/RemoteControl.cshtml @@ -52,6 +52,7 @@
+ @@ -78,12 +79,19 @@
-
+
Image Quality
+
+
+ Paste to Transfer +
+ +
+

Connect to a client:

@@ -108,10 +116,9 @@
- +
-