mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Rebranding
This commit is contained in:
parent
fe5dbc23e1
commit
b3f03a8b56
@ -62,9 +62,6 @@ namespace Remotely_ScreenCast.Capture
|
||||
PreviousFrame = new Bitmap(CurrentScreenBounds.Width, CurrentScreenBounds.Height, PixelFormat.Format32bppArgb);
|
||||
graphic = Graphics.FromImage(CurrentFrame);
|
||||
desktopName = Win32Interop.GetCurrentDesktop();
|
||||
|
||||
Debug.WriteLine($"Starting BitBltCapture.");
|
||||
Debug.WriteLine($"Current Desktop: {desktopName}");
|
||||
}
|
||||
|
||||
public void Capture()
|
||||
@ -97,12 +94,5 @@ namespace Remotely_ScreenCast.Capture
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Point GetAbsoluteScreenCoordinatesFromPercentages(decimal percentX, decimal percentY)
|
||||
{
|
||||
var absoluteX = (CurrentScreenBounds.Width * percentX) + CurrentScreenBounds.Left;
|
||||
var absoluteY = (CurrentScreenBounds.Height * percentY) + CurrentScreenBounds.Top;
|
||||
return new Point((int)absoluteX, (int)absoluteY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,10 +119,6 @@ namespace Remotely_ScreenCast.Capture
|
||||
};
|
||||
screenTexture = new Texture2D(device, textureDesc);
|
||||
duplicatedOutput = output1.DuplicateOutput(device);
|
||||
|
||||
Debug.WriteLine($"Starting DXCapture.");
|
||||
Debug.WriteLine($"Current Desktop: {desktopName}");
|
||||
|
||||
}
|
||||
public void Capture()
|
||||
{
|
||||
@ -216,12 +212,5 @@ namespace Remotely_ScreenCast.Capture
|
||||
Capture();
|
||||
}
|
||||
}
|
||||
|
||||
public Point GetAbsoluteScreenCoordinatesFromPercentages(decimal percentX, decimal percentY)
|
||||
{
|
||||
var absoluteX = (CurrentScreenBounds.Width * percentX) + CurrentScreenBounds.Left;
|
||||
var absoluteY = (CurrentScreenBounds.Height * percentY) + CurrentScreenBounds.Top;
|
||||
return new Point((int)absoluteX, (int)absoluteY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,5 @@ namespace Remotely_ScreenCast.Capture
|
||||
void Capture();
|
||||
EventHandler<Rectangle> ScreenChanged { get; set; }
|
||||
int SelectedScreen { get; set; }
|
||||
|
||||
Point GetAbsoluteScreenCoordinatesFromPercentages(decimal percentX, decimal percentY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,16 +14,9 @@ namespace Remotely_ScreenCast.Capture
|
||||
public class ImageDiff
|
||||
{
|
||||
|
||||
private static BitmapData bd1;
|
||||
private static BitmapData bd2;
|
||||
private static BitmapData bd3;
|
||||
private static Bitmap mergedFrame;
|
||||
private static byte[] rgbValues1;
|
||||
private static byte[] rgbValues2;
|
||||
private static byte[] rgbValues3;
|
||||
|
||||
public static Bitmap GetImageDiff(Bitmap currentFrame, Bitmap previousFrame, bool captureFullscreen)
|
||||
{
|
||||
|
||||
if (currentFrame.Height != previousFrame.Height || currentFrame.Width != previousFrame.Width)
|
||||
{
|
||||
throw new Exception("Bitmaps are not of equal dimensions.");
|
||||
@ -36,11 +29,11 @@ namespace Remotely_ScreenCast.Capture
|
||||
var width = currentFrame.Width;
|
||||
var height = currentFrame.Height;
|
||||
|
||||
mergedFrame = new Bitmap(width, height);
|
||||
var mergedFrame = new Bitmap(width, height);
|
||||
|
||||
bd1 = previousFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, currentFrame.PixelFormat);
|
||||
bd2 = currentFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, previousFrame.PixelFormat);
|
||||
bd3 = mergedFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, currentFrame.PixelFormat);
|
||||
var bd1 = previousFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, currentFrame.PixelFormat);
|
||||
var bd2 = currentFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, previousFrame.PixelFormat);
|
||||
var bd3 = mergedFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, currentFrame.PixelFormat);
|
||||
|
||||
|
||||
// Get the address of the first line.
|
||||
@ -50,9 +43,9 @@ namespace Remotely_ScreenCast.Capture
|
||||
|
||||
// Declare an array to hold the bytes of the bitmap.
|
||||
int arraySize = Math.Abs(bd1.Stride) * currentFrame.Height;
|
||||
rgbValues1 = new byte[arraySize];
|
||||
rgbValues2 = new byte[arraySize];
|
||||
rgbValues3 = new byte[arraySize];
|
||||
var rgbValues1 = new byte[arraySize];
|
||||
var rgbValues2 = new byte[arraySize];
|
||||
var rgbValues3 = new byte[arraySize];
|
||||
|
||||
// Copy the RGBA values into the array.
|
||||
Marshal.Copy(ptr1, rgbValues1, 0, arraySize);
|
||||
@ -89,7 +82,7 @@ namespace Remotely_ScreenCast.Capture
|
||||
currentFrame.UnlockBits(bd2);
|
||||
mergedFrame.UnlockBits(bd3);
|
||||
|
||||
return mergedFrame;
|
||||
return mergedFrame;
|
||||
}
|
||||
|
||||
public static byte[] EncodeBitmap(Bitmap bitmap)
|
||||
@ -101,152 +94,5 @@ namespace Remotely_ScreenCast.Capture
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] GetDiffImage(Rectangle imageArea, Bitmap CurrentFrame)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var croppedFrame = CurrentFrame.Clone(imageArea, PixelFormat.Format24bppRgb))
|
||||
{
|
||||
var encoderParams = new EncoderParameters(1);
|
||||
encoderParams.Param = new EncoderParameter[] { new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 1) };
|
||||
croppedFrame.Save(ms, ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == ImageFormat.Png.Guid), encoderParams);
|
||||
// Byte array that indicates top left coordinates of the image.
|
||||
byte[] header;
|
||||
using (var headerStream = new MemoryStream())
|
||||
{
|
||||
var formatter = new BinaryFormatter();
|
||||
formatter.Serialize(headerStream, new Point(imageArea.X, imageArea.Y));
|
||||
header = headerStream.ToArray();
|
||||
}
|
||||
|
||||
return header.Concat(ms.ToArray()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Rectangle GetDiffArea(Bitmap previousFrame, Bitmap currentFrame, bool captureFullscreen)
|
||||
{
|
||||
if (captureFullscreen)
|
||||
{
|
||||
captureFullscreen = false;
|
||||
return new Rectangle(0, 0, currentFrame.Width, currentFrame.Height);
|
||||
}
|
||||
if (currentFrame.Height != previousFrame.Height || currentFrame.Width != previousFrame.Width)
|
||||
{
|
||||
throw new Exception("Bitmaps are not of equal dimensions.");
|
||||
}
|
||||
if (!Bitmap.IsAlphaPixelFormat(currentFrame.PixelFormat) || !Bitmap.IsAlphaPixelFormat(previousFrame.PixelFormat) ||
|
||||
!Bitmap.IsCanonicalPixelFormat(currentFrame.PixelFormat) || !Bitmap.IsCanonicalPixelFormat(previousFrame.PixelFormat))
|
||||
{
|
||||
throw new Exception("Bitmaps must be 32 bits per pixel and contain alpha channel.");
|
||||
}
|
||||
var width = currentFrame.Width;
|
||||
var height = currentFrame.Height;
|
||||
var left = int.MaxValue;
|
||||
var top = int.MaxValue;
|
||||
var right = int.MinValue;
|
||||
var bottom = int.MinValue;
|
||||
|
||||
try
|
||||
{
|
||||
bd1 = currentFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, currentFrame.PixelFormat);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
currentFrame.UnlockBits(bd1);
|
||||
bd1 = currentFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, currentFrame.PixelFormat);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
bd2 = previousFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, previousFrame.PixelFormat);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
previousFrame.UnlockBits(bd2);
|
||||
bd2 = previousFrame.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, previousFrame.PixelFormat);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
}
|
||||
// Get the address of the first line.
|
||||
IntPtr ptr1 = bd1.Scan0;
|
||||
IntPtr ptr2 = bd2.Scan0;
|
||||
|
||||
// Declare an array to hold the bytes of the bitmap.
|
||||
int arraySize = Math.Abs(bd1.Stride) * currentFrame.Height;
|
||||
rgbValues1 = new byte[arraySize];
|
||||
rgbValues2 = new byte[arraySize];
|
||||
|
||||
// Copy the RGBA values into the array.
|
||||
Marshal.Copy(ptr1, rgbValues1, 0, arraySize);
|
||||
Marshal.Copy(ptr2, rgbValues2, 0, arraySize);
|
||||
|
||||
// Check RGBA value for each pixel.
|
||||
for (int counter = 0; counter < rgbValues1.Length - 4; counter += 4)
|
||||
{
|
||||
if (rgbValues1[counter] != rgbValues2[counter] ||
|
||||
rgbValues1[counter + 1] != rgbValues2[counter + 1] ||
|
||||
rgbValues1[counter + 2] != rgbValues2[counter + 2] ||
|
||||
rgbValues1[counter + 3] != rgbValues2[counter + 3])
|
||||
{
|
||||
// Change was found.
|
||||
var pixel = counter / 4;
|
||||
var row = (int)Math.Floor((double)pixel / bd1.Width);
|
||||
var column = pixel % bd1.Width;
|
||||
if (row < top)
|
||||
{
|
||||
top = row;
|
||||
}
|
||||
if (row > bottom)
|
||||
{
|
||||
bottom = row;
|
||||
}
|
||||
if (column < left)
|
||||
{
|
||||
left = column;
|
||||
}
|
||||
if (column > right)
|
||||
{
|
||||
right = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left < right && top < bottom)
|
||||
{
|
||||
// Bounding box is valid.
|
||||
|
||||
left = Math.Max(left - 20, 0);
|
||||
top = Math.Max(top - 20, 0);
|
||||
right = Math.Min(right + 20, currentFrame.Width);
|
||||
bottom = Math.Min(bottom + 20, currentFrame.Height);
|
||||
|
||||
currentFrame.UnlockBits(bd1);
|
||||
previousFrame.UnlockBits(bd2);
|
||||
|
||||
return new Rectangle(left, top, right - left, bottom - top);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFrame.UnlockBits(bd1);
|
||||
previousFrame.UnlockBits(bd2);
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
119
Remotely_ScreenCast/Capture/ScreenCaster.cs
Normal file
119
Remotely_ScreenCast/Capture/ScreenCaster.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Remotely_ScreenCast.Sockets;
|
||||
using Remotely_ScreenCast.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Remotely_ScreenCast.Capture
|
||||
{
|
||||
public class ScreenCaster
|
||||
{
|
||||
private static object CaptureLock { get; } = new object();
|
||||
public static async void BeginScreenCasting(HubConnection hubConnection,
|
||||
string viewerID,
|
||||
string requesterName,
|
||||
OutgoingMessages outgoingMessages)
|
||||
{
|
||||
ICapturer capturer;
|
||||
CaptureMode captureMode;
|
||||
|
||||
try
|
||||
{
|
||||
if (Program.Viewers.Count == 0)
|
||||
{
|
||||
capturer = new DXCapture();
|
||||
captureMode = CaptureMode.DirectX;
|
||||
}
|
||||
else
|
||||
{
|
||||
capturer = new BitBltCapture();
|
||||
captureMode = CaptureMode.BitBtl;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(ex);
|
||||
capturer = new BitBltCapture();
|
||||
captureMode = CaptureMode.BitBtl;
|
||||
}
|
||||
|
||||
Logger.Write($"Starting screen cast. Requester: {requesterName}. Viewer ID: {viewerID}. Capture Mode: {captureMode.ToString()}.");
|
||||
|
||||
var viewer = new Models.Viewer()
|
||||
{
|
||||
Capturer = capturer,
|
||||
CurrentScreenIndex = 0,
|
||||
DisconnectRequested = false,
|
||||
Name = requesterName,
|
||||
ViewerConnectionID = viewerID,
|
||||
HasControl = Program.Mode == "unattended"
|
||||
};
|
||||
|
||||
|
||||
var success = false;
|
||||
while (!success)
|
||||
{
|
||||
success = Program.Viewers.TryAdd(viewerID, viewer);
|
||||
}
|
||||
|
||||
await outgoingMessages.SendScreenCount(
|
||||
Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen),
|
||||
Screen.AllScreens.Length,
|
||||
viewerID);
|
||||
|
||||
await outgoingMessages.SendScreenSize(capturer.CurrentScreenBounds.Width, capturer.CurrentScreenBounds.Height, viewerID);
|
||||
|
||||
capturer.ScreenChanged += async (sender, size) =>
|
||||
{
|
||||
await outgoingMessages.SendScreenSize(size.Width, size.Height, viewerID);
|
||||
};
|
||||
|
||||
while (!viewer.DisconnectRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (viewer.NextCaptureDelay > 0)
|
||||
{
|
||||
await Task.Delay(viewer.NextCaptureDelay);
|
||||
viewer.NextCaptureDelay = 0;
|
||||
}
|
||||
|
||||
capturer.Capture();
|
||||
|
||||
var newImage = ImageDiff.GetImageDiff(capturer.CurrentFrame, capturer.PreviousFrame, capturer.CaptureFullscreen);
|
||||
var img = ImageDiff.EncodeBitmap(newImage);
|
||||
if (capturer.CaptureFullscreen)
|
||||
{
|
||||
capturer.CaptureFullscreen = false;
|
||||
}
|
||||
if (img?.Length > 0)
|
||||
{
|
||||
await outgoingMessages.SendScreenCapture(img, viewerID, DateTime.Now);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write($"Outer Error: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
success = false;
|
||||
while (!success)
|
||||
{
|
||||
Program.Viewers.TryRemove(viewerID, out _);
|
||||
}
|
||||
Logger.Write($"Ended screen cast. Requester: {requesterName}. Viewer ID: {viewerID}.");
|
||||
}
|
||||
public static Tuple<double, double> GetAbsoluteScreenCoordinatesFromPercentages(double percentX, double percentY, ICapturer capturer)
|
||||
{
|
||||
var absoluteX = (capturer.CurrentScreenBounds.Width * percentX) + capturer.CurrentScreenBounds.Left;
|
||||
var absoluteY = (capturer.CurrentScreenBounds.Height * percentY) + capturer.CurrentScreenBounds.Top;
|
||||
return new Tuple<double, double>(absoluteX / SystemInformation.VirtualScreen.Width, absoluteY / SystemInformation.VirtualScreen.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,6 @@ using Remotely_ScreenCast.Capture;
|
||||
using Remotely_ScreenCast.Models;
|
||||
using Remotely_ScreenCast.Sockets;
|
||||
using Remotely_ScreenCast.Utilities;
|
||||
using Remotely_ScreenCast.Capture;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@ -146,6 +146,7 @@
|
||||
<Compile Include="Capture\DXCapture.cs" />
|
||||
<Compile Include="Capture\ICapturer.cs" />
|
||||
<Compile Include="Capture\ImageDiff.cs" />
|
||||
<Compile Include="Capture\ScreenCaster.cs" />
|
||||
<Compile Include="Models\Viewer.cs" />
|
||||
<Compile Include="Sockets\MessageHandlers.cs" />
|
||||
<Compile Include="Sockets\OutgoingMessages.cs" />
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
using Remotely_ScreenCast.Capture;
|
||||
using Remotely_ScreenCast.Utilities;
|
||||
using Remotely_ScreenCast;
|
||||
using Remotely_ScreenCast.Capture;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -26,7 +25,7 @@ namespace Remotely_ScreenCast.Sockets
|
||||
|
||||
hubConnection.On("GetScreenCast", (string viewerID, string requesterName) =>
|
||||
{
|
||||
BeginScreenCasting(hubConnection, viewerID, requesterName, outgoingMessages);
|
||||
ScreenCaster.BeginScreenCasting(hubConnection, viewerID, requesterName, outgoingMessages);
|
||||
});
|
||||
|
||||
hubConnection.On("KeyDown", (int keyCode, string viewerID) =>
|
||||
@ -57,13 +56,13 @@ namespace Remotely_ScreenCast.Sockets
|
||||
}
|
||||
});
|
||||
|
||||
hubConnection.On("MouseMove", (decimal percentX, decimal percentY, string viewerID) =>
|
||||
hubConnection.On("MouseMove", (double percentX, double percentY, string viewerID) =>
|
||||
{
|
||||
var viewer = Program.Viewers[viewerID];
|
||||
if (viewer.HasControl)
|
||||
{
|
||||
var mousePoint = viewer.Capturer.GetAbsoluteScreenCoordinatesFromPercentages(percentX, percentY);
|
||||
Win32Interop.SendMouseMove(mousePoint.X, mousePoint.Y);
|
||||
var mousePoint = ScreenCaster.GetAbsoluteScreenCoordinatesFromPercentages(percentX, percentY, viewer.Capturer);
|
||||
Win32Interop.SendMouseMove(mousePoint.Item1, mousePoint.Item2);
|
||||
}
|
||||
});
|
||||
|
||||
@ -93,91 +92,5 @@ namespace Remotely_ScreenCast.Sockets
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static async void BeginScreenCasting(HubConnection hubConnection,
|
||||
string viewerID,
|
||||
string requesterName,
|
||||
OutgoingMessages outgoingMessages)
|
||||
{
|
||||
ICapturer capturer;
|
||||
CaptureMode captureMode;
|
||||
|
||||
try
|
||||
{
|
||||
capturer = new DXCapture();
|
||||
captureMode = CaptureMode.DirectX;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(ex);
|
||||
capturer = new BitBltCapture();
|
||||
captureMode = CaptureMode.BitBtl;
|
||||
}
|
||||
|
||||
Logger.Write($"Starting screen cast. Requester: {requesterName}. Viewer ID: {viewerID}. Capture Mode: {captureMode.ToString()}.");
|
||||
|
||||
var viewer = new Models.Viewer()
|
||||
{
|
||||
Capturer = capturer,
|
||||
CurrentScreenIndex = 0,
|
||||
DisconnectRequested = false,
|
||||
Name = requesterName,
|
||||
ViewerConnectionID = viewerID,
|
||||
HasControl = Program.Mode == "unattended"
|
||||
};
|
||||
|
||||
|
||||
var success = false;
|
||||
while (!success)
|
||||
{
|
||||
success = Program.Viewers.TryAdd(viewerID, viewer);
|
||||
}
|
||||
|
||||
await outgoingMessages.SendScreenCount(
|
||||
Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen),
|
||||
Screen.AllScreens.Length,
|
||||
viewerID);
|
||||
|
||||
await outgoingMessages.SendScreenSize(capturer.CurrentScreenBounds.Width, capturer.CurrentScreenBounds.Height, viewerID);
|
||||
|
||||
capturer.ScreenChanged += async (sender, size) =>
|
||||
{
|
||||
await outgoingMessages.SendScreenSize(size.Width, size.Height, viewerID);
|
||||
};
|
||||
|
||||
while (!viewer.DisconnectRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (viewer.NextCaptureDelay > 0)
|
||||
{
|
||||
await Task.Delay(viewer.NextCaptureDelay);
|
||||
viewer.NextCaptureDelay = 0;
|
||||
}
|
||||
capturer.Capture();
|
||||
var newImage = ImageDiff.GetImageDiff(capturer.CurrentFrame, capturer.PreviousFrame, capturer.CaptureFullscreen);
|
||||
var img = ImageDiff.EncodeBitmap(newImage);
|
||||
if (capturer.CaptureFullscreen)
|
||||
{
|
||||
capturer.CaptureFullscreen = false;
|
||||
}
|
||||
if (img?.Length > 0)
|
||||
{
|
||||
await outgoingMessages.SendScreenCapture(img, viewerID, DateTime.Now);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write($"Outer Error: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
success = false;
|
||||
while (!success)
|
||||
{
|
||||
Program.Viewers.TryRemove(viewerID, out _);
|
||||
}
|
||||
Logger.Write($"Ended screen cast. Requester: {requesterName}. Viewer ID: {viewerID}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ namespace Win32
|
||||
}
|
||||
|
||||
// Offsets are used in case there's a multi-monitor setup where the left-most or top-most edge of the virtual screen
|
||||
// is not 0. The coordinates sent from the web viewer are always zero-based, so the offset must be applied.
|
||||
// is not 0.
|
||||
public static uint SendMouseMove(double x, double y)
|
||||
{
|
||||
// Coordinates must be normalized. The bottom-right coordinate is mapped to 65535.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user