feat: Added wiimote support and cleaned up the project
This commit is contained in:
parent
36574bdf46
commit
baf15cdb0f
25 changed files with 263 additions and 69 deletions
27
SlidePresenter/ControllerSelector.cs
Normal file
27
SlidePresenter/ControllerSelector.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
namespace SwitchSlidePresenter;
|
||||
|
||||
public static class ControllerSelector {
|
||||
public static IGamepadReader? GetReader() {
|
||||
Console.WriteLine("Write a number to select controller type:");
|
||||
Console.WriteLine("[1] - JoyCon");
|
||||
Console.WriteLine("[2] - Wiimote");
|
||||
|
||||
string? line = Console.ReadLine();
|
||||
if (line == null) {
|
||||
Console.WriteLine("Invalid input.");
|
||||
return null;
|
||||
}
|
||||
if (!int.TryParse(line, out int id)) {
|
||||
Console.WriteLine("Invalid number.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetReader(id);
|
||||
}
|
||||
|
||||
private static IGamepadReader? GetReader(int id) => id switch {
|
||||
1 => new JoyConRead(),
|
||||
2 => new WiimoteRead(),
|
||||
_ => null
|
||||
};
|
||||
}
|
8
SlidePresenter/IGamepadReader.cs
Normal file
8
SlidePresenter/IGamepadReader.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace SwitchSlidePresenter;
|
||||
|
||||
public interface IGamepadReader {
|
||||
public event Action NextSlide;
|
||||
public event Action PrevSlide;
|
||||
|
||||
public Task Read();
|
||||
}
|
117
SlidePresenter/JoyConRead.cs
Normal file
117
SlidePresenter/JoyConRead.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
using System.Text;
|
||||
using HidSharp;
|
||||
using wtf.cluster.JoyCon;
|
||||
using wtf.cluster.JoyCon.ExtraData;
|
||||
using wtf.cluster.JoyCon.InputData;
|
||||
using wtf.cluster.JoyCon.InputReports;
|
||||
|
||||
namespace SwitchSlidePresenter;
|
||||
|
||||
public class JoyConRead : IGamepadReader {
|
||||
public event Action NextSlide;
|
||||
public event Action PrevSlide;
|
||||
|
||||
public async Task Read() {
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
HidDevice? device = GetHidDevice();
|
||||
if (device == null) {
|
||||
Console.WriteLine("No controller. Please connect Joy-Con or Pro controller via Bluetooth.");
|
||||
Console.WriteLine("Press any key to exit program.");
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
JoyCon joycon = new(device);
|
||||
joycon.Start();
|
||||
await joycon.SetInputReportModeAsync(JoyCon.InputReportType.Simple);
|
||||
|
||||
await LogDeviceInfo(joycon);
|
||||
|
||||
joycon.ReportReceived += OnJoyConOnReportReceived;
|
||||
|
||||
joycon.StoppedOnError += (_, ex) => {
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Critical error: {ex.Message}");
|
||||
Console.WriteLine("Controller polling stopped.");
|
||||
Environment.Exit(1);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
Console.WriteLine("JoyCon ready for presenting.");
|
||||
Console.WriteLine("Press Enter to exit program.");
|
||||
while (Console.ReadKey().Key != ConsoleKey.Enter) { }
|
||||
joycon.Stop();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Stopped.");
|
||||
}
|
||||
|
||||
private static async Task LogDeviceInfo(JoyCon joycon) {
|
||||
DeviceInfo deviceInfo = await joycon.GetDeviceInfoAsync();
|
||||
Console.WriteLine(
|
||||
$"Type: {deviceInfo.ControllerType}, Firmware: {deviceInfo.FirmwareVersionMajor}.{deviceInfo.FirmwareVersionMinor}");
|
||||
string? serial = await joycon.GetSerialNumberAsync();
|
||||
Console.WriteLine($"Serial number: {serial ?? "<none>"}");
|
||||
ControllerColors? colors = await joycon.GetColorsAsync();
|
||||
Console.WriteLine(colors != null
|
||||
? $"Body color: {colors.BodyColor}, buttons color: {colors.ButtonsColor}"
|
||||
: "Colors not specified, seems like the controller is grey.");
|
||||
}
|
||||
|
||||
private static HidDevice? GetHidDevice() {
|
||||
DeviceList list = DeviceList.Local;
|
||||
HidDevice? device = null;
|
||||
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
var nintendos = list.GetHidDevices(0x057e);
|
||||
device = nintendos.FirstOrDefault();
|
||||
} else {
|
||||
var hidDevices = list.GetHidDevices();
|
||||
foreach (var d in hidDevices) {
|
||||
var rd = d.GetReportDescriptor();
|
||||
if (rd != null) {
|
||||
if (
|
||||
rd.OutputReports.Count() == 4
|
||||
&& rd.OutputReports.Count(r => r.ReportID == 0x01) == 1
|
||||
&& rd.OutputReports.Count(r => r.ReportID == 0x10) == 1
|
||||
&& rd.OutputReports.Count(r => r.ReportID == 0x11) == 1
|
||||
&& rd.OutputReports.Count(r => r.ReportID == 0x12) == 1
|
||||
&& rd.InputReports.Count() == 6
|
||||
&& rd.InputReports.Count(r => r.ReportID == 0x21) == 1
|
||||
&& rd.InputReports.Count(r => r.ReportID == 0x30) == 1
|
||||
&& rd.InputReports.Count(r => r.ReportID == 0x31) == 1
|
||||
&& rd.InputReports.Count(r => r.ReportID == 0x32) == 1
|
||||
&& rd.InputReports.Count(r => r.ReportID == 0x33) == 1
|
||||
&& rd.InputReports.Count(r => r.ReportID == 0x3F) == 1
|
||||
) {
|
||||
device = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
private Task OnJoyConOnReportReceived(JoyCon _, IJoyConReport input) {
|
||||
if (input is not InputSimple j) {
|
||||
Console.WriteLine($"Invalid input report type: {input.GetType()}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
bool prev = PreviousPressed(j.Buttons);
|
||||
bool next = NextPressed(j.Buttons);
|
||||
if (prev)
|
||||
PrevSlide?.Invoke();
|
||||
if (next)
|
||||
NextSlide?.Invoke();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static bool PreviousPressed(ButtonsSimple input) {
|
||||
return input.LorR || input.Left;
|
||||
}
|
||||
private static bool NextPressed(ButtonsSimple input) {
|
||||
return input.ZLorZR || input.Down;
|
||||
}
|
||||
}
|
14
SlidePresenter/Program.cs
Normal file
14
SlidePresenter/Program.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace SwitchSlidePresenter {
|
||||
internal abstract class Program {
|
||||
private static async Task Main() {
|
||||
IGamepadReader? reader = ControllerSelector.GetReader();
|
||||
if (reader == null) {
|
||||
Console.WriteLine("Invalid Controller Selected.");
|
||||
return;
|
||||
}
|
||||
SlideSwitcher switcher = new(reader);
|
||||
await reader.Read();
|
||||
switcher.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
65
SlidePresenter/SlideSwitcher.cs
Normal file
65
SlidePresenter/SlideSwitcher.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Win32Api;
|
||||
|
||||
namespace SwitchSlidePresenter;
|
||||
|
||||
public class SlideSwitcher : IDisposable {
|
||||
private readonly IGamepadReader? _reader;
|
||||
|
||||
private const uint INPUT_KEYBOARD = 1;
|
||||
private const ushort VK_NEXT = 0x22;
|
||||
private const ushort VK_PRIOR = 0x21;
|
||||
private const uint KEYEVENTF_KEYDOWN = 0x0000;
|
||||
private const uint KEYEVENTF_KEYUP = 0x0002;
|
||||
|
||||
public SlideSwitcher(IGamepadReader? reader) {
|
||||
_reader = reader;
|
||||
_reader.NextSlide += NextSlide;
|
||||
_reader.PrevSlide += PreviousSlide;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_reader.NextSlide -= NextSlide;
|
||||
_reader.PrevSlide -= PreviousSlide;
|
||||
}
|
||||
|
||||
private static void NextSlide() {
|
||||
SimulateKeyPress(VK_NEXT);
|
||||
}
|
||||
|
||||
private static void PreviousSlide() {
|
||||
SimulateKeyPress(VK_PRIOR);
|
||||
}
|
||||
|
||||
private static void SimulateKeyPress(ushort keyCode) {
|
||||
Input[] inputs = new Input[2];
|
||||
|
||||
inputs[0] = new Input {
|
||||
type = INPUT_KEYBOARD,
|
||||
u = new InputUnion {
|
||||
ki = new KeyboardInput {
|
||||
wVk = keyCode,
|
||||
wScan = 0,
|
||||
dwFlags = KEYEVENTF_KEYDOWN,
|
||||
time = 0,
|
||||
dwExtraInfo = IntPtr.Zero
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inputs[1] = new Input {
|
||||
type = INPUT_KEYBOARD,
|
||||
u = new InputUnion {
|
||||
ki = new KeyboardInput {
|
||||
wVk = keyCode,
|
||||
wScan = 0,
|
||||
dwFlags = KEYEVENTF_KEYUP,
|
||||
time = 0,
|
||||
dwExtraInfo = IntPtr.Zero
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Win32Api.Win32Api.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(Input)));
|
||||
}
|
||||
}
|
19
SlidePresenter/SwitchSlidePresenter.csproj
Normal file
19
SlidePresenter/SwitchSlidePresenter.csproj
Normal file
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JoyCon.NET" Version="1.0.1" />
|
||||
<PackageReference Include="WiimoteLib.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Win32Api\Win32Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
58
SlidePresenter/WiimoteRead.cs
Normal file
58
SlidePresenter/WiimoteRead.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using WiimoteLib.NetCore;
|
||||
|
||||
namespace SwitchSlidePresenter;
|
||||
|
||||
public class WiimoteRead : IGamepadReader {
|
||||
public event Action NextSlide;
|
||||
public event Action PrevSlide;
|
||||
|
||||
private const int RetryDelay = 1000;
|
||||
|
||||
public async Task Read() {
|
||||
Wiimote wiimote = new();
|
||||
while (string.IsNullOrEmpty(wiimote.HIDDevicePath)) {
|
||||
wiimote.Connect();
|
||||
if (string.IsNullOrEmpty(wiimote.HIDDevicePath)) {
|
||||
Console.WriteLine("Wiimote connection failed, trying again...");
|
||||
await Task.Delay(RetryDelay);
|
||||
} else {
|
||||
Console.WriteLine("Wiimote ready for presenting!");
|
||||
}
|
||||
}
|
||||
|
||||
ButtonState previousState = wiimote.WiimoteState.ButtonState;
|
||||
while (true) {
|
||||
if (PreviousPressed(wiimote.WiimoteState.ButtonState, previousState)) {
|
||||
PrevSlide?.Invoke();
|
||||
}
|
||||
if (NextPressed(wiimote.WiimoteState.ButtonState, previousState)) {
|
||||
NextSlide?.Invoke();
|
||||
}
|
||||
previousState = wiimote.WiimoteState.ButtonState;
|
||||
|
||||
await Task.Yield();
|
||||
|
||||
if (!Console.KeyAvailable || Console.ReadKey().Key != ConsoleKey.Enter)
|
||||
continue;
|
||||
wiimote.Disconnect();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Stopped.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PreviousPressed(ButtonState input) {
|
||||
return input.B || input.Left;
|
||||
}
|
||||
private static bool NextPressed(ButtonState input) {
|
||||
return input.A || input.Right;
|
||||
}
|
||||
|
||||
private static bool PreviousPressed(ButtonState input, ButtonState previousState) {
|
||||
return PreviousPressed(input) && !PreviousPressed(previousState);
|
||||
}
|
||||
private static bool NextPressed(ButtonState input, ButtonState previousState) {
|
||||
return NextPressed(input) && !NextPressed(previousState);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue