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
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
.claude/
PLAN.md
CLAUDE.md
AGENTS.md

# Build results
[Dd]ebug/
Expand Down Expand Up @@ -340,4 +344,6 @@ ASALocalRun/
# BeatPulse healthcheck temp database
healthchecksdb
/ExternalResources
dist/
dist/
# Added by code-review-graph
.code-review-graph/
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ItemGroup>
<PackageVersion Include="AvalonEdit" Version="6.3.1.120" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="12.0.0" />
<PackageVersion Include="GamepadApi" Version="1.0.1" />
<PackageVersion Include="System.CommandLine" Version="3.0.0-preview.2.26159.112" />
<PackageVersion Include="System.IO.Ports" Version="10.0.6" />
<PackageVersion Include="System.Management" Version="10.0.6" />
Expand Down
3 changes: 3 additions & 0 deletions EasyCon2.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@
</Folder>
<Folder Name="/Test/">
<Project Path="test/EasyCon.Tests/EasyCon.Tests.csproj" />
<Project Path="test/EasyCon.WinInput.Tests/EasyCon.WinInput.Tests.csproj" />
</Folder>
<Folder Name="/Demo/">
<Project Path="tools/DirectInput/DirectInput.csproj" />
<Project Path="tools/GamepadApiDemo/GamepadApiDemo.csproj" />
</Folder>
<Folder Name="/Lib/">
<Project Path="src/EasyCon.Capture/EasyCon.Capture.csproj" />
<Project Path="src/EasyCon.Core/EasyCon.Core.csproj" />
<Project Path="src/EasyCon.Device/EasyCon.Device.csproj" />
<Project Path="src/EasyCon.Script/EasyCon.Script.csproj" />
<Project Path="src/EasyCon2.Avalonia.Core/EasyCon2.Avalonia.Core.csproj" />
<Project Path="src/EasyCon.WinInput/EasyCon.WinInput.csproj" />
</Folder>
<Folder Name="/Common/">
<Project Path="src/EasyCon2.UI.Common/EasyCon2.UI.Common.csproj" />
Expand Down
17 changes: 0 additions & 17 deletions PLAN.md

This file was deleted.

3 changes: 1 addition & 2 deletions ci/test.bat
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ chcp 65001 >nul
setlocal enabledelayedexpansion

set "ROOT_DIR=%~dp0.."
set "TFM=net10.0"
set "CONFIG=%1"
if "%CONFIG%"=="" set "CONFIG=Release"

echo Running tests (%CONFIG%)...
dotnet test "%ROOT_DIR%\EasyCon2.slnx" --nologo -c %CONFIG% -f %TFM%
dotnet test "%ROOT_DIR%\EasyCon2.slnx" --nologo -c %CONFIG%
if errorlevel 1 (
echo ERROR: Tests failed
pause
Expand Down
16 changes: 8 additions & 8 deletions src/EasyCon.Core/Config/KeyMappingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ public record KeyMappingConfig
public int B { get; set; } = 75; // Keys.K
public int X { get; set; } = 73; // Keys.I
public int Y { get; set; } = 74; // Keys.J
public int L { get; set; } = 85; // Keys.U
public int R { get; set; } = 79; // Keys.O
public int ZL { get; set; } = 81; // Keys.Q
public int ZR { get; set; } = 69; // Keys.E
public int L { get; set; } = 71; // Keys.G
public int R { get; set; } = 84; // Keys.T
public int ZL { get; set; } = 70; // Keys.F
public int ZR { get; set; } = 82; // Keys.R
public int Plus { get; set; } = 107; // Numpad Add
public int Minus { get; set; } = 109; // Numpad Subtract
public int Capture { get; set; } = 90; // Keys.Z
public int Home { get; set; } = 67; // Keys.C
public int LClick { get; set; } = 81; // Keys.Q
public int RClick { get; set; } = 69; // Keys.E
public int Up { get; set; } = 38; // Keys.Up
public int Down { get; set; } = 40; // Keys.Down
public int Left { get; set; } = 37; // Keys.Left
public int Right { get; set; } = 39; // Keys.Right
public int Up { get; set; } = 0;
public int Down { get; set; } = 0;
public int Left { get; set; } = 0;
public int Right { get; set; } = 0;
public int UpRight { get; set; } = 0;
public int DownRight { get; set; } = 0;
public int UpLeft { get; set; } = 0;
Expand Down
13 changes: 13 additions & 0 deletions src/EasyCon.Core/Input/IInputBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using EasyCon.Core.Config;
using EasyDevice;

namespace EasyCon.Core.Input;

public interface IInputBinder
{
void Start();
void Stop();
void SetEnabled(bool enabled);
void UpdateKeyMapping(KeyMappingConfig mapping);
void RegisterEscapeKey(Func<bool> keydown, Func<bool> keyup);
}
15 changes: 15 additions & 0 deletions src/EasyCon.Device/NintendoSwitchPriv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ public partial class NintendoSwitch
DateTime _nextSendTime = DateTime.MinValue;
private readonly EventWaitHandle _ewh = new(false, EventResetMode.ManualReset);

public void ApplyReport(SwitchReport report)
{
lock (this)
{
_keystrokes.Clear();
_report.Button = report.Button;
_report.HAT = report.HAT;
_report.LX = report.LX;
_report.LY = report.LY;
_report.RX = report.RX;
_report.RY = report.RY;
Signal();
}
}

void Signal()
{
if (this.IsConnected())
Expand Down
12 changes: 12 additions & 0 deletions src/EasyCon.WinInput/EasyCon.WinInput.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>warnings</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GamepadApi" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EasyCon.Core\EasyCon.Core.csproj" />
</ItemGroup>
</Project>
50 changes: 50 additions & 0 deletions src/EasyCon.WinInput/GamepadInputBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using EasyCon.Core.Config;
using EasyCon.Core.Input;
using EasyDevice;
using GamepadApi;
using System.Runtime.Versioning;

namespace EasyCon.WinInput;

[SupportedOSPlatform("windows")]
public class GamepadInputBinder : IInputBinder
{
private readonly GamepadManager _manager;
private readonly int _deviceIndex;
private readonly NintendoSwitch _ns;
private readonly GamepadMappingConfig _config;
private volatile bool _enabled;

public GamepadInputBinder(GamepadManager manager, int deviceIndex, NintendoSwitch ns, GamepadMappingConfig config)
{
_manager = manager;
_deviceIndex = deviceIndex;
_ns = ns;
_config = config;
}

public void Start()
{
_manager.StateChanged += OnStateChanged;
}

public void Stop()
{
_manager.StateChanged -= OnStateChanged;
_ns.Reset();
}

public void SetEnabled(bool enabled) => _enabled = enabled;

public void UpdateKeyMapping(KeyMappingConfig mapping) { }

public void RegisterEscapeKey(Func<bool> keydown, Func<bool> keyup) { }

private void OnStateChanged(GamepadDevice device, GamepadState state)
{
if (!_enabled) return;
if (device.Index != _deviceIndex) return;
var report = GamepadMapper.Map(state, _config);
_ns.ApplyReport(report);
}
}
81 changes: 81 additions & 0 deletions src/EasyCon.WinInput/GamepadMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using EasyDevice;
using GamepadApi;
using System.Numerics;

namespace EasyCon.WinInput;

public static class GamepadMapper
{
public static SwitchReport Map(GamepadState state, GamepadMappingConfig config)
{
var report = new SwitchReport();

MapButtons(state.Buttons, config.IsSwitchXY, report);
MapTriggers(state.LeftTrigger, state.RightTrigger, config.TriggerThreshold, report);
MapHat(state.Buttons, report);
(report.LX, report.LY) = MapStick(state.LeftStick);
(report.RX, report.RY) = MapStick(state.RightStick);

return report;
}

static void MapButtons(GamepadButtons buttons, bool isSwitchXY, SwitchReport report)
{
if (isSwitchXY)
{
if (buttons.HasFlag(GamepadButtons.A)) report.Button |= (ushort)SwitchButton.B;
if (buttons.HasFlag(GamepadButtons.B)) report.Button |= (ushort)SwitchButton.A;
if (buttons.HasFlag(GamepadButtons.X)) report.Button |= (ushort)SwitchButton.Y;
if (buttons.HasFlag(GamepadButtons.Y)) report.Button |= (ushort)SwitchButton.X;
}
else
{
if (buttons.HasFlag(GamepadButtons.A)) report.Button |= (ushort)SwitchButton.A;
if (buttons.HasFlag(GamepadButtons.B)) report.Button |= (ushort)SwitchButton.B;
if (buttons.HasFlag(GamepadButtons.X)) report.Button |= (ushort)SwitchButton.X;
if (buttons.HasFlag(GamepadButtons.Y)) report.Button |= (ushort)SwitchButton.Y;
}

if (buttons.HasFlag(GamepadButtons.LeftShoulder)) report.Button |= (ushort)SwitchButton.L;
if (buttons.HasFlag(GamepadButtons.RightShoulder)) report.Button |= (ushort)SwitchButton.R;
if (buttons.HasFlag(GamepadButtons.LeftThumb)) report.Button |= (ushort)SwitchButton.LCLICK;
if (buttons.HasFlag(GamepadButtons.RightThumb)) report.Button |= (ushort)SwitchButton.RCLICK;
if (buttons.HasFlag(GamepadButtons.Start)) report.Button |= (ushort)SwitchButton.PLUS;
if (buttons.HasFlag(GamepadButtons.Back)) report.Button |= (ushort)SwitchButton.MINUS;
}

static void MapTriggers(float left, float right, float threshold, SwitchReport report)
{
if (left > threshold) report.Button |= (ushort)SwitchButton.ZL;
if (right > threshold) report.Button |= (ushort)SwitchButton.ZR;
}

static void MapHat(GamepadButtons buttons, SwitchReport report)
{
var up = buttons.HasFlag(GamepadButtons.DPadUp);
var down = buttons.HasFlag(GamepadButtons.DPadDown);
var left = buttons.HasFlag(GamepadButtons.DPadLeft);
var right = buttons.HasFlag(GamepadButtons.DPadRight);

report.HAT = (up, down, left, right) switch
{
(true, false, false, false) => (byte)SwitchHAT.TOP,
(false, true, false, false) => (byte)SwitchHAT.BOTTOM,
(false, false, true, false) => (byte)SwitchHAT.LEFT,
(false, false, false, true) => (byte)SwitchHAT.RIGHT,
(true, false, true, false) => (byte)SwitchHAT.TOP_LEFT,
(true, false, false, true) => (byte)SwitchHAT.TOP_RIGHT,
(false, true, true, false) => (byte)SwitchHAT.BOTTOM_LEFT,
(false, true, false, true) => (byte)SwitchHAT.BOTTOM_RIGHT,
_ => (byte)SwitchHAT.CENTER,
};
}

static (byte x, byte y) MapStick(Vector2 stick)
{
return (
(byte)Math.Clamp((int)(stick.X * 127) + 128, 0, 255),
(byte)Math.Clamp((int)(-stick.Y * 127) + 128, 0, 255)
);
}
}
7 changes: 7 additions & 0 deletions src/EasyCon.WinInput/GamepadMappingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace EasyCon.WinInput;

public record GamepadMappingConfig
{
public bool IsSwitchXY { get; init; } = true;
public float TriggerThreshold { get; init; } = 0.1f;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using EasyCon.Core.Config;
using EasyCon.Core.Input;
using EasyDevice;
using System.Runtime.Versioning;

namespace EasyCon2.Avalonia.Core.VPad;
namespace EasyCon.WinInput;

[SupportedOSPlatform("windows")]
public class KeyBinder
Expand Down Expand Up @@ -81,4 +82,9 @@ public void UnregisterAllKeys()
{
LowLevelKeyboard.GetInstance().UnregisterKeyEventAll();
}

public void RegisterEscapeKey(Func<bool> keydown, Func<bool> keyup)
{
LowLevelKeyboard.GetInstance().RegisterKeyEvent(0x1B, keydown, keyup);
}
}
32 changes: 32 additions & 0 deletions src/EasyCon.WinInput/KeyboardInputBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using EasyCon.Core.Config;
using EasyCon.Core.Input;
using EasyDevice;
using System.Runtime.Versioning;

namespace EasyCon.WinInput;

[SupportedOSPlatform("windows")]
public class KeyboardInputBinder : IInputBinder
{
private readonly KeyBinder _keyBinder = new();
private readonly NintendoSwitch _ns;
private KeyMappingConfig _mapping;

public KeyboardInputBinder(NintendoSwitch ns, KeyMappingConfig mapping)
{
_ns = ns;
_mapping = mapping;
}

public void Start() => _keyBinder.RegisterAllKeys(_mapping, _ns);
public void Stop() => _keyBinder.UnregisterAllKeys();
public void SetEnabled(bool enabled) => _keyBinder.ControllerEnabled = enabled;
public void UpdateKeyMapping(KeyMappingConfig mapping)
{
_mapping = mapping;
_keyBinder.RegisterAllKeys(_mapping, _ns);
}

public void RegisterEscapeKey(Func<bool> keydown, Func<bool> keyup)
=> _keyBinder.RegisterEscapeKey(keydown, keyup);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace EasyCon2.Avalonia.Core.VPad;
namespace EasyCon.WinInput;

[SupportedOSPlatform("windows")]
internal class LowLevelKeyboard
class LowLevelKeyboard
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
Expand Down
8 changes: 4 additions & 4 deletions src/EasyCon2.Avalonia.Core/VPad/JCPainter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ internal class JCPainter(IReporter repoter, IControllerAdapter adapter, GetJCImg
{
private DateTime _starttime = DateTime.MinValue;
private static readonly Color _gray50 = Color.FromRgb(50, 50, 50);
private readonly Brush _brushLightDefault = new SolidColorBrush(Colors.Black, 50 / 255);
private readonly Brush _brushLightDefault = new SolidColorBrush(Colors.Black, 130.0 / 255.0);
private readonly Brush _brushStickBG = new SolidColorBrush(_gray50);
private readonly Brush _brushStickBGDown = new SolidColorBrush(Colors.Lime);
private readonly Brush _brushStickAreaBG = new SolidColorBrush(Colors.Black, 50 / 255);
private readonly Brush _brushStickAreaBGDown = new SolidColorBrush(Colors.Lime, 200 / 255);
private readonly Brush _brushStickAreaBG = new SolidColorBrush(Colors.Black, 50.0 / 255.0);
private readonly Brush _brushStickAreaBGDown = new SolidColorBrush(Colors.Lime, 200.0 / 255.0);
private readonly Brush _brushButtonDown = new SolidColorBrush(Colors.Lime);
private readonly Brush _brushButtonUp = new SolidColorBrush(_gray50);

Expand Down Expand Up @@ -67,7 +67,7 @@ public void OnPaint(DrawingContext g, Rect rectangle)
DrawRectangle(
g,
_pen,
new SolidColorBrush(adapter.CurrentLight, 50 / 255),
new SolidColorBrush(adapter.CurrentLight, 130.0 / 255.0),
x,
y,
w,
Expand Down
Loading
Loading