diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a29c1ef --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: .NET Build + +on: + workflow_dispatch: + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest] + include: + - os: windows-latest + vs-version: 'latest' + runtime: 'win-x64' + - os: ubuntu-latest + vs-version: 'latest' + runtime: 'linux-x64' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Build self-contained application + run: dotnet publish SlidePresenter/ControllerSlidePresenter.csproj --no-restore --configuration Release --self-contained -r ${{ matrix.runtime }} /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.runtime }} + path: | + **/bin/Release/net8.0/${{ matrix.runtime }}/publish/ diff --git a/.idea/.idea.SwitchSlidePresenter/.idea/workspace.xml b/.idea/.idea.SwitchSlidePresenter/.idea/workspace.xml new file mode 100644 index 0000000..41f33f2 --- /dev/null +++ b/.idea/.idea.SwitchSlidePresenter/.idea/workspace.xml @@ -0,0 +1,144 @@ + + + + SwitchSlidePresenter.csproj + + + + + + + + + + + { + "lastFilter": { + "state": "OPEN", + "assignee": "GerardGascon" + } +} + { + "selectedUrlAndAccountId": { + "url": "https://github.com/GerardGascon/Switch-Slide-Presenter.git", + "accountId": "0af66c52-cbb7-4844-ad24-01b5c5b9bee8" + } +} + + + + + + + + + { + "associatedIndex": 6 +} + + + + + + + + { + "keyToString": { + ".NET Project.SwitchSlidePresenter.executor": "Run", + "Publish to folder.Publish ControllerSlidePresenter to folder.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "git-widget-placeholder": "master", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "vue.rearranger.settings.migration": "true" + }, + "keyToStringList": { + "rider.external.source.directories": [ + "C:\\Users\\ggasc\\AppData\\Roaming\\JetBrains\\Rider2024.1\\resharper-host\\DecompilerCache", + "C:\\Users\\ggasc\\AppData\\Roaming\\JetBrains\\Rider2024.1\\resharper-host\\SourcesCache", + "C:\\Users\\ggasc\\AppData\\Local\\Symbols\\src" + ] + } +} + + + + + + + + + + + + + + + + + 1718404015037 + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/Publish Linux-x64.run.xml b/.run/Publish Linux-x64.run.xml new file mode 100644 index 0000000..24fa928 --- /dev/null +++ b/.run/Publish Linux-x64.run.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.run/Publish Windows-x64.run.xml b/.run/Publish Windows-x64.run.xml new file mode 100644 index 0000000..78279af --- /dev/null +++ b/.run/Publish Windows-x64.run.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3599a9 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Controller Slide Presenter + +A small tool to emulate a Slide Presenter using a Wiimote or a Joy-Con. + +## Controls + +### Wiimote + +- A or Right - Next Slide +- B or Left - Previous Slide + +### Joy-Con + +- A or ZL/ZR - Next Slide +- B or L/R - Previous Slide + +## Platform Support + +### Windows + +For the Joy-Con you just need to connect it via Bluetooth and then run the program. + +For the Wiimote you may need to connect it using Dolphin Emulator and then just run the program. + +**WARNING:** It's possible that Steam tries reading the Joy-Con in the background and keeps it from working. + +### Linux + +**Prerequisite:** You need [xdotool](https://github.com/jordansissel/xdotool) installed in order to redirect the input. + +At the moment there's only Joy-Con support, so connect it via Bluetooth and run the program. + +### MacOS + +At the moment there's no MacOS support as I don't have a computer to test, but feel free to submit a Pull Request adding that feature, the Joy-Con reading should work just fine, only the input redirection is needed. + +## Packages used + +**JoyCon.NET** - ([GitHub](https://github.com/ClusterM/joycon)) ([NuGet](https://www.nuget.org/packages/JoyCon.NET)) + +**WiimoteLib.NetCore** - ([GitHub](https://github.com/BrianPeek/WiimoteLib)) ([NuGet](https://www.nuget.org/packages/WiimoteLib.NetCore)) diff --git a/SlidePresenter/ControllerSelector.cs b/SlidePresenter/ControllerSelector.cs index 8efff0f..31e5dd8 100644 --- a/SlidePresenter/ControllerSelector.cs +++ b/SlidePresenter/ControllerSelector.cs @@ -1,11 +1,33 @@ -namespace SwitchSlidePresenter; +using ControllerSlidePresenter.GamepadReader; + +namespace ControllerSlidePresenter; 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"); + private static readonly List<(string name, IGamepadReader reader)> Readers = [ +#if JoyCon + ("JoyCon", new JoyConRead()), +#endif +#if Wiimote + ("Wiimote", new WiimoteRead()) +#endif + ]; + public static IGamepadReader? GetReader() { + if (Readers.Count == 1) + return Readers[0].reader; + + Console.WriteLine("Write a number to select controller type:"); + for (int i = 0; i < Readers.Count; i++) + Console.WriteLine($"[{i+1}] - {Readers[i].name}"); + + int? id = GetReaderIndex(); + if (id == null) + return null; + + return Readers[id.Value].reader; + } + + private static int? GetReaderIndex() { string? line = Console.ReadLine(); if (line == null) { Console.WriteLine("Invalid input."); @@ -15,13 +37,11 @@ public static class ControllerSelector { Console.WriteLine("Invalid number."); return null; } + if (id <= 0 || id >= Readers.Count) { + Console.WriteLine("Invalid number"); + return null; + } - return GetReader(id); + return id - 1; } - - private static IGamepadReader? GetReader(int id) => id switch { - 1 => new JoyConRead(), - 2 => new WiimoteRead(), - _ => null - }; } \ No newline at end of file diff --git a/SlidePresenter/ControllerSlidePresenter.csproj b/SlidePresenter/ControllerSlidePresenter.csproj index 66a3144..2324e47 100644 --- a/SlidePresenter/ControllerSlidePresenter.csproj +++ b/SlidePresenter/ControllerSlidePresenter.csproj @@ -5,16 +5,31 @@ net8.0 enable enable - SwitchSlidePresenter + win-x64;linux-x64 + + $(DefineConstants);OS_WINDOWS + + + $(DefineConstants);OS_LINUX + + + $(DefineConstants);OS_MAC + + + + $(DefineConstants);JoyCon + $(DefineConstants);Wiimote + + - - + + - + diff --git a/SlidePresenter/IGamepadReader.cs b/SlidePresenter/GamepadReader/IGamepadReader.cs similarity index 69% rename from SlidePresenter/IGamepadReader.cs rename to SlidePresenter/GamepadReader/IGamepadReader.cs index 2fbf258..f3f0be5 100644 --- a/SlidePresenter/IGamepadReader.cs +++ b/SlidePresenter/GamepadReader/IGamepadReader.cs @@ -1,4 +1,4 @@ -namespace SwitchSlidePresenter; +namespace ControllerSlidePresenter.GamepadReader; public interface IGamepadReader { public event Action NextSlide; diff --git a/SlidePresenter/JoyConRead.cs b/SlidePresenter/GamepadReader/JoyConRead.cs similarity index 58% rename from SlidePresenter/JoyConRead.cs rename to SlidePresenter/GamepadReader/JoyConRead.cs index 3eb2a94..9459431 100644 --- a/SlidePresenter/JoyConRead.cs +++ b/SlidePresenter/GamepadReader/JoyConRead.cs @@ -1,22 +1,24 @@ -using System.Text; +#if JoyCon +using System.Text; using HidSharp; +using HidSharp.Reports; using wtf.cluster.JoyCon; using wtf.cluster.JoyCon.ExtraData; using wtf.cluster.JoyCon.InputData; using wtf.cluster.JoyCon.InputReports; -namespace SwitchSlidePresenter; +namespace ControllerSlidePresenter.GamepadReader; public class JoyConRead : IGamepadReader { - public event Action NextSlide; - public event Action PrevSlide; + 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("No controller. Please connect Joy-Con via Bluetooth."); Console.WriteLine("Press any key to exit program."); Console.ReadKey(); return; @@ -39,11 +41,14 @@ public class JoyConRead : IGamepadReader { Console.WriteLine("JoyCon ready for presenting."); Console.WriteLine("Press Enter to exit program."); - while (Console.ReadKey().Key != ConsoleKey.Enter) { } + while (Console.ReadKey().Key != ConsoleKey.Enter) { + await Task.Yield(); + } joycon.Stop(); Console.WriteLine(); Console.WriteLine("Stopped."); + await Task.CompletedTask; } private static async Task LogDeviceInfo(JoyCon joycon) { @@ -59,36 +64,42 @@ public class JoyConRead : IGamepadReader { } private static HidDevice? GetHidDevice() { - DeviceList list = DeviceList.Local; - HidDevice? device = null; + return OperatingSystem.IsWindows() + ? GetWindowsHidDevice() + : GetNonWindowsHidDevice(); + } - 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; - } - } - } + private static HidDevice? GetWindowsHidDevice() { + DeviceList list = DeviceList.Local; + IEnumerable? nintendos = list.GetHidDevices(0x057e); + HidDevice? device = nintendos.FirstOrDefault(); + return device; + } + + private static HidDevice? GetNonWindowsHidDevice() { + HidDevice? device = null; + DeviceList list = DeviceList.Local; + + IEnumerable? hidDevices = list.GetHidDevices(); + foreach (HidDevice d in hidDevices) + { + ReportDescriptor? rd = d.GetReportDescriptor(); + if (rd == null) continue; + 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) continue; + + device = d; + break; } return device; } @@ -114,4 +125,5 @@ public class JoyConRead : IGamepadReader { private static bool NextPressed(ButtonsSimple input) { return input.ZLorZR || input.Down; } -} \ No newline at end of file +} +#endif diff --git a/SlidePresenter/GamepadReader/WiimoteRead.cs b/SlidePresenter/GamepadReader/WiimoteRead.cs new file mode 100644 index 0000000..5918a2e --- /dev/null +++ b/SlidePresenter/GamepadReader/WiimoteRead.cs @@ -0,0 +1,51 @@ +#if Wiimote +using WiimoteLib.NetCore; + +namespace ControllerSlidePresenter.GamepadReader; + +public class WiimoteRead : IGamepadReader { + public event Action? NextSlide; + public event Action? PrevSlide; + + public async Task Read() { + Wiimote wiimote = new(); + wiimote.Connect(); + + if (string.IsNullOrEmpty(wiimote.HIDDevicePath)) { + Console.WriteLine("No controller. Please connect Wiimote via Bluetooth."); + Console.WriteLine("Press any key to exit program."); + Console.ReadKey(); + return; + } + + wiimote.WiimoteChanged += WiimoteChanged; + + Console.WriteLine("Wiimote ready for presenting."); + Console.WriteLine("Press Enter to exit program."); + while (Console.ReadKey().Key != ConsoleKey.Enter) { + await Task.Yield(); + } + wiimote.Disconnect(); + + Console.WriteLine(); + Console.WriteLine("Stopped."); + await Task.CompletedTask; + } + + private void WiimoteChanged(object? sender, WiimoteChangedEventArgs e) { + if (PreviousPressed(e.WiimoteState.ButtonState)) { + PrevSlide?.Invoke(); + } + if (NextPressed(e.WiimoteState.ButtonState)) { + NextSlide?.Invoke(); + } + } + + private static bool PreviousPressed(ButtonState input) { + return input.B || input.Left; + } + private static bool NextPressed(ButtonState input) { + return input.A || input.Right; + } +} +#endif diff --git a/SlidePresenter/InputSender/IInputSender.cs b/SlidePresenter/InputSender/IInputSender.cs new file mode 100644 index 0000000..461f08d --- /dev/null +++ b/SlidePresenter/InputSender/IInputSender.cs @@ -0,0 +1,6 @@ +namespace ControllerSlidePresenter.InputSender; + +public interface IInputSender { + void NextSlide(); + void PreviousSlide(); +} \ No newline at end of file diff --git a/SlidePresenter/InputSender/LinuxInputSender.cs b/SlidePresenter/InputSender/LinuxInputSender.cs new file mode 100644 index 0000000..011061c --- /dev/null +++ b/SlidePresenter/InputSender/LinuxInputSender.cs @@ -0,0 +1,32 @@ +#if OS_LINUX +using System.Diagnostics; + +namespace ControllerSlidePresenter.InputSender; + +public class LinuxInputSender : IInputSender { + private const string PageUp = "0xff55"; + private const string PageDown = "0xff56"; + + public void NextSlide() { + SendKeys(PageDown); + } + + public void PreviousSlide() { + SendKeys(PageUp); + } + + private static void SendKeys(string keycode) { + Process proc = new() { + StartInfo = { + FileName = "xdotool", + Arguments = $"key {keycode}", + UseShellExecute = false, + RedirectStandardError = false, + RedirectStandardInput = false, + RedirectStandardOutput = false + } + }; + proc.Start(); + } +} +#endif \ No newline at end of file diff --git a/SlidePresenter/InputSender/MacInputSender.cs b/SlidePresenter/InputSender/MacInputSender.cs new file mode 100644 index 0000000..270704a --- /dev/null +++ b/SlidePresenter/InputSender/MacInputSender.cs @@ -0,0 +1,13 @@ +#if OS_MAC +namespace ControllerSlidePresenter.InputSender; + +public class MacInputSender : IInputSender { + public void NextSlide() { + + } + + public void PreviousSlide() { + + } +} +#endif \ No newline at end of file diff --git a/SlidePresenter/InputSender/WindowsInputSender.cs b/SlidePresenter/InputSender/WindowsInputSender.cs new file mode 100644 index 0000000..3ecb477 --- /dev/null +++ b/SlidePresenter/InputSender/WindowsInputSender.cs @@ -0,0 +1,49 @@ +#if OS_WINDOWS +using System.Runtime.InteropServices; +using Win32Api; + +namespace ControllerSlidePresenter.InputSender; + +public class WindowsInputSender : IInputSender { + 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 void NextSlide() => SimulateKeyPress(VK_NEXT); + public 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))); + } +} +#endif \ No newline at end of file diff --git a/SlidePresenter/Linux.cs b/SlidePresenter/Linux.cs new file mode 100644 index 0000000..b13c885 --- /dev/null +++ b/SlidePresenter/Linux.cs @@ -0,0 +1,55 @@ +#if OS_LINUX +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ControllerSlidePresenter; + +public static class Linux { + public static bool CanRun() { + if (getuid() != 0) { + Console.WriteLine("On Linux you need tu run as 'sudo'"); + return false; + } + if (!IsXdoToolInstalled()) + return false; + + return true; + } + + private static bool IsXdoToolInstalled() { + string result = RunShellCommand("which xdotool"); + if (!string.IsNullOrEmpty(result)) + return true; + + Console.WriteLine("xdotool is not installed."); + return false; + } + + private static string RunShellCommand(string command) { + try { + ProcessStartInfo procStartInfo = new("bash", "-c \"" + command + "\"") { + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using Process process = new(); + process.StartInfo = procStartInfo; + process.Start(); + + string result = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return result.Trim(); + } + catch (Exception ex) { + Console.WriteLine("Error: " + ex.Message); + return string.Empty; + } + } + + + [DllImport("libc")] + private static extern uint getuid(); +} +#endif diff --git a/SlidePresenter/Program.cs b/SlidePresenter/Program.cs index 9a8e12c..53df152 100644 --- a/SlidePresenter/Program.cs +++ b/SlidePresenter/Program.cs @@ -1,6 +1,13 @@ -namespace SwitchSlidePresenter { +using ControllerSlidePresenter.GamepadReader; + +namespace ControllerSlidePresenter { internal abstract class Program { private static async Task Main() { +#if OS_LINUX + if (!Linux.CanRun()) + return; +#endif + IGamepadReader? reader = ControllerSelector.GetReader(); if (reader == null) { Console.WriteLine("Invalid Controller Selected."); diff --git a/SlidePresenter/SlideSwitcher.cs b/SlidePresenter/SlideSwitcher.cs index d81adae..3cd7c5d 100644 --- a/SlidePresenter/SlideSwitcher.cs +++ b/SlidePresenter/SlideSwitcher.cs @@ -1,65 +1,33 @@ -using System.Runtime.InteropServices; -using Win32Api; +using ControllerSlidePresenter.GamepadReader; +using ControllerSlidePresenter.InputSender; -namespace SwitchSlidePresenter; +namespace ControllerSlidePresenter; 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; +#if OS_WINDOWS + private readonly IInputSender _inputSender = new WindowsInputSender(); +#elif OS_MAC + private readonly IInputSender _inputSender = new MacInputSender(); +#elif OS_LINUX + private readonly IInputSender _inputSender = new LinuxInputSender(); +#endif public SlideSwitcher(IGamepadReader? reader) { _reader = reader; + if (_reader == null) return; + _reader.NextSlide += NextSlide; _reader.PrevSlide += PreviousSlide; } public void Dispose() { + if (_reader == null) return; _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))); - } + private void NextSlide() => _inputSender.NextSlide(); + private void PreviousSlide() => _inputSender.PreviousSlide(); } \ No newline at end of file diff --git a/SlidePresenter/WiimoteRead.cs b/SlidePresenter/WiimoteRead.cs deleted file mode 100644 index 7ab8dca..0000000 --- a/SlidePresenter/WiimoteRead.cs +++ /dev/null @@ -1,58 +0,0 @@ -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); - } -} \ No newline at end of file