mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-08-03 04:47:12 +02:00
Miria: The Death of OpenTK 3 (#2194)
* openal: Update to OpenTK 4 * Ryujinx.Graphics.OpenGL: Update to OpenTK 4 * Entirely removed OpenTK 3, still wip * Use SPB for context creation and handling Still need to test on GLX and readd input support * Start implementing a new input system So far only gamepad are supported, no configuration possible via UI but detected via hotplug/removal Button mapping backend is implemented TODO: front end, configuration handling and configuration migration TODO: keyboard support * Enforce RGB only framebuffer on the GLWidget Fix possible transparent window * Implement UI gamepad frontend Also fix bad mapping of minus button and ensure gamepad config is updated in real time * Handle controller being disconnected and reconnected again * Revert "Enforce RGB only framebuffer on the GLWidget" This reverts commit 0949715d1a03ec793e35e37f7b610cbff2d63965. * Fix first color clear * Filter SDL2 events a bit * Start working on the keyboard detail - Rework configuration classes a bit to be more clean. - Integrate fully the keyboard configuration to the front end (TODO: assigner) - Start skeleton for the GTK3 keyboard driver * Add KeyboardStateSnapshot and its integration * Implement keyboard assigner and GTK3 key mapping TODO: controller configuration mapping and IGamepad implementation for keyboard * Add missing SR and SL definitions * Fix copy pasta mistake on config for previous commit * Implement IGamepad interface for GTK3 keyboard * Fix some implementation still being commented in the controller ui for keyboard * Port screen handle code * Remove all configuration management code and move HidNew to Hid * Rename InputConfigNew to InputConfig * Add a version field to the input config * Prepare serialization and deserialization of new input config and migrate profile loading and saving * Support input configuration saving to config and bump config version to 23. * Clean up in ConfigurationState * Reference SPB via a nuget package * Move new input system to Ryujinx.Input project and SDL2 detail to Ryujinx.Input.SDL2 * move GTK3 input to the right directory * Fix triggers on SDL2 * Update to SDL2 2.0.14 via our own fork * Update buttons definition for SDL2 2.0.14 and report gamepad features * Implement motion support again with SDL2 TODO: cemu hooks integration * Switch to latest of nightly SDL2 * SDL2: Fix bugs in gamepad id matching allowing different gamepad to match on the same device index * Ensure values are set in UI when the gamepad get hot plugged * Avoid trying to add controllers in the Update method and don't open SDL2 gamepad instance before checking ids This fixes permanent rumble of pro controller in some hotplug scenario * Fix more UI bugs * Move legcay motion code around before reintegration * gamecontroller UI tweaks here and there * Hide Motion on non motion configurations * Update the TODO grave Some TODO were fixed long time ago or are quite oudated... * Integrate cemu hooks motion configuration * Integrate cemu hooks configuration options to the UI again * cemuhooks => cemuhooks * Add cemu hook support again * Fix regression on normal motion and fix some very nasty bugs around * Fix for XCB multithreads issue on Linux * Enable motion by default * Block inputs in the main view when in the controller configuration window * Some fixes for the controller ui again * Add joycon support and fixes other hints * Bug fixes and clean up - Invert default mapping if not a Nintendo controller - Keep alive the controller being selected on the controller window (allow to avoid big delay for controller needing time to init when doing button assignment) - Clean up hints in use - Remove debug logs around - Fixes potential double free with SDL2Gamepad * Move the button assigner and motion logic to the Ryujinx.Input project * Reimplement raw keyboard hle input Also move out the logic of the hotkeys * Move all remaining Input manager stuffs to the Ryujinx.Input project * Increment configuration version yet again because of master changes * Ensure input config isn't null when not present * Fixes for VS not being nice * Fix broken gamepad caching logic causing crashes on ui * Ensure the background context is destroyed * Update dependencies * Readd retrocompat with old format of the config to avoid parsing and crashes on those versions Also updated the debug Config.json * Document new input APIs * Isolate SDL2Driver to the project and remove external export of it * Add support for external gamepad db mappings on SDL2 * Last clean up before PR * Addresses first part of comments * Address gdkchan's comments * Do not use JsonException * Last comment fixes
This commit is contained in:
parent
978b69b706
commit
6cb22c9d38
91 changed files with 4516 additions and 2048 deletions
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 22,
|
||||
"version": 24,
|
||||
"res_scale": 1,
|
||||
"res_scale_custom": 1,
|
||||
"max_anisotropy": -1,
|
||||
|
@ -13,6 +13,7 @@
|
|||
"logging_enable_guest": true,
|
||||
"logging_enable_fs_access_log": false,
|
||||
"logging_filtered_classes": [],
|
||||
"logging_graphics_debug_level": "None",
|
||||
"enable_file_log": true,
|
||||
"system_language": "AmericanEnglish",
|
||||
"system_region": "USA",
|
||||
|
@ -25,10 +26,12 @@
|
|||
"hide_cursor_on_idle": false,
|
||||
"enable_vsync": true,
|
||||
"enable_shader_cache": true,
|
||||
"enable_multicore_scheduling": false,
|
||||
"enable_ptc": true,
|
||||
"enable_fs_integrity_checks": true,
|
||||
"fs_global_access_log_mode": 0,
|
||||
"audio_backend": "OpenAl",
|
||||
"expand_ram": false,
|
||||
"ignore_missing_services": false,
|
||||
"gui_columns": {
|
||||
"fav_column": true,
|
||||
|
@ -54,51 +57,51 @@
|
|||
"hotkeys": {
|
||||
"toggle_vsync": "Tab"
|
||||
},
|
||||
"keyboard_config": [
|
||||
"keyboard_config": [],
|
||||
"controller_config": [],
|
||||
"input_config": [
|
||||
{
|
||||
"index": 0,
|
||||
"controller_type": "JoyconPair",
|
||||
"player_index": "Player1",
|
||||
"left_joycon": {
|
||||
"left_joycon_stick": {
|
||||
"stick_up": "W",
|
||||
"stick_down": "S",
|
||||
"stick_left": "A",
|
||||
"stick_right": "D",
|
||||
"stick_button": "F",
|
||||
"dpad_up": "Up",
|
||||
"dpad_down": "Down",
|
||||
"dpad_left": "Left",
|
||||
"dpad_right": "Right",
|
||||
"button_minus": "Minus",
|
||||
"button_l": "E",
|
||||
"button_zl": "Q",
|
||||
"button_sl": "Unbound",
|
||||
"button_sr": "Unbound"
|
||||
"stick_button": "F"
|
||||
},
|
||||
"right_joycon": {
|
||||
"right_joycon_stick": {
|
||||
"stick_up": "I",
|
||||
"stick_down": "K",
|
||||
"stick_left": "J",
|
||||
"stick_right": "L",
|
||||
"stick_button": "H",
|
||||
"button_a": "Z",
|
||||
"button_b": "X",
|
||||
"button_x": "C",
|
||||
"button_y": "V",
|
||||
"stick_button": "H"
|
||||
},
|
||||
"left_joycon": {
|
||||
"button_minus": "Minus",
|
||||
"button_l": "E",
|
||||
"button_zl": "Q",
|
||||
"button_sl": "Unbound",
|
||||
"button_sr": "Unbound",
|
||||
"dpad_up": "Up",
|
||||
"dpad_down": "Down",
|
||||
"dpad_left": "Left",
|
||||
"dpad_right": "Right"
|
||||
},
|
||||
"right_joycon": {
|
||||
"button_plus": "Plus",
|
||||
"button_r": "U",
|
||||
"button_zr": "O",
|
||||
"button_sl": "Unbound",
|
||||
"button_sr": "Unbound"
|
||||
"button_sr": "Unbound",
|
||||
"button_x": "C",
|
||||
"button_b": "X",
|
||||
"button_y": "V",
|
||||
"button_a": "Z"
|
||||
},
|
||||
"slot": 0,
|
||||
"alt_slot": 0,
|
||||
"mirror_input": false,
|
||||
"dsu_server_host": "127.0.0.1",
|
||||
"dsu_server_port": 26760,
|
||||
"sensitivity": 100,
|
||||
"enable_motion": false
|
||||
"version": 1,
|
||||
"backend": "WindowKeyboard",
|
||||
"id": "0",
|
||||
"controller_type": "JoyconPair",
|
||||
"player_index": "Player1"
|
||||
}
|
||||
],
|
||||
"controller_config": []
|
||||
}
|
||||
]
|
||||
}
|
204
Ryujinx/Input/GTK3/GTK3Keyboard.cs
Normal file
204
Ryujinx/Input/GTK3/GTK3Keyboard.cs
Normal file
|
@ -0,0 +1,204 @@
|
|||
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 object _userMappingLock = new object();
|
||||
|
||||
private readonly GTK3KeyboardDriver _driver;
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
private 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
|
||||
}
|
||||
|
||||
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 OpenTK.Mathematics.Vector2(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;
|
||||
}
|
||||
}
|
||||
}
|
93
Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs
Normal file
93
Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
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 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()
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
154
Ryujinx/Input/GTK3/GTK3MappingHelper.cs
Normal file
154
Ryujinx/Input/GTK3/GTK3MappingHelper.cs
Normal file
|
@ -0,0 +1,154 @@
|
|||
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.F29,
|
||||
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.minus,
|
||||
GtkKey.plus,
|
||||
GtkKey.bracketleft,
|
||||
GtkKey.bracketright,
|
||||
GtkKey.semicolon,
|
||||
GtkKey.quotedbl,
|
||||
GtkKey.comma,
|
||||
GtkKey.period,
|
||||
GtkKey.slash,
|
||||
GtkKey.backslash,
|
||||
GtkKey.backslash,
|
||||
|
||||
// NOTE: invalid
|
||||
GtkKey.blank,
|
||||
};
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GtkKey ToGtkKey(Key key)
|
||||
{
|
||||
return _keyMapping[(int)key];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,459 +0,0 @@
|
|||
using Force.Crc32;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
public class Client : IDisposable
|
||||
{
|
||||
public const uint Magic = 0x43555344; // DSUC
|
||||
public const ushort Version = 1001;
|
||||
|
||||
private bool _active;
|
||||
|
||||
private readonly Dictionary<int, IPEndPoint> _hosts;
|
||||
private readonly Dictionary<int, Dictionary<int, MotionInput>> _motionData;
|
||||
private readonly Dictionary<int, UdpClient> _clients;
|
||||
|
||||
private readonly bool[] _clientErrorStatus = new bool[Enum.GetValues(typeof(PlayerIndex)).Length];
|
||||
private readonly long[] _clientRetryTimer = new long[Enum.GetValues(typeof(PlayerIndex)).Length];
|
||||
|
||||
public Client()
|
||||
{
|
||||
_hosts = new Dictionary<int, IPEndPoint>();
|
||||
_motionData = new Dictionary<int, Dictionary<int, MotionInput>>();
|
||||
_clients = new Dictionary<int, UdpClient>();
|
||||
|
||||
CloseClients();
|
||||
}
|
||||
|
||||
public void CloseClients()
|
||||
{
|
||||
_active = false;
|
||||
|
||||
lock (_clients)
|
||||
{
|
||||
foreach (var client in _clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Value?.Dispose();
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to dispose motion client. Error: {socketException.ErrorCode}");
|
||||
}
|
||||
}
|
||||
|
||||
_hosts.Clear();
|
||||
_clients.Clear();
|
||||
_motionData.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterClient(int player, string host, int port)
|
||||
{
|
||||
if (_clients.ContainsKey(player) || !CanConnect(player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_clients)
|
||||
{
|
||||
if (_clients.ContainsKey(player) || !CanConnect(player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UdpClient client = null;
|
||||
|
||||
try
|
||||
{
|
||||
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(host), port);
|
||||
|
||||
client = new UdpClient(host, port);
|
||||
|
||||
_clients.Add(player, client);
|
||||
_hosts.Add(player, endPoint);
|
||||
|
||||
_active = true;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
ReceiveLoop(player);
|
||||
});
|
||||
}
|
||||
catch (FormatException formatException)
|
||||
{
|
||||
if (!_clientErrorStatus[player])
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error: {formatException.Message}");
|
||||
|
||||
_clientErrorStatus[player] = true;
|
||||
}
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
{
|
||||
if (!_clientErrorStatus[player])
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error: {socketException.ErrorCode}");
|
||||
|
||||
_clientErrorStatus[player] = true;
|
||||
}
|
||||
|
||||
RemoveClient(player);
|
||||
|
||||
client?.Dispose();
|
||||
|
||||
SetRetryTimer(player);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to register motion client. Error: {exception.Message}");
|
||||
|
||||
_clientErrorStatus[player] = true;
|
||||
|
||||
RemoveClient(player);
|
||||
|
||||
client?.Dispose();
|
||||
|
||||
SetRetryTimer(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetData(int player, int slot, out MotionInput input)
|
||||
{
|
||||
lock (_motionData)
|
||||
{
|
||||
if (_motionData.ContainsKey(player))
|
||||
{
|
||||
if (_motionData[player].TryGetValue(slot, out input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RemoveClient(int clientId)
|
||||
{
|
||||
_clients?.Remove(clientId);
|
||||
|
||||
_hosts?.Remove(clientId);
|
||||
}
|
||||
|
||||
private void Send(byte[] data, int clientId)
|
||||
{
|
||||
if (_clients.TryGetValue(clientId, out UdpClient _client))
|
||||
{
|
||||
if (_client != null && _client.Client != null && _client.Client.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
_client?.Send(data, data.Length);
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
{
|
||||
if (!_clientErrorStatus[clientId])
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to send data request to motion source at {_client.Client.RemoteEndPoint}. Error: {socketException.ErrorCode}");
|
||||
}
|
||||
|
||||
_clientErrorStatus[clientId] = true;
|
||||
|
||||
RemoveClient(clientId);
|
||||
|
||||
_client?.Dispose();
|
||||
|
||||
SetRetryTimer(clientId);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_clientErrorStatus[clientId] = true;
|
||||
|
||||
RemoveClient(clientId);
|
||||
|
||||
_client?.Dispose();
|
||||
|
||||
SetRetryTimer(clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] Receive(int clientId, int timeout = 0)
|
||||
{
|
||||
if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint) && _clients.TryGetValue(clientId, out UdpClient _client))
|
||||
{
|
||||
if (_client != null && _client.Client != null && _client.Client.Connected)
|
||||
{
|
||||
_client.Client.ReceiveTimeout = timeout;
|
||||
|
||||
var result = _client?.Receive(ref endPoint);
|
||||
|
||||
if (result.Length > 0)
|
||||
{
|
||||
_clientErrorStatus[clientId] = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Client {clientId} is not registered.");
|
||||
}
|
||||
|
||||
private void SetRetryTimer(int clientId)
|
||||
{
|
||||
var elapsedMs = PerformanceCounter.ElapsedMilliseconds;
|
||||
|
||||
_clientRetryTimer[clientId] = elapsedMs;
|
||||
}
|
||||
|
||||
private void ResetRetryTimer(int clientId)
|
||||
{
|
||||
_clientRetryTimer[clientId] = 0;
|
||||
}
|
||||
|
||||
private bool CanConnect(int clientId)
|
||||
{
|
||||
return _clientRetryTimer[clientId] == 0 || PerformanceCounter.ElapsedMilliseconds - 5000 > _clientRetryTimer[clientId];
|
||||
}
|
||||
|
||||
public void ReceiveLoop(int clientId)
|
||||
{
|
||||
if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint) && _clients.TryGetValue(clientId, out UdpClient _client))
|
||||
{
|
||||
if (_client != null && _client.Client != null && _client.Client.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_active)
|
||||
{
|
||||
byte[] data = Receive(clientId);
|
||||
|
||||
if (data.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Task.Run(() => HandleResponse(data, clientId));
|
||||
}
|
||||
}
|
||||
catch (SocketException socketException)
|
||||
{
|
||||
if (!_clientErrorStatus[clientId])
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to receive data from motion source at {endPoint}. Error: {socketException.ErrorCode}");
|
||||
}
|
||||
|
||||
_clientErrorStatus[clientId] = true;
|
||||
|
||||
RemoveClient(clientId);
|
||||
|
||||
_client?.Dispose();
|
||||
|
||||
SetRetryTimer(clientId);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_clientErrorStatus[clientId] = true;
|
||||
|
||||
RemoveClient(clientId);
|
||||
|
||||
_client?.Dispose();
|
||||
|
||||
SetRetryTimer(clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleResponse(byte[] data, int clientId)
|
||||
{
|
||||
ResetRetryTimer(clientId);
|
||||
|
||||
MessageType type = (MessageType)BitConverter.ToUInt32(data.AsSpan().Slice(16, 4));
|
||||
|
||||
data = data.AsSpan()[16..].ToArray();
|
||||
|
||||
using MemoryStream stream = new MemoryStream(data);
|
||||
using BinaryReader reader = new BinaryReader(stream);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MessageType.Protocol:
|
||||
break;
|
||||
case MessageType.Info:
|
||||
ControllerInfoResponse contollerInfo = reader.ReadStruct<ControllerInfoResponse>();
|
||||
break;
|
||||
case MessageType.Data:
|
||||
ControllerDataResponse inputData = reader.ReadStruct<ControllerDataResponse>();
|
||||
|
||||
Vector3 accelerometer = new Vector3()
|
||||
{
|
||||
X = -inputData.AccelerometerX,
|
||||
Y = inputData.AccelerometerZ,
|
||||
Z = -inputData.AccelerometerY
|
||||
};
|
||||
|
||||
Vector3 gyroscrope = new Vector3()
|
||||
{
|
||||
X = inputData.GyroscopePitch,
|
||||
Y = inputData.GyroscopeRoll,
|
||||
Z = -inputData.GyroscopeYaw
|
||||
};
|
||||
|
||||
ulong timestamp = inputData.MotionTimestamp;
|
||||
|
||||
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == (PlayerIndex)clientId);
|
||||
|
||||
lock (_motionData)
|
||||
{
|
||||
int slot = inputData.Shared.Slot;
|
||||
|
||||
if (_motionData.ContainsKey(clientId))
|
||||
{
|
||||
if (_motionData[clientId].ContainsKey(slot))
|
||||
{
|
||||
MotionInput previousData = _motionData[clientId][slot];
|
||||
|
||||
previousData.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
|
||||
}
|
||||
else
|
||||
{
|
||||
MotionInput input = new MotionInput();
|
||||
|
||||
input.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
|
||||
|
||||
_motionData[clientId].Add(slot, input);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MotionInput input = new MotionInput();
|
||||
|
||||
input.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
|
||||
|
||||
_motionData.Add(clientId, new Dictionary<int, MotionInput>() { { slot, input } });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestInfo(int clientId, int slot)
|
||||
{
|
||||
if (!_active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Header header = GenerateHeader(clientId);
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.WriteStruct(header);
|
||||
|
||||
ControllerInfoRequest request = new ControllerInfoRequest()
|
||||
{
|
||||
Type = MessageType.Info,
|
||||
PortsCount = 4
|
||||
};
|
||||
|
||||
request.PortIndices[0] = (byte)slot;
|
||||
|
||||
writer.WriteStruct(request);
|
||||
|
||||
header.Length = (ushort)(stream.Length - 16);
|
||||
|
||||
writer.Seek(6, SeekOrigin.Begin);
|
||||
writer.Write(header.Length);
|
||||
|
||||
header.Crc32 = Crc32Algorithm.Compute(stream.ToArray());
|
||||
|
||||
writer.Seek(8, SeekOrigin.Begin);
|
||||
writer.Write(header.Crc32);
|
||||
|
||||
byte[] data = stream.ToArray();
|
||||
|
||||
Send(data, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void RequestData(int clientId, int slot)
|
||||
{
|
||||
if (!_active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Header header = GenerateHeader(clientId);
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.WriteStruct(header);
|
||||
|
||||
ControllerDataRequest request = new ControllerDataRequest()
|
||||
{
|
||||
Type = MessageType.Data,
|
||||
Slot = (byte)slot,
|
||||
SubscriberType = SubscriberType.Slot
|
||||
};
|
||||
|
||||
writer.WriteStruct(request);
|
||||
|
||||
header.Length = (ushort)(stream.Length - 16);
|
||||
|
||||
writer.Seek(6, SeekOrigin.Begin);
|
||||
writer.Write(header.Length);
|
||||
|
||||
header.Crc32 = Crc32Algorithm.Compute(stream.ToArray());
|
||||
|
||||
writer.Seek(8, SeekOrigin.Begin);
|
||||
writer.Write(header.Crc32);
|
||||
|
||||
byte[] data = stream.ToArray();
|
||||
|
||||
Send(data, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
private Header GenerateHeader(int clientId)
|
||||
{
|
||||
Header header = new Header()
|
||||
{
|
||||
Id = (uint)clientId,
|
||||
MagicString = Magic,
|
||||
Version = Version,
|
||||
Length = 0,
|
||||
Crc32 = 0
|
||||
};
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_active = false;
|
||||
|
||||
CloseClients();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Configuration;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
public class MotionDevice
|
||||
{
|
||||
public Vector3 Gyroscope { get; private set; }
|
||||
public Vector3 Accelerometer { get; private set; }
|
||||
public Vector3 Rotation { get; private set; }
|
||||
public float[] Orientation { get; private set; }
|
||||
|
||||
private readonly Client _motionSource;
|
||||
|
||||
public MotionDevice(Client motionSource)
|
||||
{
|
||||
_motionSource = motionSource;
|
||||
}
|
||||
|
||||
public void RegisterController(PlayerIndex player)
|
||||
{
|
||||
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == player);
|
||||
|
||||
if (config != null && config.EnableMotion)
|
||||
{
|
||||
string host = config.DsuServerHost;
|
||||
int port = config.DsuServerPort;
|
||||
|
||||
_motionSource.RegisterClient((int)player, host, port);
|
||||
_motionSource.RequestData((int)player, config.Slot);
|
||||
|
||||
if (config.ControllerType == ControllerType.JoyconPair && !config.MirrorInput)
|
||||
{
|
||||
_motionSource.RequestData((int)player, config.AltSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Poll(InputConfig config, int slot)
|
||||
{
|
||||
Orientation = new float[9];
|
||||
|
||||
if (!config.EnableMotion || !_motionSource.TryGetData((int)config.PlayerIndex, slot, out MotionInput input))
|
||||
{
|
||||
Accelerometer = new Vector3();
|
||||
Gyroscope = new Vector3();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Gyroscope = Truncate(input.Gyroscrope * 0.0027f, 3);
|
||||
Accelerometer = Truncate(input.Accelerometer, 3);
|
||||
Rotation = Truncate(input.Rotation * 0.0027f, 3);
|
||||
|
||||
Matrix4x4 orientation = input.GetOrientation();
|
||||
|
||||
Orientation[0] = Math.Clamp(orientation.M11, -1f, 1f);
|
||||
Orientation[1] = Math.Clamp(orientation.M12, -1f, 1f);
|
||||
Orientation[2] = Math.Clamp(orientation.M13, -1f, 1f);
|
||||
Orientation[3] = Math.Clamp(orientation.M21, -1f, 1f);
|
||||
Orientation[4] = Math.Clamp(orientation.M22, -1f, 1f);
|
||||
Orientation[5] = Math.Clamp(orientation.M23, -1f, 1f);
|
||||
Orientation[6] = Math.Clamp(orientation.M31, -1f, 1f);
|
||||
Orientation[7] = Math.Clamp(orientation.M32, -1f, 1f);
|
||||
Orientation[8] = Math.Clamp(orientation.M33, -1f, 1f);
|
||||
}
|
||||
|
||||
private static Vector3 Truncate(Vector3 value, int decimals)
|
||||
{
|
||||
float power = MathF.Pow(10, decimals);
|
||||
|
||||
value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power;
|
||||
value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power;
|
||||
value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
public class MotionInput
|
||||
{
|
||||
public ulong TimeStamp { get; set; }
|
||||
public Vector3 Accelerometer { get; set; }
|
||||
public Vector3 Gyroscrope { get; set; }
|
||||
public Vector3 Rotation { get; set; }
|
||||
|
||||
private readonly MotionSensorFilter _filter;
|
||||
private int _calibrationFrame = 0;
|
||||
|
||||
public MotionInput()
|
||||
{
|
||||
TimeStamp = 0;
|
||||
Accelerometer = new Vector3();
|
||||
Gyroscrope = new Vector3();
|
||||
Rotation = new Vector3();
|
||||
|
||||
// TODO: RE the correct filter.
|
||||
_filter = new MotionSensorFilter(0f);
|
||||
}
|
||||
|
||||
public void Update(Vector3 accel, Vector3 gyro, ulong timestamp, int sensitivity, float deadzone)
|
||||
{
|
||||
if (TimeStamp != 0)
|
||||
{
|
||||
if (gyro.Length() <= 1f && accel.Length() >= 0.8f && accel.Z >= 0.8f)
|
||||
{
|
||||
_calibrationFrame++;
|
||||
|
||||
if (_calibrationFrame >= 90)
|
||||
{
|
||||
gyro = Vector3.Zero;
|
||||
|
||||
Rotation = Vector3.Zero;
|
||||
|
||||
_filter.Reset();
|
||||
|
||||
_calibrationFrame = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_calibrationFrame = 0;
|
||||
}
|
||||
|
||||
Accelerometer = -accel;
|
||||
|
||||
if (gyro.Length() < deadzone)
|
||||
{
|
||||
gyro = Vector3.Zero;
|
||||
}
|
||||
|
||||
gyro *= (sensitivity / 100f);
|
||||
|
||||
Gyroscrope = gyro;
|
||||
|
||||
float deltaTime = MathF.Abs((long)(timestamp - TimeStamp) / 1000000f);
|
||||
|
||||
Vector3 deltaGyro = gyro * deltaTime;
|
||||
|
||||
Rotation += deltaGyro;
|
||||
|
||||
_filter.SamplePeriod = deltaTime;
|
||||
_filter.Update(accel, DegreeToRad(gyro));
|
||||
}
|
||||
|
||||
TimeStamp = timestamp;
|
||||
}
|
||||
|
||||
public Matrix4x4 GetOrientation()
|
||||
{
|
||||
return Matrix4x4.CreateFromQuaternion(_filter.Quaternion);
|
||||
}
|
||||
|
||||
private static Vector3 DegreeToRad(Vector3 degree)
|
||||
{
|
||||
return degree * (MathF.PI / 180);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
// MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm.
|
||||
// See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
|
||||
// Based on: https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
|
||||
public class MotionSensorFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample rate coefficient.
|
||||
/// </summary>
|
||||
public const float SampleRateCoefficient = 0.45f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sample period.
|
||||
/// </summary>
|
||||
public float SamplePeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the algorithm proportional gain.
|
||||
/// </summary>
|
||||
public float Kp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the algorithm integral gain.
|
||||
/// </summary>
|
||||
public float Ki { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Quaternion output.
|
||||
/// </summary>
|
||||
public Quaternion Quaternion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Integral error.
|
||||
/// </summary>
|
||||
private Vector3 _intergralError;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="samplePeriod">
|
||||
/// Sample period.
|
||||
/// </param>
|
||||
public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="samplePeriod">
|
||||
/// Sample period.
|
||||
/// </param>
|
||||
/// <param name="kp">
|
||||
/// Algorithm proportional gain.
|
||||
/// </param>
|
||||
public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="samplePeriod">
|
||||
/// Sample period.
|
||||
/// </param>
|
||||
/// <param name="kp">
|
||||
/// Algorithm proportional gain.
|
||||
/// </param>
|
||||
/// <param name="ki">
|
||||
/// Algorithm integral gain.
|
||||
/// </param>
|
||||
public MotionSensorFilter(float samplePeriod, float kp, float ki)
|
||||
{
|
||||
SamplePeriod = samplePeriod;
|
||||
Kp = kp;
|
||||
Ki = ki;
|
||||
|
||||
Reset();
|
||||
|
||||
_intergralError = new Vector3();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm IMU update method. Requires only gyroscope and accelerometer data.
|
||||
/// </summary>
|
||||
/// <param name="accel">
|
||||
/// Accelerometer measurement in any calibrated units.
|
||||
/// </param>
|
||||
/// <param name="gyro">
|
||||
/// Gyroscope measurement in radians.
|
||||
/// </param>
|
||||
public void Update(Vector3 accel, Vector3 gyro)
|
||||
{
|
||||
// Normalise accelerometer measurement.
|
||||
float norm = 1f / accel.Length();
|
||||
|
||||
if (!float.IsFinite(norm))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
accel *= norm;
|
||||
|
||||
float q2 = Quaternion.X;
|
||||
float q3 = Quaternion.Y;
|
||||
float q4 = Quaternion.Z;
|
||||
float q1 = Quaternion.W;
|
||||
|
||||
// Estimated direction of gravity.
|
||||
Vector3 gravity = new Vector3()
|
||||
{
|
||||
X = 2f * (q2 * q4 - q1 * q3),
|
||||
Y = 2f * (q1 * q2 + q3 * q4),
|
||||
Z = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
|
||||
};
|
||||
|
||||
// Error is cross product between estimated direction and measured direction of gravity.
|
||||
Vector3 error = new Vector3()
|
||||
{
|
||||
X = accel.Y * gravity.Z - accel.Z * gravity.Y,
|
||||
Y = accel.Z * gravity.X - accel.X * gravity.Z,
|
||||
Z = accel.X * gravity.Y - accel.Y * gravity.X
|
||||
};
|
||||
|
||||
if (Ki > 0f)
|
||||
{
|
||||
_intergralError += error; // Accumulate integral error.
|
||||
}
|
||||
else
|
||||
{
|
||||
_intergralError = Vector3.Zero; // Prevent integral wind up.
|
||||
}
|
||||
|
||||
// Apply feedback terms.
|
||||
gyro += (Kp * error) + (Ki * _intergralError);
|
||||
|
||||
// Integrate rate of change of quaternion.
|
||||
Vector3 delta = new Vector3(q2, q3, q4);
|
||||
|
||||
q1 += (-q2 * gyro.X - q3 * gyro.Y - q4 * gyro.Z) * (SampleRateCoefficient * SamplePeriod);
|
||||
q2 += (q1 * gyro.X + delta.Y * gyro.Z - delta.Z * gyro.Y) * (SampleRateCoefficient * SamplePeriod);
|
||||
q3 += (q1 * gyro.Y - delta.X * gyro.Z + delta.Z * gyro.X) * (SampleRateCoefficient * SamplePeriod);
|
||||
q4 += (q1 * gyro.Z + delta.X * gyro.Y - delta.Y * gyro.X) * (SampleRateCoefficient * SamplePeriod);
|
||||
|
||||
// Normalise quaternion.
|
||||
Quaternion quaternion = new Quaternion(q2, q3, q4, q1);
|
||||
|
||||
norm = 1f / quaternion.Length();
|
||||
|
||||
if (!float.IsFinite(norm))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Quaternion = quaternion * norm;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Quaternion = Quaternion.Identity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct ControllerDataRequest
|
||||
{
|
||||
public MessageType Type;
|
||||
public SubscriberType SubscriberType;
|
||||
public byte Slot;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public byte[] MacAddress;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ControllerDataResponse
|
||||
{
|
||||
public SharedResponse Shared;
|
||||
public byte Connected;
|
||||
public uint PacketId;
|
||||
public byte ExtraButtons;
|
||||
public byte MainButtons;
|
||||
public ushort PSExtraInput;
|
||||
public ushort LeftStickXY;
|
||||
public ushort RightStickXY;
|
||||
public uint DPadAnalog;
|
||||
public ulong MainButtonsAnalog;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public byte[] Touch1;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public byte[] Touch2;
|
||||
|
||||
public ulong MotionTimestamp;
|
||||
public float AccelerometerX;
|
||||
public float AccelerometerY;
|
||||
public float AccelerometerZ;
|
||||
public float GyroscopePitch;
|
||||
public float GyroscopeYaw;
|
||||
public float GyroscopeRoll;
|
||||
}
|
||||
|
||||
enum SubscriberType : byte
|
||||
{
|
||||
All,
|
||||
Slot,
|
||||
Mac
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ControllerInfoResponse
|
||||
{
|
||||
public SharedResponse Shared;
|
||||
private byte _zero;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ControllerInfoRequest
|
||||
{
|
||||
public MessageType Type;
|
||||
public int PortsCount;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public byte[] PortIndices;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Header
|
||||
{
|
||||
public uint MagicString;
|
||||
public ushort Version;
|
||||
public ushort Length;
|
||||
public uint Crc32;
|
||||
public uint Id;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
public enum MessageType : uint
|
||||
{
|
||||
Protocol = 0x100000,
|
||||
Info,
|
||||
Data
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SharedResponse
|
||||
{
|
||||
public MessageType Type;
|
||||
public byte Slot;
|
||||
public SlotState State;
|
||||
public DeviceModelType ModelType;
|
||||
public ConnectionType ConnectionType;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public byte[] MacAddress;
|
||||
public BatteryStatus BatteryStatus;
|
||||
}
|
||||
|
||||
public enum SlotState : byte
|
||||
{
|
||||
Disconnected,
|
||||
Reserved,
|
||||
Connected
|
||||
}
|
||||
|
||||
public enum DeviceModelType : byte
|
||||
{
|
||||
None,
|
||||
PartialGyro,
|
||||
FullGyro
|
||||
}
|
||||
|
||||
public enum ConnectionType : byte
|
||||
{
|
||||
None,
|
||||
USB,
|
||||
Bluetooth
|
||||
}
|
||||
|
||||
public enum BatteryStatus : byte
|
||||
{
|
||||
NA,
|
||||
Dying,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Full,
|
||||
Charging,
|
||||
Charged
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using ARMeilleure.Translation.PTC;
|
||||
using Gtk;
|
||||
using OpenTK;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.System;
|
||||
|
@ -12,6 +11,7 @@ using Ryujinx.Ui.Widgets;
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx
|
||||
|
@ -24,6 +24,9 @@ namespace Ryujinx
|
|||
|
||||
public static string ConfigurationPath { get; set; }
|
||||
|
||||
[DllImport("libX11")]
|
||||
private extern static int XInitThreads();
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Parse Arguments.
|
||||
|
@ -63,15 +66,17 @@ namespace Ryujinx
|
|||
// Delete backup files after updating.
|
||||
Task.Run(Updater.CleanupUpdate);
|
||||
|
||||
Toolkit.Init(new ToolkitOptions
|
||||
{
|
||||
Backend = PlatformBackend.PreferNative
|
||||
});
|
||||
|
||||
Version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
|
||||
Console.Title = $"Ryujinx Console {Version}";
|
||||
|
||||
// NOTE: GTK3 doesn't init X11 in a multi threaded way.
|
||||
// This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads).
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
XInitThreads();
|
||||
}
|
||||
|
||||
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
|
||||
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
|
||||
|
||||
|
|
|
@ -12,18 +12,19 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crc32.NET" Version="1.2.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
|
||||
<PackageReference Include="GLWidget" Version="1.0.2" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
|
||||
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.3.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
|
||||
<PackageReference Include="OpenTK.Graphics" Version="4.5.0" />
|
||||
<PackageReference Include="SPB" Version="0.0.2" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
|
|
|
@ -1,39 +1,37 @@
|
|||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Gdk;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Input;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.Modules.Motion;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.Ui.Widgets;
|
||||
using SPB.Graphics;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
using Switch = HLE.Switch;
|
||||
|
||||
public class GlRenderer : GLWidget
|
||||
{
|
||||
static GlRenderer()
|
||||
{
|
||||
OpenTK.Graphics.GraphicsContext.ShareContexts = true;
|
||||
}
|
||||
|
||||
private const int SwitchPanelWidth = 1280;
|
||||
private const int SwitchPanelHeight = 720;
|
||||
private const int TargetFps = 60;
|
||||
|
||||
public ManualResetEvent WaitEvent { get; set; }
|
||||
public NpadManager NpadManager { get; }
|
||||
|
||||
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||
|
||||
|
@ -58,9 +56,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
private Renderer _renderer;
|
||||
|
||||
private HotkeyButtons _prevHotkeyButtons;
|
||||
|
||||
private Client _dsuClient;
|
||||
private KeyboardHotkeyState _prevHotkeyState;
|
||||
|
||||
private GraphicsDebugLevel _glLogLevel;
|
||||
|
||||
|
@ -71,14 +67,22 @@ namespace Ryujinx.Ui
|
|||
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
|
||||
private long _lastCursorMoveTime;
|
||||
private bool _hideCursorOnIdle;
|
||||
private InputManager _inputManager;
|
||||
private IKeyboard _keyboardInterface;
|
||||
|
||||
public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel)
|
||||
public GlRenderer(Switch device, InputManager inputManager, GraphicsDebugLevel glLogLevel)
|
||||
: base (GetGraphicsMode(),
|
||||
3, 3,
|
||||
glLogLevel == GraphicsDebugLevel.None
|
||||
? GraphicsContextFlags.ForwardCompatible
|
||||
: GraphicsContextFlags.ForwardCompatible | GraphicsContextFlags.Debug)
|
||||
? OpenGLContextFlags.Compat
|
||||
: OpenGLContextFlags.Compat | OpenGLContextFlags.Debug)
|
||||
{
|
||||
_inputManager = inputManager;
|
||||
NpadManager = _inputManager.CreateNpadManager();
|
||||
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
|
||||
|
||||
NpadManager.ReloadConfiguration(ConfigurationState.Instance.Hid.InputConfig.Value.ToList());
|
||||
|
||||
WaitEvent = new ManualResetEvent(false);
|
||||
|
||||
_device = device;
|
||||
|
@ -101,8 +105,6 @@ namespace Ryujinx.Ui
|
|||
|
||||
Shown += Renderer_Shown;
|
||||
|
||||
_dsuClient = new Client();
|
||||
|
||||
_glLogLevel = glLogLevel;
|
||||
|
||||
_exitEvent = new ManualResetEvent(false);
|
||||
|
@ -130,15 +132,15 @@ namespace Ryujinx.Ui
|
|||
});
|
||||
}
|
||||
|
||||
private static GraphicsMode GetGraphicsMode()
|
||||
private static FramebufferFormat GetGraphicsMode()
|
||||
{
|
||||
return Environment.OSVersion.Platform == PlatformID.Unix ? new GraphicsMode(new ColorFormat(24)) : new GraphicsMode(new ColorFormat());
|
||||
return Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
|
||||
}
|
||||
|
||||
private void GLRenderer_ShuttingDown(object sender, EventArgs args)
|
||||
{
|
||||
_device.DisposeGpu();
|
||||
_dsuClient?.Dispose();
|
||||
NpadManager.Dispose();
|
||||
}
|
||||
|
||||
private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
|
||||
|
@ -155,7 +157,7 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
|
||||
|
||||
_dsuClient?.Dispose();
|
||||
NpadManager.Dispose();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
|
@ -164,13 +166,13 @@ namespace Ryujinx.Ui
|
|||
_isFocused = this.ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
|
||||
}
|
||||
|
||||
public void HandleScreenState(KeyboardState keyboard)
|
||||
public void HandleScreenState(KeyboardStateSnapshot keyboard)
|
||||
{
|
||||
bool toggleFullscreen = keyboard.IsKeyDown(OpenTK.Input.Key.F11)
|
||||
|| ((keyboard.IsKeyDown(OpenTK.Input.Key.AltLeft)
|
||||
|| keyboard.IsKeyDown(OpenTK.Input.Key.AltRight))
|
||||
&& keyboard.IsKeyDown(OpenTK.Input.Key.Enter))
|
||||
|| keyboard.IsKeyDown(OpenTK.Input.Key.Escape);
|
||||
bool toggleFullscreen = keyboard.IsPressed(Key.F11)
|
||||
|| ((keyboard.IsPressed(Key.AltLeft)
|
||||
|| keyboard.IsPressed(Key.AltRight))
|
||||
&& keyboard.IsPressed(Key.Enter))
|
||||
|| keyboard.IsPressed(Key.Escape);
|
||||
|
||||
bool fullScreenToggled = ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen);
|
||||
|
||||
|
@ -185,7 +187,7 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
else
|
||||
{
|
||||
if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape))
|
||||
if (keyboard.IsPressed(Key.Escape))
|
||||
{
|
||||
if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
|
||||
{
|
||||
|
@ -203,7 +205,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
_toggleFullscreen = toggleFullscreen;
|
||||
|
||||
bool toggleDockedMode = keyboard.IsKeyDown(OpenTK.Input.Key.F9);
|
||||
bool toggleDockedMode = keyboard.IsPressed(Key.F9);
|
||||
|
||||
if (toggleDockedMode != _toggleDockedMode)
|
||||
{
|
||||
|
@ -225,8 +227,8 @@ namespace Ryujinx.Ui
|
|||
|
||||
private void GLRenderer_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
// Release the GL exclusivity that OpenTK gave us as we aren't going to use it in GTK Thread.
|
||||
GraphicsContext.MakeCurrent(null);
|
||||
// Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread.
|
||||
OpenGLContext.MakeCurrent(null);
|
||||
|
||||
WaitEvent.Set();
|
||||
}
|
||||
|
@ -244,8 +246,6 @@ namespace Ryujinx.Ui
|
|||
|
||||
public void Start()
|
||||
{
|
||||
IsRenderHandler = true;
|
||||
|
||||
_chrono.Restart();
|
||||
|
||||
_isActive = true;
|
||||
|
@ -389,7 +389,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
public void Exit()
|
||||
{
|
||||
_dsuClient?.Dispose();
|
||||
NpadManager?.Dispose();
|
||||
|
||||
if (_isStopped)
|
||||
{
|
||||
|
@ -416,15 +416,17 @@ namespace Ryujinx.Ui
|
|||
public void Render()
|
||||
{
|
||||
// First take exclusivity on the OpenGL context.
|
||||
_renderer.InitializeBackgroundContext(GraphicsContext);
|
||||
_renderer.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(OpenGLContext));
|
||||
|
||||
Gtk.Window parent = Toplevel as Gtk.Window;
|
||||
parent.Present();
|
||||
GraphicsContext.MakeCurrent(WindowInfo);
|
||||
|
||||
OpenGLContext.MakeCurrent(NativeWindow);
|
||||
|
||||
_device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||
|
||||
// Make sure the first frame is not transparent.
|
||||
GL.ClearColor(OpenTK.Color.Black);
|
||||
GL.ClearColor(0, 0, 0, 1.0f);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
SwapBuffers();
|
||||
|
||||
|
@ -478,7 +480,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
public void SwapBuffers()
|
||||
{
|
||||
OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers();
|
||||
NativeWindow.SwapBuffers();
|
||||
}
|
||||
|
||||
public void MainLoop()
|
||||
|
@ -510,13 +512,13 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
Gtk.Application.Invoke(delegate
|
||||
{
|
||||
KeyboardState keyboard = OpenTK.Input.Keyboard.GetState();
|
||||
KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot();
|
||||
|
||||
HandleScreenState(keyboard);
|
||||
|
||||
if (keyboard.IsKeyDown(OpenTK.Input.Key.Delete))
|
||||
if (keyboard.IsPressed(Key.Delete))
|
||||
{
|
||||
if (!ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen))
|
||||
if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
|
||||
{
|
||||
Ptc.Continue();
|
||||
}
|
||||
|
@ -524,154 +526,19 @@ namespace Ryujinx.Ui
|
|||
});
|
||||
}
|
||||
|
||||
List<GamepadInput> gamepadInputs = new List<GamepadInput>(NpadDevices.MaxControllers);
|
||||
List<SixAxisInput> motionInputs = new List<SixAxisInput>(NpadDevices.MaxControllers);
|
||||
|
||||
MotionDevice motionDevice = new MotionDevice(_dsuClient);
|
||||
|
||||
foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
|
||||
{
|
||||
ControllerKeys currentButton = 0;
|
||||
JoystickPosition leftJoystick = new JoystickPosition();
|
||||
JoystickPosition rightJoystick = new JoystickPosition();
|
||||
KeyboardInput? hidKeyboard = null;
|
||||
|
||||
int leftJoystickDx = 0;
|
||||
int leftJoystickDy = 0;
|
||||
int rightJoystickDx = 0;
|
||||
int rightJoystickDy = 0;
|
||||
|
||||
if (inputConfig.EnableMotion)
|
||||
{
|
||||
motionDevice.RegisterController(inputConfig.PlayerIndex);
|
||||
}
|
||||
|
||||
if (inputConfig is KeyboardConfig keyboardConfig)
|
||||
{
|
||||
if (_isFocused)
|
||||
{
|
||||
// Keyboard Input
|
||||
KeyboardController keyboardController = new KeyboardController(keyboardConfig);
|
||||
|
||||
currentButton = keyboardController.GetButtons();
|
||||
|
||||
(leftJoystickDx, leftJoystickDy) = keyboardController.GetLeftStick();
|
||||
(rightJoystickDx, rightJoystickDy) = keyboardController.GetRightStick();
|
||||
|
||||
leftJoystick = new JoystickPosition
|
||||
{
|
||||
Dx = leftJoystickDx,
|
||||
Dy = leftJoystickDy
|
||||
};
|
||||
|
||||
rightJoystick = new JoystickPosition
|
||||
{
|
||||
Dx = rightJoystickDx,
|
||||
Dy = rightJoystickDy
|
||||
};
|
||||
|
||||
if (ConfigurationState.Instance.Hid.EnableKeyboard)
|
||||
{
|
||||
hidKeyboard = keyboardController.GetKeysDown();
|
||||
}
|
||||
|
||||
if (!hidKeyboard.HasValue)
|
||||
{
|
||||
hidKeyboard = new KeyboardInput
|
||||
{
|
||||
Modifier = 0,
|
||||
Keys = new int[0x8]
|
||||
};
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Hid.EnableKeyboard)
|
||||
{
|
||||
_device.Hid.Keyboard.Update(hidKeyboard.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (inputConfig is Common.Configuration.Hid.ControllerConfig controllerConfig)
|
||||
{
|
||||
// Controller Input
|
||||
JoystickController joystickController = new JoystickController(controllerConfig);
|
||||
|
||||
currentButton |= joystickController.GetButtons();
|
||||
|
||||
(leftJoystickDx, leftJoystickDy) = joystickController.GetLeftStick();
|
||||
(rightJoystickDx, rightJoystickDy) = joystickController.GetRightStick();
|
||||
|
||||
leftJoystick = new JoystickPosition
|
||||
{
|
||||
Dx = controllerConfig.LeftJoycon.InvertStickX ? -leftJoystickDx : leftJoystickDx,
|
||||
Dy = controllerConfig.LeftJoycon.InvertStickY ? -leftJoystickDy : leftJoystickDy
|
||||
};
|
||||
|
||||
rightJoystick = new JoystickPosition
|
||||
{
|
||||
Dx = controllerConfig.RightJoycon.InvertStickX ? -rightJoystickDx : rightJoystickDx,
|
||||
Dy = controllerConfig.RightJoycon.InvertStickY ? -rightJoystickDy : rightJoystickDy
|
||||
};
|
||||
}
|
||||
|
||||
currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
|
||||
|
||||
motionDevice.Poll(inputConfig, inputConfig.Slot);
|
||||
|
||||
SixAxisInput sixAxisInput = new SixAxisInput()
|
||||
{
|
||||
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||
Accelerometer = motionDevice.Accelerometer,
|
||||
Gyroscope = motionDevice.Gyroscope,
|
||||
Rotation = motionDevice.Rotation,
|
||||
Orientation = motionDevice.Orientation
|
||||
};
|
||||
|
||||
motionInputs.Add(sixAxisInput);
|
||||
|
||||
gamepadInputs.Add(new GamepadInput
|
||||
{
|
||||
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||
Buttons = currentButton,
|
||||
LStick = leftJoystick,
|
||||
RStick = rightJoystick
|
||||
});
|
||||
|
||||
if (inputConfig.ControllerType == Common.Configuration.Hid.ControllerType.JoyconPair)
|
||||
{
|
||||
if (!inputConfig.MirrorInput)
|
||||
{
|
||||
motionDevice.Poll(inputConfig, inputConfig.AltSlot);
|
||||
|
||||
sixAxisInput = new SixAxisInput()
|
||||
{
|
||||
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||
Accelerometer = motionDevice.Accelerometer,
|
||||
Gyroscope = motionDevice.Gyroscope,
|
||||
Rotation = motionDevice.Rotation,
|
||||
Orientation = motionDevice.Orientation
|
||||
};
|
||||
}
|
||||
|
||||
motionInputs.Add(sixAxisInput);
|
||||
}
|
||||
}
|
||||
|
||||
_device.Hid.Npads.Update(gamepadInputs);
|
||||
_device.Hid.Npads.UpdateSixAxis(motionInputs);
|
||||
_device.TamperMachine.UpdateInput(gamepadInputs);
|
||||
NpadManager.Update(_device.Hid, _device.TamperMachine);
|
||||
|
||||
if(_isFocused)
|
||||
{
|
||||
// Hotkeys
|
||||
HotkeyButtons currentHotkeyButtons = KeyboardController.GetHotkeyButtons(OpenTK.Input.Keyboard.GetState());
|
||||
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
||||
|
||||
if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
|
||||
!_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
|
||||
if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) &&
|
||||
!_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync))
|
||||
{
|
||||
_device.EnableDeviceVsync = !_device.EnableDeviceVsync;
|
||||
}
|
||||
|
||||
_prevHotkeyButtons = currentHotkeyButtons;
|
||||
_prevHotkeyState = currentHotkeyState;
|
||||
}
|
||||
|
||||
//Touchscreen
|
||||
|
@ -739,5 +606,24 @@ namespace Ryujinx.Ui
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum KeyboardHotkeyState
|
||||
{
|
||||
None,
|
||||
ToggleVSync
|
||||
}
|
||||
|
||||
private KeyboardHotkeyState GetHotkeyState()
|
||||
{
|
||||
KeyboardHotkeyState state = KeyboardHotkeyState.None;
|
||||
|
||||
if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync))
|
||||
{
|
||||
state |= KeyboardHotkeyState.ToggleVSync;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
118
Ryujinx/Ui/GLWidget.cs
Normal file
118
Ryujinx/Ui/GLWidget.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
using Gtk;
|
||||
using SPB.Graphics;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
using SPB.Platform.WGL;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
[ToolboxItem(true)]
|
||||
public class GLWidget : DrawingArea
|
||||
{
|
||||
private bool _initialized;
|
||||
|
||||
public event EventHandler Initialized;
|
||||
public event EventHandler ShuttingDown;
|
||||
|
||||
public OpenGLContextBase OpenGLContext { get; private set; }
|
||||
public NativeWindowBase NativeWindow { get; private set; }
|
||||
|
||||
public FramebufferFormat FramebufferFormat { get; }
|
||||
public int GLVersionMajor { get; }
|
||||
public int GLVersionMinor { get; }
|
||||
public OpenGLContextFlags ContextFlags { get; }
|
||||
|
||||
public bool DirectRendering { get; }
|
||||
public OpenGLContextBase SharedContext { get; }
|
||||
|
||||
public GLWidget(FramebufferFormat framebufferFormat, int major, int minor, OpenGLContextFlags flags = OpenGLContextFlags.Default, bool directRendering = true, OpenGLContextBase sharedContext = null)
|
||||
{
|
||||
FramebufferFormat = framebufferFormat;
|
||||
GLVersionMajor = major;
|
||||
GLVersionMinor = minor;
|
||||
ContextFlags = flags;
|
||||
DirectRendering = directRendering;
|
||||
SharedContext = sharedContext;
|
||||
}
|
||||
|
||||
protected override bool OnDrawn(Cairo.Context cr)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
Intialize();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private NativeWindowBase RetrieveNativeWindow()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle);
|
||||
|
||||
return new WGLWindow(new NativeHandle(windowHandle));
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle);
|
||||
IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle);
|
||||
|
||||
return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[DllImport("libgdk-3-0.dll")]
|
||||
private static extern IntPtr gdk_win32_window_get_handle(IntPtr d);
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
private static extern IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay);
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
private static extern IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow);
|
||||
|
||||
private void Intialize()
|
||||
{
|
||||
NativeWindow = RetrieveNativeWindow();
|
||||
|
||||
Window.EnsureNative();
|
||||
|
||||
OpenGLContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat, GLVersionMajor, GLVersionMinor, ContextFlags, DirectRendering, SharedContext);
|
||||
|
||||
OpenGLContext.Initialize(NativeWindow);
|
||||
OpenGLContext.MakeCurrent(NativeWindow);
|
||||
|
||||
_initialized = true;
|
||||
|
||||
Initialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
// Try to bind the OpenGL context before calling the shutdown event
|
||||
try
|
||||
{
|
||||
OpenGLContext?.MakeCurrent(NativeWindow);
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
ShuttingDown?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
// Unbind context and destroy everything
|
||||
try
|
||||
{
|
||||
OpenGLContext?.MakeCurrent(null);
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
OpenGLContext.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using Ryujinx.Common.Configuration.Hid;
|
||||
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
interface ButtonAssigner
|
||||
{
|
||||
void Init();
|
||||
|
||||
void ReadInput();
|
||||
|
||||
bool HasAnyButtonPressed();
|
||||
|
||||
bool ShouldCancel();
|
||||
|
||||
string GetPressedButton();
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
using OpenTK.Input;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
class JoystickButtonAssigner : ButtonAssigner
|
||||
{
|
||||
private int _index;
|
||||
|
||||
private double _triggerThreshold;
|
||||
|
||||
private JoystickState _currState;
|
||||
|
||||
private JoystickState _prevState;
|
||||
|
||||
private JoystickButtonDetector _detector;
|
||||
|
||||
public JoystickButtonAssigner(int index, double triggerThreshold)
|
||||
{
|
||||
_index = index;
|
||||
_triggerThreshold = triggerThreshold;
|
||||
_detector = new JoystickButtonDetector();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_currState = Joystick.GetState(_index);
|
||||
_prevState = _currState;
|
||||
}
|
||||
|
||||
public void ReadInput()
|
||||
{
|
||||
_prevState = _currState;
|
||||
_currState = Joystick.GetState(_index);
|
||||
|
||||
CollectButtonStats();
|
||||
}
|
||||
|
||||
public bool HasAnyButtonPressed()
|
||||
{
|
||||
return _detector.HasAnyButtonPressed();
|
||||
}
|
||||
|
||||
public bool ShouldCancel()
|
||||
{
|
||||
return Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsAnyKeyDown;
|
||||
}
|
||||
|
||||
public string GetPressedButton()
|
||||
{
|
||||
List<ControllerInputId> pressedButtons = _detector.GetPressedButtons();
|
||||
|
||||
// Reverse list so axis button take precedence when more than one button is recognized.
|
||||
pressedButtons.Reverse();
|
||||
|
||||
return pressedButtons.Count > 0 ? pressedButtons[0].ToString() : "";
|
||||
}
|
||||
|
||||
private void CollectButtonStats()
|
||||
{
|
||||
JoystickCapabilities capabilities = Joystick.GetCapabilities(_index);
|
||||
|
||||
ControllerInputId pressedButton;
|
||||
|
||||
// Buttons
|
||||
for (int i = 0; i != capabilities.ButtonCount; i++)
|
||||
{
|
||||
if (_currState.IsButtonDown(i) && _prevState.IsButtonUp(i))
|
||||
{
|
||||
Enum.TryParse($"Button{i}", out pressedButton);
|
||||
_detector.AddInput(pressedButton, 1);
|
||||
}
|
||||
|
||||
if (_currState.IsButtonUp(i) && _prevState.IsButtonDown(i))
|
||||
{
|
||||
Enum.TryParse($"Button{i}", out pressedButton);
|
||||
_detector.AddInput(pressedButton, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Axis
|
||||
for (int i = 0; i != capabilities.AxisCount; i++)
|
||||
{
|
||||
float axisValue = _currState.GetAxis(i);
|
||||
|
||||
Enum.TryParse($"Axis{i}", out pressedButton);
|
||||
_detector.AddInput(pressedButton, axisValue);
|
||||
}
|
||||
|
||||
// Hats
|
||||
for (int i = 0; i != capabilities.HatCount; i++)
|
||||
{
|
||||
string currPos = GetHatPosition(_currState.GetHat((JoystickHat)i));
|
||||
string prevPos = GetHatPosition(_prevState.GetHat((JoystickHat)i));
|
||||
|
||||
if (currPos == prevPos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currPos != "")
|
||||
{
|
||||
Enum.TryParse($"Hat{i}{currPos}", out pressedButton);
|
||||
_detector.AddInput(pressedButton, 1);
|
||||
}
|
||||
|
||||
if (prevPos != "")
|
||||
{
|
||||
Enum.TryParse($"Hat{i}{prevPos}", out pressedButton);
|
||||
_detector.AddInput(pressedButton, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHatPosition(JoystickHatState hatState)
|
||||
{
|
||||
if (hatState.IsUp) return "Up";
|
||||
if (hatState.IsDown) return "Down";
|
||||
if (hatState.IsLeft) return "Left";
|
||||
if (hatState.IsRight) return "Right";
|
||||
return "";
|
||||
}
|
||||
|
||||
private class JoystickButtonDetector
|
||||
{
|
||||
private Dictionary<ControllerInputId, InputSummary> _stats;
|
||||
|
||||
public JoystickButtonDetector()
|
||||
{
|
||||
_stats = new Dictionary<ControllerInputId, InputSummary>();
|
||||
}
|
||||
|
||||
public bool HasAnyButtonPressed()
|
||||
{
|
||||
foreach (var inputSummary in _stats.Values)
|
||||
{
|
||||
if (checkButtonPressed(inputSummary))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<ControllerInputId> GetPressedButtons()
|
||||
{
|
||||
List<ControllerInputId> pressedButtons = new List<ControllerInputId>();
|
||||
|
||||
foreach (var kvp in _stats)
|
||||
{
|
||||
if (!checkButtonPressed(kvp.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
pressedButtons.Add(kvp.Key);
|
||||
}
|
||||
|
||||
return pressedButtons;
|
||||
}
|
||||
|
||||
public void AddInput(ControllerInputId button, float value)
|
||||
{
|
||||
InputSummary inputSummary;
|
||||
|
||||
if (!_stats.TryGetValue(button, out inputSummary))
|
||||
{
|
||||
inputSummary = new InputSummary();
|
||||
_stats.Add(button, inputSummary);
|
||||
}
|
||||
|
||||
inputSummary.AddInput(value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
TextWriter writer = new StringWriter();
|
||||
|
||||
foreach (var kvp in _stats)
|
||||
{
|
||||
writer.WriteLine($"Button {kvp.Key} -> {kvp.Value}");
|
||||
}
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
private bool checkButtonPressed(InputSummary sequence)
|
||||
{
|
||||
float distance = Math.Abs(sequence.Min - sequence.Avg) + Math.Abs(sequence.Max - sequence.Avg);
|
||||
return distance > 1.5; // distance range [0, 2]
|
||||
}
|
||||
}
|
||||
|
||||
private class InputSummary
|
||||
{
|
||||
public float Min, Max, Sum, Avg;
|
||||
|
||||
public int NumSamples;
|
||||
|
||||
public InputSummary()
|
||||
{
|
||||
Min = float.MaxValue;
|
||||
Max = float.MinValue;
|
||||
Sum = 0;
|
||||
NumSamples = 0;
|
||||
Avg = 0;
|
||||
}
|
||||
|
||||
public void AddInput(float value)
|
||||
{
|
||||
Min = Math.Min(Min, value);
|
||||
Max = Math.Max(Max, value);
|
||||
Sum += value;
|
||||
NumSamples += 1;
|
||||
Avg = Sum / NumSamples;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using OpenTK.Input;
|
||||
using System;
|
||||
using Key = Ryujinx.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
class KeyboardKeyAssigner : ButtonAssigner
|
||||
{
|
||||
private int _index;
|
||||
|
||||
private KeyboardState _keyboardState;
|
||||
|
||||
public KeyboardKeyAssigner(int index)
|
||||
{
|
||||
_index = index;
|
||||
}
|
||||
|
||||
public void Init() { }
|
||||
|
||||
public void ReadInput()
|
||||
{
|
||||
_keyboardState = KeyboardController.GetKeyboardState(_index);
|
||||
}
|
||||
|
||||
public bool HasAnyButtonPressed()
|
||||
{
|
||||
return _keyboardState.IsAnyKeyDown;
|
||||
}
|
||||
|
||||
public bool ShouldCancel()
|
||||
{
|
||||
return Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Escape);
|
||||
}
|
||||
|
||||
public string GetPressedButton()
|
||||
{
|
||||
string keyPressed = "";
|
||||
|
||||
foreach (Key key in Enum.GetValues(typeof(Key)))
|
||||
{
|
||||
if (_keyboardState.IsKeyDown((OpenTK.Input.Key)key))
|
||||
{
|
||||
keyPressed = key.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !ShouldCancel() ? keyPressed : "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
|
||||
using ControllerConfig = Ryujinx.Common.Configuration.Hid.ControllerConfig;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class JoystickController
|
||||
{
|
||||
private readonly ControllerConfig _config;
|
||||
|
||||
public JoystickController(ControllerConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
private bool IsEnabled()
|
||||
{
|
||||
return Joystick.GetState(_config.Index).IsConnected;
|
||||
}
|
||||
|
||||
public ControllerKeys GetButtons()
|
||||
{
|
||||
// NOTE: This should be initialized AFTER GTK for compat reasons with OpenTK SDL2 backend and GTK on Linux.
|
||||
// BODY: Usage of Joystick.GetState must be defer to after GTK full initialization. Otherwise, GTK will segfault because SDL2 was already init *sighs*
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
JoystickState joystickState = Joystick.GetState(_config.Index);
|
||||
|
||||
ControllerKeys buttons = 0;
|
||||
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.DPadUp)) buttons |= ControllerKeys.DpadUp;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.DPadDown)) buttons |= ControllerKeys.DpadDown;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.DPadLeft)) buttons |= ControllerKeys.DpadLeft;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.DPadRight)) buttons |= ControllerKeys.DpadRight;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.StickButton)) buttons |= ControllerKeys.LStick;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.ButtonMinus)) buttons |= ControllerKeys.Minus;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.ButtonL)) buttons |= ControllerKeys.L;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.ButtonZl)) buttons |= ControllerKeys.Zl;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.ButtonSl)) buttons |= ControllerKeys.SlLeft;
|
||||
if (IsActivated(joystickState, _config.LeftJoycon.ButtonSr)) buttons |= ControllerKeys.SrLeft;
|
||||
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonA)) buttons |= ControllerKeys.A;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonB)) buttons |= ControllerKeys.B;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonX)) buttons |= ControllerKeys.X;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonY)) buttons |= ControllerKeys.Y;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.StickButton)) buttons |= ControllerKeys.RStick;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonPlus)) buttons |= ControllerKeys.Plus;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonR)) buttons |= ControllerKeys.R;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonZr)) buttons |= ControllerKeys.Zr;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonSl)) buttons |= ControllerKeys.SlRight;
|
||||
if (IsActivated(joystickState, _config.RightJoycon.ButtonSr)) buttons |= ControllerKeys.SrRight;
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
private bool IsActivated(JoystickState joystickState, ControllerInputId controllerInputId)
|
||||
{
|
||||
if (controllerInputId <= ControllerInputId.Button20)
|
||||
{
|
||||
return joystickState.IsButtonDown((int)controllerInputId);
|
||||
}
|
||||
else if (controllerInputId <= ControllerInputId.Axis5)
|
||||
{
|
||||
int axis = controllerInputId - ControllerInputId.Axis0;
|
||||
|
||||
return joystickState.GetAxis(axis) > _config.TriggerThreshold;
|
||||
}
|
||||
else if (controllerInputId <= ControllerInputId.Hat2Right)
|
||||
{
|
||||
int hat = (controllerInputId - ControllerInputId.Hat0Up) / 4;
|
||||
|
||||
int baseHatId = (int)ControllerInputId.Hat0Up + (hat * 4);
|
||||
|
||||
JoystickHatState hatState = joystickState.GetHat((JoystickHat)hat);
|
||||
|
||||
if (hatState.IsUp && ((int)controllerInputId % baseHatId == 0)) return true;
|
||||
if (hatState.IsDown && ((int)controllerInputId % baseHatId == 1)) return true;
|
||||
if (hatState.IsLeft && ((int)controllerInputId % baseHatId == 2)) return true;
|
||||
if (hatState.IsRight && ((int)controllerInputId % baseHatId == 3)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public (short, short) GetLeftStick()
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
return GetStick(_config.LeftJoycon.StickX, _config.LeftJoycon.StickY, _config.DeadzoneLeft);
|
||||
}
|
||||
|
||||
public (short, short) GetRightStick()
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
return GetStick(_config.RightJoycon.StickX, _config.RightJoycon.StickY, _config.DeadzoneRight);
|
||||
}
|
||||
|
||||
private (short, short) GetStick(ControllerInputId stickXInputId, ControllerInputId stickYInputId, float deadzone)
|
||||
{
|
||||
if (stickXInputId < ControllerInputId.Axis0 || stickXInputId > ControllerInputId.Axis5 ||
|
||||
stickYInputId < ControllerInputId.Axis0 || stickYInputId > ControllerInputId.Axis5)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
JoystickState jsState = Joystick.GetState(_config.Index);
|
||||
|
||||
int xAxis = stickXInputId - ControllerInputId.Axis0;
|
||||
int yAxis = stickYInputId - ControllerInputId.Axis0;
|
||||
|
||||
float xValue = jsState.GetAxis(xAxis);
|
||||
float yValue = -jsState.GetAxis(yAxis); // Invert Y-axis
|
||||
|
||||
return ApplyDeadzone(new Vector2(xValue, yValue), deadzone);
|
||||
}
|
||||
|
||||
private (short, short) ApplyDeadzone(Vector2 axis, float deadzone)
|
||||
{
|
||||
return (ClampAxis(MathF.Abs(axis.X) > deadzone ? axis.X : 0f),
|
||||
ClampAxis(MathF.Abs(axis.Y) > deadzone ? axis.Y : 0f));
|
||||
}
|
||||
|
||||
private static short ClampAxis(float value)
|
||||
{
|
||||
if (value <= -short.MaxValue)
|
||||
{
|
||||
return -short.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (short)(value * short.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,291 +0,0 @@
|
|||
using System;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
[Flags]
|
||||
public enum HotkeyButtons
|
||||
{
|
||||
ToggleVSync = 1 << 0,
|
||||
}
|
||||
|
||||
public class KeyboardController
|
||||
{
|
||||
private readonly KeyboardConfig _config;
|
||||
|
||||
public KeyboardController(KeyboardConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public static KeyboardState GetKeyboardState(int index)
|
||||
{
|
||||
if (index == KeyboardConfig.AllKeyboardsIndex || index < 0)
|
||||
{
|
||||
return Keyboard.GetState();
|
||||
}
|
||||
|
||||
return Keyboard.GetState(index - 1);
|
||||
}
|
||||
|
||||
public ControllerKeys GetButtons()
|
||||
{
|
||||
KeyboardState keyboard = GetKeyboardState(_config.Index);
|
||||
|
||||
ControllerKeys buttons = 0;
|
||||
|
||||
if (keyboard[(Key)_config.LeftJoycon.StickButton]) buttons |= ControllerKeys.LStick;
|
||||
if (keyboard[(Key)_config.LeftJoycon.DPadUp]) buttons |= ControllerKeys.DpadUp;
|
||||
if (keyboard[(Key)_config.LeftJoycon.DPadDown]) buttons |= ControllerKeys.DpadDown;
|
||||
if (keyboard[(Key)_config.LeftJoycon.DPadLeft]) buttons |= ControllerKeys.DpadLeft;
|
||||
if (keyboard[(Key)_config.LeftJoycon.DPadRight]) buttons |= ControllerKeys.DpadRight;
|
||||
if (keyboard[(Key)_config.LeftJoycon.ButtonMinus]) buttons |= ControllerKeys.Minus;
|
||||
if (keyboard[(Key)_config.LeftJoycon.ButtonL]) buttons |= ControllerKeys.L;
|
||||
if (keyboard[(Key)_config.LeftJoycon.ButtonZl]) buttons |= ControllerKeys.Zl;
|
||||
if (keyboard[(Key)_config.LeftJoycon.ButtonSl]) buttons |= ControllerKeys.SlLeft;
|
||||
if (keyboard[(Key)_config.LeftJoycon.ButtonSr]) buttons |= ControllerKeys.SrLeft;
|
||||
|
||||
if (keyboard[(Key)_config.RightJoycon.StickButton]) buttons |= ControllerKeys.RStick;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonA]) buttons |= ControllerKeys.A;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonB]) buttons |= ControllerKeys.B;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonX]) buttons |= ControllerKeys.X;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonY]) buttons |= ControllerKeys.Y;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonPlus]) buttons |= ControllerKeys.Plus;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonR]) buttons |= ControllerKeys.R;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonZr]) buttons |= ControllerKeys.Zr;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonSl]) buttons |= ControllerKeys.SlRight;
|
||||
if (keyboard[(Key)_config.RightJoycon.ButtonSr]) buttons |= ControllerKeys.SrRight;
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
public (short, short) GetLeftStick()
|
||||
{
|
||||
KeyboardState keyboard = GetKeyboardState(_config.Index);
|
||||
|
||||
short dx = 0;
|
||||
short dy = 0;
|
||||
|
||||
if (keyboard[(Key)_config.LeftJoycon.StickUp]) dy += 1;
|
||||
if (keyboard[(Key)_config.LeftJoycon.StickDown]) dy += -1;
|
||||
if (keyboard[(Key)_config.LeftJoycon.StickLeft]) dx += -1;
|
||||
if (keyboard[(Key)_config.LeftJoycon.StickRight]) dx += 1;
|
||||
|
||||
Vector2 stick = new Vector2(dx, dy);
|
||||
stick.NormalizeFast();
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public (short, short) GetRightStick()
|
||||
{
|
||||
KeyboardState keyboard = GetKeyboardState(_config.Index);
|
||||
|
||||
short dx = 0;
|
||||
short dy = 0;
|
||||
|
||||
if (keyboard[(Key)_config.RightJoycon.StickUp]) dy += 1;
|
||||
if (keyboard[(Key)_config.RightJoycon.StickDown]) dy += -1;
|
||||
if (keyboard[(Key)_config.RightJoycon.StickLeft]) dx += -1;
|
||||
if (keyboard[(Key)_config.RightJoycon.StickRight]) dx += 1;
|
||||
|
||||
Vector2 stick = new Vector2(dx, dy);
|
||||
stick.NormalizeFast();
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public static HotkeyButtons GetHotkeyButtons(KeyboardState keyboard)
|
||||
{
|
||||
HotkeyButtons buttons = 0;
|
||||
|
||||
if (keyboard[(Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync])
|
||||
{
|
||||
buttons |= HotkeyButtons.ToggleVSync;
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
class KeyMappingEntry
|
||||
{
|
||||
public Key TargetKey;
|
||||
public byte Target;
|
||||
}
|
||||
|
||||
private static readonly KeyMappingEntry[] KeyMapping = new KeyMappingEntry[]
|
||||
{
|
||||
new KeyMappingEntry { TargetKey = Key.A, Target = 0x4 },
|
||||
new KeyMappingEntry { TargetKey = Key.B, Target = 0x5 },
|
||||
new KeyMappingEntry { TargetKey = Key.C, Target = 0x6 },
|
||||
new KeyMappingEntry { TargetKey = Key.D, Target = 0x7 },
|
||||
new KeyMappingEntry { TargetKey = Key.E, Target = 0x8 },
|
||||
new KeyMappingEntry { TargetKey = Key.F, Target = 0x9 },
|
||||
new KeyMappingEntry { TargetKey = Key.G, Target = 0xA },
|
||||
new KeyMappingEntry { TargetKey = Key.H, Target = 0xB },
|
||||
new KeyMappingEntry { TargetKey = Key.I, Target = 0xC },
|
||||
new KeyMappingEntry { TargetKey = Key.J, Target = 0xD },
|
||||
new KeyMappingEntry { TargetKey = Key.K, Target = 0xE },
|
||||
new KeyMappingEntry { TargetKey = Key.L, Target = 0xF },
|
||||
new KeyMappingEntry { TargetKey = Key.M, Target = 0x10 },
|
||||
new KeyMappingEntry { TargetKey = Key.N, Target = 0x11 },
|
||||
new KeyMappingEntry { TargetKey = Key.O, Target = 0x12 },
|
||||
new KeyMappingEntry { TargetKey = Key.P, Target = 0x13 },
|
||||
new KeyMappingEntry { TargetKey = Key.Q, Target = 0x14 },
|
||||
new KeyMappingEntry { TargetKey = Key.R, Target = 0x15 },
|
||||
new KeyMappingEntry { TargetKey = Key.S, Target = 0x16 },
|
||||
new KeyMappingEntry { TargetKey = Key.T, Target = 0x17 },
|
||||
new KeyMappingEntry { TargetKey = Key.U, Target = 0x18 },
|
||||
new KeyMappingEntry { TargetKey = Key.V, Target = 0x19 },
|
||||
new KeyMappingEntry { TargetKey = Key.W, Target = 0x1A },
|
||||
new KeyMappingEntry { TargetKey = Key.X, Target = 0x1B },
|
||||
new KeyMappingEntry { TargetKey = Key.Y, Target = 0x1C },
|
||||
new KeyMappingEntry { TargetKey = Key.Z, Target = 0x1D },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.Number1, Target = 0x1E },
|
||||
new KeyMappingEntry { TargetKey = Key.Number2, Target = 0x1F },
|
||||
new KeyMappingEntry { TargetKey = Key.Number3, Target = 0x20 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number4, Target = 0x21 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number5, Target = 0x22 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number6, Target = 0x23 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number7, Target = 0x24 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number8, Target = 0x25 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number9, Target = 0x26 },
|
||||
new KeyMappingEntry { TargetKey = Key.Number0, Target = 0x27 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.Enter, Target = 0x28 },
|
||||
new KeyMappingEntry { TargetKey = Key.Escape, Target = 0x29 },
|
||||
new KeyMappingEntry { TargetKey = Key.BackSpace, Target = 0x2A },
|
||||
new KeyMappingEntry { TargetKey = Key.Tab, Target = 0x2B },
|
||||
new KeyMappingEntry { TargetKey = Key.Space, Target = 0x2C },
|
||||
new KeyMappingEntry { TargetKey = Key.Minus, Target = 0x2D },
|
||||
new KeyMappingEntry { TargetKey = Key.Plus, Target = 0x2E },
|
||||
new KeyMappingEntry { TargetKey = Key.BracketLeft, Target = 0x2F },
|
||||
new KeyMappingEntry { TargetKey = Key.BracketRight, Target = 0x30 },
|
||||
new KeyMappingEntry { TargetKey = Key.BackSlash, Target = 0x31 },
|
||||
new KeyMappingEntry { TargetKey = Key.Tilde, Target = 0x32 },
|
||||
new KeyMappingEntry { TargetKey = Key.Semicolon, Target = 0x33 },
|
||||
new KeyMappingEntry { TargetKey = Key.Quote, Target = 0x34 },
|
||||
new KeyMappingEntry { TargetKey = Key.Grave, Target = 0x35 },
|
||||
new KeyMappingEntry { TargetKey = Key.Comma, Target = 0x36 },
|
||||
new KeyMappingEntry { TargetKey = Key.Period, Target = 0x37 },
|
||||
new KeyMappingEntry { TargetKey = Key.Slash, Target = 0x38 },
|
||||
new KeyMappingEntry { TargetKey = Key.CapsLock, Target = 0x39 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.F1, Target = 0x3a },
|
||||
new KeyMappingEntry { TargetKey = Key.F2, Target = 0x3b },
|
||||
new KeyMappingEntry { TargetKey = Key.F3, Target = 0x3c },
|
||||
new KeyMappingEntry { TargetKey = Key.F4, Target = 0x3d },
|
||||
new KeyMappingEntry { TargetKey = Key.F5, Target = 0x3e },
|
||||
new KeyMappingEntry { TargetKey = Key.F6, Target = 0x3f },
|
||||
new KeyMappingEntry { TargetKey = Key.F7, Target = 0x40 },
|
||||
new KeyMappingEntry { TargetKey = Key.F8, Target = 0x41 },
|
||||
new KeyMappingEntry { TargetKey = Key.F9, Target = 0x42 },
|
||||
new KeyMappingEntry { TargetKey = Key.F10, Target = 0x43 },
|
||||
new KeyMappingEntry { TargetKey = Key.F11, Target = 0x44 },
|
||||
new KeyMappingEntry { TargetKey = Key.F12, Target = 0x45 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.PrintScreen, Target = 0x46 },
|
||||
new KeyMappingEntry { TargetKey = Key.ScrollLock, Target = 0x47 },
|
||||
new KeyMappingEntry { TargetKey = Key.Pause, Target = 0x48 },
|
||||
new KeyMappingEntry { TargetKey = Key.Insert, Target = 0x49 },
|
||||
new KeyMappingEntry { TargetKey = Key.Home, Target = 0x4A },
|
||||
new KeyMappingEntry { TargetKey = Key.PageUp, Target = 0x4B },
|
||||
new KeyMappingEntry { TargetKey = Key.Delete, Target = 0x4C },
|
||||
new KeyMappingEntry { TargetKey = Key.End, Target = 0x4D },
|
||||
new KeyMappingEntry { TargetKey = Key.PageDown, Target = 0x4E },
|
||||
new KeyMappingEntry { TargetKey = Key.Right, Target = 0x4F },
|
||||
new KeyMappingEntry { TargetKey = Key.Left, Target = 0x50 },
|
||||
new KeyMappingEntry { TargetKey = Key.Down, Target = 0x51 },
|
||||
new KeyMappingEntry { TargetKey = Key.Up, Target = 0x52 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.NumLock, Target = 0x53 },
|
||||
new KeyMappingEntry { TargetKey = Key.KeypadDivide, Target = 0x54 },
|
||||
new KeyMappingEntry { TargetKey = Key.KeypadMultiply, Target = 0x55 },
|
||||
new KeyMappingEntry { TargetKey = Key.KeypadMinus, Target = 0x56 },
|
||||
new KeyMappingEntry { TargetKey = Key.KeypadPlus, Target = 0x57 },
|
||||
new KeyMappingEntry { TargetKey = Key.KeypadEnter, Target = 0x58 },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad1, Target = 0x59 },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad2, Target = 0x5A },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad3, Target = 0x5B },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad4, Target = 0x5C },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad5, Target = 0x5D },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad6, Target = 0x5E },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad7, Target = 0x5F },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad8, Target = 0x60 },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad9, Target = 0x61 },
|
||||
new KeyMappingEntry { TargetKey = Key.Keypad0, Target = 0x62 },
|
||||
new KeyMappingEntry { TargetKey = Key.KeypadPeriod, Target = 0x63 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.NonUSBackSlash, Target = 0x64 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.F13, Target = 0x68 },
|
||||
new KeyMappingEntry { TargetKey = Key.F14, Target = 0x69 },
|
||||
new KeyMappingEntry { TargetKey = Key.F15, Target = 0x6A },
|
||||
new KeyMappingEntry { TargetKey = Key.F16, Target = 0x6B },
|
||||
new KeyMappingEntry { TargetKey = Key.F17, Target = 0x6C },
|
||||
new KeyMappingEntry { TargetKey = Key.F18, Target = 0x6D },
|
||||
new KeyMappingEntry { TargetKey = Key.F19, Target = 0x6E },
|
||||
new KeyMappingEntry { TargetKey = Key.F20, Target = 0x6F },
|
||||
new KeyMappingEntry { TargetKey = Key.F21, Target = 0x70 },
|
||||
new KeyMappingEntry { TargetKey = Key.F22, Target = 0x71 },
|
||||
new KeyMappingEntry { TargetKey = Key.F23, Target = 0x72 },
|
||||
new KeyMappingEntry { TargetKey = Key.F24, Target = 0x73 },
|
||||
|
||||
new KeyMappingEntry { TargetKey = Key.ControlLeft, Target = 0xE0 },
|
||||
new KeyMappingEntry { TargetKey = Key.ShiftLeft, Target = 0xE1 },
|
||||
new KeyMappingEntry { TargetKey = Key.AltLeft, Target = 0xE2 },
|
||||
new KeyMappingEntry { TargetKey = Key.WinLeft, Target = 0xE3 },
|
||||
new KeyMappingEntry { TargetKey = Key.ControlRight, Target = 0xE4 },
|
||||
new KeyMappingEntry { TargetKey = Key.ShiftRight, Target = 0xE5 },
|
||||
new KeyMappingEntry { TargetKey = Key.AltRight, Target = 0xE6 },
|
||||
new KeyMappingEntry { TargetKey = Key.WinRight, Target = 0xE7 },
|
||||
};
|
||||
|
||||
private static readonly KeyMappingEntry[] KeyModifierMapping = new KeyMappingEntry[]
|
||||
{
|
||||
new KeyMappingEntry { TargetKey = Key.ControlLeft, Target = 0 },
|
||||
new KeyMappingEntry { TargetKey = Key.ShiftLeft, Target = 1 },
|
||||
new KeyMappingEntry { TargetKey = Key.AltLeft, Target = 2 },
|
||||
new KeyMappingEntry { TargetKey = Key.WinLeft, Target = 3 },
|
||||
new KeyMappingEntry { TargetKey = Key.ControlRight, Target = 4 },
|
||||
new KeyMappingEntry { TargetKey = Key.ShiftRight, Target = 5 },
|
||||
new KeyMappingEntry { TargetKey = Key.AltRight, Target = 6 },
|
||||
new KeyMappingEntry { TargetKey = Key.WinRight, Target = 7 },
|
||||
new KeyMappingEntry { TargetKey = Key.CapsLock, Target = 8 },
|
||||
new KeyMappingEntry { TargetKey = Key.ScrollLock, Target = 9 },
|
||||
new KeyMappingEntry { TargetKey = Key.NumLock, Target = 10 },
|
||||
};
|
||||
|
||||
public KeyboardInput GetKeysDown()
|
||||
{
|
||||
KeyboardState keyboard = GetKeyboardState(_config.Index);
|
||||
|
||||
KeyboardInput hidKeyboard = new KeyboardInput
|
||||
{
|
||||
Modifier = 0,
|
||||
Keys = new int[0x8]
|
||||
};
|
||||
|
||||
foreach (KeyMappingEntry entry in KeyMapping)
|
||||
{
|
||||
int value = keyboard[entry.TargetKey] ? 1 : 0;
|
||||
|
||||
hidKeyboard.Keys[entry.Target / 0x20] |= (value << (entry.Target % 0x20));
|
||||
}
|
||||
|
||||
foreach (KeyMappingEntry entry in KeyModifierMapping)
|
||||
{
|
||||
int value = keyboard[entry.TargetKey] ? 1 : 0;
|
||||
|
||||
hidKeyboard.Modifier |= value << entry.Target;
|
||||
}
|
||||
|
||||
return hidKeyboard;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@ using Ryujinx.HLE.FileSystem;
|
|||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Input.GTK3;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.Input.SDL2;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.App;
|
||||
using Ryujinx.Ui.Applet;
|
||||
|
@ -65,6 +68,7 @@ namespace Ryujinx.Ui
|
|||
private bool _lastScannedAmiiboShowAll = false;
|
||||
|
||||
public GlRenderer GlRendererWidget;
|
||||
public InputManager InputManager;
|
||||
|
||||
#pragma warning disable CS0169, CS0649, IDE0044
|
||||
|
||||
|
@ -223,6 +227,8 @@ namespace Ryujinx.Ui
|
|||
};
|
||||
|
||||
Task.Run(RefreshFirmwareLabel);
|
||||
|
||||
InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver());
|
||||
}
|
||||
|
||||
private void WindowStateEvent_Changed(object o, WindowStateEventArgs args)
|
||||
|
@ -295,6 +301,11 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroyed()
|
||||
{
|
||||
InputManager.Dispose();
|
||||
}
|
||||
|
||||
private void InitializeSwitchInstance()
|
||||
{
|
||||
_virtualFileSystem.Reload();
|
||||
|
@ -636,7 +647,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
DisplaySleep.Prevent();
|
||||
|
||||
GlRendererWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||
GlRendererWidget = new GlRenderer(_emulationContext, InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
|
|
20
Ryujinx/Ui/OpenToolkitBindingsContext.cs
Normal file
20
Ryujinx/Ui/OpenToolkitBindingsContext.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using SPB.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class OpenToolkitBindingsContext : OpenTK.IBindingsContext
|
||||
{
|
||||
private IBindingsContext _bindingContext;
|
||||
|
||||
public OpenToolkitBindingsContext(IBindingsContext bindingsContext)
|
||||
{
|
||||
_bindingContext = bindingsContext;
|
||||
}
|
||||
|
||||
public IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
return _bindingContext.GetProcAddress(procName);
|
||||
}
|
||||
}
|
||||
}
|
49
Ryujinx/Ui/SPBOpenGLContext.cs
Normal file
49
Ryujinx/Ui/SPBOpenGLContext.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using SPB.Graphics;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using SPB.Platform;
|
||||
using SPB.Windowing;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
class SPBOpenGLContext : IOpenGLContext
|
||||
{
|
||||
private OpenGLContextBase _context;
|
||||
private NativeWindowBase _window;
|
||||
|
||||
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
|
||||
{
|
||||
_context = context;
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Dispose();
|
||||
_window.Dispose();
|
||||
}
|
||||
|
||||
public void MakeCurrent()
|
||||
{
|
||||
_context.MakeCurrent(_window);
|
||||
}
|
||||
|
||||
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
||||
{
|
||||
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
||||
NativeWindowBase window = PlatformHelper.CreateWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
||||
|
||||
context.Initialize(window);
|
||||
context.MakeCurrent(window);
|
||||
|
||||
GL.LoadBindings(new OpenToolkitBindingsContext(context));
|
||||
|
||||
context.MakeCurrent(null);
|
||||
|
||||
return new SPBOpenGLContext(context, window);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -48,8 +48,8 @@
|
|||
<property name="title" translatable="yes">Ryujinx - Controller Settings</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="default_width">1150</property>
|
||||
<property name="default_height">690</property>
|
||||
<property name="default_width">1200</property>
|
||||
<property name="default_height">720</property>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
@ -113,21 +113,6 @@
|
|||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="_refreshInputDevicesButton">
|
||||
<property name="label" translatable="yes">Refresh</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_left">5</property>
|
||||
<signal name="toggled" handler="RefreshInputDevicesButton_Pressed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -682,7 +667,7 @@
|
|||
<property name="width_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">LStick Lt/Rt</property>
|
||||
<property name="label" translatable="yes">LStick</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -691,20 +676,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="width_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">LStick Up/Dn</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="_lStickX">
|
||||
<object class="GtkToggleButton" id="_lStick">
|
||||
<property name="label" translatable="yes"> </property>
|
||||
<property name="width_request">65</property>
|
||||
<property name="visible">True</property>
|
||||
|
@ -716,22 +688,9 @@
|
|||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="_lStickY">
|
||||
<property name="label" translatable="yes"> </property>
|
||||
<property name="width_request">65</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_invertLStickX">
|
||||
<property name="label" translatable="yes">Invert</property>
|
||||
<property name="label" translatable="yes">Invert Stick X</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
|
@ -744,7 +703,7 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_invertLStickY">
|
||||
<property name="label" translatable="yes">Invert</property>
|
||||
<property name="label" translatable="yes">Invert Stick Y</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
|
@ -1501,7 +1460,7 @@
|
|||
<property name="width_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">RStick Lt/Rt</property>
|
||||
<property name="label" translatable="yes">RStick</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -1510,20 +1469,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="width_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">RStick Up/Dn</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="_rStickX">
|
||||
<object class="GtkToggleButton" id="_rStick">
|
||||
<property name="label" translatable="yes"> </property>
|
||||
<property name="width_request">65</property>
|
||||
<property name="visible">True</property>
|
||||
|
@ -1535,22 +1481,9 @@
|
|||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="_rStickY">
|
||||
<property name="label" translatable="yes"> </property>
|
||||
<property name="width_request">65</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_invertRStickX">
|
||||
<property name="label" translatable="yes">Invert</property>
|
||||
<property name="label" translatable="yes">Invert Stick X</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
|
@ -1563,7 +1496,7 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_invertRStickY">
|
||||
<property name="label" translatable="yes">Invert</property>
|
||||
<property name="label" translatable="yes">Invert Stick Y</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
|
@ -1640,7 +1573,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="MotionBox">
|
||||
<object class="GtkBox" id="_motionBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
|
@ -1679,7 +1612,21 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkCheckButton" id="_enableCemuHook">
|
||||
<property name="label" translatable="yes">Use CemuHook compatible motion</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="_motionControllerSlot">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">10</property>
|
||||
|
@ -1718,7 +1665,7 @@
|
|||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -1761,11 +1708,11 @@
|
|||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="_altBox">
|
||||
<object class="GtkBox" id="_motionAltBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
|
@ -1829,11 +1776,11 @@
|
|||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkBox" id="_dsuServerHostBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">30</property>
|
||||
|
@ -1866,11 +1813,11 @@
|
|||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">5</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkBox" id="_dsuServerPortBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">30</property>
|
||||
|
@ -1903,7 +1850,7 @@
|
|||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">6</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -1916,7 +1863,7 @@
|
|||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -1930,7 +1877,7 @@
|
|||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -578,7 +578,7 @@ namespace Ryujinx.Ui.Windows
|
|||
{
|
||||
((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
|
||||
|
||||
ControllerWindow controllerWindow = new ControllerWindow(playerIndex);
|
||||
ControllerWindow controllerWindow = new ControllerWindow(_parent, playerIndex);
|
||||
|
||||
controllerWindow.SetSizeRequest((int)(controllerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(controllerWindow.DefaultHeight * Program.WindowScaleFactor));
|
||||
controllerWindow.Show();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue