Skip to content

Commit 834a4ca

Browse files
committed
Introdice SimulatorCapabilities that can be queried from IActions, so that the simulator can better prepare accordingly.
1 parent ccd8bbd commit 834a4ca

29 files changed

+412
-192
lines changed

TTMouseclickSimulator/Core/Actions/AbstractAction.cs

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public abstract class AbstractAction : IAction
99
{
1010
public event Action<string>? ActionInformationUpdated;
1111

12+
public abstract SimulatorCapabilities RequiredCapabilities
13+
{
14+
get;
15+
}
16+
1217
public abstract ValueTask RunAsync(IInteractionProvider provider);
1318

1419
protected void OnActionInformationUpdated(string text)

TTMouseclickSimulator/Core/Actions/AbstractActionContainer.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,20 @@ public abstract class AbstractActionContainer : AbstractAction, IActionContainer
77
{
88
public event Action<int?>? SubActionStartedOrStopped;
99

10-
public abstract IList<IAction> SubActions { get; }
10+
public abstract IReadOnlyList<IAction> SubActions { get; }
11+
12+
public override SimulatorCapabilities RequiredCapabilities
13+
{
14+
get
15+
{
16+
var capabilities = default(SimulatorCapabilities);
17+
18+
foreach (var subAction in this.SubActions ?? Array.Empty<IAction>())
19+
capabilities |= subAction.RequiredCapabilities;
20+
21+
return capabilities;
22+
}
23+
}
1124

1225
protected void OnSubActionStartedOrStopped(int? index)
1326
{

TTMouseclickSimulator/Core/Actions/CompoundAction.cs

+11-8
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ public class CompoundAction : AbstractActionContainer
1414
public const int PauseIntervalMinimum = 0;
1515
public const int PauseIntervalMaximum = 600000;
1616

17-
private readonly IList<IAction> actionList;
17+
private readonly IReadOnlyList<IAction> actionList;
1818
private readonly CompoundActionType type;
1919

2020
private readonly int minimumPauseDuration;
2121
private readonly int maximumPauseDuration;
2222
private readonly bool loop;
2323

24-
private readonly Random rng = new Random();
24+
private readonly Random rng = new();
2525

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

4849
if (minimumPause < PauseIntervalMinimum
4950
|| minimumPause > PauseIntervalMaximum
@@ -61,7 +62,7 @@ public CompoundAction(
6162
if (type is CompoundActionType.RandomIndex && !loop)
6263
throw new ArgumentException(
6364
"When using CompoundActionType.RandomIndex, it is not possible " +
64-
" to disable the loop.");
65+
"to disable the loop.");
6566

6667
this.actionList = actionList;
6768
this.type = type;
@@ -70,7 +71,7 @@ public CompoundAction(
7071
this.loop = loop;
7172
}
7273

73-
public override sealed IList<IAction> SubActions
74+
public override sealed IReadOnlyList<IAction> SubActions
7475
{
7576
get => this.actionList;
7677
}
@@ -79,8 +80,8 @@ public override sealed async ValueTask RunAsync(IInteractionProvider provider)
7980
{
8081
// Run the actions.
8182
int currentIdx = -1;
82-
8383
Func<int> getNextActionIndex;
84+
8485
if (this.type is CompoundActionType.Sequential)
8586
{
8687
getNextActionIndex = () =>
@@ -150,7 +151,7 @@ public override sealed async ValueTask RunAsync(IInteractionProvider provider)
150151

151152
await provider.WaitAsync(waitInterval);
152153
}
153-
catch (Exception ex) when (!(ex is OperationCanceledException))
154+
catch (Exception ex) when (ex is not OperationCanceledException)
154155
{
155156
await provider.CheckRetryForExceptionAsync(ex);
156157
continue;
@@ -175,6 +176,7 @@ public enum CompoundActionType : int
175176
/// (if loop is true) or the compound action returns.
176177
/// </summary>
177178
Sequential = 0,
179+
178180
/// <summary>
179181
/// Specifies that the inner actions should be executed in random order.
180182
/// This means if n actions are specified and the n-th action has been executed,
@@ -183,6 +185,7 @@ public enum CompoundActionType : int
183185
/// (if loop is true) or the compound action returns.
184186
/// </summary>
185187
RandomOrder = 1,
188+
186189
/// <summary>
187190
/// Specifies that the inner actions should be executed randomly. That means
188191
/// that some actions might be executed more often than others and some actions

TTMouseclickSimulator/Core/Actions/IAction.cs

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ public interface IAction
1919
/// </summary>
2020
event Action<string>? ActionInformationUpdated;
2121

22+
/// <summary>
23+
/// Gets the simulator capabilities that are required by this action.
24+
/// </summary>
25+
/// <returns></returns>
26+
SimulatorCapabilities RequiredCapabilities
27+
{
28+
get;
29+
}
30+
2231
/// <summary>
2332
/// Asynchonously runs the action using the specified IInteractionProvider.
2433
/// </summary>

TTMouseclickSimulator/Core/Actions/IActionContainer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ public interface IActionContainer : IAction
1111
/// </summary>
1212
event Action<int?>? SubActionStartedOrStopped;
1313

14-
IList<IAction> SubActions { get; }
14+
IReadOnlyList<IAction> SubActions { get; }
1515
}

TTMouseclickSimulator/Core/Actions/LoopAction.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ public LoopAction(IAction action, int? count = null)
2929
this.count = count;
3030
}
3131

32-
public override IList<IAction> SubActions
32+
public override IReadOnlyList<IAction> SubActions
3333
{
34-
get => new List<IAction>() { this.action };
34+
get => new IAction[] { this.action };
3535
}
3636

3737
public override sealed async ValueTask RunAsync(IInteractionProvider provider)
3838
{
3939
this.OnSubActionStartedOrStopped(0);
40+
4041
try
4142
{
4243
for (int i = 0; !this.count.HasValue || i < this.count.Value; i++)

TTMouseclickSimulator/Core/Environment/AbstractWindowsEnvironment.cs

+30-31
Original file line numberDiff line numberDiff line change
@@ -107,38 +107,26 @@ public unsafe WindowPosition GetWindowPosition(
107107

108108
var pos = new WindowPosition()
109109
{
110-
Coordinates = new Coordinates(relPos.x, relPos.y),
110+
Coordinates = (relPos.x, relPos.y),
111111
Size = new Size(clientRect.right, clientRect.bottom)
112112
};
113113

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

118-
// Validate the position.
119-
this.ValidateWindowPosition(pos);
120118
return pos;
121119
}
122120

123-
/// <summary>
124-
/// When overridden in subclasses, throws an exception if the window position is
125-
/// not valid. This implementation does nothing.
126-
/// </summary>
127-
/// <param name="pos">The WindowPosition to validate.</param>
128-
protected virtual void ValidateWindowPosition(WindowPosition pos)
129-
{
130-
// Do nothing.
131-
}
132-
133121
public void CreateWindowScreenshot(
134122
IntPtr hWnd,
123+
WindowPosition windowPosition,
135124
[NotNull] ref ScreenshotContent? existingScreenshot,
136-
bool failIfNotInForeground = true,
137125
bool fromScreen = false)
138126
{
139127
ScreenshotContent.Create(
140128
fromScreen ? IntPtr.Zero : hWnd,
141-
this.GetWindowPosition(hWnd, out _, failIfNotInForeground),
129+
windowPosition,
142130
ref existingScreenshot);
143131
}
144132

@@ -170,6 +158,11 @@ public bool TrySetWindowTopmost(IntPtr hWnd, bool topmost, bool throwIfNotSucces
170158
return result;
171159
}
172160

161+
public void SetWindowEnabled(IntPtr hWnd, bool enabled)
162+
{
163+
_ = NativeMethods.EnableWindow(hWnd, enabled);
164+
}
165+
173166
public void MoveMouse(int x, int y)
174167
{
175168
this.DoMouseInput(x, y, true, null);
@@ -188,13 +181,12 @@ public void ReleaseMouseButton()
188181
private void DoMouseInput(int x, int y, bool absoluteCoordinates, bool? mouseDown)
189182
{
190183
// Convert the screen coordinates into mouse coordinates.
191-
var cs = new Coordinates(x, y);
192-
cs = this.GetMouseCoordinatesFromScreenCoordinates(cs);
184+
var (mouseX, mouseY) = this.GetMouseCoordinatesFromScreenCoordinates(x, y);
193185

194186
var mi = new NativeMethods.MOUSEINPUT()
195187
{
196-
dx = cs.X,
197-
dy = cs.Y
188+
dx = mouseX,
189+
dy = mouseY
198190
};
199191

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

216-
var input = new NativeMethods.INPUT
208+
Span<NativeMethods.INPUT> inputs = stackalloc NativeMethods.INPUT[1];
209+
inputs[0] = new NativeMethods.INPUT
217210
{
218211
type = NativeMethods.InputType.INPUT_MOUSE,
219212
InputUnion = {
220213
mi = mi
221214
}
222215
};
223216

224-
NativeMethods.SendInput(input);
217+
NativeMethods.SendInput(inputs);
225218
}
226219

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

234-
double x = (double)0x10000 * screenCoords.X / primaryScreenSize.Width;
235-
double y = (double)0x10000 * screenCoords.Y / primaryScreenSize.Height;
227+
double x = (double)0x10000 * screenX / primaryScreenSize.Width;
228+
double y = (double)0x10000 * screenY / primaryScreenSize.Height;
236229

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

258-
return new Coordinates(resX, resY);
251+
return (resX, resY);
259252
}
260253

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

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

296-
NativeMethods.SendInput(input);
290+
NativeMethods.SendInput(inputs);
297291
}
298292

299293
public void WriteText(string characters)
300294
{
301-
var inputs = new NativeMethods.INPUT[2 * characters.Length];
295+
int inputsLength = 2 * characters.Length;
296+
var inputs = inputsLength <= 128 ?
297+
stackalloc NativeMethods.INPUT[inputsLength] :
298+
new NativeMethods.INPUT[inputsLength];
302299

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

326-
NativeMethods.SendInputs(inputs);
323+
NativeMethods.SendInput(inputs);
327324
}
328325

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

469466
public Size Size
470467
{
471-
get => new Size(this.bmp.Width, this.bmp.Height);
468+
get => new(this.bmp.Width, this.bmp.Height);
472469
}
473470

474471
public WindowPosition WindowPosition
@@ -614,7 +611,9 @@ private void FillScreenshot(IntPtr windowHandle)
614611

615612
public ScreenshotColor GetPixel(Coordinates coords)
616613
{
617-
return this.GetPixel(coords.X, coords.Y);
614+
return this.GetPixel(
615+
checked((int)MathF.Round(coords.X)),
616+
checked((int)MathF.Round(coords.Y)));
618617
}
619618

620619
[MethodImpl(MethodImplOptions.AggressiveInlining)]

0 commit comments

Comments
 (0)