Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#nullable enable
using System;

using io.github.hatayama.UnityCliLoop.ToolContracts;

namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
{
/// <summary>
Expand Down Expand Up @@ -29,6 +31,12 @@ public static InputSimulationRunInBackgroundScope Enable()

public void Dispose()
{
// Precondition: Application.runInBackground is main-thread-only, so callers resuming
// from ConfigureAwait(false) continuations must switch back to the main thread first.
UnityEngine.Debug.Assert(
MainThreadSwitcher.IsMainThread,
"InputSimulationRunInBackgroundScope.Dispose must be called on the main thread.");

if (_isDisposed)
{
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"name": "UnityCLILoop.FirstPartyTools.Common.InputSimulation.Editor",
"rootNamespace": "io.github.hatayama.UnityCliLoop.FirstPartyTools",
"references": [],
"references": [
"GUID:fc3fd32eddbee40e39c2d76dc184957b"
],
"includePlatforms": [
"Editor"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,49 +104,59 @@ public async Task<SimulateKeyboardResponse> ExecuteAsync(
correlationId: correlationId
);

using InputSimulationRunInBackgroundScope runInBackgroundScope = InputSimulationRunInBackgroundScope.Enable();

EnsureOverlayExists();
// Why not `using`: the executor awaits below use ConfigureAwait(false), so this method
// can resume on a thread-pool thread. Application.runInBackground is main-thread-only,
// so the scope must be disposed after switching back to the main thread.
InputSimulationRunInBackgroundScope runInBackgroundScope = InputSimulationRunInBackgroundScope.Enable();
try
{
EnsureOverlayExists();

SimulateKeyboardResponse response;
SimulateKeyboardResponse response;

switch (parameters.Action)
switch (parameters.Action)
{
case UnityCliLoopKeyboardAction.Press:
response = await KeyboardInputActionExecutor.ExecutePress(
keyboard,
key,
parameters.Duration,
ct).ConfigureAwait(false);
break;

case UnityCliLoopKeyboardAction.KeyDown:
response = await KeyboardInputActionExecutor.ExecuteKeyDown(keyboard, key, ct).ConfigureAwait(false);
break;

case UnityCliLoopKeyboardAction.KeyUp:
response = await KeyboardInputActionExecutor.ExecuteKeyUp(keyboard, key, ct).ConfigureAwait(false);
break;

default:
// Only reachable when an out-of-range enum value is cast from an integer;
// surface as a Success=false response so the CLI treats it as a normal validation failure.
return new SimulateKeyboardResponse
{
Success = false,
Message = $"Unknown keyboard action: {parameters.Action}",
Action = parameters.Action.ToString()
};
}

VibeLogger.LogInfo(
"simulate_keyboard_complete",
$"Keyboard simulation completed: {response.Message}",
new { Action = parameters.Action.ToString(), Success = response.Success },
correlationId: correlationId
);

return response;
}
finally
{
case UnityCliLoopKeyboardAction.Press:
response = await KeyboardInputActionExecutor.ExecutePress(
keyboard,
key,
parameters.Duration,
ct).ConfigureAwait(false);
break;

case UnityCliLoopKeyboardAction.KeyDown:
response = await KeyboardInputActionExecutor.ExecuteKeyDown(keyboard, key, ct).ConfigureAwait(false);
break;

case UnityCliLoopKeyboardAction.KeyUp:
response = await KeyboardInputActionExecutor.ExecuteKeyUp(keyboard, key, ct).ConfigureAwait(false);
break;

default:
// Only reachable when an out-of-range enum value is cast from an integer;
// surface as a Success=false response so the CLI treats it as a normal validation failure.
return new SimulateKeyboardResponse
{
Success = false,
Message = $"Unknown keyboard action: {parameters.Action}",
Action = parameters.Action.ToString()
};
await InputSystemUpdateHelper.SwitchToMainThreadIfNeeded(CancellationToken.None);
runInBackgroundScope.Dispose();
}

VibeLogger.LogInfo(
"simulate_keyboard_complete",
$"Keyboard simulation completed: {response.Message}",
new { Action = parameters.Action.ToString(), Success = response.Success },
correlationId: correlationId
);

return response;
#endif
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,46 +97,56 @@ public async Task<SimulateMouseInputResponse> ExecuteAsync(
correlationId: correlationId
);

using InputSimulationRunInBackgroundScope runInBackgroundScope = InputSimulationRunInBackgroundScope.Enable();

EnsureOverlayExists();

SimulateMouseInputResponse response;

switch (parameters.Action)
// Why not `using`: the executor awaits below use ConfigureAwait(false), so this method
// can resume on a thread-pool thread. Application.runInBackground is main-thread-only,
// so the scope must be disposed after switching back to the main thread.
InputSimulationRunInBackgroundScope runInBackgroundScope = InputSimulationRunInBackgroundScope.Enable();
try
{
case UnityCliLoopMouseInputAction.Click:
response = await MouseInputPressActionExecutor.ExecuteClick(mouse, parameters, ct).ConfigureAwait(false);
break;

case UnityCliLoopMouseInputAction.LongPress:
response = await MouseInputPressActionExecutor.ExecuteLongPress(mouse, parameters, ct).ConfigureAwait(false);
break;
EnsureOverlayExists();

case UnityCliLoopMouseInputAction.MoveDelta:
response = await MouseInputMotionActionExecutor.ExecuteMoveDelta(mouse, parameters, ct).ConfigureAwait(false);
break;
SimulateMouseInputResponse response;

case UnityCliLoopMouseInputAction.Scroll:
response = await MouseInputMotionActionExecutor.ExecuteScroll(mouse, parameters, ct).ConfigureAwait(false);
break;

case UnityCliLoopMouseInputAction.SmoothDelta:
response = await MouseInputMotionActionExecutor.ExecuteSmoothDelta(mouse, parameters, ct).ConfigureAwait(false);
break;

default:
throw new ArgumentException($"Unknown mouse input action: {parameters.Action}");
switch (parameters.Action)
{
case UnityCliLoopMouseInputAction.Click:
response = await MouseInputPressActionExecutor.ExecuteClick(mouse, parameters, ct).ConfigureAwait(false);
break;

case UnityCliLoopMouseInputAction.LongPress:
response = await MouseInputPressActionExecutor.ExecuteLongPress(mouse, parameters, ct).ConfigureAwait(false);
break;

case UnityCliLoopMouseInputAction.MoveDelta:
response = await MouseInputMotionActionExecutor.ExecuteMoveDelta(mouse, parameters, ct).ConfigureAwait(false);
break;

case UnityCliLoopMouseInputAction.Scroll:
response = await MouseInputMotionActionExecutor.ExecuteScroll(mouse, parameters, ct).ConfigureAwait(false);
break;

case UnityCliLoopMouseInputAction.SmoothDelta:
response = await MouseInputMotionActionExecutor.ExecuteSmoothDelta(mouse, parameters, ct).ConfigureAwait(false);
break;

default:
throw new ArgumentException($"Unknown mouse input action: {parameters.Action}");
}

VibeLogger.LogInfo(
"simulate_mouse_input_complete",
$"Mouse input simulation completed: {response.Message}",
new { Action = parameters.Action.ToString(), Success = response.Success },
correlationId: correlationId
);

return response;
}
finally
{
await InputSystemUpdateHelper.SwitchToMainThreadIfNeeded(CancellationToken.None);
runInBackgroundScope.Dispose();
}

VibeLogger.LogInfo(
"simulate_mouse_input_complete",
$"Mouse input simulation completed: {response.Message}",
new { Action = parameters.Action.ToString(), Success = response.Success },
correlationId: correlationId
);

return response;
#endif
}

Expand Down