mirror of
https://github.com/Ryujinx/Opentk.git
synced 2025-07-09 15:20:33 +00:00
[Linux] Implemented evdev joystick polling
This commit is contained in:
parent
397bdda076
commit
129fb81224
|
@ -381,6 +381,23 @@ namespace OpenTK.Platform.Linux
|
||||||
return (uint)v;
|
return (uint)v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get absolute value / limits
|
||||||
|
public static int GetAbs(int fd, EvdevAxis axis, out InputAbsInfo info)
|
||||||
|
{
|
||||||
|
info = default(InputAbsInfo);
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (InputAbsInfo* pinfo = &info)
|
||||||
|
{
|
||||||
|
// EVIOCGABS(abs) = _IOR('E', 0x40 + (abs), struct input_absinfo)
|
||||||
|
uint ioctl = IOCreate(DirectionFlags.Read, (int)axis + 0x40, BlittableValueType<InputAbsInfo>.Stride);
|
||||||
|
int retval = Libc.ioctl(fd, ioctl, new IntPtr(pinfo));
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get supported event bits
|
||||||
public static int GetBit(int fd, EvdevType ev, int length, IntPtr data)
|
public static int GetBit(int fd, EvdevType ev, int length, IntPtr data)
|
||||||
{
|
{
|
||||||
// EVIOCGBIT = _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
|
// EVIOCGBIT = _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
|
||||||
|
@ -577,6 +594,17 @@ namespace OpenTK.Platform.Linux
|
||||||
Name128 = (2u << 30) | ((byte)'E' << 8) | (0x06u << 0) | (128u << 16), //EVIOCGNAME(len) = _IOC(_IOC_READ, 'E', 0x06, len)
|
Name128 = (2u << 30) | ((byte)'E' << 8) | (0x06u << 0) | (128u << 16), //EVIOCGNAME(len) = _IOC(_IOC_READ, 'E', 0x06, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct InputAbsInfo
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
public int Minimum;
|
||||||
|
public int Maximum;
|
||||||
|
public int Fuzz;
|
||||||
|
public int Flat;
|
||||||
|
public int Resolution;
|
||||||
|
};
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
struct InputId
|
struct InputId
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,6 +35,12 @@ using OpenTK.Input;
|
||||||
|
|
||||||
namespace OpenTK.Platform.Linux
|
namespace OpenTK.Platform.Linux
|
||||||
{
|
{
|
||||||
|
struct AxisInfo
|
||||||
|
{
|
||||||
|
public JoystickAxis Axis;
|
||||||
|
public InputAbsInfo Info;
|
||||||
|
}
|
||||||
|
|
||||||
class LinuxJoystickDetails
|
class LinuxJoystickDetails
|
||||||
{
|
{
|
||||||
public Guid Guid;
|
public Guid Guid;
|
||||||
|
@ -44,18 +50,23 @@ namespace OpenTK.Platform.Linux
|
||||||
public JoystickState State;
|
public JoystickState State;
|
||||||
public JoystickCapabilities Caps;
|
public JoystickCapabilities Caps;
|
||||||
|
|
||||||
public readonly Dictionary<EvdevAxis, JoystickAxis> AxisMap =
|
public readonly Dictionary<EvdevAxis, AxisInfo> AxisMap =
|
||||||
new Dictionary<EvdevAxis, JoystickAxis>();
|
new Dictionary<EvdevAxis, AxisInfo>();
|
||||||
public readonly Dictionary<EvdevButton, JoystickButton> ButtonMap =
|
public readonly Dictionary<EvdevButton, JoystickButton> ButtonMap =
|
||||||
new Dictionary<EvdevButton, JoystickButton>();
|
new Dictionary<EvdevButton, JoystickButton>();
|
||||||
public readonly Dictionary<int, JoystickHat> HatMap =
|
|
||||||
new Dictionary<int, JoystickHat>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class LinuxJoystick : IJoystickDriver2
|
sealed class LinuxJoystick : IJoystickDriver2
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
|
|
||||||
|
static readonly HatPosition[,] HatPositions = new HatPosition[,]
|
||||||
|
{
|
||||||
|
{ HatPosition.UpLeft, HatPosition.Up, HatPosition.UpRight },
|
||||||
|
{ HatPosition.Left, HatPosition.Centered, HatPosition.Right },
|
||||||
|
{ HatPosition.DownLeft, HatPosition.Down, HatPosition.DownRight }
|
||||||
|
};
|
||||||
|
|
||||||
readonly object sync = new object();
|
readonly object sync = new object();
|
||||||
|
|
||||||
readonly FileSystemWatcher watcher = new FileSystemWatcher();
|
readonly FileSystemWatcher watcher = new FileSystemWatcher();
|
||||||
|
@ -141,7 +152,6 @@ namespace OpenTK.Platform.Linux
|
||||||
if (stick != null)
|
if (stick != null)
|
||||||
{
|
{
|
||||||
CloseJoystick(stick);
|
CloseJoystick(stick);
|
||||||
Sticks.TryRemove(number);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,77 +208,53 @@ namespace OpenTK.Platform.Linux
|
||||||
return (*(ptr + byte_offset) & (1 << bit_offset)) != 0;
|
return (*(ptr + byte_offset) & (1 << bit_offset)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe static int AddAxes(LinuxJoystickDetails stick, byte* axisbit, int bytecount)
|
unsafe static void QueryCapabilities(LinuxJoystickDetails stick,
|
||||||
|
byte* axisbit, int axisbytes,
|
||||||
|
byte* keybit, int keybytes,
|
||||||
|
out int axes, out int buttons, out int hats)
|
||||||
{
|
{
|
||||||
JoystickAxis axes = 0;
|
// Note: since we are trying to be compatible with the SDL2 gamepad database,
|
||||||
JoystickHat hats = 0;
|
// we have to match SDL2 behavior bug-for-bug. This means:
|
||||||
int bitcount = bytecount * 8;
|
// HAT0-HAT3 are all reported as hats (even if the docs say that HAT1 and HAT2 can be analogue triggers)
|
||||||
for (EvdevAxis axis = 0; axis < EvdevAxis.CNT && (int)axis < bitcount; axis++)
|
// DPAD buttons are reported as buttons, not as hats (unlike Windows and Mac OS X)
|
||||||
{
|
|
||||||
if (axis >= EvdevAxis.HAT0X && axis <= EvdevAxis.HAT3Y)
|
|
||||||
{
|
|
||||||
// Axis is analogue hat - skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TestBit(axisbit, (int)axis))
|
axes = buttons = hats = 0;
|
||||||
|
for (EvdevAxis axis = 0; axis < EvdevAxis.CNT && (int)axis < axisbytes * 8; axis++)
|
||||||
|
{
|
||||||
|
InputAbsInfo info;
|
||||||
|
bool is_valid = true;
|
||||||
|
is_valid &= TestBit(axisbit, (int)axis);
|
||||||
|
is_valid &= Evdev.GetAbs(stick.FileDescriptor, axis, out info) >= 0;
|
||||||
|
if (is_valid)
|
||||||
{
|
{
|
||||||
stick.AxisMap.Add(axis, axes++);
|
if (axis >= EvdevAxis.HAT0X && axis <= EvdevAxis.HAT3Y)
|
||||||
}
|
{
|
||||||
else
|
// Analogue hat
|
||||||
{
|
stick.AxisMap.Add(axis, new AxisInfo
|
||||||
stick.AxisMap.Add(axis, (JoystickAxis)(-1));
|
{
|
||||||
|
Axis = (JoystickAxis)(JoystickHat)hats++,
|
||||||
|
Info = info
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Regular axis
|
||||||
|
stick.AxisMap.Add(axis, new AxisInfo
|
||||||
|
{
|
||||||
|
Axis = (JoystickAxis)axes++,
|
||||||
|
Info = info
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (int)axes;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe static int AddButtons(LinuxJoystickDetails stick, byte* keybit, int bytecount)
|
for (EvdevButton button = 0; button < EvdevButton.Last && (int)button < keybytes * 8; button++)
|
||||||
{
|
|
||||||
JoystickButton buttons = 0;
|
|
||||||
int bitcount = bytecount * 8;
|
|
||||||
for (EvdevButton button = 0; button < EvdevButton.Last && (int)button < bitcount; button++)
|
|
||||||
{
|
{
|
||||||
if (button >= EvdevButton.DPAD_UP && button <= EvdevButton.DPAD_RIGHT)
|
|
||||||
{
|
|
||||||
// Button is dpad (hat) - skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TestBit(keybit, (int)button))
|
if (TestBit(keybit, (int)button))
|
||||||
{
|
{
|
||||||
stick.ButtonMap.Add(button, buttons++);
|
stick.ButtonMap.Add(button, (JoystickButton)buttons++);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stick.ButtonMap.Add(button, (JoystickButton)(-1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (int)buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe static int AddHats(LinuxJoystickDetails stick,
|
|
||||||
byte* axisbit, int axiscount,
|
|
||||||
byte* keybit, int keycount)
|
|
||||||
{
|
|
||||||
JoystickHat hats = 0;
|
|
||||||
for (EvdevAxis hat = EvdevAxis.HAT0X; hat < EvdevAxis.HAT3Y && (int)hat < axiscount * 8; hat++)
|
|
||||||
{
|
|
||||||
if (TestBit(axisbit, (int)hat))
|
|
||||||
{
|
|
||||||
stick.HatMap.Add((int)hat, hats++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (EvdevButton dpad = EvdevButton.DPAD_UP; dpad < EvdevButton.DPAD_RIGHT && (int)dpad < keycount * 8; dpad++)
|
|
||||||
{
|
|
||||||
if (TestBit(axisbit, (int)dpad))
|
|
||||||
{
|
|
||||||
stick.HatMap.Add((int)dpad, hats++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)hats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxJoystickDetails OpenJoystick(string path)
|
LinuxJoystickDetails OpenJoystick(string path)
|
||||||
|
@ -322,12 +308,15 @@ namespace OpenTK.Platform.Linux
|
||||||
Name = name,
|
Name = name,
|
||||||
Guid = CreateGuid(id, name),
|
Guid = CreateGuid(id, name),
|
||||||
};
|
};
|
||||||
stick.Caps = new JoystickCapabilities(
|
|
||||||
AddAxes(stick, axisbit, axissize),
|
int axes, buttons, hats;
|
||||||
AddButtons(stick, keybit, keysize),
|
QueryCapabilities(stick, axisbit, axissize, keybit, keysize,
|
||||||
AddHats(stick, axisbit, axissize, keybit, keysize),
|
out axes, out buttons, out hats);
|
||||||
true);
|
|
||||||
stick.State.SetIsConnected(true);
|
stick.Caps = new JoystickCapabilities(axes, buttons, hats, true);
|
||||||
|
|
||||||
|
// Poll the joystick once, to initialize its state
|
||||||
|
PollJoystick(stick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +349,11 @@ namespace OpenTK.Platform.Linux
|
||||||
js.Caps = new JoystickCapabilities();
|
js.Caps = new JoystickCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JoystickHatState TranslateHat(int x, int y)
|
||||||
|
{
|
||||||
|
return new JoystickHatState(HatPositions[x, y]);
|
||||||
|
}
|
||||||
|
|
||||||
void PollJoystick(LinuxJoystickDetails js)
|
void PollJoystick(LinuxJoystickDetails js)
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
|
@ -381,13 +375,71 @@ namespace OpenTK.Platform.Linux
|
||||||
switch (e->Type)
|
switch (e->Type)
|
||||||
{
|
{
|
||||||
case EvdevType.ABS:
|
case EvdevType.ABS:
|
||||||
break;
|
{
|
||||||
|
AxisInfo axis;
|
||||||
|
if (js.AxisMap.TryGetValue((EvdevAxis)e->Code, out axis))
|
||||||
|
{
|
||||||
|
if (axis.Info.Maximum > axis.Info.Minimum)
|
||||||
|
{
|
||||||
|
if (e->Code >= (int)EvdevAxis.HAT0X && e->Code <= (int)EvdevAxis.HAT3Y)
|
||||||
|
{
|
||||||
|
// We currently treat analogue hats as digital hats
|
||||||
|
// to maintain compatibility with SDL2. We can do
|
||||||
|
// better than this, however.
|
||||||
|
JoystickHat hat = JoystickHat.Hat0 + (e->Code - (int)EvdevAxis.HAT0X) / 2;
|
||||||
|
JoystickHatState pos = js.State.GetHat(hat);
|
||||||
|
int xy_axis = (int)axis.Axis & 0x1;
|
||||||
|
switch (xy_axis)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// X-axis
|
||||||
|
pos = TranslateHat(
|
||||||
|
e->Value.CompareTo(0) + 1,
|
||||||
|
pos.IsUp ? 0 : pos.IsDown ? 2 : 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// Y-axis
|
||||||
|
pos = TranslateHat(
|
||||||
|
pos.IsLeft ? 0 : pos.IsRight ? 2 : 1,
|
||||||
|
e->Value.CompareTo(0) + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
js.State.SetHat(hat, pos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This axis represents a regular axis or trigger
|
||||||
|
js.State.SetAxis(
|
||||||
|
axis.Axis,
|
||||||
|
(short)Common.HidHelper.ScaleValue(e->Value,
|
||||||
|
axis.Info.Minimum, axis.Info.Maximum,
|
||||||
|
short.MinValue, short.MaxValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case EvdevType.KEY:
|
case EvdevType.KEY:
|
||||||
break;
|
{
|
||||||
|
JoystickButton button;
|
||||||
|
if (js.ButtonMap.TryGetValue((EvdevButton)e->Code, out button))
|
||||||
|
{
|
||||||
|
js.State.SetButton(button, e->Value != 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//js.State.SetPacketNumber(unchecked((int)e->Time.Seconds));
|
// Create a serial number (total seconds in 24.8 fixed point format)
|
||||||
|
int sec = (int)((long)e->Time.Seconds & 0xffffffff);
|
||||||
|
int msec = (int)e->Time.MicroSeconds / 1000;
|
||||||
|
int packet =
|
||||||
|
((sec & 0x00ffffff) << 24) |
|
||||||
|
Common.HidHelper.ScaleValue(msec, 0, 1000, 0, 255);
|
||||||
|
js.State.SetPacketNumber(unchecked((int)e->Time.Seconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue