Compare commits

..

155 Commits

Author SHA1 Message Date
german77
51df96b7c0 settings: Add debug setting to enable all controllers 2021-11-27 20:24:37 -06:00
Fernando S
564f105277 Merge pull request #7255 from german77/kraken
Project Kraken: Input rewrite
2021-11-27 11:52:08 +01:00
german77
182cd9004f config: Remove vibration configuration 2021-11-26 19:31:04 -06:00
german77
a4a0638bc8 applet/controller: Enable configuring mode while the applet is open 2021-11-26 18:55:28 -06:00
bunnei
157985f556 Merge pull request #7431 from liushuyu/fix-linux-decoding
video_core/codecs: fix multiple decoding issues on Linux
2021-11-26 15:38:40 -08:00
Narr the Reg
639402850a input_common: Fully implement UDP controllers 2021-11-26 15:46:36 -06:00
bunnei
50a19b07d1 Merge pull request #7440 from lioncash/fmt
CMakeLists: Update fmt to 8.0.1
2021-11-25 20:58:49 -08:00
Lioncash
5303161aa1 CMakeLists: Update fmt to 8.0.1
Ensures that we're using the latest version of fmt.
2021-11-25 23:08:45 -05:00
bunnei
06ad463ec8 Merge pull request #7330 from MightyCreak/simplify-theme-selection
Replace "Light" theme by "Default"
2021-11-25 01:52:48 -08:00
Narr the Reg
23bf2e3bb6 service/hid: Finish converting LIFO objects and address some nits 2021-11-24 20:30:29 -06:00
german77
e64ee99f00 yuzu: Fix TAS from rebase 2021-11-24 20:30:29 -06:00
german77
746c85b560 input_common: Move button names to the frontend 2021-11-24 20:30:28 -06:00
german77
c4760489a0 input_common: Fix SDL controller with inverted axis 2021-11-24 20:30:28 -06:00
german77
922aa9410a bootmanager: Use cross-platform keyboard input 2021-11-24 20:30:28 -06:00
german77
42949738f2 kraken: Address comments from review
Fix compiler bug
2021-11-24 20:30:28 -06:00
german77
f4e5f89e6f core/hid: Improve accuary of mouse implementation 2021-11-24 20:30:28 -06:00
german77
654d76e79e core/hid: Fully implement native mouse 2021-11-24 20:30:28 -06:00
german77
bca299e8e0 input_common: Allow keyboard to be backwards compatible 2021-11-24 20:30:28 -06:00
german77
b673857d7d core/hid: Improve accuracy of the keyboard implementation 2021-11-24 20:30:28 -06:00
german77
7fcfe24a3e core/hid: Fix keyboard alignment 2021-11-24 20:30:28 -06:00
german77
71f9b90dd9 core/hid: Remove usage of native types, fix a couple of errors with motion 2021-11-24 20:30:28 -06:00
german77
a17550be98 settings: Remove includes of core.h 2021-11-24 20:30:27 -06:00
german77
e7eee36d52 service/hid: Remove includes of core.h and settings.h 2021-11-24 20:30:27 -06:00
Levi Behunin
690013b342 UI nits
Set top margin to 6 on Right Stick, LeftStick, Face Buttons, D-Pad.
Change property on Input Device QComboBox from minimumSize to minimumContentsLength.
2021-11-24 20:30:27 -06:00
german77
b21fcd9527 service/hid: Add support for new controllers 2021-11-24 20:30:27 -06:00
german77
d14e74132c settings: Fix controller preview not displaying the correct controller 2021-11-24 20:30:27 -06:00
german77
5d0f3540c4 core/hid: Rename NpadType to NpadStyleIndex 2021-11-24 20:30:27 -06:00
german77
84c58666a4 config: Cleanup and documentation 2021-11-24 20:30:27 -06:00
german77
c35af8d1c0 input_common: Fix motion from 3 axis 2021-11-24 20:30:27 -06:00
german77
157e0b85fd core/hid: Prevent Emulated controller from flapping with multiple inputs devices 2021-11-24 20:30:27 -06:00
german77
136eb9c4c2 core/hid: Fully emulate motion from button 2021-11-24 20:30:27 -06:00
german77
77fa4d4bf6 second commit lion review 2021-11-24 20:30:26 -06:00
german77
730f078302 settings: Fix Debug controller type options 2021-11-24 20:30:26 -06:00
german77
2b1b0c2a30 kraken: Address comments from review
start lion review
2021-11-24 20:30:26 -06:00
german77
61d9eb9f69 input_common: Revert deleted TAS functions 2021-11-24 20:30:26 -06:00
german77
5f69fdbfcc core/hid: Explain better what a temporary value does 2021-11-24 20:30:26 -06:00
german77
d8e3f2b10b input_common: Fix GC adapter initialization
Fix GC controller
2021-11-24 20:30:26 -06:00
german77
2d3a63b289 core/hid: Update structs to 13.1.0 2021-11-24 20:30:26 -06:00
german77
c085e54316 core/hid: Add TAS input 2021-11-24 20:30:26 -06:00
german77
1d71d4b874 input_common: Fix UDP uuid 2021-11-24 20:30:26 -06:00
german77
7348e205d9 input_common: Add multiple vibration curves 2021-11-24 20:30:26 -06:00
german77
064ddacf49 core/hid: Rework battery mappings 2021-11-24 20:30:26 -06:00
german77
c6c32daf40 input_common: Add manual update options to input devices 2021-11-24 20:30:26 -06:00
german77
f01dac3bf9 service/hid: Fix memory allocated incorrectly 2021-11-24 20:30:25 -06:00
german77
464c4d26ac settings: Fix mouse and keyboard mappings 2021-11-24 20:30:25 -06:00
german77
cc651c7c99 web_applet: Replace HIDButton with NpadButton 2021-11-24 20:30:25 -06:00
german77
b564f024f0 Morph review first wave 2021-11-24 20:30:25 -06:00
german77
e2e5f1beaf service/hid: Match shared memory closer to HW 2021-11-24 20:30:25 -06:00
german77
21819da8cd yuzu: Fix loading input profiles 2021-11-24 20:30:25 -06:00
german77
b5e72de753 kraken: Address comments from review
review fixes
2021-11-24 20:30:25 -06:00
german77
95cf66b655 service/hid: Use ring buffer for gestures 2021-11-24 20:30:25 -06:00
german77
85052b8662 service/hid: Fix gesture input 2021-11-24 20:30:25 -06:00
german77
af55dd1935 configuration: Migrate controller settings to emulated controller 2021-11-24 20:30:25 -06:00
german77
c3ff0a8ac0 core/hid: Fix rumble too strong at 1% 2021-11-24 20:30:24 -06:00
german77
601ac43495 core/hid: Only signal when needed 2021-11-24 20:30:24 -06:00
german77
4d308fd0b4 hid: Fix controller connection/disconnection 2021-11-24 20:30:24 -06:00
german77
72e5920240 core/hid: Documment some files 2021-11-24 20:30:24 -06:00
german77
e0da5c1bbc kraken: Fix errors from rebase and format files 2021-11-24 20:30:24 -06:00
german77
06a5ef5874 core/hid: Add output devices 2021-11-24 20:30:24 -06:00
german77
e14ae06391 core: Update input interpreter 2021-11-24 20:30:24 -06:00
german77
1b82d5bb4f yuzu: Update overlay applet 2021-11-24 20:30:24 -06:00
german77
510c7d2953 core/frontend: Update applets 2021-11-24 20:30:24 -06:00
german77
dd62a0187d core: Remove frontend/input 2021-11-24 20:30:24 -06:00
german77
c87ad2d0d6 service/hid: Rewrite npad to use ring lifo and the emulated controller 2021-11-24 20:30:24 -06:00
german77
a2ad5762e6 service/hid: Update console sixaxis to the emulated console 2021-11-24 20:30:24 -06:00
german77
800a66d25a service/hid: Update mouse and keyboard to use ring lifo and the emulated device 2021-11-24 20:30:24 -06:00
german77
afe2d667d9 service/hid: Update touch and gestures to use ring lifo and the emulated console 2021-11-24 20:30:24 -06:00
german77
072559dede service/hid: Update debug pad, xpad, stubbed and controller base to use ring lifo and the emulated controller 2021-11-24 20:30:23 -06:00
german77
dbe0301102 service/hid: Use remove duplicated code, update names 2021-11-24 20:30:23 -06:00
german77
db08721dcc service/hid: Create ring LIFO 2021-11-24 20:30:23 -06:00
german77
8fff6d6c67 Qt_applets: Use new input 2021-11-24 20:30:23 -06:00
german77
6e2c84042d settings: Cleanup settings 2021-11-24 20:30:23 -06:00
german77
456397ed39 debugger/controller: Remove TAS 2021-11-24 20:30:23 -06:00
german77
173a6b1e57 core/emu_window: Remove touch input 2021-11-24 20:30:23 -06:00
german77
c405a19b73 yuzu: Update frontend 2021-11-24 20:30:23 -06:00
german77
967cca10ff core: Register HID 2021-11-24 20:30:23 -06:00
german77
c3f54ff232 core/hid: Add emulated controllers 2021-11-24 20:30:23 -06:00
german77
14b949a0da yuzu_cmd: Use new input 2021-11-24 20:30:23 -06:00
german77
737d305f63 yuzu: Use new input on main and bootmanager 2021-11-24 20:30:23 -06:00
german77
29ae42f3e2 input_common: Rewrite main and add the new drivers 2021-11-24 20:30:23 -06:00
german77
6d108f0dcb input_common: Remove obsolete files 2021-11-24 20:30:23 -06:00
german77
59b995a9e5 input_common: Rewrite SDL 2021-11-24 20:30:23 -06:00
german77
10241886dd input_common: Rewrite udp client 2021-11-24 20:30:22 -06:00
german77
dc3ab9e110 input_common: Rewrite tas input 2021-11-24 20:30:22 -06:00
german77
395e9a449d input_common: Rewrite gc_adapter 2021-11-24 20:30:22 -06:00
german77
fa8e23b842 input_common: Rewrite touch 2021-11-24 20:30:22 -06:00
german77
00834b84dd input_common: Rewrite mouse 2021-11-24 20:30:22 -06:00
german77
5a785ed794 input_common: Rewrite keyboard 2021-11-24 20:30:22 -06:00
german77
4c6f2c2547 input_common: Move touch and analog from button. Move udp protocol 2021-11-24 20:30:22 -06:00
german77
854c933716 input_common: Create input poller and mapping 2021-11-24 20:30:22 -06:00
german77
ea7b1fbc67 input_common: Create input_engine 2021-11-24 20:30:22 -06:00
german77
449576df93 core/hid: Move motion_input, create input converter and hid_types 2021-11-24 20:30:22 -06:00
german77
bf71d18af9 core/hid: Move input_interpreter to hid 2021-11-24 20:30:22 -06:00
german77
ad5142ac2c common: Rewrite and move core/frontend/input.h to common 2021-11-24 20:30:21 -06:00
Adam Heinermann
f078d3d212 Refactor menu states and shortcuts in GMainWindow. (#7419)
Refactor menu states and shortcuts in GMainWindow.

- Removed "Start", since it was always disabled unless it was "Continue"
which has now been moved to "Pause".
- Allow hotkeys to be used while in fullscreen.
- Removed the load amiibo hotkey.
2021-11-24 21:27:25 -05:00
liushuyu
60928cf8cd video_core/codec: address comments 2021-11-24 18:06:38 -07:00
liushuyu
72aa418b0b video_core/codecs: fix multiple decoding issues on Linux ...
* when someone installed Intel video drivers on an AMD system, the
  decoder will select the Intel VA-API decoding driver and yuzu will
  crash due to incorrect driver selection; the fix will check if the
  currently about-to-use driver is loaded in the kernel
* when using NVIDIA driver on Linux with a ffmpeg that does not have
  CUDA capability enabled, the decoder will crash; the fix simply
  making the decoder prefers the VDPAU driver over CUDA on Linux
2021-11-24 17:23:57 -07:00
bunnei
cd6cf0422d Merge pull request #7404 from Kewlan/per-game-framerate-cap
configure_general: Allow framerate cap to be used in custom game configs
2021-11-24 02:36:30 -08:00
bunnei
daecbd3a7f Merge pull request #7394 from Morph1984/svc-SetMemoryPermission
kernel: svc: Implement SetProcessMemoryPermission
2021-11-22 14:06:09 -08:00
Mai M
ee76b546d4 Merge pull request #7406 from heinermann/tas_menu
Added TAS controls to the menu under Tools
2021-11-21 23:17:07 -05:00
Mai M
be238fb26a Merge pull request #7407 from jam1garner/ic-ivau-instruction
Add InstructionCacheOperationRaised handler for 'ic ivau' instruction
2021-11-21 23:16:36 -05:00
jam1garner
4d9c9e567e arm: dynarmic: Cleanup icache op handling 2021-11-21 22:44:13 -05:00
jam1garner
c8a67a725d arm: dynarmic: Implement icache op handling for 'ic iallu' instruction 2021-11-21 21:18:56 -05:00
jam1garner
84eb3e7d02 arm: dynarmic: Implement icache op handling for 'ic ivau' instruction 2021-11-21 21:10:14 -05:00
Adam Heinermann
097de2febc const fixes 2021-11-21 18:07:37 -08:00
Adam Heinermann
b7a938e817 Apply clang format 2021-11-21 18:02:08 -08:00
Adam Heinermann
f90d980837 Added TAS controls to the menu under Tools 2021-11-21 17:28:47 -08:00
Kewlan
8e3371a5c5 configure_general: Allow framerate cap to be used in custom game configs 2021-11-21 16:57:00 +01:00
bunnei
d20f91da11 Merge pull request #7395 from Morph1984/resolve-comments
general: Resolve comments in PR #7368
2021-11-21 02:42:24 -08:00
bunnei
5082712b4e Merge pull request #7389 from ameerj/screenshot-1x
Fix screenshot dimensions when at 1x scale
2021-11-21 02:31:32 -08:00
bunnei
ba5210675a Merge pull request #7359 from heinermann/kthread_crash
Fix crash on exit due to static scoped dummy threads
2021-11-20 23:59:58 -08:00
bunnei
fc34749778 Merge pull request #7393 from Morph1984/pm-ams-get-pid
service: pm: Implement AtmosphereGetProcessId
2021-11-20 22:08:25 -08:00
Morph
5cf93c1346 kernel: svc: Move all IsValid functions to an anonymous namespace 2021-11-20 22:49:13 -05:00
Morph
2726d705f8 kernel: svc: Implement SetProcessMemoryPermission
- Used by Skyline modding framework
2021-11-20 22:18:56 -05:00
Morph
281437c811 kernel: KPageTable: Rename SetCodeMemoryPermission to SetProcessMemoryPermission 2021-11-20 22:18:56 -05:00
Morph
a41c6dafea vk_texture_cache: Mark VkBufferUsageFlags as static constexpr 2021-11-20 21:49:37 -05:00
Morph
095bc88428 vk_blit_image: Consolidate CreatePipelineTargetEx functions 2021-11-20 21:18:37 -05:00
Morph
9173f07a51 service: pm: Implement AtmosphereGetProcessId
- Used by Skyline modding framework
2021-11-20 20:56:29 -05:00
Morph
3dc38d185b service: pm: Add all relevant result codes 2021-11-20 20:56:28 -05:00
bunnei
ea6fa044f3 Merge pull request #7368 from FernandoS27/vulkan-conv
Fix ART Blit detection regression and add D24S8 <-> RGBA8 conv to Vulkan
2021-11-20 16:51:13 -08:00
Morph
40cd0bb97b service: pm: Rename title id to program id 2021-11-20 19:39:26 -05:00
ameerj
fe1f06c856 Fix screenshot dimensions when at 1x scale
This was regressed by ART.
Prior to ART, the screenshots were saved at the title's framebuffer resolution. A misunderstanding of the existing logic led to screenshot dimensions becoming dependent on the host render window size.

This changes the behavior to match how it was prior to ART at 1x, with screenshots now always being the title's framebuffer dimensions scaled by the resolution scaling factor.
2021-11-20 17:50:24 -05:00
Fernando Sahmkow
da2fe81905 TextureCache: Refactor and fix linux compiling. 2021-11-20 14:46:19 +01:00
Fernando Sahmkow
4ca6e9a9e2 TextureCache: Assure full conversions on depth/stencil write shaders. 2021-11-20 06:17:01 +01:00
Fernando Sahmkow
0857f82913 TextureCache: Implement buffer copies on Vulkan. 2021-11-20 06:15:29 +01:00
bunnei
c3e1ffc44b Merge pull request #7294 from vonchenplus/fix_image_update_error_when_width_too_small
Fix image update/download error when width too small
2021-11-19 15:56:27 -08:00
Fernando Sahmkow
e02cff2f69 TextureCache: Add R16G16 to D24S8 converter. 2021-11-20 00:02:12 +01:00
Fernando Sahmkow
1d5e6a51d7 TextureCache: Add B10G11R11 to D24S8 converter. 2021-11-19 23:22:44 +01:00
Fernando Sahmkow
6f896d1fae TextureCache: Further fixes on resolve algorithm. 2021-11-19 23:02:04 +01:00
bunnei
b44fbf6cdd Merge pull request #7369 from Morph1984/amd-fsr-statusbar
main: Shorten AMD FSR status bar text
2021-11-19 01:20:27 -08:00
bunnei
d2e009f355 Merge pull request #7273 from fpdotmonkey/submodule-checkout-command-on-new-line
Move the cmake submodule checkout command to a new line
2021-11-19 01:19:50 -08:00
bunnei
a69813948f Merge pull request #7342 from goldenx86/patch-3
Replace keys error pop up
2021-11-19 01:17:18 -08:00
bunnei
c45af76ea0 Merge pull request #7357 from Morph1984/s8_uint
video_core: Implement S8_UINT format
2021-11-19 01:16:49 -08:00
Fernando Sahmkow
b805c7bf05 TextureCache: Implement additional D24S8 convertions. 2021-11-19 06:27:44 +01:00
Fernando Sahmkow
0ff228405f TextureCache: force same image format when resolving an image. 2021-11-19 05:46:57 +01:00
Morph
bc5ed1aa1b main: Fix default AA name
By default, no AA is applied, not FXAA
2021-11-18 23:22:32 -05:00
Morph
a237fb5f75 configure_graphics_ui: AMD's -> AMD
AMD officially markets FSR as AMD FidelityFX™️ Super Resolution
2021-11-18 23:17:16 -05:00
Morph
c76163b611 main: Shorten AMD FSR status bar text
AMD'S FIDELITYFX SR -> FSR
2021-11-18 23:17:16 -05:00
Fernando Sahmkow
b130f648d7 TextureCache: Fix regression caused by ART and improve blit detection algorithm to be smarter. 2021-11-19 03:17:54 +01:00
Fernando Sahmkow
2ec7fcecb7 Vulkan: implement D24S8 <-> RGBA8 convertions. 2021-11-19 03:17:02 +01:00
bunnei
0bc46fedd6 Merge pull request #7349 from ameerj/ogl-convert-image
gl_texture_cache: Implement pixel format conversions for copies
2021-11-18 11:30:47 -08:00
bunnei
a100f5d5d4 Merge pull request #7353 from v1993/no-more-epilepsy
Prevent window flickering when holding Esc
2021-11-18 11:26:28 -08:00
Morph
dc61b7045b renderer_vulkan: Implement S8_UINT stencil format
It should be noted that on Windows, only nvidia gpus support this format natively as of this commit.
2021-11-18 00:05:51 -05:00
Ameer J
66fed9ecbd Merge pull request #7348 from Morph1984/ci-disable-submodule-fetch
ci: Don't fetch submodules when fetching PRs
2021-11-17 23:58:34 -05:00
bunnei
2054013edb Merge pull request #7355 from german77/hotkey_spam
hotkeys: Don't allow hotkeys to spam
2021-11-17 18:16:17 -08:00
Adam Heinermann
d8a783a368 Fix crash on exit due to static scoped dummy threads 2021-11-17 15:29:25 -08:00
Morph
6dd6dc046c renderer_opengl: Implement S8_UINT stencil format 2021-11-17 15:05:07 -05:00
Morph
2348eb41f3 video_core: Add S8_UINT stencil format 2021-11-17 15:04:38 -05:00
german77
311324e231 hotkeys: Don't allow hotkeys to spam 2021-11-17 10:42:20 -06:00
Valeri
f7e0a37753 Prevent window flickering when holding Esc
Reported on discord by Levlight. Don't try to exit fullscreen if it's already off.
2021-11-17 18:07:47 +03:00
Feng Chen
894cc9d876 Fix image update/download error when width too small 2021-11-17 12:21:17 +08:00
Morph
16aa49d138 ci: Don't fetch submodules when fetching PRs 2021-11-16 22:26:54 -05:00
Matías Locatti
ffb79afd29 Replace keys error pop up
Fight me.
2021-11-16 03:53:28 -03:00
Romain Failliot
2e5866147e Replace "Light" theme by "Default"
This reflects the current behavior: Light = System default. If your
system is set to dark theme, then Light = Dark, which is a bit confusing
for the end user.

In this PR, I propose to change "Light" with "Default". This way, the
user has "Default" and "Default Colorful", which will apply the system
theme. Now that the Flatpak respects the system theme, I think this
makes much more sense.

I also simplified the theme update. Before the code was branching
between the default theme and the others, but I think we can have
something simpler by forcing the default theme if no theme is defined in
the settings, or if the selected theme doesn't exist. And if there's an
error, tell the theme name in the error message.
2021-11-14 08:52:55 -05:00
Fletcher Porter
2b3d66fe69 Move the cmake submodule checkout command to a new line
Presently, if you forget to initialize the git submodules before
running cmake, there'll be a helpful message that reminds you to do so.
However, on narrow terminals (e.g. 80 wide) there's a word wrap that
includes a new line in the middle of the git command, precluding easy
copy-paste.  This moves the entire git command to its own line to avoid
such tragedies.

Before:

```
CMake Error at CMakeLists.txt:59 (message):
  Git submodule externals/inih/inih not found.  Please run: git submodule
  update --init --recursive
```

After:

```
CMake Error at CMakeLists.txt:59 (message):
  Git submodule externals/inih/inih not found.  Please run:

  git submodule update --init --recursive
```
2021-11-02 00:01:39 -07:00
197 changed files with 15295 additions and 11692 deletions

View File

@@ -25,7 +25,7 @@ def check_individual(repo_id, pr_id):
def merge_pr(pn, ref):
print("Matched PR# %s" % pn)
print(subprocess.check_output(["git", "fetch", "https://%sdev.azure.com/%s/_git/%s" % (user, org, repo), ref, "-f"]))
print(subprocess.check_output(["git", "fetch", "https://%sdev.azure.com/%s/_git/%s" % (user, org, repo), ref, "-f", "--no-recurse-submodules"]))
print(subprocess.check_output(["git", "merge", "--squash", 'origin/' + ref.replace('refs/heads/','')]))
print(subprocess.check_output(["git", "commit", "-m\"Merge %s PR %s\"" % (tagline, pn)]))

View File

@@ -25,7 +25,7 @@ def do_page(page):
if (check_individual(pr["labels"])):
pn = pr["number"]
print("Matched PR# %s" % pn)
print(subprocess.check_output(["git", "fetch", "https://github.com/yuzu-emu/yuzu.git", "pull/%s/head:pr-%s" % (pn, pn), "-f"]))
print(subprocess.check_output(["git", "fetch", "https://github.com/yuzu-emu/yuzu.git", "pull/%s/head:pr-%s" % (pn, pn), "-f", "--no-recurse-submodules"]))
print(subprocess.check_output(["git", "merge", "--squash", "pr-%s" % pn]))
print(subprocess.check_output(["git", "commit", "-m\"Merge %s PR %s\"" % (tagline, pn)]))

View File

@@ -57,7 +57,7 @@ function(check_submodules_present)
string(REGEX REPLACE "path *= *" "" module ${module})
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
message(FATAL_ERROR "Git submodule ${module} not found. "
"Please run: git submodule update --init --recursive")
"Please run: \ngit submodule update --init --recursive")
endif()
endforeach()
endfunction()
@@ -167,7 +167,7 @@ macro(yuzu_find_packages)
set(REQUIRED_LIBS
# Cmake Pkg Prefix Version Conan Pkg
"Catch2 2.13.7 catch2/2.13.7"
"fmt 8.0 fmt/8.0.0"
"fmt 8.0.1 fmt/8.0.1"
"lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.8 nlohmann_json/3.8.0"
"ZLIB 1.2 zlib/1.2.11"

View File

@@ -73,6 +73,7 @@ add_library(common STATIC
hex_util.h
host_memory.cpp
host_memory.h
input.h
intrusive_red_black_tree.h
literals.h
logging/backend.cpp

View File

@@ -7,6 +7,7 @@
#include <bit>
#include <climits>
#include <cstddef>
#include <type_traits>
#include "common/common_types.h"
@@ -44,4 +45,10 @@ template <typename T>
return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] T NextPow2(T value) {
return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U)));
}
} // namespace Common

372
src/common/input.h Normal file
View File

@@ -0,0 +1,372 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/uuid.h"
namespace Common::Input {
// Type of data that is expected to recieve or send
enum class InputType {
None,
Battery,
Button,
Stick,
Analog,
Trigger,
Motion,
Touch,
Color,
Vibration,
Nfc,
Ir,
};
// Internal battery charge level
enum class BatteryLevel : u32 {
None,
Empty,
Critical,
Low,
Medium,
Full,
Charging,
};
enum class PollingMode {
// Constant polling of buttons, analogs and motion data
Active,
// Only update on button change, digital analogs
Pasive,
// Enable near field communication polling
NFC,
// Enable infrared camera polling
IR,
};
// Vibration reply from the controller
enum class VibrationError {
None,
NotSupported,
Disabled,
Unknown,
};
// Polling mode reply from the controller
enum class PollingError {
None,
NotSupported,
Unknown,
};
// Hint for amplification curve to be used
enum class VibrationAmplificationType {
Linear,
Exponential,
};
// Analog properties for calibration
struct AnalogProperties {
// Anything below this value will be detected as zero
float deadzone{};
// Anyting above this values will be detected as one
float range{1.0f};
// Minimum value to be detected as active
float threshold{0.5f};
// Drift correction applied to the raw data
float offset{};
// Invert direction of the sensor data
bool inverted{};
};
// Single analog sensor data
struct AnalogStatus {
float value{};
float raw_value{};
AnalogProperties properties{};
};
// Button data
struct ButtonStatus {
Common::UUID uuid{};
bool value{};
bool inverted{};
bool toggle{};
bool locked{};
};
// Internal battery data
using BatteryStatus = BatteryLevel;
// Analog and digital joystick data
struct StickStatus {
Common::UUID uuid{};
AnalogStatus x{};
AnalogStatus y{};
bool left{};
bool right{};
bool up{};
bool down{};
};
// Analog and digital trigger data
struct TriggerStatus {
Common::UUID uuid{};
AnalogStatus analog{};
ButtonStatus pressed{};
};
// 3D vector representing motion input
struct MotionSensor {
AnalogStatus x{};
AnalogStatus y{};
AnalogStatus z{};
};
// Motion data used to calculate controller orientation
struct MotionStatus {
// Gyroscope vector measurement in radians/s.
MotionSensor gyro{};
// Acceleration vector measurement in G force
MotionSensor accel{};
// Time since last measurement in microseconds
u64 delta_timestamp{};
// Request to update after reading the value
bool force_update{};
};
// Data of a single point on a touch screen
struct TouchStatus {
ButtonStatus pressed{};
AnalogStatus x{};
AnalogStatus y{};
int id{};
};
// Physical controller color in RGB format
struct BodyColorStatus {
u32 body{};
u32 buttons{};
};
// HD rumble data
struct VibrationStatus {
f32 low_amplitude{};
f32 low_frequency{};
f32 high_amplitude{};
f32 high_frequency{};
VibrationAmplificationType type;
};
// Physical controller LED pattern
struct LedStatus {
bool led_1{};
bool led_2{};
bool led_3{};
bool led_4{};
};
// List of buttons to be passed to Qt that can be translated
enum class ButtonNames {
Undefined,
Invalid,
// This will display the engine name instead of the button name
Engine,
// This will display the button by value instead of the button name
Value,
ButtonLeft,
ButtonRight,
ButtonDown,
ButtonUp,
TriggerZ,
TriggerR,
TriggerL,
ButtonA,
ButtonB,
ButtonX,
ButtonY,
ButtonStart,
// DS4 button names
L1,
L2,
L3,
R1,
R2,
R3,
Circle,
Cross,
Square,
Triangle,
Share,
Options,
};
// Callback data consisting of an input type and the equivalent data status
struct CallbackStatus {
InputType type{InputType::None};
ButtonStatus button_status{};
StickStatus stick_status{};
AnalogStatus analog_status{};
TriggerStatus trigger_status{};
MotionStatus motion_status{};
TouchStatus touch_status{};
BodyColorStatus color_status{};
BatteryStatus battery_status{};
VibrationStatus vibration_status{};
};
// Triggered once every input change
struct InputCallback {
std::function<void(CallbackStatus)> on_change;
};
/// An abstract class template for an input device (a button, an analog input, etc.).
class InputDevice {
public:
virtual ~InputDevice() = default;
// Request input device to update if necessary
virtual void SoftUpdate() {
return;
}
// Force input device to update data regardless of the current state
virtual void ForceUpdate() {
return;
}
// Sets the function to be triggered when input changes
void SetCallback(InputCallback callback_) {
callback = std::move(callback_);
}
// Triggers the function set in the callback
void TriggerOnChange(CallbackStatus status) {
if (callback.on_change) {
callback.on_change(status);
}
}
private:
InputCallback callback;
};
/// An abstract class template for an output device (rumble, LED pattern, polling mode).
class OutputDevice {
public:
virtual ~OutputDevice() = default;
virtual void SetLED([[maybe_unused]] LedStatus led_status) {
return;
}
virtual VibrationError SetVibration([[maybe_unused]] VibrationStatus vibration_status) {
return VibrationError::NotSupported;
}
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
return PollingError::NotSupported;
}
};
/// An abstract class template for a factory that can create input devices.
template <typename InputDeviceType>
class Factory {
public:
virtual ~Factory() = default;
virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
};
namespace Impl {
template <typename InputDeviceType>
using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
template <typename InputDeviceType>
struct FactoryList {
static FactoryListType<InputDeviceType> list;
};
template <typename InputDeviceType>
FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
} // namespace Impl
/**
* Registers an input device factory.
* @tparam InputDeviceType the type of input devices the factory can create
* @param name the name of the factory. Will be used to match the "engine" parameter when creating
* a device
* @param factory the factory object to register
*/
template <typename InputDeviceType>
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
auto pair = std::make_pair(name, std::move(factory));
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
LOG_ERROR(Input, "Factory '{}' already registered", name);
}
}
/**
* Unregisters an input device factory.
* @tparam InputDeviceType the type of input devices the factory can create
* @param name the name of the factory to unregister
*/
template <typename InputDeviceType>
void UnregisterFactory(const std::string& name) {
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
LOG_ERROR(Input, "Factory '{}' not registered", name);
}
}
/**
* Create an input device from given paramters.
* @tparam InputDeviceType the type of input devices to create
* @param params a serialized ParamPackage string that contains all parameters for creating the
* device
*/
template <typename InputDeviceType>
std::unique_ptr<InputDeviceType> CreateDeviceFromString(const std::string& params) {
const Common::ParamPackage package(params);
const std::string engine = package.Get("engine", "null");
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
const auto pair = factory_list.find(engine);
if (pair == factory_list.end()) {
if (engine != "null") {
LOG_ERROR(Input, "Unknown engine name: {}", engine);
}
return std::make_unique<InputDeviceType>();
}
return pair->second->Create(package);
}
/**
* Create an input device from given paramters.
* @tparam InputDeviceType the type of input devices to create
* @param A ParamPackage that contains all parameters for creating the device
*/
template <typename InputDeviceType>
std::unique_ptr<InputDeviceType> CreateDevice(const Common::ParamPackage package) {
const std::string engine = package.Get("engine", "null");
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
const auto pair = factory_list.find(engine);
if (pair == factory_list.end()) {
if (engine != "null") {
LOG_ERROR(Input, "Unknown engine name: {}", engine);
}
return std::make_unique<InputDeviceType>();
}
return pair->second->Create(package);
}
} // namespace Common::Input

View File

@@ -183,6 +183,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.max_anisotropy.SetGlobal(true);
values.use_speed_limit.SetGlobal(true);
values.speed_limit.SetGlobal(true);
values.fps_cap.SetGlobal(true);
values.use_disk_shader_cache.SetGlobal(true);
values.gpu_accuracy.SetGlobal(true);
values.use_asynchronous_gpu_emulation.SetGlobal(true);

View File

@@ -6,7 +6,6 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <map>
#include <optional>
#include <string>
@@ -525,7 +524,7 @@ struct Values {
Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
Setting<bool> accelerate_astc{true, "accelerate_astc"};
Setting<bool> use_vsync{true, "use_vsync"};
BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
RangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
ShaderBackend::SPIRV, "shader_backend"};
@@ -560,25 +559,19 @@ struct Values {
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
Setting<bool> motion_enabled{true, "motion_enabled"};
BasicSetting<std::string> motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01",
"motion_device"};
BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
BasicSetting<bool> enable_udp_controller{false, "enable_udp_controller"};
BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
BasicSetting<bool> tas_enable{false, "tas_enable"};
BasicSetting<bool> tas_loop{false, "tas_loop"};
BasicSetting<bool> tas_swap_controllers{true, "tas_swap_controllers"};
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
std::string mouse_device;
MouseButtonsRaw mouse_buttons;
BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
BasicSetting<bool> keyboard_enabled{false, "keyboard_enabled"};
KeyboardKeysRaw keyboard_keys;
KeyboardModsRaw keyboard_mods;
BasicSetting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
ButtonsRaw debug_pad_buttons;
@@ -586,14 +579,11 @@ struct Values {
TouchscreenInput touchscreen;
BasicSetting<bool> use_touch_from_button{false, "use_touch_from_button"};
BasicSetting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850",
"touch_device"};
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
std::vector<TouchFromButtonMap> touch_from_button_maps;
std::atomic_bool is_device_reload_pending{true};
// Data Storage
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
@@ -614,6 +604,7 @@ struct Values {
BasicSetting<bool> extended_logging{false, "extended_logging"};
BasicSetting<bool> use_debug_asserts{false, "use_debug_asserts"};
BasicSetting<bool> use_auto_stub{false, "use_auto_stub"};
BasicSetting<bool> enable_all_controllers{false, "enable_all_controllers"};
// Miscellaneous
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};

View File

@@ -62,11 +62,22 @@ enum Values : int {
constexpr int STICK_HID_BEGIN = LStick;
constexpr int STICK_HID_END = NumAnalogs;
constexpr int NUM_STICKS_HID = NumAnalogs;
extern const std::array<const char*, NumAnalogs> mapping;
} // namespace NativeAnalog
namespace NativeTrigger {
enum Values : int {
LTrigger,
RTrigger,
NumTriggers,
};
constexpr int TRIGGER_HID_BEGIN = LTrigger;
constexpr int TRIGGER_HID_END = NumTriggers;
} // namespace NativeTrigger
namespace NativeVibration {
enum Values : int {
LeftVibrationDevice,
@@ -115,10 +126,20 @@ constexpr int NUM_MOUSE_HID = NumMouseButtons;
extern const std::array<const char*, NumMouseButtons> mapping;
} // namespace NativeMouseButton
namespace NativeMouseWheel {
enum Values {
X,
Y,
NumMouseWheels,
};
extern const std::array<const char*, NumMouseWheels> mapping;
} // namespace NativeMouseWheel
namespace NativeKeyboard {
enum Keys {
None,
Error,
A = 4,
B,
@@ -156,22 +177,22 @@ enum Keys {
N8,
N9,
N0,
Enter,
Return,
Escape,
Backspace,
Tab,
Space,
Minus,
Equal,
LeftBrace,
RightBrace,
Backslash,
Plus,
OpenBracket,
CloseBracket,
Pipe,
Tilde,
Semicolon,
Apostrophe,
Grave,
Quote,
Backquote,
Comma,
Dot,
Period,
Slash,
CapsLockKey,
@@ -188,7 +209,7 @@ enum Keys {
F11,
F12,
SystemRequest,
PrintScreen,
ScrollLockKey,
Pause,
Insert,
@@ -257,8 +278,18 @@ enum Keys {
ScrollLockActive,
KPComma,
KPLeftParenthesis,
KPRightParenthesis,
Ro = 0x87,
KatakanaHiragana,
Yen,
Henkan,
Muhenkan,
NumPadCommaPc98,
HangulEnglish = 0x90,
Hanja,
KatakanaKey,
HiraganaKey,
ZenkakuHankaku,
LeftControlKey = 0xE0,
LeftShiftKey,
@@ -307,6 +338,8 @@ enum Modifiers {
CapsLock,
ScrollLock,
NumLock,
Katakana,
Hiragana,
NumKeyboardMods,
};
@@ -324,11 +357,6 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
@@ -342,6 +370,11 @@ enum class ControllerType {
RightJoycon,
Handheld,
GameCube,
Pokeball,
NES,
SNES,
N64,
SegaGenesis,
};
struct PlayerInput {
@@ -349,7 +382,6 @@ struct PlayerInput {
ControllerType controller_type;
ButtonsRaw buttons;
AnalogsRaw analogs;
VibrationsRaw vibrations;
MotionsRaw motions;
bool vibration_enabled;

View File

@@ -132,11 +132,23 @@ add_library(core STATIC
frontend/emu_window.h
frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h
frontend/input_interpreter.cpp
frontend/input_interpreter.h
frontend/input.h
hardware_interrupt_manager.cpp
hardware_interrupt_manager.h
hid/emulated_console.cpp
hid/emulated_console.h
hid/emulated_controller.cpp
hid/emulated_controller.h
hid/emulated_devices.cpp
hid/emulated_devices.h
hid/hid_core.cpp
hid/hid_core.h
hid/hid_types.h
hid/input_converter.cpp
hid/input_converter.h
hid/input_interpreter.cpp
hid/input_interpreter.h
hid/motion_input.cpp
hid/motion_input.h
hle/api_version.h
hle/ipc.h
hle/ipc_helpers.h
@@ -402,6 +414,7 @@ add_library(core STATIC
hle/service/hid/hid.h
hle/service/hid/irs.cpp
hle/service/hid/irs.h
hle/service/hid/ring_lifo.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
hle/service/hid/errors.h

View File

@@ -86,6 +86,26 @@ public:
num_instructions, MemoryReadCode(pc));
}
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
VAddr value) override {
switch (op) {
case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
static constexpr u64 ICACHE_LINE_SIZE = 64;
const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
break;
}
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
parent.ClearInstructionCache();
break;
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
default:
LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
break;
}
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
switch (exception) {
case Dynarmic::A64::Exception::WaitForInterrupt:

View File

@@ -27,6 +27,7 @@
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/hardware_interrupt_manager.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
@@ -126,7 +127,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, memory{system},
: kernel{system}, fs_controller{system}, memory{system}, hid_core{},
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
SystemResultStatus Run() {
@@ -391,6 +392,7 @@ struct System::Impl {
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
std::unique_ptr<Core::DeviceMemory> device_memory;
Core::Memory::Memory memory;
Core::HID::HIDCore hid_core;
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
bool exit_lock = false;
@@ -615,6 +617,14 @@ const Kernel::KernelCore& System::Kernel() const {
return impl->kernel;
}
HID::HIDCore& System::HIDCore() {
return impl->hid_core;
}
const HID::HIDCore& System::HIDCore() const {
return impl->hid_core;
}
Timing::CoreTiming& System::CoreTiming() {
return impl->core_timing;
}
@@ -825,8 +835,6 @@ void System::ApplySettings() {
if (IsPoweredOn()) {
Renderer().RefreshBaseSettings();
}
Service::HID::ReloadInputDevices();
}
} // namespace Core

View File

@@ -89,6 +89,10 @@ namespace Core::Hardware {
class InterruptManager;
}
namespace Core::HID {
class HIDCore;
}
namespace Core {
class ARM_Interface;
@@ -285,6 +289,12 @@ public:
/// Provides a constant reference to the kernel instance.
[[nodiscard]] const Kernel::KernelCore& Kernel() const;
/// Gets a mutable reference to the HID interface.
[[nodiscard]] HID::HIDCore& HIDCore();
/// Gets an immutable reference to the HID interface.
[[nodiscard]] const HID::HIDCore& HIDCore() const;
/// Provides a reference to the internal PerfStats instance.
[[nodiscard]] Core::PerfStats& GetPerfStats();

View File

@@ -5,16 +5,15 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/frontend/applets/controller.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
namespace Core::Frontend {
ControllerApplet::~ControllerApplet() = default;
DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_)
: service_manager{service_manager_} {}
DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_) : hid_core{hid_core_} {}
DefaultControllerApplet::~DefaultControllerApplet() = default;
@@ -22,24 +21,20 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
const ControllerParameters& parameters) const {
LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
auto& npad =
service_manager.GetService<Service::HID::Hid>("hid")
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
auto& players = Settings::values.players.GetValue();
const std::size_t min_supported_players =
parameters.enable_single_mode ? 1 : parameters.min_players;
// Disconnect Handheld first.
npad.DisconnectNpadAtIndex(8);
auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
handheld->Disconnect();
// Deduce the best configuration based on the input parameters.
for (std::size_t index = 0; index < players.size() - 2; ++index) {
for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) {
auto* controller = hid_core.GetEmulatedControllerByIndex(index);
// First, disconnect all controllers regardless of the value of keep_controllers_connected.
// This makes it easy to connect the desired controllers.
npad.DisconnectNpadAtIndex(index);
controller->Disconnect();
// Only connect the minimum number of required players.
if (index >= min_supported_players) {
@@ -49,27 +44,27 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
// Connect controllers based on the following priority list from highest to lowest priority:
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
if (parameters.allow_pro_controller) {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
controller->Connect();
} else if (parameters.allow_dual_joycons) {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);
controller->Connect();
} else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
// Assign left joycons to even player indices and right joycons to odd player indices.
// We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
// a right Joycon for Player 2 in 2 Player Assist mode.
if (index % 2 == 0) {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconLeft);
controller->Connect();
} else {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconRight);
controller->Connect();
}
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
!Settings::values.use_docked_mode.GetValue()) {
// We should *never* reach here under any normal circumstances.
npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
index);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
controller->Connect();
} else {
UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
}

View File

@@ -8,8 +8,8 @@
#include "common/common_types.h"
namespace Service::SM {
class ServiceManager;
namespace Core::HID {
class HIDCore;
}
namespace Core::Frontend {
@@ -44,14 +44,14 @@ public:
class DefaultControllerApplet final : public ControllerApplet {
public:
explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_);
explicit DefaultControllerApplet(HID::HIDCore& hid_core_);
~DefaultControllerApplet() override;
void ReconfigureControllers(std::function<void()> callback,
const ControllerParameters& parameters) const override;
private:
Service::SM::ServiceManager& service_manager;
HID::HIDCore& hid_core;
};
} // namespace Core::Frontend

View File

@@ -3,66 +3,31 @@
// Refer to the license.txt file included.
#include <mutex>
#include "common/settings.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
namespace Core::Frontend {
GraphicsContext::~GraphicsContext() = default;
class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
public std::enable_shared_from_this<TouchState> {
public:
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
return std::make_unique<Device>(shared_from_this());
}
std::mutex mutex;
Input::TouchStatus status;
private:
class Device : public Input::TouchDevice {
public:
explicit Device(std::weak_ptr<TouchState>&& touch_state_) : touch_state(touch_state_) {}
Input::TouchStatus GetStatus() const override {
if (auto state = touch_state.lock()) {
std::lock_guard guard{state->mutex};
return state->status;
}
return {};
}
private:
std::weak_ptr<TouchState> touch_state;
};
};
EmuWindow::EmuWindow() {
// TODO: Find a better place to set this.
config.min_client_area_size =
std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
active_config = config;
touch_state = std::make_shared<TouchState>();
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
}
EmuWindow::~EmuWindow() {
Input::UnregisterFactory<Input::TouchDevice>("emu_window");
}
EmuWindow::~EmuWindow() {}
/**
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
* @param framebuffer_x Framebuffer x-coordinate to check
* @param framebuffer_y Framebuffer y-coordinate to check
* @return True if the coordinates are within the touchpad, otherwise false
*/
static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, u32 framebuffer_x,
u32 framebuffer_y) {
return (framebuffer_y >= layout.screen.top && framebuffer_y < layout.screen.bottom &&
framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right);
std::pair<f32, f32> EmuWindow::MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const {
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
const float x =
static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
const float y =
static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
return std::make_pair(x, y);
}
std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
@@ -75,49 +40,6 @@ std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
return std::make_pair(new_x, new_y);
}
void EmuWindow::TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
return;
}
if (id >= touch_state->status.size()) {
return;
}
std::lock_guard guard{touch_state->mutex};
const float x =
static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
const float y =
static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
touch_state->status[id] = std::make_tuple(x, y, true);
}
void EmuWindow::TouchReleased(size_t id) {
if (id >= touch_state->status.size()) {
return;
}
std::lock_guard guard{touch_state->mutex};
touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false);
}
void EmuWindow::TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
if (id >= touch_state->status.size()) {
return;
}
if (!std::get<2>(touch_state->status[id])) {
return;
}
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
}
TouchPressed(framebuffer_x, framebuffer_y, id);
}
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height) {
NotifyFramebufferLayoutChanged(Layout::DefaultFrameLayout(width, height));
}

View File

@@ -112,28 +112,6 @@ public:
/// Returns if window is shown (not minimized)
virtual bool IsShown() const = 0;
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed
* @param framebuffer_y Framebuffer y-coordinate that was pressed
* @param id Touch event ID
*/
void TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id);
/**
* Signal that a touch released event has occurred (e.g. mouse click released)
* @param id Touch event ID
*/
void TouchReleased(size_t id);
/**
* Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window)
* @param framebuffer_x Framebuffer x-coordinate
* @param framebuffer_y Framebuffer y-coordinate
* @param id Touch event ID
*/
void TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id);
/**
* Returns currently active configuration.
* @note Accesses to the returned object need not be consistent because it may be modified in
@@ -212,6 +190,11 @@ protected:
client_area_height = size.second;
}
/**
* Converts a screen postion into the equivalent touchscreen position.
*/
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
WindowSystemInfo window_info;
private:
@@ -237,9 +220,6 @@ private:
WindowConfig config; ///< Internal configuration (changes pending for being applied in
/// ProcessConfigurationChanges)
WindowConfig active_config; ///< Internal active configuration
class TouchState;
std::shared_ptr<TouchState> touch_state;
};
} // namespace Core::Frontend

View File

@@ -25,7 +25,12 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
ASSERT(height > 0);
// The drawing code needs at least somewhat valid values for both screens
// so just calculate them both even if the other isn't showing.
FramebufferLayout res{width, height, false, {}};
FramebufferLayout res{
.width = width,
.height = height,
.screen = {},
.is_srgb = false,
};
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
const float emulation_aspect_ratio = EmulationAspectRatio(

View File

@@ -35,17 +35,8 @@ enum class AspectRatio {
struct FramebufferLayout {
u32 width{ScreenUndocked::Width};
u32 height{ScreenUndocked::Height};
bool is_srgb{};
Common::Rectangle<u32> screen;
/**
* Returns the ration of pixel size of the screen, compared to the native size of the undocked
* Switch screen.
*/
float GetScalingRatio() const {
return static_cast<float>(screen.GetWidth()) / ScreenUndocked::Width;
}
bool is_srgb{};
};
/**

View File

@@ -1,217 +0,0 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
namespace Input {
enum class AnalogDirection : u8 {
RIGHT,
LEFT,
UP,
DOWN,
};
struct AnalogProperties {
float deadzone;
float range;
float threshold;
};
template <typename StatusType>
struct InputCallback {
std::function<void(StatusType)> on_change;
};
/// An abstract class template for an input device (a button, an analog input, etc.).
template <typename StatusType>
class InputDevice {
public:
virtual ~InputDevice() = default;
virtual StatusType GetStatus() const {
return {};
}
virtual StatusType GetRawStatus() const {
return GetStatus();
}
virtual AnalogProperties GetAnalogProperties() const {
return {};
}
virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const {
return {};
}
virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low,
[[maybe_unused]] f32 amp_high,
[[maybe_unused]] f32 freq_high) const {
return {};
}
void SetCallback(InputCallback<StatusType> callback_) {
callback = std::move(callback_);
}
void TriggerOnChange() {
if (callback.on_change) {
callback.on_change(GetStatus());
}
}
private:
InputCallback<StatusType> callback;
};
/// An abstract class template for a factory that can create input devices.
template <typename InputDeviceType>
class Factory {
public:
virtual ~Factory() = default;
virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
};
namespace Impl {
template <typename InputDeviceType>
using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
template <typename InputDeviceType>
struct FactoryList {
static FactoryListType<InputDeviceType> list;
};
template <typename InputDeviceType>
FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
} // namespace Impl
/**
* Registers an input device factory.
* @tparam InputDeviceType the type of input devices the factory can create
* @param name the name of the factory. Will be used to match the "engine" parameter when creating
* a device
* @param factory the factory object to register
*/
template <typename InputDeviceType>
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
auto pair = std::make_pair(name, std::move(factory));
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
LOG_ERROR(Input, "Factory '{}' already registered", name);
}
}
/**
* Unregisters an input device factory.
* @tparam InputDeviceType the type of input devices the factory can create
* @param name the name of the factory to unregister
*/
template <typename InputDeviceType>
void UnregisterFactory(const std::string& name) {
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
LOG_ERROR(Input, "Factory '{}' not registered", name);
}
}
/**
* Create an input device from given paramters.
* @tparam InputDeviceType the type of input devices to create
* @param params a serialized ParamPackage string contains all parameters for creating the device
*/
template <typename InputDeviceType>
std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
const Common::ParamPackage package(params);
const std::string engine = package.Get("engine", "null");
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
const auto pair = factory_list.find(engine);
if (pair == factory_list.end()) {
if (engine != "null") {
LOG_ERROR(Input, "Unknown engine name: {}", engine);
}
return std::make_unique<InputDeviceType>();
}
return pair->second->Create(package);
}
/**
* A button device is an input device that returns bool as status.
* true for pressed; false for released.
*/
using ButtonDevice = InputDevice<bool>;
/**
* An analog device is an input device that returns a tuple of x and y coordinates as status. The
* coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
* direction
*/
using AnalogDevice = InputDevice<std::tuple<float, float>>;
/**
* A vibration device is an input device that returns an unsigned byte as status.
* It represents whether the vibration device supports vibration or not.
* If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
*/
using VibrationDevice = InputDevice<u8>;
/**
* A motion status is an object that returns a tuple of accelerometer state vector,
* gyroscope state vector, rotation state vector, orientation state matrix and quaterion state
* vector.
*
* For both 3D vectors:
* x+ is the same direction as RIGHT on D-pad.
* y+ is normal to the touch screen, pointing outward.
* z+ is the same direction as UP on D-pad.
*
* For accelerometer state vector
* Units: g (gravitational acceleration)
*
* For gyroscope state vector:
* Orientation is determined by right-hand rule.
* Units: deg/sec
*
* For rotation state vector
* Units: rotations
*
* For orientation state matrix
* x vector
* y vector
* z vector
*
* For quaternion state vector
* xyz vector
* w float
*/
using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
std::array<Common::Vec3f, 3>, Common::Quaternion<f32>>;
/**
* A motion device is an input device that returns a motion status object
*/
using MotionDevice = InputDevice<MotionStatus>;
/**
* A touch status is an object that returns an array of 16 tuple elements of two floats and a bool.
* The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is
* pressed.
*/
using TouchStatus = std::array<std::tuple<float, float, bool>, 16>;
/**
* A touch device is an input device that returns a touch status object
*/
using TouchDevice = InputDevice<TouchStatus>;
/**
* A mouse device is an input device that returns a tuple of two floats and four ints.
* The first two floats are X and Y device coordinates of the mouse (from 0-1).
* The s32s are the mouse wheel.
*/
using MouseDevice = InputDevice<std::tuple<float, float, s32, s32>>;
} // namespace Input

View File

@@ -0,0 +1,229 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/settings.h"
#include "core/hid/emulated_console.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
EmulatedConsole::EmulatedConsole() = default;
EmulatedConsole::~EmulatedConsole() = default;
void EmulatedConsole::ReloadFromSettings() {
// Using first motion device from player 1. No need to assign any unique config at the moment
const auto& player = Settings::values.players.GetValue()[0];
motion_params = Common::ParamPackage(player.motions[0]);
ReloadInput();
}
void EmulatedConsole::SetTouchParams() {
// TODO(german77): Support any number of fingers
std::size_t index = 0;
// Hardcode mouse, touchscreen and cemuhook parameters
if (!Settings::values.mouse_enabled) {
// We can't use mouse as touch if native mouse is enabled
touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
}
touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
touch_params[index++] =
Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
touch_params[index++] =
Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
const auto button_index =
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
// Map the rest of the fingers from touch from button configuration
for (const auto& config_entry : touch_buttons) {
if (index >= touch_params.size()) {
continue;
}
Common::ParamPackage params{config_entry};
Common::ParamPackage touch_button_params;
const int x = params.Get("x", 0);
const int y = params.Get("y", 0);
params.Erase("x");
params.Erase("y");
touch_button_params.Set("engine", "touch_from_button");
touch_button_params.Set("button", params.Serialize());
touch_button_params.Set("x", x);
touch_button_params.Set("y", y);
touch_button_params.Set("touch_id", static_cast<int>(index));
touch_params[index] = touch_button_params;
index++;
}
}
void EmulatedConsole::ReloadInput() {
// If you load any device here add the equivalent to the UnloadInput() function
SetTouchParams();
motion_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(motion_params);
if (motion_devices) {
Common::Input::InputCallback motion_callback{
[this](Common::Input::CallbackStatus callback) { SetMotion(callback); }};
motion_devices->SetCallback(motion_callback);
}
// Unique index for identifying touch device source
std::size_t index = 0;
for (auto& touch_device : touch_devices) {
touch_device = Common::Input::CreateDevice<Common::Input::InputDevice>(touch_params[index]);
if (!touch_device) {
continue;
}
Common::Input::InputCallback touch_callback{
[this, index](Common::Input::CallbackStatus callback) { SetTouch(callback, index); }};
touch_device->SetCallback(touch_callback);
index++;
}
}
void EmulatedConsole::UnloadInput() {
motion_devices.reset();
for (auto& touch : touch_devices) {
touch.reset();
}
}
void EmulatedConsole::EnableConfiguration() {
is_configuring = true;
SaveCurrentConfig();
}
void EmulatedConsole::DisableConfiguration() {
is_configuring = false;
}
bool EmulatedConsole::IsConfiguring() const {
return is_configuring;
}
void EmulatedConsole::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
}
void EmulatedConsole::RestoreConfig() {
if (!is_configuring) {
return;
}
ReloadFromSettings();
}
Common::ParamPackage EmulatedConsole::GetMotionParam() const {
return motion_params;
}
void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
motion_params = param;
ReloadInput();
}
void EmulatedConsole::SetMotion(Common::Input::CallbackStatus callback) {
std::lock_guard lock{mutex};
auto& raw_status = console.motion_values.raw_status;
auto& emulated = console.motion_values.emulated;
raw_status = TransformToMotion(callback);
emulated.SetAcceleration(Common::Vec3f{
raw_status.accel.x.value,
raw_status.accel.y.value,
raw_status.accel.z.value,
});
emulated.SetGyroscope(Common::Vec3f{
raw_status.gyro.x.value,
raw_status.gyro.y.value,
raw_status.gyro.z.value,
});
emulated.UpdateRotation(raw_status.delta_timestamp);
emulated.UpdateOrientation(raw_status.delta_timestamp);
if (is_configuring) {
TriggerOnChange(ConsoleTriggerType::Motion);
return;
}
auto& motion = console.motion_state;
motion.accel = emulated.GetAcceleration();
motion.gyro = emulated.GetGyroscope();
motion.rotation = emulated.GetGyroscope();
motion.orientation = emulated.GetOrientation();
motion.quaternion = emulated.GetQuaternion();
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
TriggerOnChange(ConsoleTriggerType::Motion);
}
void EmulatedConsole::SetTouch(Common::Input::CallbackStatus callback,
[[maybe_unused]] std::size_t index) {
if (index >= console.touch_values.size()) {
return;
}
std::lock_guard lock{mutex};
console.touch_values[index] = TransformToTouch(callback);
if (is_configuring) {
TriggerOnChange(ConsoleTriggerType::Touch);
return;
}
// TODO(german77): Remap touch id in sequential order
console.touch_state[index] = {
.position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
.id = static_cast<u32>(console.touch_values[index].id),
.pressed = console.touch_values[index].pressed.value,
};
TriggerOnChange(ConsoleTriggerType::Touch);
}
ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
return console.motion_values;
}
TouchValues EmulatedConsole::GetTouchValues() const {
return console.touch_values;
}
ConsoleMotion EmulatedConsole::GetMotion() const {
return console.motion_state;
}
TouchFingerState EmulatedConsole::GetTouch() const {
return console.touch_state;
}
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
for (const auto& poller_pair : callback_list) {
const ConsoleUpdateCallback& poller = poller_pair.second;
if (poller.on_change) {
poller.on_change(type);
}
}
}
int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
std::lock_guard lock{mutex};
callback_list.insert_or_assign(last_callback_key, update_callback);
return last_callback_key++;
}
void EmulatedConsole::DeleteCallback(int key) {
std::lock_guard lock{mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID

View File

@@ -0,0 +1,188 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/point.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
#include "core/hid/hid_types.h"
#include "core/hid/motion_input.h"
namespace Core::HID {
struct ConsoleMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
};
using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>;
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>;
using ConsoleMotionParams = Common::ParamPackage;
using TouchParams = std::array<Common::ParamPackage, 16>;
using ConsoleMotionValues = ConsoleMotionInfo;
using TouchValues = std::array<Common::Input::TouchStatus, 16>;
struct TouchFinger {
u64 last_touch{};
Common::Point<float> position{};
u32 id{};
TouchAttribute attribute{};
bool pressed{};
};
// Contains all motion related data that is used on the services
struct ConsoleMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
Common::Quaternion<f32> quaternion{};
bool is_at_rest{};
};
using TouchFingerState = std::array<TouchFinger, 16>;
struct ConsoleStatus {
// Data from input_common
ConsoleMotionValues motion_values{};
TouchValues touch_values{};
// Data for HID services
ConsoleMotion motion_state{};
TouchFingerState touch_state{};
};
enum class ConsoleTriggerType {
Motion,
Touch,
All,
};
struct ConsoleUpdateCallback {
std::function<void(ConsoleTriggerType)> on_change;
};
class EmulatedConsole {
public:
/**
* Contains all input data related to the console like motion and touch input
*/
EmulatedConsole();
~EmulatedConsole();
YUZU_NON_COPYABLE(EmulatedConsole);
YUZU_NON_MOVEABLE(EmulatedConsole);
/// Removes all callbacks created from input devices
void UnloadInput();
/// Sets the emulated console into configuring mode. Locking all HID service events from being
/// moddified
void EnableConfiguration();
/// Returns the emulated console to the normal behaivour
void DisableConfiguration();
/// Returns true if the emulated console is on configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
// Returns the current mapped motion device
Common::ParamPackage GetMotionParam() const;
/**
* Updates the current mapped motion device
* @param ParamPackage with controller data to be mapped
*/
void SetMotionParam(Common::ParamPackage param);
/// Returns the latest status of motion input from the console with parameters
ConsoleMotionValues GetMotionValues() const;
/// Returns the latest status of touch input from the console with parameters
TouchValues GetTouchValues() const;
/// Returns the latest status of motion input from the console
ConsoleMotion GetMotion() const;
/// Returns the latest status of touch input from the console
TouchFingerState GetTouch() const;
/**
* Adds a callback to the list of events
* @param ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(ConsoleUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// Creates and stores the touch params
void SetTouchParams();
/**
* Updates the motion status of the console
* @param A CallbackStatus containing gyro and accelerometer data
*/
void SetMotion(Common::Input::CallbackStatus callback);
/**
* Updates the touch status of the console
* @param callback: A CallbackStatus containing the touch position
* @param index: Finger ID to be updated
*/
void SetTouch(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Triggers a callback that something has changed on the console status
* @param Input type of the event to trigger
*/
void TriggerOnChange(ConsoleTriggerType type);
bool is_configuring{false};
f32 motion_sensitivity{0.01f};
ConsoleMotionParams motion_params;
TouchParams touch_params;
ConsoleMotionDevices motion_devices;
TouchDevices touch_devices;
mutable std::mutex mutex;
std::unordered_map<int, ConsoleUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all console input
ConsoleStatus console;
};
} // namespace Core::HID

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/point.h"
#include "common/quaternion.h"
#include "common/settings.h"
#include "common/vector_math.h"
#include "core/hid/hid_types.h"
#include "core/hid/motion_input.h"
namespace Core::HID {
const std::size_t max_emulated_controllers = 2;
struct ControllerMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
};
using ButtonDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>;
using StickDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
using TriggerDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
using BatteryDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using OutputDevices =
std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
using TriggerValues =
std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
struct AnalogSticks {
AnalogStickState left{};
AnalogStickState right{};
};
struct ControllerColors {
NpadControllerColor fullkey{};
NpadControllerColor left{};
NpadControllerColor right{};
};
struct BatteryLevelState {
NpadPowerInfo dual{};
NpadPowerInfo left{};
NpadPowerInfo right{};
};
struct ControllerMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
bool is_at_rest{};
};
enum EmulatedDeviceIndex : u8 {
LeftIndex,
RightIndex,
DualIndex,
AllDevices,
};
using MotionState = std::array<ControllerMotion, 2>;
struct ControllerStatus {
// Data from input_common
ButtonValues button_values{};
SticksValues stick_values{};
ControllerMotionValues motion_values{};
TriggerValues trigger_values{};
ColorValues color_values{};
BatteryValues battery_values{};
VibrationValues vibration_values{};
// Data for HID serices
NpadButtonState npad_button_state{};
DebugPadButton debug_pad_button_state{};
AnalogSticks analog_stick_state{};
MotionState motion_state{};
NpadGcTriggerState gc_trigger_state{};
ControllerColors colors_state{};
BatteryLevelState battery_state{};
};
enum class ControllerTriggerType {
Button,
Stick,
Trigger,
Motion,
Color,
Battery,
Vibration,
Connected,
Disconnected,
Type,
All,
};
struct ControllerUpdateCallback {
std::function<void(ControllerTriggerType)> on_change;
bool is_npad_service;
};
class EmulatedController {
public:
/**
* Contains all input data related to this controller. Like buttons, joysticks, motion.
* @param Npad id type for this specific controller
*/
explicit EmulatedController(NpadIdType npad_id_type_);
~EmulatedController();
YUZU_NON_COPYABLE(EmulatedController);
YUZU_NON_MOVEABLE(EmulatedController);
/// Converts the controller type from settings to npad type
static NpadStyleIndex MapSettingsTypeToNPad(Settings::ControllerType type);
/// Converts npad type to the equivalent of controller type from settings
static Settings::ControllerType MapNPadToSettingsType(NpadStyleIndex type);
/// Gets the NpadIdType for this controller
NpadIdType GetNpadIdType() const;
/// Sets the NpadStyleIndex for this controller
void SetNpadStyleIndex(NpadStyleIndex npad_type_);
/**
* Gets the NpadStyleIndex for this controller
* @param If true tmp_npad_type will be returned
* @return NpadStyleIndex set on the controller
*/
NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const;
/// Sets the connected status to true
void Connect();
/// Sets the connected status to false
void Disconnect();
/**
* Is the emulated connected
* @param If true tmp_is_connected will be returned
* @return true if the controller has the connected status
*/
bool IsConnected(bool get_temporary_value = false) const;
/// Returns true if vibration is enabled
bool IsVibrationEnabled() const;
/// Removes all callbacks created from input devices
void UnloadInput();
/// Sets the emulated console into configuring mode. Locking all HID service events from being
/// moddified
void EnableConfiguration();
/// Returns the emulated console to the normal behaivour
void DisableConfiguration();
/// Returns true if the emulated device is on configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
/// Returns a vector of mapped devices from the mapped button and stick parameters
std::vector<Common::ParamPackage> GetMappedDevices(EmulatedDeviceIndex device_index) const;
// Returns the current mapped button device
Common::ParamPackage GetButtonParam(std::size_t index) const;
// Returns the current mapped stick device
Common::ParamPackage GetStickParam(std::size_t index) const;
// Returns the current mapped motion device
Common::ParamPackage GetMotionParam(std::size_t index) const;
/**
* Updates the current mapped button device
* @param ParamPackage with controller data to be mapped
*/
void SetButtonParam(std::size_t index, Common::ParamPackage param);
/**
* Updates the current mapped stick device
* @param ParamPackage with controller data to be mapped
*/
void SetStickParam(std::size_t index, Common::ParamPackage param);
/**
* Updates the current mapped motion device
* @param ParamPackage with controller data to be mapped
*/
void SetMotionParam(std::size_t index, Common::ParamPackage param);
/// Returns the latest button status from the controller with parameters
ButtonValues GetButtonsValues() const;
/// Returns the latest analog stick status from the controller with parameters
SticksValues GetSticksValues() const;
/// Returns the latest trigger status from the controller with parameters
TriggerValues GetTriggersValues() const;
/// Returns the latest motion status from the controller with parameters
ControllerMotionValues GetMotionValues() const;
/// Returns the latest color status from the controller with parameters
ColorValues GetColorsValues() const;
/// Returns the latest battery status from the controller with parameters
BatteryValues GetBatteryValues() const;
/// Returns the latest status of button input for the npad service
NpadButtonState GetNpadButtons() const;
/// Returns the latest status of button input for the debug pad service
DebugPadButton GetDebugPadButtons() const;
/// Returns the latest status of stick input from the mouse
AnalogSticks GetSticks() const;
/// Returns the latest status of trigger input from the mouse
NpadGcTriggerState GetTriggers() const;
/// Returns the latest status of motion input from the mouse
MotionState GetMotions() const;
/// Returns the latest color value from the controller
ControllerColors GetColors() const;
/// Returns the latest battery status from the controller
BatteryLevelState GetBattery() const;
/*
* Sends a specific vibration to the output device
* @return returns true if vibration had no errors
*/
bool SetVibration(std::size_t device_index, VibrationValue vibration);
/*
* Sends a small vibration to the output device
* @return returns true if SetVibration was successfull
*/
bool TestVibration(std::size_t device_index);
/// Returns the led pattern corresponding to this emulated controller
LedPattern GetLedPattern() const;
/// Asks the output device to change the player led pattern
void SetLedPattern();
/**
* Adds a callback to the list of events
* @param ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(ControllerUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// creates input devices from params
void LoadDevices();
/// Set the params for TAS devices
void LoadTASParams();
/**
* Updates the button status of the controller
* @param callback: A CallbackStatus containing the button status
* @param index: Button ID of the to be updated
*/
void SetButton(Common::Input::CallbackStatus callback, std::size_t index, Common::UUID uuid);
/**
* Updates the analog stick status of the controller
* @param callback: A CallbackStatus containing the analog stick status
* @param index: stick ID of the to be updated
*/
void SetStick(Common::Input::CallbackStatus callback, std::size_t index, Common::UUID uuid);
/**
* Updates the trigger status of the controller
* @param callback: A CallbackStatus containing the trigger status
* @param index: trigger ID of the to be updated
*/
void SetTrigger(Common::Input::CallbackStatus callback, std::size_t index, Common::UUID uuid);
/**
* Updates the motion status of the controller
* @param callback: A CallbackStatus containing gyro and accelerometer data
* @param index: motion ID of the to be updated
*/
void SetMotion(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the battery status of the controller
* @param callback: A CallbackStatus containing the battery status
* @param index: Button ID of the to be updated
*/
void SetBattery(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Triggers a callback that something has changed on the controller status
* @param type: Input type of the event to trigger
* @param is_service_update: indicates if this event should be sended to only services
*/
void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
NpadIdType npad_id_type;
NpadStyleIndex npad_type{NpadStyleIndex::None};
bool is_connected{false};
bool is_configuring{false};
f32 motion_sensitivity{0.01f};
bool force_update_motion{false};
// Temporary values to avoid doing changes while the controller is on configuration mode
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
bool tmp_is_connected{false};
ButtonParams button_params;
StickParams stick_params;
ControllerMotionParams motion_params;
TriggerParams trigger_params;
BatteryParams battery_params;
OutputParams output_params;
ButtonDevices button_devices;
StickDevices stick_devices;
ControllerMotionDevices motion_devices;
TriggerDevices trigger_devices;
BatteryDevices battery_devices;
OutputDevices output_devices;
// TAS related variables
ButtonParams tas_button_params;
StickParams tas_stick_params;
ButtonDevices tas_button_devices;
StickDevices tas_stick_devices;
mutable std::mutex mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all controller input
ControllerStatus controller;
};
} // namespace Core::HID

View File

@@ -0,0 +1,451 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <algorithm>
#include <fmt/format.h>
#include "core/hid/emulated_devices.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
EmulatedDevices::EmulatedDevices() = default;
EmulatedDevices::~EmulatedDevices() = default;
void EmulatedDevices::ReloadFromSettings() {
ReloadInput();
}
void EmulatedDevices::ReloadInput() {
// If you load any device here add the equivalent to the UnloadInput() function
std::size_t key_index = 0;
for (auto& mouse_device : mouse_button_devices) {
Common::ParamPackage mouse_params;
mouse_params.Set("engine", "mouse");
mouse_params.Set("button", static_cast<int>(key_index));
mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
key_index++;
}
mouse_stick_device = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
"engine:mouse,axis_x:0,axis_y:1");
// First two axis are reserved for mouse position
key_index = 2;
for (auto& mouse_device : mouse_analog_devices) {
Common::ParamPackage mouse_params;
mouse_params.Set("engine", "mouse");
mouse_params.Set("axis", static_cast<int>(key_index));
mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
key_index++;
}
key_index = 0;
for (auto& keyboard_device : keyboard_devices) {
// Keyboard keys are only mapped on port 1, pad 0
Common::ParamPackage keyboard_params;
keyboard_params.Set("engine", "keyboard");
keyboard_params.Set("button", static_cast<int>(key_index));
keyboard_params.Set("port", 1);
keyboard_params.Set("pad", 0);
keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
key_index++;
}
key_index = 0;
for (auto& keyboard_device : keyboard_modifier_devices) {
// Keyboard moddifiers are only mapped on port 1, pad 1
Common::ParamPackage keyboard_params;
keyboard_params.Set("engine", "keyboard");
keyboard_params.Set("button", static_cast<int>(key_index));
keyboard_params.Set("port", 1);
keyboard_params.Set("pad", 1);
keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
key_index++;
}
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
if (!mouse_button_devices[index]) {
continue;
}
Common::Input::InputCallback button_callback{
[this, index](Common::Input::CallbackStatus callback) {
SetMouseButton(callback, index);
}};
mouse_button_devices[index]->SetCallback(button_callback);
}
for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) {
if (!mouse_analog_devices[index]) {
continue;
}
Common::Input::InputCallback button_callback{
[this, index](Common::Input::CallbackStatus callback) {
SetMouseAnalog(callback, index);
}};
mouse_analog_devices[index]->SetCallback(button_callback);
}
if (mouse_stick_device) {
Common::Input::InputCallback button_callback{
[this](Common::Input::CallbackStatus callback) { SetMouseStick(callback); }};
mouse_stick_device->SetCallback(button_callback);
}
for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
if (!keyboard_devices[index]) {
continue;
}
Common::Input::InputCallback button_callback{
[this, index](Common::Input::CallbackStatus callback) {
SetKeyboardButton(callback, index);
}};
keyboard_devices[index]->SetCallback(button_callback);
}
for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
if (!keyboard_modifier_devices[index]) {
continue;
}
Common::Input::InputCallback button_callback{
[this, index](Common::Input::CallbackStatus callback) {
SetKeyboardModifier(callback, index);
}};
keyboard_modifier_devices[index]->SetCallback(button_callback);
}
}
void EmulatedDevices::UnloadInput() {
for (auto& button : mouse_button_devices) {
button.reset();
}
for (auto& analog : mouse_analog_devices) {
analog.reset();
}
mouse_stick_device.reset();
for (auto& button : keyboard_devices) {
button.reset();
}
for (auto& button : keyboard_modifier_devices) {
button.reset();
}
}
void EmulatedDevices::EnableConfiguration() {
is_configuring = true;
SaveCurrentConfig();
}
void EmulatedDevices::DisableConfiguration() {
is_configuring = false;
}
bool EmulatedDevices::IsConfiguring() const {
return is_configuring;
}
void EmulatedDevices::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
}
void EmulatedDevices::RestoreConfig() {
if (!is_configuring) {
return;
}
ReloadFromSettings();
}
void EmulatedDevices::SetKeyboardButton(Common::Input::CallbackStatus callback, std::size_t index) {
if (index >= device_status.keyboard_values.size()) {
return;
}
std::lock_guard lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.keyboard_values[index];
current_status.toggle = new_status.toggle;
// Update button status with current status
if (!current_status.toggle) {
current_status.locked = false;
if (current_status.value != new_status.value) {
current_status.value = new_status.value;
value_changed = true;
}
} else {
// Toggle button and lock status
if (new_status.value && !current_status.locked) {
current_status.locked = true;
current_status.value = !current_status.value;
value_changed = true;
}
// Unlock button, ready for next press
if (!new_status.value && current_status.locked) {
current_status.locked = false;
}
}
if (!value_changed) {
return;
}
if (is_configuring) {
TriggerOnChange(DeviceTriggerType::Keyboard);
return;
}
// Index should be converted from NativeKeyboard to KeyboardKeyIndex
UpdateKey(index, current_status.value);
TriggerOnChange(DeviceTriggerType::Keyboard);
}
void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
constexpr std::size_t KEYS_PER_BYTE = 8;
auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
if (status) {
entry = entry | mask;
} else {
entry = static_cast<u8>(entry & ~mask);
}
}
void EmulatedDevices::SetKeyboardModifier(Common::Input::CallbackStatus callback,
std::size_t index) {
if (index >= device_status.keyboard_moddifier_values.size()) {
return;
}
std::lock_guard lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.keyboard_moddifier_values[index];
current_status.toggle = new_status.toggle;
// Update button status with current
if (!current_status.toggle) {
current_status.locked = false;
if (current_status.value != new_status.value) {
current_status.value = new_status.value;
value_changed = true;
}
} else {
// Toggle button and lock status
if (new_status.value && !current_status.locked) {
current_status.locked = true;
current_status.value = !current_status.value;
value_changed = true;
}
// Unlock button ready for next press
if (!new_status.value && current_status.locked) {
current_status.locked = false;
}
}
if (!value_changed) {
return;
}
if (is_configuring) {
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
return;
}
switch (index) {
case Settings::NativeKeyboard::LeftControl:
case Settings::NativeKeyboard::RightControl:
device_status.keyboard_moddifier_state.control.Assign(current_status.value);
break;
case Settings::NativeKeyboard::LeftShift:
case Settings::NativeKeyboard::RightShift:
device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
break;
case Settings::NativeKeyboard::LeftAlt:
device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
break;
case Settings::NativeKeyboard::RightAlt:
device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
break;
case Settings::NativeKeyboard::CapsLock:
device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
break;
case Settings::NativeKeyboard::ScrollLock:
device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
break;
case Settings::NativeKeyboard::NumLock:
device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
break;
}
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
}
void EmulatedDevices::SetMouseButton(Common::Input::CallbackStatus callback, std::size_t index) {
if (index >= device_status.mouse_button_values.size()) {
return;
}
std::lock_guard lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.mouse_button_values[index];
current_status.toggle = new_status.toggle;
// Update button status with current
if (!current_status.toggle) {
current_status.locked = false;
if (current_status.value != new_status.value) {
current_status.value = new_status.value;
value_changed = true;
}
} else {
// Toggle button and lock status
if (new_status.value && !current_status.locked) {
current_status.locked = true;
current_status.value = !current_status.value;
value_changed = true;
}
// Unlock button ready for next press
if (!new_status.value && current_status.locked) {
current_status.locked = false;
}
}
if (!value_changed) {
return;
}
if (is_configuring) {
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
switch (index) {
case Settings::NativeMouseButton::Left:
device_status.mouse_button_state.left.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Right:
device_status.mouse_button_state.right.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Middle:
device_status.mouse_button_state.middle.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Forward:
device_status.mouse_button_state.forward.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Back:
device_status.mouse_button_state.back.Assign(current_status.value);
break;
}
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetMouseAnalog(Common::Input::CallbackStatus callback, std::size_t index) {
if (index >= device_status.mouse_analog_values.size()) {
return;
}
std::lock_guard lock{mutex};
const auto analog_value = TransformToAnalog(callback);
device_status.mouse_analog_values[index] = analog_value;
if (is_configuring) {
device_status.mouse_position_state = {};
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
switch (index) {
case Settings::NativeMouseWheel::X:
device_status.mouse_wheel_state.x = static_cast<s32>(analog_value.value);
break;
case Settings::NativeMouseWheel::Y:
device_status.mouse_wheel_state.y = static_cast<s32>(analog_value.value);
break;
}
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetMouseStick(Common::Input::CallbackStatus callback) {
std::lock_guard lock{mutex};
const auto touch_value = TransformToTouch(callback);
device_status.mouse_stick_value = touch_value;
if (is_configuring) {
device_status.mouse_position_state = {};
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
device_status.mouse_position_state.x = touch_value.x.value;
device_status.mouse_position_state.y = touch_value.y.value;
TriggerOnChange(DeviceTriggerType::Mouse);
}
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
return device_status.keyboard_values;
}
KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
return device_status.keyboard_moddifier_values;
}
MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
return device_status.mouse_button_values;
}
KeyboardKey EmulatedDevices::GetKeyboard() const {
return device_status.keyboard_state;
}
KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
return device_status.keyboard_moddifier_state;
}
MouseButton EmulatedDevices::GetMouseButtons() const {
return device_status.mouse_button_state;
}
MousePosition EmulatedDevices::GetMousePosition() const {
return device_status.mouse_position_state;
}
AnalogStickState EmulatedDevices::GetMouseWheel() const {
return device_status.mouse_wheel_state;
}
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
for (const auto& poller_pair : callback_list) {
const InterfaceUpdateCallback& poller = poller_pair.second;
if (poller.on_change) {
poller.on_change(type);
}
}
}
int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
std::lock_guard lock{mutex};
callback_list.insert_or_assign(last_callback_key, update_callback);
return last_callback_key++;
}
void EmulatedDevices::DeleteCallback(int key) {
std::lock_guard lock{mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID

View File

@@ -0,0 +1,209 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "core/hid/hid_types.h"
namespace Core::HID {
using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeKeyboard::NumKeyboardKeys>;
using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeKeyboard::NumKeyboardMods>;
using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseButton::NumMouseButtons>;
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
using MouseButtonParams =
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
using KeyboardValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
using KeyboardModifierValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
using MouseButtonValues =
std::array<Common::Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
using MouseAnalogValues =
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickValue = Common::Input::TouchStatus;
struct MousePosition {
f32 x;
f32 y;
};
struct DeviceStatus {
// Data from input_common
KeyboardValues keyboard_values{};
KeyboardModifierValues keyboard_moddifier_values{};
MouseButtonValues mouse_button_values{};
MouseAnalogValues mouse_analog_values{};
MouseStickValue mouse_stick_value{};
// Data for HID serices
KeyboardKey keyboard_state{};
KeyboardModifier keyboard_moddifier_state{};
MouseButton mouse_button_state{};
MousePosition mouse_position_state{};
AnalogStickState mouse_wheel_state{};
};
enum class DeviceTriggerType {
Keyboard,
KeyboardModdifier,
Mouse,
};
struct InterfaceUpdateCallback {
std::function<void(DeviceTriggerType)> on_change;
};
class EmulatedDevices {
public:
/**
* Contains all input data related to external devices that aren't necesarily a controller
* like keyboard and mouse
*/
EmulatedDevices();
~EmulatedDevices();
YUZU_NON_COPYABLE(EmulatedDevices);
YUZU_NON_MOVEABLE(EmulatedDevices);
/// Removes all callbacks created from input devices
void UnloadInput();
/// Sets the emulated console into configuring mode. Locking all HID service events from being
/// moddified
void EnableConfiguration();
/// Returns the emulated console to the normal behaivour
void DisableConfiguration();
/// Returns true if the emulated device is on configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
/// Returns the latest status of button input from the keyboard with parameters
KeyboardValues GetKeyboardValues() const;
/// Returns the latest status of button input from the keyboard modifiers with parameters
KeyboardModifierValues GetKeyboardModdifierValues() const;
/// Returns the latest status of button input from the mouse with parameters
MouseButtonValues GetMouseButtonsValues() const;
/// Returns the latest status of button input from the keyboard
KeyboardKey GetKeyboard() const;
/// Returns the latest status of button input from the keyboard modifiers
KeyboardModifier GetKeyboardModifier() const;
/// Returns the latest status of button input from the mouse
MouseButton GetMouseButtons() const;
/// Returns the latest mouse coordinates
MousePosition GetMousePosition() const;
/// Returns the latest mouse wheel change
AnalogStickState GetMouseWheel() const;
/**
* Adds a callback to the list of events
* @param InterfaceUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(InterfaceUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// Helps assigning a value to keyboard_state
void UpdateKey(std::size_t key_index, bool status);
/**
* Updates the touch status of the keyboard device
* @param callback: A CallbackStatus containing the key status
* @param index: key ID to be updated
*/
void SetKeyboardButton(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the keyboard status of the keyboard device
* @param callback: A CallbackStatus containing the modifier key status
* @param index: modifier key ID to be updated
*/
void SetKeyboardModifier(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the mouse button status of the mouse device
* @param callback: A CallbackStatus containing the button status
* @param index: Button ID to be updated
*/
void SetMouseButton(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the mouse wheel status of the mouse device
* @param callback: A CallbackStatus containing the wheel status
* @param index: wheel ID to be updated
*/
void SetMouseAnalog(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the mouse position status of the mouse device
* @param callback: A CallbackStatus containing the position status
* @param index: stick ID to be updated
*/
void SetMouseStick(Common::Input::CallbackStatus callback);
/**
* Triggers a callback that something has changed on the device status
* @param Input type of the event to trigger
*/
void TriggerOnChange(DeviceTriggerType type);
bool is_configuring{false};
KeyboardDevices keyboard_devices;
KeyboardModifierDevices keyboard_modifier_devices;
MouseButtonDevices mouse_button_devices;
MouseAnalogDevices mouse_analog_devices;
MouseStickDevice mouse_stick_device;
mutable std::mutex mutex;
std::unordered_map<int, InterfaceUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all external device input
DeviceStatus device_status;
};
} // namespace Core::HID

168
src/core/hid/hid_core.cpp Normal file
View File

@@ -0,0 +1,168 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "core/hid/emulated_console.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
namespace Core::HID {
HIDCore::HIDCore()
: player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
other{std::make_unique<EmulatedController>(NpadIdType::Other)},
handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
HIDCore::~HIDCore() = default;
EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
switch (npad_id_type) {
case NpadIdType::Player1:
return player_1.get();
case NpadIdType::Player2:
return player_2.get();
case NpadIdType::Player3:
return player_3.get();
case NpadIdType::Player4:
return player_4.get();
case NpadIdType::Player5:
return player_5.get();
case NpadIdType::Player6:
return player_6.get();
case NpadIdType::Player7:
return player_7.get();
case NpadIdType::Player8:
return player_8.get();
case NpadIdType::Other:
return other.get();
case NpadIdType::Handheld:
return handheld.get();
case NpadIdType::Invalid:
default:
UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
return nullptr;
}
}
const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
switch (npad_id_type) {
case NpadIdType::Player1:
return player_1.get();
case NpadIdType::Player2:
return player_2.get();
case NpadIdType::Player3:
return player_3.get();
case NpadIdType::Player4:
return player_4.get();
case NpadIdType::Player5:
return player_5.get();
case NpadIdType::Player6:
return player_6.get();
case NpadIdType::Player7:
return player_7.get();
case NpadIdType::Player8:
return player_8.get();
case NpadIdType::Other:
return other.get();
case NpadIdType::Handheld:
return handheld.get();
case NpadIdType::Invalid:
default:
UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
return nullptr;
}
}
EmulatedConsole* HIDCore::GetEmulatedConsole() {
return console.get();
}
const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
return console.get();
}
EmulatedDevices* HIDCore::GetEmulatedDevices() {
return devices.get();
}
const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
return devices.get();
}
EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
return GetEmulatedController(IndexToNpadIdType(index));
}
const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
return GetEmulatedController(IndexToNpadIdType(index));
}
void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
supported_style_tag.raw = style_tag.raw;
}
NpadStyleTag HIDCore::GetSupportedStyleTag() const {
return supported_style_tag;
}
s8 HIDCore::GetPlayerCount() const {
s8 active_players = 0;
for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (controller->IsConnected()) {
active_players++;
}
}
return active_players;
}
NpadIdType HIDCore::GetFirstNpadId() const {
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (controller->IsConnected()) {
return controller->GetNpadIdType();
}
}
return NpadIdType::Player1;
}
void HIDCore::ReloadInputDevices() {
player_1->ReloadFromSettings();
player_2->ReloadFromSettings();
player_3->ReloadFromSettings();
player_4->ReloadFromSettings();
player_5->ReloadFromSettings();
player_6->ReloadFromSettings();
player_7->ReloadFromSettings();
player_8->ReloadFromSettings();
other->ReloadFromSettings();
handheld->ReloadFromSettings();
console->ReloadFromSettings();
devices->ReloadFromSettings();
}
void HIDCore::UnloadInputDevices() {
player_1->UnloadInput();
player_2->UnloadInput();
player_3->UnloadInput();
player_4->UnloadInput();
player_5->UnloadInput();
player_6->UnloadInput();
player_7->UnloadInput();
player_8->UnloadInput();
other->UnloadInput();
handheld->UnloadInput();
console->UnloadInput();
devices->UnloadInput();
}
} // namespace Core::HID

73
src/core/hid/hid_core.h Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/hid/hid_types.h"
namespace Core::HID {
class EmulatedConsole;
class EmulatedController;
class EmulatedDevices;
} // namespace Core::HID
namespace Core::HID {
class HIDCore {
public:
explicit HIDCore();
~HIDCore();
YUZU_NON_COPYABLE(HIDCore);
YUZU_NON_MOVEABLE(HIDCore);
EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
EmulatedConsole* GetEmulatedConsole();
const EmulatedConsole* GetEmulatedConsole() const;
EmulatedDevices* GetEmulatedDevices();
const EmulatedDevices* GetEmulatedDevices() const;
void SetSupportedStyleTag(NpadStyleTag style_tag);
NpadStyleTag GetSupportedStyleTag() const;
/// Counts the connected players from P1-P8
s8 GetPlayerCount() const;
/// Returns the first connected npad id
NpadIdType GetFirstNpadId() const;
/// Reloads all input devices from settings
void ReloadInputDevices();
/// Removes all callbacks from input common
void UnloadInputDevices();
/// Number of emulated controllers
static constexpr std::size_t available_controllers{10};
private:
std::unique_ptr<EmulatedController> player_1;
std::unique_ptr<EmulatedController> player_2;
std::unique_ptr<EmulatedController> player_3;
std::unique_ptr<EmulatedController> player_4;
std::unique_ptr<EmulatedController> player_5;
std::unique_ptr<EmulatedController> player_6;
std::unique_ptr<EmulatedController> player_7;
std::unique_ptr<EmulatedController> player_8;
std::unique_ptr<EmulatedController> other;
std::unique_ptr<EmulatedController> handheld;
std::unique_ptr<EmulatedConsole> console;
std::unique_ptr<EmulatedDevices> devices;
NpadStyleTag supported_style_tag;
};
} // namespace Core::HID

631
src/core/hid/hid_types.h Normal file
View File

@@ -0,0 +1,631 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/point.h"
#include "common/uuid.h"
namespace Core::HID {
enum class DeviceIndex : u8 {
Left = 0,
Right = 1,
None = 2,
MaxDeviceIndex = 3,
};
// This is nn::hid::NpadButton
enum class NpadButton : u64 {
None = 0,
A = 1U << 0,
B = 1U << 1,
X = 1U << 2,
Y = 1U << 3,
StickL = 1U << 4,
StickR = 1U << 5,
L = 1U << 6,
R = 1U << 7,
ZL = 1U << 8,
ZR = 1U << 9,
Plus = 1U << 10,
Minus = 1U << 11,
Left = 1U << 12,
Up = 1U << 13,
Right = 1U << 14,
Down = 1U << 15,
StickLLeft = 1U << 16,
StickLUp = 1U << 17,
StickLRight = 1U << 18,
StickLDown = 1U << 19,
StickRLeft = 1U << 20,
StickRUp = 1U << 21,
StickRRight = 1U << 22,
StickRDown = 1U << 23,
LeftSL = 1U << 24,
LeftSR = 1U << 25,
RightSL = 1U << 26,
RightSR = 1U << 27,
Palma = 1U << 28,
Verification = 1U << 29,
HandheldLeftB = 1U << 30,
LagonCLeft = 1U << 31,
LagonCUp = 1ULL << 32,
LagonCRight = 1ULL << 33,
LagonCDown = 1ULL << 34,
};
DECLARE_ENUM_FLAG_OPERATORS(NpadButton);
enum class KeyboardKeyIndex : u32 {
A = 4,
B = 5,
C = 6,
D = 7,
E = 8,
F = 9,
G = 10,
H = 11,
I = 12,
J = 13,
K = 14,
L = 15,
M = 16,
N = 17,
O = 18,
P = 19,
Q = 20,
R = 21,
S = 22,
T = 23,
U = 24,
V = 25,
W = 26,
X = 27,
Y = 28,
Z = 29,
D1 = 30,
D2 = 31,
D3 = 32,
D4 = 33,
D5 = 34,
D6 = 35,
D7 = 36,
D8 = 37,
D9 = 38,
D0 = 39,
Return = 40,
Escape = 41,
Backspace = 42,
Tab = 43,
Space = 44,
Minus = 45,
Plus = 46,
OpenBracket = 47,
CloseBracket = 48,
Pipe = 49,
Tilde = 50,
Semicolon = 51,
Quote = 52,
Backquote = 53,
Comma = 54,
Period = 55,
Slash = 56,
CapsLock = 57,
F1 = 58,
F2 = 59,
F3 = 60,
F4 = 61,
F5 = 62,
F6 = 63,
F7 = 64,
F8 = 65,
F9 = 66,
F10 = 67,
F11 = 68,
F12 = 69,
PrintScreen = 70,
ScrollLock = 71,
Pause = 72,
Insert = 73,
Home = 74,
PageUp = 75,
Delete = 76,
End = 77,
PageDown = 78,
RightArrow = 79,
LeftArrow = 80,
DownArrow = 81,
UpArrow = 82,
NumLock = 83,
NumPadDivide = 84,
NumPadMultiply = 85,
NumPadSubtract = 86,
NumPadAdd = 87,
NumPadEnter = 88,
NumPad1 = 89,
NumPad2 = 90,
NumPad3 = 91,
NumPad4 = 92,
NumPad5 = 93,
NumPad6 = 94,
NumPad7 = 95,
NumPad8 = 96,
NumPad9 = 97,
NumPad0 = 98,
NumPadDot = 99,
Backslash = 100,
Application = 101,
Power = 102,
NumPadEquals = 103,
F13 = 104,
F14 = 105,
F15 = 106,
F16 = 107,
F17 = 108,
F18 = 109,
F19 = 110,
F20 = 111,
F21 = 112,
F22 = 113,
F23 = 114,
F24 = 115,
NumPadComma = 133,
Ro = 135,
KatakanaHiragana = 136,
Yen = 137,
Henkan = 138,
Muhenkan = 139,
NumPadCommaPc98 = 140,
HangulEnglish = 144,
Hanja = 145,
Katakana = 146,
Hiragana = 147,
ZenkakuHankaku = 148,
LeftControl = 224,
LeftShift = 225,
LeftAlt = 226,
LeftGui = 227,
RightControl = 228,
RightShift = 229,
RightAlt = 230,
RightGui = 231,
};
// This is nn::hid::NpadIdType
enum class NpadIdType : u32 {
Player1 = 0x0,
Player2 = 0x1,
Player3 = 0x2,
Player4 = 0x3,
Player5 = 0x4,
Player6 = 0x5,
Player7 = 0x6,
Player8 = 0x7,
Other = 0x10,
Handheld = 0x20,
Invalid = 0xFFFFFFFF,
};
// This is nn::hid::NpadStyleIndex
enum class NpadStyleIndex : u8 {
None = 0,
ProController = 3,
Handheld = 4,
HandheldNES = 4,
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
GameCube = 8,
Pokeball = 9,
NES = 10,
SNES = 12,
N64 = 13,
SegaGenesis = 14,
SystemExt = 32,
System = 33,
MaxNpadType = 34,
};
// This is nn::hid::NpadStyleSet
enum class NpadStyleSet : u32 {
None = 0,
Fullkey = 1U << 0,
Handheld = 1U << 1,
JoyDual = 1U << 2,
JoyLeft = 1U << 3,
JoyRight = 1U << 4,
Gc = 1U << 5,
Palma = 1U << 6,
Lark = 1U << 7,
HandheldLark = 1U << 8,
Lucia = 1U << 9,
Lagoon = 1U << 10,
Lager = 1U << 11,
SystemExt = 1U << 29,
System = 1U << 30,
};
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
// This is nn::hid::VibrationDevicePosition
enum class VibrationDevicePosition : u32 {
None = 0,
Left = 1,
Right = 2,
};
// This is nn::hid::VibrationDeviceType
enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1,
GcErm = 2,
};
// This is nn::hid::VibrationGcErmCommand
enum class VibrationGcErmCommand : u64 {
Stop = 0,
Start = 1,
StopHard = 2,
};
// This is nn::hid::NpadStyleTag
struct NpadStyleTag {
union {
NpadStyleSet raw{};
BitField<0, 1, u32> fullkey;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<5, 1, u32> gamecube;
BitField<6, 1, u32> palma;
BitField<7, 1, u32> lark;
BitField<8, 1, u32> handheld_lark;
BitField<9, 1, u32> lucia;
BitField<10, 1, u32> lagoon;
BitField<11, 1, u32> lager;
BitField<29, 1, u32> system_ext;
BitField<30, 1, u32> system;
};
};
static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size");
// This is nn::hid::TouchAttribute
struct TouchAttribute {
union {
u32 raw{};
BitField<0, 1, u32> start_touch;
BitField<1, 1, u32> end_touch;
};
};
static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size");
// This is nn::hid::TouchState
struct TouchState {
u64 delta_time;
TouchAttribute attribute;
u32 finger;
Common::Point<u32> position;
u32 diameter_x;
u32 diameter_y;
u32 rotation_angle;
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
// This is nn::hid::NpadControllerColor
struct NpadControllerColor {
u32 body;
u32 button;
};
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
// This is nn::hid::AnalogStickState
struct AnalogStickState {
s32 x;
s32 y;
};
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
s32 left{};
s32 right{};
};
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::system::NpadBatteryLevel
using NpadBatteryLevel = u32;
static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size");
// This is nn::hid::system::NpadPowerInfo
struct NpadPowerInfo {
bool is_powered;
bool is_charging;
INSERT_PADDING_BYTES(0x6);
NpadBatteryLevel battery_level;
};
static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
position1.Assign(light1);
position2.Assign(light2);
position3.Assign(light3);
position4.Assign(light4);
}
union {
u64 raw{};
BitField<0, 1, u64> position1;
BitField<1, 1, u64> position2;
BitField<2, 1, u64> position3;
BitField<3, 1, u64> position4;
};
};
struct NpadButtonState {
union {
NpadButton raw{};
// Buttons
BitField<0, 1, u64> a;
BitField<1, 1, u64> b;
BitField<2, 1, u64> x;
BitField<3, 1, u64> y;
BitField<4, 1, u64> stick_l;
BitField<5, 1, u64> stick_r;
BitField<6, 1, u64> l;
BitField<7, 1, u64> r;
BitField<8, 1, u64> zl;
BitField<9, 1, u64> zr;
BitField<10, 1, u64> plus;
BitField<11, 1, u64> minus;
// D-Pad
BitField<12, 1, u64> left;
BitField<13, 1, u64> up;
BitField<14, 1, u64> right;
BitField<15, 1, u64> down;
// Left JoyStick
BitField<16, 1, u64> stick_l_left;
BitField<17, 1, u64> stick_l_up;
BitField<18, 1, u64> stick_l_right;
BitField<19, 1, u64> stick_l_down;
// Right JoyStick
BitField<20, 1, u64> stick_r_left;
BitField<21, 1, u64> stick_r_up;
BitField<22, 1, u64> stick_r_right;
BitField<23, 1, u64> stick_r_down;
BitField<24, 1, u64> left_sl;
BitField<25, 1, u64> left_sr;
BitField<26, 1, u64> right_sl;
BitField<27, 1, u64> right_sr;
BitField<28, 1, u64> palma;
BitField<29, 1, u64> verification;
BitField<30, 1, u64> handheld_left_b;
BitField<31, 1, u64> lagon_c_left;
BitField<32, 1, u64> lagon_c_up;
BitField<33, 1, u64> lagon_c_right;
BitField<34, 1, u64> lagon_c_down;
};
};
static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size.");
// This is nn::hid::DebugPadButton
struct DebugPadButton {
union {
u32 raw{};
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
BitField<2, 1, u32> x;
BitField<3, 1, u32> y;
BitField<4, 1, u32> l;
BitField<5, 1, u32> r;
BitField<6, 1, u32> zl;
BitField<7, 1, u32> zr;
BitField<8, 1, u32> plus;
BitField<9, 1, u32> minus;
BitField<10, 1, u32> d_left;
BitField<11, 1, u32> d_up;
BitField<12, 1, u32> d_right;
BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size");
// This is nn::hid::ConsoleSixAxisSensorHandle
struct ConsoleSixAxisSensorHandle {
u8 unknown_1;
u8 unknown_2;
INSERT_PADDING_BYTES_NOINIT(2);
};
static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
"ConsoleSixAxisSensorHandle is an invalid size");
// This is nn::hid::SixAxisSensorHandle
struct SixAxisSensorHandle {
NpadStyleIndex npad_type;
u8 npad_id;
DeviceIndex device_index;
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size");
struct SixAxisSensorFusionParameters {
f32 parameter1;
f32 parameter2;
};
static_assert(sizeof(SixAxisSensorFusionParameters) == 8,
"SixAxisSensorFusionParameters is an invalid size");
// This is nn::hid::VibrationDeviceHandle
struct VibrationDeviceHandle {
NpadStyleIndex npad_type;
u8 npad_id;
DeviceIndex device_index;
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size");
// This is nn::hid::VibrationValue
struct VibrationValue {
f32 low_amplitude;
f32 low_frequency;
f32 high_amplitude;
f32 high_frequency;
};
static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
// This is nn::hid::VibrationDeviceInfo
struct VibrationDeviceInfo {
VibrationDeviceType type{};
VibrationDevicePosition position{};
};
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
// This is nn::hid::KeyboardModifier
struct KeyboardModifier {
union {
u32 raw{};
BitField<0, 1, u32> control;
BitField<1, 1, u32> shift;
BitField<2, 1, u32> left_alt;
BitField<3, 1, u32> right_alt;
BitField<4, 1, u32> gui;
BitField<8, 1, u32> caps_lock;
BitField<9, 1, u32> scroll_lock;
BitField<10, 1, u32> num_lock;
BitField<11, 1, u32> katakana;
BitField<12, 1, u32> hiragana;
};
};
static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size");
// This is nn::hid::KeyboardAttribute
struct KeyboardAttribute {
union {
u32 raw{};
BitField<0, 1, u32> is_connected;
};
};
static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size");
// This is nn::hid::KeyboardKey
struct KeyboardKey {
// This should be a 256 bit flag
std::array<u8, 32> key;
};
static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
// This is nn::hid::MouseButton
struct MouseButton {
union {
u32_le raw{};
BitField<0, 1, u32> left;
BitField<1, 1, u32> right;
BitField<2, 1, u32> middle;
BitField<3, 1, u32> forward;
BitField<4, 1, u32> back;
};
};
static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
// This is nn::hid::MouseAttribute
struct MouseAttribute {
union {
u32 raw{};
BitField<0, 1, u32> transferable;
BitField<1, 1, u32> is_connected;
};
};
static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size");
// This is nn::hid::detail::MouseState
struct MouseState {
s64 sampling_number;
s32 x;
s32 y;
s32 delta_x;
s32 delta_y;
// Axis Order in HW is switched for the wheel
s32 delta_wheel_y;
s32 delta_wheel_x;
MouseButton button;
MouseAttribute attribute;
};
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
/// Converts a NpadIdType to an array index.
constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
switch (npad_id_type) {
case NpadIdType::Player1:
return 0;
case NpadIdType::Player2:
return 1;
case NpadIdType::Player3:
return 2;
case NpadIdType::Player4:
return 3;
case NpadIdType::Player5:
return 4;
case NpadIdType::Player6:
return 5;
case NpadIdType::Player7:
return 6;
case NpadIdType::Player8:
return 7;
case NpadIdType::Handheld:
return 8;
case NpadIdType::Other:
return 9;
default:
return 0;
}
}
/// Converts an array index to a NpadIdType
constexpr NpadIdType IndexToNpadIdType(size_t index) {
switch (index) {
case 0:
return NpadIdType::Player1;
case 1:
return NpadIdType::Player2;
case 2:
return NpadIdType::Player3;
case 3:
return NpadIdType::Player4;
case 4:
return NpadIdType::Player5;
case 5:
return NpadIdType::Player6;
case 6:
return NpadIdType::Player7;
case 7:
return NpadIdType::Player8;
case 8:
return NpadIdType::Handheld;
case 9:
return NpadIdType::Other;
default:
return NpadIdType::Invalid;
}
}
} // namespace Core::HID

View File

@@ -0,0 +1,383 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <random>
#include "common/input.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) {
Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None};
switch (callback.type) {
case Common::Input::InputType::Analog:
case Common::Input::InputType::Trigger: {
const auto value = TransformToTrigger(callback).analog.value;
battery = Common::Input::BatteryLevel::Empty;
if (value > 0.2f) {
battery = Common::Input::BatteryLevel::Critical;
}
if (value > 0.4f) {
battery = Common::Input::BatteryLevel::Low;
}
if (value > 0.6f) {
battery = Common::Input::BatteryLevel::Medium;
}
if (value > 0.8f) {
battery = Common::Input::BatteryLevel::Full;
}
if (value >= 1.0f) {
battery = Common::Input::BatteryLevel::Charging;
}
break;
}
case Common::Input::InputType::Button:
battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging
: Common::Input::BatteryLevel::Critical;
break;
case Common::Input::InputType::Battery:
battery = callback.battery_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
break;
}
return battery;
}
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) {
Common::Input::ButtonStatus status{};
switch (callback.type) {
case Common::Input::InputType::Analog:
case Common::Input::InputType::Trigger:
status.value = TransformToTrigger(callback).pressed.value;
break;
case Common::Input::InputType::Button:
status = callback.button_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
break;
}
if (status.inverted) {
status.value = !status.value;
}
return status;
}
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) {
Common::Input::MotionStatus status{};
switch (callback.type) {
case Common::Input::InputType::Button: {
Common::Input::AnalogProperties properties{
.deadzone = 0.0f,
.range = 1.0f,
.offset = 0.0f,
};
status.delta_timestamp = 5000;
status.force_update = true;
status.accel.x = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.accel.y = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.accel.z = {
.value = 0.0f,
.raw_value = -1.0f,
.properties = properties,
};
status.gyro.x = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.gyro.y = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.gyro.z = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
if (TransformToButton(callback).value) {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<s16> distribution(-1000, 1000);
status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
}
break;
}
case Common::Input::InputType::Motion:
status = callback.motion_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
break;
}
SanitizeAnalog(status.accel.x, false);
SanitizeAnalog(status.accel.y, false);
SanitizeAnalog(status.accel.z, false);
SanitizeAnalog(status.gyro.x, false);
SanitizeAnalog(status.gyro.y, false);
SanitizeAnalog(status.gyro.z, false);
return status;
}
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) {
Common::Input::StickStatus status{};
switch (callback.type) {
case Common::Input::InputType::Stick:
status = callback.stick_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
break;
}
SanitizeStick(status.x, status.y, true);
const auto& properties_x = status.x.properties;
const auto& properties_y = status.y.properties;
const float x = status.x.value;
const float y = status.y.value;
// Set directional buttons
status.right = x > properties_x.threshold;
status.left = x < -properties_x.threshold;
status.up = y > properties_y.threshold;
status.down = y < -properties_y.threshold;
return status;
}
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) {
Common::Input::TouchStatus status{};
switch (callback.type) {
case Common::Input::InputType::Touch:
status = callback.touch_status;
break;
case Common::Input::InputType::Stick:
status.x = callback.stick_status.x;
status.y = callback.stick_status.y;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
break;
}
SanitizeAnalog(status.x, true);
SanitizeAnalog(status.y, true);
float& x = status.x.value;
float& y = status.y.value;
// Adjust if value is inverted
x = status.x.properties.inverted ? 1.0f + x : x;
y = status.y.properties.inverted ? 1.0f + y : y;
// clamp value
x = std::clamp(x, 0.0f, 1.0f);
y = std::clamp(y, 0.0f, 1.0f);
if (status.pressed.inverted) {
status.pressed.value = !status.pressed.value;
}
return status;
}
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) {
Common::Input::TriggerStatus status{};
float& raw_value = status.analog.raw_value;
bool calculate_button_value = true;
switch (callback.type) {
case Common::Input::InputType::Analog:
status.analog.properties = callback.analog_status.properties;
raw_value = callback.analog_status.raw_value;
break;
case Common::Input::InputType::Button:
status.analog.properties.range = 1.0f;
status.analog.properties.inverted = callback.button_status.inverted;
raw_value = callback.button_status.value ? 1.0f : 0.0f;
break;
case Common::Input::InputType::Trigger:
status = callback.trigger_status;
calculate_button_value = false;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
break;
}
SanitizeAnalog(status.analog, true);
const auto& properties = status.analog.properties;
float& value = status.analog.value;
// Set button status
if (calculate_button_value) {
status.pressed.value = value > properties.threshold;
}
// Adjust if value is inverted
value = properties.inverted ? 1.0f + value : value;
// clamp value
value = std::clamp(value, 0.0f, 1.0f);
return status;
}
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) {
Common::Input::AnalogStatus status{};
switch (callback.type) {
case Common::Input::InputType::Analog:
status.properties = callback.analog_status.properties;
status.raw_value = callback.analog_status.raw_value;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type);
break;
}
SanitizeAnalog(status, false);
// Adjust if value is inverted
status.value = status.properties.inverted ? -status.value : status.value;
return status;
}
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
const auto& properties = analog.properties;
float& raw_value = analog.raw_value;
float& value = analog.value;
if (!std::isnormal(raw_value)) {
raw_value = 0;
}
// Apply center offset
raw_value -= properties.offset;
// Set initial values to be formated
value = raw_value;
// Calculate vector size
const float r = std::abs(value);
// Return zero if value is smaller than the deadzone
if (r <= properties.deadzone || properties.deadzone == 1.0f) {
analog.value = 0;
return;
}
// Adjust range of value
const float deadzone_factor =
1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
value = value * deadzone_factor / properties.range;
// Invert direction if needed
if (properties.inverted) {
value = -value;
}
// Clamp value
if (clamp_value) {
value = std::clamp(value, -1.0f, 1.0f);
}
}
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
bool clamp_value) {
const auto& properties_x = analog_x.properties;
const auto& properties_y = analog_y.properties;
float& raw_x = analog_x.raw_value;
float& raw_y = analog_y.raw_value;
float& x = analog_x.value;
float& y = analog_y.value;
if (!std::isnormal(raw_x)) {
raw_x = 0;
}
if (!std::isnormal(raw_y)) {
raw_y = 0;
}
// Apply center offset
raw_x += properties_x.offset;
raw_y += properties_y.offset;
// Apply X scale correction from offset
if (std::abs(properties_x.offset) < 0.5f) {
if (raw_x > 0) {
raw_x /= 1 + properties_x.offset;
} else {
raw_x /= 1 - properties_x.offset;
}
}
// Apply Y scale correction from offset
if (std::abs(properties_y.offset) < 0.5f) {
if (raw_y > 0) {
raw_y /= 1 + properties_y.offset;
} else {
raw_y /= 1 - properties_y.offset;
}
}
// Invert direction if needed
raw_x = properties_x.inverted ? -raw_x : raw_x;
raw_y = properties_y.inverted ? -raw_y : raw_y;
// Set initial values to be formated
x = raw_x;
y = raw_y;
// Calculate vector size
float r = x * x + y * y;
r = std::sqrt(r);
// TODO(German77): Use deadzone and range of both axis
// Return zero if values are smaller than the deadzone
if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
x = 0;
y = 0;
return;
}
// Adjust range of joystick
const float deadzone_factor =
1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
x = x * deadzone_factor / properties_x.range;
y = y * deadzone_factor / properties_x.range;
r = r * deadzone_factor / properties_x.range;
// Normalize joystick
if (clamp_value && r > 1.0f) {
x /= r;
y /= r;
}
}
} // namespace Core::HID

View File

@@ -0,0 +1,95 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
namespace Common::Input {
struct CallbackStatus;
enum class BatteryLevel : u32;
using BatteryStatus = BatteryLevel;
struct AnalogStatus;
struct ButtonStatus;
struct MotionStatus;
struct StickStatus;
struct TouchStatus;
struct TriggerStatus;
}; // namespace Common::Input
namespace Core::HID {
/**
* Converts raw input data into a valid battery status.
*
* @param Supported callbacks: Analog, Battery, Trigger.
* @return A valid BatteryStatus object.
*/
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid button status. Applies invert properties to the output.
*
* @param Supported callbacks: Analog, Button, Trigger.
* @return A valid TouchStatus object.
*/
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid motion status.
*
* @param Supported callbacks: Motion.
* @return A valid TouchStatus object.
*/
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert
* properties to the output.
*
* @param Supported callbacks: Stick.
* @return A valid StickStatus object.
*/
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid touch status.
*
* @param Supported callbacks: Touch.
* @return A valid TouchStatus object.
*/
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid trigger status. Applies offset, deadzone, range and
* invert properties to the output. Button status uses the threshold property if necessary.
*
* @param Supported callbacks: Analog, Button, Trigger.
* @return A valid TriggerStatus object.
*/
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid analog status. Applies offset, deadzone, range and
* invert properties to the output.
*
* @param Supported callbacks: Analog.
* @return A valid AnalogStatus object.
*/
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
/**
* Converts raw analog data into a valid analog value
* @param An analog object containing raw data and properties, bool that determines if the value
* needs to be clamped between -1.0f and 1.0f.
*/
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value);
/**
* Converts raw stick data into a valid stick value
* @param Two analog objects containing raw data and properties, bool that determines if the value
* needs to be clamped into the unit circle.
*/
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
bool clamp_value);
} // namespace Core::HID

View File

@@ -3,7 +3,8 @@
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/frontend/input_interpreter.h"
#include "core/hid/hid_types.h"
#include "core/hid/input_interpreter.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
@@ -19,7 +20,7 @@ InputInterpreter::InputInterpreter(Core::System& system)
InputInterpreter::~InputInterpreter() = default;
void InputInterpreter::PollInput() {
const u32 button_state = npad.GetAndResetPressState();
const u64 button_state = npad.GetAndResetPressState();
previous_index = current_index;
current_index = (current_index + 1) % button_states.size();
@@ -31,32 +32,30 @@ void InputInterpreter::ResetButtonStates() {
previous_index = 0;
current_index = 0;
button_states[0] = 0xFFFFFFFF;
button_states[0] = 0xFFFFFFFFFFFFFFFF;
for (std::size_t i = 1; i < button_states.size(); ++i) {
button_states[i] = 0;
}
}
bool InputInterpreter::IsButtonPressed(HIDButton button) const {
return (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const {
return (button_states[current_index] & static_cast<u64>(button)) != 0;
}
bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
const bool current_press =
(button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
const bool previous_press =
(button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const {
const bool current_press = (button_states[current_index] & static_cast<u64>(button)) != 0;
const bool previous_press = (button_states[previous_index] & static_cast<u64>(button)) != 0;
return current_press && !previous_press;
}
bool InputInterpreter::IsButtonHeld(HIDButton button) const {
u32 held_buttons{button_states[0]};
bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const {
u64 held_buttons{button_states[0]};
for (std::size_t i = 1; i < button_states.size(); ++i) {
held_buttons &= button_states[i];
}
return (held_buttons & (1U << static_cast<u8>(button))) != 0;
return (held_buttons & static_cast<u64>(button)) != 0;
}

View File

@@ -12,46 +12,14 @@ namespace Core {
class System;
}
namespace Core::HID {
enum class NpadButton : u64;
}
namespace Service::HID {
class Controller_NPad;
}
enum class HIDButton : u8 {
A,
B,
X,
Y,
LStick,
RStick,
L,
R,
ZL,
ZR,
Plus,
Minus,
DLeft,
DUp,
DRight,
DDown,
LStickLeft,
LStickUp,
LStickRight,
LStickDown,
RStickLeft,
RStickUp,
RStickRight,
RStickDown,
LeftSL,
LeftSR,
RightSL,
RightSR,
};
/**
* The InputInterpreter class interfaces with HID to retrieve button press states.
* Input is intended to be polled every 50ms so that a button is considered to be
@@ -76,7 +44,7 @@ public:
*
* @returns True when the button is pressed.
*/
[[nodiscard]] bool IsButtonPressed(HIDButton button) const;
[[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed.
@@ -85,7 +53,7 @@ public:
*
* @returns True when at least one of the buttons is pressed.
*/
template <HIDButton... T>
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonPressed() {
return (IsButtonPressed(T) || ...);
}
@@ -98,7 +66,7 @@ public:
*
* @returns True when the button is pressed once.
*/
[[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
[[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed once.
@@ -107,7 +75,7 @@ public:
*
* @returns True when at least one of the buttons is pressed once.
*/
template <HIDButton... T>
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonPressedOnce() const {
return (IsButtonPressedOnce(T) || ...);
}
@@ -119,7 +87,7 @@ public:
*
* @returns True when the button is held down.
*/
[[nodiscard]] bool IsButtonHeld(HIDButton button) const;
[[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is held down.
@@ -128,7 +96,7 @@ public:
*
* @returns True when at least one of the buttons is held down.
*/
template <HIDButton... T>
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonHeld() const {
return (IsButtonHeld(T) || ...);
}
@@ -137,7 +105,7 @@ private:
Service::HID::Controller_NPad& npad;
/// Stores 9 consecutive button states polled from HID.
std::array<u32, 9> button_states{};
std::array<u64, 9> button_states{};
std::size_t previous_index{};
std::size_t current_index{};

View File

@@ -2,13 +2,21 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <random>
#include "common/math_util.h"
#include "input_common/motion_input.h"
#include "core/hid/motion_input.h"
namespace InputCommon {
namespace Core::HID {
MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
MotionInput::MotionInput() {
// Initialize PID constants with default values
SetPID(0.3f, 0.005f, 0.0f);
}
void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
kp = new_kp;
ki = new_ki;
kd = new_kd;
}
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
accel = acceleration;
@@ -65,6 +73,8 @@ void MotionInput::UpdateRotation(u64 elapsed_time) {
rotations += gyro * sample_period;
}
// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
// 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
void MotionInput::UpdateOrientation(u64 elapsed_time) {
if (!IsCalibrated(0.1f)) {
ResetOrientation();
@@ -190,43 +200,6 @@ Common::Vec3f MotionInput::GetRotations() const {
return rotations;
}
Input::MotionStatus MotionInput::GetMotion() const {
const Common::Vec3f gyroscope = GetGyroscope();
const Common::Vec3f accelerometer = GetAcceleration();
const Common::Vec3f rotation = GetRotations();
const std::array<Common::Vec3f, 3> orientation = GetOrientation();
const Common::Quaternion<f32> quaternion = GetQuaternion();
return {accelerometer, gyroscope, rotation, orientation, quaternion};
}
Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<s16> distribution(-1000, 1000);
const Common::Vec3f gyroscope{
static_cast<f32>(distribution(gen)) * 0.001f,
static_cast<f32>(distribution(gen)) * 0.001f,
static_cast<f32>(distribution(gen)) * 0.001f,
};
const Common::Vec3f accelerometer{
static_cast<f32>(distribution(gen)) * 0.001f,
static_cast<f32>(distribution(gen)) * 0.001f,
static_cast<f32>(distribution(gen)) * 0.001f,
};
constexpr Common::Vec3f rotation;
constexpr std::array orientation{
Common::Vec3f{1.0f, 0.0f, 0.0f},
Common::Vec3f{0.0f, 1.0f, 0.0f},
Common::Vec3f{0.0f, 0.0f, 1.0f},
};
constexpr Common::Quaternion<f32> quaternion{
{0.0f, 0.0f, 0.0f},
1.0f,
};
return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation,
quaternion};
}
void MotionInput::ResetOrientation() {
if (!reset_enabled || only_accelerometer) {
return;
@@ -304,4 +277,4 @@ void MotionInput::SetOrientationFromAccelerometer() {
quat = quat.Normalized();
}
}
} // namespace InputCommon
} // namespace Core::HID

View File

@@ -7,13 +7,12 @@
#include "common/common_types.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
#include "core/frontend/input.h"
namespace InputCommon {
namespace Core::HID {
class MotionInput {
public:
explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
explicit MotionInput();
MotionInput(const MotionInput&) = default;
MotionInput& operator=(const MotionInput&) = default;
@@ -21,6 +20,7 @@ public:
MotionInput(MotionInput&&) = default;
MotionInput& operator=(MotionInput&&) = default;
void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
void SetAcceleration(const Common::Vec3f& acceleration);
void SetGyroscope(const Common::Vec3f& gyroscope);
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
@@ -38,9 +38,6 @@ public:
[[nodiscard]] Common::Vec3f GetGyroscope() const;
[[nodiscard]] Common::Vec3f GetRotations() const;
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
[[nodiscard]] Input::MotionStatus GetMotion() const;
[[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
int gyro_magnitude) const;
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
@@ -59,16 +56,32 @@ private:
Common::Vec3f integral_error;
Common::Vec3f derivative_error;
// Quaternion containing the device orientation
Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
// Number of full rotations in each axis
Common::Vec3f rotations;
// Acceleration vector measurement in G force
Common::Vec3f accel;
// Gyroscope vector measurement in radians/s.
Common::Vec3f gyro;
// Vector to be substracted from gyro measurements
Common::Vec3f gyro_drift;
// Minimum gyro amplitude to detect if the device is moving
f32 gyro_threshold = 0.0f;
// Number of invalid sequential data
u32 reset_counter = 0;
// If the provided data is invalid the device will be autocalibrated
bool reset_enabled = true;
// Use accelerometer values to calculate position
bool only_accelerometer = true;
};
} // namespace InputCommon
} // namespace Core::HID

View File

@@ -685,8 +685,8 @@ ResultCode KPageTable::UnmapPages(VAddr addr, KPageLinkedList& page_linked_list,
return ResultSuccess;
}
ResultCode KPageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size,
KMemoryPermission perm) {
ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
KMemoryPermission perm) {
std::lock_guard lock{page_table_lock};

View File

@@ -41,7 +41,7 @@ public:
ResultCode MapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state,
KMemoryPermission perm);
ResultCode UnmapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state);
ResultCode SetCodeMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
ResultCode SetProcessMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
KMemoryInfo QueryInfo(VAddr addr);
ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
ResultCode ResetTransferMemory(VAddr addr, std::size_t size);

View File

@@ -528,7 +528,7 @@ void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
std::lock_guard lock{HLE::g_hle_lock};
const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
KMemoryPermission permission) {
page_table->SetCodeMemoryPermission(segment.addr + base_addr, segment.size, permission);
page_table->SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
};
kernel.System().Memory().WriteBlock(*this, base_addr, code_set.memory.data(),

View File

@@ -300,15 +300,16 @@ struct KernelCore::Impl {
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
KThread* GetHostDummyThread() {
auto make_thread = [this]() {
std::unique_ptr<KThread> thread = std::make_unique<KThread>(system.Kernel());
std::lock_guard lk(dummy_thread_lock);
auto& thread = dummy_threads.emplace_back(std::make_unique<KThread>(system.Kernel()));
KAutoObject::Create(thread.get());
ASSERT(KThread::InitializeDummyThread(thread.get()).IsSuccess());
thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
return thread;
return thread.get();
};
thread_local auto thread = make_thread();
return thread.get();
thread_local KThread* saved_thread = make_thread();
return saved_thread;
}
/// Registers a CPU core thread by allocating a host thread ID for it
@@ -695,6 +696,12 @@ struct KernelCore::Impl {
return port;
}
std::mutex server_ports_lock;
std::mutex server_sessions_lock;
std::mutex registered_objects_lock;
std::mutex registered_in_use_objects_lock;
std::mutex dummy_thread_lock;
std::atomic<u32> next_object_id{0};
std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
@@ -725,10 +732,6 @@ struct KernelCore::Impl {
std::unordered_set<KServerSession*> server_sessions;
std::unordered_set<KAutoObject*> registered_objects;
std::unordered_set<KAutoObject*> registered_in_use_objects;
std::mutex server_ports_lock;
std::mutex server_sessions_lock;
std::mutex registered_objects_lock;
std::mutex registered_in_use_objects_lock;
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
std::vector<Kernel::PhysicalCore> cores;
@@ -753,6 +756,9 @@ struct KernelCore::Impl {
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
// Specifically tracked to be automatically destroyed with kernel
std::vector<std::unique_ptr<KThread>> dummy_threads;
bool is_multicore{};
bool is_phantom_mode_for_singlecore{};
u32 single_core_thread_id{};

View File

@@ -1169,6 +1169,8 @@ static u32 GetCurrentProcessorNumber32(Core::System& system) {
return GetCurrentProcessorNumber(system);
}
namespace {
constexpr bool IsValidSharedMemoryPermission(Svc::MemoryPermission perm) {
switch (perm) {
case Svc::MemoryPermission::Read:
@@ -1179,10 +1181,24 @@ constexpr bool IsValidSharedMemoryPermission(Svc::MemoryPermission perm) {
}
}
constexpr bool IsValidRemoteSharedMemoryPermission(Svc::MemoryPermission perm) {
[[maybe_unused]] constexpr bool IsValidRemoteSharedMemoryPermission(Svc::MemoryPermission perm) {
return IsValidSharedMemoryPermission(perm) || perm == Svc::MemoryPermission::DontCare;
}
constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) {
switch (perm) {
case Svc::MemoryPermission::None:
case Svc::MemoryPermission::Read:
case Svc::MemoryPermission::ReadWrite:
case Svc::MemoryPermission::ReadExecute:
return true;
default:
return false;
}
}
} // Anonymous namespace
static ResultCode MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address,
u64 size, Svc::MemoryPermission map_perm) {
LOG_TRACE(Kernel_SVC,
@@ -1262,6 +1278,34 @@ static ResultCode UnmapSharedMemory32(Core::System& system, Handle shmem_handle,
return UnmapSharedMemory(system, shmem_handle, address, size);
}
static ResultCode SetProcessMemoryPermission(Core::System& system, Handle process_handle,
VAddr address, u64 size, Svc::MemoryPermission perm) {
LOG_TRACE(Kernel_SVC,
"called, process_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
process_handle, address, size, perm);
// Validate the address/size.
R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
R_UNLESS(size > 0, ResultInvalidSize);
R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
// Validate the memory permission.
R_UNLESS(IsValidProcessMemoryPermission(perm), ResultInvalidNewMemoryPermission);
// Get the process from its handle.
KScopedAutoObject process =
system.CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle);
R_UNLESS(process.IsNotNull(), ResultInvalidHandle);
// Validate that the address is in range.
auto& page_table = process->PageTable();
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
// Set the memory permission.
return page_table.SetProcessMemoryPermission(address, size, ConvertToKMemoryPermission(perm));
}
static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
VAddr page_info_address, Handle process_handle,
VAddr address) {
@@ -1459,10 +1503,14 @@ static void ExitProcess32(Core::System& system) {
ExitProcess(system);
}
static constexpr bool IsValidVirtualCoreId(int32_t core_id) {
namespace {
constexpr bool IsValidVirtualCoreId(int32_t core_id) {
return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES));
}
} // Anonymous namespace
/// Creates a new thread
static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg,
VAddr stack_bottom, u32 priority, s32 core_id) {
@@ -1846,7 +1894,9 @@ static ResultCode ResetSignal32(Core::System& system, Handle handle) {
return ResetSignal(system, handle);
}
static constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) {
namespace {
constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) {
switch (perm) {
case MemoryPermission::None:
case MemoryPermission::Read:
@@ -1857,6 +1907,8 @@ static constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) {
}
}
} // Anonymous namespace
/// Creates a TransferMemory object
static ResultCode CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size,
MemoryPermission map_perm) {
@@ -2588,7 +2640,7 @@ static const FunctionDef SVC_Table_64[] = {
{0x70, nullptr, "CreatePort"},
{0x71, nullptr, "ManageNamedPort"},
{0x72, nullptr, "ConnectToPort"},
{0x73, nullptr, "SetProcessMemoryPermission"},
{0x73, SvcWrap64<SetProcessMemoryPermission>, "SetProcessMemoryPermission"},
{0x74, nullptr, "MapProcessMemory"},
{0x75, nullptr, "UnmapProcessMemory"},
{0x76, SvcWrap64<QueryProcessMemory>, "QueryProcessMemory"},

View File

@@ -10,6 +10,9 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_controller.h"
@@ -25,7 +28,7 @@ namespace Service::AM::Applets {
static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
HID::Controller_NPad::NpadStyleSet npad_style_set;
Core::HID::NpadStyleTag npad_style_set;
npad_style_set.raw = private_arg.style_set;
return {
@@ -243,19 +246,11 @@ void Controller::Execute() {
void Controller::ConfigurationComplete() {
ControllerSupportResultInfo result_info{};
const auto& players = Settings::values.players.GetValue();
// If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
// Otherwise, only count connected players from P1-P8.
result_info.player_count =
is_single_mode
? 1
: static_cast<s8>(std::count_if(players.begin(), players.end() - 2,
[](const auto& player) { return player.connected; }));
result_info.player_count = is_single_mode ? 1 : system.HIDCore().GetPlayerCount();
result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance(
players.begin(), std::find_if(players.begin(), players.end(),
[](const auto& player) { return player.connected; })));
result_info.selected_id = static_cast<u32>(system.HIDCore().GetFirstNpadId());
result_info.result = 0;

View File

@@ -16,6 +16,10 @@ namespace Core {
class System;
}
namespace Core::HID {
enum class NpadStyleSet : u32;
}
namespace Service::AM::Applets {
using IdentificationColor = std::array<u8, 4>;
@@ -52,7 +56,7 @@ struct ControllerSupportArgPrivate {
bool flag_1{};
ControllerSupportMode mode{};
ControllerSupportCaller caller{};
u32 style_set{};
Core::HID::NpadStyleSet style_set{};
u32 joy_hold_type{};
};
static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,

View File

@@ -231,7 +231,7 @@ void AppletManager::SetDefaultAppletFrontendSet() {
void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.controller == nullptr) {
frontend.controller =
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.HIDCore());
}
if (frontend.error == nullptr) {

View File

@@ -4,13 +4,18 @@
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hid/emulated_console.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/console_sixaxis.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200;
Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::System& system_)
: ControllerBase{system_} {}
Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_)
: ControllerBase{hid_core_} {
console = hid_core.GetEmulatedConsole();
}
Controller_ConsoleSixAxis::~Controller_ConsoleSixAxis() = default;
void Controller_ConsoleSixAxis::OnInit() {}
@@ -19,44 +24,31 @@ void Controller_ConsoleSixAxis::OnRelease() {}
void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
seven_six_axis.header.timestamp = core_timing.GetCPUTicks();
seven_six_axis.header.total_entry_count = 17;
if (!IsControllerActivated() || !is_transfer_memory_set) {
seven_six_axis.header.entry_count = 0;
seven_six_axis.header.last_entry_index = 0;
seven_sixaxis_lifo.buffer_count = 0;
seven_sixaxis_lifo.buffer_tail = 0;
return;
}
seven_six_axis.header.entry_count = 16;
const auto& last_entry =
seven_six_axis.sevensixaxis_states[seven_six_axis.header.last_entry_index];
seven_six_axis.header.last_entry_index = (seven_six_axis.header.last_entry_index + 1) % 17;
auto& cur_entry = seven_six_axis.sevensixaxis_states[seven_six_axis.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
const auto& last_entry = seven_sixaxis_lifo.ReadCurrentEntry().state;
next_seven_sixaxis_state.sampling_number = last_entry.sampling_number + 1;
// Try to read sixaxis sensor states
MotionDevice motion_device{};
const auto& device = motions[0];
if (device) {
std::tie(motion_device.accel, motion_device.gyro, motion_device.rotation,
motion_device.orientation, motion_device.quaternion) = device->GetStatus();
console_six_axis.is_seven_six_axis_sensor_at_rest = motion_device.gyro.Length2() < 0.0001f;
}
const auto motion_status = console->GetMotion();
cur_entry.accel = motion_device.accel;
console_six_axis.is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest;
next_seven_sixaxis_state.accel = motion_status.accel;
// Zero gyro values as they just mess up with the camera
// Note: Probably a correct sensivity setting must be set
cur_entry.gyro = {};
cur_entry.quaternion = {
next_seven_sixaxis_state.gyro = {};
next_seven_sixaxis_state.quaternion = {
{
motion_device.quaternion.xyz.y,
motion_device.quaternion.xyz.x,
-motion_device.quaternion.w,
motion_status.quaternion.xyz.y,
motion_status.quaternion.xyz.x,
-motion_status.quaternion.w,
},
-motion_device.quaternion.xyz.z,
-motion_status.quaternion.xyz.z,
};
console_six_axis.sampling_number++;
@@ -67,14 +59,8 @@ void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_ti
// Update console six axis shared memory
std::memcpy(data + SHARED_MEMORY_OFFSET, &console_six_axis, sizeof(console_six_axis));
// Update seven six axis transfer memory
std::memcpy(transfer_memory, &seven_six_axis, sizeof(seven_six_axis));
}
void Controller_ConsoleSixAxis::OnLoadInputDevices() {
const auto player = Settings::values.players.GetValue()[0];
std::transform(player.motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
player.motions.begin() + Settings::NativeMotion::MOTION_HID_END, motions.begin(),
Input::CreateDevice<Input::MotionDevice>);
seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state);
std::memcpy(transfer_memory, &seven_sixaxis_lifo, sizeof(seven_sixaxis_lifo));
}
void Controller_ConsoleSixAxis::SetTransferMemoryPointer(u8* t_mem) {
@@ -83,8 +69,7 @@ void Controller_ConsoleSixAxis::SetTransferMemoryPointer(u8* t_mem) {
}
void Controller_ConsoleSixAxis::ResetTimestamp() {
auto& cur_entry = seven_six_axis.sevensixaxis_states[seven_six_axis.header.last_entry_index];
cur_entry.sampling_number = 0;
cur_entry.sampling_number2 = 0;
seven_sixaxis_lifo.buffer_count = 0;
seven_sixaxis_lifo.buffer_tail = 0;
}
} // namespace Service::HID

View File

@@ -5,16 +5,21 @@
#pragma once
#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/quaternion.h"
#include "core/frontend/input.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedConsole;
} // namespace Core::HID
namespace Service::HID {
class Controller_ConsoleSixAxis final : public ControllerBase {
public:
explicit Controller_ConsoleSixAxis(Core::System& system_);
explicit Controller_ConsoleSixAxis(Core::HID::HIDCore& hid_core_);
~Controller_ConsoleSixAxis() override;
// Called when the controller is initialized
@@ -26,9 +31,6 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
// Called on InitializeSevenSixAxisSensor
void SetTransferMemoryPointer(u8* t_mem);
@@ -38,43 +40,31 @@ public:
private:
struct SevenSixAxisState {
INSERT_PADDING_WORDS(4); // unused
s64_le sampling_number{};
s64_le sampling_number2{};
s64 sampling_number{};
u64 unknown{};
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Quaternion<f32> quaternion{};
};
static_assert(sizeof(SevenSixAxisState) == 0x50, "SevenSixAxisState is an invalid size");
struct SevenSixAxisMemory {
CommonHeader header{};
std::array<SevenSixAxisState, 0x21> sevensixaxis_states{};
};
static_assert(sizeof(SevenSixAxisMemory) == 0xA70, "SevenSixAxisMemory is an invalid size");
static_assert(sizeof(SevenSixAxisState) == 0x48, "SevenSixAxisState is an invalid size");
// This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
struct ConsoleSharedMemory {
u64_le sampling_number{};
u64 sampling_number{};
bool is_seven_six_axis_sensor_at_rest{};
INSERT_PADDING_BYTES(4); // padding
f32 verticalization_error{};
Common::Vec3f gyro_bias{};
};
static_assert(sizeof(ConsoleSharedMemory) == 0x20, "ConsoleSharedMemory is an invalid size");
struct MotionDevice {
Common::Vec3f accel;
Common::Vec3f gyro;
Common::Vec3f rotation;
std::array<Common::Vec3f, 3> orientation;
Common::Quaternion<f32> quaternion;
};
Lifo<SevenSixAxisState, 0x21> seven_sixaxis_lifo{};
static_assert(sizeof(seven_sixaxis_lifo) == 0xA70, "SevenSixAxisState is an invalid size");
using MotionArray =
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>;
MotionArray motions;
Core::HID::EmulatedConsole* console;
u8* transfer_memory = nullptr;
bool is_transfer_memory_set = false;
ConsoleSharedMemory console_six_axis{};
SevenSixAxisMemory seven_six_axis{};
SevenSixAxisState next_seven_sixaxis_state{};
};
} // namespace Service::HID

View File

@@ -6,12 +6,12 @@
namespace Service::HID {
ControllerBase::ControllerBase(Core::System& system_) : system(system_) {}
ControllerBase::ControllerBase(Core::HID::HIDCore& hid_core_) : hid_core(hid_core_) {}
ControllerBase::~ControllerBase() = default;
void ControllerBase::ActivateController() {
if (is_activated) {
OnRelease();
return;
}
is_activated = true;
OnInit();

View File

@@ -11,14 +11,14 @@ namespace Core::Timing {
class CoreTiming;
}
namespace Core {
class System;
namespace Core::HID {
class HIDCore;
}
namespace Service::HID {
class ControllerBase {
public:
explicit ControllerBase(Core::System& system_);
explicit ControllerBase(Core::HID::HIDCore& hid_core_);
virtual ~ControllerBase();
// Called when the controller is initialized
@@ -35,26 +35,17 @@ public:
virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {}
// Called when input devices should be loaded
virtual void OnLoadInputDevices() = 0;
void ActivateController();
void DeactivateController();
bool IsControllerActivated() const;
static const std::size_t hid_entry_count = 17;
protected:
bool is_activated{false};
struct CommonHeader {
s64_le timestamp;
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
Core::System& system;
Core::HID::HIDCore& hid_core;
};
} // namespace Service::HID

View File

@@ -6,15 +6,19 @@
#include "common/common_types.h"
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/debug_pad.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x00000;
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
Controller_DebugPad::Controller_DebugPad(Core::HID::HIDCore& hid_core_)
: ControllerBase{hid_core_} {
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
}
Controller_DebugPad::Controller_DebugPad(Core::System& system_) : ControllerBase{system_} {}
Controller_DebugPad::~Controller_DebugPad() = default;
void Controller_DebugPad::OnInit() {}
@@ -23,63 +27,29 @@ void Controller_DebugPad::OnRelease() {}
void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
debug_pad_lifo.buffer_count = 0;
debug_pad_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo));
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
const auto& last_entry = debug_pad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.debug_pad_enabled) {
cur_entry.attribute.connected.Assign(1);
auto& pad = cur_entry.pad_state;
next_state.attribute.connected.Assign(1);
using namespace Settings::NativeButton;
pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
const auto& button_state = controller->GetDebugPadButtons();
const auto& stick_state = controller->GetSticks();
const auto [stick_l_x_f, stick_l_y_f] =
analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] =
analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
next_state.pad_state = button_state;
next_state.l_stick = stick_state.left;
next_state.r_stick = stick_state.right;
}
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
debug_pad_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo));
}
void Controller_DebugPad::OnLoadInputDevices() {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.begin() +
Settings::NativeButton::NUM_BUTTONS_HID,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.debug_pad_analogs.begin(),
Settings::values.debug_pad_analogs.end(), analogs.begin(),
Input::CreateDevice<Input::AnalogDevice>);
}
} // namespace Service::HID

View File

@@ -8,15 +8,20 @@
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedController;
struct DebugPadButton;
struct AnalogStickState;
} // namespace Core::HID
namespace Service::HID {
class Controller_DebugPad final : public ControllerBase {
public:
explicit Controller_DebugPad(Core::System& system_);
explicit Controller_DebugPad(Core::HID::HIDCore& hid_core_);
~Controller_DebugPad() override;
// Called when the controller is initialized
@@ -28,66 +33,31 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct AnalogStick {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogStick) == 0x8);
struct PadState {
// This is nn::hid::DebugPadAttribute
struct DebugPadAttribute {
union {
u32_le raw{};
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
BitField<2, 1, u32> x;
BitField<3, 1, u32> y;
BitField<4, 1, u32> l;
BitField<5, 1, u32> r;
BitField<6, 1, u32> zl;
BitField<7, 1, u32> zr;
BitField<8, 1, u32> plus;
BitField<9, 1, u32> minus;
BitField<10, 1, u32> d_left;
BitField<11, 1, u32> d_up;
BitField<12, 1, u32> d_right;
BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
struct Attributes {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
static_assert(sizeof(DebugPadAttribute) == 0x4, "DebugPadAttribute is an invalid size");
struct PadStates {
s64_le sampling_number;
s64_le sampling_number2;
Attributes attribute;
PadState pad_state;
AnalogStick r_stick;
AnalogStick l_stick;
// This is nn::hid::DebugPadState
struct DebugPadState {
s64 sampling_number;
DebugPadAttribute attribute;
Core::HID::DebugPadButton pad_state;
Core::HID::AnalogStickState r_stick;
Core::HID::AnalogStickState l_stick;
};
static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state");
static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state");
struct SharedMemory {
CommonHeader header;
std::array<PadStates, 17> pad_states;
INSERT_PADDING_BYTES(0x138);
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
// This is nn::hid::detail::DebugPadLifo
Lifo<DebugPadState, hid_entry_count> debug_pad_lifo{};
static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size");
DebugPadState next_state{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>
analogs;
Core::HID::EmulatedController* controller;
};
} // namespace Service::HID

View File

@@ -7,6 +7,7 @@
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/gesture.h"
namespace Service::HID {
@@ -23,16 +24,14 @@ constexpr f32 Square(s32 num) {
return static_cast<f32>(num * num);
}
Controller_Gesture::Controller_Gesture(Core::System& system_) : ControllerBase(system_) {}
Controller_Gesture::Controller_Gesture(Core::HID::HIDCore& hid_core_) : ControllerBase(hid_core_) {
console = hid_core.GetEmulatedConsole();
}
Controller_Gesture::~Controller_Gesture() = default;
void Controller_Gesture::OnInit() {
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
mouse_finger_id[id] = MAX_POINTS;
keyboard_finger_id[id] = MAX_POINTS;
udp_finger_id[id] = MAX_POINTS;
}
shared_memory.header.entry_count = 0;
gesture_lifo.buffer_count = 0;
gesture_lifo.buffer_tail = 0;
force_update = true;
}
@@ -40,50 +39,38 @@ void Controller_Gesture::OnRelease() {}
void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
gesture_lifo.buffer_count = 0;
gesture_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo));
return;
}
ReadTouchInput();
GestureProperties gesture = GetGestureProperties();
f32 time_difference = static_cast<f32>(shared_memory.header.timestamp - last_update_timestamp) /
(1000 * 1000 * 1000);
f32 time_difference =
static_cast<f32>(gesture_lifo.timestamp - last_update_timestamp) / (1000 * 1000 * 1000);
// Only update if necesary
if (!ShouldUpdateGesture(gesture, time_difference)) {
return;
}
last_update_timestamp = shared_memory.header.timestamp;
last_update_timestamp = gesture_lifo.timestamp;
UpdateGestureSharedMemory(data, size, gesture, time_difference);
}
void Controller_Gesture::ReadTouchInput() {
const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus();
const Input::TouchStatus& udp_status = touch_udp_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]);
udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]);
}
if (Settings::values.use_touch_from_button) {
const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
keyboard_finger_id[id] =
UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]);
}
const auto touch_status = console->GetTouch();
for (std::size_t id = 0; id < fingers.size(); ++id) {
fingers[id] = touch_status[id];
}
}
bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
f32 time_difference) {
const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = GetLastGestureEntry();
if (force_update) {
force_update = false;
return true;
@@ -97,7 +84,7 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
}
// Update on press and hold event after 0.5 seconds
if (last_entry.type == TouchType::Touch && last_entry.point_count == 1 &&
if (last_entry.type == GestureType::Touch && last_entry.point_count == 1 &&
time_difference > press_delay) {
return enable_press_and_tap;
}
@@ -108,27 +95,19 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size,
GestureProperties& gesture,
f32 time_difference) {
TouchType type = TouchType::Idle;
Attribute attributes{};
GestureType type = GestureType::Idle;
GestureAttribute attributes{};
const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = gesture_lifo.ReadCurrentEntry().state;
if (shared_memory.header.entry_count < 16) {
shared_memory.header.entry_count++;
}
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// Reset values to default
cur_entry.delta = {};
cur_entry.vel_x = 0;
cur_entry.vel_y = 0;
cur_entry.direction = Direction::None;
cur_entry.rotation_angle = 0;
cur_entry.scale = 0;
// Reset next state to default
next_state.sampling_number = last_entry.sampling_number + 1;
next_state.delta = {};
next_state.vel_x = 0;
next_state.vel_y = 0;
next_state.direction = GestureDirection::None;
next_state.rotation_angle = 0;
next_state.scale = 0;
if (gesture.active_points > 0) {
if (last_gesture.active_points == 0) {
@@ -141,46 +120,47 @@ void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size,
}
// Apply attributes
cur_entry.detection_count = gesture.detection_count;
cur_entry.type = type;
cur_entry.attributes = attributes;
cur_entry.pos = gesture.mid_point;
cur_entry.point_count = static_cast<s32>(gesture.active_points);
cur_entry.points = gesture.points;
next_state.detection_count = gesture.detection_count;
next_state.type = type;
next_state.attributes = attributes;
next_state.pos = gesture.mid_point;
next_state.point_count = static_cast<s32>(gesture.active_points);
next_state.points = gesture.points;
last_gesture = gesture;
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
gesture_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo));
}
void Controller_Gesture::NewGesture(GestureProperties& gesture, TouchType& type,
Attribute& attributes) {
void Controller_Gesture::NewGesture(GestureProperties& gesture, GestureType& type,
GestureAttribute& attributes) {
const auto& last_entry = GetLastGestureEntry();
gesture.detection_count++;
type = TouchType::Touch;
type = GestureType::Touch;
// New touch after cancel is not considered new
if (last_entry.type != TouchType::Cancel) {
if (last_entry.type != GestureType::Cancel) {
attributes.is_new_touch.Assign(1);
enable_press_and_tap = true;
}
}
void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, TouchType& type,
void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, GestureType& type,
f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
// Promote to pan type if touch moved
for (size_t id = 0; id < MAX_POINTS; id++) {
if (gesture.points[id] != last_gesture.points[id]) {
type = TouchType::Pan;
type = GestureType::Pan;
break;
}
}
// Number of fingers changed cancel the last event and clear data
if (gesture.active_points != last_gesture.active_points) {
type = TouchType::Cancel;
type = GestureType::Cancel;
enable_press_and_tap = false;
gesture.active_points = 0;
gesture.mid_point = {};
@@ -189,41 +169,41 @@ void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, Touch
}
// Calculate extra parameters of panning
if (type == TouchType::Pan) {
if (type == GestureType::Pan) {
UpdatePanEvent(gesture, last_gesture, type, time_difference);
return;
}
// Promote to press type
if (last_entry.type == TouchType::Touch) {
type = TouchType::Press;
if (last_entry.type == GestureType::Touch) {
type = GestureType::Press;
}
}
void Controller_Gesture::EndGesture(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
Attribute& attributes, f32 time_difference) {
GestureProperties& last_gesture_props, GestureType& type,
GestureAttribute& attributes, f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
if (last_gesture_props.active_points != 0) {
switch (last_entry.type) {
case TouchType::Touch:
case GestureType::Touch:
if (enable_press_and_tap) {
SetTapEvent(gesture, last_gesture_props, type, attributes);
return;
}
type = TouchType::Cancel;
type = GestureType::Cancel;
force_update = true;
break;
case TouchType::Press:
case TouchType::Tap:
case TouchType::Swipe:
case TouchType::Pinch:
case TouchType::Rotate:
type = TouchType::Complete;
case GestureType::Press:
case GestureType::Tap:
case GestureType::Swipe:
case GestureType::Pinch:
case GestureType::Rotate:
type = GestureType::Complete;
force_update = true;
break;
case TouchType::Pan:
case GestureType::Pan:
EndPanEvent(gesture, last_gesture_props, type, time_difference);
break;
default:
@@ -231,15 +211,15 @@ void Controller_Gesture::EndGesture(GestureProperties& gesture,
}
return;
}
if (last_entry.type == TouchType::Complete || last_entry.type == TouchType::Cancel) {
if (last_entry.type == GestureType::Complete || last_entry.type == GestureType::Cancel) {
gesture.detection_count++;
}
}
void Controller_Gesture::SetTapEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
Attribute& attributes) {
type = TouchType::Tap;
GestureProperties& last_gesture_props, GestureType& type,
GestureAttribute& attributes) {
type = GestureType::Tap;
gesture = last_gesture_props;
force_update = true;
f32 tap_time_difference =
@@ -251,44 +231,42 @@ void Controller_Gesture::SetTapEvent(GestureProperties& gesture,
}
void Controller_Gesture::UpdatePanEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
GestureProperties& last_gesture_props, GestureType& type,
f32 time_difference) {
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = GetLastGestureEntry();
cur_entry.delta = gesture.mid_point - last_entry.pos;
cur_entry.vel_x = static_cast<f32>(cur_entry.delta.x) / time_difference;
cur_entry.vel_y = static_cast<f32>(cur_entry.delta.y) / time_difference;
next_state.delta = gesture.mid_point - last_entry.pos;
next_state.vel_x = static_cast<f32>(next_state.delta.x) / time_difference;
next_state.vel_y = static_cast<f32>(next_state.delta.y) / time_difference;
last_pan_time_difference = time_difference;
// Promote to pinch type
if (std::abs(gesture.average_distance - last_gesture_props.average_distance) >
pinch_threshold) {
type = TouchType::Pinch;
cur_entry.scale = gesture.average_distance / last_gesture_props.average_distance;
type = GestureType::Pinch;
next_state.scale = gesture.average_distance / last_gesture_props.average_distance;
}
const f32 angle_between_two_lines = std::atan((gesture.angle - last_gesture_props.angle) /
(1 + (gesture.angle * last_gesture_props.angle)));
// Promote to rotate type
if (std::abs(angle_between_two_lines) > angle_threshold) {
type = TouchType::Rotate;
cur_entry.scale = 0;
cur_entry.rotation_angle = angle_between_two_lines * 180.0f / Common::PI;
type = GestureType::Rotate;
next_state.scale = 0;
next_state.rotation_angle = angle_between_two_lines * 180.0f / Common::PI;
}
}
void Controller_Gesture::EndPanEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
GestureProperties& last_gesture_props, GestureType& type,
f32 time_difference) {
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = GetLastGestureEntry();
cur_entry.vel_x =
next_state.vel_x =
static_cast<f32>(last_entry.delta.x) / (last_pan_time_difference + time_difference);
cur_entry.vel_y =
next_state.vel_y =
static_cast<f32>(last_entry.delta.y) / (last_pan_time_difference + time_difference);
const f32 curr_vel =
std::sqrt((cur_entry.vel_x * cur_entry.vel_x) + (cur_entry.vel_y * cur_entry.vel_y));
std::sqrt((next_state.vel_x * next_state.vel_x) + (next_state.vel_y * next_state.vel_y));
// Set swipe event with parameters
if (curr_vel > swipe_threshold) {
@@ -297,105 +275,50 @@ void Controller_Gesture::EndPanEvent(GestureProperties& gesture,
}
// End panning without swipe
type = TouchType::Complete;
cur_entry.vel_x = 0;
cur_entry.vel_y = 0;
type = GestureType::Complete;
next_state.vel_x = 0;
next_state.vel_y = 0;
force_update = true;
}
void Controller_Gesture::SetSwipeEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type) {
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
GestureProperties& last_gesture_props, GestureType& type) {
const auto& last_entry = GetLastGestureEntry();
type = TouchType::Swipe;
type = GestureType::Swipe;
gesture = last_gesture_props;
force_update = true;
cur_entry.delta = last_entry.delta;
next_state.delta = last_entry.delta;
if (std::abs(cur_entry.delta.x) > std::abs(cur_entry.delta.y)) {
if (cur_entry.delta.x > 0) {
cur_entry.direction = Direction::Right;
if (std::abs(next_state.delta.x) > std::abs(next_state.delta.y)) {
if (next_state.delta.x > 0) {
next_state.direction = GestureDirection::Right;
return;
}
cur_entry.direction = Direction::Left;
next_state.direction = GestureDirection::Left;
return;
}
if (cur_entry.delta.y > 0) {
cur_entry.direction = Direction::Down;
if (next_state.delta.y > 0) {
next_state.direction = GestureDirection::Down;
return;
}
cur_entry.direction = Direction::Up;
}
void Controller_Gesture::OnLoadInputDevices() {
touch_mouse_device = Input::CreateDevice<Input::TouchDevice>("engine:emu_window");
touch_udp_device = Input::CreateDevice<Input::TouchDevice>("engine:cemuhookudp");
touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
}
std::optional<std::size_t> Controller_Gesture::GetUnusedFingerID() const {
// Dont assign any touch input to a point if disabled
if (!Settings::values.touchscreen.enabled) {
return std::nullopt;
}
std::size_t first_free_id = 0;
while (first_free_id < MAX_POINTS) {
if (!fingers[first_free_id].pressed) {
return first_free_id;
} else {
first_free_id++;
}
}
return std::nullopt;
}
Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() {
return shared_memory.gesture_states[(shared_memory.header.last_entry_index + 16) % 17];
next_state.direction = GestureDirection::Up;
}
const Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() const {
return shared_memory.gesture_states[(shared_memory.header.last_entry_index + 16) % 17];
}
std::size_t Controller_Gesture::UpdateTouchInputEvent(
const std::tuple<float, float, bool>& touch_input, std::size_t finger_id) {
const auto& [x, y, pressed] = touch_input;
if (finger_id > MAX_POINTS) {
LOG_ERROR(Service_HID, "Invalid finger id {}", finger_id);
return MAX_POINTS;
}
if (pressed) {
if (finger_id == MAX_POINTS) {
const auto first_free_id = GetUnusedFingerID();
if (!first_free_id) {
// Invalid finger id do nothing
return MAX_POINTS;
}
finger_id = first_free_id.value();
fingers[finger_id].pressed = true;
}
fingers[finger_id].pos = {x, y};
return finger_id;
}
if (finger_id != MAX_POINTS) {
fingers[finger_id].pressed = false;
}
return MAX_POINTS;
return gesture_lifo.ReadCurrentEntry().state;
}
Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() {
GestureProperties gesture;
std::array<Finger, MAX_POINTS> active_fingers;
std::array<Core::HID::TouchFinger, MAX_POINTS> active_fingers;
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
[](const auto& finger) { return finger.pressed; });
gesture.active_points =
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
for (size_t id = 0; id < gesture.active_points; ++id) {
const auto& [active_x, active_y] = active_fingers[id].pos;
const auto& [active_x, active_y] = active_fingers[id].position;
gesture.points[id] = {
.x = static_cast<s32>(active_x * Layout::ScreenUndocked::Width),
.y = static_cast<s32>(active_y * Layout::ScreenUndocked::Height),

View File

@@ -8,13 +8,14 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/point.h"
#include "core/frontend/input.h"
#include "core/hid/emulated_console.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
class Controller_Gesture final : public ControllerBase {
public:
explicit Controller_Gesture(Core::System& system_);
explicit Controller_Gesture(Core::HID::HIDCore& hid_core_);
~Controller_Gesture() override;
// Called when the controller is initialized
@@ -26,14 +27,12 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
static constexpr size_t MAX_FINGERS = 16;
static constexpr size_t MAX_POINTS = 4;
enum class TouchType : u32 {
// This is nn::hid::GestureType
enum class GestureType : u32 {
Idle, // Nothing touching the screen
Complete, // Set at the end of a touch event
Cancel, // Set when the number of fingers change
@@ -46,7 +45,8 @@ private:
Rotate, // All points rotating from the midpoint
};
enum class Direction : u32 {
// This is nn::hid::GestureDirection
enum class GestureDirection : u32 {
None,
Left,
Up,
@@ -54,51 +54,41 @@ private:
Down,
};
struct Attribute {
// This is nn::hid::GestureAttribute
struct GestureAttribute {
union {
u32_le raw{};
u32 raw{};
BitField<4, 1, u32> is_new_touch;
BitField<8, 1, u32> is_double_tap;
};
};
static_assert(sizeof(Attribute) == 4, "Attribute is an invalid size");
static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size");
// This is nn::hid::GestureState
struct GestureState {
s64_le sampling_number;
s64_le sampling_number2;
s64_le detection_count;
TouchType type;
Direction direction;
Common::Point<s32_le> pos;
Common::Point<s32_le> delta;
s64 sampling_number;
s64 detection_count;
GestureType type;
GestureDirection direction;
Common::Point<s32> pos;
Common::Point<s32> delta;
f32 vel_x;
f32 vel_y;
Attribute attributes;
GestureAttribute attributes;
f32 scale;
f32 rotation_angle;
s32_le point_count;
std::array<Common::Point<s32_le>, 4> points;
};
static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<GestureState, 17> gesture_states;
};
static_assert(sizeof(SharedMemory) == 0x708, "SharedMemory is an invalid size");
struct Finger {
Common::Point<f32> pos{};
bool pressed{};
s32 point_count;
std::array<Common::Point<s32>, 4> points;
};
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size");
struct GestureProperties {
std::array<Common::Point<s32_le>, MAX_POINTS> points{};
std::array<Common::Point<s32>, MAX_POINTS> points{};
std::size_t active_points{};
Common::Point<s32_le> mid_point{};
s64_le detection_count{};
u64_le delta_time{};
Common::Point<s32> mid_point{};
s64 detection_count{};
u64 delta_time{};
f32 average_distance{};
f32 angle{};
};
@@ -114,61 +104,48 @@ private:
f32 time_difference);
// Initializes new gesture
void NewGesture(GestureProperties& gesture, TouchType& type, Attribute& attributes);
void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes);
// Updates existing gesture state
void UpdateExistingGesture(GestureProperties& gesture, TouchType& type, f32 time_difference);
void UpdateExistingGesture(GestureProperties& gesture, GestureType& type, f32 time_difference);
// Terminates exiting gesture
void EndGesture(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, Attribute& attributes, f32 time_difference);
GestureType& type, GestureAttribute& attributes, f32 time_difference);
// Set current event to a tap event
void SetTapEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, Attribute& attributes);
GestureType& type, GestureAttribute& attributes);
// Calculates and set the extra parameters related to a pan event
void UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, f32 time_difference);
GestureType& type, f32 time_difference);
// Terminates the pan event
void EndPanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, f32 time_difference);
GestureType& type, f32 time_difference);
// Set current event to a swipe event
void SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type);
// Returns an unused finger id, if there is no fingers available std::nullopt is returned.
[[nodiscard]] std::optional<size_t> GetUnusedFingerID() const;
GestureType& type);
// Retrieves the last gesture entry, as indicated by shared memory indices.
[[nodiscard]] GestureState& GetLastGestureEntry();
[[nodiscard]] const GestureState& GetLastGestureEntry() const;
/**
* If the touch is new it tries to assign a new finger id, if there is no fingers available no
* changes will be made. Updates the coordinates if the finger id it's already set. If the touch
* ends delays the output by one frame to set the end_touch flag before finally freeing the
* finger id
*/
size_t UpdateTouchInputEvent(const std::tuple<float, float, bool>& touch_input,
size_t finger_id);
// Returns the average distance, angle and middle point of the active fingers
GestureProperties GetGestureProperties();
SharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_mouse_device;
std::unique_ptr<Input::TouchDevice> touch_udp_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device;
std::array<size_t, MAX_FINGERS> mouse_finger_id{};
std::array<size_t, MAX_FINGERS> keyboard_finger_id{};
std::array<size_t, MAX_FINGERS> udp_finger_id{};
std::array<Finger, MAX_POINTS> fingers{};
// This is nn::hid::detail::GestureLifo
Lifo<GestureState, hid_entry_count> gesture_lifo{};
static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size");
GestureState next_state{};
Core::HID::EmulatedConsole* console;
std::array<Core::HID::TouchFinger, MAX_POINTS> fingers{};
GestureProperties last_gesture{};
s64_le last_update_timestamp{};
s64_le last_tap_timestamp{};
s64 last_update_timestamp{};
s64 last_tap_timestamp{};
f32 last_pan_time_difference{};
bool force_update{false};
bool enable_press_and_tap{false};

View File

@@ -6,13 +6,18 @@
#include "common/common_types.h"
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/keyboard.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
constexpr u8 KEYS_PER_BYTE = 8;
Controller_Keyboard::Controller_Keyboard(Core::System& system_) : ControllerBase{system_} {}
Controller_Keyboard::Controller_Keyboard(Core::HID::HIDCore& hid_core_)
: ControllerBase{hid_core_} {
emulated_devices = hid_core.GetEmulatedDevices();
}
Controller_Keyboard::~Controller_Keyboard() = default;
void Controller_Keyboard::OnInit() {}
@@ -21,51 +26,27 @@ void Controller_Keyboard::OnRelease() {}
void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
keyboard_lifo.buffer_count = 0;
keyboard_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo));
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
const auto& last_entry = keyboard_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
cur_entry.key.fill(0);
if (Settings::values.keyboard_enabled) {
for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
auto& entry = cur_entry.key[i / KEYS_PER_BYTE];
entry = static_cast<u8>(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE)));
}
const auto& keyboard_state = emulated_devices->GetKeyboard();
const auto& keyboard_modifier_state = emulated_devices->GetKeyboardModifier();
using namespace Settings::NativeKeyboard;
// TODO: Assign the correct key to all modifiers
cur_entry.modifier.control.Assign(keyboard_mods[LeftControl]->GetStatus());
cur_entry.modifier.shift.Assign(keyboard_mods[LeftShift]->GetStatus());
cur_entry.modifier.left_alt.Assign(keyboard_mods[LeftAlt]->GetStatus());
cur_entry.modifier.right_alt.Assign(keyboard_mods[RightAlt]->GetStatus());
cur_entry.modifier.gui.Assign(0);
cur_entry.modifier.caps_lock.Assign(keyboard_mods[CapsLock]->GetStatus());
cur_entry.modifier.scroll_lock.Assign(keyboard_mods[ScrollLock]->GetStatus());
cur_entry.modifier.num_lock.Assign(keyboard_mods[NumLock]->GetStatus());
cur_entry.modifier.katakana.Assign(0);
cur_entry.modifier.hiragana.Assign(0);
next_state.key = keyboard_state;
next_state.modifier = keyboard_modifier_state;
next_state.attribute.is_connected.Assign(1);
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
keyboard_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo));
}
void Controller_Keyboard::OnLoadInputDevices() {
std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(),
keyboard_keys.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(),
keyboard_mods.begin(), Input::CreateDevice<Input::ButtonDevice>);
}
} // namespace Service::HID

View File

@@ -8,15 +8,20 @@
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedDevices;
struct KeyboardModifier;
struct KeyboardKey;
} // namespace Core::HID
namespace Service::HID {
class Controller_Keyboard final : public ControllerBase {
public:
explicit Controller_Keyboard(Core::System& system_);
explicit Controller_Keyboard(Core::HID::HIDCore& hid_core_);
~Controller_Keyboard() override;
// Called when the controller is initialized
@@ -28,47 +33,21 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Modifiers {
union {
u32_le raw{};
BitField<0, 1, u32> control;
BitField<1, 1, u32> shift;
BitField<2, 1, u32> left_alt;
BitField<3, 1, u32> right_alt;
BitField<4, 1, u32> gui;
BitField<8, 1, u32> caps_lock;
BitField<9, 1, u32> scroll_lock;
BitField<10, 1, u32> num_lock;
BitField<11, 1, u32> katakana;
BitField<12, 1, u32> hiragana;
};
};
static_assert(sizeof(Modifiers) == 0x4, "Modifiers is an invalid size");
// This is nn::hid::detail::KeyboardState
struct KeyboardState {
s64_le sampling_number;
s64_le sampling_number2;
Modifiers modifier;
std::array<u8, 32> key;
s64 sampling_number;
Core::HID::KeyboardModifier modifier;
Core::HID::KeyboardAttribute attribute;
Core::HID::KeyboardKey key;
};
static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<KeyboardState, 17> pad_states;
INSERT_PADDING_BYTES(0x28);
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
// This is nn::hid::detail::KeyboardLifo
Lifo<KeyboardState, hid_entry_count> keyboard_lifo{};
static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size");
KeyboardState next_state{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardKeys>
keyboard_keys;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods>
keyboard_mods;
Core::HID::EmulatedDevices* emulated_devices;
};
} // namespace Service::HID

View File

@@ -6,12 +6,17 @@
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/mouse.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
Controller_Mouse::Controller_Mouse(Core::System& system_) : ControllerBase{system_} {}
Controller_Mouse::Controller_Mouse(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {
emulated_devices = hid_core.GetEmulatedDevices();
}
Controller_Mouse::~Controller_Mouse() = default;
void Controller_Mouse::OnInit() {}
@@ -19,50 +24,35 @@ void Controller_Mouse::OnRelease() {}
void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
mouse_lifo.buffer_count = 0;
mouse_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo));
return;
}
shared_memory.header.entry_count = 16;
auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
const auto& last_entry = mouse_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
cur_entry.attribute.raw = 0;
next_state.attribute.raw = 0;
if (Settings::values.mouse_enabled) {
const auto [px, py, sx, sy] = mouse_device->GetStatus();
const auto x = static_cast<s32>(px * Layout::ScreenUndocked::Width);
const auto y = static_cast<s32>(py * Layout::ScreenUndocked::Height);
cur_entry.x = x;
cur_entry.y = y;
cur_entry.delta_x = x - last_entry.x;
cur_entry.delta_y = y - last_entry.y;
cur_entry.mouse_wheel_x = sx;
cur_entry.mouse_wheel_y = sy;
cur_entry.attribute.is_connected.Assign(1);
const auto& mouse_button_state = emulated_devices->GetMouseButtons();
const auto& mouse_position_state = emulated_devices->GetMousePosition();
const auto& mouse_wheel_state = emulated_devices->GetMouseWheel();
next_state.attribute.is_connected.Assign(1);
next_state.x = static_cast<s32>(mouse_position_state.x * Layout::ScreenUndocked::Width);
next_state.y = static_cast<s32>(mouse_position_state.y * Layout::ScreenUndocked::Height);
next_state.delta_x = next_state.x - last_entry.x;
next_state.delta_y = next_state.y - last_entry.y;
next_state.delta_wheel_x = mouse_wheel_state.x - last_mouse_wheel_state.x;
next_state.delta_wheel_y = mouse_wheel_state.y - last_mouse_wheel_state.y;
using namespace Settings::NativeMouseButton;
cur_entry.button.left.Assign(mouse_button_devices[Left]->GetStatus());
cur_entry.button.right.Assign(mouse_button_devices[Right]->GetStatus());
cur_entry.button.middle.Assign(mouse_button_devices[Middle]->GetStatus());
cur_entry.button.forward.Assign(mouse_button_devices[Forward]->GetStatus());
cur_entry.button.back.Assign(mouse_button_devices[Back]->GetStatus());
last_mouse_wheel_state = mouse_wheel_state;
next_state.button = mouse_button_state;
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
mouse_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo));
}
void Controller_Mouse::OnLoadInputDevices() {
mouse_device = Input::CreateDevice<Input::MouseDevice>(Settings::values.mouse_device);
std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
mouse_button_devices.begin(), Input::CreateDevice<Input::ButtonDevice>);
}
} // namespace Service::HID

View File

@@ -7,15 +7,20 @@
#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedDevices;
struct MouseState;
struct AnalogStickState;
} // namespace Core::HID
namespace Service::HID {
class Controller_Mouse final : public ControllerBase {
public:
explicit Controller_Mouse(Core::System& system_);
explicit Controller_Mouse(Core::HID::HIDCore& hid_core_);
~Controller_Mouse() override;
// Called when the controller is initialized
@@ -27,53 +32,13 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Buttons {
union {
u32_le raw{};
BitField<0, 1, u32> left;
BitField<1, 1, u32> right;
BitField<2, 1, u32> middle;
BitField<3, 1, u32> forward;
BitField<4, 1, u32> back;
};
};
static_assert(sizeof(Buttons) == 0x4, "Buttons is an invalid size");
// This is nn::hid::detail::MouseLifo
Lifo<Core::HID::MouseState, hid_entry_count> mouse_lifo{};
static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size");
Core::HID::MouseState next_state{};
struct Attributes {
union {
u32_le raw{};
BitField<0, 1, u32> transferable;
BitField<1, 1, u32> is_connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
struct MouseState {
s64_le sampling_number;
s64_le sampling_number2;
s32_le x;
s32_le y;
s32_le delta_x;
s32_le delta_y;
s32_le mouse_wheel_x;
s32_le mouse_wheel_y;
Buttons button;
Attributes attribute;
};
static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<MouseState, 17> mouse_states;
};
SharedMemory shared_memory{};
std::unique_ptr<Input::MouseDevice> mouse_device;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons>
mouse_button_devices;
Core::HID::AnalogStickState last_mouse_wheel_state;
Core::HID::EmulatedDevices* emulated_devices;
};
} // namespace Service::HID

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,14 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/quaternion.h"
#include "common/settings.h"
#include "core/frontend/input.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedController;
enum class ControllerTriggerType;
} // namespace Core::HID
namespace Kernel {
class KEvent;
@@ -26,12 +31,9 @@ class ServiceContext;
namespace Service::HID {
constexpr u32 NPAD_HANDHELD = 32;
constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this?
class Controller_NPad final : public ControllerBase {
public:
explicit Controller_NPad(Core::System& system_,
explicit Controller_NPad(Core::HID::HIDCore& hid_core_,
KernelHelpers::ServiceContext& service_context_);
~Controller_NPad() override;
@@ -48,60 +50,39 @@ public:
void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
enum class NPadControllerType {
None,
ProController,
Handheld,
JoyDual,
JoyLeft,
JoyRight,
GameCube,
Pokeball,
};
enum class NpadType : u8 {
ProController = 3,
Handheld = 4,
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
GameCube = 8,
Pokeball = 9,
MaxNpadType = 10,
};
enum class DeviceIndex : u8 {
Left = 0,
Right = 1,
None = 2,
MaxDeviceIndex = 3,
};
// This is nn::hid::GyroscopeZeroDriftMode
enum class GyroscopeZeroDriftMode : u32 {
Loose = 0,
Standard = 1,
Tight = 2,
};
enum class NpadHoldType : u64 {
// This is nn::hid::NpadJoyHoldType
enum class NpadJoyHoldType : u64 {
Vertical = 0,
Horizontal = 1,
};
enum class NpadAssignments : u32 {
// This is nn::hid::NpadJoyAssignmentMode
enum class NpadJoyAssignmentMode : u32 {
Dual = 0,
Single = 1,
};
// This is nn::hid::NpadJoyDeviceType
enum class NpadJoyDeviceType : s64 {
Left = 0,
Right = 1,
};
// This is nn::hid::NpadHandheldActivationMode
enum class NpadHandheldActivationMode : u64 {
Dual = 0,
Single = 1,
None = 2,
};
// This is nn::hid::NpadCommunicationMode
enum class NpadCommunicationMode : u64 {
Mode_5ms = 0,
Mode_10ms = 1,
@@ -109,74 +90,22 @@ public:
Default = 3,
};
struct DeviceHandle {
NpadType npad_type;
u8 npad_id;
DeviceIndex device_index;
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size");
struct NpadStyleSet {
union {
u32_le raw{};
BitField<0, 1, u32> fullkey;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<5, 1, u32> gamecube;
BitField<6, 1, u32> palma;
BitField<7, 1, u32> lark;
BitField<8, 1, u32> handheld_lark;
BitField<9, 1, u32> lucia;
BitField<29, 1, u32> system_ext;
BitField<30, 1, u32> system;
};
};
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
struct VibrationValue {
f32 amp_low;
f32 freq_low;
f32 amp_high;
f32 freq_high;
};
static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size");
static constexpr VibrationValue DEFAULT_VIBRATION_VALUE{
.amp_low = 0.0f,
.freq_low = 160.0f,
.amp_high = 0.0f,
.freq_high = 320.0f,
static constexpr Core::HID::VibrationValue DEFAULT_VIBRATION_VALUE{
.low_amplitude = 0.0f,
.low_frequency = 160.0f,
.high_amplitude = 0.0f,
.high_frequency = 320.0f,
};
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
position1.Assign(light1);
position2.Assign(light2);
position3.Assign(light3);
position4.Assign(light4);
}
union {
u64 raw{};
BitField<0, 1, u64> position1;
BitField<1, 1, u64> position2;
BitField<2, 1, u64> position3;
BitField<3, 1, u64> position4;
};
};
void SetSupportedStyleSet(NpadStyleSet style_set);
NpadStyleSet GetSupportedStyleSet() const;
void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set);
Core::HID::NpadStyleTag GetSupportedStyleSet() const;
void SetSupportedNpadIdTypes(u8* data, std::size_t length);
void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
std::size_t GetSupportedNpadIdTypesSize() const;
void SetHoldType(NpadHoldType joy_hold_type);
NpadHoldType GetHoldType() const;
void SetHoldType(NpadJoyHoldType joy_hold_type);
NpadJoyHoldType GetHoldType() const;
void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
@@ -184,162 +113,106 @@ public:
void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_);
NpadCommunicationMode GetNpadCommunicationMode() const;
void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode);
void SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyAssignmentMode assignment_mode);
bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
const VibrationValue& vibration_value);
bool VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index,
const Core::HID::VibrationValue& vibration_value);
void VibrateController(const DeviceHandle& vibration_device_handle,
const VibrationValue& vibration_value);
void VibrateController(const Core::HID::VibrationDeviceHandle& vibration_device_handle,
const Core::HID::VibrationValue& vibration_value);
void VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
const std::vector<VibrationValue>& vibration_values);
void VibrateControllers(
const std::vector<Core::HID::VibrationDeviceHandle>& vibration_device_handles,
const std::vector<Core::HID::VibrationValue>& vibration_values);
VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const;
Core::HID::VibrationValue GetLastVibration(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle);
void InitializeVibrationDevice(const Core::HID::VibrationDeviceHandle& vibration_device_handle);
void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index);
void InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index);
void SetPermitVibrationSession(bool permit_vibration_session);
bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const;
bool IsVibrationDeviceMounted(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
Kernel::KReadableEvent& GetStyleSetChangedEvent(u32 npad_id);
void SignalStyleSetChangedEvent(u32 npad_id) const;
Kernel::KReadableEvent& GetStyleSetChangedEvent(Core::HID::NpadIdType npad_id);
void SignalStyleSetChangedEvent(Core::HID::NpadIdType npad_id) const;
// Adds a new controller at an index.
void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index);
void AddNewControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id);
// Adds a new controller at an index with connection status.
void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
void UpdateControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id,
bool connected);
void DisconnectNpad(u32 npad_id);
void DisconnectNpadAtIndex(std::size_t index);
void DisconnectNpad(Core::HID::NpadIdType npad_id);
void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
bool IsSixAxisSensorAtRest() const;
void SetSixAxisEnabled(bool six_axis_status);
void SetSixAxisFusionParameters(f32 parameter1, f32 parameter2);
std::pair<f32, f32> GetSixAxisFusionParameters();
void ResetSixAxisFusionParameters();
LedPattern GetLedPattern(u32 npad_id);
bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
void SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
GyroscopeZeroDriftMode drift_mode);
GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode(
Core::HID::SixAxisSensorHandle sixaxis_handle) const;
bool IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const;
void SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, bool sixaxis_status);
void SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
bool sixaxis_fusion_status);
void SetSixAxisFusionParameters(
Core::HID::SixAxisSensorHandle sixaxis_handle,
Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters);
Core::HID::SixAxisSensorFusionParameters GetSixAxisFusionParameters(
Core::HID::SixAxisSensorHandle sixaxis_handle);
void ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle);
Core::HID::LedPattern GetLedPattern(Core::HID::NpadIdType npad_id);
bool IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id) const;
void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
Core::HID::NpadIdType npad_id);
void SetAnalogStickUseCenterClamp(bool use_center_clamp);
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2);
void MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2);
void StartLRAssignmentMode();
void StopLRAssignmentMode();
bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2);
bool SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2);
// Logical OR for all buttons presses on all controllers
// Specifically for cheat engine and other features.
u32 GetAndResetPressState();
static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type);
static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
static bool IsNpadIdValid(u32 npad_id);
static bool IsDeviceHandleValid(const DeviceHandle& device_handle);
static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
static bool IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle);
static bool IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
private:
struct CommonHeader {
s64_le timestamp;
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
enum class ColorAttributes : u32_le {
// This is nn::hid::detail::ColorAttribute
enum class ColorAttribute : u32 {
Ok = 0,
ReadError = 1,
NoController = 2,
};
static_assert(sizeof(ColorAttributes) == 4, "ColorAttributes is an invalid size");
static_assert(sizeof(ColorAttribute) == 4, "ColorAttribute is an invalid size");
struct ControllerColor {
u32_le body;
u32_le button;
// This is nn::hid::detail::NpadFullKeyColorState
struct NpadFullKeyColorState {
ColorAttribute attribute;
Core::HID::NpadControllerColor fullkey;
};
static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size");
struct FullKeyColor {
ColorAttributes attribute;
ControllerColor fullkey;
// This is nn::hid::detail::NpadJoyColorState
struct NpadJoyColorState {
ColorAttribute attribute;
Core::HID::NpadControllerColor left;
Core::HID::NpadControllerColor right;
};
static_assert(sizeof(FullKeyColor) == 0xC, "FullKeyColor is an invalid size");
static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size");
struct JoyconColor {
ColorAttributes attribute;
ControllerColor left;
ControllerColor right;
};
static_assert(sizeof(JoyconColor) == 0x14, "JoyconColor is an invalid size");
struct ControllerPadState {
// This is nn::hid::NpadAttribute
struct NpadAttribute {
union {
u64_le raw{};
// Button states
BitField<0, 1, u64> a;
BitField<1, 1, u64> b;
BitField<2, 1, u64> x;
BitField<3, 1, u64> y;
BitField<4, 1, u64> l_stick;
BitField<5, 1, u64> r_stick;
BitField<6, 1, u64> l;
BitField<7, 1, u64> r;
BitField<8, 1, u64> zl;
BitField<9, 1, u64> zr;
BitField<10, 1, u64> plus;
BitField<11, 1, u64> minus;
// D-Pad
BitField<12, 1, u64> d_left;
BitField<13, 1, u64> d_up;
BitField<14, 1, u64> d_right;
BitField<15, 1, u64> d_down;
// Left JoyStick
BitField<16, 1, u64> l_stick_left;
BitField<17, 1, u64> l_stick_up;
BitField<18, 1, u64> l_stick_right;
BitField<19, 1, u64> l_stick_down;
// Right JoyStick
BitField<20, 1, u64> r_stick_left;
BitField<21, 1, u64> r_stick_up;
BitField<22, 1, u64> r_stick_right;
BitField<23, 1, u64> r_stick_down;
// Not always active?
BitField<24, 1, u64> left_sl;
BitField<25, 1, u64> left_sr;
BitField<26, 1, u64> right_sl;
BitField<27, 1, u64> right_sr;
BitField<28, 1, u64> palma;
BitField<30, 1, u64> handheld_left_b;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
struct AnalogPosition {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size");
struct ConnectionState {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_wired;
BitField<2, 1, u32> is_left_connected;
@@ -348,79 +221,60 @@ private:
BitField<5, 1, u32> is_right_wired;
};
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
static_assert(sizeof(NpadAttribute) == 4, "NpadAttribute is an invalid size");
struct ControllerPad {
ControllerPadState pad_states;
AnalogPosition l_stick;
AnalogPosition r_stick;
// This is nn::hid::NpadFullKeyState
// This is nn::hid::NpadHandheldState
// This is nn::hid::NpadJoyDualState
// This is nn::hid::NpadJoyLeftState
// This is nn::hid::NpadJoyRightState
// This is nn::hid::NpadPalmaState
// This is nn::hid::NpadSystemExtState
struct NPadGenericState {
s64_le sampling_number;
Core::HID::NpadButtonState npad_buttons;
Core::HID::AnalogStickState l_stick;
Core::HID::AnalogStickState r_stick;
NpadAttribute connection_status;
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(ControllerPad) == 0x18, "ControllerPad is an invalid size");
static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size");
struct GenericStates {
s64_le timestamp;
s64_le timestamp2;
ControllerPad pad;
ConnectionState connection_status;
};
static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size");
struct NPadGeneric {
CommonHeader common;
std::array<GenericStates, 17> npad;
};
static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
struct SixAxisAttributes {
// This is nn::hid::SixAxisSensorAttribute
struct SixAxisSensorAttribute {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_interpolated;
};
};
static_assert(sizeof(SixAxisAttributes) == 4, "SixAxisAttributes is an invalid size");
static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an invalid size");
struct SixAxisStates {
s64_le timestamp{};
INSERT_PADDING_WORDS(2);
s64_le timestamp2{};
// This is nn::hid::SixAxisSensorState
struct SixAxisSensorState {
s64 delta_time{};
s64 sampling_number{};
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
SixAxisAttributes attribute;
SixAxisSensorAttribute attribute;
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size");
static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size");
struct SixAxisGeneric {
CommonHeader common{};
std::array<SixAxisStates, 17> sixaxis{};
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
s32 l_analog{};
s32 r_analog{};
};
static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
struct TriggerState {
s64_le timestamp{};
s64_le timestamp2{};
s32_le l_analog{};
s32_le r_analog{};
};
static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size");
struct TriggerGeneric {
INSERT_PADDING_BYTES(0x4);
s64_le timestamp;
INSERT_PADDING_BYTES(0x4);
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
std::array<TriggerState, 17> trigger{};
};
static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size");
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::NpadSystemProperties
struct NPadSystemProperties {
union {
s64_le raw{};
s64 raw{};
BitField<0, 1, s64> is_charging_joy_dual;
BitField<1, 1, s64> is_charging_joy_left;
BitField<2, 1, s64> is_charging_joy_right;
@@ -438,17 +292,20 @@ private:
};
static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size");
struct NPadButtonProperties {
// This is nn::hid::NpadSystemButtonProperties
struct NpadSystemButtonProperties {
union {
s32_le raw{};
s32 raw{};
BitField<0, 1, s32> is_home_button_protection_enabled;
};
};
static_assert(sizeof(NPadButtonProperties) == 0x4, "NPadButtonProperties is an invalid size");
static_assert(sizeof(NpadSystemButtonProperties) == 0x4,
"NPadButtonProperties is an invalid size");
struct NPadDevice {
// This is nn::hid::system::DeviceType
struct DeviceType {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, s32> fullkey;
BitField<1, 1, s32> debug_pad;
BitField<2, 1, s32> handheld_left;
@@ -465,26 +322,29 @@ private:
BitField<13, 1, s32> handheld_lark_nes_left;
BitField<14, 1, s32> handheld_lark_nes_right;
BitField<15, 1, s32> lucia;
BitField<16, 1, s32> lagon;
BitField<17, 1, s32> lager;
BitField<31, 1, s32> system;
};
};
struct MotionDevice {
Common::Vec3f accel;
Common::Vec3f gyro;
Common::Vec3f rotation;
std::array<Common::Vec3f, 3> orientation;
Common::Quaternion<f32> quaternion;
};
struct NfcXcdHandle {
INSERT_PADDING_BYTES(0x60);
// This is nn::hid::detail::NfcXcdDeviceHandleStateImpl
struct NfcXcdDeviceHandleStateImpl {
u64 handle;
bool is_available;
bool is_activated;
INSERT_PADDING_BYTES(0x6); // Reserved
u64 sampling_number;
};
static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18,
"NfcXcdDeviceHandleStateImpl is an invalid size");
// This is nn::hid::system::AppletFooterUiAttributesSet
struct AppletFooterUiAttributes {
INSERT_PADDING_BYTES(0x4);
};
// This is nn::hid::system::AppletFooterUiType
enum class AppletFooterUiType : u8 {
None = 0,
HandheldNone = 1,
@@ -510,95 +370,150 @@ private:
Lagon = 21,
};
struct NPadEntry {
NpadStyleSet style_set;
NpadAssignments assignment_mode;
FullKeyColor fullkey_color;
JoyconColor joycon_color;
struct AppletFooterUi {
AppletFooterUiAttributes attributes;
AppletFooterUiType type;
INSERT_PADDING_BYTES(0x5B); // Reserved
};
static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
NPadGeneric fullkey_states;
NPadGeneric handheld_states;
NPadGeneric joy_dual_states;
NPadGeneric joy_left_states;
NPadGeneric joy_right_states;
NPadGeneric palma_states;
NPadGeneric system_ext_states;
SixAxisGeneric sixaxis_fullkey;
SixAxisGeneric sixaxis_handheld;
SixAxisGeneric sixaxis_dual_left;
SixAxisGeneric sixaxis_dual_right;
SixAxisGeneric sixaxis_left;
SixAxisGeneric sixaxis_right;
NPadDevice device_type;
INSERT_PADDING_BYTES(0x4); // reserved
// This is nn::hid::NpadLarkType
enum class NpadLarkType : u32 {
Invalid,
H1,
H2,
NL,
NR,
};
// This is nn::hid::NpadLuciaType
enum class NpadLuciaType : u32 {
Invalid,
J,
E,
U,
};
// This is nn::hid::NpadLagonType
enum class NpadLagonType : u32 {
Invalid,
};
// This is nn::hid::NpadLagerType
enum class NpadLagerType : u32 {
Invalid,
J,
E,
U,
};
// This is nn::hid::detail::NpadInternalState
struct NpadInternalState {
Core::HID::NpadStyleTag style_tag;
NpadJoyAssignmentMode assignment_mode;
NpadFullKeyColorState fullkey_color;
NpadJoyColorState joycon_color;
Lifo<NPadGenericState, hid_entry_count> fullkey_lifo;
Lifo<NPadGenericState, hid_entry_count> handheld_lifo;
Lifo<NPadGenericState, hid_entry_count> joy_dual_lifo;
Lifo<NPadGenericState, hid_entry_count> joy_left_lifo;
Lifo<NPadGenericState, hid_entry_count> joy_right_lifo;
Lifo<NPadGenericState, hid_entry_count> palma_lifo;
Lifo<NPadGenericState, hid_entry_count> system_ext_lifo;
Lifo<SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo;
Lifo<SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo;
Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo;
Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo;
Lifo<SixAxisSensorState, hid_entry_count> sixaxis_left_lifo;
Lifo<SixAxisSensorState, hid_entry_count> sixaxis_right_lifo;
DeviceType device_type;
INSERT_PADDING_BYTES(0x4); // Reserved
NPadSystemProperties system_properties;
NPadButtonProperties button_properties;
u32 battery_level_dual;
u32 battery_level_left;
u32 battery_level_right;
AppletFooterUiAttributes footer_attributes;
AppletFooterUiType footer_type;
// nfc_states needs to be checked switchbrew does not match with HW
NfcXcdHandle nfc_states;
INSERT_PADDING_BYTES(0x8); // Mutex
TriggerGeneric gc_trigger_states;
INSERT_PADDING_BYTES(0xc1f);
NpadSystemButtonProperties button_properties;
Core::HID::NpadBatteryLevel battery_level_dual;
Core::HID::NpadBatteryLevel battery_level_left;
Core::HID::NpadBatteryLevel battery_level_right;
union {
Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo{};
AppletFooterUi applet_footer;
};
INSERT_PADDING_BYTES(0x20); // Unknown
Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo;
NpadLarkType lark_type_l_and_main;
NpadLarkType lark_type_r;
NpadLuciaType lucia_type;
NpadLagonType lagon_type;
NpadLagerType lager_type;
// FW 13.x Investigate there is some sort of bitflag related to joycons
INSERT_PADDING_BYTES(0x4);
INSERT_PADDING_BYTES(0xc08); // Unknown
};
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
static_assert(sizeof(NpadInternalState) == 0x5000, "NpadInternalState is an invalid size");
struct ControllerHolder {
NPadControllerType type;
bool is_connected;
struct VibrationData {
bool device_mounted{};
Core::HID::VibrationValue latest_vibration_value{};
std::chrono::steady_clock::time_point last_vibration_timepoint{};
};
void InitNewlyAddedController(std::size_t controller_idx);
bool IsControllerSupported(NPadControllerType controller) const;
void RequestPadStateUpdate(u32 npad_id);
struct NpadControllerData {
Core::HID::EmulatedController* device;
Kernel::KEvent* styleset_changed_event{};
NpadInternalState shared_memory_entry{};
std::array<VibrationData, 2> vibration{};
bool unintended_home_button_input_protection{};
bool is_connected{};
Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None};
// Motion parameters
bool sixaxis_at_rest{true};
bool sixaxis_sensor_enabled{true};
bool sixaxis_fusion_enabled{false};
Core::HID::SixAxisSensorFusionParameters sixaxis_fusion{};
GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
// Current pad state
NPadGenericState npad_pad_state{};
NPadGenericState npad_libnx_state{};
NpadGcTriggerState npad_trigger_state{};
SixAxisSensorState sixaxis_fullkey_state{};
SixAxisSensorState sixaxis_handheld_state{};
SixAxisSensorState sixaxis_dual_left_state{};
SixAxisSensorState sixaxis_dual_right_state{};
SixAxisSensorState sixaxis_left_lifo_state{};
SixAxisSensorState sixaxis_right_lifo_state{};
int callback_key;
};
void ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx);
void InitNewlyAddedController(Core::HID::NpadIdType npad_id);
bool IsControllerSupported(Core::HID::NpadStyleIndex controller) const;
void RequestPadStateUpdate(Core::HID::NpadIdType npad_id);
void WriteEmptyEntry(NpadInternalState& npad);
NpadControllerData& GetControllerFromHandle(
const Core::HID::SixAxisSensorHandle& device_handle);
const NpadControllerData& GetControllerFromHandle(
const Core::HID::SixAxisSensorHandle& device_handle) const;
NpadControllerData& GetControllerFromHandle(
const Core::HID::VibrationDeviceHandle& device_handle);
const NpadControllerData& GetControllerFromHandle(
const Core::HID::VibrationDeviceHandle& device_handle) const;
NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id);
const NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) const;
std::atomic<u32> press_state{};
NpadStyleSet style{};
std::array<NPadEntry, 10> shared_memory_entries{};
using ButtonArray = std::array<
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
10>;
using StickArray = std::array<
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
10>;
using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>,
Settings::NativeVibration::NUM_VIBRATIONS_HID>,
10>;
using MotionArray = std::array<
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
10>;
std::array<NpadControllerData, 10> controller_data{};
KernelHelpers::ServiceContext& service_context;
std::mutex mutex;
ButtonArray buttons;
StickArray sticks;
VibrationArray vibrations;
MotionArray motions;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
std::vector<Core::HID::NpadIdType> supported_npad_id_types{};
NpadJoyHoldType hold_type{NpadJoyHoldType::Vertical};
NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
NpadCommunicationMode communication_mode{NpadCommunicationMode::Default};
// Each controller should have their own styleset changed event
std::array<Kernel::KEvent*, 10> styleset_changed_events{};
std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10>
last_vibration_timepoints{};
std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{};
bool permit_vibration_session_enabled{false};
std::array<std::array<bool, 2>, 10> vibration_devices_mounted{};
std::array<ControllerHolder, 10> connected_controllers{};
std::array<bool, 10> unintended_home_button_input_protection{};
bool analog_stick_use_center_clamp{};
GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
bool sixaxis_sensors_enabled{true};
f32 sixaxis_fusion_parameter1{};
f32 sixaxis_fusion_parameter2{};
bool sixaxis_at_rest{true};
std::array<ControllerPad, 10> npad_pad_states{};
std::array<TriggerState, 10> npad_trigger_states{};
bool is_in_lr_assignment_mode{false};
};
} // namespace Service::HID

View File

@@ -5,11 +5,12 @@
#include <cstring>
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/stubbed.h"
namespace Service::HID {
Controller_Stubbed::Controller_Stubbed(Core::System& system_) : ControllerBase{system_} {}
Controller_Stubbed::Controller_Stubbed(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {}
Controller_Stubbed::~Controller_Stubbed() = default;
void Controller_Stubbed::OnInit() {}
@@ -31,10 +32,9 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u
std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
}
void Controller_Stubbed::OnLoadInputDevices() {}
void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
common_offset = off;
smart_update = true;
}
} // namespace Service::HID

View File

@@ -10,7 +10,7 @@
namespace Service::HID {
class Controller_Stubbed final : public ControllerBase {
public:
explicit Controller_Stubbed(Core::System& system_);
explicit Controller_Stubbed(Core::HID::HIDCore& hid_core_);
~Controller_Stubbed() override;
// Called when the controller is initialized
@@ -22,12 +22,17 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
void SetCommonHeaderOffset(std::size_t off);
private:
struct CommonHeader {
s64 timestamp;
s64 total_entry_count;
s64 last_entry_index;
s64 entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
bool smart_update{};
std::size_t common_offset{};
};

View File

@@ -7,72 +7,82 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hid/emulated_console.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
Controller_Touchscreen::Controller_Touchscreen(Core::System& system_) : ControllerBase{system_} {}
Controller_Touchscreen::Controller_Touchscreen(Core::HID::HIDCore& hid_core_)
: ControllerBase{hid_core_} {
console = hid_core.GetEmulatedConsole();
}
Controller_Touchscreen::~Controller_Touchscreen() = default;
void Controller_Touchscreen::OnInit() {
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
mouse_finger_id[id] = MAX_FINGERS;
keyboard_finger_id[id] = MAX_FINGERS;
udp_finger_id[id] = MAX_FINGERS;
}
}
void Controller_Touchscreen::OnInit() {}
void Controller_Touchscreen::OnRelease() {}
void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
touch_screen_lifo.timestamp = core_timing.GetCPUTicks();
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
touch_screen_lifo.buffer_count = 0;
touch_screen_lifo.buffer_tail = 0;
std::memcpy(data, &touch_screen_lifo, sizeof(touch_screen_lifo));
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry =
shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
const auto touch_status = console->GetTouch();
for (std::size_t id = 0; id < MAX_FINGERS; id++) {
const auto& current_touch = touch_status[id];
auto& finger = fingers[id];
finger.position = current_touch.position;
finger.id = current_touch.id;
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
if (finger.attribute.start_touch) {
finger.attribute.raw = 0;
continue;
}
const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus();
const Input::TouchStatus& udp_status = touch_udp_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]);
udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]);
}
if (finger.attribute.end_touch) {
finger.attribute.raw = 0;
finger.pressed = false;
continue;
}
if (Settings::values.use_touch_from_button) {
const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
keyboard_finger_id[id] =
UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]);
if (!finger.pressed && current_touch.pressed) {
finger.attribute.start_touch.Assign(1);
finger.pressed = true;
continue;
}
if (finger.pressed && !current_touch.pressed) {
finger.attribute.raw = 0;
finger.attribute.end_touch.Assign(1);
}
}
std::array<Finger, 16> active_fingers;
std::array<Core::HID::TouchFinger, MAX_FINGERS> active_fingers;
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
[](const auto& finger) { return finger.pressed; });
const auto active_fingers_count =
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
const u64 tick = core_timing.GetCPUTicks();
cur_entry.entry_count = static_cast<s32_le>(active_fingers_count);
const auto& last_entry = touch_screen_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
next_state.entry_count = static_cast<s32>(active_fingers_count);
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
auto& touch_entry = cur_entry.states[id];
auto& touch_entry = next_state.states[id];
if (id < active_fingers_count) {
const auto& [active_x, active_y] = active_fingers[id].position;
touch_entry.position = {
@@ -97,66 +107,9 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
touch_entry.finger = 0;
}
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
}
void Controller_Touchscreen::OnLoadInputDevices() {
touch_mouse_device = Input::CreateDevice<Input::TouchDevice>("engine:emu_window");
touch_udp_device = Input::CreateDevice<Input::TouchDevice>("engine:cemuhookudp");
touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
}
std::optional<std::size_t> Controller_Touchscreen::GetUnusedFingerID() const {
// Dont assign any touch input to a finger if disabled
if (!Settings::values.touchscreen.enabled) {
return std::nullopt;
}
std::size_t first_free_id = 0;
while (first_free_id < MAX_FINGERS) {
if (!fingers[first_free_id].pressed) {
return first_free_id;
} else {
first_free_id++;
}
}
return std::nullopt;
}
std::size_t Controller_Touchscreen::UpdateTouchInputEvent(
const std::tuple<float, float, bool>& touch_input, std::size_t finger_id) {
const auto& [x, y, pressed] = touch_input;
if (finger_id > MAX_FINGERS) {
LOG_ERROR(Service_HID, "Invalid finger id {}", finger_id);
return MAX_FINGERS;
}
if (pressed) {
Attributes attribute{};
if (finger_id == MAX_FINGERS) {
const auto first_free_id = GetUnusedFingerID();
if (!first_free_id) {
// Invalid finger id do nothing
return MAX_FINGERS;
}
finger_id = first_free_id.value();
fingers[finger_id].pressed = true;
fingers[finger_id].id = static_cast<u32_le>(finger_id);
attribute.start_touch.Assign(1);
}
fingers[finger_id].position = {x, y};
fingers[finger_id].attribute = attribute;
return finger_id;
}
if (finger_id != MAX_FINGERS) {
if (!fingers[finger_id].attribute.end_touch) {
fingers[finger_id].attribute.end_touch.Assign(1);
fingers[finger_id].attribute.start_touch.Assign(0);
return finger_id;
}
fingers[finger_id].pressed = false;
}
return MAX_FINGERS;
touch_screen_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &touch_screen_lifo, sizeof(touch_screen_lifo));
}
} // namespace Service::HID

View File

@@ -9,18 +9,25 @@
#include "common/common_types.h"
#include "common/point.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedConsole;
} // namespace Core::HID
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
// This is nn::hid::TouchScreenModeForNx
enum class TouchScreenModeForNx : u8 {
UseSystemSetting,
Finger,
Heat2,
};
// This is nn::hid::TouchScreenConfigurationForNx
struct TouchScreenConfigurationForNx {
TouchScreenModeForNx mode;
INSERT_PADDING_BYTES_NOINIT(0x7);
@@ -29,7 +36,7 @@ public:
static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
"TouchScreenConfigurationForNx is an invalid size");
explicit Controller_Touchscreen(Core::System& system_);
explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_);
~Controller_Touchscreen() override;
// Called when the controller is initialized
@@ -41,73 +48,24 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
static constexpr std::size_t MAX_FINGERS = 16;
// Returns an unused finger id, if there is no fingers available std::nullopt will be returned
std::optional<std::size_t> GetUnusedFingerID() const;
// If the touch is new it tries to assing a new finger id, if there is no fingers avaliable no
// changes will be made. Updates the coordinates if the finger id it's already set. If the touch
// ends delays the output by one frame to set the end_touch flag before finally freeing the
// finger id
std::size_t UpdateTouchInputEvent(const std::tuple<float, float, bool>& touch_input,
std::size_t finger_id);
struct Attributes {
union {
u32 raw{};
BitField<0, 1, u32> start_touch;
BitField<1, 1, u32> end_touch;
};
// This is nn::hid::TouchScreenState
struct TouchScreenState {
s64 sampling_number;
s32 entry_count;
INSERT_PADDING_BYTES(4); // Reserved
std::array<Core::HID::TouchState, MAX_FINGERS> states;
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
struct TouchState {
u64_le delta_time;
Attributes attribute;
u32_le finger;
Common::Point<u32_le> position;
u32_le diameter_x;
u32_le diameter_y;
u32_le rotation_angle;
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
// This is nn::hid::detail::TouchScreenLifo
Lifo<TouchScreenState, hid_entry_count> touch_screen_lifo{};
static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
TouchScreenState next_state{};
struct TouchScreenEntry {
s64_le sampling_number;
s64_le sampling_number2;
s32_le entry_count;
std::array<TouchState, MAX_FINGERS> states;
};
static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
struct TouchScreenSharedMemory {
CommonHeader header;
std::array<TouchScreenEntry, 17> shared_memory_entries{};
INSERT_PADDING_BYTES(0x3c8);
};
static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
"TouchScreenSharedMemory is an invalid size");
struct Finger {
u64_le last_touch{};
Common::Point<float> position;
u32_le id{};
bool pressed{};
Attributes attribute;
};
TouchScreenSharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_mouse_device;
std::unique_ptr<Input::TouchDevice> touch_udp_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device;
std::array<std::size_t, MAX_FINGERS> mouse_finger_id;
std::array<std::size_t, MAX_FINGERS> keyboard_finger_id;
std::array<std::size_t, MAX_FINGERS> udp_finger_id;
std::array<Finger, MAX_FINGERS> fingers;
std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers;
Core::HID::EmulatedConsole* console;
};
} // namespace Service::HID

View File

@@ -5,12 +5,13 @@
#include <cstring>
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/xpad.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
Controller_XPad::Controller_XPad(Core::System& system_) : ControllerBase{system_} {}
Controller_XPad::Controller_XPad(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {}
Controller_XPad::~Controller_XPad() = default;
void Controller_XPad::OnInit() {}
@@ -19,28 +20,19 @@ void Controller_XPad::OnRelease() {}
void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
for (auto& xpad_entry : shared_memory.shared_memory_entries) {
xpad_entry.header.timestamp = core_timing.GetCPUTicks();
xpad_entry.header.total_entry_count = 17;
if (!IsControllerActivated()) {
xpad_entry.header.entry_count = 0;
xpad_entry.header.last_entry_index = 0;
return;
}
xpad_entry.header.entry_count = 16;
const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17;
auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
if (!IsControllerActivated()) {
basic_xpad_lifo.buffer_count = 0;
basic_xpad_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo));
return;
}
const auto& last_entry = basic_xpad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
// TODO(ogniK): Update xpad states
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
basic_xpad_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo));
}
void Controller_XPad::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -8,12 +8,14 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
class Controller_XPad final : public ControllerBase {
public:
explicit Controller_XPad(Core::System& system_);
explicit Controller_XPad(Core::HID::HIDCore& hid_core_);
~Controller_XPad() override;
// Called when the controller is initialized
@@ -25,13 +27,11 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Attributes {
// This is nn::hid::BasicXpadAttributeSet
struct BasicXpadAttributeSet {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_wired;
BitField<2, 1, u32> is_left_connected;
@@ -40,11 +40,12 @@ private:
BitField<5, 1, u32> is_right_wired;
};
};
static_assert(sizeof(Attributes) == 4, "Attributes is an invalid size");
static_assert(sizeof(BasicXpadAttributeSet) == 4, "BasicXpadAttributeSet is an invalid size");
struct Buttons {
// This is nn::hid::BasicXpadButtonSet
struct BasicXpadButtonSet {
union {
u32_le raw{};
u32 raw{};
// Button states
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
@@ -88,35 +89,21 @@ private:
BitField<30, 1, u32> handheld_left_b;
};
};
static_assert(sizeof(Buttons) == 4, "Buttons is an invalid size");
static_assert(sizeof(BasicXpadButtonSet) == 4, "BasicXpadButtonSet is an invalid size");
struct AnalogStick {
s32_le x;
s32_le y;
// This is nn::hid::detail::BasicXpadState
struct BasicXpadState {
s64 sampling_number;
BasicXpadAttributeSet attributes;
BasicXpadButtonSet pad_states;
Core::HID::AnalogStickState l_stick;
Core::HID::AnalogStickState r_stick;
};
static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size");
static_assert(sizeof(BasicXpadState) == 0x20, "BasicXpadState is an invalid size");
struct XPadState {
s64_le sampling_number;
s64_le sampling_number2;
Attributes attributes;
Buttons pad_states;
AnalogStick l_stick;
AnalogStick r_stick;
};
static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
struct XPadEntry {
CommonHeader header;
std::array<XPadState, 17> pad_states{};
INSERT_PADDING_BYTES(0x138);
};
static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size");
struct SharedMemory {
std::array<XPadEntry, 4> shared_memory_entries{};
};
static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
// This is nn::hid::detail::BasicXpadLifo
Lifo<BasicXpadState, hid_entry_count> basic_xpad_lifo{};
static_assert(sizeof(basic_xpad_lifo) == 0x2C8, "basic_xpad_lifo is an invalid size");
BasicXpadState next_state{};
};
} // namespace Service::HID

View File

@@ -8,7 +8,7 @@
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/input.h"
#include "core/hid/hid_core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_shared_memory.h"
@@ -34,10 +34,10 @@
namespace Service::HID {
// Updating period for each HID device.
// HID is polled every 15ms, this value was derived from
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet
constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz)
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource(Core::System& system_,
@@ -79,17 +79,24 @@ IAppletResource::IAppletResource(Core::System& system_,
const auto guard = LockService();
UpdateControllers(user_data, ns_late);
});
mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
const auto guard = LockService();
UpdateMouseKeyboard(user_data, ns_late);
});
motion_update_event = Core::Timing::CreateEvent(
"HID::MotionPadCallback",
"HID::UpdateMotionCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
const auto guard = LockService();
UpdateMotion(user_data, ns_late);
});
system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
system.CoreTiming().ScheduleEvent(mouse_keyboard_update_ns, mouse_keyboard_update_event);
system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
ReloadInputDevices();
system.HIDCore().ReloadInputDevices();
}
void IAppletResource::ActivateController(HidController controller) {
@@ -102,6 +109,7 @@ void IAppletResource::DeactivateController(HidController controller) {
IAppletResource::~IAppletResource() {
system.CoreTiming().UnscheduleEvent(pad_update_event, 0);
system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0);
system.CoreTiming().UnscheduleEvent(motion_update_event, 0);
}
@@ -117,23 +125,44 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
for (const auto& controller : controllers) {
if (should_reload) {
controller->OnLoadInputDevices();
// Keyboard has it's own update event
if (controller == controllers[static_cast<size_t>(HidController::Keyboard)]) {
continue;
}
// Mouse has it's own update event
if (controller == controllers[static_cast<size_t>(HidController::Mouse)]) {
continue;
}
controller->OnUpdate(core_timing, system.Kernel().GetHidSharedMem().GetPointer(),
SHARED_MEMORY_SIZE);
}
// If ns_late is higher than the update rate ignore the delay
if (ns_late > motion_update_ns) {
if (ns_late > pad_update_ns) {
ns_late = {};
}
core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
}
void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
controllers[static_cast<size_t>(HidController::Mouse)]->OnUpdate(
core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE);
controllers[static_cast<size_t>(HidController::Keyboard)]->OnUpdate(
core_timing, system.Kernel().GetHidSharedMem().GetPointer(), SHARED_MEMORY_SIZE);
// If ns_late is higher than the update rate ignore the delay
if (ns_late > mouse_keyboard_update_ns) {
ns_late = {};
}
core_timing.ScheduleEvent(mouse_keyboard_update_ns - ns_late, mouse_keyboard_update_event);
}
void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
@@ -166,7 +195,7 @@ public:
private:
void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
if (applet_resource != nullptr) {
applet_resource->GetController<Controller_NPad>(HidController::NPad)
@@ -422,6 +451,7 @@ void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) {
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -448,19 +478,18 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) {
void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
u32 basic_xpad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
// This function does nothing on 10.0.0+
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
LOG_WARNING(Service_HID, "(STUBBED) called, basic_xpad_id={}, applet_resource_user_id={}",
parameters.basic_xpad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -469,19 +498,18 @@ void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
u32 basic_xpad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
// This function does nothing on 10.0.0+
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
LOG_WARNING(Service_HID, "(STUBBED) called, basic_xpad_id={}, applet_resource_user_id={}",
parameters.basic_xpad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -490,14 +518,16 @@ void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSixAxisEnabled(parameters.sixaxis_handle, true);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -511,14 +541,16 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSixAxisEnabled(parameters.sixaxis_handle, false);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -534,19 +566,23 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
struct Parameters {
bool enable_sixaxis_sensor_fusion;
INSERT_PADDING_BYTES_NOINIT(3);
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID,
"(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
"device_index={}, applet_resource_user_id={}",
parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type,
parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
parameters.applet_resource_user_id);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSixAxisFusionEnabled(parameters.sixaxis_handle,
parameters.enable_sixaxis_sensor_fusion);
LOG_DEBUG(Service_HID,
"called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
"device_index={}, applet_resource_user_id={}",
parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type,
parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -555,9 +591,9 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
f32 parameter1;
f32 parameter2;
Core::HID::SixAxisSensorHandle sixaxis_handle;
Core::HID::SixAxisSensorFusionParameters sixaxis_fusion;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
@@ -565,14 +601,14 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSixAxisFusionParameters(parameters.parameter1, parameters.parameter2);
.SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion);
LOG_WARNING(Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, parameter1={}, "
"parameter2={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.parameter1,
parameters.parameter2, parameters.applet_resource_user_id);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, parameter1={}, "
"parameter2={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.sixaxis_fusion.parameter1,
parameters.sixaxis_fusion.parameter2, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -581,35 +617,33 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
f32 parameter1 = 0;
f32 parameter2 = 0;
const auto parameters{rp.PopRaw<Parameters>()};
std::tie(parameter1, parameter2) =
const auto sixaxis_fusion_parameters =
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetSixAxisFusionParameters();
.GetSixAxisFusionParameters(parameters.sixaxis_handle);
LOG_WARNING(
Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push(parameter1);
rb.Push(parameter2);
rb.PushRaw(sixaxis_fusion_parameters);
}
void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
@@ -617,13 +651,12 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.ResetSixAxisFusionParameters();
.ResetSixAxisFusionParameters(parameters.sixaxis_handle);
LOG_WARNING(
Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -631,12 +664,12 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sixaxis_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
const auto sixaxis_handle{rp.PopRaw<Core::HID::SixAxisSensorHandle>()};
const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetGyroscopeZeroDriftMode(drift_mode);
.SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, "
@@ -651,10 +684,11 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -666,21 +700,23 @@ void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetGyroscopeZeroDriftMode());
.GetGyroscopeZeroDriftMode(parameters.sixaxis_handle));
}
void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
const auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard);
.SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -694,10 +730,11 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -709,16 +746,17 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
.IsSixAxisSensorAtRest());
.IsSixAxisSensorAtRest(parameters.sixaxis_handle));
}
void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::SixAxisSensorHandle sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -740,13 +778,14 @@ void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->ActivateController(HidController::Gesture);
LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown,
parameters.applet_resource_user_id);
LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}, applet_resource_user_id={}",
parameters.unknown, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -754,12 +793,20 @@ void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto supported_styleset{rp.Pop<u32>()};
struct Parameters {
Core::HID::NpadStyleSet supported_styleset;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedStyleSet({supported_styleset});
.SetSupportedStyleSet({parameters.supported_styleset});
LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
LOG_DEBUG(Service_HID, "called, supported_styleset={}, applet_resource_user_id={}",
parameters.supported_styleset, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -773,9 +820,9 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetSupportedStyleSet()
.raw);
rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetSupportedStyleSet()
.raw);
}
void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
@@ -818,11 +865,12 @@ void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) {
void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
u32 npad_id;
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
u64 unknown;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -838,10 +886,11 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
u32 npad_id;
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -857,7 +906,7 @@ void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id{rp.Pop<u32>()};
const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()};
LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
@@ -872,16 +921,17 @@ void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
// Should have no effect with how our npad sets up the data
IPC::RequestParser rp{ctx};
struct Parameters {
u32 unknown;
s32 revision;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->ActivateController(HidController::NPad);
LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown,
LOG_DEBUG(Service_HID, "called, revision={}, applet_resource_user_id={}", parameters.revision,
parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -891,7 +941,7 @@ void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
const auto hold_type{rp.PopEnum<Controller_NPad::NpadHoldType>()};
const auto hold_type{rp.PopEnum<Controller_NPad::NpadJoyHoldType>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type);
@@ -916,15 +966,16 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
u32 npad_id;
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Single);
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
parameters.npad_id, parameters.applet_resource_user_id);
@@ -937,16 +988,17 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
// TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault
IPC::RequestParser rp{ctx};
struct Parameters {
u32 npad_id;
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
u64 npad_joy_device_type;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Single);
LOG_WARNING(Service_HID,
"(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
@@ -960,15 +1012,16 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
u32 npad_id;
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual);
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Dual);
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
parameters.npad_id, parameters.applet_resource_user_id);
@@ -979,8 +1032,8 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id_1{rp.Pop<u32>()};
const auto npad_id_2{rp.Pop<u32>()};
const auto npad_id_1{rp.PopEnum<Core::HID::NpadIdType>()};
const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
@@ -1046,8 +1099,8 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id_1{rp.Pop<u32>()};
const auto npad_id_2{rp.Pop<u32>()};
const auto npad_id_1{rp.PopEnum<Core::HID::NpadIdType>()};
const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad)
@@ -1068,10 +1121,11 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
u32 npad_id;
Core::HID::NpadIdType npad_id;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -1089,9 +1143,10 @@ void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& c
struct Parameters {
bool unintended_home_button_input_protection;
INSERT_PADDING_BYTES_NOINIT(3);
u32 npad_id;
Core::HID::NpadIdType npad_id;
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -1113,6 +1168,7 @@ void Hid::SetNpadAnalogStickUseCenterClamp(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
bool analog_stick_use_center_clamp;
INSERT_PADDING_BYTES_NOINIT(7);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
@@ -1132,38 +1188,38 @@ void Hid::SetNpadAnalogStickUseCenterClamp(Kernel::HLERequestContext& ctx) {
void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
VibrationDeviceInfo vibration_device_info;
Core::HID::VibrationDeviceInfo vibration_device_info;
switch (vibration_device_handle.npad_type) {
case Controller_NPad::NpadType::ProController:
case Controller_NPad::NpadType::Handheld:
case Controller_NPad::NpadType::JoyconDual:
case Controller_NPad::NpadType::JoyconLeft:
case Controller_NPad::NpadType::JoyconRight:
case Core::HID::NpadStyleIndex::ProController:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::JoyconLeft:
case Core::HID::NpadStyleIndex::JoyconRight:
default:
vibration_device_info.type = VibrationDeviceType::LinearResonantActuator;
vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
break;
case Controller_NPad::NpadType::GameCube:
vibration_device_info.type = VibrationDeviceType::GcErm;
case Core::HID::NpadStyleIndex::GameCube:
vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
break;
case Controller_NPad::NpadType::Pokeball:
vibration_device_info.type = VibrationDeviceType::Unknown;
case Core::HID::NpadStyleIndex::Pokeball:
vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
break;
}
switch (vibration_device_handle.device_index) {
case Controller_NPad::DeviceIndex::Left:
vibration_device_info.position = VibrationDevicePosition::Left;
case Core::HID::DeviceIndex::Left:
vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
break;
case Controller_NPad::DeviceIndex::Right:
vibration_device_info.position = VibrationDevicePosition::Right;
case Core::HID::DeviceIndex::Right:
vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
break;
case Controller_NPad::DeviceIndex::None:
case Core::HID::DeviceIndex::None:
default:
UNREACHABLE_MSG("DeviceIndex should never be None!");
vibration_device_info.position = VibrationDevicePosition::None;
vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
break;
}
@@ -1178,11 +1234,12 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
Controller_NPad::VibrationValue vibration_value;
Core::HID::VibrationDeviceHandle vibration_device_handle;
Core::HID::VibrationValue vibration_value;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -1202,10 +1259,11 @@ void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
Core::HID::VibrationDeviceHandle vibration_device_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -1256,10 +1314,10 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
const auto handles = ctx.ReadBuffer(0);
const auto vibrations = ctx.ReadBuffer(1);
std::vector<Controller_NPad::DeviceHandle> vibration_device_handles(
handles.size() / sizeof(Controller_NPad::DeviceHandle));
std::vector<Controller_NPad::VibrationValue> vibration_values(
vibrations.size() / sizeof(Controller_NPad::VibrationValue));
std::vector<Core::HID::VibrationDeviceHandle> vibration_device_handles(
handles.size() / sizeof(Core::HID::VibrationDeviceHandle));
std::vector<Core::HID::VibrationValue> vibration_values(vibrations.size() /
sizeof(Core::HID::VibrationValue));
std::memcpy(vibration_device_handles.data(), handles.data(), handles.size());
std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size());
@@ -1276,9 +1334,10 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
Core::HID::VibrationDeviceHandle vibration_device_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
VibrationGcErmCommand gc_erm_command;
Core::HID::VibrationGcErmCommand gc_erm_command;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
@@ -1292,26 +1351,26 @@ void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
*/
const auto vibration_value = [parameters] {
switch (parameters.gc_erm_command) {
case VibrationGcErmCommand::Stop:
return Controller_NPad::VibrationValue{
.amp_low = 0.0f,
.freq_low = 160.0f,
.amp_high = 0.0f,
.freq_high = 320.0f,
case Core::HID::VibrationGcErmCommand::Stop:
return Core::HID::VibrationValue{
.low_amplitude = 0.0f,
.low_frequency = 160.0f,
.high_amplitude = 0.0f,
.high_frequency = 320.0f,
};
case VibrationGcErmCommand::Start:
return Controller_NPad::VibrationValue{
.amp_low = 1.0f,
.freq_low = 160.0f,
.amp_high = 1.0f,
.freq_high = 320.0f,
case Core::HID::VibrationGcErmCommand::Start:
return Core::HID::VibrationValue{
.low_amplitude = 1.0f,
.low_frequency = 160.0f,
.high_amplitude = 1.0f,
.high_frequency = 320.0f,
};
case VibrationGcErmCommand::StopHard:
return Controller_NPad::VibrationValue{
.amp_low = 0.0f,
.freq_low = 0.0f,
.amp_high = 0.0f,
.freq_high = 0.0f,
case Core::HID::VibrationGcErmCommand::StopHard:
return Core::HID::VibrationValue{
.low_amplitude = 0.0f,
.low_frequency = 0.0f,
.high_amplitude = 0.0f,
.high_frequency = 0.0f,
};
default:
return Controller_NPad::DEFAULT_VIBRATION_VALUE;
@@ -1336,7 +1395,7 @@ void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
Core::HID::VibrationDeviceHandle vibration_device_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
@@ -1347,8 +1406,8 @@ void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
.GetLastVibration(parameters.vibration_device_handle);
const auto gc_erm_command = [last_vibration] {
if (last_vibration.amp_low != 0.0f || last_vibration.amp_high != 0.0f) {
return VibrationGcErmCommand::Start;
if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) {
return Core::HID::VibrationGcErmCommand::Start;
}
/**
@@ -1357,11 +1416,11 @@ void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
* SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands.
* This is done to reuse the controller vibration functions made for regular controllers.
*/
if (last_vibration.freq_low == 0.0f && last_vibration.freq_high == 0.0f) {
return VibrationGcErmCommand::StopHard;
if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) {
return Core::HID::VibrationGcErmCommand::StopHard;
}
return VibrationGcErmCommand::Stop;
return Core::HID::VibrationGcErmCommand::Stop;
}();
LOG_DEBUG(Service_HID,
@@ -1401,10 +1460,11 @@ void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
Core::HID::VibrationDeviceHandle vibration_device_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
@@ -1435,18 +1495,18 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(
Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
LOG_WARNING(Service_HID,
"(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
parameters.console_sixaxis_handle.unknown_1,
parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1455,18 +1515,18 @@ void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle sixaxis_handle;
Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(
Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
LOG_WARNING(Service_HID,
"(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
parameters.console_sixaxis_handle.unknown_1,
parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1620,10 +1680,8 @@ void Hid::SetNpadCommunicationMode(Kernel::HLERequestContext& ctx) {
void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
applet_resource_user_id);
LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
@@ -2037,10 +2095,6 @@ public:
}
};
void ReloadInputDevices() {
Settings::values.is_device_reload_pending.store(true);
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<Hid>(system)->InstallAsService(service_manager);
std::make_shared<HidBus>(system)->InstallAsService(service_manager);

View File

@@ -60,21 +60,23 @@ public:
private:
template <typename T>
void MakeController(HidController controller) {
controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(system);
controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(system.HIDCore());
}
template <typename T>
void MakeControllerWithServiceContext(HidController controller) {
controllers[static_cast<std::size_t>(controller)] =
std::make_unique<T>(system, service_context);
std::make_unique<T>(system.HIDCore(), service_context);
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
KernelHelpers::ServiceContext& service_context;
std::shared_ptr<Core::Timing::EventType> pad_update_event;
std::shared_ptr<Core::Timing::EventType> mouse_keyboard_update_event;
std::shared_ptr<Core::Timing::EventType> motion_update_event;
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
@@ -161,38 +163,11 @@ private:
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1,
GcErm = 2,
};
enum class VibrationDevicePosition : u32 {
None = 0,
Left = 1,
Right = 2,
};
enum class VibrationGcErmCommand : u64 {
Stop = 0,
Start = 1,
StopHard = 2,
};
struct VibrationDeviceInfo {
VibrationDeviceType type{};
VibrationDevicePosition position{};
};
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
std::shared_ptr<IAppletResource> applet_resource;
KernelHelpers::ServiceContext service_context;
};
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
/// Registers all HID services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);

View File

@@ -0,0 +1,54 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include <array>
#include "common/common_types.h"
namespace Service::HID {
template <typename State>
struct AtomicStorage {
s64 sampling_number;
State state;
};
template <typename State, std::size_t max_buffer_size>
struct Lifo {
s64 timestamp{};
s64 total_buffer_count = static_cast<s64>(max_buffer_size);
s64 buffer_tail{};
s64 buffer_count{};
std::array<AtomicStorage<State>, max_buffer_size> entries{};
const AtomicStorage<State>& ReadCurrentEntry() const {
return entries[buffer_tail];
}
const AtomicStorage<State>& ReadPreviousEntry() const {
return entries[GetPreviousEntryIndex()];
}
std::size_t GetPreviousEntryIndex() const {
return static_cast<size_t>((buffer_tail + total_buffer_count - 1) % total_buffer_count);
}
std::size_t GetNextEntryIndex() const {
return static_cast<size_t>((buffer_tail + 1) % total_buffer_count);
}
void WriteNextEntry(const State& new_state) {
if (buffer_count < total_buffer_count - 1) {
buffer_count++;
}
buffer_tail = GetNextEntryIndex();
const auto& previous_entry = ReadPreviousEntry();
entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1;
entries[buffer_tail].state = new_state;
}
};
} // namespace Service::HID

View File

@@ -396,12 +396,12 @@ public:
CopyCode(nro_addr + nro_header.segment_headers[DATA_INDEX].memory_offset, data_start,
nro_header.segment_headers[DATA_INDEX].memory_size);
CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
CASCADE_CODE(process->PageTable().SetProcessMemoryPermission(
text_start, ro_start - text_start, Kernel::KMemoryPermission::ReadAndExecute));
CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(ro_start, data_start - ro_start,
Kernel::KMemoryPermission::Read));
CASCADE_CODE(process->PageTable().SetProcessMemoryPermission(
ro_start, data_start - ro_start, Kernel::KMemoryPermission::Read));
return process->PageTable().SetCodeMemoryPermission(
return process->PageTable().SetProcessMemoryPermission(
data_start, bss_end_addr - data_start, Kernel::KMemoryPermission::ReadAndWrite);
}

View File

@@ -13,7 +13,12 @@ namespace Service::PM {
namespace {
constexpr ResultCode ERROR_PROCESS_NOT_FOUND{ErrorModule::PM, 1};
constexpr ResultCode ResultProcessNotFound{ErrorModule::PM, 1};
[[maybe_unused]] constexpr ResultCode ResultAlreadyStarted{ErrorModule::PM, 2};
[[maybe_unused]] constexpr ResultCode ResultNotTerminated{ErrorModule::PM, 3};
[[maybe_unused]] constexpr ResultCode ResultDebugHookInUse{ErrorModule::PM, 4};
[[maybe_unused]] constexpr ResultCode ResultApplicationRunning{ErrorModule::PM, 5};
[[maybe_unused]] constexpr ResultCode ResultInvalidSize{ErrorModule::PM, 6};
constexpr u64 NO_PROCESS_FOUND_PID{0};
@@ -95,18 +100,18 @@ public:
private:
void GetProcessId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
const auto program_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_PM, "called, title_id={:016X}", title_id);
LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
const auto process =
SearchProcessList(kernel.GetProcessList(), [title_id](const auto& proc) {
return proc->GetProgramID() == title_id;
SearchProcessList(kernel.GetProcessList(), [program_id](const auto& proc) {
return proc->GetProgramID() == program_id;
});
if (!process.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_PROCESS_NOT_FOUND);
rb.Push(ResultProcessNotFound);
return;
}
@@ -128,13 +133,16 @@ public:
explicit Info(Core::System& system_, const std::vector<Kernel::KProcess*>& process_list_)
: ServiceFramework{system_, "pm:info"}, process_list{process_list_} {
static const FunctionInfo functions[] = {
{0, &Info::GetTitleId, "GetTitleId"},
{0, &Info::GetProgramId, "GetProgramId"},
{65000, &Info::AtmosphereGetProcessId, "AtmosphereGetProcessId"},
{65001, nullptr, "AtmosphereHasLaunchedProgram"},
{65002, nullptr, "AtmosphereGetProcessInfo"},
};
RegisterHandlers(functions);
}
private:
void GetTitleId(Kernel::HLERequestContext& ctx) {
void GetProgramId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>();
@@ -146,7 +154,7 @@ private:
if (!process.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_PROCESS_NOT_FOUND);
rb.Push(ResultProcessNotFound);
return;
}
@@ -155,6 +163,27 @@ private:
rb.Push((*process)->GetProgramID());
}
void AtmosphereGetProcessId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto program_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
const auto process = SearchProcessList(process_list, [program_id](const auto& proc) {
return proc->GetProgramID() == program_id;
});
if (!process.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultProcessNotFound);
return;
}
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push((*process)->GetProcessID());
}
const std::vector<Kernel::KProcess*>& process_list;
};

View File

@@ -1,36 +1,32 @@
add_library(input_common STATIC
analog_from_button.cpp
analog_from_button.h
keyboard.cpp
keyboard.h
drivers/gc_adapter.cpp
drivers/gc_adapter.h
drivers/keyboard.cpp
drivers/keyboard.h
drivers/mouse.cpp
drivers/mouse.h
drivers/sdl_driver.cpp
drivers/sdl_driver.h
drivers/tas_input.cpp
drivers/tas_input.h
drivers/touch_screen.cpp
drivers/touch_screen.h
drivers/udp_client.cpp
drivers/udp_client.h
helpers/stick_from_buttons.cpp
helpers/stick_from_buttons.h
helpers/touch_from_buttons.cpp
helpers/touch_from_buttons.h
helpers/udp_protocol.cpp
helpers/udp_protocol.h
input_engine.cpp
input_engine.h
input_mapping.cpp
input_mapping.h
input_poller.cpp
input_poller.h
main.cpp
main.h
motion_from_button.cpp
motion_from_button.h
motion_input.cpp
motion_input.h
touch_from_button.cpp
touch_from_button.h
gcadapter/gc_adapter.cpp
gcadapter/gc_adapter.h
gcadapter/gc_poller.cpp
gcadapter/gc_poller.h
mouse/mouse_input.cpp
mouse/mouse_input.h
mouse/mouse_poller.cpp
mouse/mouse_poller.h
sdl/sdl.cpp
sdl/sdl.h
tas/tas_input.cpp
tas/tas_input.h
tas/tas_poller.cpp
tas/tas_poller.h
udp/client.cpp
udp/client.h
udp/protocol.cpp
udp/protocol.h
udp/udp.cpp
udp/udp.h
)
if (MSVC)
@@ -57,8 +53,8 @@ endif()
if (ENABLE_SDL2)
target_sources(input_common PRIVATE
sdl/sdl_impl.cpp
sdl/sdl_impl.h
drivers/sdl_driver.cpp
drivers/sdl_driver.h
)
target_link_libraries(input_common PRIVATE SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)

View File

@@ -1,238 +0,0 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <chrono>
#include <cmath>
#include <thread>
#include "common/math_util.h"
#include "common/settings.h"
#include "input_common/analog_from_button.h"
namespace InputCommon {
class Analog final : public Input::AnalogDevice {
public:
using Button = std::unique_ptr<Input::ButtonDevice>;
Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
float modifier_scale_, float modifier_angle_)
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
modifier_angle(modifier_angle_) {
Input::InputCallback<bool> callbacks{
[this]([[maybe_unused]] bool status) { UpdateStatus(); }};
up->SetCallback(callbacks);
down->SetCallback(callbacks);
left->SetCallback(callbacks);
right->SetCallback(callbacks);
modifier->SetCallback(callbacks);
}
bool IsAngleGreater(float old_angle, float new_angle) const {
constexpr float TAU = Common::PI * 2.0f;
// Use wider angle to ease the transition.
constexpr float aperture = TAU * 0.15f;
const float top_limit = new_angle + aperture;
return (old_angle > new_angle && old_angle <= top_limit) ||
(old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
}
bool IsAngleSmaller(float old_angle, float new_angle) const {
constexpr float TAU = Common::PI * 2.0f;
// Use wider angle to ease the transition.
constexpr float aperture = TAU * 0.15f;
const float bottom_limit = new_angle - aperture;
return (old_angle >= bottom_limit && old_angle < new_angle) ||
(old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
}
float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
constexpr float TAU = Common::PI * 2.0f;
float new_angle = angle;
auto time_difference = static_cast<float>(
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
time_difference /= 1000.0f * 1000.0f;
if (time_difference > 0.5f) {
time_difference = 0.5f;
}
if (IsAngleGreater(new_angle, goal_angle)) {
new_angle -= modifier_angle * time_difference;
if (new_angle < 0) {
new_angle += TAU;
}
if (!IsAngleGreater(new_angle, goal_angle)) {
return goal_angle;
}
} else if (IsAngleSmaller(new_angle, goal_angle)) {
new_angle += modifier_angle * time_difference;
if (new_angle >= TAU) {
new_angle -= TAU;
}
if (!IsAngleSmaller(new_angle, goal_angle)) {
return goal_angle;
}
} else {
return goal_angle;
}
return new_angle;
}
void SetGoalAngle(bool r, bool l, bool u, bool d) {
// Move to the right
if (r && !u && !d) {
goal_angle = 0.0f;
}
// Move to the upper right
if (r && u && !d) {
goal_angle = Common::PI * 0.25f;
}
// Move up
if (u && !l && !r) {
goal_angle = Common::PI * 0.5f;
}
// Move to the upper left
if (l && u && !d) {
goal_angle = Common::PI * 0.75f;
}
// Move to the left
if (l && !u && !d) {
goal_angle = Common::PI;
}
// Move to the bottom left
if (l && !u && d) {
goal_angle = Common::PI * 1.25f;
}
// Move down
if (d && !l && !r) {
goal_angle = Common::PI * 1.5f;
}
// Move to the bottom right
if (r && !u && d) {
goal_angle = Common::PI * 1.75f;
}
}
void UpdateStatus() {
const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
bool r = right->GetStatus();
bool l = left->GetStatus();
bool u = up->GetStatus();
bool d = down->GetStatus();
// Eliminate contradictory movements
if (r && l) {
r = false;
l = false;
}
if (u && d) {
u = false;
d = false;
}
// Move if a key is pressed
if (r || l || u || d) {
amplitude = coef;
} else {
amplitude = 0;
}
const auto now = std::chrono::steady_clock::now();
const auto time_difference = static_cast<u64>(
std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
if (time_difference < 10) {
// Disable analog mode if inputs are too fast
SetGoalAngle(r, l, u, d);
angle = goal_angle;
} else {
angle = GetAngle(now);
SetGoalAngle(r, l, u, d);
}
last_update = now;
}
std::tuple<float, float> GetStatus() const override {
if (Settings::values.emulate_analog_keyboard) {
const auto now = std::chrono::steady_clock::now();
float angle_ = GetAngle(now);
return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude);
}
constexpr float SQRT_HALF = 0.707106781f;
int x = 0, y = 0;
if (right->GetStatus()) {
++x;
}
if (left->GetStatus()) {
--x;
}
if (up->GetStatus()) {
++y;
}
if (down->GetStatus()) {
--y;
}
const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
}
Input::AnalogProperties GetAnalogProperties() const override {
return {modifier_scale, 1.0f, 0.5f};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
switch (direction) {
case Input::AnalogDirection::RIGHT:
return right->GetStatus();
case Input::AnalogDirection::LEFT:
return left->GetStatus();
case Input::AnalogDirection::UP:
return up->GetStatus();
case Input::AnalogDirection::DOWN:
return down->GetStatus();
}
return false;
}
private:
Button up;
Button down;
Button left;
Button right;
Button modifier;
float modifier_scale;
float modifier_angle;
float angle{};
float goal_angle{};
float amplitude{};
std::chrono::time_point<std::chrono::steady_clock> last_update;
};
std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
auto modifier_scale = params.Get("modifier_scale", 0.5f);
auto modifier_angle = params.Get("modifier_angle", 5.5f);
return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
std::move(right), std::move(modifier), modifier_scale,
modifier_angle);
}
} // namespace InputCommon

View File

@@ -2,47 +2,103 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <chrono>
#include <thread>
#include <fmt/format.h>
#include <libusb.h>
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/settings_input.h"
#include "input_common/gcadapter/gc_adapter.h"
#include "common/thread.h"
#include "input_common/drivers/gc_adapter.h"
namespace GCAdapter {
namespace InputCommon {
Adapter::Adapter() {
if (usb_adapter_handle != nullptr) {
class LibUSBContext {
public:
explicit LibUSBContext() {
init_result = libusb_init(&ctx);
}
~LibUSBContext() {
libusb_exit(ctx);
}
LibUSBContext& operator=(const LibUSBContext&) = delete;
LibUSBContext(const LibUSBContext&) = delete;
LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
LibUSBContext(LibUSBContext&&) noexcept = delete;
[[nodiscard]] int InitResult() const noexcept {
return init_result;
}
[[nodiscard]] libusb_context* get() noexcept {
return ctx;
}
private:
libusb_context* ctx;
int init_result{};
};
class LibUSBDeviceHandle {
public:
explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
}
~LibUSBDeviceHandle() noexcept {
if (handle) {
libusb_release_interface(handle, 1);
libusb_close(handle);
}
}
LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
[[nodiscard]] libusb_device_handle* get() noexcept {
return handle;
}
private:
libusb_device_handle* handle{};
};
GCAdapter::GCAdapter(const std::string& input_engine_) : InputEngine(input_engine_) {
if (usb_adapter_handle) {
return;
}
LOG_INFO(Input, "GC Adapter Initialization started");
LOG_DEBUG(Input, "Initialization started");
const int init_res = libusb_init(&libusb_ctx);
libusb_ctx = std::make_unique<LibUSBContext>();
const int init_res = libusb_ctx->InitResult();
if (init_res == LIBUSB_SUCCESS) {
adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
adapter_scan_thread =
std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
} else {
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
}
}
Adapter::~Adapter() {
GCAdapter::~GCAdapter() {
Reset();
}
void Adapter::AdapterInputThread() {
LOG_DEBUG(Input, "GC Adapter input thread started");
void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
LOG_DEBUG(Input, "Input thread started");
Common::SetCurrentThreadName("yuzu:input:GCAdapter");
s32 payload_size{};
AdapterPayload adapter_payload{};
if (adapter_scan_thread.joinable()) {
adapter_scan_thread.join();
}
adapter_scan_thread = {};
while (adapter_input_thread_running) {
libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
while (!stop_token.stop_requested()) {
libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
static_cast<s32>(adapter_payload.size()), &payload_size, 16);
if (IsPayloadCorrect(adapter_payload, payload_size)) {
UpdateControllers(adapter_payload);
@@ -52,19 +108,20 @@ void Adapter::AdapterInputThread() {
}
if (restart_scan_thread) {
adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
adapter_scan_thread =
std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
restart_scan_thread = false;
}
}
bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
if (payload_size != static_cast<s32>(adapter_payload.size()) ||
adapter_payload[0] != LIBUSB_DT_HID) {
LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
adapter_payload[0]);
if (input_error_counter++ > 20) {
LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?");
adapter_input_thread_running = false;
LOG_ERROR(Input, "Timeout, Is the adapter connected?");
adapter_input_thread.request_stop();
restart_scan_thread = true;
}
return false;
@@ -74,7 +131,7 @@ bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payloa
return true;
}
void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) {
for (std::size_t port = 0; port < pads.size(); ++port) {
const std::size_t offset = 1 + (9 * port);
const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
@@ -84,23 +141,24 @@ void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
const u8 b2 = adapter_payload[offset + 2];
UpdateStateButtons(port, b1, b2);
UpdateStateAxes(port, adapter_payload);
if (configuring) {
UpdateYuzuSettings(port);
}
}
}
}
void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
if (pads[port].type == pad_type) {
return;
}
// Device changed reset device and set new type
ResetDevice(port);
pads[port].axis_origin = {};
pads[port].reset_origin_counter = {};
pads[port].enable_vibration = {};
pads[port].rumble_amplitude = {};
pads[port].type = pad_type;
}
void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
[[maybe_unused]] u8 b2) {
if (port >= pads.size()) {
return;
}
@@ -116,25 +174,21 @@ void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
PadButton::TriggerR,
PadButton::TriggerL,
};
pads[port].buttons = 0;
for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
if ((b1 & (1U << i)) != 0) {
pads[port].buttons =
static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i]));
pads[port].last_button = b1_buttons[i];
}
const bool button_status = (b1 & (1U << i)) != 0;
const int button = static_cast<int>(b1_buttons[i]);
SetButton(pads[port].identifier, button, button_status);
}
for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
if ((b2 & (1U << j)) != 0) {
pads[port].buttons =
static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j]));
pads[port].last_button = b2_buttons[j];
}
const bool button_status = (b2 & (1U << j)) != 0;
const int button = static_cast<int>(b2_buttons[j]);
SetButton(pads[port].identifier, button, button_status);
}
}
void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
if (port >= pads.size()) {
return;
}
@@ -155,134 +209,63 @@ void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_pa
pads[port].axis_origin[index] = axis_value;
pads[port].reset_origin_counter++;
}
pads[port].axis_values[index] =
static_cast<s16>(axis_value - pads[port].axis_origin[index]);
const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
}
}
void Adapter::UpdateYuzuSettings(std::size_t port) {
if (port >= pads.size()) {
return;
}
constexpr u8 axis_threshold = 50;
GCPadStatus pad_status = {.port = port};
if (pads[port].buttons != 0) {
pad_status.button = pads[port].last_button;
pad_queue.Push(pad_status);
}
// Accounting for a threshold here to ensure an intentional press
for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
const s16 value = pads[port].axis_values[i];
if (value > axis_threshold || value < -axis_threshold) {
pad_status.axis = static_cast<PadAxes>(i);
pad_status.axis_value = value;
pad_status.axis_threshold = axis_threshold;
pad_queue.Push(pad_status);
}
void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
usb_adapter_handle = nullptr;
pads = {};
while (!stop_token.stop_requested() && !Setup()) {
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
void Adapter::UpdateVibrations() {
// Use 8 states to keep the switching between on/off fast enough for
// a human to not notice the difference between switching from on/off
// More states = more rumble strengths = slower update time
constexpr u8 vibration_states = 8;
vibration_counter = (vibration_counter + 1) % vibration_states;
for (GCController& pad : pads) {
const bool vibrate = pad.rumble_amplitude > vibration_counter;
vibration_changed |= vibrate != pad.enable_vibration;
pad.enable_vibration = vibrate;
}
SendVibrations();
}
void Adapter::SendVibrations() {
if (!rumble_enabled || !vibration_changed) {
return;
}
s32 size{};
constexpr u8 rumble_command = 0x11;
const u8 p1 = pads[0].enable_vibration;
const u8 p2 = pads[1].enable_vibration;
const u8 p3 = pads[2].enable_vibration;
const u8 p4 = pads[3].enable_vibration;
std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
static_cast<s32>(payload.size()), &size, 16);
if (err) {
LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
if (output_error_counter++ > 5) {
LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
rumble_enabled = false;
}
return;
}
output_error_counter = 0;
vibration_changed = false;
}
bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
pads[port].rumble_amplitude = amplitude;
return rumble_enabled;
}
void Adapter::AdapterScanThread() {
adapter_scan_thread_running = true;
adapter_input_thread_running = false;
if (adapter_input_thread.joinable()) {
adapter_input_thread.join();
}
ClearLibusbHandle();
ResetDevices();
while (adapter_scan_thread_running && !adapter_input_thread_running) {
Setup();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void Adapter::Setup() {
usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337);
if (usb_adapter_handle == NULL) {
return;
bool GCAdapter::Setup() {
constexpr u16 nintendo_vid = 0x057e;
constexpr u16 gc_adapter_pid = 0x0337;
usb_adapter_handle =
std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
if (!usb_adapter_handle->get()) {
return false;
}
if (!CheckDeviceAccess()) {
ClearLibusbHandle();
return;
usb_adapter_handle = nullptr;
return false;
}
libusb_device* device = libusb_get_device(usb_adapter_handle);
libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
LOG_INFO(Input, "GC adapter is now connected");
// GC Adapter found and accessible, registering it
if (GetGCEndpoint(device)) {
adapter_scan_thread_running = false;
adapter_input_thread_running = true;
rumble_enabled = true;
input_error_counter = 0;
output_error_counter = 0;
adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
std::size_t port = 0;
for (GCController& pad : pads) {
pad.identifier = {
.guid = Common::UUID{Common::INVALID_UUID},
.port = port++,
.pad = 0,
};
PreSetController(pad.identifier);
}
adapter_input_thread =
std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
return true;
}
return false;
}
bool Adapter::CheckDeviceAccess() {
// This fixes payload problems from offbrand GCAdapters
const s32 control_transfer_error =
libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
if (control_transfer_error < 0) {
LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
}
s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
bool GCAdapter::CheckDeviceAccess() {
s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
if (kernel_driver_error == 1) {
kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0);
if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
kernel_driver_error);
@@ -290,23 +273,28 @@ bool Adapter::CheckDeviceAccess() {
}
if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
return false;
}
const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
if (interface_claim_error) {
LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
return false;
}
// This fixes payload problems from offbrand GCAdapters
const s32 control_transfer_error =
libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
if (control_transfer_error < 0) {
LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
}
return true;
}
bool Adapter::GetGCEndpoint(libusb_device* device) {
bool GCAdapter::GetGCEndpoint(libusb_device* device) {
libusb_config_descriptor* config = nullptr;
const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
if (config_descriptor_return != LIBUSB_SUCCESS) {
@@ -332,77 +320,95 @@ bool Adapter::GetGCEndpoint(libusb_device* device) {
// This transfer seems to be responsible for clearing the state of the adapter
// Used to clear the "busy" state of when the device is unexpectedly unplugged
unsigned char clear_payload = 0x13;
libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload,
sizeof(clear_payload), nullptr, 16);
return true;
}
void Adapter::JoinThreads() {
restart_scan_thread = false;
adapter_input_thread_running = false;
adapter_scan_thread_running = false;
Common::Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier,
const Common::Input::VibrationStatus vibration) {
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
const auto processed_amplitude =
static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
if (adapter_scan_thread.joinable()) {
adapter_scan_thread.join();
}
pads[identifier.port].rumble_amplitude = processed_amplitude;
if (adapter_input_thread.joinable()) {
adapter_input_thread.join();
if (!rumble_enabled) {
return Common::Input::VibrationError::Disabled;
}
return Common::Input::VibrationError::None;
}
void Adapter::ClearLibusbHandle() {
if (usb_adapter_handle) {
libusb_release_interface(usb_adapter_handle, 1);
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
void GCAdapter::UpdateVibrations() {
// Use 8 states to keep the switching between on/off fast enough for
// a human to feel different vibration strenght
// More states == more rumble strengths == slower update time
constexpr u8 vibration_states = 8;
vibration_counter = (vibration_counter + 1) % vibration_states;
for (GCController& pad : pads) {
const bool vibrate = pad.rumble_amplitude > vibration_counter;
vibration_changed |= vibrate != pad.enable_vibration;
pad.enable_vibration = vibrate;
}
SendVibrations();
}
void Adapter::ResetDevices() {
for (std::size_t i = 0; i < pads.size(); ++i) {
ResetDevice(i);
void GCAdapter::SendVibrations() {
if (!rumble_enabled || !vibration_changed) {
return;
}
}
void Adapter::ResetDevice(std::size_t port) {
pads[port].type = ControllerTypes::None;
pads[port].enable_vibration = false;
pads[port].rumble_amplitude = 0;
pads[port].buttons = 0;
pads[port].last_button = PadButton::Undefined;
pads[port].axis_values.fill(0);
pads[port].reset_origin_counter = 0;
}
void Adapter::Reset() {
JoinThreads();
ClearLibusbHandle();
ResetDevices();
if (libusb_ctx) {
libusb_exit(libusb_ctx);
s32 size{};
constexpr u8 rumble_command = 0x11;
const u8 p1 = pads[0].enable_vibration;
const u8 p2 = pads[1].enable_vibration;
const u8 p3 = pads[2].enable_vibration;
const u8 p4 = pads[3].enable_vibration;
std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
const int err =
libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
static_cast<s32>(payload.size()), &size, 16);
if (err) {
LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err));
if (output_error_counter++ > 5) {
LOG_ERROR(Input, "Output timeout, Rumble disabled");
rumble_enabled = false;
}
return;
}
output_error_counter = 0;
vibration_changed = false;
}
std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
bool GCAdapter::DeviceConnected(std::size_t port) const {
return pads[port].type != ControllerTypes::None;
}
void GCAdapter::Reset() {
adapter_scan_thread = {};
adapter_input_thread = {};
usb_adapter_handle = nullptr;
pads = {};
libusb_ctx = nullptr;
}
std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
for (std::size_t port = 0; port < pads.size(); ++port) {
if (!DeviceConnected(port)) {
continue;
}
std::string name = fmt::format("Gamecube Controller {}", port + 1);
devices.emplace_back(Common::ParamPackage{
{"class", "gcpad"},
{"display", std::move(name)},
{"port", std::to_string(port)},
});
Common::ParamPackage identifier{};
identifier.Set("engine", GetEngineName());
identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1));
identifier.Set("port", static_cast<int>(port));
devices.emplace_back(identifier);
}
return devices;
}
InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
const Common::ParamPackage& params) const {
ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
// This list is missing ZL/ZR since those are not considered buttons.
// We will add those afterwards
// This list also excludes any button that can't be really mapped
@@ -425,47 +431,49 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
return {};
}
InputCommon::ButtonMapping mapping{};
ButtonMapping mapping{};
for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
Common::ParamPackage button_params({{"engine", "gcpad"}});
Common::ParamPackage button_params{};
button_params.Set("engine", GetEngineName());
button_params.Set("port", params.Get("port", 0));
button_params.Set("button", static_cast<int>(gcadapter_button));
mapping.insert_or_assign(switch_button, std::move(button_params));
}
// Add the missing bindings for ZL/ZR
static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
switch_to_gcadapter_axis = {
std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
{Settings::NativeButton::ZR, PadAxes::TriggerRight},
std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
{Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
};
for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
Common::ParamPackage button_params({{"engine", "gcpad"}});
for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
Common::ParamPackage button_params{};
button_params.Set("engine", GetEngineName());
button_params.Set("port", params.Get("port", 0));
button_params.Set("button", static_cast<s32>(PadButton::Stick));
button_params.Set("button", static_cast<s32>(gcadapter_buton));
button_params.Set("axis", static_cast<s32>(gcadapter_axis));
button_params.Set("threshold", 0.5f);
button_params.Set("range", 1.9f);
button_params.Set("direction", "+");
mapping.insert_or_assign(switch_button, std::move(button_params));
}
return mapping;
}
InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
const Common::ParamPackage& params) const {
AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("port")) {
return {};
}
InputCommon::AnalogMapping mapping = {};
AnalogMapping mapping = {};
Common::ParamPackage left_analog_params;
left_analog_params.Set("engine", "gcpad");
left_analog_params.Set("engine", GetEngineName());
left_analog_params.Set("port", params.Get("port", 0));
left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
Common::ParamPackage right_analog_params;
right_analog_params.Set("engine", "gcpad");
right_analog_params.Set("engine", GetEngineName());
right_analog_params.Set("port", params.Get("port", 0));
right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
@@ -473,34 +481,47 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
return mapping;
}
bool Adapter::DeviceConnected(std::size_t port) const {
return pads[port].type != ControllerTypes::None;
Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
PadButton button = static_cast<PadButton>(params.Get("button", 0));
switch (button) {
case PadButton::ButtonLeft:
return Common::Input::ButtonNames::ButtonLeft;
case PadButton::ButtonRight:
return Common::Input::ButtonNames::ButtonRight;
case PadButton::ButtonDown:
return Common::Input::ButtonNames::ButtonDown;
case PadButton::ButtonUp:
return Common::Input::ButtonNames::ButtonUp;
case PadButton::TriggerZ:
return Common::Input::ButtonNames::TriggerZ;
case PadButton::TriggerR:
return Common::Input::ButtonNames::TriggerR;
case PadButton::TriggerL:
return Common::Input::ButtonNames::TriggerL;
case PadButton::ButtonA:
return Common::Input::ButtonNames::ButtonA;
case PadButton::ButtonB:
return Common::Input::ButtonNames::ButtonB;
case PadButton::ButtonX:
return Common::Input::ButtonNames::ButtonX;
case PadButton::ButtonY:
return Common::Input::ButtonNames::ButtonY;
case PadButton::ButtonStart:
return Common::Input::ButtonNames::ButtonStart;
default:
return Common::Input::ButtonNames::Undefined;
}
}
void Adapter::BeginConfiguration() {
pad_queue.Clear();
configuring = true;
Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
return GetUIButtonName(params);
}
if (params.Has("axis")) {
return Common::Input::ButtonNames::Value;
}
return Common::Input::ButtonNames::Invalid;
}
void Adapter::EndConfiguration() {
pad_queue.Clear();
configuring = false;
}
Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {
return pad_queue;
}
const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {
return pad_queue;
}
GCController& Adapter::GetPadState(std::size_t port) {
return pads.at(port);
}
const GCController& Adapter::GetPadState(std::size_t port) const {
return pads.at(port);
}
} // namespace GCAdapter
} // namespace InputCommon

View File

@@ -0,0 +1,135 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <mutex>
#include <stop_token>
#include <string>
#include <thread>
#include "input_common/input_engine.h"
struct libusb_context;
struct libusb_device;
struct libusb_device_handle;
namespace InputCommon {
class LibUSBContext;
class LibUSBDeviceHandle;
class GCAdapter : public InputCommon::InputEngine {
public:
explicit GCAdapter(const std::string& input_engine_);
~GCAdapter();
Common::Input::VibrationError SetRumble(
const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override;
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const override;
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
private:
enum class PadButton {
Undefined = 0x0000,
ButtonLeft = 0x0001,
ButtonRight = 0x0002,
ButtonDown = 0x0004,
ButtonUp = 0x0008,
TriggerZ = 0x0010,
TriggerR = 0x0020,
TriggerL = 0x0040,
ButtonA = 0x0100,
ButtonB = 0x0200,
ButtonX = 0x0400,
ButtonY = 0x0800,
ButtonStart = 0x1000,
};
enum class PadAxes : u8 {
StickX,
StickY,
SubstickX,
SubstickY,
TriggerLeft,
TriggerRight,
Undefined,
};
enum class ControllerTypes {
None,
Wired,
Wireless,
};
struct GCController {
ControllerTypes type = ControllerTypes::None;
PadIdentifier identifier{};
bool enable_vibration = false;
u8 rumble_amplitude{};
std::array<u8, 6> axis_origin{};
u8 reset_origin_counter{};
};
using AdapterPayload = std::array<u8, 37>;
void UpdatePadType(std::size_t port, ControllerTypes pad_type);
void UpdateControllers(const AdapterPayload& adapter_payload);
void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
void AdapterInputThread(std::stop_token stop_token);
void AdapterScanThread(std::stop_token stop_token);
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
/// For use in initialization, querying devices to find the adapter
bool Setup();
/// Returns true if we successfully gain access to GC Adapter
bool CheckDeviceAccess();
/// Captures GC Adapter endpoint address
/// Returns true if the endpoint was set correctly
bool GetGCEndpoint(libusb_device* device);
/// Returns true if there is a device connected to port
bool DeviceConnected(std::size_t port) const;
/// For shutting down, clear all data, join all threads, release usb
void Reset();
void UpdateVibrations();
/// Updates vibration state of all controllers
void SendVibrations();
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
std::array<GCController, 4> pads;
std::jthread adapter_input_thread;
std::jthread adapter_scan_thread;
bool restart_scan_thread{};
std::unique_ptr<LibUSBContext> libusb_ctx;
u8 input_endpoint{0};
u8 output_endpoint{0};
u8 input_error_counter{0};
u8 output_error_counter{0};
int vibration_counter{0};
bool rumble_enabled{true};
bool vibration_changed{true};
};
} // namespace InputCommon

View File

@@ -0,0 +1,112 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/param_package.h"
#include "common/settings_input.h"
#include "input_common/drivers/keyboard.h"
namespace InputCommon {
constexpr PadIdentifier key_identifier = {
.guid = Common::UUID{Common::INVALID_UUID},
.port = 0,
.pad = 0,
};
constexpr PadIdentifier keyboard_key_identifier = {
.guid = Common::UUID{Common::INVALID_UUID},
.port = 1,
.pad = 0,
};
constexpr PadIdentifier keyboard_modifier_identifier = {
.guid = Common::UUID{Common::INVALID_UUID},
.port = 1,
.pad = 1,
};
Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) {
// Keyboard is broken into 3 diferent sets:
// key: Unfiltered intended for controllers.
// keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation.
// keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard
// emulation.
PreSetController(key_identifier);
PreSetController(keyboard_key_identifier);
PreSetController(keyboard_modifier_identifier);
}
void Keyboard::PressKey(int key_code) {
SetButton(key_identifier, key_code, true);
}
void Keyboard::ReleaseKey(int key_code) {
SetButton(key_identifier, key_code, false);
}
void Keyboard::PressKeyboardKey(int key_index) {
if (key_index == Settings::NativeKeyboard::None) {
return;
}
SetButton(keyboard_key_identifier, key_index, true);
}
void Keyboard::ReleaseKeyboardKey(int key_index) {
if (key_index == Settings::NativeKeyboard::None) {
return;
}
SetButton(keyboard_key_identifier, key_index, false);
}
void Keyboard::SetKeyboardModifiers(int key_modifiers) {
for (int i = 0; i < 32; ++i) {
bool key_value = ((key_modifiers >> i) & 0x1) != 0;
SetButton(keyboard_modifier_identifier, i, key_value);
// Use the modifier to press the key button equivalent
switch (i) {
case Settings::NativeKeyboard::LeftControl:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value);
break;
case Settings::NativeKeyboard::LeftShift:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value);
break;
case Settings::NativeKeyboard::LeftAlt:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value);
break;
case Settings::NativeKeyboard::LeftMeta:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value);
break;
case Settings::NativeKeyboard::RightControl:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey,
key_value);
break;
case Settings::NativeKeyboard::RightShift:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value);
break;
case Settings::NativeKeyboard::RightAlt:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value);
break;
case Settings::NativeKeyboard::RightMeta:
SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value);
break;
default:
// Other modifier keys should be pressed with PressKey since they stay enabled until
// next press
break;
}
}
}
void Keyboard::ReleaseAllKeys() {
ResetButtonState();
}
std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", "Keyboard Only"},
});
return devices;
}
} // namespace InputCommon

View File

@@ -0,0 +1,56 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include "input_common/input_engine.h"
namespace InputCommon {
/**
* A button device factory representing a keyboard. It receives keyboard events and forward them
* to all button devices it created.
*/
class Keyboard final : public InputCommon::InputEngine {
public:
explicit Keyboard(const std::string& input_engine_);
/**
* Sets the status of all buttons bound with the key to pressed
* @param key_code the code of the key to press
*/
void PressKey(int key_code);
/**
* Sets the status of all buttons bound with the key to released
* @param key_code the code of the key to release
*/
void ReleaseKey(int key_code);
/**
* Sets the status of the keyboard key to pressed
* @param key_index index of the key to press
*/
void PressKeyboardKey(int key_index);
/**
* Sets the status of the keyboard key to released
* @param key_index index of the key to release
*/
void ReleaseKeyboardKey(int key_index);
/**
* Sets the status of all keyboard modifier keys
* @param key_modifiers the code of the key to release
*/
void SetKeyboardModifiers(int key_modifiers);
/// Sets all keys to the non pressed state
void ReleaseAllKeys();
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const override;
};
} // namespace InputCommon

View File

@@ -0,0 +1,185 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <stop_token>
#include <thread>
#include <fmt/format.h>
#include "common/param_package.h"
#include "common/settings.h"
#include "common/thread.h"
#include "input_common/drivers/mouse.h"
namespace InputCommon {
constexpr int mouse_axis_x = 0;
constexpr int mouse_axis_y = 1;
constexpr int wheel_axis_x = 2;
constexpr int wheel_axis_y = 3;
constexpr int touch_axis_x = 10;
constexpr int touch_axis_y = 11;
constexpr PadIdentifier identifier = {
.guid = Common::UUID{Common::INVALID_UUID},
.port = 0,
.pad = 0,
};
Mouse::Mouse(const std::string& input_engine_) : InputEngine(input_engine_) {
PreSetController(identifier);
PreSetAxis(identifier, mouse_axis_x);
PreSetAxis(identifier, mouse_axis_y);
PreSetAxis(identifier, wheel_axis_x);
PreSetAxis(identifier, wheel_axis_y);
PreSetAxis(identifier, touch_axis_x);
PreSetAxis(identifier, touch_axis_x);
update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
}
void Mouse::UpdateThread(std::stop_token stop_token) {
Common::SetCurrentThreadName("yuzu:input:Mouse");
constexpr int update_time = 10;
while (!stop_token.stop_requested()) {
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
// Slow movement by 4%
last_mouse_change *= 0.96f;
const float sensitivity =
Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f;
SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity);
SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity);
}
if (mouse_panning_timout++ > 20) {
StopPanning();
}
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
}
}
void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
// If native mouse is enabled just set the screen coordinates
if (Settings::values.mouse_enabled) {
SetAxis(identifier, mouse_axis_x, touch_x);
SetAxis(identifier, mouse_axis_y, touch_y);
return;
}
SetAxis(identifier, touch_axis_x, touch_x);
SetAxis(identifier, touch_axis_y, touch_y);
if (Settings::values.mouse_panning) {
auto mouse_change =
(Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
mouse_panning_timout = 0;
const auto move_distance = mouse_change.Length();
if (move_distance == 0) {
return;
}
// Make slow movements at least 3 units on lenght
if (move_distance < 3.0f) {
// Normalize value
mouse_change /= move_distance;
mouse_change *= 3.0f;
}
// Average mouse movements
last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
const auto last_move_distance = last_mouse_change.Length();
// Make fast movements clamp to 8 units on lenght
if (last_move_distance > 8.0f) {
// Normalize value
last_mouse_change /= last_move_distance;
last_mouse_change *= 8.0f;
}
// Ignore average if it's less than 1 unit and use current movement value
if (last_move_distance < 1.0f) {
last_mouse_change = mouse_change / mouse_change.Length();
}
return;
}
if (button_pressed) {
const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
}
}
void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
SetAxis(identifier, touch_axis_x, touch_x);
SetAxis(identifier, touch_axis_y, touch_y);
SetButton(identifier, static_cast<int>(button), true);
// Set initial analog parameters
mouse_origin = {x, y};
last_mouse_position = {x, y};
button_pressed = true;
}
void Mouse::ReleaseButton(MouseButton button) {
SetButton(identifier, static_cast<int>(button), false);
if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
SetAxis(identifier, mouse_axis_x, 0);
SetAxis(identifier, mouse_axis_y, 0);
}
button_pressed = false;
}
void Mouse::MouseWheelChange(int x, int y) {
wheel_position.x += x;
wheel_position.y += y;
SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x));
SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y));
}
void Mouse::ReleaseAllButtons() {
ResetButtonState();
button_pressed = false;
}
void Mouse::StopPanning() {
last_mouse_change = {};
}
std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", "Keyboard/Mouse"},
});
return devices;
}
AnalogMapping Mouse::GetAnalogMappingForDevice(
[[maybe_unused]] const Common::ParamPackage& params) {
// Only overwrite different buttons from default
AnalogMapping mapping = {};
Common::ParamPackage right_analog_params;
right_analog_params.Set("engine", GetEngineName());
right_analog_params.Set("axis_x", 0);
right_analog_params.Set("axis_y", 1);
right_analog_params.Set("threshold", 0.5f);
right_analog_params.Set("range", 1.0f);
right_analog_params.Set("deadzone", 0.0f);
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
return mapping;
}
Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("axis")) {
return Common::Input::ButtonNames::Value;
}
return Common::Input::ButtonNames::Invalid;
}
} // namespace InputCommon

View File

@@ -0,0 +1,81 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include <stop_token>
#include <thread>
#include "common/vector_math.h"
#include "input_common/input_engine.h"
namespace InputCommon {
enum class MouseButton {
Left,
Right,
Wheel,
Backward,
Forward,
Task,
Extra,
Undefined,
};
/**
* A button device factory representing a keyboard. It receives keyboard events and forward them
* to all button devices it created.
*/
class Mouse final : public InputCommon::InputEngine {
public:
explicit Mouse(const std::string& input_engine_);
/**
* Signals that mouse has moved.
* @param x the x-coordinate of the cursor
* @param y the y-coordinate of the cursor
* @param center_x the x-coordinate of the middle of the screen
* @param center_y the y-coordinate of the middle of the screen
*/
void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
/**
* Sets the status of all buttons bound with the key to pressed
* @param key_code the code of the key to press
*/
void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
/**
* Sets the status of all buttons bound with the key to released
* @param key_code the code of the key to release
*/
void ReleaseButton(MouseButton button);
/**
* Sets the status of the mouse wheel
* @param x delta movement in the x direction
* @param y delta movement in the y direction
*/
void MouseWheelChange(int x, int y);
void ReleaseAllButtons();
std::vector<Common::ParamPackage> GetInputDevices() const override;
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
private:
void UpdateThread(std::stop_token stop_token);
void StopPanning();
Common::Vec2<int> mouse_origin;
Common::Vec2<int> last_mouse_position;
Common::Vec2<float> last_mouse_change;
Common::Vec2<int> wheel_position;
bool button_pressed;
int mouse_panning_timout{};
std::jthread update_thread;
};
} // namespace InputCommon

View File

@@ -0,0 +1,924 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "common/thread.h"
#include "common/vector_math.h"
#include "input_common/drivers/sdl_driver.h"
namespace InputCommon {
namespace {
std::string GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
char guid_str[33];
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
return guid_str;
}
} // Anonymous namespace
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
auto* const sdl_state = static_cast<SDLDriver*>(user_data);
sdl_state->HandleGameControllerEvent(*event);
return 0;
}
class SDLJoystick {
public:
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
SDL_GameController* game_controller)
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
sdl_controller{game_controller, &SDL_GameControllerClose} {
EnableMotion();
}
void EnableMotion() {
if (sdl_controller) {
SDL_GameController* controller = sdl_controller.get();
if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
has_accel = true;
}
if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
has_gyro = true;
}
}
}
bool HasGyro() const {
return has_gyro;
}
bool HasAccel() const {
return has_accel;
}
bool UpdateMotion(SDL_ControllerSensorEvent event) {
constexpr float gravity_constant = 9.80665f;
std::lock_guard lock{mutex};
const u64 time_difference = event.timestamp - last_motion_update;
last_motion_update = event.timestamp;
switch (event.sensor) {
case SDL_SENSOR_ACCEL: {
motion.accel_x = -event.data[0] / gravity_constant;
motion.accel_y = event.data[2] / gravity_constant;
motion.accel_z = -event.data[1] / gravity_constant;
break;
}
case SDL_SENSOR_GYRO: {
motion.gyro_x = event.data[0] / (Common::PI * 2);
motion.gyro_y = -event.data[2] / (Common::PI * 2);
motion.gyro_z = event.data[1] / (Common::PI * 2);
break;
}
}
// Ignore duplicated timestamps
if (time_difference == 0) {
return false;
}
motion.delta_timestamp = time_difference * 1000;
return true;
}
BasicMotion GetMotion() {
return motion;
}
bool RumblePlay(const Common::Input::VibrationStatus vibration) {
constexpr u32 rumble_max_duration_ms = 1000;
if (sdl_controller) {
return SDL_GameControllerRumble(
sdl_controller.get(), static_cast<u16>(vibration.low_amplitude),
static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
} else if (sdl_joystick) {
return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
static_cast<u16>(vibration.high_amplitude),
rumble_max_duration_ms) != -1;
}
return false;
}
bool HasHDRumble() const {
if (sdl_controller) {
return (SDL_GameControllerGetType(sdl_controller.get()) ==
SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO);
}
return false;
}
/**
* The Pad identifier of the joystick
*/
const PadIdentifier GetPadIdentifier() const {
return {
.guid = Common::UUID{guid},
.port = static_cast<std::size_t>(port),
.pad = 0,
};
}
/**
* The guid of the joystick
*/
const std::string& GetGUID() const {
return guid;
}
/**
* The number of joystick from the same type that were connected before this joystick
*/
int GetPort() const {
return port;
}
SDL_Joystick* GetSDLJoystick() const {
return sdl_joystick.get();
}
SDL_GameController* GetSDLGameController() const {
return sdl_controller.get();
}
void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
sdl_joystick.reset(joystick);
sdl_controller.reset(controller);
}
bool IsJoyconLeft() const {
const std::string controller_name = GetControllerName();
if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
return true;
}
if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
return true;
}
return false;
}
bool IsJoyconRight() const {
const std::string controller_name = GetControllerName();
if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
return true;
}
if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
return true;
}
return false;
}
BatteryLevel GetBatteryLevel() {
const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
switch (level) {
case SDL_JOYSTICK_POWER_EMPTY:
return BatteryLevel::Empty;
case SDL_JOYSTICK_POWER_LOW:
return BatteryLevel::Critical;
case SDL_JOYSTICK_POWER_MEDIUM:
return BatteryLevel::Low;
case SDL_JOYSTICK_POWER_FULL:
return BatteryLevel::Medium;
case SDL_JOYSTICK_POWER_MAX:
return BatteryLevel::Full;
case SDL_JOYSTICK_POWER_UNKNOWN:
case SDL_JOYSTICK_POWER_WIRED:
default:
return BatteryLevel::Charging;
}
}
std::string GetControllerName() const {
if (sdl_controller) {
switch (SDL_GameControllerGetType(sdl_controller.get())) {
case SDL_CONTROLLER_TYPE_XBOX360:
return "XBox 360 Controller";
case SDL_CONTROLLER_TYPE_XBOXONE:
return "XBox One Controller";
default:
break;
}
const auto name = SDL_GameControllerName(sdl_controller.get());
if (name) {
return name;
}
}
if (sdl_joystick) {
const auto name = SDL_JoystickName(sdl_joystick.get());
if (name) {
return name;
}
}
return "Unknown";
}
private:
std::string guid;
int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
mutable std::mutex mutex;
u64 last_motion_update{};
bool has_gyro{false};
bool has_accel{false};
BasicMotion motion;
};
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
std::lock_guard lock{joystick_map_mutex};
const auto it = joystick_map.find(guid);
if (it != joystick_map.end()) {
while (it->second.size() <= static_cast<std::size_t>(port)) {
auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
nullptr, nullptr);
it->second.emplace_back(std::move(joystick));
}
return it->second[static_cast<std::size_t>(port)];
}
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
return joystick_map[guid].emplace_back(std::move(joystick));
}
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
const auto map_it = joystick_map.find(guid);
if (map_it == joystick_map.end()) {
return nullptr;
}
const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
[&sdl_joystick](const auto& joystick) {
return joystick->GetSDLJoystick() == sdl_joystick;
});
if (vec_it == map_it->second.end()) {
return nullptr;
}
return *vec_it;
}
void SDLDriver::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
SDL_GameController* sdl_gamecontroller = nullptr;
if (SDL_IsGameController(joystick_index)) {
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
}
if (!sdl_joystick) {
LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
return;
}
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
PreSetController(joystick->GetPadIdentifier());
SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
joystick_map[guid].emplace_back(std::move(joystick));
return;
}
auto& joystick_guid_list = joystick_map[guid];
const auto joystick_it =
std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
[](const auto& joystick) { return !joystick->GetSDLJoystick(); });
if (joystick_it != joystick_guid_list.end()) {
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
return;
}
const int port = static_cast<int>(joystick_guid_list.size());
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
PreSetController(joystick->GetPadIdentifier());
SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
joystick_guid_list.emplace_back(std::move(joystick));
}
void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
// This call to guid is safe since the joystick is guaranteed to be in the map
const auto& joystick_guid_list = joystick_map[guid];
const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
[&sdl_joystick](const auto& joystick) {
return joystick->GetSDLJoystick() == sdl_joystick;
});
if (joystick_it != joystick_guid_list.end()) {
(*joystick_it)->SetSDLJoystick(nullptr, nullptr);
}
}
void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
switch (event.type) {
case SDL_JOYBUTTONUP: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, false);
}
break;
}
case SDL_JOYBUTTONDOWN: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetButton(identifier, event.jbutton.button, true);
}
break;
}
case SDL_JOYHATMOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetHatButton(identifier, event.jhat.hat, event.jhat.value);
}
break;
}
case SDL_JOYAXISMOTION: {
if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
}
break;
}
case SDL_CONTROLLERSENSORUPDATE: {
if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
if (joystick->UpdateMotion(event.csensor)) {
const PadIdentifier identifier = joystick->GetPadIdentifier();
SetMotion(identifier, 0, joystick->GetMotion());
};
}
break;
}
case SDL_JOYDEVICEREMOVED:
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
break;
case SDL_JOYDEVICEADDED:
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
InitJoystick(event.jdevice.which);
break;
}
}
void SDLDriver::CloseJoysticks() {
std::lock_guard lock{joystick_map_mutex};
joystick_map.clear();
}
SDLDriver::SDLDriver(const std::string& input_engine_) : InputEngine(input_engine_) {
Common::SetCurrentThreadName("yuzu:input:SDL");
if (!Settings::values.enable_raw_input) {
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
}
// Prevent SDL from adding undesired axis
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
// not a generic one
SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1");
// Turn off Pro controller home led
SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0");
// If the frontend is going to manage the event loop, then we don't start one here
start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
return;
}
SDL_AddEventWatch(&SDLEventWatcher, this);
initialized = true;
if (start_thread) {
poll_thread = std::thread([this] {
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
std::this_thread::sleep_for(1ms);
}
});
}
// Because the events for joystick connection happens before we have our event watcher added, we
// can just open all the joysticks right here
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
InitJoystick(i);
}
}
SDLDriver::~SDLDriver() {
CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this);
initialized = false;
if (start_thread) {
poll_thread.join();
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
}
}
std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
for (const auto& [key, value] : joystick_map) {
for (const auto& joystick : value) {
if (!joystick->GetSDLJoystick()) {
continue;
}
const std::string name =
fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", std::move(name)},
{"guid", joystick->GetGUID()},
{"port", std::to_string(joystick->GetPort())},
});
if (joystick->IsJoyconLeft()) {
joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
}
}
}
// Add dual controllers
for (const auto& [key, value] : joystick_map) {
for (const auto& joystick : value) {
if (joystick->IsJoyconRight()) {
if (!joycon_pairs.contains(joystick->GetPort())) {
continue;
}
const auto joystick2 = joycon_pairs.at(joystick->GetPort());
const std::string name =
fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", std::move(name)},
{"guid", joystick->GetGUID()},
{"guid2", joystick2->GetGUID()},
{"port", std::to_string(joystick->GetPort())},
});
}
}
}
return devices;
}
Common::Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier,
const Common::Input::VibrationStatus vibration) {
const auto joystick =
GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port));
const auto process_amplitude_exp = [](f32 amplitude, f32 factor) {
return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
};
// Default exponential curve for rumble
f32 factor = 0.35f;
// If vibration is set as a linear output use a flatter value
if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
factor = 0.5f;
}
// Amplitude for HD rumble needs no modification
if (joystick->HasHDRumble()) {
factor = 1.0f;
}
const Common::Input::VibrationStatus new_vibration{
.low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor),
.low_frequency = vibration.low_frequency,
.high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor),
.high_frequency = vibration.high_frequency,
.type = Common::Input::VibrationAmplificationType::Exponential,
};
if (!joystick->RumblePlay(new_vibration)) {
return Common::Input::VibrationError::Unknown;
}
return Common::Input::VibrationError::None;
}
Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
s32 axis, float value) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("port", port);
params.Set("guid", std::move(guid));
params.Set("axis", axis);
params.Set("threshold", "0.5");
params.Set("invert", value < 0 ? "-" : "+");
return params;
}
Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid,
s32 button) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("port", port);
params.Set("guid", std::move(guid));
params.Set("button", button);
return params;
}
Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
u8 value) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("port", port);
params.Set("guid", std::move(guid));
params.Set("hat", hat);
params.Set("direction", GetHatButtonName(value));
return params;
}
Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const {
Common::ParamPackage params{};
params.Set("engine", GetEngineName());
params.Set("motion", 0);
params.Set("port", port);
params.Set("guid", std::move(guid));
return params;
}
Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const {
switch (binding.bindType) {
case SDL_CONTROLLER_BINDTYPE_NONE:
break;
case SDL_CONTROLLER_BINDTYPE_AXIS:
return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
case SDL_CONTROLLER_BINDTYPE_BUTTON:
return BuildButtonParamPackageForButton(port, guid, binding.value.button);
case SDL_CONTROLLER_BINDTYPE_HAT:
return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
static_cast<u8>(binding.value.hat.hat_mask));
}
return {};
}
Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
int axis_y, float offset_x,
float offset_y) const {
Common::ParamPackage params;
params.Set("engine", GetEngineName());
params.Set("port", static_cast<int>(identifier.port));
params.Set("guid", identifier.guid.Format());
params.Set("axis_x", axis_x);
params.Set("axis_y", axis_y);
params.Set("offset_x", offset_x);
params.Set("offset_y", offset_y);
params.Set("invert_x", "+");
params.Set("invert_y", "+");
return params;
}
ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("guid") || !params.Has("port")) {
return {};
}
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
return {};
}
// This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
// We will add those afterwards
// This list also excludes Screenshot since theres not really a mapping for that
ButtonBindings switch_to_sdl_button;
if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
switch_to_sdl_button = GetNintendoButtonBinding(joystick);
} else {
switch_to_sdl_button = GetDefaultButtonBinding();
}
// Add the missing bindings for ZL/ZR
static constexpr ZButtonBindings switch_to_sdl_axis{{
{Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
{Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
}};
// Parameters contain two joysticks return dual
if (params.Has("guid2")) {
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
if (joystick2->GetSDLGameController() != nullptr) {
return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
switch_to_sdl_axis);
}
}
return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
}
ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
return {
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
{Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
};
}
ButtonBindings SDLDriver::GetNintendoButtonBinding(
const std::shared_ptr<SDLJoystick>& joystick) const {
// Default SL/SR mapping for pro controllers
auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
if (joystick->IsJoyconLeft()) {
sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
}
if (joystick->IsJoyconRight()) {
sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
}
return {
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
{Settings::NativeButton::SL, sl_button},
{Settings::NativeButton::SR, sr_button},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
};
}
ButtonMapping SDLDriver::GetSingleControllerMapping(
const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const {
ButtonMapping mapping;
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
auto* controller = joystick->GetSDLGameController();
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
mapping.insert_or_assign(
switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
mapping.insert_or_assign(
switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
return mapping;
}
ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
const std::shared_ptr<SDLJoystick>& joystick2,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const {
ButtonMapping mapping;
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
auto* controller = joystick->GetSDLGameController();
auto* controller2 = joystick2->GetSDLGameController();
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
if (IsButtonOnLeftSide(switch_button)) {
const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
mapping.insert_or_assign(
switch_button,
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
continue;
}
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
mapping.insert_or_assign(
switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
if (IsButtonOnLeftSide(switch_button)) {
const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
mapping.insert_or_assign(
switch_button,
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
continue;
}
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
mapping.insert_or_assign(
switch_button,
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
return mapping;
}
bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
switch (button) {
case Settings::NativeButton::DDown:
case Settings::NativeButton::DLeft:
case Settings::NativeButton::DRight:
case Settings::NativeButton::DUp:
case Settings::NativeButton::L:
case Settings::NativeButton::LStick:
case Settings::NativeButton::Minus:
case Settings::NativeButton::Screenshot:
case Settings::NativeButton::ZL:
return true;
default:
return false;
}
}
AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("guid") || !params.Has("port")) {
return {};
}
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
return {};
}
AnalogMapping mapping = {};
const auto& binding_left_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
const auto& binding_left_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
if (params.Has("guid2")) {
const auto identifier = joystick2->GetPadIdentifier();
PreSetController(identifier);
PreSetAxis(identifier, binding_left_x.value.axis);
PreSetAxis(identifier, binding_left_y.value.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
mapping.insert_or_assign(Settings::NativeAnalog::LStick,
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
binding_left_y.value.axis,
left_offset_x, left_offset_y));
} else {
const auto identifier = joystick->GetPadIdentifier();
PreSetController(identifier);
PreSetAxis(identifier, binding_left_x.value.axis);
PreSetAxis(identifier, binding_left_y.value.axis);
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
mapping.insert_or_assign(Settings::NativeAnalog::LStick,
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
binding_left_y.value.axis,
left_offset_x, left_offset_y));
}
const auto& binding_right_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
const auto& binding_right_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
const auto identifier = joystick->GetPadIdentifier();
PreSetController(identifier);
PreSetAxis(identifier, binding_right_x.value.axis);
PreSetAxis(identifier, binding_right_y.value.axis);
const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis);
mapping.insert_or_assign(Settings::NativeAnalog::RStick,
BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
binding_right_y.value.axis, right_offset_x,
right_offset_y));
return mapping;
}
MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("guid") || !params.Has("port")) {
return {};
}
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
auto* controller = joystick->GetSDLGameController();
if (controller == nullptr) {
return {};
}
MotionMapping mapping = {};
joystick->EnableMotion();
if (joystick->HasGyro() || joystick->HasAccel()) {
mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
}
if (params.Has("guid2")) {
joystick2->EnableMotion();
if (joystick2->HasGyro() || joystick2->HasAccel()) {
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
}
} else {
if (joystick->HasGyro() || joystick->HasAccel()) {
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
}
}
return mapping;
}
Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
// TODO(German77): Find how to substitue the values for real button names
return Common::Input::ButtonNames::Value;
}
if (params.Has("hat")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("axis")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("motion")) {
return Common::Input::ButtonNames::Engine;
}
return Common::Input::ButtonNames::Invalid;
}
std::string SDLDriver::GetHatButtonName(u8 direction_value) const {
switch (direction_value) {
case SDL_HAT_UP:
return "up";
case SDL_HAT_DOWN:
return "down";
case SDL_HAT_LEFT:
return "left";
case SDL_HAT_RIGHT:
return "right";
default:
return {};
}
}
u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
Uint8 direction;
if (direction_name == "up") {
direction = SDL_HAT_UP;
} else if (direction_name == "down") {
direction = SDL_HAT_DOWN;
} else if (direction_name == "left") {
direction = SDL_HAT_LEFT;
} else if (direction_name == "right") {
direction = SDL_HAT_RIGHT;
} else {
direction = 0;
}
return direction;
}
} // namespace InputCommon

View File

@@ -5,7 +5,6 @@
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
@@ -13,8 +12,7 @@
#include <SDL.h>
#include "common/common_types.h"
#include "common/threadsafe_queue.h"
#include "input_common/sdl/sdl.h"
#include "input_common/input_engine.h"
union SDL_Event;
using SDL_GameController = struct _SDL_GameController;
@@ -26,21 +24,17 @@ using ButtonBindings =
using ZButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
namespace InputCommon::SDL {
namespace InputCommon {
class SDLAnalogFactory;
class SDLButtonFactory;
class SDLMotionFactory;
class SDLVibrationFactory;
class SDLJoystick;
class SDLState : public State {
class SDLDriver : public InputCommon::InputEngine {
public:
/// Initializes and registers SDL device factories
SDLState();
SDLDriver(const std::string& input_engine_);
/// Unregisters SDL device factories and shut them down.
~SDLState() override;
~SDLDriver() override;
/// Handle SDL_Events for joysticks from SDL_PollEvent
void HandleGameControllerEvent(const SDL_Event& event);
@@ -54,18 +48,18 @@ public:
*/
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
/// Get all DevicePoller that use the SDL backend for a specific device type
Pollers GetPollers(Polling::DeviceType type) override;
/// Used by the Pollers during config
std::atomic<bool> polling = false;
Common::SPSCQueue<SDL_Event> event_queue;
std::vector<Common::ParamPackage> GetInputDevices() override;
std::vector<Common::ParamPackage> GetInputDevices() const override;
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
std::string GetHatButtonName(u8 direction_value) const override;
u8 GetHatButtonId(const std::string& direction_name) const override;
Common::Input::VibrationError SetRumble(
const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override;
private:
void InitJoystick(int joystick_index);
@@ -74,6 +68,23 @@ private:
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
float value = 0.1f) const;
Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid,
s32 button) const;
Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
u8 value) const;
Common::ParamPackage BuildMotionParam(int port, std::string guid) const;
Common::ParamPackage BuildParamPackageForBinding(
int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const;
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
int axis_y, float offset_x,
float offset_y) const;
/// Returns the default button bindings list for generic controllers
ButtonBindings GetDefaultButtonBinding() const;
@@ -94,21 +105,13 @@ private:
/// Returns true if the button is on the left joycon
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
// Set to true if SDL supports game controller subsystem
bool has_gamecontroller = false;
/// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory;
std::shared_ptr<SDLVibrationFactory> vibration_factory;
std::shared_ptr<SDLMotionFactory> motion_factory;
bool start_thread = false;
std::atomic<bool> initialized = false;
std::thread poll_thread;
};
} // namespace InputCommon::SDL
} // namespace InputCommon

View File

@@ -0,0 +1,315 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <cstring>
#include <regex>
#include <fmt/format.h>
#include "common/fs/file.h"
#include "common/fs/fs_types.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/drivers/tas_input.h"
namespace InputCommon::TasInput {
enum TasAxes : u8 {
StickX,
StickY,
SubstickX,
SubstickY,
Undefined,
};
// Supported keywords and buttons from a TAS file
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
std::pair{"KEY_A", TasButton::BUTTON_A},
{"KEY_B", TasButton::BUTTON_B},
{"KEY_X", TasButton::BUTTON_X},
{"KEY_Y", TasButton::BUTTON_Y},
{"KEY_LSTICK", TasButton::STICK_L},
{"KEY_RSTICK", TasButton::STICK_R},
{"KEY_L", TasButton::TRIGGER_L},
{"KEY_R", TasButton::TRIGGER_R},
{"KEY_PLUS", TasButton::BUTTON_PLUS},
{"KEY_MINUS", TasButton::BUTTON_MINUS},
{"KEY_DLEFT", TasButton::BUTTON_LEFT},
{"KEY_DUP", TasButton::BUTTON_UP},
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
{"KEY_SL", TasButton::BUTTON_SL},
{"KEY_SR", TasButton::BUTTON_SR},
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
{"KEY_HOME", TasButton::BUTTON_HOME},
{"KEY_ZL", TasButton::TRIGGER_ZL},
{"KEY_ZR", TasButton::TRIGGER_ZR},
};
Tas::Tas(const std::string& input_engine_) : InputCommon::InputEngine(input_engine_) {
for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
PadIdentifier identifier{
.guid = Common::UUID{},
.port = player_index,
.pad = 0,
};
PreSetController(identifier);
}
ClearInput();
if (!Settings::values.tas_enable) {
needs_reset = true;
return;
}
LoadTasFiles();
}
Tas::~Tas() {
Stop();
};
void Tas::LoadTasFiles() {
script_length = 0;
for (size_t i = 0; i < commands.size(); i++) {
LoadTasFile(i, 0);
if (commands[i].size() > script_length) {
script_length = commands[i].size();
}
}
}
void Tas::LoadTasFile(size_t player_index, size_t file_index) {
if (!commands[player_index].empty()) {
commands[player_index].clear();
}
std::string file = Common::FS::ReadStringFromFile(
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
fmt::format("script{}-{}.txt", file_index, player_index + 1),
Common::FS::FileType::BinaryFile);
std::stringstream command_line(file);
std::string line;
int frame_no = 0;
while (std::getline(command_line, line, '\n')) {
if (line.empty()) {
continue;
}
std::smatch m;
std::stringstream linestream(line);
std::string segment;
std::vector<std::string> seglist;
while (std::getline(linestream, segment, ' ')) {
seglist.push_back(segment);
}
if (seglist.size() < 4) {
continue;
}
while (frame_no < std::stoi(seglist.at(0))) {
commands[player_index].push_back({});
frame_no++;
}
TASCommand command = {
.buttons = ReadCommandButtons(seglist.at(1)),
.l_axis = ReadCommandAxis(seglist.at(2)),
.r_axis = ReadCommandAxis(seglist.at(3)),
};
commands[player_index].push_back(command);
frame_no++;
}
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
}
void Tas::WriteTasFile(std::u8string file_name) {
std::string output_text;
for (size_t frame = 0; frame < record_commands.size(); frame++) {
const TASCommand& line = record_commands[frame];
output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
}
const auto bytes_written = Common::FS::WriteStringToFile(
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
Common::FS::FileType::TextFile, output_text);
if (bytes_written == output_text.size()) {
LOG_INFO(Input, "TAS file written to file!");
} else {
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
output_text.size());
}
}
void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
last_input = {
.buttons = buttons,
.l_axis = left_axis,
.r_axis = right_axis,
};
}
std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
TasState state;
if (is_recording) {
return {TasState::Recording, 0, record_commands.size()};
}
if (is_running) {
state = TasState::Running;
} else {
state = TasState::Stopped;
}
return {state, current_command, script_length};
}
void Tas::UpdateThread() {
if (!Settings::values.tas_enable) {
if (is_running) {
Stop();
}
return;
}
if (is_recording) {
record_commands.push_back(last_input);
}
if (needs_reset) {
current_command = 0;
needs_reset = false;
LoadTasFiles();
LOG_DEBUG(Input, "tas_reset done");
}
if (!is_running) {
ClearInput();
return;
}
if (current_command < script_length) {
LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
const size_t frame = current_command++;
for (size_t player_index = 0; player_index < commands.size(); player_index++) {
TASCommand command{};
if (frame < commands[player_index].size()) {
command = commands[player_index][frame];
}
PadIdentifier identifier{
.guid = Common::UUID{},
.port = player_index,
.pad = 0,
};
for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
const bool button_status = (command.buttons & (1LLU << i)) != 0;
const int button = static_cast<int>(i);
SetButton(identifier, button, button_status);
}
SetAxis(identifier, TasAxes::StickX, command.l_axis.x);
SetAxis(identifier, TasAxes::StickY, command.l_axis.y);
SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x);
SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y);
}
} else {
is_running = Settings::values.tas_loop.GetValue();
LoadTasFiles();
current_command = 0;
ClearInput();
}
}
void Tas::ClearInput() {
ResetButtonState();
ResetAnalogState();
}
TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
std::stringstream linestream(line);
std::string segment;
std::vector<std::string> seglist;
while (std::getline(linestream, segment, ';')) {
seglist.push_back(segment);
}
const float x = std::stof(seglist.at(0)) / 32767.0f;
const float y = std::stof(seglist.at(1)) / 32767.0f;
return {x, y};
}
u64 Tas::ReadCommandButtons(const std::string& data) const {
std::stringstream button_text(data);
std::string line;
u64 buttons = 0;
while (std::getline(button_text, line, ';')) {
for (auto [text, tas_button] : text_to_tas_button) {
if (text == line) {
buttons |= static_cast<u64>(tas_button);
break;
}
}
}
return buttons;
}
std::string Tas::WriteCommandButtons(u64 buttons) const {
std::string returns = "";
for (auto [text_button, tas_button] : text_to_tas_button) {
if ((buttons & static_cast<u64>(tas_button)) != 0) {
returns += fmt::format("{};", text_button);
}
}
return returns.empty() ? "NONE" : returns;
}
std::string Tas::WriteCommandAxis(TasAnalog analog) const {
return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
}
void Tas::StartStop() {
if (!Settings::values.tas_enable) {
return;
}
if (is_running) {
Stop();
} else {
is_running = true;
}
}
void Tas::Stop() {
is_running = false;
}
void Tas::Reset() {
if (!Settings::values.tas_enable) {
return;
}
needs_reset = true;
}
bool Tas::Record() {
if (!Settings::values.tas_enable) {
return true;
}
is_recording = !is_recording;
return is_recording;
}
void Tas::SaveRecording(bool overwrite_file) {
if (is_recording) {
return;
}
if (record_commands.empty()) {
return;
}
WriteTasFile(u8"record.txt");
if (overwrite_file) {
WriteTasFile(u8"script0-1.txt");
}
needs_reset = true;
record_commands.clear();
}
} // namespace InputCommon::TasInput

View File

@@ -8,7 +8,7 @@
#include "common/common_types.h"
#include "common/settings_input.h"
#include "core/frontend/input.h"
#include "input_common/input_engine.h"
#include "input_common/main.h"
/*
@@ -43,19 +43,11 @@ For debugging purposes, the common controller debugger can be used (View -> Debu
P1).
*/
namespace TasInput {
namespace InputCommon::TasInput {
constexpr size_t PLAYER_NUMBER = 8;
constexpr size_t PLAYER_NUMBER = 10;
using TasAnalog = std::pair<float, float>;
enum class TasState {
Running,
Recording,
Stopped,
};
enum class TasButton : u32 {
enum class TasButton : u64 {
BUTTON_A = 1U << 0,
BUTTON_B = 1U << 1,
BUTTON_X = 1U << 2,
@@ -78,26 +70,29 @@ enum class TasButton : u32 {
BUTTON_CAPTURE = 1U << 19,
};
enum class TasAxes : u8 {
StickX,
StickY,
SubstickX,
SubstickY,
Undefined,
struct TasAnalog {
float x{};
float y{};
};
struct TasData {
u32 buttons{};
std::array<float, 4> axis{};
enum class TasState {
Running,
Recording,
Stopped,
};
class Tas {
class Tas final : public InputCommon::InputEngine {
public:
Tas();
explicit Tas(const std::string& input_engine_);
~Tas();
// Changes the input status that will be stored in each frame
void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
/**
* Changes the input status that will be stored in each frame
* @param buttons: bitfield with the status of the buttons
* @param left_axis: value of the left axis
* @param right_axis: value of the right axis
*/
void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis);
// Main loop that records or executes input
void UpdateThread();
@@ -117,112 +112,77 @@ public:
*/
bool Record();
// Saves contents of record_commands on a file if overwrite is enabled player 1 will be
// overwritten with the recorded commands
/**
* Saves contents of record_commands on a file
* @param overwrite_file: Indicates if player 1 should be overwritten
*/
void SaveRecording(bool overwrite_file);
/**
* Returns the current status values of TAS playback/recording
* @return Tuple of
* TasState indicating the current state out of Running, Recording or Stopped ;
* Current playback progress or amount of frames (so far) for Recording ;
* Total length of script file currently loaded or amount of frames (so far) for Recording
* TasState indicating the current state out of Running ;
* Current playback progress ;
* Total length of script file currently loaded or being recorded
*/
std::tuple<TasState, size_t, size_t> GetStatus() const;
// Retuns an array of the default button mappings
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
// Retuns an array of the default analog mappings
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
[[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
private:
struct TASCommand {
u32 buttons{};
u64 buttons{};
TasAnalog l_axis{};
TasAnalog r_axis{};
};
// Loads TAS files from all players
/// Loads TAS files from all players
void LoadTasFiles();
// Loads TAS file from the specified player
void LoadTasFile(size_t player_index);
/** Loads TAS file from the specified player
* @param player_index: player number to save the script
* @param file_index: script number of the file
*/
void LoadTasFile(size_t player_index, size_t file_index);
// Writes a TAS file from the recorded commands
/** Writes a TAS file from the recorded commands
* @param file_name: name of the file to be written
*/
void WriteTasFile(std::u8string file_name);
/**
* Parses a string containing the axis values with the following format "x;y"
* X and Y have a range from -32767 to 32767
* Parses a string containing the axis values. X and Y have a range from -32767 to 32767
* @param line: string containing axis values with the following format "x;y"
* @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
*/
TasAnalog ReadCommandAxis(const std::string& line) const;
/**
* Parses a string containing the button values with the following format "a;b;c;d..."
* Each button is represented by it's text format specified in text_to_tas_button array
* @return Returns a u32 with each bit representing the status of a button
* Parses a string containing the button values. Each button is represented by it's text format
* specified in text_to_tas_button array
* @param line: string containing button name with the following format "a;b;c;d..."
* @return Returns a u64 with each bit representing the status of a button
*/
u32 ReadCommandButtons(const std::string& line) const;
u64 ReadCommandButtons(const std::string& line) const;
/**
* Converts an u32 containing the button status into the text equivalent
* Reset state of all players
*/
void ClearInput();
/**
* Converts an u64 containing the button status into the text equivalent
* @param buttons: bitfield with the status of the buttons
* @return Returns a string with the name of the buttons to be written to the file
*/
std::string WriteCommandButtons(u32 data) const;
std::string WriteCommandButtons(u64 buttons) const;
/**
* Converts an TAS analog object containing the axis status into the text equivalent
* @return Returns a string with the value of the axis to be written to the file
* @param data: value of the axis
* @return A string with the value of the axis to be written to the file
*/
std::string WriteCommandAxis(TasAnalog data) const;
// Inverts the Y axis polarity
std::pair<float, float> FlipAxisY(std::pair<float, float> old);
/**
* Converts an u32 containing the button status into the text equivalent
* @return Returns a string with the name of the buttons to be printed on console
*/
std::string DebugButtons(u32 buttons) const;
/**
* Converts an TAS analog object containing the axis status into the text equivalent
* @return Returns a string with the value of the axis to be printed on console
*/
std::string DebugJoystick(float x, float y) const;
/**
* Converts the given TAS status into the text equivalent
* @return Returns a string with the value of the TAS status to be printed on console
*/
std::string DebugInput(const TasData& data) const;
/**
* Converts the given TAS status of multiple players into the text equivalent
* @return Returns a string with the value of the status of all TAS players to be printed on
* console
*/
std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
/**
* Converts an u32 containing the button status into the text equivalent
* @return Returns a string with the name of the buttons
*/
std::string ButtonsToString(u32 button) const;
// Stores current controller configuration and sets a TAS controller for every active controller
// to the current config
void SwapToTasController();
// Sets the stored controller configuration to the current config
void SwapToStoredController();
size_t script_length{0};
std::array<TasData, PLAYER_NUMBER> tas_data;
bool is_old_input_saved{false};
bool is_recording{false};
bool is_running{false};
bool needs_reset{false};
@@ -230,8 +190,5 @@ private:
std::vector<TASCommand> record_commands{};
size_t current_command{0};
TASCommand last_input{}; // only used for recording
// Old settings for swapping controllers
std::array<Settings::PlayerInput, 10> player_mappings;
};
} // namespace TasInput
} // namespace InputCommon::TasInput

View File

@@ -0,0 +1,53 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/param_package.h"
#include "input_common/drivers/touch_screen.h"
namespace InputCommon {
constexpr PadIdentifier identifier = {
.guid = Common::UUID{Common::INVALID_UUID},
.port = 0,
.pad = 0,
};
TouchScreen::TouchScreen(const std::string& input_engine_) : InputEngine(input_engine_) {
PreSetController(identifier);
}
void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
if (finger >= 16) {
return;
}
TouchPressed(x, y, finger);
}
void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
if (finger >= 16) {
return;
}
SetButton(identifier, static_cast<int>(finger), true);
SetAxis(identifier, static_cast<int>(finger * 2), x);
SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
}
void TouchScreen::TouchReleased(std::size_t finger) {
if (finger >= 16) {
return;
}
SetButton(identifier, static_cast<int>(finger), false);
SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
}
void TouchScreen::ReleaseAllTouch() {
for (int index = 0; index < 16; ++index) {
SetButton(identifier, index, false);
SetAxis(identifier, index * 2, 0.0f);
SetAxis(identifier, index * 2 + 1, 0.0f);
}
}
} // namespace InputCommon

View File

@@ -0,0 +1,44 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include "input_common/input_engine.h"
namespace InputCommon {
/**
* A button device factory representing a keyboard. It receives keyboard events and forward them
* to all button devices it created.
*/
class TouchScreen final : public InputCommon::InputEngine {
public:
explicit TouchScreen(const std::string& input_engine_);
/**
* Signals that mouse has moved.
* @param x the x-coordinate of the cursor
* @param y the y-coordinate of the cursor
* @param center_x the x-coordinate of the middle of the screen
* @param center_y the y-coordinate of the middle of the screen
*/
void TouchMoved(float x, float y, std::size_t finger);
/**
* Sets the status of all buttons bound with the key to pressed
* @param key_code the code of the key to press
*/
void TouchPressed(float x, float y, std::size_t finger);
/**
* Sets the status of all buttons bound with the key to released
* @param key_code the code of the key to release
*/
void TouchReleased(std::size_t finger);
/// Resets all inputs to their initial value
void ReleaseAllTouch();
};
} // namespace InputCommon

View File

@@ -0,0 +1,591 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <random>
#include <boost/asio.hpp>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/helpers/udp_protocol.h"
using boost::asio::ip::udp;
namespace InputCommon::CemuhookUDP {
struct SocketCallback {
std::function<void(Response::Version)> version;
std::function<void(Response::PortInfo)> port_info;
std::function<void(Response::PadData)> pad_data;
};
class Socket {
public:
using clock = std::chrono::system_clock;
explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
: callback(std::move(callback_)), timer(io_service),
socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
boost::system::error_code ec{};
auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
if (ec.value() != boost::system::errc::success) {
LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
ipv4 = boost::asio::ip::address_v4{};
}
send_endpoint = {udp::endpoint(ipv4, port)};
}
void Stop() {
io_service.stop();
}
void Loop() {
io_service.run();
}
void StartSend(const clock::time_point& from) {
timer.expires_at(from + std::chrono::seconds(3));
timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
}
void StartReceive() {
socket.async_receive_from(
boost::asio::buffer(receive_buffer), receive_endpoint,
[this](const boost::system::error_code& error, std::size_t bytes_transferred) {
HandleReceive(error, bytes_transferred);
});
}
private:
u32 GenerateRandomClientId() const {
std::random_device device;
return device();
}
void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
switch (*type) {
case Type::Version: {
Response::Version version;
std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
callback.version(std::move(version));
break;
}
case Type::PortInfo: {
Response::PortInfo port_info;
std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
sizeof(Response::PortInfo));
callback.port_info(std::move(port_info));
break;
}
case Type::PadData: {
Response::PadData pad_data;
std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
callback.pad_data(std::move(pad_data));
break;
}
}
}
StartReceive();
}
void HandleSend(const boost::system::error_code&) {
boost::system::error_code _ignored{};
// Send a request for getting port info for the pad
const Request::PortInfo port_info{4, {0, 1, 2, 3}};
const auto port_message = Request::Create(port_info, client_id);
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
// Send a request for getting pad data for the pad
const Request::PadData pad_data{
Request::RegisterFlags::AllPads,
0,
EMPTY_MAC_ADDRESS,
};
const auto pad_message = Request::Create(pad_data, client_id);
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
StartSend(timer.expiry());
}
SocketCallback callback;
boost::asio::io_service io_service;
boost::asio::basic_waitable_timer<clock> timer;
udp::socket socket;
const u32 client_id;
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
std::array<u8, PORT_INFO_SIZE> send_buffer1;
std::array<u8, PAD_DATA_SIZE> send_buffer2;
udp::endpoint send_endpoint;
std::array<u8, MAX_PACKET_SIZE> receive_buffer;
udp::endpoint receive_endpoint;
};
static void SocketLoop(Socket* socket) {
socket->StartReceive();
socket->StartSend(Socket::clock::now());
socket->Loop();
}
UDPClient::UDPClient(const std::string& input_engine_) : InputEngine(input_engine_) {
LOG_INFO(Input, "Udp Initialization started");
ReloadSockets();
}
UDPClient::~UDPClient() {
Reset();
}
UDPClient::ClientConnection::ClientConnection() = default;
UDPClient::ClientConnection::~ClientConnection() = default;
void UDPClient::ReloadSockets() {
Reset();
std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
std::string server_token;
std::size_t client = 0;
while (std::getline(servers_ss, server_token, ',')) {
if (client == MAX_UDP_CLIENTS) {
break;
}
std::stringstream server_ss(server_token);
std::string token;
std::getline(server_ss, token, ':');
std::string udp_input_address = token;
std::getline(server_ss, token, ':');
char* temp;
const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
if (*temp != '\0') {
LOG_ERROR(Input, "Port number is not valid {}", token);
continue;
}
const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
if (client_number != MAX_UDP_CLIENTS) {
LOG_ERROR(Input, "Duplicated UDP servers found");
continue;
}
StartCommunication(client++, udp_input_address, udp_input_port);
}
}
std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
for (std::size_t client = 0; client < clients.size(); client++) {
if (clients[client].active == -1) {
continue;
}
if (clients[client].host == host && clients[client].port == port) {
return client;
}
}
return MAX_UDP_CLIENTS;
}
void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
LOG_TRACE(Input, "Version packet received: {}", data.version);
}
void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
}
void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
if (pad_index >= pads.size()) {
LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
return;
}
LOG_TRACE(Input, "PadData packet received");
if (data.packet_counter == pads[pad_index].packet_sequence) {
LOG_WARNING(
Input,
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
pads[pad_index].packet_sequence, data.packet_counter);
pads[pad_index].connected = false;
return;
}
clients[client].active = 1;
pads[pad_index].connected = true;
pads[pad_index].packet_sequence = data.packet_counter;
const auto now = std::chrono::steady_clock::now();
const auto time_difference = static_cast<u64>(
std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
.count());
pads[pad_index].last_update = now;
// Gyroscope values are not it the correct scale from better joy.
// Dividing by 312 allows us to make one full turn = 1 turn
// This must be a configurable valued called sensitivity
const float gyro_scale = 1.0f / 312.0f;
const BasicMotion motion{
.gyro_x = data.gyro.pitch * gyro_scale,
.gyro_y = data.gyro.roll * gyro_scale,
.gyro_z = -data.gyro.yaw * gyro_scale,
.accel_x = data.accel.x,
.accel_y = -data.accel.z,
.accel_z = data.accel.y,
.delta_timestamp = time_difference,
};
const PadIdentifier identifier = GetPadIdentifier(pad_index);
SetMotion(identifier, 0, motion);
for (std::size_t id = 0; id < data.touch.size(); ++id) {
const auto touch_pad = data.touch[id];
const auto touch_axis_x_id =
static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
const auto touch_axis_y_id =
static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
const auto touch_button_id =
static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::touch2);
// TODO: Use custom calibration per device
const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
const f32 x =
static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
static_cast<f32>(max_x - min_x);
const f32 y =
static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
static_cast<f32>(max_y - min_y);
if (touch_pad.is_active) {
SetAxis(identifier, touch_axis_x_id, x);
SetAxis(identifier, touch_axis_y_id, y);
SetButton(identifier, touch_button_id, true);
continue;
}
SetAxis(identifier, touch_axis_x_id, 0);
SetAxis(identifier, touch_axis_y_id, 0);
SetButton(identifier, touch_button_id, false);
}
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX),
(data.left_stick_x - 127.0f) / 127.0f);
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
(data.left_stick_y - 127.0f) / 127.0f);
SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
(data.right_stick_x - 127.0f) / 127.0f);
SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
(data.right_stick_y - 127.0f) / 127.0f);
static constexpr std::array<PadButton, 16> buttons{
PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options,
PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left,
PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1,
PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square};
for (std::size_t i = 0; i < buttons.size(); ++i) {
const bool button_status = (data.digital_button & (1U << i)) != 0;
const int button = static_cast<int>(buttons[i]);
SetButton(identifier, button, button_status);
}
}
void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
[this](Response::PortInfo info) { OnPortInfo(info); },
[this, client](Response::PadData data) { OnPadData(data, client); }};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
clients[client].uuid = GetHostUUID(host);
clients[client].host = host;
clients[client].port = port;
clients[client].active = 0;
clients[client].socket = std::make_unique<Socket>(host, port, callback);
clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
PreSetController(identifier);
}
}
const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
const std::size_t client = pad_index / PADS_PER_CLIENT;
return {
.guid = clients[client].uuid,
.port = static_cast<std::size_t>(clients[client].port),
.pad = pad_index,
};
}
const Common::UUID UDPClient::GetHostUUID(const std::string host) const {
const auto ip = boost::asio::ip::address_v4::from_string(host);
const auto hex_host = fmt::format("{:06x}", ip.to_ulong());
return Common::UUID{hex_host};
}
void UDPClient::Reset() {
for (auto& client : clients) {
if (client.thread.joinable()) {
client.active = -1;
client.socket->Stop();
client.thread.join();
}
}
}
std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
if (!Settings::values.enable_udp_controller) {
return devices;
}
for (std::size_t client = 0; client < clients.size(); client++) {
if (clients[client].active != 1) {
continue;
}
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
const std::size_t pad_index = client * PADS_PER_CLIENT + index;
if (!pads[pad_index].connected) {
continue;
}
const auto pad_identifier = GetPadIdentifier(pad_index);
Common::ParamPackage identifier{};
identifier.Set("engine", GetEngineName());
identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad));
identifier.Set("guid", pad_identifier.guid.Format());
identifier.Set("port", static_cast<int>(pad_identifier.port));
identifier.Set("pad", static_cast<int>(pad_identifier.pad));
devices.emplace_back(identifier);
}
}
return devices;
}
ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
// This list excludes any button that can't be really mapped
static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 18>
switch_to_dsu_button = {
std::pair{Settings::NativeButton::A, PadButton::Circle},
{Settings::NativeButton::B, PadButton::Cross},
{Settings::NativeButton::X, PadButton::Triangle},
{Settings::NativeButton::Y, PadButton::Square},
{Settings::NativeButton::Plus, PadButton::Options},
{Settings::NativeButton::Minus, PadButton::Share},
{Settings::NativeButton::DLeft, PadButton::Left},
{Settings::NativeButton::DUp, PadButton::Up},
{Settings::NativeButton::DRight, PadButton::Right},
{Settings::NativeButton::DDown, PadButton::Down},
{Settings::NativeButton::L, PadButton::L1},
{Settings::NativeButton::R, PadButton::R1},
{Settings::NativeButton::ZL, PadButton::L2},
{Settings::NativeButton::ZR, PadButton::R2},
{Settings::NativeButton::SL, PadButton::L2},
{Settings::NativeButton::SR, PadButton::R2},
{Settings::NativeButton::LStick, PadButton::L3},
{Settings::NativeButton::RStick, PadButton::R3},
};
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
return {};
}
ButtonMapping mapping{};
for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) {
Common::ParamPackage button_params{};
button_params.Set("engine", GetEngineName());
button_params.Set("guid", params.Get("guid", ""));
button_params.Set("port", params.Get("port", 0));
button_params.Set("pad", params.Get("pad", 0));
button_params.Set("button", static_cast<int>(dsu_button));
mapping.insert_or_assign(switch_button, std::move(button_params));
}
return mapping;
}
AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
return {};
}
AnalogMapping mapping = {};
Common::ParamPackage left_analog_params;
left_analog_params.Set("engine", GetEngineName());
left_analog_params.Set("guid", params.Get("guid", ""));
left_analog_params.Set("port", params.Get("port", 0));
left_analog_params.Set("pad", params.Get("pad", 0));
left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY));
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
Common::ParamPackage right_analog_params;
right_analog_params.Set("engine", GetEngineName());
right_analog_params.Set("guid", params.Get("guid", ""));
right_analog_params.Set("port", params.Get("port", 0));
right_analog_params.Set("pad", params.Get("pad", 0));
right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY));
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
return mapping;
}
MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
return {};
}
MotionMapping mapping = {};
Common::ParamPackage motion_params;
motion_params.Set("engine", GetEngineName());
motion_params.Set("guid", params.Get("guid", ""));
motion_params.Set("port", params.Get("port", 0));
motion_params.Set("pad", params.Get("pad", 0));
motion_params.Set("motion", 0);
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params));
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params));
return mapping;
}
Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const {
PadButton button = static_cast<PadButton>(params.Get("button", 0));
switch (button) {
case PadButton::Left:
return Common::Input::ButtonNames::ButtonLeft;
case PadButton::Right:
return Common::Input::ButtonNames::ButtonRight;
case PadButton::Down:
return Common::Input::ButtonNames::ButtonDown;
case PadButton::Up:
return Common::Input::ButtonNames::ButtonUp;
case PadButton::L1:
return Common::Input::ButtonNames::L1;
case PadButton::L2:
return Common::Input::ButtonNames::L2;
case PadButton::L3:
return Common::Input::ButtonNames::L3;
case PadButton::R1:
return Common::Input::ButtonNames::R1;
case PadButton::R2:
return Common::Input::ButtonNames::R2;
case PadButton::R3:
return Common::Input::ButtonNames::R3;
case PadButton::Circle:
return Common::Input::ButtonNames::Circle;
case PadButton::Cross:
return Common::Input::ButtonNames::Cross;
case PadButton::Square:
return Common::Input::ButtonNames::Square;
case PadButton::Triangle:
return Common::Input::ButtonNames::Triangle;
case PadButton::Share:
return Common::Input::ButtonNames::Share;
case PadButton::Options:
return Common::Input::ButtonNames::Options;
default:
return Common::Input::ButtonNames::Undefined;
}
}
Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
return GetUIButtonName(params);
}
if (params.Has("axis")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("motion")) {
return Common::Input::ButtonNames::Engine;
}
return Common::Input::ButtonNames::Invalid;
}
void TestCommunication(const std::string& host, u16 port,
const std::function<void()>& success_callback,
const std::function<void()>& failure_callback) {
std::thread([=] {
Common::Event success_event;
SocketCallback callback{
.version = [](Response::Version) {},
.port_info = [](Response::PortInfo) {},
.pad_data = [&](Response::PadData) { success_event.Set(); },
};
Socket socket{host, port, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
const bool result =
success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
socket.Stop();
worker_thread.join();
if (result) {
success_callback();
} else {
failure_callback();
}
}).detach();
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
const std::string& host, u16 port, std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback) {
std::thread([=, this] {
Status current_status{Status::Initialized};
SocketCallback callback{
[](Response::Version) {}, [](Response::PortInfo) {},
[&](Response::PadData data) {
static constexpr u16 CALIBRATION_THRESHOLD = 100;
static constexpr u16 MAX_VALUE = UINT16_MAX;
if (current_status == Status::Initialized) {
// Receiving data means the communication is ready now
current_status = Status::Ready;
status_callback(current_status);
}
const auto& touchpad_0 = data.touch[0];
if (touchpad_0.is_active == 0) {
return;
}
LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
if (current_status == Status::Ready) {
// First touch - min data (min_x/min_y)
current_status = Status::Stage1Completed;
status_callback(current_status);
}
if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
// Set the current position as max value and finishes configuration
const u16 max_x = touchpad_0.x;
const u16 max_y = touchpad_0.y;
current_status = Status::Completed;
data_callback(min_x, min_y, max_x, max_y);
status_callback(current_status);
complete_event.Set();
}
}};
Socket socket{host, port, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
complete_event.Wait();
socket.Stop();
worker_thread.join();
}).detach();
}
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
Stop();
}
void CalibrationConfigurationJob::Stop() {
complete_event.Set();
}
} // namespace InputCommon::CemuhookUDP

View File

@@ -4,20 +4,11 @@
#pragma once
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <tuple>
#include "common/common_types.h"
#include "common/param_package.h"
#include "common/thread.h"
#include "common/threadsafe_queue.h"
#include "common/vector_math.h"
#include "core/frontend/input.h"
#include "input_common/motion_input.h"
#include "input_common/input_engine.h"
namespace InputCommon::CemuhookUDP {
@@ -30,16 +21,6 @@ struct TouchPad;
struct Version;
} // namespace Response
enum class PadMotion {
GyroX,
GyroY,
GyroZ,
AccX,
AccY,
AccZ,
Undefined,
};
enum class PadTouch {
Click,
Undefined,
@@ -49,14 +30,10 @@ struct UDPPadStatus {
std::string host{"127.0.0.1"};
u16 port{26760};
std::size_t pad_index{};
PadMotion motion{PadMotion::Undefined};
f32 motion_value{0.0f};
};
struct DeviceStatus {
std::mutex update_mutex;
Input::MotionStatus motion_status;
std::tuple<float, float, bool> touch_status;
// calibration data for scaling the device's touch area to 3ds
struct CalibrationData {
@@ -68,48 +45,85 @@ struct DeviceStatus {
std::optional<CalibrationData> touch_calibration;
};
class Client {
/**
* A button device factory representing a keyboard. It receives keyboard events and forward them
* to all button devices it created.
*/
class UDPClient final : public InputCommon::InputEngine {
public:
// Initialize the UDP client capture and read sequence
Client();
explicit UDPClient(const std::string& input_engine_);
~UDPClient();
// Close and release the client
~Client();
// Used for polling
void BeginConfiguration();
void EndConfiguration();
std::vector<Common::ParamPackage> GetInputDevices() const;
bool DeviceConnected(std::size_t pad) const;
void ReloadSockets();
Common::SPSCQueue<UDPPadStatus>& GetPadQueue();
const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const;
DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad);
const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const;
Input::TouchStatus& GetTouchState();
const Input::TouchStatus& GetTouchState() const;
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const override;
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
private:
enum class PadButton {
Undefined = 0x0000,
Share = 0x0001,
L3 = 0x0002,
R3 = 0x0004,
Options = 0x0008,
Up = 0x0010,
Right = 0x0020,
Down = 0x0040,
Left = 0x0080,
L2 = 0x0100,
R2 = 0x0200,
L1 = 0x0400,
R1 = 0x0800,
Triangle = 0x1000,
Circle = 0x2000,
Cross = 0x4000,
Square = 0x8000,
Touch1 = 0x10000,
touch2 = 0x20000,
};
enum class PadAxes : u8 {
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
AnalogLeft,
AnalogDown,
AnalogRight,
AnalogUp,
AnalogSquare,
AnalogCross,
AnalogCircle,
AnalogTriangle,
AnalogR1,
AnalogL1,
AnalogR2,
AnalogL3,
AnalogR3,
Touch1X,
Touch1Y,
Touch2X,
Touch2Y,
Undefined,
};
struct PadData {
std::size_t pad_index{};
bool connected{};
DeviceStatus status;
u64 packet_sequence{};
// Realtime values
// motion is initalized with PID values for drift correction on joycons
InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
std::chrono::time_point<std::chrono::steady_clock> last_update;
};
struct ClientConnection {
ClientConnection();
~ClientConnection();
Common::UUID uuid{"7F000001"};
std::string host{"127.0.0.1"};
u16 port{26760};
s8 active{-1};
@@ -127,28 +141,16 @@ private:
void OnPortInfo(Response::PortInfo);
void OnPadData(Response::PadData, std::size_t client);
void StartCommunication(std::size_t client, const std::string& host, u16 port);
void UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro);
const PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
const Common::UUID GetHostUUID(const std::string host) const;
// Returns an unused finger id, if there is no fingers available std::nullopt will be
// returned
std::optional<std::size_t> GetUnusedFingerID() const;
// Merges and updates all touch inputs into the touch_status array
void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id);
bool configuring = false;
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
// Allocate clients for 8 udp servers
static constexpr std::size_t MAX_UDP_CLIENTS = 8;
static constexpr std::size_t PADS_PER_CLIENT = 4;
// Each client can have up 2 touch inputs
static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2;
std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
Common::SPSCQueue<UDPPadStatus> pad_queue{};
Input::TouchStatus touch_status{};
std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{};
};
/// An async job allowing configuration of the touchpad calibration.

View File

@@ -1,168 +0,0 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <functional>
#include <mutex>
#include <thread>
#include <unordered_map>
#include "common/common_types.h"
#include "common/threadsafe_queue.h"
#include "input_common/main.h"
struct libusb_context;
struct libusb_device;
struct libusb_device_handle;
namespace GCAdapter {
enum class PadButton {
Undefined = 0x0000,
ButtonLeft = 0x0001,
ButtonRight = 0x0002,
ButtonDown = 0x0004,
ButtonUp = 0x0008,
TriggerZ = 0x0010,
TriggerR = 0x0020,
TriggerL = 0x0040,
ButtonA = 0x0100,
ButtonB = 0x0200,
ButtonX = 0x0400,
ButtonY = 0x0800,
ButtonStart = 0x1000,
// Below is for compatibility with "AxisButton" type
Stick = 0x2000,
};
enum class PadAxes : u8 {
StickX,
StickY,
SubstickX,
SubstickY,
TriggerLeft,
TriggerRight,
Undefined,
};
enum class ControllerTypes {
None,
Wired,
Wireless,
};
struct GCPadStatus {
std::size_t port{};
PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
PadAxes axis{PadAxes::Undefined};
s16 axis_value{};
u8 axis_threshold{50};
};
struct GCController {
ControllerTypes type{};
bool enable_vibration{};
u8 rumble_amplitude{};
u16 buttons{};
PadButton last_button{};
std::array<s16, 6> axis_values{};
std::array<u8, 6> axis_origin{};
u8 reset_origin_counter{};
};
class Adapter {
public:
Adapter();
~Adapter();
/// Request a vibration for a controller
bool RumblePlay(std::size_t port, u8 amplitude);
/// Used for polling
void BeginConfiguration();
void EndConfiguration();
Common::SPSCQueue<GCPadStatus>& GetPadQueue();
const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
GCController& GetPadState(std::size_t port);
const GCController& GetPadState(std::size_t port) const;
/// Returns true if there is a device connected to port
bool DeviceConnected(std::size_t port) const;
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const;
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
private:
using AdapterPayload = std::array<u8, 37>;
void UpdatePadType(std::size_t port, ControllerTypes pad_type);
void UpdateControllers(const AdapterPayload& adapter_payload);
void UpdateYuzuSettings(std::size_t port);
void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
void UpdateVibrations();
void AdapterInputThread();
void AdapterScanThread();
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
// Updates vibration state of all controllers
void SendVibrations();
/// For use in initialization, querying devices to find the adapter
void Setup();
/// Resets status of all GC controller devices to a disconnected state
void ResetDevices();
/// Resets status of device connected to a disconnected state
void ResetDevice(std::size_t port);
/// Returns true if we successfully gain access to GC Adapter
bool CheckDeviceAccess();
/// Captures GC Adapter endpoint address
/// Returns true if the endpoint was set correctly
bool GetGCEndpoint(libusb_device* device);
/// For shutting down, clear all data, join all threads, release usb
void Reset();
// Join all threads
void JoinThreads();
// Release usb handles
void ClearLibusbHandle();
libusb_device_handle* usb_adapter_handle = nullptr;
std::array<GCController, 4> pads;
Common::SPSCQueue<GCPadStatus> pad_queue;
std::thread adapter_input_thread;
std::thread adapter_scan_thread;
bool adapter_input_thread_running;
bool adapter_scan_thread_running;
bool restart_scan_thread;
libusb_context* libusb_ctx;
u8 input_endpoint{0};
u8 output_endpoint{0};
u8 input_error_counter{0};
u8 output_error_counter{0};
int vibration_counter{0};
bool configuring{false};
bool rumble_enabled{true};
bool vibration_changed{true};
};
} // namespace GCAdapter

View File

@@ -1,356 +0,0 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <list>
#include <mutex>
#include <utility>
#include "common/assert.h"
#include "common/threadsafe_queue.h"
#include "input_common/gcadapter/gc_adapter.h"
#include "input_common/gcadapter/gc_poller.h"
namespace InputCommon {
class GCButton final : public Input::ButtonDevice {
public:
explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
: port(port_), button(button_), gcadapter(adapter) {}
~GCButton() override;
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
return (gcadapter->GetPadState(port).buttons & button) != 0;
}
return false;
}
private:
const u32 port;
const s32 button;
const GCAdapter::Adapter* gcadapter;
};
class GCAxisButton final : public Input::ButtonDevice {
public:
explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
const GCAdapter::Adapter* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
gcadapter(adapter) {}
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
const float axis_value = current_axis_value / 128.0f;
if (trigger_if_greater) {
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
return axis_value > threshold;
}
return axis_value < -threshold;
}
return false;
}
private:
const u32 port;
const u32 axis;
float threshold;
bool trigger_if_greater;
const GCAdapter::Adapter* gcadapter;
};
GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
: adapter(std::move(adapter_)) {}
GCButton::~GCButton() = default;
std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
const auto button_id = params.Get("button", 0);
const auto port = static_cast<u32>(params.Get("port", 0));
constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
// button is not an axis/stick button
if (button_id != PAD_STICK_ID) {
return std::make_unique<GCButton>(port, button_id, adapter.get());
}
// For Axis buttons, used by the binary sticks.
if (button_id == PAD_STICK_ID) {
const int axis = params.Get("axis", 0);
const float threshold = params.Get("threshold", 0.25f);
const std::string direction_name = params.Get("direction", "");
bool trigger_if_greater;
if (direction_name == "+") {
trigger_if_greater = true;
} else if (direction_name == "-") {
trigger_if_greater = false;
} else {
trigger_if_greater = true;
LOG_ERROR(Input, "Unknown direction {}", direction_name);
}
return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
adapter.get());
}
return nullptr;
}
Common::ParamPackage GCButtonFactory::GetNextInput() const {
Common::ParamPackage params;
GCAdapter::GCPadStatus pad;
auto& queue = adapter->GetPadQueue();
while (queue.Pop(pad)) {
// This while loop will break on the earliest detected button
params.Set("engine", "gcpad");
params.Set("port", static_cast<s32>(pad.port));
if (pad.button != GCAdapter::PadButton::Undefined) {
params.Set("button", static_cast<u16>(pad.button));
}
// For Axis button implementation
if (pad.axis != GCAdapter::PadAxes::Undefined) {
params.Set("axis", static_cast<u8>(pad.axis));
params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
params.Set("threshold", "0.25");
if (pad.axis_value > 0) {
params.Set("direction", "+");
} else {
params.Set("direction", "-");
}
break;
}
}
return params;
}
void GCButtonFactory::BeginConfiguration() {
polling = true;
adapter->BeginConfiguration();
}
void GCButtonFactory::EndConfiguration() {
polling = false;
adapter->EndConfiguration();
}
class GCAnalog final : public Input::AnalogDevice {
public:
explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
float deadzone_, float range_, const GCAdapter::Adapter* adapter)
: port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
deadzone(deadzone_), range(range_), gcadapter(adapter) {}
float GetAxis(u32 axis) const {
if (gcadapter->DeviceConnected(port)) {
std::lock_guard lock{mutex};
const auto axis_value =
static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
return (axis_value) / (100.0f * range);
}
return 0.0f;
}
std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
float x = GetAxis(analog_axis_x);
float y = GetAxis(analog_axis_y);
if (invert_x) {
x = -x;
}
if (invert_y) {
y = -y;
}
// Make sure the coordinates are in the unit circle,
// otherwise normalize it.
float r = x * x + y * y;
if (r > 1.0f) {
r = std::sqrt(r);
x /= r;
y /= r;
}
return {x, y};
}
std::tuple<float, float> GetStatus() const override {
const auto [x, y] = GetAnalog(axis_x, axis_y);
const float r = std::sqrt((x * x) + (y * y));
if (r > deadzone) {
return {x / r * (r - deadzone) / (1 - deadzone),
y / r * (r - deadzone) / (1 - deadzone)};
}
return {0.0f, 0.0f};
}
std::tuple<float, float> GetRawStatus() const override {
const float x = GetAxis(axis_x);
const float y = GetAxis(axis_y);
return {x, y};
}
Input::AnalogProperties GetAnalogProperties() const override {
return {deadzone, range, 0.5f};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
const float directional_deadzone = 0.5f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
case Input::AnalogDirection::LEFT:
return x < -directional_deadzone;
case Input::AnalogDirection::UP:
return y > directional_deadzone;
case Input::AnalogDirection::DOWN:
return y < -directional_deadzone;
}
return false;
}
private:
const u32 port;
const u32 axis_x;
const u32 axis_y;
const bool invert_x;
const bool invert_y;
const float deadzone;
const float range;
const GCAdapter::Adapter* gcadapter;
mutable std::mutex mutex;
};
/// An analog device factory that creates analog devices from GC Adapter
GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
: adapter(std::move(adapter_)) {}
/**
* Creates analog device from joystick axes
* @param params contains parameters for creating the device:
* - "port": the nth gcpad on the adapter
* - "axis_x": the index of the axis to be bind as x-axis
* - "axis_y": the index of the axis to be bind as y-axis
*/
std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
const auto port = static_cast<u32>(params.Get("port", 0));
const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
const std::string invert_x_value = params.Get("invert_x", "+");
const std::string invert_y_value = params.Get("invert_y", "+");
const bool invert_x = invert_x_value == "-";
const bool invert_y = invert_y_value == "-";
return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
adapter.get());
}
void GCAnalogFactory::BeginConfiguration() {
polling = true;
adapter->BeginConfiguration();
}
void GCAnalogFactory::EndConfiguration() {
polling = false;
adapter->EndConfiguration();
}
Common::ParamPackage GCAnalogFactory::GetNextInput() {
GCAdapter::GCPadStatus pad;
Common::ParamPackage params;
auto& queue = adapter->GetPadQueue();
while (queue.Pop(pad)) {
if (pad.button != GCAdapter::PadButton::Undefined) {
params.Set("engine", "gcpad");
params.Set("port", static_cast<s32>(pad.port));
params.Set("button", static_cast<u16>(pad.button));
return params;
}
if (pad.axis == GCAdapter::PadAxes::Undefined ||
std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
continue;
}
// An analog device needs two axes, so we need to store the axis for later and wait for
// a second input event. The axes also must be from the same joystick.
const u8 axis = static_cast<u8>(pad.axis);
if (axis == 0 || axis == 1) {
analog_x_axis = 0;
analog_y_axis = 1;
controller_number = static_cast<s32>(pad.port);
break;
}
if (axis == 2 || axis == 3) {
analog_x_axis = 2;
analog_y_axis = 3;
controller_number = static_cast<s32>(pad.port);
break;
}
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<s32>(pad.port);
} else if (analog_y_axis == -1 && analog_x_axis != axis &&
controller_number == static_cast<s32>(pad.port)) {
analog_y_axis = axis;
break;
}
}
if (analog_x_axis != -1 && analog_y_axis != -1) {
params.Set("engine", "gcpad");
params.Set("port", controller_number);
params.Set("axis_x", analog_x_axis);
params.Set("axis_y", analog_y_axis);
params.Set("invert_x", "+");
params.Set("invert_y", "+");
analog_x_axis = -1;
analog_y_axis = -1;
controller_number = -1;
return params;
}
return params;
}
class GCVibration final : public Input::VibrationDevice {
public:
explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
: port(port_), gcadapter(adapter) {}
u8 GetStatus() const override {
return gcadapter->RumblePlay(port, 0);
}
bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
[[maybe_unused]] f32 freq_high) const override {
const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
const auto processed_amplitude =
static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
return gcadapter->RumblePlay(port, processed_amplitude);
}
private:
const u32 port;
GCAdapter::Adapter* gcadapter;
};
/// An vibration device factory that creates vibration devices from GC Adapter
GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
: adapter(std::move(adapter_)) {}
/**
* Creates a vibration device from a joystick
* @param params contains parameters for creating the device:
* - "port": the nth gcpad on the adapter
*/
std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
const Common::ParamPackage& params) {
const auto port = static_cast<u32>(params.Get("port", 0));
return std::make_unique<GCVibration>(port, adapter.get());
}
} // namespace InputCommon

View File

@@ -1,78 +0,0 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/frontend/input.h"
#include "input_common/gcadapter/gc_adapter.h"
namespace InputCommon {
/**
* A button device factory representing a gcpad. It receives gcpad events and forward them
* to all button devices it created.
*/
class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
public:
explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
/**
* Creates a button device from a button press
* @param params contains parameters for creating the device:
* - "code": the code of the key to bind with the button
*/
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
Common::ParamPackage GetNextInput() const;
/// For device input configuration/polling
void BeginConfiguration();
void EndConfiguration();
bool IsPolling() const {
return polling;
}
private:
std::shared_ptr<GCAdapter::Adapter> adapter;
bool polling = false;
};
/// An analog device factory that creates analog devices from GC Adapter
class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
public:
explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
Common::ParamPackage GetNextInput();
/// For device input configuration/polling
void BeginConfiguration();
void EndConfiguration();
bool IsPolling() const {
return polling;
}
private:
std::shared_ptr<GCAdapter::Adapter> adapter;
int analog_x_axis = -1;
int analog_y_axis = -1;
int controller_number = -1;
bool polling = false;
};
/// A vibration device factory creates vibration devices from GC Adapter
class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
public:
explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
private:
std::shared_ptr<GCAdapter::Adapter> adapter;
};
} // namespace InputCommon

View File

@@ -0,0 +1,304 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <cmath>
#include "common/math_util.h"
#include "common/settings.h"
#include "input_common/helpers/stick_from_buttons.h"
namespace InputCommon {
class Stick final : public Common::Input::InputDevice {
public:
using Button = std::unique_ptr<Common::Input::InputDevice>;
Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
float modifier_scale_, float modifier_angle_)
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
modifier_angle(modifier_angle_) {
Common::Input::InputCallback button_up_callback{
[this](Common::Input::CallbackStatus callback_) { UpdateUpButtonStatus(callback_); }};
Common::Input::InputCallback button_down_callback{
[this](Common::Input::CallbackStatus callback_) { UpdateDownButtonStatus(callback_); }};
Common::Input::InputCallback button_left_callback{
[this](Common::Input::CallbackStatus callback_) { UpdateLeftButtonStatus(callback_); }};
Common::Input::InputCallback button_right_callback{
[this](Common::Input::CallbackStatus callback_) {
UpdateRightButtonStatus(callback_);
}};
Common::Input::InputCallback button_modifier_callback{
[this](Common::Input::CallbackStatus callback_) { UpdateModButtonStatus(callback_); }};
up->SetCallback(button_up_callback);
down->SetCallback(button_down_callback);
left->SetCallback(button_left_callback);
right->SetCallback(button_right_callback);
modifier->SetCallback(button_modifier_callback);
last_x_axis_value = 0.0f;
last_y_axis_value = 0.0f;
}
bool IsAngleGreater(float old_angle, float new_angle) const {
constexpr float TAU = Common::PI * 2.0f;
// Use wider angle to ease the transition.
constexpr float aperture = TAU * 0.15f;
const float top_limit = new_angle + aperture;
return (old_angle > new_angle && old_angle <= top_limit) ||
(old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
}
bool IsAngleSmaller(float old_angle, float new_angle) const {
constexpr float TAU = Common::PI * 2.0f;
// Use wider angle to ease the transition.
constexpr float aperture = TAU * 0.15f;
const float bottom_limit = new_angle - aperture;
return (old_angle >= bottom_limit && old_angle < new_angle) ||
(old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
}
float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
constexpr float TAU = Common::PI * 2.0f;
float new_angle = angle;
auto time_difference = static_cast<float>(
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
time_difference /= 1000.0f * 1000.0f;
if (time_difference > 0.5f) {
time_difference = 0.5f;
}
if (IsAngleGreater(new_angle, goal_angle)) {
new_angle -= modifier_angle * time_difference;
if (new_angle < 0) {
new_angle += TAU;
}
if (!IsAngleGreater(new_angle, goal_angle)) {
return goal_angle;
}
} else if (IsAngleSmaller(new_angle, goal_angle)) {
new_angle += modifier_angle * time_difference;
if (new_angle >= TAU) {
new_angle -= TAU;
}
if (!IsAngleSmaller(new_angle, goal_angle)) {
return goal_angle;
}
} else {
return goal_angle;
}
return new_angle;
}
void SetGoalAngle(bool r, bool l, bool u, bool d) {
// Move to the right
if (r && !u && !d) {
goal_angle = 0.0f;
}
// Move to the upper right
if (r && u && !d) {
goal_angle = Common::PI * 0.25f;
}
// Move up
if (u && !l && !r) {
goal_angle = Common::PI * 0.5f;
}
// Move to the upper left
if (l && u && !d) {
goal_angle = Common::PI * 0.75f;
}
// Move to the left
if (l && !u && !d) {
goal_angle = Common::PI;
}
// Move to the bottom left
if (l && !u && d) {
goal_angle = Common::PI * 1.25f;
}
// Move down
if (d && !l && !r) {
goal_angle = Common::PI * 1.5f;
}
// Move to the bottom right
if (r && !u && d) {
goal_angle = Common::PI * 1.75f;
}
}
void UpdateUpButtonStatus(Common::Input::CallbackStatus button_callback) {
up_status = button_callback.button_status.value;
UpdateStatus();
}
void UpdateDownButtonStatus(Common::Input::CallbackStatus button_callback) {
down_status = button_callback.button_status.value;
UpdateStatus();
}
void UpdateLeftButtonStatus(Common::Input::CallbackStatus button_callback) {
left_status = button_callback.button_status.value;
UpdateStatus();
}
void UpdateRightButtonStatus(Common::Input::CallbackStatus button_callback) {
right_status = button_callback.button_status.value;
UpdateStatus();
}
void UpdateModButtonStatus(Common::Input::CallbackStatus button_callback) {
modifier_status = button_callback.button_status.value;
UpdateStatus();
}
void UpdateStatus() {
const float coef = modifier_status ? modifier_scale : 1.0f;
bool r = right_status;
bool l = left_status;
bool u = up_status;
bool d = down_status;
// Eliminate contradictory movements
if (r && l) {
r = false;
l = false;
}
if (u && d) {
u = false;
d = false;
}
// Move if a key is pressed
if (r || l || u || d) {
amplitude = coef;
} else {
amplitude = 0;
}
const auto now = std::chrono::steady_clock::now();
const auto time_difference = static_cast<u64>(
std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
if (time_difference < 10) {
// Disable analog mode if inputs are too fast
SetGoalAngle(r, l, u, d);
angle = goal_angle;
} else {
angle = GetAngle(now);
SetGoalAngle(r, l, u, d);
}
last_update = now;
Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Stick,
.stick_status = GetStatus(),
};
last_x_axis_value = status.stick_status.x.raw_value;
last_y_axis_value = status.stick_status.y.raw_value;
TriggerOnChange(status);
}
void ForceUpdate() override {
up->ForceUpdate();
down->ForceUpdate();
left->ForceUpdate();
right->ForceUpdate();
modifier->ForceUpdate();
}
void SoftUpdate() override {
Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Stick,
.stick_status = GetStatus(),
};
if (last_x_axis_value == status.stick_status.x.raw_value &&
last_y_axis_value == status.stick_status.y.raw_value) {
return;
}
last_x_axis_value = status.stick_status.x.raw_value;
last_y_axis_value = status.stick_status.y.raw_value;
TriggerOnChange(status);
}
Common::Input::StickStatus GetStatus() const {
Common::Input::StickStatus status{};
status.x.properties = properties;
status.y.properties = properties;
if (Settings::values.emulate_analog_keyboard) {
const auto now = std::chrono::steady_clock::now();
float angle_ = GetAngle(now);
status.x.raw_value = std::cos(angle_) * amplitude;
status.y.raw_value = std::sin(angle_) * amplitude;
return status;
}
constexpr float SQRT_HALF = 0.707106781f;
int x = 0, y = 0;
if (right_status) {
++x;
}
if (left_status) {
--x;
}
if (up_status) {
++y;
}
if (down_status) {
--y;
}
const float coef = modifier_status ? modifier_scale : 1.0f;
status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
return status;
}
private:
Button up;
Button down;
Button left;
Button right;
Button modifier;
float modifier_scale;
float modifier_angle;
float angle{};
float goal_angle{};
float amplitude{};
bool up_status;
bool down_status;
bool left_status;
bool right_status;
bool modifier_status;
float last_x_axis_value;
float last_y_axis_value;
const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
std::chrono::time_point<std::chrono::steady_clock> last_update;
};
std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
const Common::ParamPackage& params) {
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
params.Get("up", null_engine));
auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
params.Get("down", null_engine));
auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
params.Get("left", null_engine));
auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
params.Get("right", null_engine));
auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
params.Get("modifier", null_engine));
auto modifier_scale = params.Get("modifier_scale", 0.5f);
auto modifier_angle = params.Get("modifier_angle", 5.5f);
return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
std::move(right), std::move(modifier), modifier_scale,
modifier_angle);
}
} // namespace InputCommon

View File

@@ -4,8 +4,7 @@
#pragma once
#include <memory>
#include "core/frontend/input.h"
#include "common/input.h"
namespace InputCommon {
@@ -13,7 +12,7 @@ namespace InputCommon {
* An analog device factory that takes direction button devices and combines them into a analog
* device.
*/
class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> {
class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
public:
/**
* Creates an analog device from direction button devices
@@ -25,7 +24,7 @@ public:
* - "modifier": a serialized ParamPackage for creating a button device as the modifier
* - "modifier_scale": a float for the multiplier the modifier gives to the position
*/
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
};
} // namespace InputCommon

View File

@@ -0,0 +1,81 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "common/settings.h"
#include "core/frontend/framebuffer_layout.h"
#include "input_common/helpers/touch_from_buttons.h"
namespace InputCommon {
class TouchFromButtonDevice final : public Common::Input::InputDevice {
public:
using Button = std::unique_ptr<Common::Input::InputDevice>;
TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_)
: button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) {
Common::Input::InputCallback button_up_callback{
[this](Common::Input::CallbackStatus callback_) { UpdateButtonStatus(callback_); }};
last_button_value = false;
button->SetCallback(button_up_callback);
button->ForceUpdate();
}
void ForceUpdate() override {
button->ForceUpdate();
}
Common::Input::TouchStatus GetStatus(bool pressed) const {
const Common::Input::ButtonStatus button_status{
.value = pressed,
};
Common::Input::TouchStatus status{
.pressed = button_status,
.x = {},
.y = {},
.id = touch_id,
};
status.x.properties = properties;
status.y.properties = properties;
if (!pressed) {
return status;
}
status.x.raw_value = x;
status.y.raw_value = y;
return status;
}
void UpdateButtonStatus(Common::Input::CallbackStatus button_callback) {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Touch,
.touch_status = GetStatus(button_callback.button_status.value),
};
if (last_button_value != button_callback.button_status.value) {
last_button_value = button_callback.button_status.value;
TriggerOnChange(status);
}
}
private:
Button button;
bool last_button_value;
const int touch_id;
const float x;
const float y;
const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
};
std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
const Common::ParamPackage& params) {
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
params.Get("button", null_engine));
const auto touch_id = params.Get("touch_id", 0);
const float x = params.Get("x", 0.0f) / 1280.0f;
const float y = params.Get("y", 0.0f) / 720.0f;
return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y);
}
} // namespace InputCommon

View File

@@ -4,20 +4,19 @@
#pragma once
#include <memory>
#include "core/frontend/input.h"
#include "common/input.h"
namespace InputCommon {
/**
* A touch device factory that takes a list of button devices and combines them into a touch device.
*/
class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
public:
/**
* Creates a touch device from a list of button devices
*/
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
};
} // namespace InputCommon

View File

@@ -5,7 +5,7 @@
#include <cstddef>
#include <cstring>
#include "common/logging/log.h"
#include "input_common/udp/protocol.h"
#include "input_common/helpers/udp_protocol.h"
namespace InputCommon::CemuhookUDP {

View File

@@ -56,6 +56,12 @@ constexpr Type GetMessageType();
namespace Request {
enum RegisterFlags : u8 {
AllPads,
PadID,
PadMACAdddress,
};
struct Version {};
/**
* Requests the server to send information about what controllers are plugged into the ports
@@ -77,13 +83,8 @@ static_assert(std::is_trivially_copyable_v<PortInfo>,
* timeout seems to be 5 seconds.
*/
struct PadData {
enum class Flags : u8 {
AllPorts,
Id,
Mac,
};
/// Determines which method will be used as a look up for the controller
Flags flags{};
RegisterFlags flags{};
/// Index of the port of the controller to retrieve data about
u8 port_id{};
/// Mac address of the controller to retrieve data about
@@ -113,6 +114,36 @@ Message<T> Create(const T data, const u32 client_id = 0) {
namespace Response {
enum class ConnectionType : u8 {
None,
Usb,
Bluetooth,
};
enum class State : u8 {
Disconnected,
Reserved,
Connected,
};
enum class Model : u8 {
None,
PartialGyro,
FullGyro,
Generic,
};
enum class Battery : u8 {
None = 0x00,
Dying = 0x01,
Low = 0x02,
Medium = 0x03,
High = 0x04,
Full = 0x05,
Charging = 0xEE,
Charged = 0xEF,
};
struct Version {
u16_le version{};
};
@@ -122,11 +153,11 @@ static_assert(std::is_trivially_copyable_v<Version>,
struct PortInfo {
u8 id{};
u8 state{};
u8 model{};
u8 connection_type{};
State state{};
Model model{};
ConnectionType connection_type{};
MacAddress mac;
u8 battery{};
Battery battery{};
u8 is_pad_active{};
};
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
@@ -177,18 +208,18 @@ struct PadData {
u8 right_stick_y{};
struct AnalogButton {
u8 button_8{};
u8 button_7{};
u8 button_6{};
u8 button_5{};
u8 button_12{};
u8 button_11{};
u8 button_10{};
u8 button_9{};
u8 button_16{};
u8 button_15{};
u8 button_14{};
u8 button_13{};
u8 button_dpad_left_analog{};
u8 button_dpad_down_analog{};
u8 button_dpad_right_analog{};
u8 button_dpad_up_analog{};
u8 button_square_analog{};
u8 button_cross_analog{};
u8 button_circle_analog{};
u8 button_triangle_analog{};
u8 button_r1_analog{};
u8 button_l1_analog{};
u8 trigger_r2{};
u8 trigger_l2{};
} analog_button;
std::array<TouchPad, 2> touch;

View File

@@ -0,0 +1,364 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/logging/log.h"
#include "common/param_package.h"
#include "input_common/input_engine.h"
namespace InputCommon {
void InputEngine::PreSetController(const PadIdentifier& identifier) {
std::lock_guard lock{mutex};
if (!controller_list.contains(identifier)) {
controller_list.insert_or_assign(identifier, ControllerData{});
}
}
void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!controller.buttons.contains(button)) {
controller.buttons.insert_or_assign(button, false);
}
}
void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!controller.hat_buttons.contains(button)) {
controller.hat_buttons.insert_or_assign(button, u8{0});
}
}
void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!controller.axes.contains(axis)) {
controller.axes.insert_or_assign(axis, 0.0f);
}
}
void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!controller.motions.contains(motion)) {
controller.motions.insert_or_assign(motion, BasicMotion{});
}
}
void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
{
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.buttons.insert_or_assign(button, value);
}
}
TriggerOnButtonChange(identifier, button, value);
}
void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
{
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.hat_buttons.insert_or_assign(button, value);
}
}
TriggerOnHatButtonChange(identifier, button, value);
}
void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
{
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.axes.insert_or_assign(axis, value);
}
}
TriggerOnAxisChange(identifier, axis, value);
}
void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) {
{
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.battery = value;
}
}
TriggerOnBatteryChange(identifier, value);
}
void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value) {
{
std::lock_guard lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.motions.insert_or_assign(motion, value);
}
}
TriggerOnMotionChange(identifier, motion, value);
}
bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
std::lock_guard lock{mutex};
if (!controller_list.contains(identifier)) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
identifier.pad, identifier.port);
return false;
}
ControllerData controller = controller_list.at(identifier);
if (!controller.buttons.contains(button)) {
LOG_ERROR(Input, "Invalid button {}", button);
return false;
}
return controller.buttons.at(button);
}
bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
std::lock_guard lock{mutex};
if (!controller_list.contains(identifier)) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
identifier.pad, identifier.port);
return false;
}
ControllerData controller = controller_list.at(identifier);
if (!controller.hat_buttons.contains(button)) {
LOG_ERROR(Input, "Invalid hat button {}", button);
return false;
}
return (controller.hat_buttons.at(button) & direction) != 0;
}
f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
std::lock_guard lock{mutex};
if (!controller_list.contains(identifier)) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
identifier.pad, identifier.port);
return 0.0f;
}
ControllerData controller = controller_list.at(identifier);
if (!controller.axes.contains(axis)) {
LOG_ERROR(Input, "Invalid axis {}", axis);
return 0.0f;
}
return controller.axes.at(axis);
}
BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
std::lock_guard lock{mutex};
if (!controller_list.contains(identifier)) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
identifier.pad, identifier.port);
return BatteryLevel::Charging;
}
ControllerData controller = controller_list.at(identifier);
return controller.battery;
}
BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
std::lock_guard lock{mutex};
if (!controller_list.contains(identifier)) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
identifier.pad, identifier.port);
return {};
}
ControllerData controller = controller_list.at(identifier);
return controller.motions.at(motion);
}
void InputEngine::ResetButtonState() {
for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
for (std::pair<int, bool> button : controller.second.buttons) {
SetButton(controller.first, button.first, false);
}
for (std::pair<int, bool> button : controller.second.hat_buttons) {
SetHatButton(controller.first, button.first, false);
}
}
}
void InputEngine::ResetAnalogState() {
for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
for (std::pair<int, float> axis : controller.second.axes) {
SetAxis(controller.first, axis.first, 0.0);
}
}
}
void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
std::lock_guard lock{mutex_callback};
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
if (!configuring || !mapping_callback.on_data) {
return;
}
PreSetButton(identifier, button);
if (value == GetButton(identifier, button)) {
return;
}
mapping_callback.on_data(MappingData{
.engine = GetEngineName(),
.pad = identifier,
.type = EngineInputType::Button,
.index = button,
.button_value = value,
});
}
void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
std::lock_guard lock{mutex_callback};
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
if (!configuring || !mapping_callback.on_data) {
return;
}
for (std::size_t index = 1; index < 0xff; index <<= 1) {
bool button_value = (value & index) != 0;
if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
continue;
}
mapping_callback.on_data(MappingData{
.engine = GetEngineName(),
.pad = identifier,
.type = EngineInputType::HatButton,
.index = button,
.hat_name = GetHatButtonName(static_cast<u8>(index)),
});
}
}
void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
std::lock_guard lock{mutex_callback};
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
if (!configuring || !mapping_callback.on_data) {
return;
}
if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
return;
}
mapping_callback.on_data(MappingData{
.engine = GetEngineName(),
.pad = identifier,
.type = EngineInputType::Analog,
.index = axis,
.axis_value = value,
});
}
void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
[[maybe_unused]] BatteryLevel value) {
std::lock_guard lock{mutex_callback};
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
}
void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
BasicMotion value) {
std::lock_guard lock{mutex_callback};
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
if (!configuring || !mapping_callback.on_data) {
return;
}
if (std::abs(value.gyro_x) < 0.6f && std::abs(value.gyro_y) < 0.6f &&
std::abs(value.gyro_z) < 0.6f) {
return;
}
mapping_callback.on_data(MappingData{
.engine = GetEngineName(),
.pad = identifier,
.type = EngineInputType::Motion,
.index = motion,
.motion_value = value,
});
}
bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
const PadIdentifier& identifier, EngineInputType type,
int index) const {
if (input_identifier.type != type) {
return false;
}
if (input_identifier.index != index) {
return false;
}
if (input_identifier.identifier != identifier) {
return false;
}
return true;
}
void InputEngine::BeginConfiguration() {
configuring = true;
}
void InputEngine::EndConfiguration() {
configuring = false;
}
const std::string& InputEngine::GetEngineName() const {
return input_engine;
}
int InputEngine::SetCallback(InputIdentifier input_identifier) {
std::lock_guard lock{mutex_callback};
callback_list.insert_or_assign(last_callback_key, input_identifier);
return last_callback_key++;
}
void InputEngine::SetMappingCallback(MappingCallback callback) {
std::lock_guard lock{mutex_callback};
mapping_callback = std::move(callback);
}
void InputEngine::DeleteCallback(int key) {
std::lock_guard lock{mutex_callback};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace InputCommon

View File

@@ -0,0 +1,232 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include <functional>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/uuid.h"
#include "input_common/main.h"
// Pad Identifier of data source
struct PadIdentifier {
Common::UUID guid{};
std::size_t port{};
std::size_t pad{};
friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
};
// Basic motion data containing data from the sensors and a timestamp in microsecons
struct BasicMotion {
float gyro_x;
float gyro_y;
float gyro_z;
float accel_x;
float accel_y;
float accel_z;
u64 delta_timestamp;
};
// Stages of a battery charge
enum class BatteryLevel {
Empty,
Critical,
Low,
Medium,
Full,
Charging,
};
// Types of input that are stored in the engine
enum class EngineInputType {
None,
Button,
HatButton,
Analog,
Motion,
Battery,
};
namespace std {
// Hash used to create lists from PadIdentifier data
template <>
struct hash<PadIdentifier> {
size_t operator()(const PadIdentifier& pad_id) const noexcept {
u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0];
hash_value ^= (static_cast<u64>(pad_id.port) << 32);
hash_value ^= static_cast<u64>(pad_id.pad);
return static_cast<size_t>(hash_value);
}
};
} // namespace std
namespace InputCommon {
// Data from the engine and device needed for creating a ParamPackage
struct MappingData {
std::string engine{};
PadIdentifier pad{};
EngineInputType type{};
int index{};
bool button_value{};
std::string hat_name{};
f32 axis_value{};
BasicMotion motion_value{};
};
// Triggered if data changed on the controller
struct UpdateCallback {
std::function<void()> on_change;
};
// Triggered if data changed on the controller and the engine is on configuring mode
struct MappingCallback {
std::function<void(MappingData)> on_data;
};
// Input Identifier of data source
struct InputIdentifier {
PadIdentifier identifier;
EngineInputType type;
int index;
UpdateCallback callback;
};
class InputEngine {
public:
explicit InputEngine(const std::string& input_engine_) : input_engine(input_engine_) {
callback_list.clear();
}
virtual ~InputEngine() = default;
// Enable configuring mode for mapping
void BeginConfiguration();
// Disable configuring mode for mapping
void EndConfiguration();
// Sets a led pattern for a controller
virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::LedStatus led_status) {
return;
}
// Sets rumble to a controller
virtual Common::Input::VibrationError SetRumble(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::VibrationStatus vibration) {
return Common::Input::VibrationError::NotSupported;
}
// Sets polling mode to a controller
virtual Common::Input::PollingError SetPollingMode(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::PollingMode vibration) {
return Common::Input::PollingError::NotSupported;
}
// Returns the engine name
[[nodiscard]] const std::string& GetEngineName() const;
/// Used for automapping features
virtual std::vector<Common::ParamPackage> GetInputDevices() const {
return {};
};
/// Retrieves the button mappings for the given device
virtual InputCommon::ButtonMapping GetButtonMappingForDevice(
[[maybe_unused]] const Common::ParamPackage& params) {
return {};
};
/// Retrieves the analog mappings for the given device
virtual InputCommon::AnalogMapping GetAnalogMappingForDevice(
[[maybe_unused]] const Common::ParamPackage& params) {
return {};
};
/// Retrieves the motion mappings for the given device
virtual InputCommon::MotionMapping GetMotionMappingForDevice(
[[maybe_unused]] const Common::ParamPackage& params) {
return {};
};
/// Retrieves the name of the given input.
virtual Common::Input::ButtonNames GetUIName(
[[maybe_unused]] const Common::ParamPackage& params) const {
return Common::Input::ButtonNames::Engine;
};
/// Retrieves the index number of the given hat button direction
virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const {
return 0;
};
void PreSetController(const PadIdentifier& identifier);
void PreSetButton(const PadIdentifier& identifier, int button);
void PreSetHatButton(const PadIdentifier& identifier, int button);
void PreSetAxis(const PadIdentifier& identifier, int axis);
void PreSetMotion(const PadIdentifier& identifier, int motion);
void ResetButtonState();
void ResetAnalogState();
bool GetButton(const PadIdentifier& identifier, int button) const;
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
BatteryLevel GetBattery(const PadIdentifier& identifier) const;
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
int SetCallback(InputIdentifier input_identifier);
void SetMappingCallback(MappingCallback callback);
void DeleteCallback(int key);
protected:
void SetButton(const PadIdentifier& identifier, int button, bool value);
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
void SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value);
virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
return "Unknown";
}
private:
struct ControllerData {
std::unordered_map<int, bool> buttons;
std::unordered_map<int, u8> hat_buttons;
std::unordered_map<int, float> axes;
std::unordered_map<int, BasicMotion> motions;
BatteryLevel battery;
};
void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
void TriggerOnAxisChange(const PadIdentifier& identifier, int button, f32 value);
void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, BasicMotion value);
bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
const PadIdentifier& identifier, EngineInputType type,
int index) const;
mutable std::mutex mutex;
mutable std::mutex mutex_callback;
bool configuring{false};
const std::string input_engine;
int last_callback_key = 0;
std::unordered_map<PadIdentifier, ControllerData> controller_list;
std::unordered_map<int, InputIdentifier> callback_list;
MappingCallback mapping_callback;
};
} // namespace InputCommon

View File

@@ -0,0 +1,207 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/common_types.h"
#include "common/settings.h"
#include "input_common/input_engine.h"
#include "input_common/input_mapping.h"
namespace InputCommon {
MappingFactory::MappingFactory() {}
void MappingFactory::BeginMapping(Polling::InputType type) {
is_enabled = true;
input_type = type;
input_queue.Clear();
first_axis = -1;
second_axis = -1;
}
[[nodiscard]] const Common::ParamPackage MappingFactory::GetNextInput() {
Common::ParamPackage input;
input_queue.Pop(input);
return input;
}
void MappingFactory::RegisterInput(const MappingData& data) {
if (!is_enabled) {
return;
}
if (!IsDriverValid(data)) {
return;
}
switch (input_type) {
case Polling::InputType::Button:
RegisterButton(data);
return;
case Polling::InputType::Stick:
RegisterStick(data);
return;
case Polling::InputType::Motion:
RegisterMotion(data);
return;
default:
return;
}
}
void MappingFactory::StopMapping() {
is_enabled = false;
input_type = Polling::InputType::None;
input_queue.Clear();
}
void MappingFactory::RegisterButton(const MappingData& data) {
Common::ParamPackage new_input;
new_input.Set("engine", data.engine);
if (data.pad.guid != Common::UUID{}) {
new_input.Set("guid", data.pad.guid.Format());
}
new_input.Set("port", static_cast<int>(data.pad.port));
new_input.Set("pad", static_cast<int>(data.pad.pad));
switch (data.type) {
case EngineInputType::Button:
// Workaround for old compatibility
if (data.engine == "keyboard") {
new_input.Set("code", data.index);
break;
}
new_input.Set("button", data.index);
break;
case EngineInputType::HatButton:
new_input.Set("hat", data.index);
new_input.Set("direction", data.hat_name);
break;
case EngineInputType::Analog:
// Ignore mouse axis when mapping buttons
if (data.engine == "mouse") {
return;
}
new_input.Set("axis", data.index);
new_input.Set("threshold", 0.5f);
break;
default:
return;
}
input_queue.Push(new_input);
}
void MappingFactory::RegisterStick(const MappingData& data) {
Common::ParamPackage new_input;
new_input.Set("engine", data.engine);
if (data.pad.guid != Common::UUID{}) {
new_input.Set("guid", data.pad.guid.Format());
}
new_input.Set("port", static_cast<int>(data.pad.port));
new_input.Set("pad", static_cast<int>(data.pad.pad));
// If engine is mouse map the mouse position as a joystick
if (data.engine == "mouse") {
new_input.Set("axis_x", 0);
new_input.Set("axis_y", 1);
new_input.Set("threshold", 0.5f);
new_input.Set("range", 1.0f);
new_input.Set("deadzone", 0.0f);
input_queue.Push(new_input);
return;
}
switch (data.type) {
case EngineInputType::Button:
case EngineInputType::HatButton:
RegisterButton(data);
return;
case EngineInputType::Analog:
if (first_axis == data.index) {
return;
}
if (first_axis == -1) {
first_axis = data.index;
return;
}
new_input.Set("axis_x", first_axis);
new_input.Set("axis_y", data.index);
new_input.Set("threshold", 0.5f);
new_input.Set("range", 0.95f);
new_input.Set("deadzone", 0.15f);
break;
default:
return;
}
input_queue.Push(new_input);
}
void MappingFactory::RegisterMotion(const MappingData& data) {
Common::ParamPackage new_input;
new_input.Set("engine", data.engine);
if (data.pad.guid != Common::UUID{}) {
new_input.Set("guid", data.pad.guid.Format());
}
new_input.Set("port", static_cast<int>(data.pad.port));
new_input.Set("pad", static_cast<int>(data.pad.pad));
switch (data.type) {
case EngineInputType::Button:
case EngineInputType::HatButton:
RegisterButton(data);
return;
case EngineInputType::Analog:
if (first_axis == data.index) {
return;
}
if (second_axis == data.index) {
return;
}
if (first_axis == -1) {
first_axis = data.index;
return;
}
if (second_axis == -1) {
second_axis = data.index;
return;
}
new_input.Set("axis_x", first_axis);
new_input.Set("axis_y", second_axis);
new_input.Set("axis_z", data.index);
new_input.Set("range", 1.0f);
new_input.Set("deadzone", 0.20f);
break;
case EngineInputType::Motion:
new_input.Set("motion", data.index);
break;
default:
return;
}
input_queue.Push(new_input);
}
bool MappingFactory::IsDriverValid(const MappingData& data) const {
// Only port 0 can be mapped on the keyboard
if (data.engine == "keyboard" && data.pad.port != 0) {
return false;
}
// To prevent mapping with two devices we disable any UDP except motion
if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" &&
data.type != EngineInputType::Motion) {
return false;
}
// The following drivers don't need to be mapped
if (data.engine == "tas") {
return false;
}
if (data.engine == "touch") {
return false;
}
if (data.engine == "touch_from_button") {
return false;
}
if (data.engine == "analog_from_button") {
return false;
}
return true;
}
} // namespace InputCommon

View File

@@ -0,0 +1,83 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include "common/threadsafe_queue.h"
namespace InputCommon {
class InputEngine;
struct MappingData;
class MappingFactory {
public:
MappingFactory();
/**
* Resets all varables to beggin the mapping process
* @param "type": type of input desired to be returned
*/
void BeginMapping(Polling::InputType type);
/// Returns an input event with mapping information from the input_queue
[[nodiscard]] const Common::ParamPackage GetNextInput();
/**
* Registers mapping input data from the driver
* @param "data": An struct containing all the information needed to create a proper
* ParamPackage
*/
void RegisterInput(const MappingData& data);
/// Stop polling from all backends
void StopMapping();
private:
/**
* If provided data satisfies the requeriments it will push an element to the input_queue
* Supported input:
* - Button: Creates a basic button ParamPackage
* - HatButton: Creates a basic hat button ParamPackage
* - Analog: Creates a basic analog ParamPackage
* @param "data": An struct containing all the information needed to create a proper
* ParamPackage
*/
void RegisterButton(const MappingData& data);
/**
* If provided data satisfies the requeriments it will push an element to the input_queue
* Supported input:
* - Button, HatButton: Pass the data to RegisterButton
* - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage
* @param "data": An struct containing all the information needed to create a proper
* ParamPackage
*/
void RegisterStick(const MappingData& data);
/**
* If provided data satisfies the requeriments it will push an element to the input_queue
* Supported input:
* - Button, HatButton: Pass the data to RegisterButton
* - Analog: Stores the first two axis and on the third axis creates a basic Motion
* ParamPackage
* - Motion: Creates a basic Motion ParamPackage
* @param "data": An struct containing all the information needed to create a proper
* ParamPackage
*/
void RegisterMotion(const MappingData& data);
/**
* Returns true if driver can be mapped
* @param "data": An struct containing all the information needed to create a proper
* ParamPackage
*/
bool IsDriverValid(const MappingData& data) const;
Common::SPSCQueue<Common::ParamPackage> input_queue;
Polling::InputType input_type{Polling::InputType::None};
bool is_enabled{};
int first_axis = -1;
int second_axis = -1;
};
} // namespace InputCommon

View File

@@ -0,0 +1,971 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/common_types.h"
#include "common/input.h"
#include "input_common/input_engine.h"
#include "input_common/input_poller.h"
namespace InputCommon {
class DummyInput final : public Common::Input::InputDevice {
public:
explicit DummyInput() {}
~DummyInput() {}
};
class InputFromButton final : public Common::Input::InputDevice {
public:
explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
InputEngine* input_engine_)
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::Button,
.index = button,
.callback = engine_callback,
};
last_button_value = false;
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromButton() {
input_engine->DeleteCallback(callback_key);
}
Common::Input::ButtonStatus GetStatus() const {
return {
.value = input_engine->GetButton(identifier, button),
.inverted = inverted,
.toggle = toggle,
};
}
void ForceUpdate() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Button,
.button_status = GetStatus(),
};
last_button_value = status.button_status.value;
TriggerOnChange(status);
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Button,
.button_status = GetStatus(),
};
if (status.button_status.value != last_button_value) {
last_button_value = status.button_status.value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int button;
const bool toggle;
const bool inverted;
int callback_key;
bool last_button_value;
InputEngine* input_engine;
};
class InputFromHatButton final : public Common::Input::InputDevice {
public:
explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_,
bool inverted_, InputEngine* input_engine_)
: identifier(identifier_), button(button_), direction(direction_), toggle(toggle_),
inverted(inverted_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::HatButton,
.index = button,
.callback = engine_callback,
};
last_button_value = false;
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromHatButton() {
input_engine->DeleteCallback(callback_key);
}
Common::Input::ButtonStatus GetStatus() const {
return {
.value = input_engine->GetHatButton(identifier, button, direction),
.inverted = inverted,
.toggle = toggle,
};
}
void ForceUpdate() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Button,
.button_status = GetStatus(),
};
last_button_value = status.button_status.value;
TriggerOnChange(status);
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Button,
.button_status = GetStatus(),
};
if (status.button_status.value != last_button_value) {
last_button_value = status.button_status.value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int button;
const u8 direction;
const bool toggle;
const bool inverted;
int callback_key;
bool last_button_value;
InputEngine* input_engine;
};
class InputFromStick final : public Common::Input::InputDevice {
public:
explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_,
Common::Input::AnalogProperties properties_x_,
Common::Input::AnalogProperties properties_y_,
InputEngine* input_engine_)
: identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
properties_y(properties_y_),
input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier x_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_x,
.callback = engine_callback,
};
const InputIdentifier y_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_y,
.callback = engine_callback,
};
last_axis_x_value = 0.0f;
last_axis_y_value = 0.0f;
callback_key_x = input_engine->SetCallback(x_input_identifier);
callback_key_y = input_engine->SetCallback(y_input_identifier);
}
~InputFromStick() {
input_engine->DeleteCallback(callback_key_x);
input_engine->DeleteCallback(callback_key_y);
}
Common::Input::StickStatus GetStatus() const {
Common::Input::StickStatus status;
status.x = {
.raw_value = input_engine->GetAxis(identifier, axis_x),
.properties = properties_x,
};
status.y = {
.raw_value = input_engine->GetAxis(identifier, axis_y),
.properties = properties_y,
};
// This is a workaround too keep compatibility with old yuzu versions. Vertical axis is
// inverted on SDL compared to Nintendo
if (invert_axis_y) {
status.y.raw_value = -status.y.raw_value;
}
return status;
}
void ForceUpdate() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Stick,
.stick_status = GetStatus(),
};
last_axis_x_value = status.stick_status.x.raw_value;
last_axis_y_value = status.stick_status.y.raw_value;
TriggerOnChange(status);
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Stick,
.stick_status = GetStatus(),
};
if (status.stick_status.x.raw_value != last_axis_x_value ||
status.stick_status.y.raw_value != last_axis_y_value) {
last_axis_x_value = status.stick_status.x.raw_value;
last_axis_y_value = status.stick_status.y.raw_value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int axis_x;
const int axis_y;
const Common::Input::AnalogProperties properties_x;
const Common::Input::AnalogProperties properties_y;
int callback_key_x;
int callback_key_y;
float last_axis_x_value;
float last_axis_y_value;
InputEngine* input_engine;
const bool invert_axis_y;
};
class InputFromTouch final : public Common::Input::InputDevice {
public:
explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_,
bool inverted_, int axis_x_, int axis_y_,
Common::Input::AnalogProperties properties_x_,
Common::Input::AnalogProperties properties_y_,
InputEngine* input_engine_)
: identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_),
inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
properties_y(properties_y_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier button_input_identifier{
.identifier = identifier,
.type = EngineInputType::Button,
.index = button,
.callback = engine_callback,
};
const InputIdentifier x_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_x,
.callback = engine_callback,
};
const InputIdentifier y_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_y,
.callback = engine_callback,
};
last_axis_x_value = 0.0f;
last_axis_y_value = 0.0f;
last_button_value = false;
callback_key_button = input_engine->SetCallback(button_input_identifier);
callback_key_x = input_engine->SetCallback(x_input_identifier);
callback_key_y = input_engine->SetCallback(y_input_identifier);
}
~InputFromTouch() {
input_engine->DeleteCallback(callback_key_button);
input_engine->DeleteCallback(callback_key_x);
input_engine->DeleteCallback(callback_key_y);
}
Common::Input::TouchStatus GetStatus() const {
Common::Input::TouchStatus status;
status.id = touch_id;
status.pressed = {
.value = input_engine->GetButton(identifier, button),
.inverted = inverted,
.toggle = toggle,
};
status.x = {
.raw_value = input_engine->GetAxis(identifier, axis_x),
.properties = properties_x,
};
status.y = {
.raw_value = input_engine->GetAxis(identifier, axis_y),
.properties = properties_y,
};
return status;
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Touch,
.touch_status = GetStatus(),
};
if (status.touch_status.x.raw_value != last_axis_x_value ||
status.touch_status.y.raw_value != last_axis_y_value ||
status.touch_status.pressed.value != last_button_value) {
last_axis_x_value = status.touch_status.x.raw_value;
last_axis_y_value = status.touch_status.y.raw_value;
last_button_value = status.touch_status.pressed.value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int touch_id;
const int button;
const bool toggle;
const bool inverted;
const int axis_x;
const int axis_y;
const Common::Input::AnalogProperties properties_x;
const Common::Input::AnalogProperties properties_y;
int callback_key_button;
int callback_key_x;
int callback_key_y;
bool last_button_value;
float last_axis_x_value;
float last_axis_y_value;
InputEngine* input_engine;
};
class InputFromTrigger final : public Common::Input::InputDevice {
public:
explicit InputFromTrigger(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
int axis_, Common::Input::AnalogProperties properties_,
InputEngine* input_engine_)
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
axis(axis_), properties(properties_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier button_input_identifier{
.identifier = identifier,
.type = EngineInputType::Button,
.index = button,
.callback = engine_callback,
};
const InputIdentifier axis_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis,
.callback = engine_callback,
};
last_axis_value = 0.0f;
last_button_value = false;
callback_key_button = input_engine->SetCallback(button_input_identifier);
axis_callback_key = input_engine->SetCallback(axis_input_identifier);
}
~InputFromTrigger() {
input_engine->DeleteCallback(callback_key_button);
input_engine->DeleteCallback(axis_callback_key);
}
Common::Input::TriggerStatus GetStatus() const {
const Common::Input::AnalogStatus analog_status{
.raw_value = input_engine->GetAxis(identifier, axis),
.properties = properties,
};
const Common::Input::ButtonStatus button_status{
.value = input_engine->GetButton(identifier, button),
.inverted = inverted,
.toggle = toggle,
};
return {
.analog = analog_status,
.pressed = button_status,
};
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Trigger,
.trigger_status = GetStatus(),
};
if (status.trigger_status.analog.raw_value != last_axis_value ||
status.trigger_status.pressed.value != last_button_value) {
last_axis_value = status.trigger_status.analog.raw_value;
last_button_value = status.trigger_status.pressed.value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int button;
const bool toggle;
const bool inverted;
const int axis;
const Common::Input::AnalogProperties properties;
int callback_key_button;
int axis_callback_key;
bool last_button_value;
float last_axis_value;
InputEngine* input_engine;
};
class InputFromAnalog final : public Common::Input::InputDevice {
public:
explicit InputFromAnalog(PadIdentifier identifier_, int axis_,
Common::Input::AnalogProperties properties_,
InputEngine* input_engine_)
: identifier(identifier_), axis(axis_), properties(properties_),
input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis,
.callback = engine_callback,
};
last_axis_value = 0.0f;
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromAnalog() {
input_engine->DeleteCallback(callback_key);
}
Common::Input::AnalogStatus GetStatus() const {
return {
.raw_value = input_engine->GetAxis(identifier, axis),
.properties = properties,
};
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Analog,
.analog_status = GetStatus(),
};
if (status.analog_status.raw_value != last_axis_value) {
last_axis_value = status.analog_status.raw_value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int axis;
const Common::Input::AnalogProperties properties;
int callback_key;
float last_axis_value;
InputEngine* input_engine;
};
class InputFromBattery final : public Common::Input::InputDevice {
public:
explicit InputFromBattery(PadIdentifier identifier_, InputEngine* input_engine_)
: identifier(identifier_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::Battery,
.index = 0,
.callback = engine_callback,
};
last_battery_value = Common::Input::BatteryStatus::Charging;
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromBattery() {
input_engine->DeleteCallback(callback_key);
}
Common::Input::BatteryStatus GetStatus() const {
return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier));
}
void ForceUpdate() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Battery,
.battery_status = GetStatus(),
};
last_battery_value = status.battery_status;
TriggerOnChange(status);
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Battery,
.battery_status = GetStatus(),
};
if (status.battery_status != last_battery_value) {
last_battery_value = status.battery_status;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
int callback_key;
Common::Input::BatteryStatus last_battery_value;
InputEngine* input_engine;
};
class InputFromMotion final : public Common::Input::InputDevice {
public:
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_,
InputEngine* input_engine_)
: identifier(identifier_), motion_sensor(motion_sensor_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::Motion,
.index = motion_sensor,
.callback = engine_callback,
};
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromMotion() {
input_engine->DeleteCallback(callback_key);
}
Common::Input::MotionStatus GetStatus() const {
const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
Common::Input::MotionStatus status{};
const Common::Input::AnalogProperties properties = {
.deadzone = 0.001f,
.range = 1.0f,
.offset = 0.0f,
};
status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties};
status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties};
status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties};
status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties};
status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties};
status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties};
status.delta_timestamp = basic_motion.delta_timestamp;
return status;
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Motion,
.motion_status = GetStatus(),
};
TriggerOnChange(status);
}
private:
const PadIdentifier identifier;
const int motion_sensor;
int callback_key;
InputEngine* input_engine;
};
class InputFromAxisMotion final : public Common::Input::InputDevice {
public:
explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_,
Common::Input::AnalogProperties properties_x_,
Common::Input::AnalogProperties properties_y_,
Common::Input::AnalogProperties properties_z_,
InputEngine* input_engine_)
: identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_),
properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_),
input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier x_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_x,
.callback = engine_callback,
};
const InputIdentifier y_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_y,
.callback = engine_callback,
};
const InputIdentifier z_input_identifier{
.identifier = identifier,
.type = EngineInputType::Analog,
.index = axis_z,
.callback = engine_callback,
};
last_axis_x_value = 0.0f;
last_axis_y_value = 0.0f;
last_axis_z_value = 0.0f;
callback_key_x = input_engine->SetCallback(x_input_identifier);
callback_key_y = input_engine->SetCallback(y_input_identifier);
callback_key_z = input_engine->SetCallback(z_input_identifier);
}
~InputFromAxisMotion() {
input_engine->DeleteCallback(callback_key_x);
input_engine->DeleteCallback(callback_key_y);
input_engine->DeleteCallback(callback_key_z);
}
Common::Input::MotionStatus GetStatus() const {
Common::Input::MotionStatus status{};
status.gyro.x = {
.raw_value = input_engine->GetAxis(identifier, axis_x),
.properties = properties_x,
};
status.gyro.y = {
.raw_value = input_engine->GetAxis(identifier, axis_y),
.properties = properties_y,
};
status.gyro.z = {
.raw_value = input_engine->GetAxis(identifier, axis_z),
.properties = properties_z,
};
status.delta_timestamp = 5000;
status.force_update = true;
return status;
}
void ForceUpdate() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Motion,
.motion_status = GetStatus(),
};
last_axis_x_value = status.motion_status.gyro.x.raw_value;
last_axis_y_value = status.motion_status.gyro.y.raw_value;
last_axis_z_value = status.motion_status.gyro.z.raw_value;
TriggerOnChange(status);
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Motion,
.motion_status = GetStatus(),
};
if (status.motion_status.gyro.x.raw_value != last_axis_x_value ||
status.motion_status.gyro.y.raw_value != last_axis_y_value ||
status.motion_status.gyro.z.raw_value != last_axis_z_value) {
last_axis_x_value = status.motion_status.gyro.x.raw_value;
last_axis_y_value = status.motion_status.gyro.y.raw_value;
last_axis_z_value = status.motion_status.gyro.z.raw_value;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
const int axis_x;
const int axis_y;
const int axis_z;
const Common::Input::AnalogProperties properties_x;
const Common::Input::AnalogProperties properties_y;
const Common::Input::AnalogProperties properties_z;
int callback_key_x;
int callback_key_y;
int callback_key_z;
float last_axis_x_value;
float last_axis_y_value;
float last_axis_z_value;
InputEngine* input_engine;
};
class OutputFromIdentifier final : public Common::Input::OutputDevice {
public:
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
: identifier(identifier_), input_engine(input_engine_) {}
virtual void SetLED(Common::Input::LedStatus led_status) {
input_engine->SetLeds(identifier, led_status);
}
virtual Common::Input::VibrationError SetVibration(
Common::Input::VibrationStatus vibration_status) {
return input_engine->SetRumble(identifier, vibration_status);
}
virtual Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) {
return input_engine->SetPollingMode(identifier, polling_mode);
}
private:
const PadIdentifier identifier;
InputEngine* input_engine;
};
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
const auto button_id = params.Get("button", 0);
const auto keyboard_key = params.Get("code", 0);
const auto toggle = params.Get("toggle", false);
const auto inverted = params.Get("inverted", false);
input_engine->PreSetController(identifier);
input_engine->PreSetButton(identifier, button_id);
input_engine->PreSetButton(identifier, keyboard_key);
if (keyboard_key != 0) {
return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted,
input_engine.get());
}
return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
const auto button_id = params.Get("hat", 0);
const auto direction = input_engine->GetHatButtonId(params.Get("direction", ""));
const auto toggle = params.Get("toggle", false);
const auto inverted = params.Get("inverted", false);
input_engine->PreSetController(identifier);
input_engine->PreSetHatButton(identifier, button_id);
return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
const Common::ParamPackage& params) {
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
const auto axis_x = params.Get("axis_x", 0);
const Common::Input::AnalogProperties properties_x = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_x", "+") == "-",
};
const auto axis_y = params.Get("axis_y", 1);
const Common::Input::AnalogProperties properties_y = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_y", "+") != "+",
};
input_engine->PreSetController(identifier);
input_engine->PreSetAxis(identifier, axis_x);
input_engine->PreSetAxis(identifier, axis_y);
return std::make_unique<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y,
input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
const auto axis = params.Get("axis", 0);
const Common::Input::AnalogProperties properties = {
.deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
.range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f),
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert", "+") == "-",
};
input_engine->PreSetController(identifier);
input_engine->PreSetAxis(identifier, axis);
return std::make_unique<InputFromAnalog>(identifier, axis, properties, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
const auto button = params.Get("button", 0);
const auto toggle = params.Get("toggle", false);
const auto inverted = params.Get("inverted", false);
const auto axis = params.Get("axis", 0);
const Common::Input::AnalogProperties properties = {
.deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
.range = std::clamp(params.Get("range", 1.0f), 0.25f, 2.50f),
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert", false) != 0,
};
input_engine->PreSetController(identifier);
input_engine->PreSetAxis(identifier, axis);
input_engine->PreSetButton(identifier, button);
return std::make_unique<InputFromTrigger>(identifier, button, toggle, inverted, axis,
properties, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
const Common::ParamPackage& params) {
const auto touch_id = params.Get("touch_id", 0);
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
const auto button = params.Get("button", 0);
const auto toggle = params.Get("toggle", false);
const auto inverted = params.Get("inverted", false);
const auto axis_x = params.Get("axis_x", 0);
const Common::Input::AnalogProperties properties_x = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_x", "+") == "-",
};
const auto axis_y = params.Get("axis_y", 1);
const Common::Input::AnalogProperties properties_y = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_y", false) != 0,
};
input_engine->PreSetController(identifier);
input_engine->PreSetAxis(identifier, axis_x);
input_engine->PreSetAxis(identifier, axis_y);
input_engine->PreSetButton(identifier, button);
return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x,
axis_y, properties_x, properties_y, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
input_engine->PreSetController(identifier);
return std::make_unique<InputFromBattery>(identifier, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
Common::ParamPackage params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
if (params.Has("motion")) {
const auto motion_sensor = params.Get("motion", 0);
input_engine->PreSetController(identifier);
input_engine->PreSetMotion(identifier, motion_sensor);
return std::make_unique<InputFromMotion>(identifier, motion_sensor, input_engine.get());
}
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
const auto axis_x = params.Get("axis_x", 0);
const Common::Input::AnalogProperties properties_x = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_x", "+") == "-",
};
const auto axis_y = params.Get("axis_y", 1);
const Common::Input::AnalogProperties properties_y = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_y", "+") != "+",
};
const auto axis_z = params.Get("axis_z", 1);
const Common::Input::AnalogProperties properties_z = {
.deadzone = deadzone,
.range = range,
.threshold = threshold,
.offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert_z", "+") != "+",
};
input_engine->PreSetController(identifier);
input_engine->PreSetAxis(identifier, axis_x);
input_engine->PreSetAxis(identifier, axis_y);
input_engine->PreSetAxis(identifier, axis_z);
return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
properties_y, properties_z, input_engine.get());
}
InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
: input_engine(std::move(input_engine_)) {}
std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
const Common::ParamPackage& params) {
if (params.Has("battery")) {
return CreateBatteryDevice(params);
}
if (params.Has("button") && params.Has("axis")) {
return CreateTriggerDevice(params);
}
if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) {
return CreateTouchDevice(params);
}
if (params.Has("button") || params.Has("code")) {
return CreateButtonDevice(params);
}
if (params.Has("hat")) {
return CreateHatButtonDevice(params);
}
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
return CreateMotionDevice(params);
}
if (params.Has("motion")) {
return CreateMotionDevice(params);
}
if (params.Has("axis_x") && params.Has("axis_y")) {
return CreateStickDevice(params);
}
if (params.Has("axis")) {
return CreateAnalogDevice(params);
}
LOG_ERROR(Input, "Invalid parameters given");
return std::make_unique<DummyInput>();
}
OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
: input_engine(std::move(input_engine_)) {}
std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
input_engine->PreSetController(identifier);
return std::make_unique<OutputFromIdentifier>(identifier, input_engine.get());
}
} // namespace InputCommon

View File

@@ -0,0 +1,217 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
namespace Input {
class InputDevice;
template <typename InputDevice>
class Factory;
}; // namespace Input
namespace InputCommon {
class InputEngine;
/**
* An Input factory. It receives input events and forward them to all input devices it created.
*/
class OutputFactory final : public Common::Input::Factory<Common::Input::OutputDevice> {
public:
explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_);
/**
* Creates an output device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique ouput device with the parameters specified
*/
std::unique_ptr<Common::Input::OutputDevice> Create(
const Common::ParamPackage& params) override;
private:
std::shared_ptr<InputEngine> input_engine;
};
class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> {
public:
explicit InputFactory(std::shared_ptr<InputEngine> input_engine_);
/**
* Creates an input device from the parameters given. Identifies the type of input to be
* returned if it contains the following parameters:
* - button: Contains "button" or "code"
* - hat_button: Contains "hat"
* - analog: Contains "axis"
* - trigger: Contains "button" and "axis"
* - stick: Contains "axis_x" and "axis_y"
* - motion: Contains "axis_x", "axis_y" and "axis_z"
* - motion: Contains "motion"
* - touch: Contains "button", "axis_x" and "axis_y"
* - battery: Contains "battery"
* - output: Contains "output"
* @param params contains parameters for creating the device:
* @param - "code": the code of the keyboard key to bind with the input
* @param - "button": same as "code" but for controller buttons
* @param - "hat": similar as "button" but it's a group of hat buttons from SDL
* @param - "axis": the axis number of the axis to bind with the input
* @param - "motion": the motion number of the motion to bind with the input
* @param - "axis_x": same as axis but specifing horizontal direction
* @param - "axis_y": same as axis but specifing vertical direction
* @param - "axis_z": same as axis but specifing forward direction
* @param - "battery": Only used as a placeholder to set the input type
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
private:
/**
* Creates a button device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "code": the code of the keyboard key to bind with the input
* @param - "button": same as "code" but for controller buttons
* @param - "toggle": press once to enable, press again to disable
* @param - "inverted": inverts the output of the button
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateButtonDevice(
const Common::ParamPackage& params);
/**
* Creates a hat button device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "button": the controller hat id to bind with the input
* @param - "direction": the direction id to be detected
* @param - "toggle": press once to enable, press again to disable
* @param - "inverted": inverts the output of the button
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateHatButtonDevice(
const Common::ParamPackage& params);
/**
* Creates a stick device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "axis_x": the controller horizontal axis id to bind with the input
* @param - "axis_y": the controller vertical axis id to bind with the input
* @param - "deadzone": the mimimum required value to be detected
* @param - "range": the maximum value required to reach 100%
* @param - "threshold": the mimimum required value to considered pressed
* @param - "offset_x": the amount of offset in the x axis
* @param - "offset_y": the amount of offset in the y axis
* @param - "invert_x": inverts the sign of the horizontal axis
* @param - "invert_y": inverts the sign of the vertical axis
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateStickDevice(
const Common::ParamPackage& params);
/**
* Creates an analog device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "axis": the controller axis id to bind with the input
* @param - "deadzone": the mimimum required value to be detected
* @param - "range": the maximum value required to reach 100%
* @param - "threshold": the mimimum required value to considered pressed
* @param - "offset": the amount of offset in the axis
* @param - "invert": inverts the sign of the axis
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateAnalogDevice(
const Common::ParamPackage& params);
/**
* Creates a trigger device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "button": the controller hat id to bind with the input
* @param - "direction": the direction id to be detected
* @param - "toggle": press once to enable, press again to disable
* @param - "inverted": inverts the output of the button
* @param - "axis": the controller axis id to bind with the input
* @param - "deadzone": the mimimum required value to be detected
* @param - "range": the maximum value required to reach 100%
* @param - "threshold": the mimimum required value to considered pressed
* @param - "offset": the amount of offset in the axis
* @param - "invert": inverts the sign of the axis
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateTriggerDevice(
const Common::ParamPackage& params);
/**
* Creates a touch device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "button": the controller hat id to bind with the input
* @param - "direction": the direction id to be detected
* @param - "toggle": press once to enable, press again to disable
* @param - "inverted": inverts the output of the button
* @param - "axis_x": the controller horizontal axis id to bind with the input
* @param - "axis_y": the controller vertical axis id to bind with the input
* @param - "deadzone": the mimimum required value to be detected
* @param - "range": the maximum value required to reach 100%
* @param - "threshold": the mimimum required value to considered pressed
* @param - "offset_x": the amount of offset in the x axis
* @param - "offset_y": the amount of offset in the y axis
* @param - "invert_x": inverts the sign of the horizontal axis
* @param - "invert_y": inverts the sign of the vertical axis
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateTouchDevice(
const Common::ParamPackage& params);
/**
* Creates a battery device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
const Common::ParamPackage& params);
/**
* Creates a motion device from the parameters given.
* @param params contains parameters for creating the device:
* @param - "axis_x": the controller horizontal axis id to bind with the input
* @param - "axis_y": the controller vertical axis id to bind with the input
* @param - "axis_z": the controller fordward axis id to bind with the input
* @param - "deadzone": the mimimum required value to be detected
* @param - "range": the maximum value required to reach 100%
* @param - "offset_x": the amount of offset in the x axis
* @param - "offset_y": the amount of offset in the y axis
* @param - "offset_z": the amount of offset in the z axis
* @param - "invert_x": inverts the sign of the horizontal axis
* @param - "invert_y": inverts the sign of the vertical axis
* @param - "invert_z": inverts the sign of the fordward axis
* @param - "guid": text string for identifing controllers
* @param - "port": port of the connected device
* @param - "pad": slot of the connected controller
* @return an unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
std::shared_ptr<InputEngine> input_engine;
};
} // namespace InputCommon

View File

@@ -1,121 +0,0 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <list>
#include <mutex>
#include <utility>
#include "input_common/keyboard.h"
namespace InputCommon {
class KeyButton final : public Input::ButtonDevice {
public:
explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_, bool toggle_)
: key_button_list(std::move(key_button_list_)), toggle(toggle_) {}
~KeyButton() override;
bool GetStatus() const override {
if (toggle) {
return toggled_status.load(std::memory_order_relaxed);
}
return status.load();
}
void ToggleButton() {
if (lock) {
return;
}
lock = true;
const bool old_toggle_status = toggled_status.load();
toggled_status.store(!old_toggle_status);
}
void UnlockButton() {
lock = false;
}
friend class KeyButtonList;
private:
std::shared_ptr<KeyButtonList> key_button_list;
std::atomic<bool> status{false};
std::atomic<bool> toggled_status{false};
bool lock{false};
const bool toggle;
};
struct KeyButtonPair {
int key_code;
KeyButton* key_button;
};
class KeyButtonList {
public:
void AddKeyButton(int key_code, KeyButton* key_button) {
std::lock_guard guard{mutex};
list.push_back(KeyButtonPair{key_code, key_button});
}
void RemoveKeyButton(const KeyButton* key_button) {
std::lock_guard guard{mutex};
list.remove_if(
[key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
}
void ChangeKeyStatus(int key_code, bool pressed) {
std::lock_guard guard{mutex};
for (const KeyButtonPair& pair : list) {
if (pair.key_code == key_code) {
pair.key_button->status.store(pressed);
if (pressed) {
pair.key_button->ToggleButton();
} else {
pair.key_button->UnlockButton();
}
pair.key_button->TriggerOnChange();
}
}
}
void ChangeAllKeyStatus(bool pressed) {
std::lock_guard guard{mutex};
for (const KeyButtonPair& pair : list) {
pair.key_button->status.store(pressed);
}
}
private:
std::mutex mutex;
std::list<KeyButtonPair> list;
};
Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
KeyButton::~KeyButton() {
key_button_list->RemoveKeyButton(this);
}
std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
const int key_code = params.Get("code", 0);
const bool toggle = params.Get("toggle", false);
std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list, toggle);
key_button_list->AddKeyButton(key_code, button.get());
return button;
}
void Keyboard::PressKey(int key_code) {
key_button_list->ChangeKeyStatus(key_code, true);
}
void Keyboard::ReleaseKey(int key_code) {
key_button_list->ChangeKeyStatus(key_code, false);
}
void Keyboard::ReleaseAllKeys() {
key_button_list->ChangeAllKeyStatus(false);
}
} // namespace InputCommon

Some files were not shown because too many files have changed in this diff Show More