aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Gtk3/Input
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Gtk3/Input')
-rw-r--r--src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs205
-rw-r--r--src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs94
-rw-r--r--src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs178
-rw-r--r--src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs90
-rw-r--r--src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs108
5 files changed, 675 insertions, 0 deletions
diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs
new file mode 100644
index 00000000..ff7a2c3b
--- /dev/null
+++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs
@@ -0,0 +1,205 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
+
+namespace Ryujinx.Input.GTK3
+{
+ public class GTK3Keyboard : IKeyboard
+ {
+ private class ButtonMappingEntry
+ {
+ public readonly GamepadButtonInputId To;
+ public readonly Key From;
+
+ public ButtonMappingEntry(GamepadButtonInputId to, Key from)
+ {
+ To = to;
+ From = from;
+ }
+ }
+
+ private readonly object _userMappingLock = new();
+
+ private readonly GTK3KeyboardDriver _driver;
+ private StandardKeyboardInputConfig _configuration;
+ private readonly List<ButtonMappingEntry> _buttonsUserMapping;
+
+ public GTK3Keyboard(GTK3KeyboardDriver driver, string id, string name)
+ {
+ _driver = driver;
+ Id = id;
+ Name = name;
+ _buttonsUserMapping = new List<ButtonMappingEntry>();
+ }
+
+ private bool HasConfiguration => _configuration != null;
+
+ public string Id { get; }
+
+ public string Name { get; }
+
+ public bool IsConnected => true;
+
+ public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
+
+ public void Dispose()
+ {
+ // No operations
+ GC.SuppressFinalize(this);
+ }
+
+ public KeyboardStateSnapshot GetKeyboardStateSnapshot()
+ {
+ return IKeyboard.GetStateSnapshot(this);
+ }
+
+ private static float ConvertRawStickValue(short value)
+ {
+ const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
+
+ return value * ConvertRate;
+ }
+
+ private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
+ {
+ short stickX = 0;
+ short stickY = 0;
+
+ if (snapshot.IsPressed((Key)stickConfig.StickUp))
+ {
+ stickY += 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickDown))
+ {
+ stickY -= 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickRight))
+ {
+ stickX += 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickLeft))
+ {
+ stickX -= 1;
+ }
+
+ OpenTK.Mathematics.Vector2 stick = new(stickX, stickY);
+
+ stick.NormalizeFast();
+
+ return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
+ GamepadStateSnapshot result = default;
+
+ lock (_userMappingLock)
+ {
+ if (!HasConfiguration)
+ {
+ return result;
+ }
+
+ foreach (ButtonMappingEntry entry in _buttonsUserMapping)
+ {
+ if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
+ {
+ continue;
+ }
+
+ // Do not touch state of button already pressed
+ if (!result.IsPressed(entry.To))
+ {
+ result.SetPressed(entry.To, rawState.IsPressed(entry.From));
+ }
+ }
+
+ (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
+ (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
+
+ result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
+ result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
+ }
+
+ return result;
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ throw new NotSupportedException();
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool IsPressed(Key key)
+ {
+ return _driver.IsPressed(key);
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ lock (_userMappingLock)
+ {
+ _configuration = (StandardKeyboardInputConfig)configuration;
+
+ _buttonsUserMapping.Clear();
+
+ // Then left joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
+
+ // Finally right joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
+ }
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ // No operations
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ // No operations
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ // No operations
+
+ return Vector3.Zero;
+ }
+ }
+}
diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs
new file mode 100644
index 00000000..e502254b
--- /dev/null
+++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs
@@ -0,0 +1,94 @@
+using Gdk;
+using Gtk;
+using System;
+using System.Collections.Generic;
+using GtkKey = Gdk.Key;
+
+namespace Ryujinx.Input.GTK3
+{
+ public class GTK3KeyboardDriver : IGamepadDriver
+ {
+ private readonly Widget _widget;
+ private readonly HashSet<GtkKey> _pressedKeys;
+
+ public GTK3KeyboardDriver(Widget widget)
+ {
+ _widget = widget;
+ _pressedKeys = new HashSet<GtkKey>();
+
+ _widget.KeyPressEvent += OnKeyPress;
+ _widget.KeyReleaseEvent += OnKeyRelease;
+ }
+
+ public string DriverName => "GTK3";
+
+ private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
+
+ public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
+
+ public event Action<string> OnGamepadConnected
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action<string> OnGamepadDisconnected
+ {
+ add { }
+ remove { }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _widget.KeyPressEvent -= OnKeyPress;
+ _widget.KeyReleaseEvent -= OnKeyRelease;
+ }
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ Dispose(true);
+ }
+
+ [GLib.ConnectBefore]
+ protected void OnKeyPress(object sender, KeyPressEventArgs args)
+ {
+ GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key);
+
+ _pressedKeys.Add(key);
+ }
+
+ [GLib.ConnectBefore]
+ protected void OnKeyRelease(object sender, KeyReleaseEventArgs args)
+ {
+ GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key);
+
+ _pressedKeys.Remove(key);
+ }
+
+ internal bool IsPressed(Key key)
+ {
+ if (key == Key.Unbound || key == Key.Unknown)
+ {
+ return false;
+ }
+
+ GtkKey nativeKey = GTK3MappingHelper.ToGtkKey(key);
+
+ return _pressedKeys.Contains(nativeKey);
+ }
+
+ public IGamepad GetGamepad(string id)
+ {
+ if (!_keyboardIdentifers[0].Equals(id))
+ {
+ return null;
+ }
+
+ return new GTK3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
+ }
+ }
+}
diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs
new file mode 100644
index 00000000..422a9603
--- /dev/null
+++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using GtkKey = Gdk.Key;
+
+namespace Ryujinx.Input.GTK3
+{
+ public static class GTK3MappingHelper
+ {
+ private static readonly GtkKey[] _keyMapping = new GtkKey[(int)Key.Count]
+ {
+ // NOTE: invalid
+ GtkKey.blank,
+
+ GtkKey.Shift_L,
+ GtkKey.Shift_R,
+ GtkKey.Control_L,
+ GtkKey.Control_R,
+ GtkKey.Alt_L,
+ GtkKey.Alt_R,
+ GtkKey.Super_L,
+ GtkKey.Super_R,
+ GtkKey.Menu,
+ GtkKey.F1,
+ GtkKey.F2,
+ GtkKey.F3,
+ GtkKey.F4,
+ GtkKey.F5,
+ GtkKey.F6,
+ GtkKey.F7,
+ GtkKey.F8,
+ GtkKey.F9,
+ GtkKey.F10,
+ GtkKey.F11,
+ GtkKey.F12,
+ GtkKey.F13,
+ GtkKey.F14,
+ GtkKey.F15,
+ GtkKey.F16,
+ GtkKey.F17,
+ GtkKey.F18,
+ GtkKey.F19,
+ GtkKey.F20,
+ GtkKey.F21,
+ GtkKey.F22,
+ GtkKey.F23,
+ GtkKey.F24,
+ GtkKey.F25,
+ GtkKey.F26,
+ GtkKey.F27,
+ GtkKey.F28,
+ GtkKey.F29,
+ GtkKey.F30,
+ GtkKey.F31,
+ GtkKey.F32,
+ GtkKey.F33,
+ GtkKey.F34,
+ GtkKey.F35,
+ GtkKey.Up,
+ GtkKey.Down,
+ GtkKey.Left,
+ GtkKey.Right,
+ GtkKey.Return,
+ GtkKey.Escape,
+ GtkKey.space,
+ GtkKey.Tab,
+ GtkKey.BackSpace,
+ GtkKey.Insert,
+ GtkKey.Delete,
+ GtkKey.Page_Up,
+ GtkKey.Page_Down,
+ GtkKey.Home,
+ GtkKey.End,
+ GtkKey.Caps_Lock,
+ GtkKey.Scroll_Lock,
+ GtkKey.Print,
+ GtkKey.Pause,
+ GtkKey.Num_Lock,
+ GtkKey.Clear,
+ GtkKey.KP_0,
+ GtkKey.KP_1,
+ GtkKey.KP_2,
+ GtkKey.KP_3,
+ GtkKey.KP_4,
+ GtkKey.KP_5,
+ GtkKey.KP_6,
+ GtkKey.KP_7,
+ GtkKey.KP_8,
+ GtkKey.KP_9,
+ GtkKey.KP_Divide,
+ GtkKey.KP_Multiply,
+ GtkKey.KP_Subtract,
+ GtkKey.KP_Add,
+ GtkKey.KP_Decimal,
+ GtkKey.KP_Enter,
+ GtkKey.a,
+ GtkKey.b,
+ GtkKey.c,
+ GtkKey.d,
+ GtkKey.e,
+ GtkKey.f,
+ GtkKey.g,
+ GtkKey.h,
+ GtkKey.i,
+ GtkKey.j,
+ GtkKey.k,
+ GtkKey.l,
+ GtkKey.m,
+ GtkKey.n,
+ GtkKey.o,
+ GtkKey.p,
+ GtkKey.q,
+ GtkKey.r,
+ GtkKey.s,
+ GtkKey.t,
+ GtkKey.u,
+ GtkKey.v,
+ GtkKey.w,
+ GtkKey.x,
+ GtkKey.y,
+ GtkKey.z,
+ GtkKey.Key_0,
+ GtkKey.Key_1,
+ GtkKey.Key_2,
+ GtkKey.Key_3,
+ GtkKey.Key_4,
+ GtkKey.Key_5,
+ GtkKey.Key_6,
+ GtkKey.Key_7,
+ GtkKey.Key_8,
+ GtkKey.Key_9,
+ GtkKey.grave,
+ GtkKey.grave,
+ GtkKey.minus,
+ GtkKey.plus,
+ GtkKey.bracketleft,
+ GtkKey.bracketright,
+ GtkKey.semicolon,
+ GtkKey.quoteright,
+ GtkKey.comma,
+ GtkKey.period,
+ GtkKey.slash,
+ GtkKey.backslash,
+
+ // NOTE: invalid
+ GtkKey.blank,
+ };
+
+ private static readonly Dictionary<GtkKey, Key> _gtkKeyMapping;
+
+ static GTK3MappingHelper()
+ {
+ var inputKeys = Enum.GetValues<Key>().SkipLast(1);
+
+ // GtkKey is not contiguous and quite large, so use a dictionary instead of an array.
+ _gtkKeyMapping = new Dictionary<GtkKey, Key>();
+
+ foreach (var key in inputKeys)
+ {
+ var index = ToGtkKey(key);
+ _gtkKeyMapping[index] = key;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GtkKey ToGtkKey(Key key)
+ {
+ return _keyMapping[(int)key];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Key ToInputKey(GtkKey key)
+ {
+ return _gtkKeyMapping.GetValueOrDefault(key, Key.Unknown);
+ }
+ }
+}
diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs
new file mode 100644
index 00000000..0ab817ec
--- /dev/null
+++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs
@@ -0,0 +1,90 @@
+using Ryujinx.Common.Configuration.Hid;
+using System;
+using System.Drawing;
+using System.Numerics;
+
+namespace Ryujinx.Input.GTK3
+{
+ public class GTK3Mouse : IMouse
+ {
+ private GTK3MouseDriver _driver;
+
+ public GamepadFeaturesFlag Features => throw new NotImplementedException();
+
+ public string Id => "0";
+
+ public string Name => "GTKMouse";
+
+ public bool IsConnected => true;
+
+ public bool[] Buttons => _driver.PressedButtons;
+
+ public GTK3Mouse(GTK3MouseDriver driver)
+ {
+ _driver = driver;
+ }
+
+ public Size ClientSize => _driver.GetClientSize();
+
+ public Vector2 GetPosition()
+ {
+ return _driver.CurrentPosition;
+ }
+
+ public Vector2 GetScroll()
+ {
+ return _driver.Scroll;
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ throw new NotImplementedException();
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsButtonPressed(MouseButton button)
+ {
+ return _driver.IsButtonPressed(button);
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ _driver = null;
+ }
+ }
+}
diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs
new file mode 100644
index 00000000..5962bcb2
--- /dev/null
+++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs
@@ -0,0 +1,108 @@
+using Gdk;
+using Gtk;
+using System;
+using System.Numerics;
+using Size = System.Drawing.Size;
+
+namespace Ryujinx.Input.GTK3
+{
+ public class GTK3MouseDriver : IGamepadDriver
+ {
+ private Widget _widget;
+ private bool _isDisposed;
+
+ public bool[] PressedButtons { get; }
+
+ public Vector2 CurrentPosition { get; private set; }
+ public Vector2 Scroll { get; private set; }
+
+ public GTK3MouseDriver(Widget parent)
+ {
+ _widget = parent;
+
+ _widget.MotionNotifyEvent += Parent_MotionNotifyEvent;
+ _widget.ButtonPressEvent += Parent_ButtonPressEvent;
+ _widget.ButtonReleaseEvent += Parent_ButtonReleaseEvent;
+ _widget.ScrollEvent += Parent_ScrollEvent;
+
+ PressedButtons = new bool[(int)MouseButton.Count];
+ }
+
+
+ [GLib.ConnectBefore]
+ private void Parent_ScrollEvent(object o, ScrollEventArgs args)
+ {
+ Scroll = new Vector2((float)args.Event.X, (float)args.Event.Y);
+ }
+
+ [GLib.ConnectBefore]
+ private void Parent_ButtonReleaseEvent(object o, ButtonReleaseEventArgs args)
+ {
+ PressedButtons[args.Event.Button - 1] = false;
+ }
+
+ [GLib.ConnectBefore]
+ private void Parent_ButtonPressEvent(object o, ButtonPressEventArgs args)
+ {
+ PressedButtons[args.Event.Button - 1] = true;
+ }
+
+ [GLib.ConnectBefore]
+ private void Parent_MotionNotifyEvent(object o, MotionNotifyEventArgs args)
+ {
+ if (args.Event.Device.InputSource == InputSource.Mouse)
+ {
+ CurrentPosition = new Vector2((float)args.Event.X, (float)args.Event.Y);
+ }
+ }
+
+ public bool IsButtonPressed(MouseButton button)
+ {
+ return PressedButtons[(int)button];
+ }
+
+ public Size GetClientSize()
+ {
+ return new Size(_widget.AllocatedWidth, _widget.AllocatedHeight);
+ }
+
+ public string DriverName => "GTK3";
+
+ public event Action<string> OnGamepadConnected
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action<string> OnGamepadDisconnected
+ {
+ add { }
+ remove { }
+ }
+
+ public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
+
+ public IGamepad GetGamepad(string id)
+ {
+ return new GTK3Mouse(this);
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ GC.SuppressFinalize(this);
+
+ _isDisposed = true;
+
+ _widget.MotionNotifyEvent -= Parent_MotionNotifyEvent;
+ _widget.ButtonPressEvent -= Parent_ButtonPressEvent;
+ _widget.ButtonReleaseEvent -= Parent_ButtonReleaseEvent;
+
+ _widget = null;
+ }
+ }
+}