Skip to content

Commit

Permalink
Introdice SimulatorCapabilities that can be queried from IActions, so…
Browse files Browse the repository at this point in the history
… that the simulator can better prepare accordingly.
  • Loading branch information
kpreisser committed Jan 15, 2022
1 parent ccd8bbd commit 834a4ca
Show file tree
Hide file tree
Showing 29 changed files with 412 additions and 192 deletions.
5 changes: 5 additions & 0 deletions TTMouseclickSimulator/Core/Actions/AbstractAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public abstract class AbstractAction : IAction
{
public event Action<string>? ActionInformationUpdated;

public abstract SimulatorCapabilities RequiredCapabilities
{
get;
}

public abstract ValueTask RunAsync(IInteractionProvider provider);

protected void OnActionInformationUpdated(string text)
Expand Down
15 changes: 14 additions & 1 deletion TTMouseclickSimulator/Core/Actions/AbstractActionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ public abstract class AbstractActionContainer : AbstractAction, IActionContainer
{
public event Action<int?>? SubActionStartedOrStopped;

public abstract IList<IAction> SubActions { get; }
public abstract IReadOnlyList<IAction> SubActions { get; }

public override SimulatorCapabilities RequiredCapabilities
{
get
{
var capabilities = default(SimulatorCapabilities);

foreach (var subAction in this.SubActions ?? Array.Empty<IAction>())
capabilities |= subAction.RequiredCapabilities;

return capabilities;
}
}

protected void OnSubActionStartedOrStopped(int? index)
{
Expand Down
19 changes: 11 additions & 8 deletions TTMouseclickSimulator/Core/Actions/CompoundAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ public class CompoundAction : AbstractActionContainer
public const int PauseIntervalMinimum = 0;
public const int PauseIntervalMaximum = 600000;

private readonly IList<IAction> actionList;
private readonly IReadOnlyList<IAction> actionList;
private readonly CompoundActionType type;

private readonly int minimumPauseDuration;
private readonly int maximumPauseDuration;
private readonly bool loop;

private readonly Random rng = new Random();
private readonly Random rng = new();

/// <summary>
///
Expand All @@ -36,14 +36,15 @@ public class CompoundAction : AbstractActionContainer
/// it will loop endlessly. Note that using false is not possible when specifying
/// CompoundActionType.RandomIndex as type.</param>
public CompoundAction(
IList<IAction> actionList,
IReadOnlyList<IAction> actionList,
CompoundActionType type = CompoundActionType.Sequential,
int minimumPause = 0,
int maximumPause = 0,
bool loop = true)
{
if (actionList is null || actionList.Count is 0)
throw new ArgumentException("There must be at least one IAction to start the simulator.");
throw new ArgumentException(
"There must be at least one IAction to start the simulator.");

if (minimumPause < PauseIntervalMinimum
|| minimumPause > PauseIntervalMaximum
Expand All @@ -61,7 +62,7 @@ public CompoundAction(
if (type is CompoundActionType.RandomIndex && !loop)
throw new ArgumentException(
"When using CompoundActionType.RandomIndex, it is not possible " +
" to disable the loop.");
"to disable the loop.");

this.actionList = actionList;
this.type = type;
Expand All @@ -70,7 +71,7 @@ public CompoundAction(
this.loop = loop;
}

public override sealed IList<IAction> SubActions
public override sealed IReadOnlyList<IAction> SubActions
{
get => this.actionList;
}
Expand All @@ -79,8 +80,8 @@ public override sealed async ValueTask RunAsync(IInteractionProvider provider)
{
// Run the actions.
int currentIdx = -1;

Func<int> getNextActionIndex;

if (this.type is CompoundActionType.Sequential)
{
getNextActionIndex = () =>
Expand Down Expand Up @@ -150,7 +151,7 @@ public override sealed async ValueTask RunAsync(IInteractionProvider provider)

await provider.WaitAsync(waitInterval);
}
catch (Exception ex) when (!(ex is OperationCanceledException))
catch (Exception ex) when (ex is not OperationCanceledException)
{
await provider.CheckRetryForExceptionAsync(ex);
continue;
Expand All @@ -175,6 +176,7 @@ public enum CompoundActionType : int
/// (if loop is true) or the compound action returns.
/// </summary>
Sequential = 0,

/// <summary>
/// Specifies that the inner actions should be executed in random order.
/// This means if n actions are specified and the n-th action has been executed,
Expand All @@ -183,6 +185,7 @@ public enum CompoundActionType : int
/// (if loop is true) or the compound action returns.
/// </summary>
RandomOrder = 1,

/// <summary>
/// Specifies that the inner actions should be executed randomly. That means
/// that some actions might be executed more often than others and some actions
Expand Down
9 changes: 9 additions & 0 deletions TTMouseclickSimulator/Core/Actions/IAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ public interface IAction
/// </summary>
event Action<string>? ActionInformationUpdated;

/// <summary>
/// Gets the simulator capabilities that are required by this action.
/// </summary>
/// <returns></returns>
SimulatorCapabilities RequiredCapabilities
{
get;
}

/// <summary>
/// Asynchonously runs the action using the specified IInteractionProvider.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion TTMouseclickSimulator/Core/Actions/IActionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public interface IActionContainer : IAction
/// </summary>
event Action<int?>? SubActionStartedOrStopped;

IList<IAction> SubActions { get; }
IReadOnlyList<IAction> SubActions { get; }
}
5 changes: 3 additions & 2 deletions TTMouseclickSimulator/Core/Actions/LoopAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ public LoopAction(IAction action, int? count = null)
this.count = count;
}

public override IList<IAction> SubActions
public override IReadOnlyList<IAction> SubActions
{
get => new List<IAction>() { this.action };
get => new IAction[] { this.action };
}

public override sealed async ValueTask RunAsync(IInteractionProvider provider)
{
this.OnSubActionStartedOrStopped(0);

try
{
for (int i = 0; !this.count.HasValue || i < this.count.Value; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,38 +107,26 @@ public unsafe WindowPosition GetWindowPosition(

var pos = new WindowPosition()
{
Coordinates = new Coordinates(relPos.x, relPos.y),
Coordinates = (relPos.x, relPos.y),
Size = new Size(clientRect.right, clientRect.bottom)
};

// Check if the window is minimized.
if (failIfMinimized && pos.IsMinimized)
throw new Exception("The window has been minimized.");

// Validate the position.
this.ValidateWindowPosition(pos);
return pos;
}

/// <summary>
/// When overridden in subclasses, throws an exception if the window position is
/// not valid. This implementation does nothing.
/// </summary>
/// <param name="pos">The WindowPosition to validate.</param>
protected virtual void ValidateWindowPosition(WindowPosition pos)
{
// Do nothing.
}

public void CreateWindowScreenshot(
IntPtr hWnd,
WindowPosition windowPosition,
[NotNull] ref ScreenshotContent? existingScreenshot,
bool failIfNotInForeground = true,
bool fromScreen = false)
{
ScreenshotContent.Create(
fromScreen ? IntPtr.Zero : hWnd,
this.GetWindowPosition(hWnd, out _, failIfNotInForeground),
windowPosition,
ref existingScreenshot);
}

Expand Down Expand Up @@ -170,6 +158,11 @@ public bool TrySetWindowTopmost(IntPtr hWnd, bool topmost, bool throwIfNotSucces
return result;
}

public void SetWindowEnabled(IntPtr hWnd, bool enabled)
{
_ = NativeMethods.EnableWindow(hWnd, enabled);
}

public void MoveMouse(int x, int y)
{
this.DoMouseInput(x, y, true, null);
Expand All @@ -188,13 +181,12 @@ public void ReleaseMouseButton()
private void DoMouseInput(int x, int y, bool absoluteCoordinates, bool? mouseDown)
{
// Convert the screen coordinates into mouse coordinates.
var cs = new Coordinates(x, y);
cs = this.GetMouseCoordinatesFromScreenCoordinates(cs);
var (mouseX, mouseY) = this.GetMouseCoordinatesFromScreenCoordinates(x, y);

var mi = new NativeMethods.MOUSEINPUT()
{
dx = cs.X,
dy = cs.Y
dx = mouseX,
dy = mouseY
};

if (absoluteCoordinates)
Expand All @@ -213,26 +205,27 @@ private void DoMouseInput(int x, int y, bool absoluteCoordinates, bool? mouseDow
NativeMethods.MOUSEEVENTF.LEFTUP;
}

var input = new NativeMethods.INPUT
Span<NativeMethods.INPUT> inputs = stackalloc NativeMethods.INPUT[1];
inputs[0] = new NativeMethods.INPUT
{
type = NativeMethods.InputType.INPUT_MOUSE,
InputUnion = {
mi = mi
}
};

NativeMethods.SendInput(input);
NativeMethods.SendInput(inputs);
}

private Coordinates GetMouseCoordinatesFromScreenCoordinates(Coordinates screenCoords)
private (int mouseX, int mouseY) GetMouseCoordinatesFromScreenCoordinates(int screenX, int screenY)
{
// Note: The mouse coordinates are relative to the primary monitor size and
// location, not to the virtual screen size, so we use
// SystemInformation.PrimaryMonitorSize.
var primaryScreenSize = SystemInformation.PrimaryMonitorSize;

double x = (double)0x10000 * screenCoords.X / primaryScreenSize.Width;
double y = (double)0x10000 * screenCoords.Y / primaryScreenSize.Height;
double x = (double)0x10000 * screenX / primaryScreenSize.Width;
double y = (double)0x10000 * screenY / primaryScreenSize.Height;

/* For correct conversion when converting the flointing point numbers
* to integers, we need round away from 0, e.g.
Expand All @@ -255,7 +248,7 @@ private Coordinates GetMouseCoordinatesFromScreenCoordinates(Coordinates screenC
int resX = checked((int)(x >= 0 ? Math.Ceiling(x) : Math.Floor(x)));
int resY = checked((int)(y >= 0 ? Math.Ceiling(y) : Math.Floor(y)));

return new Coordinates(resX, resY);
return (resX, resY);
}

public Coordinates GetCurrentMousePosition()
Expand Down Expand Up @@ -284,7 +277,8 @@ private void PressOrReleaseKey(VirtualKey keyCode, bool down)
if (!down)
ki.dwFlags = NativeMethods.KEYEVENTF.KEYUP;

var input = new NativeMethods.INPUT
Span<NativeMethods.INPUT> inputs = stackalloc NativeMethods.INPUT[1];
inputs[0] = new NativeMethods.INPUT
{
type = NativeMethods.InputType.INPUT_KEYBOARD,
InputUnion =
Expand All @@ -293,12 +287,15 @@ private void PressOrReleaseKey(VirtualKey keyCode, bool down)
}
};

NativeMethods.SendInput(input);
NativeMethods.SendInput(inputs);
}

public void WriteText(string characters)
{
var inputs = new NativeMethods.INPUT[2 * characters.Length];
int inputsLength = 2 * characters.Length;
var inputs = inputsLength <= 128 ?
stackalloc NativeMethods.INPUT[inputsLength] :
new NativeMethods.INPUT[inputsLength];

for (int i = 0; i < inputs.Length; i++)
{
Expand All @@ -323,7 +320,7 @@ public void WriteText(string characters)
inputs[i] = input;
}

NativeMethods.SendInputs(inputs);
NativeMethods.SendInput(inputs);
}

public void MoveWindowMouse(IntPtr hWnd, int x, int y, bool isButtonDown)
Expand Down Expand Up @@ -468,7 +465,7 @@ private ScreenshotContent(WindowPosition pos)

public Size Size
{
get => new Size(this.bmp.Width, this.bmp.Height);
get => new(this.bmp.Width, this.bmp.Height);
}

public WindowPosition WindowPosition
Expand Down Expand Up @@ -614,7 +611,9 @@ private void FillScreenshot(IntPtr windowHandle)

public ScreenshotColor GetPixel(Coordinates coords)
{
return this.GetPixel(coords.X, coords.Y);
return this.GetPixel(
checked((int)MathF.Round(coords.X)),
checked((int)MathF.Round(coords.Y)));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Loading

0 comments on commit 834a4ca

Please sign in to comment.