Compare commits

..

No commits in common. "master" and "0.2.1" have entirely different histories.

17 changed files with 74 additions and 376 deletions

View file

@ -1,41 +0,0 @@
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/

View file

@ -37,15 +37,11 @@
<setting file="file://$PROJECT_DIR$/SlidePresenter/Program.cs" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
<component name="ProblemsViewState">
<option name="selectedTabId" value="Toolset" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="2htCW7BWCLo4IJC4dn1HIEGkg1I" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<OptionsSetting value="false" id="Update" />
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">

View file

@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish Linux-x64" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" platform="Any CPU" runtime="linux-x64" self_contained="true" target_folder="$PROJECT_DIR$/SlidePresenter/bin/Release/net8.0/linux-x64/publish" target_framework="net8.0" uuid_high="-467478054054508763" uuid_low="-8110965388391740835" />
<method v="2" />
</configuration>
</component>

View file

@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish Windows-x64" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" include_native_libs_for_self_extract="true" platform="Any CPU" produce_single_file="true" runtime="win-x64" self_contained="true" target_folder="$PROJECT_DIR$/SlidePresenter/bin/Release/net8.0/win-x64/publish" target_framework="net8.0" uuid_high="-467478054054508763" uuid_low="-8110965388391740835" />
<method v="2" />
</configuration>
</component>

View file

@ -1,41 +0,0 @@
# 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))

View file

@ -1,33 +1,11 @@
using ControllerSlidePresenter.GamepadReader;
namespace ControllerSlidePresenter;
namespace SwitchSlidePresenter;
public static class ControllerSelector {
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}");
Console.WriteLine("[1] - JoyCon");
Console.WriteLine("[2] - Wiimote");
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.");
@ -37,11 +15,13 @@ public static class ControllerSelector {
Console.WriteLine("Invalid number.");
return null;
}
if (id <= 0 || id >= Readers.Count) {
Console.WriteLine("Invalid number");
return null;
}
return id - 1;
return GetReader(id);
}
private static IGamepadReader? GetReader(int id) => id switch {
1 => new JoyConRead(),
2 => new WiimoteRead(),
_ => null
};
}

View file

@ -5,31 +5,16 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<RootNamespace>SwitchSlidePresenter</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<DefineConstants>$(DefineConstants);OS_WINDOWS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<DefineConstants>$(DefineConstants);OS_LINUX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<DefineConstants>$(DefineConstants);OS_MAC</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);JoyCon</DefineConstants>
<DefineConstants>$(DefineConstants);Wiimote</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JoyCon.NET" Version="1.0.1" Condition="$(DefineConstants.Contains(JoyCon))" />
<PackageReference Include="WiimoteLib.NetCore" Version="1.0.0" Condition="$(DefineConstants.Contains(Wiimote))" />
<PackageReference Include="JoyCon.NET" Version="1.0.1" />
<PackageReference Include="WiimoteLib.NetCore" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Win32Api\Win32Api.csproj" Condition="$([MSBuild]::IsOSPlatform('Windows'))"/>
<ProjectReference Include="..\Win32Api\Win32Api.csproj" />
</ItemGroup>
</Project>

View file

@ -1,4 +1,4 @@
namespace ControllerSlidePresenter.GamepadReader;
namespace SwitchSlidePresenter;
public interface IGamepadReader {
public event Action NextSlide;

View file

@ -1,6 +0,0 @@
namespace ControllerSlidePresenter.InputSender;
public interface IInputSender {
void NextSlide();
void PreviousSlide();
}

View file

@ -1,32 +0,0 @@
#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

View file

@ -1,13 +0,0 @@
#if OS_MAC
namespace ControllerSlidePresenter.InputSender;
public class MacInputSender : IInputSender {
public void NextSlide() {
}
public void PreviousSlide() {
}
}
#endif

View file

@ -1,49 +0,0 @@
#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

View file

@ -1,17 +1,15 @@
#if JoyCon
using System.Text;
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 ControllerSlidePresenter.GamepadReader;
namespace SwitchSlidePresenter;
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;
@ -64,44 +62,10 @@ public class JoyConRead : IGamepadReader {
}
private static HidDevice? GetHidDevice() {
return OperatingSystem.IsWindows()
? GetWindowsHidDevice()
: GetNonWindowsHidDevice();
}
private static HidDevice? GetWindowsHidDevice() {
DeviceList list = DeviceList.Local;
IEnumerable<HidDevice>? nintendos = list.GetHidDevices(0x057e);
HidDevice? device = nintendos.FirstOrDefault();
return device;
}
private static HidDevice? GetNonWindowsHidDevice() {
HidDevice? device = null;
DeviceList list = DeviceList.Local;
IEnumerable<HidDevice>? 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;
return nintendos.FirstOrDefault();
}
private Task OnJoyConOnReportReceived(JoyCon _, IJoyConReport input) {
@ -125,5 +89,4 @@ public class JoyConRead : IGamepadReader {
private static bool NextPressed(ButtonsSimple input) {
return input.ZLorZR || input.Down;
}
}
#endif
}

View file

@ -1,55 +0,0 @@
#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

View file

@ -1,13 +1,6 @@
using ControllerSlidePresenter.GamepadReader;
namespace ControllerSlidePresenter {
namespace SwitchSlidePresenter {
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.");

View file

@ -1,33 +1,65 @@
using ControllerSlidePresenter.GamepadReader;
using ControllerSlidePresenter.InputSender;
using System.Runtime.InteropServices;
using Win32Api;
namespace ControllerSlidePresenter;
namespace SwitchSlidePresenter;
public class SlideSwitcher : IDisposable {
private readonly IGamepadReader? _reader;
#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
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;
if (_reader == null) return;
_reader.NextSlide += NextSlide;
_reader.PrevSlide += PreviousSlide;
}
public void Dispose() {
if (_reader == null) return;
_reader.NextSlide -= NextSlide;
_reader.PrevSlide -= PreviousSlide;
}
private void NextSlide() => _inputSender.NextSlide();
private void PreviousSlide() => _inputSender.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)));
}
}

View file

@ -1,11 +1,10 @@
#if Wiimote
using WiimoteLib.NetCore;
using WiimoteLib.NetCore;
namespace ControllerSlidePresenter.GamepadReader;
namespace SwitchSlidePresenter;
public class WiimoteRead : IGamepadReader {
public event Action? NextSlide;
public event Action? PrevSlide;
public event Action NextSlide;
public event Action PrevSlide;
public async Task Read() {
Wiimote wiimote = new();
@ -47,5 +46,4 @@ public class WiimoteRead : IGamepadReader {
private static bool NextPressed(ButtonState input) {
return input.A || input.Right;
}
}
#endif
}