diff --git a/Desktop.Unix/ViewModels/MainWindowViewModel.cs b/Desktop.Unix/ViewModels/MainWindowViewModel.cs index a307752b..49ea932f 100644 --- a/Desktop.Unix/ViewModels/MainWindowViewModel.cs +++ b/Desktop.Unix/ViewModels/MainWindowViewModel.cs @@ -9,6 +9,7 @@ using Remotely.ScreenCast.Core.Capture; using Remotely.ScreenCast.Core.Interfaces; using Remotely.ScreenCast.Core.Models; using Remotely.ScreenCast.Core.Services; +using Remotely.ScreenCast.Core.Sockets; using Remotely.ScreenCast.Linux.Capture; using Remotely.ScreenCast.Linux.Services; using Remotely.Shared.Models; @@ -36,11 +37,9 @@ namespace Remotely.Desktop.Unix.ViewModels public MainWindowViewModel() { Current = this; - Conductor = new Conductor( - new X11Input(), - new LinuxAudioCapturer(), - new LinuxClipboardService(), - new LinuxScreenCaster()); + var screenCaster = new LinuxScreenCaster(); + var casterSocket = new CasterSocket(new X11Input(), screenCaster, new LinuxAudioCapturer(), new LinuxClipboardService()); + Conductor = new Conductor(casterSocket, screenCaster); Conductor.SessionIDChanged += SessionIDChanged; Conductor.ViewerRemoved += ViewerRemoved; diff --git a/Desktop.Win/ViewModels/MainWindowViewModel.cs b/Desktop.Win/ViewModels/MainWindowViewModel.cs index a3522e3e..6aff7e51 100644 --- a/Desktop.Win/ViewModels/MainWindowViewModel.cs +++ b/Desktop.Win/ViewModels/MainWindowViewModel.cs @@ -18,6 +18,7 @@ using System.Security.Principal; using System.Windows.Input; using Remotely.ScreenCast.Win.Services; using Remotely.ScreenCast.Core.Interfaces; +using Remotely.ScreenCast.Core.Sockets; namespace Remotely.Desktop.Win.ViewModels { @@ -32,11 +33,11 @@ namespace Remotely.Desktop.Win.ViewModels CursorIconWatcher = new CursorIconWatcher(Conductor); CursorIconWatcher.OnChange += CursorIconWatcher_OnChange; - Conductor = new Conductor( - new WinInput(), - new WinAudioCapturer(), - new WinClipboardService(), - new WinScreenCaster(CursorIconWatcher)); + var screenCaster = new WinScreenCaster(CursorIconWatcher); + var clipboardService = new WinClipboardService(); + clipboardService.BeginWatching(); + var casterSocket = new CasterSocket(new WinInput(), screenCaster, new WinAudioCapturer(), clipboardService); + Conductor = new Conductor(casterSocket, screenCaster); Conductor.SessionIDChanged += SessionIDChanged; Conductor.ViewerRemoved += ViewerRemoved; diff --git a/ScreenCast.Core/Conductor.cs b/ScreenCast.Core/Conductor.cs index 7a06bafe..b1f66109 100644 --- a/ScreenCast.Core/Conductor.cs +++ b/ScreenCast.Core/Conductor.cs @@ -21,14 +21,12 @@ namespace Remotely.ScreenCast.Core public static Conductor Current { get; private set; } public IScreenCaster ScreenCaster { get; } - public Conductor(IKeyboardMouseInput keyboardMouse, - IAudioCapturer audioService, - IClipboardService clipboardService, + public Conductor(CasterSocket casterSocket, IScreenCaster screenCaster) { Current = this; ScreenCaster = screenCaster; - CasterSocket = new CasterSocket(this, keyboardMouse, screenCaster, audioService, clipboardService); + CasterSocket = casterSocket; } public event EventHandler ScreenCastRequested; diff --git a/ScreenCast.Core/Input/IKeyboardMouseInput.cs b/ScreenCast.Core/Input/IKeyboardMouseInput.cs index d68b4bd2..1172fc8c 100644 --- a/ScreenCast.Core/Input/IKeyboardMouseInput.cs +++ b/ScreenCast.Core/Input/IKeyboardMouseInput.cs @@ -17,5 +17,6 @@ namespace Remotely.ScreenCast.Core.Input uint SendRightMouseDown(double percentX, double percentY, Viewer viewer); uint SendRightMouseUp(double percentX, double percentY, Viewer viewer); uint SendMouseWheel(int deltaY, Viewer viewer); + void SendText(string transferText, Viewer viewer); } } diff --git a/ScreenCast.Core/Interfaces/IClipboardService.cs b/ScreenCast.Core/Interfaces/IClipboardService.cs index 3ee52627..a5891bea 100644 --- a/ScreenCast.Core/Interfaces/IClipboardService.cs +++ b/ScreenCast.Core/Interfaces/IClipboardService.cs @@ -6,6 +6,10 @@ namespace Remotely.ScreenCast.Core.Interfaces { public interface IClipboardService { + event EventHandler ClipboardTextChanged; + + void BeginWatching(); + void SetText(string clipboardText); } } diff --git a/ScreenCast.Core/Services/Logger.cs b/ScreenCast.Core/Services/Logger.cs index 2b8e6738..0c4e2f60 100644 --- a/ScreenCast.Core/Services/Logger.cs +++ b/ScreenCast.Core/Services/Logger.cs @@ -46,6 +46,7 @@ namespace Remotely.ScreenCast.Core.Services } } File.AppendAllText(path, JsonSerializer.Serialize(jsoninfo) + Environment.NewLine); + Console.WriteLine(message); } } catch { } @@ -90,6 +91,7 @@ namespace Remotely.ScreenCast.Core.Services } } File.AppendAllText(path, JsonSerializer.Serialize(jsonError) + Environment.NewLine); + Console.WriteLine(exception.Message); exception = exception.InnerException; } } diff --git a/ScreenCast.Core/Sockets/CasterSocket.cs b/ScreenCast.Core/Sockets/CasterSocket.cs index 1b2381b8..735b1ed7 100644 --- a/ScreenCast.Core/Sockets/CasterSocket.cs +++ b/ScreenCast.Core/Sockets/CasterSocket.cs @@ -22,25 +22,29 @@ namespace Remotely.ScreenCast.Core.Sockets public class CasterSocket { public CasterSocket( - Conductor conductor, IKeyboardMouseInput keyboardMouseInput, IScreenCaster screenCastService, IAudioCapturer audioCapturer, IClipboardService clipboardService) { - Conductor = conductor; KeyboardMouseInput = keyboardMouseInput; ClipboardService = clipboardService; AudioCapturer = audioCapturer; ScreenCaster = screenCastService; + + ClipboardService.ClipboardTextChanged += ClipboardService_ClipboardTextChanged; } public IScreenCaster ScreenCaster { get; } + private IAudioCapturer AudioCapturer { get; } + private IClipboardService ClipboardService { get; } - private Conductor Conductor { get; } + private HubConnection Connection { get; set; } + private IKeyboardMouseInput KeyboardMouseInput { get; } + public async Task Connect(string host) { Connection = new HubConnectionBuilder() @@ -73,11 +77,17 @@ namespace Remotely.ScreenCast.Core.Sockets { await Connection.SendAsync("NotifyViewersRelaunchedScreenCasterReady", viewerIDs); } + public async Task SendAudioSample(byte[] buffer, List viewerIDs) { await Connection.SendAsync("SendAudioSample", buffer, viewerIDs); } + public async Task SendClipboardText(string clipboardText, List viewerIDs) + { + await Connection.SendAsync("SendClipboardText", clipboardText, viewerIDs); + } + public async Task SendConnectionFailedToViewers(List viewerIDs) { await Connection.SendAsync("SendConnectionFailedToViewers", viewerIDs); @@ -97,6 +107,7 @@ namespace Remotely.ScreenCast.Core.Sockets { await Connection.SendAsync("SendMachineName", machineName, 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); @@ -119,6 +130,7 @@ namespace Remotely.ScreenCast.Core.Sockets private void ApplyConnectionHandlers() { + var conductor = Conductor.Current; Connection.Closed += (ex) => { Logger.Write($"Connection closed. Error: {ex?.Message}"); @@ -126,13 +138,20 @@ namespace Remotely.ScreenCast.Core.Sockets return Task.CompletedTask; }; - Connection.On("ClipboardTransfer", (string transferText, string viewerID) => + Connection.On("ClipboardTransfer", (string transferText, bool typeText, string viewerID) => { try { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { - ClipboardService.SetText(transferText); + if (typeText) + { + KeyboardMouseInput.SendText(transferText, viewer); + } + else + { + ClipboardService.SetText(transferText); + } } } catch (Exception ex) @@ -143,7 +162,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("CtrlAltDel", async (string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { await Connection.InvokeAsync("CtrlAltDel"); } @@ -163,12 +182,12 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("RequestScreenCast", (string viewerID, string requesterName) => { - Conductor.InvokeScreenCastRequested(new ScreenCastRequest() { ViewerID = viewerID, RequesterName = requesterName }); + conductor.InvokeScreenCastRequested(new ScreenCastRequest() { ViewerID = viewerID, RequesterName = requesterName }); }); Connection.On("KeyDown", (string key, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { KeyboardMouseInput.SendKeyDown(key, viewer); } @@ -176,7 +195,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("KeyUp", (string key, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { KeyboardMouseInput.SendKeyUp(key, viewer); } @@ -184,7 +203,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("KeyPress", async (string key, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { KeyboardMouseInput.SendKeyDown(key, viewer); await Task.Delay(1); @@ -194,7 +213,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("MouseMove", (double percentX, double percentY, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { KeyboardMouseInput.SendMouseMove(percentX, percentY, viewer); } @@ -202,7 +221,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("MouseDown", (int button, double percentX, double percentY, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { if (button == 0) { @@ -217,7 +236,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("MouseUp", (int button, double percentX, double percentY, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { if (button == 0) { @@ -232,7 +251,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("MouseWheel", (double deltaX, double deltaY, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { KeyboardMouseInput.SendMouseWheel(-(int)deltaY, viewer); } @@ -240,17 +259,17 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("ViewerDisconnected", async (string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer)) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer)) { viewer.DisconnectRequested = true; } await Connection.InvokeAsync("ViewerDisconnected", viewerID); - Conductor.InvokeViewerRemoved(viewerID); + conductor.InvokeViewerRemoved(viewerID); }); Connection.On("LatencyUpdate", (double latency, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer)) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer)) { viewer.PendingFrames--; viewer.Latency = latency; @@ -259,7 +278,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("SelectScreen", (int screenIndex, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer)) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer)) { viewer.Capturer.SetSelectedScreen(screenIndex); } @@ -267,7 +286,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("QualityChange", (int qualityLevel, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer)) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer)) { viewer.ImageQuality = qualityLevel; } @@ -275,7 +294,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("ToggleAudio", (bool toggleOn, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { AudioCapturer.ToggleAudio(toggleOn); } @@ -284,7 +303,7 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("TouchDown", (string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { //User32.GetCursorPos(out var point); //Win32Interop.SendLeftMouseDown(point.X, point.Y); @@ -292,7 +311,7 @@ namespace Remotely.ScreenCast.Core.Sockets }); Connection.On("LongPress", (string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { //User32.GetCursorPos(out var point); //Win32Interop.SendRightMouseDown(point.X, point.Y); @@ -301,7 +320,7 @@ namespace Remotely.ScreenCast.Core.Sockets }); Connection.On("TouchMove", (double moveX, double moveY, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { //User32.GetCursorPos(out var point); //Win32Interop.SendMouseMove(point.X + moveX, point.Y + moveY); @@ -309,7 +328,7 @@ namespace Remotely.ScreenCast.Core.Sockets }); Connection.On("TouchUp", (string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { //User32.GetCursorPos(out var point); //Win32Interop.SendLeftMouseUp(point.X, point.Y); @@ -317,7 +336,7 @@ namespace Remotely.ScreenCast.Core.Sockets }); Connection.On("Tap", (double percentX, double percentY, string viewerID) => { - if (Conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) + if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl) { KeyboardMouseInput.SendLeftMouseDown(percentX, percentY, viewer); KeyboardMouseInput.SendLeftMouseUp(percentX, percentY, viewer); @@ -327,7 +346,7 @@ namespace Remotely.ScreenCast.Core.Sockets { fileIDs.ForEach(id => { - var url = $"{Conductor.Host}/API/FileSharing/{id}"; + var url = $"{conductor.Host}/API/FileSharing/{id}"; var webRequest = WebRequest.CreateHttp(url); var response = webRequest.GetResponse(); var contentDisp = response.Headers["Content-Disposition"]; @@ -355,8 +374,17 @@ namespace Remotely.ScreenCast.Core.Sockets Connection.On("SessionID", (string sessionID) => { - Conductor.InvokeSessionIDChanged(sessionID); + conductor.InvokeSessionIDChanged(sessionID); }); } + + private async void ClipboardService_ClipboardTextChanged(object sender, string clipboardText) + { + var viewerIDs = Conductor.Current.Viewers.Keys.ToList(); + if (viewerIDs.Any()) + { + await SendClipboardText(clipboardText, viewerIDs); + } + } } } diff --git a/ScreenCast.Linux/Program.cs b/ScreenCast.Linux/Program.cs index 8ea4cf77..cac0029d 100644 --- a/ScreenCast.Linux/Program.cs +++ b/ScreenCast.Linux/Program.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using Remotely.ScreenCast.Linux.Services; using Remotely.ScreenCast.Linux.Capture; +using Remotely.ScreenCast.Core.Sockets; namespace Remotely.ScreenCast.Linux { @@ -15,11 +16,9 @@ namespace Remotely.ScreenCast.Linux try { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - Conductor = new Conductor( - new X11Input(), - new LinuxAudioCapturer(), - new LinuxClipboardService(), - new LinuxScreenCaster()); + var screenCaster = new LinuxScreenCaster(); + var casterSocket = new CasterSocket(new X11Input(), screenCaster, new LinuxAudioCapturer(), new LinuxClipboardService()); + Conductor = new Conductor(casterSocket, screenCaster); Conductor.ProcessArgs(args); Conductor.Connect().ContinueWith(async (task) => diff --git a/ScreenCast.Linux/Services/LinuxClipboardService.cs b/ScreenCast.Linux/Services/LinuxClipboardService.cs index 78900b8a..f3c91458 100644 --- a/ScreenCast.Linux/Services/LinuxClipboardService.cs +++ b/ScreenCast.Linux/Services/LinuxClipboardService.cs @@ -10,6 +10,13 @@ namespace Remotely.ScreenCast.Linux.Services { public class LinuxClipboardService : IClipboardService { + public event EventHandler ClipboardTextChanged; + + public void BeginWatching() + { + // Not implemented. + } + public void SetText(string clipboardText) { var tempPath = Path.GetTempFileName(); diff --git a/ScreenCast.Linux/Services/X11Input.cs b/ScreenCast.Linux/Services/X11Input.cs index 1e663ab1..bcb2e541 100644 --- a/ScreenCast.Linux/Services/X11Input.cs +++ b/ScreenCast.Linux/Services/X11Input.cs @@ -171,6 +171,15 @@ namespace Remotely.ScreenCast.Linux.Services } } + public void SendText(string transferText, Viewer viewer) + { + foreach (var key in transferText) + { + SendKeyDown(key.ToString(), viewer); + SendKeyUp(key.ToString(), viewer); + } + } + private string ConvertJavaScriptKeyToX11Key(string key) { string keySym; diff --git a/ScreenCast.Win/Program.cs b/ScreenCast.Win/Program.cs index c54ffb57..7b6e6d66 100644 --- a/ScreenCast.Win/Program.cs +++ b/ScreenCast.Win/Program.cs @@ -10,6 +10,7 @@ using System.Threading; using Remotely.ScreenCast.Win.Services; using Remotely.ScreenCast.Core.Interfaces; using Remotely.ScreenCast.Win.Capture; +using Remotely.ScreenCast.Core.Sockets; namespace Remotely.ScreenCast.Win { @@ -31,11 +32,11 @@ namespace Remotely.ScreenCast.Win { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; CursorIconWatcher = new CursorIconWatcher(Conductor); - Conductor = new Conductor( - new WinInput(), - new WinAudioCapturer(), - new WinClipboardService(), - new WinScreenCaster(CursorIconWatcher)); + var screenCaster = new WinScreenCaster(CursorIconWatcher); + var clipboardService = new WinClipboardService(); + clipboardService.BeginWatching(); + var casterSocket = new CasterSocket(new WinInput(), screenCaster, new WinAudioCapturer(), clipboardService); + Conductor = new Conductor(casterSocket, screenCaster); Conductor.ProcessArgs(args); Conductor.Connect().ContinueWith(async (task) => diff --git a/ScreenCast.Win/Services/WinClipboardService.cs b/ScreenCast.Win/Services/WinClipboardService.cs index 6dc343e5..3c8c3552 100644 --- a/ScreenCast.Win/Services/WinClipboardService.cs +++ b/ScreenCast.Win/Services/WinClipboardService.cs @@ -1,4 +1,6 @@ using Remotely.ScreenCast.Core.Interfaces; +using Remotely.ScreenCast.Core.Services; +using Remotely.ScreenCast.Core.Sockets; using System; using System.Collections.Generic; using System.Linq; @@ -11,14 +13,74 @@ namespace Remotely.ScreenCast.Win.Services { public class WinClipboardService : IClipboardService { + public event EventHandler ClipboardTextChanged; + + private string ClipboardText { get; set; } + + private System.Timers.Timer ClipboardWatcher { get; set; } + + public void BeginWatching() + { + try + { + if (ClipboardWatcher?.Enabled == true) + { + ClipboardWatcher.Stop(); + } + + if (Clipboard.ContainsText()) + { + ClipboardText = Clipboard.GetText(); + ClipboardTextChanged.Invoke(this, ClipboardText); + } + ClipboardWatcher = new System.Timers.Timer(500); + } + catch + { + return; + } + ClipboardWatcher.Elapsed += (sender, args) => + { + var thread = new Thread(() => + { + try + { + if (Clipboard.ContainsText() && Clipboard.GetText() != ClipboardText) + { + ClipboardText = Clipboard.GetText(); + ClipboardTextChanged.Invoke(this, ClipboardText); + } + } + catch (Exception ex) + { + Logger.Write(ex); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + }; + ClipboardWatcher.Start(); + } + public void SetText(string clipboardText) { - var thread = new Thread(() => + try { - Clipboard.SetText(clipboardText); - }); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); + var thread = new Thread(() => + { + Clipboard.SetText(clipboardText); + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + catch (Exception ex) + { + Logger.Write(ex); + } + } + public void StopWatching() + { + ClipboardWatcher?.Stop(); } } } diff --git a/ScreenCast.Win/Services/WinInput.cs b/ScreenCast.Win/Services/WinInput.cs index 2c5d47d0..8f6c52b0 100644 --- a/ScreenCast.Win/Services/WinInput.cs +++ b/ScreenCast.Win/Services/WinInput.cs @@ -10,6 +10,57 @@ namespace Remotely.ScreenCast.Win.Services { public class WinInput : IKeyboardMouseInput { + public Tuple GetAbsolutePercentFromRelativePercent(double percentX, double percentY, ICapturer 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(absoluteX / capturer.GetVirtualScreenBounds().Width, absoluteY / capturer.GetVirtualScreenBounds().Height); + } + + public Tuple GetAbsolutePointFromRelativePercent(double percentX, double percentY, ICapturer capturer) + { + var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left; + var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top; + return new Tuple(absoluteX, absoluteY); + } + + public void SendKeyDown(string key, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var keyCode = ConvertJavaScriptKeyToVirtualKey(key); + var union = new InputUnion() + { + ki = new KEYBDINPUT() + { + wVk = keyCode, + wScan = 0, + time = 0, + dwExtraInfo = GetMessageExtraInfo() + } + }; + var input = new INPUT() { type = InputType.KEYBOARD, U = union }; + SendInput(1, new INPUT[] { input }, INPUT.Size); + } + + public void SendKeyUp(string key, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + var keyCode = ConvertJavaScriptKeyToVirtualKey(key); + var union = new InputUnion() + { + ki = new KEYBDINPUT() + { + wVk = keyCode, + wScan = 0, + time = 0, + dwFlags = KEYEVENTF.KEYUP, + dwExtraInfo = GetMessageExtraInfo() + } + }; + var input = new INPUT() { type = InputType.KEYBOARD, U = union }; + SendInput(1, new INPUT[] { input }, INPUT.Size); + } + public uint SendLeftMouseDown(double percentX, double percentY, Viewer viewer) { Win32Interop.SwitchToInputDesktop(); @@ -32,6 +83,34 @@ namespace Remotely.ScreenCast.Win.Services var input = new INPUT() { type = InputType.MOUSE, U = union }; return SendInput(1, new INPUT[] { input }, INPUT.Size); } + public uint SendMouseMove(double percentX, double percentY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + 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 }; + return SendInput(1, new INPUT[] { input }, INPUT.Size); + } + + public uint SendMouseWheel(int deltaY, Viewer viewer) + { + Win32Interop.SwitchToInputDesktop(); + if (deltaY < 0) + { + deltaY = -120; + } + else if (deltaY > 0) + { + deltaY = 120; + } + var union = new User32.InputUnion() { mi = new User32.MOUSEINPUT() { dwFlags = MOUSEEVENTF.WHEEL, dx = 0, dy = 0, time = 0, mouseData = deltaY, dwExtraInfo = GetMessageExtraInfo() } }; + var input = new User32.INPUT() { type = InputType.MOUSE, U = union }; + return SendInput(1, new User32.INPUT[] { input }, INPUT.Size); + } + public uint SendRightMouseDown(double percentX, double percentY, Viewer viewer) { Win32Interop.SwitchToInputDesktop(); @@ -54,66 +133,13 @@ namespace Remotely.ScreenCast.Win.Services var input = new INPUT() { type = InputType.MOUSE, U = union }; return SendInput(1, new INPUT[] { input }, INPUT.Size); } - public uint SendMouseMove(double percentX, double percentY, Viewer viewer) + public void SendText(string transferText, Viewer viewer) { - Win32Interop.SwitchToInputDesktop(); - 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 }; - return SendInput(1, new INPUT[] { input }, INPUT.Size); - } - public uint SendMouseWheel(int deltaY, Viewer viewer) - { - Win32Interop.SwitchToInputDesktop(); - if (deltaY < 0) + foreach (var key in transferText) { - deltaY = -120; + SendKeyDown(key.ToString(), viewer); + SendKeyUp(key.ToString(), viewer); } - else if (deltaY > 0) - { - deltaY = 120; - } - var union = new User32.InputUnion() { mi = new User32.MOUSEINPUT() { dwFlags = MOUSEEVENTF.WHEEL, dx = 0, dy = 0, time = 0, mouseData = deltaY, dwExtraInfo = GetMessageExtraInfo() } }; - var input = new User32.INPUT() { type = InputType.MOUSE, U = union }; - return SendInput(1, new User32.INPUT[] { input }, INPUT.Size); - } - public void SendKeyDown(string key, Viewer viewer) - { - Win32Interop.SwitchToInputDesktop(); - var keyCode = ConvertJavaScriptKeyToVirtualKey(key); - var union = new InputUnion() - { - ki = new KEYBDINPUT() - { - wVk = keyCode, - wScan = 0, - time = 0, - dwExtraInfo = GetMessageExtraInfo() - } - }; - var input = new INPUT() { type = InputType.KEYBOARD, U = union }; - SendInput(1, new INPUT[] { input }, INPUT.Size); - } - public void SendKeyUp(string key, Viewer viewer) - { - Win32Interop.SwitchToInputDesktop(); - var keyCode = ConvertJavaScriptKeyToVirtualKey(key); - var union = new InputUnion() - { - ki = new KEYBDINPUT() - { - wVk = keyCode, - wScan = 0, - time = 0, - dwFlags = KEYEVENTF.KEYUP, - dwExtraInfo = GetMessageExtraInfo() - } - }; - var input = new INPUT() { type = InputType.KEYBOARD, U = union }; - SendInput(1, new INPUT[] { input }, INPUT.Size); } private VirtualKey ConvertJavaScriptKeyToVirtualKey(string key) @@ -234,18 +260,5 @@ namespace Remotely.ScreenCast.Win.Services } return keyCode; } - - public Tuple GetAbsolutePercentFromRelativePercent(double percentX, double percentY, ICapturer 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(absoluteX / capturer.GetVirtualScreenBounds().Width, absoluteY / capturer.GetVirtualScreenBounds().Height); - } - public Tuple GetAbsolutePointFromRelativePercent(double percentX, double percentY, ICapturer capturer) - { - var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left; - var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top; - return new Tuple(absoluteX, absoluteY); - } } } diff --git a/Server/Pages/RemoteControl.cshtml b/Server/Pages/RemoteControl.cshtml index ea8a2420..d266a4d7 100644 --- a/Server/Pages/RemoteControl.cshtml +++ b/Server/Pages/RemoteControl.cshtml @@ -95,9 +95,13 @@
- Paste to Transfer + Shared Clipboard
+
+ + +
diff --git a/Server/Services/RCBrowserSocketHub.cs b/Server/Services/RCBrowserSocketHub.cs index ed5bbc33..2e010517 100644 --- a/Server/Services/RCBrowserSocketHub.cs +++ b/Server/Services/RCBrowserSocketHub.cs @@ -145,9 +145,9 @@ namespace Remotely.Server.Services await RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("SelectScreen", screenIndex, Context.ConnectionId); } - public async Task SendClipboardTransfer(string transferText) + public async Task SendClipboardTransfer(string transferText, bool typeText) { - await RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("ClipboardTransfer", transferText, Context.ConnectionId); + await RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("ClipboardTransfer", transferText, typeText, Context.ConnectionId); } public async Task SendLatencyUpdate(double latency) diff --git a/Server/Services/RCDeviceSocketHub.cs b/Server/Services/RCDeviceSocketHub.cs index dc5ac983..326b4f63 100644 --- a/Server/Services/RCDeviceSocketHub.cs +++ b/Server/Services/RCDeviceSocketHub.cs @@ -52,6 +52,12 @@ namespace Remotely.Server.Services Context.Items["CurrentScreenSize"] = value; } } + private DataService DataService { get; } + + private IHubContext DeviceHub { get; } + + private IHubContext RCBrowserHub { get; } + private RCSessionInfo SessionInfo { get @@ -70,13 +76,6 @@ namespace Remotely.Server.Services Context.Items["SessionInfo"] = value; } } - - - private DataService DataService { get; } - private IHubContext DeviceHub { get; } - - private IHubContext RCBrowserHub { get; } - private List ViewerList { get @@ -155,15 +154,15 @@ namespace Remotely.Server.Services SessionInfo.MachineName = machineName; SessionInfo.DeviceID = deviceID; } - public async Task SendMachineName(string machineName, string viewerID) - { - await RCBrowserHub.Clients.Client(viewerID).SendAsync("ReceiveMachineName", machineName); - } public async Task SendAudioSample(byte[] buffer, List viewerIDs) { await RCBrowserHub.Clients.Clients(viewerIDs).SendAsync("AudioSample", buffer); } + public async Task SendClipboardText(string clipboardText, List viewerIDs) + { + await RCBrowserHub.Clients.Clients(viewerIDs).SendAsync("ClipboardTextChanged", clipboardText); + } public async Task SendConnectionFailedToViewers(List viewerIDs) { await RCBrowserHub.Clients.Clients(viewerIDs).SendAsync("ConnectionFailed"); @@ -174,6 +173,10 @@ namespace Remotely.Server.Services await RCBrowserHub.Clients.Clients(viewerIDs).SendAsync("CursorChange", cursor); } + public async Task SendMachineName(string machineName, string viewerID) + { + await RCBrowserHub.Clients.Client(viewerID).SendAsync("ReceiveMachineName", machineName); + } public Task SendScreenCapture(byte[] captureBytes, string rcBrowserHubConnectionID, int left, int top, int width, int height, DateTime captureTime) { if (AppConfig.RecordRemoteControlSessions) diff --git a/Server/wwwroot/scripts/RemoteControl/RCBrowserSockets.ts b/Server/wwwroot/scripts/RemoteControl/RCBrowserSockets.ts index 4396022d..07d43062 100644 --- a/Server/wwwroot/scripts/RemoteControl/RCBrowserSockets.ts +++ b/Server/wwwroot/scripts/RemoteControl/RCBrowserSockets.ts @@ -3,6 +3,7 @@ import * as UI from "./UI.js"; import { RemoteControl } from "./Main.js"; import { CursorInfo } from "../Models/CursorInfo.js"; import { Sound } from "../Sound.js"; +import { PopupMessage } from "../UI.js"; var signalR = window["signalR"]; @@ -103,10 +104,15 @@ export class RCBrowserSockets { SendToggleAudio(toggleOn: boolean) { this.Connection.invoke("SendToggleAudio", toggleOn); }; - SendClipboardTransfer(text: string) { - this.Connection.invoke("SendClipboardTransfer", text); + SendClipboardTransfer(text: string, typeText: boolean) { + this.Connection.invoke("SendClipboardTransfer", text, typeText); } private ApplyMessageHandlers(hubConnection) { + hubConnection.on("ClipboardTextChanged", (clipboardText: string) => { + Utilities.SetClipboardText(clipboardText); + UI.ClipboardTransferTextArea.innerHTML = clipboardText; + PopupMessage("Clipboard updated."); + }); hubConnection.on("ScreenCount", (primaryScreenIndex: number, screenCount: number) => { document.querySelector("#screenSelectBar").innerHTML = ""; for (let i = 0; i < screenCount; i++) { diff --git a/Server/wwwroot/scripts/RemoteControl/UI.ts b/Server/wwwroot/scripts/RemoteControl/UI.ts index 51186e07..a10bd4db 100644 --- a/Server/wwwroot/scripts/RemoteControl/UI.ts +++ b/Server/wwwroot/scripts/RemoteControl/UI.ts @@ -1,5 +1,5 @@ import { RCBrowserSockets } from "./RCBrowserSockets.js"; -import { GetDistanceBetween } from "../Utilities.js"; +import { GetDistanceBetween, SetClipboardText } from "../Utilities.js"; import { RemoteControl } from "./Main.js"; import { PopupMessage } from "../UI.js"; import { RemoteControlMode } from "../Enums/RemoteControlMode.js"; @@ -35,6 +35,7 @@ export var TouchKeyboardTextArea = document.getElementById("touchKeyboardTextAre export var ClipboardTransferBar = document.getElementById("clipboardTransferBar") as HTMLDivElement; export var ClipboardTransferTextArea = document.getElementById("clipboardTransferTextArea") as HTMLTextAreaElement; export var ClipboardTransferButton = document.getElementById("clipboardTransferButton") as HTMLButtonElement; +export var ClipboardTransferTypeCheckbox = document.getElementById("clipboardTransferTypeCheckbox") as HTMLInputElement; var lastPointerMove = Date.now(); var isDragging: boolean; @@ -66,10 +67,8 @@ export function ApplyInputHandlers(sockets: RCBrowserSockets) { if (ClipboardTransferTextArea.value.length == 0) { return; } - sockets.SendClipboardTransfer(ClipboardTransferTextArea.value); - ClipboardTransferTextArea.value = ""; + sockets.SendClipboardTransfer(ClipboardTransferTextArea.value, ClipboardTransferTypeCheckbox.checked); ClipboardTransferTextArea.blur(); - ClipboardTransferBar.classList.remove("open"); PopupMessage("Clipboard sent!"); }); ConnectButton.addEventListener("click", (ev) => { @@ -120,15 +119,7 @@ export function ApplyInputHandlers(sockets: RCBrowserSockets) { else { url = `${location.origin}${location.pathname}?clientID=${RemoteControl.ClientID}&serviceID=${RemoteControl.ServiceID}`; } - var input = document.createElement("input"); - input.style.position = "fixed"; - input.style.top = "-1000px"; - input.type = "text"; - document.body.appendChild(input); - input.value = url; - input.select(); - document.execCommand("copy", false, location.href); - input.remove(); + SetClipboardText(url); PopupMessage("Link copied to clipboard."); }); diff --git a/Server/wwwroot/scripts/Utilities.ts b/Server/wwwroot/scripts/Utilities.ts index a3ae92da..2f4041e4 100644 --- a/Server/wwwroot/scripts/Utilities.ts +++ b/Server/wwwroot/scripts/Utilities.ts @@ -84,4 +84,16 @@ export function RemoveFromArray(array: Array, item: any) { if (index > -1) { array.splice(index, 1); } -}; \ No newline at end of file +}; + +export function SetClipboardText(text: string) { + var input = document.createElement("input"); + input.style.position = "fixed"; + input.style.top = "-1000px"; + input.type = "text"; + document.body.appendChild(input); + input.value = text; + input.select(); + document.execCommand("copy", false); + input.remove(); +} \ No newline at end of file