Compare commits

..

109 Commits

Author SHA1 Message Date
Rodolfo Bogado
02eacfd431 Multiple viewports are only valid while using geometry shaders so avoid uploading states that can be potentially invalid. 2018-11-22 16:04:02 -03:00
Rodolfo Bogado
3ae614c493 Add support for viewport_transform_enabled
fix Clear viewport area by calculating overlapping area when viewport and scissor are enabled at the same time.
add warning messages for extension that are not required but are needed for a correct emulation.
2018-11-22 12:17:37 -03:00
Rodolfo Bogado
0749600ca8 Implement depth clamp 2018-11-22 12:00:12 -03:00
Rodolfo Bogado
7aa12b2467 Add support for clear_flags register 2018-11-22 12:00:12 -03:00
bunnei
b84f4cfb62 Merge pull request #1737 from FernandoS27/layer-copy
Implemented Fast Layered Copy
2018-11-21 21:39:16 -08:00
bunnei
f0d3f1b376 Merge pull request #1771 from lioncash/bit-set
common: Remove bit_set.h
2018-11-21 21:36:15 -08:00
bunnei
f926559ef4 Merge pull request #1767 from lioncash/handle
kernel/handle_table: Minor changes
2018-11-21 21:26:48 -08:00
Lioncash
8b27e73bd7 common: Remove bit_set.h
This is an analog of BitSet from Dolphin that was introduced to allow
iterating over a set of bits. Given it's currently unused, and given
that std::bitset exists, we can remove this. If it's ever needed in the
future it can be brought back.
2018-11-21 21:30:10 -05:00
Lioncash
0e35f1bb18 kernel/handle_table: Move private static functions into the cpp file
These don't depend on class state, and are effectively implementation
details, so they can go into the cpp file .
2018-11-21 18:31:01 -05:00
Lioncash
568bcbc29d kernel/handle_table: Restrict handle table size to 1024 entries
The previous handle table size is a holdover from Citra. The actual
handle table construct on Horizon only allows for a maximum of 1024
entries.
2018-11-21 18:28:03 -05:00
Lioncash
f5ce71793e kernel/handle_table: Default destructor in the cpp file
We don't need to potentially inline the teardown logic of all of the
handle instances.
2018-11-21 18:23:09 -05:00
bunnei
ec38b4e883 Merge pull request #1753 from FernandoS27/ufbtype
Use default values for unknown framebuffer pixel format
2018-11-21 14:15:27 -08:00
bunnei
61586e8794 Merge pull request #1752 from ReinUsesLisp/unimpl-decompiler
gl_shader_decompiler: Use UNIMPLEMENTED when applicable
2018-11-21 14:13:28 -08:00
bunnei
d4012a4540 Merge pull request #1742 from lioncash/hle-swkbd
am/applets: Minor cleanup
2018-11-21 11:43:43 -08:00
bunnei
bb175ab430 Merge pull request #1754 from ReinUsesLisp/zero-register
gl_shader_decompiler: Remove UNREACHABLE when setting RZ
2018-11-21 08:06:29 -08:00
bunnei
8cdb48224d Merge pull request #1758 from lioncash/rect
common/math_util: Minor cleanup
2018-11-21 08:05:39 -08:00
bunnei
81e14c072a Merge pull request #1759 from lioncash/unused
common: Remove depencency on xbyak
2018-11-21 08:05:14 -08:00
FernandoS27
0a9fedfac9 Use default values for unknown framebuffer pixel format 2018-11-21 07:33:34 -04:00
Lioncash
3533d33ff5 common: Remove dependency on xbyak
Xbyak is currently entirely unused. Rather than carting it along, remove
it and get rid of a dependency. If it's ever needed in the future, then
it can be re-added (and likely be more up to date at that point in
time).
2018-11-21 03:43:41 -05:00
Lioncash
45211a7a91 common/math_util: Simplify std::make_signed usages to std::make_signed_t
Gets rid of the need to use typename to access the ::type alias.
2018-11-21 02:08:18 -05:00
Lioncash
f11173f88c common/math_util: Make Rectangle's constructors constexpr
Allows objects that contain rectangle instances to be constexpr
constructible as well.
2018-11-21 02:08:18 -05:00
Lioncash
cc0801745a common/math_util: Remove unnecessary static from PI
const/constexpr variables have internal linkage by default.
2018-11-21 02:08:18 -05:00
Lioncash
74fd0aa2e8 common/math_util: Remove unused IntervalsIntersect() function
This hasn't been used since the project started, so we may as well get
rid of it to keep it from bit rotting.
2018-11-21 02:08:15 -05:00
bunnei
5af4160bf2 Merge pull request #1751 from bunnei/color-mask-fix
maxwell_3d: Initialize rasterizer color mask registers as enabled.
2018-11-20 19:20:13 -08:00
ReinUsesLisp
423a3ed2c8 gl_shader_decompiler: Remove UNREACHABLE when setting RZ 2018-11-20 22:23:10 -03:00
bunnei
b55c7bbcf7 Merge pull request #1750 from lioncash/amend
am: Correct build failure
2018-11-20 17:00:29 -08:00
ReinUsesLisp
bb893188eb gl_shader_decompiler: Use UNIMPLEMENTED instead of LOG+UNREACHABLE when applicable 2018-11-20 22:00:13 -03:00
bunnei
1a543723ab maxwell_3d: Initialize rasterizer color mask registers as enabled.
- Fixes rendering regression with Sonic Mania.
2018-11-20 19:58:06 -05:00
Lioncash
f17e122025 am: Correct build failure
The interface for shared memory was changed, but another commit was
merged that relied on the (previously public) internals of SharedMemory.

This amends that discrepancy.
2018-11-20 19:49:07 -05:00
bunnei
aa7e53ab5c Merge pull request #1734 from lioncash/shared
kernel/shared_memory: Make data members private, plus minor interface changes
2018-11-20 16:13:30 -08:00
bunnei
ab292c501c Merge pull request #1733 from lioncash/ldr
ldr: Clean up error codes
2018-11-20 16:13:09 -08:00
bunnei
67486c0568 Merge pull request #1746 from lioncash/random
kernel/process: Move <random> include to the cpp file
2018-11-20 16:12:29 -08:00
bunnei
1d0604e33c Merge pull request #1748 from lioncash/assert
common/assert: Make the UNIMPLEMENTED macro properly assert
2018-11-20 16:11:30 -08:00
bunnei
58d82243f7 Merge pull request #1749 from lioncash/gc-info
file_sys/card_image: Provide named members for the GamecardInfo struct
2018-11-20 16:10:55 -08:00
Lioncash
820bcee6a4 file_sys/card_image: Provide named members for the GamecardInfo struct
Fills out the struct according to information provided by SwitchBrew
2018-11-20 18:40:53 -05:00
Lioncash
9dcc229dfe common/assert: Add UNIMPLEMENTED_IF and UNIMPLEMENTED_IF_MSG for conditional assertions
Currently, there's no way to specify if an assertion should
conditionally occur due to unimplemented behavior. This is useful when
something is only partially implemented (e.g. due to ongoing RE work).
In particular, this would be useful within the graphics code.

The rationale behind this is it allows a dev to disable unimplemented
feature assertions (which can occur in an unrelated work area), while
still enabling regular assertions, which act as behavior guards for
conditions or states which must not occur. Previously, the only way a
dev could temporarily disable asserts, was to disable the regular
assertion macros, which has the downside of also disabling, well, the
regular assertions which hold more sanitizing value, as opposed to
unimplemented feature assertions.
2018-11-20 18:15:37 -05:00
Lioncash
aaec85df9e common/assert: Make the UNIMPLEMENTED macro properly assert
Currently, this was only performing a logging call, which doesn't
actually invoke any assertion behavior. This is unlike
UNIMPLEMENTED_MSG, which *does* assert.

This makes the expected behavior uniform across both macros.
2018-11-20 17:59:00 -05:00
Lioncash
31d1e06eb1 kernel/process: Move <random> include to the cpp file
<random> isn't necesary directly within the header and can be placed in
the cpp file where its needed. Avoids propagating random generation
utilities via a header file.
2018-11-20 17:46:20 -05:00
Lioncash
73b7748984 am/applets: Make the applet data broker part of the applet itself.
The accessor should be doing just that, accessing, rather than retaining
the lifetime of the data broker as well.
2018-11-20 12:36:33 -05:00
Lioncash
8b4b560df5 am/applets: Replace includes with forward declarations where applicable
Also resolve places where includes should have been provided, but
weren't.
2018-11-20 11:53:55 -05:00
Lioncash
dd254c603d am/applets: Relocate comments above the relevant data member in AppletDataBroker
Avoids wonky wrapping and makes it nicer to read.
2018-11-20 11:49:49 -05:00
bunnei
b6d2c64f4d Merge pull request #1667 from DarkLordZach/swkbd
am: Implement HLE software keyboard applet
2018-11-20 08:24:11 -08:00
bunnei
e9265ac598 Merge pull request #1739 from lioncash/lm
lm: Implement SetDestination by doing nothing
2018-11-19 18:40:17 -08:00
bunnei
2caac4a395 Merge pull request #1738 from lioncash/res-limit
kernel/resource_limit: Clean up interface
2018-11-19 18:40:02 -08:00
FernandoS27
eb36463e03 Implemented Fast Layered Copy 2018-11-19 19:51:13 -04:00
Lioncash
5d46038c5c kernel/resource_limit: Clean up interface
Cleans out the citra/3DS-specific implementation details that don't
apply to the Switch. Sets the stage for implementing ResourceLimit
instances properly.

While we're at it, remove the erroneous checks within CreateThread() and
SetThreadPriority(). While these are indeed checked in some capacity,
they are not checked via a ResourceLimit instance.

In the process of moving out Citra-specifics, this also replaces the
system ResourceLimit instance's values with ones from the Switch.
2018-11-19 18:16:39 -05:00
Lioncash
34e4aaddd9 lm: Implement SetDestination by doing nothing
This service function was likely intended to be a way to redirect where
the output of a log went. e.g. Firing a log over a network, dumping over
a tunneling session, etc.

Given we always want to see the log and not change its output. It's one
of the lucky service functions where the easiest implementation is to
just do nothing at all and return success.
2018-11-19 18:09:40 -05:00
Zach Hilman
a9fa890f14 software_keyboard: Fix erroneous extra PushNormalData 2018-11-19 16:30:17 -05:00
Zach Hilman
d68795c665 software_keyboard: Return correct result code on user cancel operation 2018-11-19 15:10:01 -05:00
Zach Hilman
32775125b7 applet: Add AppletDataBroker to manage HLE to AM service interaction
This cleans up most of the callbacks and such in the Applets::Applet interface, while also properly implementing all four data channels.
2018-11-19 14:24:36 -05:00
bunnei
048da7240d Merge pull request #1634 from DarkLordZach/better-hid-2
hid: Add support for multiplayer and multilayout controllers
2018-11-19 08:30:10 -08:00
Zach Hilman
96535c13a5 software_keyboard: Use correct offset for inital text string 2018-11-19 11:22:04 -05:00
Lioncash
233e495c14 kernel/shared_memory: Make Map() and Unmap() take the target process by reference rather than as a pointer
Both member functions assume the passed in target process will not be
null. Instead of making this assumption implicit, we can change the
functions to be references and enforce this at the type-system level.
2018-11-19 09:20:29 -05:00
Lioncash
fb5d4b17de kernel/shared_memory: Add a const qualified member function overload for GetPointer()
Given this doesn't mutate instance state, we can provide a
const-qualified variant as well.
2018-11-19 09:20:29 -05:00
Lioncash
2d37ca3726 kernel/shared_memory: Use 64-bit types for offset and size in CreateForApplet
Keeps the interface consistent with the regular Create() function.
2018-11-19 09:20:29 -05:00
Lioncash
76ac234bf6 kernel/shared_memory: Make GetPointer() take a std::size_t instead of a u32
Makes the interface nicer to use in terms of 64-bit code, as it makes it
less likely for one to get truncation warnings (and also makes sense in
the context of the rest of the interface where 64-bit types are used for
sizes and offsets
2018-11-19 09:20:29 -05:00
Lioncash
f472232705 kernel/shared_memory: Make data members private
Rather than allow unfettered access to the class internals, we hide all
members by default and create and API that other code can operate
against.
2018-11-19 09:20:25 -05:00
Lioncash
43e7c6cf49 ldr: Clean up error codes
The separate enum isn't particularly necessary here, and the values can
just be directly put into the ResultCode instances, given the names are
also self-documenting here.
2018-11-19 08:12:25 -05:00
Zach Hilman
aef0d88165 configure_input: Use Joycons Docked instead of Connected as label 2018-11-18 23:22:36 -05:00
Zach Hilman
312ef596a5 configure_input_player: Set minimum width on controls 2018-11-18 23:22:36 -05:00
Zach Hilman
dd92db3fb0 configure_input: Properly update UI components on removal of player 2018-11-18 23:22:36 -05:00
Zach Hilman
e58c951a59 configure_input: Make None a controller option instead of checkbox 2018-11-18 23:22:36 -05:00
Zach Hilman
3a6cd5b3c8 hid: Use player-defined controller type as PREFERRED_CONTROLLER 2018-11-18 23:22:36 -05:00
Zach Hilman
3d1a221893 qt: Move controller button config to separate dialog
Handles button configuration for all controller layouts and debug pads. Configurable at construction.
2018-11-18 23:22:36 -05:00
Zach Hilman
afe8df5020 qt: Add UI to configure touchscreen parameters
This allows adjusting the finger, diameter, and angle of the emulated touchscreen. It also provides a warning to the user about what changing these parameters can do.
2018-11-18 23:22:36 -05:00
Zach Hilman
2e1dd9c649 qt: Add UI to configure mouse buttons
Supports setting the five mouse buttons to any valid controller button/keyboard key (Left, Right, Middle, Foward, Back)
2018-11-18 23:22:36 -05:00
Zach Hilman
f1aec256d7 configure_input: Add support for multiplayer and controller types
This moves the actual button configuration to a separate dialog and only has the enabled and type controls in the tab.
2018-11-18 23:22:36 -05:00
Zach Hilman
55ded706d6 hid/npad: Update NPad to use player controller bindings and type 2018-11-18 23:22:36 -05:00
Zach Hilman
e9145c3e16 hid/touchscreen: Update Touchscreen to use advanced parameters
Including finger ID, diamater x/y, and angle. Additionally, checks if the touchscreen is enabled.
2018-11-18 23:22:36 -05:00
Zach Hilman
3b25426bd9 hid: Add controller bindings for Mouse controller 2018-11-18 23:22:36 -05:00
Zach Hilman
0fd45e78f4 hid: Add keyboard bindings for Keyboard controller 2018-11-18 23:22:36 -05:00
Zach Hilman
06cf050c0a hid: Add controller bindings for DebugPad controller
Used by developers to test games, not present on retail systems. Some games are known to respond to DebugPad input though, for example Kirby Star Allies.
2018-11-18 23:22:36 -05:00
Zach Hilman
d1b7c65b9e yuzu/config: Add (de-)serialization for multiplayer
Defaults to full keyboard for keyboard -- It did not seem to be necessary to make the keyboard configurable (besides enabled/disabled).
2018-11-18 23:22:36 -05:00
Zach Hilman
b8f7f9651e yuzu_cmd/config: Add config deserialization for multiplayer 2018-11-18 23:22:36 -05:00
Zach Hilman
c77454b9d0 settings: Add settings for multiple players and controllers
Uses the PlayerInput struct to represent all of the data that constitutes a player.
2018-11-18 23:21:33 -05:00
Zach Hilman
fd5fa48674 settings: Add Native type for keyboard 2018-11-18 23:21:33 -05:00
Zach Hilman
152422bab1 settings: Add Native type for mouse buttons 2018-11-18 23:21:33 -05:00
David Marcec
0c3e7b7086 Added missing start/end touch attributes to touchscreen 2018-11-18 23:21:33 -05:00
David Marcec
f66c6fe554 Added debugpad skeleton 2018-11-18 23:21:33 -05:00
David Marcec
362b28d052 Added controller helper funcs 2018-11-18 23:21:33 -05:00
David Marcec
a69b9d73f5 Changed polling rate of hid and Right joycon rotation 2018-11-18 23:21:33 -05:00
David Marcec
7fbe2c83a7 Left joycon rotation button remapping 2018-11-18 23:21:33 -05:00
David Marcec
b9c1e4b0e7 Added automatic npad switch based on supported stylesets 2018-11-18 23:21:33 -05:00
David Marcec
beab38601b Added multi-input support and controller assignment at any port 2018-11-18 23:21:33 -05:00
Zach Hilman
ea680bea60 software_keyboard: Check for UTF-8 config flag 2018-11-18 23:14:48 -05:00
Zach Hilman
56cf5b7b17 software_keyboard: Add max and current length display to dialog 2018-11-18 10:53:47 -05:00
Zach Hilman
02e6602baa software_keyboard: Push all data over all channels on dialog completion 2018-11-18 10:53:47 -05:00
Zach Hilman
4ee087fb3c applet: Use std::queue instead of std::vector for storage stack 2018-11-18 10:53:47 -05:00
Zach Hilman
19b2571aec applet: Add operation completed callback 2018-11-18 10:53:47 -05:00
Zach Hilman
6209fe0c27 software_keyboard: Push buffer size to offset 0x4 in output data 2018-11-18 10:53:47 -05:00
Zach Hilman
8b433beff3 software_keyboard: Make GetText asynchronous
a
2018-11-18 10:53:47 -05:00
Zach Hilman
7cfb29de23 am: Allow applets to push multiple and different channels of data 2018-11-18 10:53:47 -05:00
Zach Hilman
3cf7246e37 am: Implement ILibraryAppletAccessor IsCompleted and GetResult 2018-11-18 10:53:47 -05:00
Zach Hilman
fed6ab14c3 am: Implement text check software keyboard mode
Allows the game to verify and send a message to the frontend.
2018-11-18 10:53:47 -05:00
Zach Hilman
e696ed1f4d am: Deglobalize software keyboard applet 2018-11-18 10:53:47 -05:00
Zach Hilman
a81645400f qt/main: Register Qt Software Keyboard frontend with AM
Allows using Qt provider over default.
2018-11-18 10:53:47 -05:00
Zach Hilman
48fcb43585 am: Construct and use proper applets with ILibraryAppletAccessor
Allows use of software keyboard applet and future applets to be easily added by adding enum ID and a switch case.
2018-11-18 10:53:47 -05:00
Zach Hilman
5454494adb qt/applets: Provide Qt frontend implementation of software keyboard
Implements all of the features of the keyboard, including length, default text, character validation, and UTF-16 character support.
2018-11-18 10:53:47 -05:00
Zach Hilman
de16c1e453 am/applets: Add connector between frontend and AM applet classes
Provides a middleman between the Frontend provider class and the expected AM::Applets::Applet class needed by ILibraryAppletAccessor
2018-11-18 10:53:47 -05:00
Zach Hilman
ae53b84efd frontend/applets: Add frontend software keyboard provider and default
Default implementation will return "yuzu" for any string. GUI clients (or CLI) can implement the Frontend::SoftwareKeyboardApplet class and register an instance to provide functionality.
2018-11-18 10:53:47 -05:00
Zach Hilman
5b95de0c9c am/applets: Add Applet superclass to describe a generic applet
Adds an Initialize and Execute methods which are used by the ILibraryAppletAccessor to start and control the applet.
2018-11-18 10:53:47 -05:00
Zach Hilman
731b4bd691 am: Unstub ILibraryAppletAccessor::Start
Now starts the applet provided in constructor.
2018-11-18 10:53:47 -05:00
Zach Hilman
ba03bfa430 am: Implement PopInteractiveOutData and PushInteractiveInData
Used by software keyboard applet for data transfer.
2018-11-18 10:53:47 -05:00
Zach Hilman
5ce6b8fea7 am: Convert storage stack to vector
std::stack was no longer suitable for non-trivial operations
2018-11-18 10:53:47 -05:00
Zach Hilman
0682a908c0 am: Move AM::IStorage to header
Needs to be accessible by applet files.
2018-11-18 10:53:47 -05:00
Zach Hilman
c7b6c9de9c am: Move IStorageAccessor to header and update backing buffer
Writes to an AM::IStorage object through an IStorageAccessor will now be preserved once the accessor is destroyed.
2018-11-18 10:53:47 -05:00
Zach Hilman
76d515327b am: Implement CreateTransferMemoryStorage
Creates an AM::IStorage object with the contents of the transfer memory located at the handle provided.
2018-11-18 10:53:47 -05:00
Zach Hilman
c70529c1ec string_util: Implement buffer to UTF-16 string helper function
Needed as most all software keyboard functions use fixed-length UTF16 string buffers.
2018-11-18 10:53:47 -05:00
Zach Hilman
7901de2b75 svc: Implement svcCreateTransferMemory
Seems to be used and created identically to SharedMemory, so just reuse that.
2018-11-18 10:53:47 -05:00
88 changed files with 6571 additions and 2893 deletions

3
.gitmodules vendored
View File

@@ -13,9 +13,6 @@
[submodule "dynarmic"]
path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git
[submodule "xbyak"]
path = externals/xbyak
url = https://github.com/herumi/xbyak.git
[submodule "fmt"]
path = externals/fmt
url = https://github.com/fmtlib/fmt.git

View File

@@ -9,7 +9,6 @@ target_include_directories(catch-single-include INTERFACE catch/single_include)
# Dynarmic
if (ARCHITECTURE_x86_64)
add_library(xbyak INTERFACE)
set(DYNARMIC_TESTS OFF)
set(DYNARMIC_NO_BUNDLED_FMT ON)
add_subdirectory(dynarmic)
@@ -53,14 +52,6 @@ target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
# SoundTouch
add_subdirectory(soundtouch)
# Xbyak
if (ARCHITECTURE_x86_64)
# Defined before "dynarmic" above
# add_library(xbyak INTERFACE)
target_include_directories(xbyak INTERFACE ./xbyak/xbyak)
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
endif()
# Opus
add_subdirectory(opus)
target_include_directories(opus INTERFACE ./opus/include)

1
externals/xbyak vendored

Submodule externals/xbyak deleted from 1de435ed04

View File

@@ -44,7 +44,6 @@ add_library(common STATIC
detached_tasks.cpp
detached_tasks.h
bit_field.h
bit_set.h
cityhash.cpp
cityhash.h
color.h
@@ -95,14 +94,9 @@ if(ARCHITECTURE_x86_64)
PRIVATE
x64/cpu_detect.cpp
x64/cpu_detect.h
x64/xbyak_abi.h
x64/xbyak_util.h
)
endif()
create_target_directory_groups(common)
target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
if (ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak)
endif()

View File

@@ -52,5 +52,8 @@ __declspec(noinline, noreturn)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...)
#endif
#define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!")
#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)

View File

@@ -1,244 +0,0 @@
// This file is under the public domain.
#pragma once
#include <cstddef>
#ifdef _WIN32
#include <intrin.h>
#endif
#include <initializer_list>
#include <new>
#include <type_traits>
#include "common/common_types.h"
// namespace avoids conflict with OS X Carbon; don't use BitSet<T> directly
namespace Common {
// Helper functions:
#ifdef _MSC_VER
template <typename T>
static inline int CountSetBits(T v) {
// from https://graphics.stanford.edu/~seander/bithacks.html
// GCC has this built in, but MSVC's intrinsic will only emit the actual
// POPCNT instruction, which we're not depending on
v = v - ((v >> 1) & (T) ~(T)0 / 3);
v = (v & (T) ~(T)0 / 15 * 3) + ((v >> 2) & (T) ~(T)0 / 15 * 3);
v = (v + (v >> 4)) & (T) ~(T)0 / 255 * 15;
return (T)(v * ((T) ~(T)0 / 255)) >> (sizeof(T) - 1) * 8;
}
static inline int LeastSignificantSetBit(u8 val) {
unsigned long index;
_BitScanForward(&index, val);
return (int)index;
}
static inline int LeastSignificantSetBit(u16 val) {
unsigned long index;
_BitScanForward(&index, val);
return (int)index;
}
static inline int LeastSignificantSetBit(u32 val) {
unsigned long index;
_BitScanForward(&index, val);
return (int)index;
}
static inline int LeastSignificantSetBit(u64 val) {
unsigned long index;
_BitScanForward64(&index, val);
return (int)index;
}
#else
static inline int CountSetBits(u8 val) {
return __builtin_popcount(val);
}
static inline int CountSetBits(u16 val) {
return __builtin_popcount(val);
}
static inline int CountSetBits(u32 val) {
return __builtin_popcount(val);
}
static inline int CountSetBits(u64 val) {
return __builtin_popcountll(val);
}
static inline int LeastSignificantSetBit(u8 val) {
return __builtin_ctz(val);
}
static inline int LeastSignificantSetBit(u16 val) {
return __builtin_ctz(val);
}
static inline int LeastSignificantSetBit(u32 val) {
return __builtin_ctz(val);
}
static inline int LeastSignificantSetBit(u64 val) {
return __builtin_ctzll(val);
}
#endif
// Similar to std::bitset, this is a class which encapsulates a bitset, i.e.
// using the set bits of an integer to represent a set of integers. Like that
// class, it acts like an array of bools:
// BitSet32 bs;
// bs[1] = true;
// but also like the underlying integer ([0] = least significant bit):
// BitSet32 bs2 = ...;
// bs = (bs ^ bs2) & BitSet32(0xffff);
// The following additional functionality is provided:
// - Construction using an initializer list.
// BitSet bs { 1, 2, 4, 8 };
// - Efficiently iterating through the set bits:
// for (int i : bs)
// [i is the *index* of a set bit]
// (This uses the appropriate CPU instruction to find the next set bit in one
// operation.)
// - Counting set bits using .Count() - see comment on that method.
// TODO: use constexpr when MSVC gets out of the Dark Ages
template <typename IntTy>
class BitSet {
static_assert(!std::is_signed_v<IntTy>, "BitSet should not be used with signed types");
public:
// A reference to a particular bit, returned from operator[].
class Ref {
public:
Ref(Ref&& other) : m_bs(other.m_bs), m_mask(other.m_mask) {}
Ref(BitSet* bs, IntTy mask) : m_bs(bs), m_mask(mask) {}
operator bool() const {
return (m_bs->m_val & m_mask) != 0;
}
bool operator=(bool set) {
m_bs->m_val = (m_bs->m_val & ~m_mask) | (set ? m_mask : 0);
return set;
}
private:
BitSet* m_bs;
IntTy m_mask;
};
// A STL-like iterator is required to be able to use range-based for loops.
class Iterator {
public:
Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {}
Iterator(IntTy val) : m_val(val), m_bit(0) {}
Iterator& operator=(Iterator other) {
new (this) Iterator(other);
return *this;
}
int operator*() {
return m_bit + ComputeLsb();
}
Iterator& operator++() {
int lsb = ComputeLsb();
m_val >>= lsb + 1;
m_bit += lsb + 1;
m_has_lsb = false;
return *this;
}
Iterator operator++(int _) {
Iterator other(*this);
++*this;
return other;
}
bool operator==(Iterator other) const {
return m_val == other.m_val;
}
bool operator!=(Iterator other) const {
return m_val != other.m_val;
}
private:
int ComputeLsb() {
if (!m_has_lsb) {
m_lsb = LeastSignificantSetBit(m_val);
m_has_lsb = true;
}
return m_lsb;
}
IntTy m_val;
int m_bit;
int m_lsb = -1;
bool m_has_lsb = false;
};
BitSet() : m_val(0) {}
explicit BitSet(IntTy val) : m_val(val) {}
BitSet(std::initializer_list<int> init) {
m_val = 0;
for (int bit : init)
m_val |= (IntTy)1 << bit;
}
static BitSet AllTrue(std::size_t count) {
return BitSet(count == sizeof(IntTy) * 8 ? ~(IntTy)0 : (((IntTy)1 << count) - 1));
}
Ref operator[](std::size_t bit) {
return Ref(this, (IntTy)1 << bit);
}
const Ref operator[](std::size_t bit) const {
return (*const_cast<BitSet*>(this))[bit];
}
bool operator==(BitSet other) const {
return m_val == other.m_val;
}
bool operator!=(BitSet other) const {
return m_val != other.m_val;
}
bool operator<(BitSet other) const {
return m_val < other.m_val;
}
bool operator>(BitSet other) const {
return m_val > other.m_val;
}
BitSet operator|(BitSet other) const {
return BitSet(m_val | other.m_val);
}
BitSet operator&(BitSet other) const {
return BitSet(m_val & other.m_val);
}
BitSet operator^(BitSet other) const {
return BitSet(m_val ^ other.m_val);
}
BitSet operator~() const {
return BitSet(~m_val);
}
BitSet& operator|=(BitSet other) {
return *this = *this | other;
}
BitSet& operator&=(BitSet other) {
return *this = *this & other;
}
BitSet& operator^=(BitSet other) {
return *this = *this ^ other;
}
operator u32() = delete;
operator bool() {
return m_val != 0;
}
// Warning: Even though on modern CPUs this is a single fast instruction,
// Dolphin's official builds do not currently assume POPCNT support on x86,
// so slower explicit bit twiddling is generated. Still should generally
// be faster than a loop.
unsigned int Count() const {
return CountSetBits(m_val);
}
Iterator begin() const {
return Iterator(m_val);
}
Iterator end() const {
return Iterator(0);
}
IntTy m_val;
};
} // namespace Common
typedef Common::BitSet<u8> BitSet8;
typedef Common::BitSet<u16> BitSet16;
typedef Common::BitSet<u32> BitSet32;
typedef Common::BitSet<u64> BitSet64;

View File

@@ -4,18 +4,12 @@
#pragma once
#include <algorithm>
#include <cstdlib>
#include <type_traits>
namespace MathUtil {
static constexpr float PI = 3.14159265f;
inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1,
unsigned length1) {
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
}
constexpr float PI = 3.14159265f;
template <class T>
struct Rectangle {
@@ -24,16 +18,16 @@ struct Rectangle {
T right{};
T bottom{};
Rectangle() = default;
constexpr Rectangle() = default;
Rectangle(T left, T top, T right, T bottom)
constexpr Rectangle(T left, T top, T right, T bottom)
: left(left), top(top), right(right), bottom(bottom) {}
T GetWidth() const {
return std::abs(static_cast<typename std::make_signed<T>::type>(right - left));
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
}
T GetHeight() const {
return std::abs(static_cast<typename std::make_signed<T>::type>(bottom - top));
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
}
Rectangle<T> TranslateX(const T x) const {
return Rectangle{left + x, top, right + x, bottom};

View File

@@ -214,6 +214,15 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t
return std::string(buffer, len);
}
std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
std::size_t max_len) {
std::size_t len = 0;
while (len < max_len && buffer[len] != '\0')
++len;
return std::u16string(buffer.begin(), buffer.begin() + len);
}
const char* TrimSourcePath(const char* path, const char* root) {
const char* p = path;

View File

@@ -66,6 +66,14 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) {
*/
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len);
/**
* Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't
* null-terminated, then the string ends at the greatest multiple of two less then or equal to
* max_len_bytes.
*/
std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
std::size_t max_len);
/**
* Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
* intended to be used to strip a system-specific build directory from the `__FILE__` macro,

View File

@@ -49,22 +49,6 @@ struct ThreadQueueList {
return T();
}
template <typename UnaryPredicate>
T get_first_filter(UnaryPredicate filter) const {
const Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
for (const auto& item : cur->data) {
if (filter(item))
return item;
}
}
cur = cur->next_nonempty;
}
return T();
}
T pop_first() {
Queue* cur = first;
while (cur != nullptr) {

View File

@@ -1,222 +0,0 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <initializer_list>
#include <xbyak.h>
#include "common/assert.h"
#include "common/bit_set.h"
namespace Common::X64 {
inline int RegToIndex(const Xbyak::Reg& reg) {
using Kind = Xbyak::Reg::Kind;
ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
"RegSet only support GPRs and XMM registers.");
ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15.");
return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16);
}
inline Xbyak::Reg64 IndexToReg64(int reg_index) {
ASSERT(reg_index < 16);
return Xbyak::Reg64(reg_index);
}
inline Xbyak::Xmm IndexToXmm(int reg_index) {
ASSERT(reg_index >= 16 && reg_index < 32);
return Xbyak::Xmm(reg_index - 16);
}
inline Xbyak::Reg IndexToReg(int reg_index) {
if (reg_index < 16) {
return IndexToReg64(reg_index);
} else {
return IndexToXmm(reg_index);
}
}
inline BitSet32 BuildRegSet(std::initializer_list<Xbyak::Reg> regs) {
BitSet32 bits;
for (const Xbyak::Reg& reg : regs) {
bits[RegToIndex(reg)] = true;
}
return bits;
}
const BitSet32 ABI_ALL_GPRS(0x0000FFFF);
const BitSet32 ABI_ALL_XMMS(0xFFFF0000);
#ifdef _WIN32
// Microsoft x64 ABI
const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
Xbyak::util::rcx,
Xbyak::util::rdx,
Xbyak::util::r8,
Xbyak::util::r9,
Xbyak::util::r10,
Xbyak::util::r11,
// XMMs
Xbyak::util::xmm0,
Xbyak::util::xmm1,
Xbyak::util::xmm2,
Xbyak::util::xmm3,
Xbyak::util::xmm4,
Xbyak::util::xmm5,
});
const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({
// GPRs
Xbyak::util::rbx,
Xbyak::util::rsi,
Xbyak::util::rdi,
Xbyak::util::rbp,
Xbyak::util::r12,
Xbyak::util::r13,
Xbyak::util::r14,
Xbyak::util::r15,
// XMMs
Xbyak::util::xmm6,
Xbyak::util::xmm7,
Xbyak::util::xmm8,
Xbyak::util::xmm9,
Xbyak::util::xmm10,
Xbyak::util::xmm11,
Xbyak::util::xmm12,
Xbyak::util::xmm13,
Xbyak::util::xmm14,
Xbyak::util::xmm15,
});
constexpr std::size_t ABI_SHADOW_SPACE = 0x20;
#else
// System V x86-64 ABI
const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
Xbyak::util::rcx,
Xbyak::util::rdx,
Xbyak::util::rdi,
Xbyak::util::rsi,
Xbyak::util::r8,
Xbyak::util::r9,
Xbyak::util::r10,
Xbyak::util::r11,
// XMMs
Xbyak::util::xmm0,
Xbyak::util::xmm1,
Xbyak::util::xmm2,
Xbyak::util::xmm3,
Xbyak::util::xmm4,
Xbyak::util::xmm5,
Xbyak::util::xmm6,
Xbyak::util::xmm7,
Xbyak::util::xmm8,
Xbyak::util::xmm9,
Xbyak::util::xmm10,
Xbyak::util::xmm11,
Xbyak::util::xmm12,
Xbyak::util::xmm13,
Xbyak::util::xmm14,
Xbyak::util::xmm15,
});
const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({
// GPRs
Xbyak::util::rbx,
Xbyak::util::rbp,
Xbyak::util::r12,
Xbyak::util::r13,
Xbyak::util::r14,
Xbyak::util::r15,
});
constexpr std::size_t ABI_SHADOW_SPACE = 0;
#endif
inline void ABI_CalculateFrameSize(BitSet32 regs, std::size_t rsp_alignment,
std::size_t needed_frame_size, s32* out_subtraction,
s32* out_xmm_offset) {
int count = (regs & ABI_ALL_GPRS).Count();
rsp_alignment -= count * 8;
std::size_t subtraction = 0;
int xmm_count = (regs & ABI_ALL_XMMS).Count();
if (xmm_count) {
// If we have any XMMs to save, we must align the stack here.
subtraction = rsp_alignment & 0xF;
}
subtraction += 0x10 * xmm_count;
std::size_t xmm_base_subtraction = subtraction;
subtraction += needed_frame_size;
subtraction += ABI_SHADOW_SPACE;
// Final alignment.
rsp_alignment -= subtraction;
subtraction += rsp_alignment & 0xF;
*out_subtraction = (s32)subtraction;
*out_xmm_offset = (s32)(subtraction - xmm_base_subtraction);
}
inline std::size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
std::size_t rsp_alignment,
std::size_t needed_frame_size = 0) {
s32 subtraction, xmm_offset;
ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset);
for (int reg_index : (regs & ABI_ALL_GPRS)) {
code.push(IndexToReg64(reg_index));
}
if (subtraction != 0) {
code.sub(code.rsp, subtraction);
}
for (int reg_index : (regs & ABI_ALL_XMMS)) {
code.movaps(code.xword[code.rsp + xmm_offset], IndexToXmm(reg_index));
xmm_offset += 0x10;
}
return ABI_SHADOW_SPACE;
}
inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
std::size_t rsp_alignment,
std::size_t needed_frame_size = 0) {
s32 subtraction, xmm_offset;
ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset);
for (int reg_index : (regs & ABI_ALL_XMMS)) {
code.movaps(IndexToXmm(reg_index), code.xword[code.rsp + xmm_offset]);
xmm_offset += 0x10;
}
if (subtraction != 0) {
code.add(code.rsp, subtraction);
}
// GPRs need to be popped in reverse order
for (int reg_index = 15; reg_index >= 0; reg_index--) {
if (regs[reg_index]) {
code.pop(IndexToReg64(reg_index));
}
}
}
} // namespace Common::X64

View File

@@ -1,47 +0,0 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <type_traits>
#include <xbyak.h>
#include "common/x64/xbyak_abi.h"
namespace Common::X64 {
// Constants for use with cmpps/cmpss
enum {
CMP_EQ = 0,
CMP_LT = 1,
CMP_LE = 2,
CMP_UNORD = 3,
CMP_NEQ = 4,
CMP_NLT = 5,
CMP_NLE = 6,
CMP_ORD = 7,
};
inline bool IsWithin2G(uintptr_t ref, uintptr_t target) {
u64 distance = target - (ref + 5);
return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL);
}
inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target);
}
template <typename T>
inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
std::size_t addr = reinterpret_cast<std::size_t>(f);
if (IsWithin2G(code, addr)) {
code.call(f);
} else {
// ABI_RETURN is a safe temp register to use before a call
code.mov(ABI_RETURN, addr);
code.call(ABI_RETURN);
}
}
} // namespace Common::X64

View File

@@ -77,6 +77,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
frontend/applets/software_keyboard.cpp
frontend/applets/software_keyboard.h
frontend/emu_window.cpp
frontend/emu_window.h
frontend/framebuffer_layout.cpp
@@ -150,6 +152,10 @@ add_library(core STATIC
hle/service/am/applet_ae.h
hle/service/am/applet_oe.cpp
hle/service/am/applet_oe.h
hle/service/am/applets/applets.cpp
hle/service/am/applets/applets.h
hle/service/am/applets/software_keyboard.cpp
hle/service/am/applets/software_keyboard.h
hle/service/am/idle.cpp
hle/service/am/idle.h
hle/service/am/omm.cpp

View File

@@ -23,12 +23,14 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/am/applets/software_keyboard.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
#include "frontend/applets/software_keyboard.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
@@ -136,6 +138,10 @@ struct System::Impl {
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
/// Create default implementations of applets if one is not provided.
if (software_keyboard == nullptr)
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
auto main_process = Kernel::Process::Create(kernel, "main");
kernel.MakeCurrentProcess(main_process.get());
@@ -289,6 +295,9 @@ struct System::Impl {
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
std::size_t active_core{}; ///< Active core, only used in single thread mode
/// Frontend applets
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -488,6 +497,14 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
return impl->virtual_filesystem;
}
void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) {
impl->software_keyboard = std::move(applet);
}
const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
return *impl->software_keyboard;
}
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(emu_window);
}

View File

@@ -13,6 +13,7 @@
namespace Core::Frontend {
class EmuWindow;
class SoftwareKeyboardApplet;
} // namespace Core::Frontend
namespace FileSys {
@@ -236,6 +237,10 @@ public:
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet);
const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
private:
System();

View File

@@ -32,7 +32,18 @@ enum class GamecardSize : u8 {
};
struct GamecardInfo {
std::array<u8, 0x70> data;
u64_le firmware_version;
u32_le access_control_flags;
u32_le read_wait_time1;
u32_le read_wait_time2;
u32_le write_wait_time1;
u32_le write_wait_time2;
u32_le firmware_mode;
u32_le cup_version;
std::array<u8, 4> reserved1;
u64_le update_partition_hash;
u64_le cup_id;
std::array<u8, 0x38> reserved2;
};
static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");

View File

@@ -0,0 +1,29 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/backend.h"
#include "common/string_util.h"
#include "core/frontend/applets/software_keyboard.h"
namespace Core::Frontend {
SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default;
void DefaultSoftwareKeyboardApplet::RequestText(
std::function<void(std::optional<std::u16string>)> out,
SoftwareKeyboardParameters parameters) const {
if (parameters.initial_text.empty())
out(u"yuzu");
out(parameters.initial_text);
}
void DefaultSoftwareKeyboardApplet::SendTextCheckDialog(
std::u16string error_message, std::function<void()> finished_check) const {
LOG_WARNING(Service_AM,
"(STUBBED) called - Default fallback software keyboard does not support text "
"check! (error_message={})",
Common::UTF16ToUTF8(error_message));
finished_check();
}
} // namespace Core::Frontend

View File

@@ -0,0 +1,54 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <optional>
#include <string>
#include "common/bit_field.h"
#include "common/common_types.h"
namespace Core::Frontend {
struct SoftwareKeyboardParameters {
std::u16string submit_text;
std::u16string header_text;
std::u16string sub_text;
std::u16string guide_text;
std::u16string initial_text;
std::size_t max_length;
bool password;
bool cursor_at_beginning;
union {
u8 value;
BitField<1, 1, u8> disable_space;
BitField<2, 1, u8> disable_address;
BitField<3, 1, u8> disable_percent;
BitField<4, 1, u8> disable_slash;
BitField<6, 1, u8> disable_number;
BitField<7, 1, u8> disable_download_code;
};
};
class SoftwareKeyboardApplet {
public:
virtual ~SoftwareKeyboardApplet();
virtual void RequestText(std::function<void(std::optional<std::u16string>)> out,
SoftwareKeyboardParameters parameters) const = 0;
virtual void SendTextCheckDialog(std::u16string error_message,
std::function<void()> finished_check) const = 0;
};
class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet {
public:
void RequestText(std::function<void(std::optional<std::u16string>)> out,
SoftwareKeyboardParameters parameters) const override;
void SendTextCheckDialog(std::u16string error_message,
std::function<void()> finished_check) const override;
};
} // namespace Core::Frontend

View File

@@ -132,4 +132,11 @@ using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>
*/
using TouchDevice = InputDevice<std::tuple<float, float, bool>>;
/**
* 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

@@ -12,12 +12,23 @@
#include "core/hle/kernel/thread.h"
namespace Kernel {
namespace {
constexpr u16 GetSlot(Handle handle) {
return handle >> 15;
}
constexpr u16 GetGeneration(Handle handle) {
return handle & 0x7FFF;
}
} // Anonymous namespace
HandleTable::HandleTable() {
next_generation = 1;
Clear();
}
HandleTable::~HandleTable() = default;
ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
DEBUG_ASSERT(obj != nullptr);

View File

@@ -43,6 +43,7 @@ enum KernelHandle : Handle {
class HandleTable final : NonCopyable {
public:
HandleTable();
~HandleTable();
/**
* Allocates a handle for the given object.
@@ -89,18 +90,8 @@ public:
void Clear();
private:
/**
* This is the maximum limit of handles allowed per process in CTR-OS. It can be further
* reduced by ExHeader values, but this is not emulated here.
*/
static const std::size_t MAX_COUNT = 4096;
static u16 GetSlot(Handle handle) {
return handle >> 15;
}
static u16 GetGeneration(Handle handle) {
return handle & 0x7FFF;
}
/// This is the maximum limit of handles allowed per process in Horizon
static constexpr std::size_t MAX_COUNT = 1024;
/// Stores the Object referenced by the handle or null if the slot is empty.
std::array<SharedPtr<Object>, MAX_COUNT> objects;

View File

@@ -105,7 +105,7 @@ struct KernelCore::Impl {
void Initialize(KernelCore& kernel) {
Shutdown();
InitializeResourceLimits(kernel);
InitializeSystemResourceLimit(kernel);
InitializeThreads();
InitializeTimers();
}
@@ -118,7 +118,7 @@ struct KernelCore::Impl {
process_list.clear();
current_process = nullptr;
resource_limits.fill(nullptr);
system_resource_limit = nullptr;
thread_wakeup_callback_handle_table.Clear();
thread_wakeup_event_type = nullptr;
@@ -129,63 +129,17 @@ struct KernelCore::Impl {
named_ports.clear();
}
void InitializeResourceLimits(KernelCore& kernel) {
// Create the four resource limits that the system uses
// Create the APPLICATION resource limit
SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create(kernel, "Applications");
resource_limit->max_priority = 0x18;
resource_limit->max_commit = 0x4000000;
resource_limit->max_threads = 0x20;
resource_limit->max_events = 0x20;
resource_limit->max_mutexes = 0x20;
resource_limit->max_semaphores = 0x8;
resource_limit->max_timers = 0x8;
resource_limit->max_shared_mems = 0x10;
resource_limit->max_address_arbiters = 0x2;
resource_limit->max_cpu_time = 0x1E;
resource_limits[static_cast<u8>(ResourceLimitCategory::APPLICATION)] = resource_limit;
// Creates the default system resource limit
void InitializeSystemResourceLimit(KernelCore& kernel) {
system_resource_limit = ResourceLimit::Create(kernel, "System");
// Create the SYS_APPLET resource limit
resource_limit = ResourceLimit::Create(kernel, "System Applets");
resource_limit->max_priority = 0x4;
resource_limit->max_commit = 0x5E00000;
resource_limit->max_threads = 0x1D;
resource_limit->max_events = 0xB;
resource_limit->max_mutexes = 0x8;
resource_limit->max_semaphores = 0x4;
resource_limit->max_timers = 0x4;
resource_limit->max_shared_mems = 0x8;
resource_limit->max_address_arbiters = 0x3;
resource_limit->max_cpu_time = 0x2710;
resource_limits[static_cast<u8>(ResourceLimitCategory::SYS_APPLET)] = resource_limit;
// Create the LIB_APPLET resource limit
resource_limit = ResourceLimit::Create(kernel, "Library Applets");
resource_limit->max_priority = 0x4;
resource_limit->max_commit = 0x600000;
resource_limit->max_threads = 0xE;
resource_limit->max_events = 0x8;
resource_limit->max_mutexes = 0x8;
resource_limit->max_semaphores = 0x4;
resource_limit->max_timers = 0x4;
resource_limit->max_shared_mems = 0x8;
resource_limit->max_address_arbiters = 0x1;
resource_limit->max_cpu_time = 0x2710;
resource_limits[static_cast<u8>(ResourceLimitCategory::LIB_APPLET)] = resource_limit;
// Create the OTHER resource limit
resource_limit = ResourceLimit::Create(kernel, "Others");
resource_limit->max_priority = 0x4;
resource_limit->max_commit = 0x2180000;
resource_limit->max_threads = 0xE1;
resource_limit->max_events = 0x108;
resource_limit->max_mutexes = 0x25;
resource_limit->max_semaphores = 0x43;
resource_limit->max_timers = 0x2C;
resource_limit->max_shared_mems = 0x1F;
resource_limit->max_address_arbiters = 0x2D;
resource_limit->max_cpu_time = 0x3E8;
resource_limits[static_cast<u8>(ResourceLimitCategory::OTHER)] = resource_limit;
// If setting the default system values fails, then something seriously wrong has occurred.
ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x200000000)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Threads, 800).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Events, 700).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::TransferMemory, 200).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess());
}
void InitializeThreads() {
@@ -208,7 +162,7 @@ struct KernelCore::Impl {
std::vector<SharedPtr<Process>> process_list;
Process* current_process = nullptr;
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
SharedPtr<ResourceLimit> system_resource_limit;
/// The event type of the generic timer callback event
CoreTiming::EventType* timer_callback_event_type = nullptr;
@@ -239,9 +193,8 @@ void KernelCore::Shutdown() {
impl->Shutdown();
}
SharedPtr<ResourceLimit> KernelCore::ResourceLimitForCategory(
ResourceLimitCategory category) const {
return impl->resource_limits.at(static_cast<std::size_t>(category));
SharedPtr<ResourceLimit> KernelCore::GetSystemResourceLimit() const {
return impl->system_resource_limit;
}
SharedPtr<Thread> KernelCore::RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const {

View File

@@ -24,8 +24,6 @@ class ResourceLimit;
class Thread;
class Timer;
enum class ResourceLimitCategory : u8;
/// Represents a single instance of the kernel.
class KernelCore {
private:
@@ -47,8 +45,8 @@ public:
/// Clears all resources in use by the kernel instance.
void Shutdown();
/// Retrieves a shared pointer to a ResourceLimit identified by the given category.
SharedPtr<ResourceLimit> ResourceLimitForCategory(ResourceLimitCategory category) const;
/// Retrieves a shared pointer to the system resource limit instance.
SharedPtr<ResourceLimit> GetSystemResourceLimit() const;
/// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table.
SharedPtr<Thread> RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const;

View File

@@ -4,6 +4,7 @@
#include <algorithm>
#include <memory>
#include <random>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
@@ -28,7 +29,7 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
process->name = std::move(name);
process->flags.raw = 0;
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
process->resource_limit = kernel.ResourceLimitForCategory(ResourceLimitCategory::APPLICATION);
process->resource_limit = kernel.GetSystemResourceLimit();
process->status = ProcessStatus::Created;
process->program_id = 0;
process->process_id = kernel.CreateNewProcessID();

View File

@@ -8,7 +8,6 @@
#include <bitset>
#include <cstddef>
#include <memory>
#include <random>
#include <string>
#include <vector>
#include <boost/container/static_vector.hpp>

View File

@@ -2,12 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/result.h"
namespace Kernel {
namespace {
constexpr std::size_t ResourceTypeToIndex(ResourceType type) {
return static_cast<std::size_t>(type);
}
} // Anonymous namespace
ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {}
ResourceLimit::~ResourceLimit() = default;
@@ -19,59 +23,22 @@ SharedPtr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel, std::string n
return resource_limit;
}
s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
switch (resource) {
case ResourceType::Commit:
return current_commit;
case ResourceType::Thread:
return current_threads;
case ResourceType::Event:
return current_events;
case ResourceType::Mutex:
return current_mutexes;
case ResourceType::Semaphore:
return current_semaphores;
case ResourceType::Timer:
return current_timers;
case ResourceType::SharedMemory:
return current_shared_mems;
case ResourceType::AddressArbiter:
return current_address_arbiters;
case ResourceType::CPUTime:
return current_cpu_time;
default:
LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource));
UNIMPLEMENTED();
return 0;
}
s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
return values.at(ResourceTypeToIndex(resource));
}
u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
switch (resource) {
case ResourceType::Priority:
return max_priority;
case ResourceType::Commit:
return max_commit;
case ResourceType::Thread:
return max_threads;
case ResourceType::Event:
return max_events;
case ResourceType::Mutex:
return max_mutexes;
case ResourceType::Semaphore:
return max_semaphores;
case ResourceType::Timer:
return max_timers;
case ResourceType::SharedMemory:
return max_shared_mems;
case ResourceType::AddressArbiter:
return max_address_arbiters;
case ResourceType::CPUTime:
return max_cpu_time;
default:
LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource));
UNIMPLEMENTED();
return 0;
s64 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
return limits.at(ResourceTypeToIndex(resource));
}
ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) {
const auto index = ResourceTypeToIndex(resource);
if (value < values[index]) {
return ERR_INVALID_STATE;
}
values[index] = value;
return RESULT_SUCCESS;
}
} // namespace Kernel

View File

@@ -4,31 +4,25 @@
#pragma once
#include <array>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
union ResultCode;
namespace Kernel {
class KernelCore;
enum class ResourceLimitCategory : u8 {
APPLICATION = 0,
SYS_APPLET = 1,
LIB_APPLET = 2,
OTHER = 3
};
enum class ResourceType {
Priority = 0,
Commit = 1,
Thread = 2,
Event = 3,
Mutex = 4,
Semaphore = 5,
Timer = 6,
SharedMemory = 7,
AddressArbiter = 8,
CPUTime = 9,
PhysicalMemory,
Threads,
Events,
TransferMemory,
Sessions,
// Used as a count, not an actual type.
ResourceTypeCount
};
class ResourceLimit final : public Object {
@@ -55,61 +49,51 @@ public:
* @param resource Requested resource type
* @returns The current value of the resource type
*/
s32 GetCurrentResourceValue(ResourceType resource) const;
s64 GetCurrentResourceValue(ResourceType resource) const;
/**
* Gets the max value for the specified resource.
* @param resource Requested resource type
* @returns The max value of the resource type
*/
u32 GetMaxResourceValue(ResourceType resource) const;
s64 GetMaxResourceValue(ResourceType resource) const;
/// Name of resource limit object.
std::string name;
/// Max thread priority that a process in this category can create
s32 max_priority = 0;
/// Max memory that processes in this category can use
s32 max_commit = 0;
///< Max number of objects that can be collectively created by the processes in this category
s32 max_threads = 0;
s32 max_events = 0;
s32 max_mutexes = 0;
s32 max_semaphores = 0;
s32 max_timers = 0;
s32 max_shared_mems = 0;
s32 max_address_arbiters = 0;
/// Max CPU time that the processes in this category can utilize
s32 max_cpu_time = 0;
// TODO(Subv): Increment these in their respective Kernel::T::Create functions, keeping in mind
// that APPLICATION resource limits should not be affected by the objects created by service
// modules.
// Currently we have no way of distinguishing if a Create was called by the running application,
// or by a service module. Approach this once we have separated the service modules into their
// own processes
/// Current memory that the processes in this category are using
s32 current_commit = 0;
///< Current number of objects among all processes in this category
s32 current_threads = 0;
s32 current_events = 0;
s32 current_mutexes = 0;
s32 current_semaphores = 0;
s32 current_timers = 0;
s32 current_shared_mems = 0;
s32 current_address_arbiters = 0;
/// Current CPU time that the processes in this category are utilizing
s32 current_cpu_time = 0;
/**
* Sets the limit value for a given resource type.
*
* @param resource The resource type to apply the limit to.
* @param value The limit to apply to the given resource type.
*
* @return A result code indicating if setting the limit value
* was successful or not.
*
* @note The supplied limit value *must* be greater than or equal to
* the current resource value for the given resource type,
* otherwise ERR_INVALID_STATE will be returned.
*/
ResultCode SetLimitValue(ResourceType resource, s64 value);
private:
explicit ResourceLimit(KernelCore& kernel);
~ResourceLimit() override;
// TODO(Subv): Increment resource limit current values in their respective Kernel::T::Create
// functions
//
// Currently we have no way of distinguishing if a Create was called by the running application,
// or by a service module. Approach this once we have separated the service modules into their
// own processes
using ResourceArray =
std::array<s64, static_cast<std::size_t>(ResourceType::ResourceTypeCount)>;
/// Maximum values a resource type may reach.
ResourceArray limits{};
/// Current resource limit values.
ResourceArray values{};
/// Name of resource limit object.
std::string name;
};
} // namespace Kernel

View File

@@ -9,7 +9,6 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@@ -180,69 +179,4 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
ready_queue.prepare(priority);
}
Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
std::lock_guard<std::mutex> lock(scheduler_mutex);
const u32 mask = 1U << core;
return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
});
}
void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
ASSERT(thread != nullptr);
// Avoid yielding if the thread isn't even running.
ASSERT(thread->GetStatus() == ThreadStatus::Running);
// Sanity check that the priority is valid
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
// Yield this thread -- sleep for zero time and force reschedule to different thread
WaitCurrentThread_Sleep();
GetCurrentThread()->WakeAfterDelay(0);
}
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
ASSERT(thread != nullptr);
const auto priority = thread->GetPriority();
const auto core = static_cast<u32>(thread->GetProcessorID());
// Avoid yielding if the thread isn't even running.
ASSERT(thread->GetStatus() == ThreadStatus::Running);
// Sanity check that the priority is valid
ASSERT(priority < THREADPRIO_COUNT);
// Sleep for zero time to be able to force reschedule to different thread
WaitCurrentThread_Sleep();
GetCurrentThread()->WakeAfterDelay(0);
Thread* suggested_thread = nullptr;
// Search through all of the cpu cores (except this one) for a suggested thread.
// Take the first non-nullptr one
for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
const auto res =
Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
core, priority);
// If scheduler provides a suggested thread
if (res != nullptr) {
// And its better than the current suggested thread (or is the first valid one)
if (suggested_thread == nullptr ||
suggested_thread->GetPriority() > res->GetPriority()) {
suggested_thread = res;
}
}
}
// If a suggested thread was found, queue that for this core
if (suggested_thread != nullptr)
suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask());
}
void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) {
UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!");
}
} // namespace Kernel

View File

@@ -51,75 +51,6 @@ public:
/// Sets the priority of a thread in the scheduler
void SetThreadPriority(Thread* thread, u32 priority);
/// Gets the next suggested thread for load balancing
Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const;
/**
* YieldWithoutLoadBalancing -- analogous to normal yield on a system
* Moves the thread to the end of the ready queue for its priority, and then reschedules the
* system to the new head of the queue.
*
* Example (Single Core -- but can be extrapolated to multi):
* ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->)
* Currently Running: ThreadR
*
* ThreadR calls YieldWithoutLoadBalancing
*
* ThreadR is moved to the end of ready_queue[prio=0]:
* ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->)
* Currently Running: Nothing
*
* System is rescheduled (ThreadA is popped off of queue):
* ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->)
* Currently Running: ThreadA
*
* If the queue is empty at time of call, no yielding occurs. This does not cross between cores
* or priorities at all.
*/
void YieldWithoutLoadBalancing(Thread* thread);
/**
* YieldWithLoadBalancing -- yield but with better selection of the new running thread
* Moves the current thread to the end of the ready queue for its priority, then selects a
* 'suggested thread' (a thread on a different core that could run on this core) from the
* scheduler, changes its core, and reschedules the current core to that thread.
*
* Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were
* single core):
* ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant
* ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
* Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
*
* ThreadQ calls YieldWithLoadBalancing
*
* ThreadQ is moved to the end of ready_queue[core=0][prio=0]:
* ready_queue[core=0][prio=0]: ThreadA, ThreadB
* ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
* Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
*
* A list of suggested threads for each core is compiled
* Suggested Threads: {ThreadC on Core 1}
* If this were quad core (as the switch is), there could be between 0 and 3 threads in this
* list. If there are more than one, the thread is selected by highest prio.
*
* ThreadC is core changed to Core 0:
* ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ
* ready_queue[core=1][prio=0]: ThreadD
* Currently Running: None on Core 0 || ThreadP on Core 1
*
* System is rescheduled (ThreadC is popped off of queue):
* ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ
* ready_queue[core=1][prio=0]: ThreadD
* Currently Running: ThreadC on Core 0 || ThreadP on Core 1
*
* If no suggested threads can be found this will behave just as normal yield. If there are
* multiple candidates for the suggested thread on a core, the highest prio is taken.
*/
void YieldWithLoadBalancing(Thread* thread);
/// Currently unknown -- asserts as unimplemented on call
void YieldAndWaitForLoadBalancing(Thread* thread);
/// Returns a list of all threads managed by the scheduler
const std::vector<SharedPtr<Thread>>& GetThreadList() const {
return thread_list;

View File

@@ -61,7 +61,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce
}
SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size,
KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, std::size_t offset, u64 size,
MemoryPermission permissions, MemoryPermission other_permissions, std::string name) {
SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel));
@@ -78,10 +78,10 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
return shared_memory;
}
ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions,
ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions,
MemoryPermission other_permissions) {
const MemoryPermission own_other_permissions =
target_process == owner_process ? this->permissions : this->other_permissions;
&target_process == owner_process ? this->permissions : this->other_permissions;
// Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
@@ -106,7 +106,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
VAddr target_address = address;
// Map the memory block into the target process
auto result = target_process->VMManager().MapMemoryBlock(
auto result = target_process.VMManager().MapMemoryBlock(
target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
if (result.Failed()) {
LOG_ERROR(
@@ -116,14 +116,14 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
return result.Code();
}
return target_process->VMManager().ReprotectRange(target_address, size,
ConvertPermissions(permissions));
return target_process.VMManager().ReprotectRange(target_address, size,
ConvertPermissions(permissions));
}
ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) {
ResultCode SharedMemory::Unmap(Process& target_process, VAddr address) {
// TODO(Subv): Verify what happens if the application tries to unmap an address that is not
// mapped to a SharedMemory.
return target_process->VMManager().UnmapRange(address, size);
return target_process.VMManager().UnmapRange(address, size);
}
VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
@@ -132,7 +132,11 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
return static_cast<VMAPermission>(masked_permissions);
}
u8* SharedMemory::GetPointer(u32 offset) {
u8* SharedMemory::GetPointer(std::size_t offset) {
return backing_block->data() + backing_block_offset + offset;
}
const u8* SharedMemory::GetPointer(std::size_t offset) const {
return backing_block->data() + backing_block_offset + offset;
}

View File

@@ -64,7 +64,7 @@ public:
*/
static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel,
std::shared_ptr<std::vector<u8>> heap_block,
u32 offset, u32 size,
std::size_t offset, u64 size,
MemoryPermission permissions,
MemoryPermission other_permissions,
std::string name = "Unknown Applet");
@@ -81,6 +81,11 @@ public:
return HANDLE_TYPE;
}
/// Gets the size of the underlying memory block in bytes.
u64 GetSize() const {
return size;
}
/**
* Converts the specified MemoryPermission into the equivalent VMAPermission.
* @param permission The MemoryPermission to convert.
@@ -94,44 +99,51 @@ public:
* @param permissions Memory block map permissions (specified by SVC field)
* @param other_permissions Memory block map other permissions (specified by SVC field)
*/
ResultCode Map(Process* target_process, VAddr address, MemoryPermission permissions,
ResultCode Map(Process& target_process, VAddr address, MemoryPermission permissions,
MemoryPermission other_permissions);
/**
* Unmaps a shared memory block from the specified address in system memory
* @param target_process Process from which to umap the memory block.
* @param target_process Process from which to unmap the memory block.
* @param address Address in system memory where the shared memory block is mapped
* @return Result code of the unmap operation
*/
ResultCode Unmap(Process* target_process, VAddr address);
ResultCode Unmap(Process& target_process, VAddr address);
/**
* Gets a pointer to the shared memory block
* @param offset Offset from the start of the shared memory block to get pointer
* @return Pointer to the shared memory block from the specified offset
* @return A pointer to the shared memory block from the specified offset
*/
u8* GetPointer(u32 offset = 0);
u8* GetPointer(std::size_t offset = 0);
/// Process that created this shared memory block.
SharedPtr<Process> owner_process;
/// Address of shared memory block in the owner process if specified.
VAddr base_address;
/// Backing memory for this shared memory block.
std::shared_ptr<std::vector<u8>> backing_block;
/// Offset into the backing block for this shared memory.
std::size_t backing_block_offset;
/// Size of the memory block. Page-aligned.
u64 size;
/// Permission restrictions applied to the process which created the block.
MemoryPermission permissions;
/// Permission restrictions applied to other processes mapping the block.
MemoryPermission other_permissions;
/// Name of shared memory object.
std::string name;
/**
* Gets a constant pointer to the shared memory block
* @param offset Offset from the start of the shared memory block to get pointer
* @return A constant pointer to the shared memory block from the specified offset
*/
const u8* GetPointer(std::size_t offset = 0) const;
private:
explicit SharedMemory(KernelCore& kernel);
~SharedMemory() override;
/// Backing memory for this shared memory block.
std::shared_ptr<std::vector<u8>> backing_block;
/// Offset into the backing block for this shared memory.
std::size_t backing_block_offset = 0;
/// Size of the memory block. Page-aligned.
u64 size = 0;
/// Permission restrictions applied to the process which created the block.
MemoryPermission permissions{};
/// Permission restrictions applied to other processes mapping the block.
MemoryPermission other_permissions{};
/// Process that created this shared memory block.
SharedPtr<Process> owner_process;
/// Address of shared memory block in the owner process if specified.
VAddr base_address = 0;
/// Name of shared memory object.
std::string name;
};
} // namespace Kernel

View File

@@ -736,13 +736,6 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
const auto* const current_process = Core::CurrentProcess();
// Note: The kernel uses the current process's resource limit instead of
// the one from the thread owner's resource limit.
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
return ERR_INVALID_THREAD_PRIORITY;
}
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
return ERR_INVALID_HANDLE;
@@ -796,7 +789,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
return ERR_INVALID_MEMORY_RANGE;
}
return shared_memory->Map(current_process, addr, permissions_type, MemoryPermission::DontCare);
return shared_memory->Map(*current_process, addr, permissions_type, MemoryPermission::DontCare);
}
static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
@@ -826,7 +819,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
return ERR_INVALID_MEMORY_RANGE;
}
return shared_memory->Unmap(current_process, addr);
return shared_memory->Unmap(*current_process, addr);
}
/// Query process memory
@@ -885,10 +878,6 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
}
auto* const current_process = Core::CurrentProcess();
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
return ERR_INVALID_THREAD_PRIORITY;
}
if (processor_id == THREADPROCESSORID_DEFAULT) {
// Set the target CPU to the one specified in the process' exheader.
@@ -960,38 +949,18 @@ static void ExitThread() {
static void SleepThread(s64 nanoseconds) {
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
enum class SleepType : s64 {
YieldWithoutLoadBalancing = 0,
YieldWithLoadBalancing = -1,
YieldAndWaitForLoadBalancing = -2,
};
// Don't attempt to yield execution if there are no available threads to run,
// this way we avoid a useless reschedule to the idle thread.
if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads())
return;
if (nanoseconds <= 0) {
auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
switch (static_cast<SleepType>(nanoseconds)) {
case SleepType::YieldWithoutLoadBalancing:
scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
break;
case SleepType::YieldWithLoadBalancing:
scheduler.YieldWithLoadBalancing(GetCurrentThread());
break;
case SleepType::YieldAndWaitForLoadBalancing:
scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
break;
default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
} else {
// Sleep current thread and check for next thread to schedule
WaitCurrentThread_Sleep();
// Sleep current thread and check for next thread to schedule
WaitCurrentThread_Sleep();
// Create an event to wake the thread up after the specified nanosecond delay has passed
GetCurrentThread()->WakeAfterDelay(nanoseconds);
}
// Create an event to wake the thread up after the specified nanosecond delay has passed
GetCurrentThread()->WakeAfterDelay(nanoseconds);
// Reschedule all CPU cores
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
Core::System::GetInstance().CpuCore(i).PrepareReschedule();
Core::System::GetInstance().PrepareReschedule();
}
/// Wait process wide key atomic
@@ -1214,9 +1183,39 @@ static ResultCode ResetSignal(Handle handle) {
/// Creates a TransferMemory object
static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) {
LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size,
permissions);
*handle = 0;
LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size,
permissions);
if (!Common::Is4KBAligned(addr)) {
LOG_ERROR(Kernel_SVC, "Address ({:016X}) is not page aligned!", addr);
return ERR_INVALID_ADDRESS;
}
if (!Common::Is4KBAligned(size) || size == 0) {
LOG_ERROR(Kernel_SVC, "Size ({:016X}) is not page aligned or equal to zero!", size);
return ERR_INVALID_ADDRESS;
}
if (!IsValidAddressRange(addr, size)) {
LOG_ERROR(Kernel_SVC, "Address and size cause overflow! (address={:016X}, size={:016X})",
addr, size);
return ERR_INVALID_ADDRESS_STATE;
}
const auto perms = static_cast<MemoryPermission>(permissions);
if (perms != MemoryPermission::None && perms != MemoryPermission::Read &&
perms != MemoryPermission::ReadWrite) {
LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})",
permissions);
return ERR_INVALID_MEMORY_PERMISSIONS;
}
auto& kernel = Core::System::GetInstance().Kernel();
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
const auto shared_mem_handle = SharedMemory::Create(
kernel, handle_table.Get<Process>(CurrentProcess), size, perms, perms, addr);
CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
return RESULT_SUCCESS;
}

View File

@@ -26,7 +26,6 @@ enum ThreadPriority : u32 {
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
};
enum ThreadProcessorId : s32 {

View File

@@ -6,10 +6,14 @@
#include <cinttypes>
#include <cstring>
#include <stack>
#include "applets/applets.h"
#include "applets/software_keyboard.h"
#include "audio_core/audio_renderer.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
@@ -28,6 +32,13 @@
namespace Service::AM {
constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
enum class AppletId : u32 {
SoftwareKeyboard = 0x11,
};
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
struct LaunchParameters {
@@ -481,6 +492,24 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
LOG_DEBUG(Service_AM, "called");
}
IStorage::IStorage(std::vector<u8> buffer)
: ServiceFramework("IStorage"), buffer(std::move(buffer)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IStorage::Open, "Open"},
{1, nullptr, "OpenTransferStorage"},
};
// clang-format on
RegisterHandlers(functions);
}
IStorage::~IStorage() = default;
const std::vector<u8>& IStorage::GetData() const {
return buffer;
}
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
const bool use_docked_mode{Settings::values.use_docked_mode};
IPC::ResponseBuilder rb{ctx, 3};
@@ -500,100 +529,14 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
}
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
public:
explicit IStorageAccessor(std::vector<u8> buffer)
: ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IStorageAccessor::GetSize, "GetSize"},
{10, &IStorageAccessor::Write, "Write"},
{11, &IStorageAccessor::Read, "Read"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
std::vector<u8> buffer;
void GetSize(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u64>(buffer.size()));
LOG_DEBUG(Service_AM, "called");
}
void Write(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
const std::vector<u8> data{ctx.ReadBuffer()};
ASSERT(offset + data.size() <= buffer.size());
std::memcpy(&buffer[offset], data.data(), data.size());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_AM, "called, offset={}", offset);
}
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
const std::size_t size{ctx.GetWriteBufferSize()};
ASSERT(offset + size <= buffer.size());
ctx.WriteBuffer(buffer.data() + offset, size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_AM, "called, offset={}", offset);
}
};
class IStorage final : public ServiceFramework<IStorage> {
public:
explicit IStorage(std::vector<u8> buffer)
: ServiceFramework("IStorage"), buffer(std::move(buffer)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IStorage::Open, "Open"},
{1, nullptr, "OpenTransferStorage"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
std::vector<u8> buffer;
void Open(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<AM::IStorageAccessor>(buffer);
LOG_DEBUG(Service_AM, "called");
}
};
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
public:
explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
explicit ILibraryAppletAccessor(std::shared_ptr<Applets::Applet> applet)
: ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
{1, nullptr, "IsCompleted"},
{1, &ILibraryAppletAccessor::IsCompleted, "IsCompleted"},
{10, &ILibraryAppletAccessor::Start, "Start"},
{20, nullptr, "RequestExit"},
{25, nullptr, "Terminate"},
@@ -602,10 +545,10 @@ public:
{100, &ILibraryAppletAccessor::PushInData, "PushInData"},
{101, &ILibraryAppletAccessor::PopOutData, "PopOutData"},
{102, nullptr, "PushExtraStorage"},
{103, nullptr, "PushInteractiveInData"},
{104, nullptr, "PopInteractiveOutData"},
{105, nullptr, "GetPopOutDataEvent"},
{106, nullptr, "GetPopInteractiveOutDataEvent"},
{103, &ILibraryAppletAccessor::PushInteractiveInData, "PushInteractiveInData"},
{104, &ILibraryAppletAccessor::PopInteractiveOutData, "PopInteractiveOutData"},
{105, &ILibraryAppletAccessor::GetPopOutDataEvent, "GetPopOutDataEvent"},
{106, &ILibraryAppletAccessor::GetPopInteractiveOutDataEvent, "GetPopInteractiveOutDataEvent"},
{110, nullptr, "NeedsToExitProcess"},
{120, nullptr, "GetLibraryAppletInfo"},
{150, nullptr, "RequestForAppletToGetForeground"},
@@ -614,40 +557,50 @@ public:
// clang-format on
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
"ILibraryAppletAccessor:StateChangedEvent");
}
private:
void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) {
state_changed_event->Signal();
const auto event = applet->GetBroker().GetStateChangedEvent();
event->Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(state_changed_event);
rb.PushCopyObjects(event);
LOG_WARNING(Service_AM, "(STUBBED) called");
LOG_DEBUG(Service_AM, "called");
}
void IsCompleted(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(applet->TransactionComplete());
LOG_DEBUG(Service_AM, "called");
}
void GetResult(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
rb.Push(applet->GetStatus());
LOG_WARNING(Service_AM, "(STUBBED) called");
LOG_DEBUG(Service_AM, "called");
}
void Start(Kernel::HLERequestContext& ctx) {
ASSERT(applet != nullptr);
applet->Initialize();
applet->Execute();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_AM, "(STUBBED) called");
LOG_DEBUG(Service_AM, "called");
}
void PushInData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
storage_stack.push(rp.PopIpcInterface<AM::IStorage>());
applet->GetBroker().PushNormalDataFromGame(*rp.PopIpcInterface<IStorage>());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -657,25 +610,145 @@ private:
void PopOutData(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top()));
storage_stack.pop();
const auto storage = applet->GetBroker().PopNormalDataToGame();
if (storage == nullptr) {
rb.Push(ERR_NO_DATA_IN_CHANNEL);
return;
}
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorage>(std::move(*storage));
LOG_DEBUG(Service_AM, "called");
}
std::stack<std::shared_ptr<AM::IStorage>> storage_stack;
Kernel::SharedPtr<Kernel::Event> state_changed_event;
void PushInteractiveInData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
applet->GetBroker().PushInteractiveDataFromGame(*rp.PopIpcInterface<IStorage>());
ASSERT(applet->IsInitialized());
applet->ExecuteInteractive();
applet->Execute();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_AM, "called");
}
void PopInteractiveOutData(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
const auto storage = applet->GetBroker().PopInteractiveDataToGame();
if (storage == nullptr) {
rb.Push(ERR_NO_DATA_IN_CHANNEL);
return;
}
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorage>(std::move(*storage));
LOG_DEBUG(Service_AM, "called");
}
void GetPopOutDataEvent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(applet->GetBroker().GetNormalDataEvent());
LOG_DEBUG(Service_AM, "called");
}
void GetPopInteractiveOutDataEvent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent());
LOG_DEBUG(Service_AM, "called");
}
std::shared_ptr<Applets::Applet> applet;
};
void IStorage::Open(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorageAccessor>(*this);
LOG_DEBUG(Service_AM, "called");
}
IStorageAccessor::IStorageAccessor(IStorage& storage)
: ServiceFramework("IStorageAccessor"), backing(storage) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IStorageAccessor::GetSize, "GetSize"},
{10, &IStorageAccessor::Write, "Write"},
{11, &IStorageAccessor::Read, "Read"},
};
// clang-format on
RegisterHandlers(functions);
}
IStorageAccessor::~IStorageAccessor() = default;
void IStorageAccessor::GetSize(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u64>(backing.buffer.size()));
LOG_DEBUG(Service_AM, "called");
}
void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
const std::vector<u8> data{ctx.ReadBuffer()};
if (data.size() > backing.buffer.size() - offset) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
}
std::memcpy(backing.buffer.data() + offset, data.data(), data.size());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_AM, "called, offset={}", offset);
}
void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
const std::size_t size{ctx.GetWriteBufferSize()};
if (size > backing.buffer.size() - offset) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
}
ctx.WriteBuffer(backing.buffer.data() + offset, size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_AM, "called, offset={}", offset);
}
ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
static const FunctionInfo functions[] = {
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"},
{2, nullptr, "AreAnyLibraryAppletsLeft"},
{10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"},
{11, nullptr, "CreateTransferMemoryStorage"},
{11, &ILibraryAppletCreator::CreateTransferMemoryStorage, "CreateTransferMemoryStorage"},
{12, nullptr, "CreateHandleStorage"},
};
RegisterHandlers(functions);
@@ -683,11 +756,36 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple
ILibraryAppletCreator::~ILibraryAppletCreator() = default;
static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
switch (id) {
case AppletId::SoftwareKeyboard:
return std::make_shared<Applets::SoftwareKeyboard>();
default:
UNREACHABLE_MSG("Unimplemented AppletId [{:08X}]!", static_cast<u32>(id));
return nullptr;
}
}
void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_id = rp.PopRaw<AppletId>();
const auto applet_mode = rp.PopRaw<u32>();
LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}",
static_cast<u32>(applet_id), applet_mode);
const auto applet = GetAppletFromId(applet_id);
if (applet == nullptr) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1));
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<AM::ILibraryAppletAccessor>();
rb.PushIpcInterface<AM::ILibraryAppletAccessor>(applet);
LOG_DEBUG(Service_AM, "called");
}
@@ -704,6 +802,31 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called, size={}", size);
}
void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
rp.SetCurrentOffset(3);
const auto handle{rp.Pop<Kernel::Handle>()};
const auto shared_mem =
Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::SharedMemory>(
handle);
if (shared_mem == nullptr) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1));
return;
}
const u8* mem_begin = shared_mem->GetPointer();
const u8* mem_end = mem_begin + shared_mem->GetSize();
std::vector<u8> memory{mem_begin, mem_end};
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory)));
}
IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
// clang-format off
static const FunctionInfo functions[] = {

View File

@@ -155,6 +155,34 @@ private:
std::shared_ptr<AppletMessageQueue> msg_queue;
};
class IStorage final : public ServiceFramework<IStorage> {
public:
explicit IStorage(std::vector<u8> buffer);
~IStorage() override;
const std::vector<u8>& GetData() const;
private:
void Open(Kernel::HLERequestContext& ctx);
std::vector<u8> buffer;
friend class IStorageAccessor;
};
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
public:
explicit IStorageAccessor(IStorage& backing);
~IStorageAccessor() override;
private:
void GetSize(Kernel::HLERequestContext& ctx);
void Write(Kernel::HLERequestContext& ctx);
void Read(Kernel::HLERequestContext& ctx);
IStorage& backing;
};
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
public:
ILibraryAppletCreator();
@@ -163,6 +191,7 @@ public:
private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
void CreateStorage(Kernel::HLERequestContext& ctx);
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {

View File

@@ -0,0 +1,113 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
AppletDataBroker::AppletDataBroker() {
auto& kernel = Core::System::GetInstance().Kernel();
state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
"ILibraryAppletAccessor:StateChangedEvent");
pop_out_data_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
"ILibraryAppletAccessor:PopDataOutEvent");
pop_interactive_out_data_event = Kernel::Event::Create(
kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
}
AppletDataBroker::~AppletDataBroker() = default;
std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
if (out_channel.empty())
return nullptr;
auto out = std::move(out_channel.front());
out_channel.pop();
return out;
}
std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
if (in_channel.empty())
return nullptr;
auto out = std::move(in_channel.front());
in_channel.pop();
return out;
}
std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
if (out_interactive_channel.empty())
return nullptr;
auto out = std::move(out_interactive_channel.front());
out_interactive_channel.pop();
return out;
}
std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
if (in_interactive_channel.empty())
return nullptr;
auto out = std::move(in_interactive_channel.front());
in_interactive_channel.pop();
return out;
}
void AppletDataBroker::PushNormalDataFromGame(IStorage storage) {
in_channel.push(std::make_unique<IStorage>(storage));
}
void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) {
out_channel.push(std::make_unique<IStorage>(storage));
pop_out_data_event->Signal();
}
void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) {
in_interactive_channel.push(std::make_unique<IStorage>(storage));
}
void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) {
out_interactive_channel.push(std::make_unique<IStorage>(storage));
pop_interactive_out_data_event->Signal();
}
void AppletDataBroker::SignalStateChanged() const {
state_changed_event->Signal();
}
Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetNormalDataEvent() const {
return pop_out_data_event;
}
Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetInteractiveDataEvent() const {
return pop_interactive_out_data_event;
}
Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetStateChangedEvent() const {
return state_changed_event;
}
Applet::Applet() = default;
Applet::~Applet() = default;
void Applet::Initialize() {
const auto common = broker.PopNormalDataToApplet();
ASSERT(common != nullptr);
const auto common_data = common->GetData();
ASSERT(common_data.size() >= sizeof(CommonArguments));
std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments));
initialized = true;
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,112 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <queue>
#include "common/swap.h"
#include "core/hle/kernel/kernel.h"
union ResultCode;
namespace Kernel {
class Event;
}
namespace Service::AM {
class IStorage;
namespace Applets {
class AppletDataBroker final {
public:
AppletDataBroker();
~AppletDataBroker();
std::unique_ptr<IStorage> PopNormalDataToGame();
std::unique_ptr<IStorage> PopNormalDataToApplet();
std::unique_ptr<IStorage> PopInteractiveDataToGame();
std::unique_ptr<IStorage> PopInteractiveDataToApplet();
void PushNormalDataFromGame(IStorage storage);
void PushNormalDataFromApplet(IStorage storage);
void PushInteractiveDataFromGame(IStorage storage);
void PushInteractiveDataFromApplet(IStorage storage);
void SignalStateChanged() const;
Kernel::SharedPtr<Kernel::Event> GetNormalDataEvent() const;
Kernel::SharedPtr<Kernel::Event> GetInteractiveDataEvent() const;
Kernel::SharedPtr<Kernel::Event> GetStateChangedEvent() const;
private:
// Queues are named from applet's perspective
// PopNormalDataToApplet and PushNormalDataFromGame
std::queue<std::unique_ptr<IStorage>> in_channel;
// PopNormalDataToGame and PushNormalDataFromApplet
std::queue<std::unique_ptr<IStorage>> out_channel;
// PopInteractiveDataToApplet and PushInteractiveDataFromGame
std::queue<std::unique_ptr<IStorage>> in_interactive_channel;
// PopInteractiveDataToGame and PushInteractiveDataFromApplet
std::queue<std::unique_ptr<IStorage>> out_interactive_channel;
Kernel::SharedPtr<Kernel::Event> state_changed_event;
// Signaled on PushNormalDataFromApplet
Kernel::SharedPtr<Kernel::Event> pop_out_data_event;
// Signaled on PushInteractiveDataFromApplet
Kernel::SharedPtr<Kernel::Event> pop_interactive_out_data_event;
};
class Applet {
public:
Applet();
virtual ~Applet();
virtual void Initialize();
virtual bool TransactionComplete() const = 0;
virtual ResultCode GetStatus() const = 0;
virtual void ExecuteInteractive() = 0;
virtual void Execute() = 0;
bool IsInitialized() const {
return initialized;
}
AppletDataBroker& GetBroker() {
return broker;
}
const AppletDataBroker& GetBroker() const {
return broker;
}
protected:
struct CommonArguments {
u32_le arguments_version;
u32_le size;
u32_le library_version;
u32_le theme_color;
u8 play_startup_sound;
u64_le system_tick;
};
static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
CommonArguments common_args{};
AppletDataBroker broker;
bool initialized = false;
};
} // namespace Applets
} // namespace Service::AM

View File

@@ -0,0 +1,161 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/software_keyboard.h"
namespace Service::AM::Applets {
constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8;
constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4;
constexpr std::size_t DEFAULT_MAX_LENGTH = 500;
constexpr bool INTERACTIVE_STATUS_OK = false;
static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
KeyboardConfig config, std::u16string initial_text) {
Core::Frontend::SoftwareKeyboardParameters params{};
params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
config.submit_text.data(), config.submit_text.size());
params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
config.header_text.data(), config.header_text.size());
params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(),
config.sub_text.size());
params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(),
config.guide_text.size());
params.initial_text = initial_text;
params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit;
params.password = static_cast<bool>(config.is_password);
params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position);
params.value = static_cast<u8>(config.keyset_disable_bitmask);
return params;
}
SoftwareKeyboard::SoftwareKeyboard() = default;
SoftwareKeyboard::~SoftwareKeyboard() = default;
void SoftwareKeyboard::Initialize() {
complete = false;
initial_text.clear();
final_data.clear();
Applet::Initialize();
const auto keyboard_config_storage = broker.PopNormalDataToApplet();
ASSERT(keyboard_config_storage != nullptr);
const auto& keyboard_config = keyboard_config_storage->GetData();
ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig));
std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig));
const auto work_buffer_storage = broker.PopNormalDataToApplet();
ASSERT(work_buffer_storage != nullptr);
const auto& work_buffer = work_buffer_storage->GetData();
if (config.initial_string_size == 0)
return;
std::vector<char16_t> string(config.initial_string_size);
std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset,
string.size() * 2);
initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size());
}
bool SoftwareKeyboard::TransactionComplete() const {
return complete;
}
ResultCode SoftwareKeyboard::GetStatus() const {
return RESULT_SUCCESS;
}
void SoftwareKeyboard::ExecuteInteractive() {
if (complete)
return;
const auto storage = broker.PopInteractiveDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
const auto status = static_cast<bool>(data[0]);
if (status == INTERACTIVE_STATUS_OK) {
complete = true;
} else {
const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()};
std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
std::memcpy(string.data(), data.data() + 4, string.size() * 2);
frontend.SendTextCheckDialog(
Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()),
[this] { broker.SignalStateChanged(); });
}
}
void SoftwareKeyboard::Execute() {
if (complete) {
broker.PushNormalDataFromApplet(IStorage{final_data});
return;
}
const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()};
const auto parameters = ConvertToFrontendParameters(config, initial_text);
frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); },
parameters);
}
void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
std::vector<u8> output_main(SWKBD_OUTPUT_BUFFER_SIZE);
if (text.has_value()) {
std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE);
if (config.utf_8) {
const u64 size = text->size() + 8;
const auto new_text = Common::UTF16ToUTF8(*text);
std::memcpy(output_sub.data(), &size, sizeof(u64));
std::memcpy(output_sub.data() + 8, new_text.data(),
std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8));
output_main[0] = INTERACTIVE_STATUS_OK;
std::memcpy(output_main.data() + 4, new_text.data(),
std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4));
} else {
const u64 size = text->size() * 2 + 8;
std::memcpy(output_sub.data(), &size, sizeof(u64));
std::memcpy(output_sub.data() + 8, text->data(),
std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8));
output_main[0] = INTERACTIVE_STATUS_OK;
std::memcpy(output_main.data() + 4, text->data(),
std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4));
}
complete = !config.text_check;
final_data = output_main;
if (complete) {
broker.PushNormalDataFromApplet(IStorage{output_main});
} else {
broker.PushInteractiveDataFromApplet(IStorage{output_sub});
}
broker.SignalStateChanged();
} else {
output_main[0] = 1;
complete = true;
broker.PushNormalDataFromApplet(IStorage{output_main});
broker.SignalStateChanged();
}
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,74 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <string>
#include <vector>
#include "common/common_funcs.h"
#include "common/swap.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
enum class KeysetDisable : u32 {
Space = 0x02,
Address = 0x04,
Percent = 0x08,
Slashes = 0x10,
Numbers = 0x40,
DownloadCode = 0x80,
};
struct KeyboardConfig {
INSERT_PADDING_BYTES(4);
std::array<char16_t, 9> submit_text;
u16_le left_symbol_key;
u16_le right_symbol_key;
INSERT_PADDING_BYTES(1);
KeysetDisable keyset_disable_bitmask;
u32_le initial_cursor_position;
std::array<char16_t, 65> header_text;
std::array<char16_t, 129> sub_text;
std::array<char16_t, 257> guide_text;
u32_le length_limit;
INSERT_PADDING_BYTES(4);
u32_le is_password;
INSERT_PADDING_BYTES(5);
bool utf_8;
bool draw_background;
u32_le initial_string_offset;
u32_le initial_string_size;
u32_le user_dictionary_offset;
u32_le user_dictionary_size;
bool text_check;
u64_le text_check_callback;
};
static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size.");
class SoftwareKeyboard final : public Applet {
public:
SoftwareKeyboard();
~SoftwareKeyboard() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void WriteText(std::optional<std::u16string> text);
private:
KeyboardConfig config;
std::u16string initial_text;
bool complete = false;
std::vector<u8> final_data;
};
} // namespace Service::AM::Applets

View File

@@ -6,9 +6,14 @@
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/debug_pad.h"
#include "core/settings.h"
namespace Service::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
Controller_DebugPad::Controller_DebugPad() = default;
Controller_DebugPad::~Controller_DebugPad() = default;
@@ -33,10 +38,43 @@ void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) {
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update debug pad states
cur_entry.attribute.connected.Assign(1);
auto& pad = cur_entry.pad_state;
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 [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);
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
}
void Controller_DebugPad::OnLoadInputDevices() {}
void Controller_DebugPad::OnLoadInputDevices() {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), 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

@@ -5,10 +5,13 @@
#pragma once
#include <array>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/settings.h"
namespace Service::HID {
class Controller_DebugPad final : public ControllerBase {
@@ -35,11 +38,40 @@ private:
};
static_assert(sizeof(AnalogStick) == 0x8);
struct PadState {
union {
u32_le raw{};
BitField<0, 1, u32_le> a;
BitField<1, 1, u32_le> b;
BitField<2, 1, u32_le> x;
BitField<3, 1, u32_le> y;
BitField<4, 1, u32_le> l;
BitField<5, 1, u32_le> r;
BitField<6, 1, u32_le> zl;
BitField<7, 1, u32_le> zr;
BitField<8, 1, u32_le> plus;
BitField<9, 1, u32_le> minus;
BitField<10, 1, u32_le> d_left;
BitField<11, 1, u32_le> d_up;
BitField<12, 1, u32_le> d_right;
BitField<13, 1, u32_le> d_down;
};
};
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
struct Attributes {
union {
u32_le raw{};
BitField<0, 1, u32_le> connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
struct PadStates {
s64_le sampling_number;
s64_le sampling_number2;
u32_le attribute;
u32_le button_state;
Attributes attribute;
PadState pad_state;
AnalogStick r_stick;
AnalogStick l_stick;
};
@@ -52,5 +84,10 @@ private:
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
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;
};
} // namespace Service::HID

View File

@@ -6,9 +6,11 @@
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/settings.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
constexpr u8 KEYS_PER_BYTE = 8;
Controller_Keyboard::Controller_Keyboard() = default;
Controller_Keyboard::~Controller_Keyboard() = default;
@@ -34,10 +36,24 @@ void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) {
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update keyboard states
for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
for (std::size_t k = 0; k < KEYS_PER_BYTE; ++k) {
cur_entry.key[i / KEYS_PER_BYTE] |= (keyboard_keys[i]->GetStatus() << k);
}
}
for (std::size_t i = 0; i < keyboard_mods.size(); ++i) {
cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i);
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
void Controller_Keyboard::OnLoadInputDevices() {}
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,7 +8,9 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/settings.h"
namespace Service::HID {
class Controller_Keyboard final : public ControllerBase {
@@ -46,5 +48,10 @@ private:
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardKeys>
keyboard_keys;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods>
keyboard_mods;
};
} // namespace Service::HID

View File

@@ -5,6 +5,7 @@
#include <cstring>
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/hle/service/hid/controllers/mouse.h"
namespace Service::HID {
@@ -14,7 +15,6 @@ Controller_Mouse::Controller_Mouse() = default;
Controller_Mouse::~Controller_Mouse() = default;
void Controller_Mouse::OnInit() {}
void Controller_Mouse::OnRelease() {}
void Controller_Mouse::OnUpdate(u8* data, std::size_t size) {
@@ -34,10 +34,29 @@ void Controller_Mouse::OnUpdate(u8* data, std::size_t size) {
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update mouse states
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;
for (std::size_t i = 0; i < mouse_button_devices.size(); ++i) {
cur_entry.button |= (mouse_button_devices[i]->GetStatus() << i);
}
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
void Controller_Mouse::OnLoadInputDevices() {}
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,7 +7,9 @@
#include <array>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/settings.h"
namespace Service::HID {
class Controller_Mouse final : public ControllerBase {
@@ -35,7 +37,8 @@ private:
s32_le y;
s32_le delta_x;
s32_le delta_y;
s32_le mouse_wheel;
s32_le mouse_wheel_x;
s32_le mouse_wheel_y;
s32_le button;
s32_le attribute;
};
@@ -46,5 +49,9 @@ private:
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;
};
} // namespace Service::HID

View File

@@ -17,22 +17,13 @@
#include "core/settings.h"
namespace Service::HID {
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
constexpr u32 NPAD_HANDHELD = 32;
constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this?
constexpr u32 MAX_NPAD_ID = 7;
constexpr Controller_NPad::NPadControllerType PREFERRED_CONTROLLER =
Controller_NPad::NPadControllerType::JoyDual;
constexpr std::array<u32, 10> npad_id_list{
0, 1, 2, 3, 4, 5, 6, 7, 32, 16,
0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN,
};
enum class JoystickId : std::size_t {
@@ -40,7 +31,23 @@ enum class JoystickId : std::size_t {
Joystick_Right,
};
static std::size_t NPadIdToIndex(u32 npad_id) {
static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type) {
switch (type) {
case Settings::ControllerType::ProController:
return Controller_NPad::NPadControllerType::ProController;
case Settings::ControllerType::DualJoycon:
return Controller_NPad::NPadControllerType::JoyDual;
case Settings::ControllerType::LeftJoycon:
return Controller_NPad::NPadControllerType::JoyLeft;
case Settings::ControllerType::RightJoycon:
return Controller_NPad::NPadControllerType::JoyRight;
default:
UNREACHABLE();
return Controller_NPad::NPadControllerType::JoyDual;
}
}
std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) {
switch (npad_id) {
case 0:
case 1:
@@ -63,6 +70,27 @@ static std::size_t NPadIdToIndex(u32 npad_id) {
}
}
u32 Controller_NPad::IndexToNPad(std::size_t index) {
switch (index) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
return static_cast<u32>(index);
case 8:
return NPAD_HANDHELD;
case 9:
return NPAD_UNKNOWN;
default:
UNIMPLEMENTED_MSG("Unknown npad index {}", index);
return 0;
};
}
Controller_NPad::Controller_NPad() = default;
Controller_NPad::~Controller_NPad() = default;
@@ -79,22 +107,32 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.joy_styles.handheld.Assign(1);
controller.device_type.handheld.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
break;
case NPadControllerType::JoyDual:
controller.joy_styles.joycon_dual.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.device_type.joycon_right.Assign(1);
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
break;
case NPadControllerType::JoyLeft:
controller.joy_styles.joycon_left.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
controller.properties.is_horizontal.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
break;
case NPadControllerType::JoyRight:
controller.joy_styles.joycon_right.Assign(1);
controller.device_type.joycon_right.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
controller.properties.is_horizontal.Assign(1);
controller.properties.use_plus.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
break;
case NPadControllerType::Pokeball:
controller.joy_styles.pokeball.Assign(1);
@@ -104,6 +142,9 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
case NPadControllerType::ProController:
controller.joy_styles.pro_controller.Assign(1);
controller.device_type.pro_controller.Assign(1);
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
break;
}
@@ -113,14 +154,12 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.single_color.button_color = 0;
controller.dual_color_error = ColorReadError::ReadOk;
controller.left_color.body_color = JOYCON_BODY_NEON_BLUE;
controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE;
controller.right_color.body_color = JOYCON_BODY_NEON_RED;
controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED;
controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left;
controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left;
controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right;
controller.right_color.button_color =
Settings::values.players[controller_idx].button_color_right;
controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.battery_level[0] = BATTERY_FULL;
controller.battery_level[1] = BATTERY_FULL;
controller.battery_level[2] = BATTERY_FULL;
@@ -144,26 +183,109 @@ void Controller_NPad::OnInit() {
style.pro_controller.Assign(1);
style.pokeball.Assign(1);
}
std::transform(
Settings::values.players.begin(), Settings::values.players.end(),
connected_controllers.begin(), [](const Settings::PlayerInput& player) {
return ControllerHolder{MapSettingsTypeToNPad(player.type), player.connected};
});
std::stable_partition(connected_controllers.begin(), connected_controllers.begin() + 8,
[](const ControllerHolder& holder) { return holder.is_connected; });
// Account for handheld
if (connected_controllers[8].is_connected)
connected_controllers[8].type = NPadControllerType::Handheld;
supported_npad_id_types.resize(npad_id_list.size());
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
npad_id_list.size() * sizeof(u32));
// Add a default dual joycon controller if none are present.
if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
[](const ControllerHolder& controller) { return controller.is_connected; })) {
supported_npad_id_types.resize(npad_id_list.size());
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
npad_id_list.size() * sizeof(u32));
AddNewController(PREFERRED_CONTROLLER);
AddNewController(NPadControllerType::JoyDual);
}
for (std::size_t i = 0; i < connected_controllers.size(); ++i) {
const auto& controller = connected_controllers[i];
if (controller.is_connected) {
AddNewControllerAt(controller.type, IndexToNPad(i));
}
}
}
void Controller_NPad::OnLoadInputDevices() {
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
const auto& players = Settings::values.players;
for (std::size_t i = 0; i < players.size(); ++i) {
std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons[i].begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
}
}
void Controller_NPad::OnRelease() {}
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
const auto controller_idx = NPadIdToIndex(npad_id);
const auto controller_type = connected_controllers[controller_idx].type;
if (!connected_controllers[controller_idx].is_connected) {
return;
}
auto& pad_state = npad_pad_states[controller_idx].pad_states;
auto& lstick_entry = npad_pad_states[controller_idx].l_stick;
auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
const auto& button_state = buttons[controller_idx];
const auto& analog_state = sticks[controller_idx];
using namespace Settings::NativeButton;
pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus());
pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus());
pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus());
pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus());
pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_left.Assign(button_state[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_up.Assign(button_state[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_right.Assign(button_state[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_down.Assign(button_state[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_left.Assign(button_state[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_up.Assign(button_state[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_right.Assign(button_state[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_down.Assign(button_state[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
const auto [stick_l_x_f, stick_l_y_f] =
analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] =
analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
}
void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
if (!IsControllerActivated())
return;
@@ -199,97 +321,9 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
continue;
}
// Pad states
ControllerPadState pad_state{};
using namespace Settings::NativeButton;
pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
AnalogPosition lstick_entry{};
AnalogPosition rstick_entry{};
const auto [stick_l_x_f, stick_l_y_f] =
sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] =
sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
if (controller_type == NPadControllerType::JoyLeft ||
controller_type == NPadControllerType::JoyRight) {
if (npad.properties.is_horizontal) {
ControllerPadState state{};
AnalogPosition temp_lstick_entry{};
AnalogPosition temp_rstick_entry{};
if (controller_type == NPadControllerType::JoyLeft) {
state.d_down.Assign(pad_state.d_left.Value());
state.d_left.Assign(pad_state.d_up.Value());
state.d_right.Assign(pad_state.d_down.Value());
state.d_up.Assign(pad_state.d_right.Value());
state.l.Assign(pad_state.l.Value() | pad_state.sl.Value());
state.r.Assign(pad_state.r.Value() | pad_state.sr.Value());
state.zl.Assign(pad_state.zl.Value());
state.plus.Assign(pad_state.minus.Value());
temp_lstick_entry = lstick_entry;
temp_rstick_entry = rstick_entry;
std::swap(temp_lstick_entry.x, temp_lstick_entry.y);
std::swap(temp_rstick_entry.x, temp_rstick_entry.y);
temp_lstick_entry.y *= -1;
} else if (controller_type == NPadControllerType::JoyRight) {
state.x.Assign(pad_state.a.Value());
state.a.Assign(pad_state.b.Value());
state.b.Assign(pad_state.y.Value());
state.y.Assign(pad_state.b.Value());
state.l.Assign(pad_state.l.Value() | pad_state.sl.Value());
state.r.Assign(pad_state.r.Value() | pad_state.sr.Value());
state.zr.Assign(pad_state.zr.Value());
state.plus.Assign(pad_state.plus.Value());
temp_lstick_entry = lstick_entry;
temp_rstick_entry = rstick_entry;
std::swap(temp_lstick_entry.x, temp_lstick_entry.y);
std::swap(temp_rstick_entry.x, temp_rstick_entry.y);
temp_rstick_entry.x *= -1;
}
pad_state.raw = state.raw;
lstick_entry = temp_lstick_entry;
rstick_entry = temp_rstick_entry;
}
}
const u32 npad_index = static_cast<u32>(i);
RequestPadStateUpdate(npad_index);
auto& pad_state = npad_pad_states[npad_index];
auto& main_controller =
npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index];
@@ -304,8 +338,51 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
if (hold_type == NpadHoldType::Horizontal) {
// TODO(ogniK): Remap buttons for different orientations
ControllerPadState state{};
AnalogPosition temp_lstick_entry{};
AnalogPosition temp_rstick_entry{};
if (controller_type == NPadControllerType::JoyLeft) {
state.d_down.Assign(pad_state.pad_states.d_left.Value());
state.d_left.Assign(pad_state.pad_states.d_up.Value());
state.d_right.Assign(pad_state.pad_states.d_down.Value());
state.d_up.Assign(pad_state.pad_states.d_right.Value());
state.l.Assign(pad_state.pad_states.l.Value() |
pad_state.pad_states.left_sl.Value());
state.r.Assign(pad_state.pad_states.r.Value() |
pad_state.pad_states.left_sr.Value());
state.zl.Assign(pad_state.pad_states.zl.Value());
state.plus.Assign(pad_state.pad_states.minus.Value());
temp_lstick_entry = pad_state.l_stick;
temp_rstick_entry = pad_state.r_stick;
std::swap(temp_lstick_entry.x, temp_lstick_entry.y);
std::swap(temp_rstick_entry.x, temp_rstick_entry.y);
temp_lstick_entry.y *= -1;
} else if (controller_type == NPadControllerType::JoyRight) {
state.x.Assign(pad_state.pad_states.a.Value());
state.a.Assign(pad_state.pad_states.b.Value());
state.b.Assign(pad_state.pad_states.y.Value());
state.y.Assign(pad_state.pad_states.b.Value());
state.l.Assign(pad_state.pad_states.l.Value() |
pad_state.pad_states.right_sl.Value());
state.r.Assign(pad_state.pad_states.r.Value() |
pad_state.pad_states.right_sr.Value());
state.zr.Assign(pad_state.pad_states.zr.Value());
state.plus.Assign(pad_state.pad_states.plus.Value());
temp_lstick_entry = pad_state.l_stick;
temp_rstick_entry = pad_state.r_stick;
std::swap(temp_lstick_entry.x, temp_lstick_entry.y);
std::swap(temp_rstick_entry.x, temp_rstick_entry.y);
temp_rstick_entry.x *= -1;
}
pad_state.pad_states.raw = state.raw;
pad_state.l_stick = temp_lstick_entry;
pad_state.r_stick = temp_rstick_entry;
}
libnx_entry.connection_status.raw = 0;
switch (controller_type) {
@@ -316,9 +393,9 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
handheld_entry.connection_status.IsRightJoyConnected.Assign(1);
handheld_entry.connection_status.IsLeftJoyWired.Assign(1);
handheld_entry.connection_status.IsRightJoyWired.Assign(1);
handheld_entry.pad_states.raw = pad_state.raw;
handheld_entry.l_stick = lstick_entry;
handheld_entry.r_stick = rstick_entry;
handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw;
handheld_entry.pad.l_stick = pad_state.l_stick;
handheld_entry.pad.r_stick = pad_state.r_stick;
break;
case NPadControllerType::JoyDual:
dual_entry.connection_status.raw = 0;
@@ -331,25 +408,25 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
libnx_entry.connection_status.IsConnected.Assign(1);
dual_entry.pad_states.raw = pad_state.raw;
dual_entry.l_stick = lstick_entry;
dual_entry.r_stick = rstick_entry;
dual_entry.pad.pad_states.raw = pad_state.pad_states.raw;
dual_entry.pad.l_stick = pad_state.l_stick;
dual_entry.pad.r_stick = pad_state.r_stick;
break;
case NPadControllerType::JoyLeft:
left_entry.connection_status.raw = 0;
left_entry.connection_status.IsConnected.Assign(1);
left_entry.pad_states.raw = pad_state.raw;
left_entry.l_stick = lstick_entry;
left_entry.r_stick = rstick_entry;
left_entry.pad.pad_states.raw = pad_state.pad_states.raw;
left_entry.pad.l_stick = pad_state.l_stick;
left_entry.pad.r_stick = pad_state.r_stick;
break;
case NPadControllerType::JoyRight:
right_entry.connection_status.raw = 0;
right_entry.connection_status.IsConnected.Assign(1);
right_entry.pad_states.raw = pad_state.raw;
right_entry.l_stick = lstick_entry;
right_entry.r_stick = rstick_entry;
right_entry.pad.pad_states.raw = pad_state.pad_states.raw;
right_entry.pad.l_stick = pad_state.l_stick;
right_entry.pad.r_stick = pad_state.r_stick;
break;
case NPadControllerType::Pokeball:
pokeball_entry.connection_status.raw = 0;
@@ -357,30 +434,30 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
pokeball_entry.connection_status.IsConnected.Assign(1);
pokeball_entry.connection_status.IsWired.Assign(1);
pokeball_entry.pad_states.raw = pad_state.raw;
pokeball_entry.l_stick = lstick_entry;
pokeball_entry.r_stick = rstick_entry;
pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw;
pokeball_entry.pad.l_stick = pad_state.l_stick;
pokeball_entry.pad.r_stick = pad_state.r_stick;
break;
case NPadControllerType::ProController:
main_controller.connection_status.raw = 0;
main_controller.connection_status.IsConnected.Assign(1);
main_controller.connection_status.IsWired.Assign(1);
main_controller.pad_states.raw = pad_state.raw;
main_controller.l_stick = lstick_entry;
main_controller.r_stick = rstick_entry;
main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
main_controller.pad.l_stick = pad_state.l_stick;
main_controller.pad.r_stick = pad_state.r_stick;
break;
}
// LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
// any controllers.
libnx_entry.pad_states.raw = pad_state.raw;
libnx_entry.l_stick = lstick_entry;
libnx_entry.r_stick = rstick_entry;
libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw;
libnx_entry.pad.l_stick = pad_state.l_stick;
libnx_entry.pad.r_stick = pad_state.r_stick;
}
std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
shared_memory_entries.size() * sizeof(NPadEntry));
} // namespace Service::HID
}
void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
style.raw = style_set.raw;
@@ -401,23 +478,24 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
if (!controller.is_connected) {
continue;
}
if (!IsControllerSupported(PREFERRED_CONTROLLER)) {
const auto best_type = DecideBestController(PREFERRED_CONTROLLER);
const bool is_handheld = (best_type == NPadControllerType::Handheld ||
PREFERRED_CONTROLLER == NPadControllerType::Handheld);
const auto requested_controller =
i <= MAX_NPAD_ID ? MapSettingsTypeToNPad(Settings::values.players[i].type)
: NPadControllerType::Handheld;
if (!IsControllerSupported(requested_controller)) {
const auto is_handheld = requested_controller == NPadControllerType::Handheld;
if (is_handheld) {
controller.type = NPadControllerType::None;
controller.is_connected = false;
AddNewController(best_type);
AddNewController(requested_controller);
} else {
controller.type = best_type;
controller.type = requested_controller;
InitNewlyAddedControler(i);
}
had_controller_update = true;
}
}
if (had_controller_update) {
styleset_changed_event->Signal();
if (had_controller_update) {
styleset_changed_event->Signal();
}
}
}
@@ -450,15 +528,7 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
return;
}
for (std::size_t i = 0; i < controller_ids.size(); i++) {
std::size_t controller_pos = i;
// Handheld controller conversion
if (controller_pos == NPAD_HANDHELD) {
controller_pos = 8;
}
// Unknown controller conversion
if (controller_pos == NPAD_UNKNOWN) {
controller_pos = 9;
}
std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i));
if (connected_controllers[controller_pos].is_connected) {
// TODO(ogniK): Vibrate the physical controller
}
@@ -477,7 +547,9 @@ Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() cons
Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
return last_processed_vibration;
}
void Controller_NPad::AddNewController(NPadControllerType controller) {
controller = DecideBestController(controller);
if (controller == NPadControllerType::Handheld) {
connected_controllers[8] = {controller, true};
InitNewlyAddedControler(8);
@@ -495,6 +567,18 @@ void Controller_NPad::AddNewController(NPadControllerType controller) {
InitNewlyAddedControler(controller_id);
}
void Controller_NPad::AddNewControllerAt(NPadControllerType controller, u32 npad_id) {
controller = DecideBestController(controller);
if (controller == NPadControllerType::Handheld) {
connected_controllers[NPadIdToIndex(NPAD_HANDHELD)] = {controller, true};
InitNewlyAddedControler(NPadIdToIndex(NPAD_HANDHELD));
return;
}
connected_controllers[npad_id] = {controller, true};
InitNewlyAddedControler(npad_id);
}
void Controller_NPad::ConnectNPad(u32 npad_id) {
connected_controllers[NPadIdToIndex(npad_id)].is_connected = true;
}
@@ -503,6 +587,36 @@ void Controller_NPad::DisconnectNPad(u32 npad_id) {
connected_controllers[NPadIdToIndex(npad_id)].is_connected = false;
}
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) {
if (controller == NPadControllerType::Handheld) {
// Handheld is not even a supported type, lets stop here
if (std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(),
NPAD_HANDHELD) == supported_npad_id_types.end()) {
return false;
}
// Handheld should not be supported in docked mode
if (Settings::values.use_docked_mode) {
return false;
}
}
switch (controller) {
case NPadControllerType::ProController:
return style.pro_controller;
case NPadControllerType::Handheld:
return style.handheld;
case NPadControllerType::JoyDual:
return style.joycon_dual;
case NPadControllerType::JoyLeft:
return style.joycon_left;
case NPadControllerType::JoyRight:
return style.joycon_right;
case NPadControllerType::Pokeball:
return style.pokeball;
default:
return false;
}
}
Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) {
// These are controllers without led patterns
@@ -534,6 +648,36 @@ void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
can_controllers_vibrate = can_vibrate;
}
void Controller_NPad::ClearAllConnectedControllers() {
for (auto& controller : connected_controllers) {
if (controller.is_connected && controller.type != NPadControllerType::None) {
controller.type = NPadControllerType::None;
controller.is_connected = false;
}
}
}
void Controller_NPad::DisconnectAllConnectedControllers() {
std::for_each(connected_controllers.begin(), connected_controllers.end(),
[](ControllerHolder& controller) { controller.is_connected = false; });
}
void Controller_NPad::ConnectAllDisconnectedControllers() {
std::for_each(connected_controllers.begin(), connected_controllers.end(),
[](ControllerHolder& controller) {
if (controller.type != NPadControllerType::None && !controller.is_connected) {
controller.is_connected = false;
}
});
}
void Controller_NPad::ClearAllControllers() {
std::for_each(connected_controllers.begin(), connected_controllers.end(),
[](ControllerHolder& controller) {
controller.type = NPadControllerType::None;
controller.is_connected = false;
});
}
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
const bool support_handheld =
std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=

View File

@@ -5,13 +5,18 @@
#pragma once
#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/frontend/input.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/settings.h"
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:
Controller_NPad();
@@ -107,11 +112,19 @@ public:
Vibration GetLastVibration() const;
void AddNewController(NPadControllerType controller);
void AddNewControllerAt(NPadControllerType controller, u32 npad_id);
void ConnectNPad(u32 npad_id);
void DisconnectNPad(u32 npad_id);
LedPattern GetLedPattern(u32 npad_id);
void SetVibrationEnabled(bool can_vibrate);
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
private:
struct CommonHeader {
@@ -164,8 +177,11 @@ private:
BitField<23, 1, u64_le> r_stick_down;
// Not always active?
BitField<24, 1, u64_le> sl;
BitField<25, 1, u64_le> sr;
BitField<24, 1, u64_le> left_sl;
BitField<25, 1, u64_le> left_sr;
BitField<26, 1, u64_le> right_sl;
BitField<27, 1, u64_le> right_sr;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
@@ -189,12 +205,17 @@ private:
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
struct GenericStates {
s64_le timestamp;
s64_le timestamp2;
struct ControllerPad {
ControllerPadState pad_states;
AnalogPosition l_stick;
AnalogPosition r_stick;
};
static_assert(sizeof(ControllerPad) == 0x18, "ControllerPad 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");
@@ -266,15 +287,20 @@ private:
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
struct ControllerHolder {
Controller_NPad::NPadControllerType type;
NPadControllerType type;
bool is_connected;
};
NPadType style{};
std::array<NPadEntry, 10> shared_memory_entries{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
std::array<
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
10>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
std::array<
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
10>
sticks;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
Kernel::SharedPtr<Kernel::Event> styleset_changed_event;
@@ -285,5 +311,8 @@ private:
void InitNewlyAddedControler(std::size_t controller_idx);
bool IsControllerSupported(NPadControllerType controller) const;
NPadControllerType DecideBestController(NPadControllerType priority) const;
void RequestPadStateUpdate(u32 npad_id);
std::array<ControllerPad, 10> npad_pad_states{};
bool IsControllerSupported(NPadControllerType controller);
};
} // namespace Service::HID

View File

@@ -41,16 +41,17 @@ void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) {
const auto [x, y, pressed] = touch_device->GetStatus();
auto& touch_entry = cur_entry.states[0];
if (pressed) {
touch_entry.attribute.raw = 0;
if (pressed && Settings::values.touchscreen.enabled) {
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
touch_entry.diameter_x = 15;
touch_entry.diameter_y = 15;
touch_entry.rotation_angle = 0;
touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle;
const u64 tick = CoreTiming::GetTicks();
touch_entry.delta_time = tick - last_touch;
last_touch = tick;
touch_entry.finger = 0;
touch_entry.finger = Settings::values.touchscreen.finger;
cur_entry.entry_count = 1;
} else {
cur_entry.entry_count = 0;
@@ -60,6 +61,6 @@ void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) {
}
void Controller_Touchscreen::OnLoadInputDevices() {
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device);
}
} // namespace Service::HID

View File

@@ -4,6 +4,7 @@
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -29,9 +30,18 @@ public:
void OnLoadInputDevices() override;
private:
struct Attributes {
union {
u32 raw{};
BitField<0, 1, u32_le> start_touch;
BitField<1, 1, u32_le> end_touch;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
struct TouchState {
u64_le delta_time;
u32_le attribute;
Attributes attribute;
u32_le finger;
u32_le x;
u32_le y;

View File

@@ -34,8 +34,8 @@
namespace Service::HID {
// Updating period for each HID device.
// TODO(shinyquagsire23): These need better values.
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
// TODO(ogniK): Find actual polling rate of hid
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 66;
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;

View File

@@ -16,35 +16,18 @@
namespace Service::LDR {
namespace ErrCodes {
enum {
InvalidMemoryState = 51,
InvalidNRO = 52,
InvalidNRR = 53,
MissingNRRHash = 54,
MaximumNRO = 55,
MaximumNRR = 56,
AlreadyLoaded = 57,
InvalidAlignment = 81,
InvalidSize = 82,
InvalidNROAddress = 84,
InvalidNRRAddress = 85,
NotInitialized = 87,
};
}
constexpr ResultCode ERROR_INVALID_MEMORY_STATE(ErrorModule::Loader, ErrCodes::InvalidMemoryState);
constexpr ResultCode ERROR_INVALID_NRO(ErrorModule::Loader, ErrCodes::InvalidNRO);
constexpr ResultCode ERROR_INVALID_NRR(ErrorModule::Loader, ErrCodes::InvalidNRR);
constexpr ResultCode ERROR_MISSING_NRR_HASH(ErrorModule::Loader, ErrCodes::MissingNRRHash);
constexpr ResultCode ERROR_MAXIMUM_NRO(ErrorModule::Loader, ErrCodes::MaximumNRO);
constexpr ResultCode ERROR_MAXIMUM_NRR(ErrorModule::Loader, ErrCodes::MaximumNRR);
constexpr ResultCode ERROR_ALREADY_LOADED(ErrorModule::Loader, ErrCodes::AlreadyLoaded);
constexpr ResultCode ERROR_INVALID_ALIGNMENT(ErrorModule::Loader, ErrCodes::InvalidAlignment);
constexpr ResultCode ERROR_INVALID_SIZE(ErrorModule::Loader, ErrCodes::InvalidSize);
constexpr ResultCode ERROR_INVALID_NRO_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNROAddress);
constexpr ResultCode ERROR_INVALID_NRR_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNRRAddress);
constexpr ResultCode ERROR_NOT_INITIALIZED(ErrorModule::Loader, ErrCodes::NotInitialized);
constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52};
constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53};
constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54};
constexpr ResultCode ERROR_MAXIMUM_NRO{ErrorModule::Loader, 55};
constexpr ResultCode ERROR_MAXIMUM_NRR{ErrorModule::Loader, 56};
constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57};
constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81};
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82};
constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
constexpr u64 MAXIMUM_LOADED_RO = 0x40;

View File

@@ -18,7 +18,7 @@ public:
ILogger() : ServiceFramework("ILogger") {
static const FunctionInfo functions[] = {
{0x00000000, &ILogger::Initialize, "Initialize"},
{0x00000001, nullptr, "SetDestination"},
{0x00000001, &ILogger::SetDestination, "SetDestination"},
};
RegisterHandlers(functions);
}
@@ -178,6 +178,17 @@ private:
}
}
// This service function is intended to be used as a way to
// redirect logging output to different destinations, however,
// given we always want to see the logging output, it's sufficient
// to do nothing and return success here.
void SetDestination(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LM, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
std::ostringstream log_stream;
};

View File

@@ -10,6 +10,56 @@
namespace Settings {
namespace NativeButton {
const std::array<const char*, NumButtons> mapping = {{
"button_a",
"button_b",
"button_x",
"button_y",
"button_lstick",
"button_rstick",
"button_l",
"button_r",
"button_zl",
"button_zr",
"button_plus",
"button_minus",
"button_dleft",
"button_dup",
"button_dright",
"button_ddown",
"button_lstick_left",
"button_lstick_up",
"button_lstick_right",
"button_lstick_down",
"button_rstick_left",
"button_rstick_up",
"button_rstick_right",
"button_rstick_down",
"button_sl",
"button_sr",
"button_home",
"button_screenshot",
}};
}
namespace NativeAnalog {
const std::array<const char*, NumAnalogs> mapping = {{
"lstick",
"rstick",
}};
}
namespace NativeMouseButton {
const std::array<const char*, NumMouseButtons> mapping = {{
"left",
"right",
"middle",
"forward",
"back",
}};
}
Values values = {};
void Apply() {

View File

@@ -60,36 +60,7 @@ constexpr int BUTTON_NS_END = NumButtons;
constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
static const std::array<const char*, NumButtons> mapping = {{
"button_a",
"button_b",
"button_x",
"button_y",
"button_lstick",
"button_rstick",
"button_l",
"button_r",
"button_zl",
"button_zr",
"button_plus",
"button_minus",
"button_dleft",
"button_dup",
"button_dright",
"button_ddown",
"button_lstick_left",
"button_lstick_up",
"button_lstick_right",
"button_lstick_down",
"button_rstick_left",
"button_rstick_up",
"button_rstick_right",
"button_rstick_down",
"button_sl",
"button_sr",
"button_home",
"button_screenshot",
}};
extern const std::array<const char*, NumButtons> mapping;
} // namespace NativeButton
@@ -105,12 +76,273 @@ constexpr int STICK_HID_BEGIN = LStick;
constexpr int STICK_HID_END = NumAnalogs;
constexpr int NUM_STICKS_HID = NumAnalogs;
static const std::array<const char*, NumAnalogs> mapping = {{
"lstick",
"rstick",
}};
extern const std::array<const char*, NumAnalogs> mapping;
} // namespace NativeAnalog
namespace NativeMouseButton {
enum Values {
Left,
Right,
Middle,
Forward,
Back,
NumMouseButtons,
};
constexpr int MOUSE_HID_BEGIN = Left;
constexpr int MOUSE_HID_END = NumMouseButtons;
constexpr int NUM_MOUSE_HID = NumMouseButtons;
extern const std::array<const char*, NumMouseButtons> mapping;
} // namespace NativeMouseButton
namespace NativeKeyboard {
enum Keys {
None,
Error,
A = 4,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
N1,
N2,
N3,
N4,
N5,
N6,
N7,
N8,
N9,
N0,
Enter,
Escape,
Backspace,
Tab,
Space,
Minus,
Equal,
LeftBrace,
RightBrace,
Backslash,
Tilde,
Semicolon,
Apostrophe,
Grave,
Comma,
Dot,
Slash,
CapsLockKey,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
SystemRequest,
ScrollLockKey,
Pause,
Insert,
Home,
PageUp,
Delete,
End,
PageDown,
Right,
Left,
Down,
Up,
NumLockKey,
KPSlash,
KPAsterisk,
KPMinus,
KPPlus,
KPEnter,
KP1,
KP2,
KP3,
KP4,
KP5,
KP6,
KP7,
KP8,
KP9,
KP0,
KPDot,
Key102,
Compose,
Power,
KPEqual,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
Open,
Help,
Properties,
Front,
Stop,
Repeat,
Undo,
Cut,
Copy,
Paste,
Find,
Mute,
VolumeUp,
VolumeDown,
CapsLockActive,
NumLockActive,
ScrollLockActive,
KPComma,
KPLeftParenthesis,
KPRightParenthesis,
LeftControlKey = 0xE0,
LeftShiftKey,
LeftAltKey,
LeftMetaKey,
RightControlKey,
RightShiftKey,
RightAltKey,
RightMetaKey,
MediaPlayPause,
MediaStopCD,
MediaPrevious,
MediaNext,
MediaEject,
MediaVolumeUp,
MediaVolumeDown,
MediaMute,
MediaWebsite,
MediaBack,
MediaForward,
MediaStop,
MediaFind,
MediaScrollUp,
MediaScrollDown,
MediaEdit,
MediaSleep,
MediaCoffee,
MediaRefresh,
MediaCalculator,
NumKeyboardKeys,
};
static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
enum Modifiers {
LeftControl,
LeftShift,
LeftAlt,
LeftMeta,
RightControl,
RightShift,
RightAlt,
RightMeta,
CapsLock,
ScrollLock,
NumLock,
NumKeyboardMods,
};
constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
} // namespace NativeKeyboard
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
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;
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
enum class ControllerType {
ProController,
DualJoycon,
RightJoycon,
LeftJoycon,
};
struct PlayerInput {
bool connected;
ControllerType type;
ButtonsRaw buttons;
AnalogsRaw analogs;
u32 body_color_right;
u32 button_color_right;
u32 body_color_left;
u32 button_color_left;
};
struct TouchscreenInput {
bool enabled;
std::string device;
u32 finger;
u32 diameter_x;
u32 diameter_y;
u32 rotation_angle;
};
struct Values {
// System
bool use_docked_mode;
@@ -120,10 +352,22 @@ struct Values {
s32 language_index;
// Controls
std::array<std::string, NativeButton::NumButtons> buttons;
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
std::array<PlayerInput, 10> players;
bool mouse_enabled;
std::string mouse_device;
MouseButtonsRaw mouse_buttons;
bool keyboard_enabled;
KeyboardKeysRaw keyboard_keys;
KeyboardModsRaw keyboard_mods;
bool debug_pad_enabled;
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
std::string motion_device;
std::string touch_device;
TouchscreenInput touchscreen;
std::atomic_bool is_device_reload_pending{true};
// Core

View File

@@ -69,6 +69,15 @@ void Maxwell3D::InitializeRegisterDefaults() {
// TODO(Rodrigo): Most games do not set a point size. I think this is a case of a
// register carrying a default value. Assume it's OpenGL's default (1).
regs.point_size = 1.0f;
// TODO(bunnei): Some games do not initialize the color masks (e.g. Sonic Mania). Assuming a
// default of enabled fixes rendering here.
for (std::size_t color_mask = 0; color_mask < Regs::NumRenderTargets; color_mask++) {
regs.color_mask[color_mask].R.Assign(1);
regs.color_mask[color_mask].G.Assign(1);
regs.color_mask[color_mask].B.Assign(1);
regs.color_mask[color_mask].A.Assign(1);
}
}
void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {

View File

@@ -624,7 +624,16 @@ public:
}
} zeta;
INSERT_PADDING_WORDS(0x5B);
INSERT_PADDING_WORDS(0x41);
union {
BitField<0, 4, u32> stencil;
BitField<4, 4, u32> unknown;
BitField<8, 4, u32> scissor;
BitField<12, 4, u32> viewport;
} clear_flags;
INSERT_PADDING_WORDS(0x19);
std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format;
@@ -719,7 +728,20 @@ public:
u32 vb_element_base;
INSERT_PADDING_WORDS(0x38);
INSERT_PADDING_WORDS(0x36);
union {
BitField<0, 1, u32> c0;
BitField<1, 1, u32> c1;
BitField<2, 1, u32> c2;
BitField<3, 1, u32> c3;
BitField<4, 1, u32> c4;
BitField<5, 1, u32> c5;
BitField<6, 1, u32> c6;
BitField<7, 1, u32> c7;
} clip_distance_enabled;
INSERT_PADDING_WORDS(0x1);
float point_size;
@@ -863,7 +885,32 @@ public:
Cull cull;
INSERT_PADDING_WORDS(0x28);
u32 pixel_center_integer;
INSERT_PADDING_WORDS(0x1);
u32 viewport_transform_enabled;
INSERT_PADDING_WORDS(0x3);
union {
BitField<0, 1, u32> depth_range_0_1;
BitField<3, 1, u32> depth_clamp_near;
BitField<4, 1, u32> depth_clamp_far;
} view_volume_clip_control;
union {
BitField<0, 4, u32> c0;
BitField<4, 4, u32> c1;
BitField<8, 4, u32> c2;
BitField<12, 4, u32> c3;
BitField<16, 4, u32> c4;
BitField<20, 4, u32> c5;
BitField<24, 4, u32> c6;
BitField<28, 4, u32> c7;
} clip_distance_mode;
INSERT_PADDING_WORDS(0x20);
struct {
u32 enable;
@@ -1127,6 +1174,7 @@ ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
ASSERT_REG_POSITION(color_mask_common, 0x3E4);
ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
ASSERT_REG_POSITION(zeta, 0x3F8);
ASSERT_REG_POSITION(clear_flags, 0x43E);
ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
ASSERT_REG_POSITION(rt_control, 0x487);
ASSERT_REG_POSITION(zeta_width, 0x48a);
@@ -1153,6 +1201,7 @@ ASSERT_REG_POSITION(stencil_front_mask, 0x4E7);
ASSERT_REG_POSITION(frag_color_clamp, 0x4EA);
ASSERT_REG_POSITION(screen_y_control, 0x4EB);
ASSERT_REG_POSITION(vb_element_base, 0x50D);
ASSERT_REG_POSITION(clip_distance_enabled, 0x544);
ASSERT_REG_POSITION(point_size, 0x546);
ASSERT_REG_POSITION(zeta_enable, 0x54E);
ASSERT_REG_POSITION(multisample_control, 0x54F);
@@ -1171,6 +1220,9 @@ ASSERT_REG_POSITION(primitive_restart, 0x591);
ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
ASSERT_REG_POSITION(cull, 0x646);
ASSERT_REG_POSITION(pixel_center_integer, 0x649);
ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B);
ASSERT_REG_POSITION(clip_distance_mode, 0x650);
ASSERT_REG_POSITION(logic_op, 0x671);
ASSERT_REG_POSITION(clear_buffers, 0x674);
ASSERT_REG_POSITION(color_mask, 0x680);

View File

@@ -17,6 +17,8 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
switch (format) {
case PixelFormat::ABGR8:
return 4;
default:
return 4;
}
UNREACHABLE();

View File

@@ -118,10 +118,43 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
CheckExtensions();
}
RasterizerOpenGL::~RasterizerOpenGL() {}
void RasterizerOpenGL::CheckExtensions() {
if (!GLAD_GL_ARB_texture_filter_anisotropic && !GLAD_GL_EXT_texture_filter_anisotropic) {
LOG_WARNING(
Render_OpenGL,
"Anisotropic filter is not supported! this can cause graphical issues in some games.");
}
if (!GLAD_GL_ARB_viewport_array) {
LOG_WARNING(Render_OpenGL, "Viewport arrays are not supported! this can potyentially cause "
"issues in games that use geometry shaders.");
}
if (!GLAD_GL_ARB_color_buffer_float) {
LOG_WARNING(
Render_OpenGL,
"Color clamp control is not supported! this can cause graphical issues in some games.");
}
if (!GLAD_GL_ARB_buffer_storage) {
LOG_WARNING(
Render_OpenGL,
"Buffer storage control is not supported! this can cause performance degradation.");
}
if (!GLAD_GL_AMD_depth_clamp_separate) {
if (!GLAD_GL_ARB_depth_clamp) {
LOG_WARNING(
Render_OpenGL,
"Depth Clamp is not supported! this can cause graphical issues in some games.");
} else {
LOG_WARNING(Render_OpenGL, "Separate Depth Clamp is not supported! this can cause "
"graphical issues in some games.");
}
}
}
void RasterizerOpenGL::SetupVertexFormat() {
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
const auto& regs = gpu.regs;
@@ -266,6 +299,11 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
return params;
}
bool RasterizerOpenGL::GeometryShaderActive() {
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
return gpu.regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
}
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@@ -542,6 +580,30 @@ void RasterizerOpenGL::Clear() {
ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!");
use_stencil = true;
clear_state.stencil.test_enabled = true;
if (regs.clear_flags.stencil) {
// Stencil affects the clear so fill it with the used masks
clear_state.stencil.front.test_func = GL_ALWAYS;
clear_state.stencil.front.test_mask = regs.stencil_front_func_mask;
clear_state.stencil.front.action_stencil_fail = GL_KEEP;
clear_state.stencil.front.action_depth_fail = GL_KEEP;
clear_state.stencil.front.action_depth_pass = GL_KEEP;
clear_state.stencil.front.write_mask = regs.stencil_front_mask;
if (regs.stencil_two_side_enable) {
clear_state.stencil.back.test_func = GL_ALWAYS;
clear_state.stencil.back.test_mask = regs.stencil_back_func_mask;
clear_state.stencil.back.action_stencil_fail = GL_KEEP;
clear_state.stencil.back.action_depth_fail = GL_KEEP;
clear_state.stencil.back.action_depth_pass = GL_KEEP;
clear_state.stencil.back.write_mask = regs.stencil_back_mask;
} else {
clear_state.stencil.back.test_func = GL_ALWAYS;
clear_state.stencil.back.test_mask = 0xFFFFFFFF;
clear_state.stencil.back.write_mask = 0xFFFFFFFF;
clear_state.stencil.back.action_stencil_fail = GL_KEEP;
clear_state.stencil.back.action_depth_fail = GL_KEEP;
clear_state.stencil.back.action_depth_pass = GL_KEEP;
}
}
}
if (!use_color && !use_depth && !use_stencil) {
@@ -553,6 +615,15 @@ void RasterizerOpenGL::Clear() {
ConfigureFramebuffers(clear_state, use_color, use_depth || use_stencil, false,
regs.clear_buffers.RT.Value());
if (regs.clear_flags.scissor) {
SyncScissorTest(clear_state);
}
if (regs.clear_flags.viewport) {
// Viewport does not affects glClearBuffer so emulate viewport using Scissor
clear_state.TranslateViewportToScissor();
}
clear_state.Apply();
if (use_color) {
@@ -577,7 +648,7 @@ void RasterizerOpenGL::DrawArrays() {
const auto& regs = gpu.regs;
ScopeAcquireGLContext acquire_context{emu_window};
state.geometry_shaders.enabled = GeometryShaderActive();
ConfigureFramebuffers(state);
SyncColorMask();
SyncFragmentColorClampState();
@@ -588,7 +659,7 @@ void RasterizerOpenGL::DrawArrays() {
SyncLogicOpState();
SyncCullMode();
SyncPrimitiveRestart();
SyncScissorTest();
SyncScissorTest(state);
// Alpha Testing is synced on shaders.
SyncTransformFeedback();
SyncPointState();
@@ -815,7 +886,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
}
const u32 bias = config.mip_lod_bias.Value();
// Sign extend the 13-bit value.
const u32 mask = 1U << (13 - 1);
constexpr u32 mask = 1U << (13 - 1);
const float bias_lod = static_cast<s32>((bias ^ mask) - mask) / 256.f;
if (lod_bias != bias_lod) {
lod_bias = bias_lod;
@@ -942,16 +1013,28 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumViewports; i++) {
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[i].GetRect()};
const std::size_t viewport_count =
current_state.geometry_shaders.enabled ? Tegra::Engines::Maxwell3D::Regs::NumViewports : 1;
for (std::size_t i = 0; i < viewport_count; i++) {
auto& viewport = current_state.viewports[i];
viewport.x = viewport_rect.left;
viewport.y = viewport_rect.bottom;
viewport.width = static_cast<GLfloat>(viewport_rect.GetWidth());
viewport.height = static_cast<GLfloat>(viewport_rect.GetHeight());
const auto& src = regs.viewports[i];
if (regs.viewport_transform_enabled) {
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[i].GetRect()};
viewport.x = viewport_rect.left;
viewport.y = viewport_rect.bottom;
viewport.width = viewport_rect.GetWidth();
viewport.height = viewport_rect.GetHeight();
} else {
viewport.x = src.x;
viewport.y = src.y;
viewport.width = src.width;
viewport.height = src.height;
}
viewport.depth_range_far = regs.viewports[i].depth_range_far;
viewport.depth_range_near = regs.viewports[i].depth_range_near;
}
current_state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0;
current_state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0;
}
void RasterizerOpenGL::SyncClipEnabled() {
@@ -1120,11 +1203,13 @@ void RasterizerOpenGL::SyncLogicOpState() {
state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
}
void RasterizerOpenGL::SyncScissorTest() {
void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumViewports; i++) {
const std::size_t viewport_count =
current_state.geometry_shaders.enabled ? Tegra::Engines::Maxwell3D::Regs::NumViewports : 1;
for (std::size_t i = 0; i < viewport_count; i++) {
const auto& src = regs.scissor_test[i];
auto& dst = state.viewports[i].scissor;
auto& dst = current_state.viewports[i].scissor;
dst.enabled = (src.enable != 0);
if (dst.enabled == 0) {
return;

View File

@@ -91,19 +91,20 @@ private:
void SyncWithConfig(const Tegra::Texture::TSCEntry& info);
private:
Tegra::Texture::TextureFilter mag_filter;
Tegra::Texture::TextureFilter min_filter;
Tegra::Texture::TextureMipmapFilter mip_filter;
Tegra::Texture::WrapMode wrap_u;
Tegra::Texture::WrapMode wrap_v;
Tegra::Texture::WrapMode wrap_p;
bool uses_depth_compare;
Tegra::Texture::DepthCompareFunc depth_compare_func;
GLvec4 border_color;
float min_lod;
float max_lod;
float lod_bias;
float max_anisotropic;
Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest;
Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest;
Tegra::Texture::TextureMipmapFilter mip_filter = Tegra::Texture::TextureMipmapFilter::None;
Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge;
Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge;
Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge;
bool uses_depth_compare = false;
Tegra::Texture::DepthCompareFunc depth_compare_func =
Tegra::Texture::DepthCompareFunc::Always;
GLvec4 border_color = {};
float min_lod = 0.f;
float max_lod = 16.0f;
float lod_bias = 0.0f;
float max_anisotropic = 1.0f;
};
/**
@@ -171,7 +172,7 @@ private:
void SyncMultiSampleState();
/// Syncs the scissor test state to match the guest state
void SyncScissorTest();
void SyncScissorTest(OpenGLState& current_state);
/// Syncs the transform feedback state to match the guest state
void SyncTransformFeedback();
@@ -185,6 +186,10 @@ private:
/// Check asserts for alpha testing.
void CheckAlphaTests();
/// check for extension that are not strictly required
/// but are needed for correct emulation
void CheckExtensions();
bool has_ARB_direct_state_access = false;
bool has_ARB_multi_bind = false;
bool has_ARB_separate_shader_objects = false;
@@ -223,6 +228,7 @@ private:
DrawParameters SetupDraw();
void SetupShaders(GLenum primitive_mode);
bool GeometryShaderActive();
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;

View File

@@ -1275,6 +1275,31 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) {
return surface;
}
void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface,
const Surface& dst_surface) {
const auto& init_params{src_surface->GetSurfaceParams()};
const auto& dst_params{dst_surface->GetSurfaceParams()};
VAddr address = init_params.addr;
const std::size_t layer_size = dst_params.LayerMemorySize();
for (u32 layer = 0; layer < dst_params.depth; layer++) {
for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) {
const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap);
const Surface& copy = TryGet(sub_address);
if (!copy)
continue;
const auto& src_params{copy->GetSurfaceParams()};
const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))};
const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))};
glCopyImageSubData(copy->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0,
0, 0, dst_surface->Texture().handle,
SurfaceTargetToGL(dst_params.target), mipmap, 0, 0, layer, width,
height, 1);
}
address += layer_size;
}
}
void RasterizerCacheOpenGL::FermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) {
@@ -1340,11 +1365,13 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
CopySurface(old_surface, new_surface, copy_pbo.handle);
}
break;
case SurfaceTarget::TextureCubemap:
case SurfaceTarget::Texture3D:
AccurateCopySurface(old_surface, new_surface);
break;
case SurfaceTarget::TextureCubemap:
case SurfaceTarget::Texture2DArray:
case SurfaceTarget::TextureCubeArray:
AccurateCopySurface(old_surface, new_surface);
FastLayeredCopySurface(old_surface, new_surface);
break;
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",

View File

@@ -350,6 +350,7 @@ private:
/// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
void FastLayeredCopySurface(const Surface& src_surface, const Surface& dst_surface);
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
/// previously been used. This is to prevent surfaces from being constantly created and

File diff suppressed because it is too large Load Diff

View File

@@ -92,6 +92,8 @@ OpenGLState::OpenGLState() {
point.size = 1;
fragment_color_clamp.enabled = false;
depth_clamp.far_plane = false;
depth_clamp.near_plane = false;
}
void OpenGLState::ApplyDefaultState() {
@@ -234,6 +236,28 @@ void OpenGLState::ApplyStencilTest() const {
}
}
void OpenGLState::TranslateViewportToScissor() {
auto& current = this->viewports[0];
if (current.scissor.enabled) {
GLint left = std::max(current.x, current.scissor.x);
GLint right =
std::max(current.x + current.width, current.scissor.x + current.scissor.width);
GLint bottom = std::max(current.y, current.scissor.y);
GLint top =
std::max(current.y + current.height, current.scissor.y + current.scissor.height);
current.scissor.x = std::max(left, 0);
current.scissor.y = std::max(bottom, 0);
current.scissor.width = std::max(right - left, 0);
current.scissor.height = std::max(top - bottom, 0);
} else {
current.scissor.enabled = true;
current.scissor.x = current.x;
current.scissor.y = current.y;
current.scissor.width = current.width;
current.scissor.height = current.height;
}
}
void OpenGLState::ApplyViewport() const {
if (GLAD_GL_ARB_viewport_array && geometry_shaders.enabled) {
for (GLuint i = 0; i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumViewports);
@@ -242,7 +266,9 @@ void OpenGLState::ApplyViewport() const {
const auto& updated = viewports[i];
if (updated.x != current.x || updated.y != current.y ||
updated.width != current.width || updated.height != current.height) {
glViewportIndexedf(i, updated.x, updated.y, updated.width, updated.height);
glViewportIndexedf(
i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y),
static_cast<GLfloat>(updated.width), static_cast<GLfloat>(updated.height));
}
if (updated.depth_range_near != current.depth_range_near ||
updated.depth_range_far != current.depth_range_far) {
@@ -270,8 +296,7 @@ void OpenGLState::ApplyViewport() const {
const auto& updated = viewports[0];
if (updated.x != current.x || updated.y != current.y || updated.width != current.width ||
updated.height != current.height) {
glViewport(static_cast<GLint>(updated.x), static_cast<GLint>(updated.y),
static_cast<GLsizei>(updated.width), static_cast<GLsizei>(updated.height));
glViewport(updated.x, updated.y, updated.width, updated.height);
}
if (updated.depth_range_near != current.depth_range_near ||
updated.depth_range_far != current.depth_range_far) {
@@ -483,6 +508,29 @@ void OpenGLState::Apply() const {
fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
}
}
if (depth_clamp.far_plane != cur_state.depth_clamp.far_plane ||
depth_clamp.near_plane != cur_state.depth_clamp.near_plane) {
if (GLAD_GL_AMD_depth_clamp_separate) {
if (depth_clamp.far_plane) {
glEnable(GL_DEPTH_CLAMP_FAR_AMD);
} else {
glDisable(GL_DEPTH_CLAMP_FAR_AMD);
}
if (depth_clamp.near_plane) {
glEnable(GL_DEPTH_CLAMP_NEAR_AMD);
} else {
glDisable(GL_DEPTH_CLAMP_NEAR_AMD);
}
} else {
if (depth_clamp.far_plane || depth_clamp.near_plane) {
glEnable(GL_DEPTH_CLAMP);
} else {
glDisable(GL_DEPTH_CLAMP);
}
UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
"Unimplemented Depth Clamp Separation!");
}
}
if (multisample_control.alpha_to_coverage != cur_state.multisample_control.alpha_to_coverage) {
if (multisample_control.alpha_to_coverage) {
glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);

View File

@@ -48,6 +48,11 @@ public:
bool enabled; // GL_CLAMP_FRAGMENT_COLOR_ARB
} fragment_color_clamp;
struct {
bool far_plane;
bool near_plane;
} depth_clamp; // GL_DEPTH_CLAMP
struct {
bool enabled; // viewports arrays are only supported when geometry shaders are enabled.
} geometry_shaders;
@@ -156,10 +161,10 @@ public:
} draw;
struct viewport {
GLfloat x;
GLfloat y;
GLfloat width;
GLfloat height;
GLint x;
GLint y;
GLint width;
GLint height;
GLfloat depth_range_near; // GL_DEPTH_RANGE
GLfloat depth_range_far; // GL_DEPTH_RANGE
struct {
@@ -206,6 +211,7 @@ public:
OpenGLState& ResetBuffer(GLuint handle);
OpenGLState& ResetVertexArray(GLuint handle);
OpenGLState& ResetFramebuffer(GLuint handle);
void TranslateViewportToScissor();
private:
static OpenGLState cur_state;

View File

@@ -304,6 +304,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
gl_framebuffer_data.resize(texture.width * texture.height * 4);
break;
default:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
gl_framebuffer_data.resize(texture.width * texture.height * 4);
LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer pixel format: {}",
static_cast<u32>(framebuffer.pixel_format));
UNREACHABLE();
}

View File

@@ -7,6 +7,8 @@ add_executable(yuzu
Info.plist
about_dialog.cpp
about_dialog.h
applets/software_keyboard.cpp
applets/software_keyboard.h
bootmanager.cpp
bootmanager.h
compatibility_list.cpp
@@ -27,8 +29,14 @@ add_executable(yuzu
configuration/configure_graphics.h
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input_player.cpp
configuration/configure_input_player.h
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_web.cpp
configuration/configure_web.h
debugger/graphics/graphics_breakpoint_observer.cpp
@@ -76,7 +84,10 @@ set(UIS
configuration/configure_general.ui
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_mouse_advanced.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
hotkeys.ui
main.ui

View File

@@ -0,0 +1,152 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <mutex>
#include <QDialogButtonBox>
#include <QFont>
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
#include "core/hle/lock.h"
#include "yuzu/applets/software_keyboard.h"
#include "yuzu/main.h"
QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator(
Core::Frontend::SoftwareKeyboardParameters parameters)
: parameters(std::move(parameters)) {}
QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {
if (input.size() > parameters.max_length)
return Invalid;
if (parameters.disable_space && input.contains(' '))
return Invalid;
if (parameters.disable_address && input.contains('@'))
return Invalid;
if (parameters.disable_percent && input.contains('%'))
return Invalid;
if (parameters.disable_slash && (input.contains('/') || input.contains('\\')))
return Invalid;
if (parameters.disable_number &&
std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) {
return Invalid;
}
if (parameters.disable_download_code &&
std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) {
return Invalid;
}
return Acceptable;
}
QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_)
: QDialog(parent), parameters(std::move(parameters_)) {
layout = new QVBoxLayout;
header_label = new QLabel(QString::fromStdU16String(parameters.header_text));
header_label->setFont({header_label->font().family(), 11, QFont::Bold});
if (header_label->text().isEmpty())
header_label->setText(tr("Enter text:"));
sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text));
sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(),
sub_label->font().weight(), true});
sub_label->setHidden(parameters.sub_text.empty());
guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text));
guide_label->setHidden(parameters.guide_text.empty());
length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length));
length_label->setAlignment(Qt::AlignRight);
length_label->setFont({length_label->font().family(), 8});
line_edit = new QLineEdit;
line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters));
line_edit->setMaxLength(static_cast<int>(parameters.max_length));
line_edit->setText(QString::fromStdU16String(parameters.initial_text));
line_edit->setCursorPosition(
parameters.cursor_at_beginning ? 0 : static_cast<int>(parameters.initial_text.size()));
line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal);
connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) {
length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length));
});
buttons = new QDialogButtonBox;
buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
buttons->addButton(parameters.submit_text.empty()
? tr("OK")
: QString::fromStdU16String(parameters.submit_text),
QDialogButtonBox::AcceptRole);
connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::Submit);
connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::Reject);
layout->addWidget(header_label);
layout->addWidget(sub_label);
layout->addWidget(guide_label);
layout->addWidget(length_label);
layout->addWidget(line_edit);
layout->addWidget(buttons);
setLayout(layout);
setWindowTitle(tr("Software Keyboard"));
}
QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default;
void QtSoftwareKeyboardDialog::Submit() {
ok = true;
text = line_edit->text().toStdU16String();
accept();
}
void QtSoftwareKeyboardDialog::Reject() {
ok = false;
text.clear();
accept();
}
std::u16string QtSoftwareKeyboardDialog::GetText() const {
return text;
}
bool QtSoftwareKeyboardDialog::GetStatus() const {
return ok;
}
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window,
&GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window,
&GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection);
connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this,
&QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection);
}
QtSoftwareKeyboard::~QtSoftwareKeyboard() = default;
void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16string>)> out,
Core::Frontend::SoftwareKeyboardParameters parameters) const {
text_output = out;
emit MainWindowGetText(parameters);
}
void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message,
std::function<void()> finished_check) const {
this->finished_check = finished_check;
emit MainWindowTextCheckDialog(error_message);
}
void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) {
// Acquire the HLE mutex
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
text_output(text);
}
void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() {
// Acquire the HLE mutex
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
finished_check();
}

View File

@@ -0,0 +1,80 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
#include <QValidator>
#include "common/assert.h"
#include "core/frontend/applets/software_keyboard.h"
class GMainWindow;
class QDialogButtonBox;
class QLabel;
class QLineEdit;
class QVBoxLayout;
class QtSoftwareKeyboard;
class QtSoftwareKeyboardValidator final : public QValidator {
public:
explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters);
State validate(QString& input, int& pos) const override;
private:
Core::Frontend::SoftwareKeyboardParameters parameters;
};
class QtSoftwareKeyboardDialog final : public QDialog {
Q_OBJECT
public:
QtSoftwareKeyboardDialog(QWidget* parent,
Core::Frontend::SoftwareKeyboardParameters parameters);
~QtSoftwareKeyboardDialog() override;
void Submit();
void Reject();
std::u16string GetText() const;
bool GetStatus() const;
private:
bool ok = false;
std::u16string text;
QDialogButtonBox* buttons;
QLabel* header_label;
QLabel* sub_label;
QLabel* guide_label;
QLabel* length_label;
QLineEdit* line_edit;
QVBoxLayout* layout;
Core::Frontend::SoftwareKeyboardParameters parameters;
};
class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet {
Q_OBJECT
public:
explicit QtSoftwareKeyboard(GMainWindow& parent);
~QtSoftwareKeyboard() override;
void RequestText(std::function<void(std::optional<std::u16string>)> out,
Core::Frontend::SoftwareKeyboardParameters parameters) const override;
void SendTextCheckDialog(std::u16string error_message,
std::function<void()> finished_check) const override;
signals:
void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const;
void MainWindowTextCheckDialog(std::u16string error_message) const;
public slots:
void MainWindowFinishedText(std::optional<std::u16string> text);
void MainWindowFinishedCheckDialog();
private:
mutable std::function<void(std::optional<std::u16string>)> text_output;
mutable std::function<void()> finished_check;
};

View File

@@ -5,6 +5,7 @@
#include <QSettings>
#include "common/file_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "yuzu/configuration/config.h"
#include "yuzu/ui_settings.h"
@@ -47,40 +48,313 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
},
}};
void Config::ReadValues() {
qt_config->beginGroup("Controls");
const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons =
{
Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal,
};
const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> Config::default_keyboard_keys = {
0,
0,
0,
0,
Qt::Key_A,
Qt::Key_B,
Qt::Key_C,
Qt::Key_D,
Qt::Key_E,
Qt::Key_F,
Qt::Key_G,
Qt::Key_H,
Qt::Key_I,
Qt::Key_J,
Qt::Key_K,
Qt::Key_L,
Qt::Key_M,
Qt::Key_N,
Qt::Key_O,
Qt::Key_P,
Qt::Key_Q,
Qt::Key_R,
Qt::Key_S,
Qt::Key_T,
Qt::Key_U,
Qt::Key_V,
Qt::Key_W,
Qt::Key_X,
Qt::Key_Y,
Qt::Key_Z,
Qt::Key_1,
Qt::Key_2,
Qt::Key_3,
Qt::Key_4,
Qt::Key_5,
Qt::Key_6,
Qt::Key_7,
Qt::Key_8,
Qt::Key_9,
Qt::Key_0,
Qt::Key_Enter,
Qt::Key_Escape,
Qt::Key_Backspace,
Qt::Key_Tab,
Qt::Key_Space,
Qt::Key_Minus,
Qt::Key_Equal,
Qt::Key_BracketLeft,
Qt::Key_BracketRight,
Qt::Key_Backslash,
Qt::Key_Dead_Tilde,
Qt::Key_Semicolon,
Qt::Key_Apostrophe,
Qt::Key_Dead_Grave,
Qt::Key_Comma,
Qt::Key_Period,
Qt::Key_Slash,
Qt::Key_CapsLock,
Qt::Key_F1,
Qt::Key_F2,
Qt::Key_F3,
Qt::Key_F4,
Qt::Key_F5,
Qt::Key_F6,
Qt::Key_F7,
Qt::Key_F8,
Qt::Key_F9,
Qt::Key_F10,
Qt::Key_F11,
Qt::Key_F12,
Qt::Key_SysReq,
Qt::Key_ScrollLock,
Qt::Key_Pause,
Qt::Key_Insert,
Qt::Key_Home,
Qt::Key_PageUp,
Qt::Key_Delete,
Qt::Key_End,
Qt::Key_PageDown,
Qt::Key_Right,
Qt::Key_Left,
Qt::Key_Down,
Qt::Key_Up,
Qt::Key_NumLock,
Qt::Key_Slash,
Qt::Key_Asterisk,
Qt::Key_Minus,
Qt::Key_Plus,
Qt::Key_Enter,
Qt::Key_1,
Qt::Key_2,
Qt::Key_3,
Qt::Key_4,
Qt::Key_5,
Qt::Key_6,
Qt::Key_7,
Qt::Key_8,
Qt::Key_9,
Qt::Key_0,
Qt::Key_Period,
0,
0,
Qt::Key_PowerOff,
Qt::Key_Equal,
Qt::Key_F13,
Qt::Key_F14,
Qt::Key_F15,
Qt::Key_F16,
Qt::Key_F17,
Qt::Key_F18,
Qt::Key_F19,
Qt::Key_F20,
Qt::Key_F21,
Qt::Key_F22,
Qt::Key_F23,
Qt::Key_F24,
Qt::Key_Open,
Qt::Key_Help,
Qt::Key_Menu,
0,
Qt::Key_Stop,
Qt::Key_AudioRepeat,
Qt::Key_Undo,
Qt::Key_Cut,
Qt::Key_Copy,
Qt::Key_Paste,
Qt::Key_Find,
Qt::Key_VolumeMute,
Qt::Key_VolumeUp,
Qt::Key_VolumeDown,
Qt::Key_CapsLock,
Qt::Key_NumLock,
Qt::Key_ScrollLock,
Qt::Key_Comma,
Qt::Key_ParenLeft,
Qt::Key_ParenRight,
};
const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default_keyboard_mods = {
Qt::Key_Control, Qt::Key_Shift, Qt::Key_Alt, Qt::Key_ApplicationLeft,
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
};
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
Settings::values.players[p].connected =
qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
Settings::values.players[p].type = static_cast<Settings::ControllerType>(
qt_config
->value(QString("player_%1_type").arg(p),
static_cast<u8>(Settings::ControllerType::DualJoycon))
.toUInt());
Settings::values.players[p].body_color_left =
qt_config
->value(QString("player_%1_body_color_left").arg(p),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
Settings::values.players[p].body_color_right =
qt_config
->value(QString("player_%1_body_color_right").arg(p),
Settings::JOYCON_BODY_NEON_RED)
.toUInt();
Settings::values.players[p].button_color_left =
qt_config
->value(QString("player_%1_button_color_left").arg(p),
Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt();
Settings::values.players[p].button_color_right =
qt_config
->value(QString("player_%1_button_color_right").arg(p),
Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt();
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.players[p].buttons[i] =
qt_config
->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.players[p].buttons[i].empty())
Settings::values.players[p].buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.players[p].analogs[i] =
qt_config
->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.players[p].analogs[i].empty())
Settings::values.players[p].analogs[i] = default_param;
}
}
std::stable_partition(
Settings::values.players.begin(),
Settings::values.players.begin() +
Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
[](const auto& player) { return player.connected; });
}
void Config::ReadDebugValues() {
Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool();
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.buttons[i] =
Settings::values.debug_pad_buttons[i] =
qt_config
->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param))
->value(QString("debug_pad_") + Settings::NativeButton::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.buttons[i].empty())
Settings::values.buttons[i] = default_param;
if (Settings::values.debug_pad_buttons[i].empty())
Settings::values.debug_pad_buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.analogs[i] =
Settings::values.debug_pad_analogs[i] =
qt_config
->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param))
->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.analogs[i].empty())
Settings::values.analogs[i] = default_param;
if (Settings::values.debug_pad_analogs[i].empty())
Settings::values.debug_pad_analogs[i] = default_param;
}
}
void Config::ReadKeyboardValues() {
Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool();
std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(),
Settings::values.keyboard_keys.begin() +
Settings::NativeKeyboard::LeftControlKey,
InputCommon::GenerateKeyboardParam);
std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(),
Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam);
}
void Config::ReadMouseValues() {
Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool();
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
Settings::values.mouse_buttons[i] =
qt_config
->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
if (Settings::values.mouse_buttons[i].empty())
Settings::values.mouse_buttons[i] = default_param;
}
}
void Config::ReadTouchscreenValues() {
Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool();
Settings::values.touchscreen.device =
qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString();
Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt();
Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt();
Settings::values.touchscreen.diameter_x =
qt_config->value("touchscreen_diameter_x", 15).toUInt();
Settings::values.touchscreen.diameter_y =
qt_config->value("touchscreen_diameter_y", 15).toUInt();
qt_config->endGroup();
}
void Config::ReadValues() {
qt_config->beginGroup("Controls");
ReadPlayerValues();
ReadDebugValues();
ReadKeyboardValues();
ReadMouseValues();
ReadTouchscreenValues();
Settings::values.motion_device =
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
.toString()
.toStdString();
Settings::values.touch_device =
qt_config->value("touch_device", "engine:emu_window").toString().toStdString();
qt_config->endGroup();
qt_config->beginGroup("Core");
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
@@ -126,6 +400,11 @@ void Config::ReadValues() {
.toStdString());
qt_config->endGroup();
qt_config->beginGroup("Core");
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("System");
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
@@ -230,18 +509,81 @@ void Config::ReadValues() {
qt_config->endGroup();
}
void Config::SaveValues() {
qt_config->beginGroup("Controls");
void Config::SavePlayerValues() {
for (int p = 0; p < Settings::values.players.size(); ++p) {
qt_config->setValue(QString("player_%1_connected").arg(p),
Settings::values.players[p].connected);
qt_config->setValue(QString("player_%1_type").arg(p),
static_cast<u8>(Settings::values.players[p].type));
qt_config->setValue(QString("player_%1_body_color_left").arg(p),
Settings::values.players[p].body_color_left);
qt_config->setValue(QString("player_%1_body_color_right").arg(p),
Settings::values.players[p].body_color_right);
qt_config->setValue(QString("player_%1_button_color_left").arg(p),
Settings::values.players[p].button_color_left);
qt_config->setValue(QString("player_%1_button_color_right").arg(p),
Settings::values.players[p].button_color_right);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
qt_config->setValue(QString("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(Settings::values.players[p].buttons[i]));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
qt_config->setValue(QString("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(Settings::values.players[p].analogs[i]));
}
}
}
void Config::SaveDebugValues() {
qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(Settings::values.buttons[i]));
qt_config->setValue(QString("debug_pad_") +
QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(Settings::values.debug_pad_buttons[i]));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(Settings::values.analogs[i]));
qt_config->setValue(QString("debug_pad_") +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(Settings::values.debug_pad_analogs[i]));
}
}
void Config::SaveMouseValues() {
qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled);
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
qt_config->setValue(QString("mouse_") +
QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
QString::fromStdString(Settings::values.mouse_buttons[i]));
}
}
void Config::SaveTouchscreenValues() {
qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled);
qt_config->setValue("touchscreen_device",
QString::fromStdString(Settings::values.touchscreen.device));
qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger);
qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle);
qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x);
qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y);
}
void Config::SaveValues() {
qt_config->beginGroup("Controls");
SavePlayerValues();
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device));
qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled);
qt_config->endGroup();
qt_config->beginGroup("Core");
@@ -280,7 +622,6 @@ void Config::SaveValues() {
qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
qt_config->setValue("current_user", Settings::values.current_user);
qt_config->setValue("language_index", Settings::values.language_index);
qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());

View File

@@ -22,10 +22,24 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
private:
void ReadValues();
void ReadPlayerValues();
void ReadDebugValues();
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
void SaveValues();
void SavePlayerValues();
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;

View File

@@ -3,10 +3,6 @@
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/sm/sm.h"
#include "core/settings.h"
#include "ui_configure_general.h"
#include "yuzu/configuration/configure_general.h"
@@ -36,7 +32,6 @@ void ConfigureGeneral::setConfiguration() {
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
ui->enable_nfc->setChecked(Settings::values.enable_nfc);
}
@@ -44,33 +39,6 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
ui->widget->Populate(registry);
}
void ConfigureGeneral::OnDockedModeChanged(bool last_state, bool new_state) {
if (last_state == new_state) {
return;
}
Core::System& system{Core::System::GetInstance()};
if (!system.IsPoweredOn()) {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
// Message queue is shared between these services, we just need to signal an operation
// change to one and it will handle both automatically
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->OperationModeChanged();
has_signalled = true;
}
if (applet_ae != nullptr && !has_signalled) {
applet_ae->GetMessageQueue()->OperationModeChanged();
}
}
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
@@ -78,9 +46,5 @@ void ConfigureGeneral::applyConfiguration() {
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
const bool pre_docked_mode = Settings::values.use_docked_mode;
Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
Settings::values.enable_nfc = ui->enable_nfc->isChecked();
}

View File

@@ -25,7 +25,6 @@ public:
private:
void setConfiguration();
void OnDockedModeChanged(bool last_state, bool new_state);
std::unique_ptr<Ui::ConfigureGeneral> ui;
};

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>300</width>
<height>377</height>
<height>407</height>
</rect>
</property>
<property name="windowTitle">
@@ -71,13 +71,6 @@
<layout class="QHBoxLayout" name="EmulationHorizontalLayout">
<item>
<layout class="QVBoxLayout" name="EmulationVerticalLayout">
<item>
<widget class="QCheckBox" name="use_docked_mode">
<property name="text">
<string>Enable docked mode</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enable_nfc">
<property name="text">

View File

@@ -9,334 +9,202 @@
#include <QMessageBox>
#include <QTimer>
#include "common/param_package.h"
#include "configuration/configure_touchscreen_advanced.h"
#include "core/core.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/sm/sm.h"
#include "input_common/main.h"
#include "ui_configure_input.h"
#include "ui_configure_input_player.h"
#include "ui_configure_mouse_advanced.h"
#include "ui_configure_touchscreen_advanced.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
ConfigureInput::analog_sub_buttons{{
"up",
"down",
"left",
"right",
"modifier",
}};
static QString getKeyName(int key_code) {
switch (key_code) {
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
return QObject::tr("Ctrl");
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
return "";
default:
return QKeySequence(key_code).toString();
}
}
static void SetAnalogButton(const Common::ParamPackage& input_param,
Common::ParamPackage& analog_param, const std::string& button_name) {
if (analog_param.Get("engine", "") != "analog_from_button") {
analog_param = {
{"engine", "analog_from_button"},
{"modifier_scale", "0.5"},
};
}
analog_param.Set(button_name, input_param.Serialize());
}
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
} else if (param.Get("engine", "") == "keyboard") {
return getKeyName(param.Get("code", 0));
} else if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
return QString(QObject::tr("Hat %1 %2"))
.arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str());
}
if (param.Has("axis")) {
return QString(QObject::tr("Axis %1%2"))
.arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str());
}
if (param.Has("button")) {
return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str());
}
return QString();
} else {
return QObject::tr("[unknown]");
}
};
static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
} else if (param.Get("engine", "") == "analog_from_button") {
return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
} else if (param.Get("engine", "") == "sdl") {
if (dir == "modifier") {
return QString(QObject::tr("[unused]"));
}
if (dir == "left" || dir == "right") {
return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str());
} else if (dir == "up" || dir == "down") {
return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str());
}
return QString();
} else {
return QObject::tr("[unknown]");
}
};
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
button_map = {
ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown,
ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown,
ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
players_controller = {
ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox,
ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox,
};
analog_map_buttons = {{
{
ui->buttonLStickUp,
ui->buttonLStickDown,
ui->buttonLStickLeft,
ui->buttonLStickRight,
ui->buttonLStickMod,
},
{
ui->buttonRStickUp,
ui->buttonRStickDown,
ui->buttonRStickLeft,
ui->buttonRStickRight,
ui->buttonRStickMod,
},
}};
players_configure = {
ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure,
ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure,
};
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
if (!button_map[button_id])
continue;
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button_map[button_id], &QPushButton::released, [=]() {
handleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
InputCommon::Polling::DeviceType::Button);
});
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
[=](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Restore Default"), [&] {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
});
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
});
for (auto* controller_box : players_controller) {
controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon",
"Single Left Joycon"});
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
if (!analog_map_buttons[analog_id][sub_button_id])
continue;
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
Qt::CustomContextMenu);
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
handleClick(analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
},
InputCommon::Polling::DeviceType::Button);
});
connect(analog_map_buttons[analog_id][sub_button_id],
&QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Restore Default"), [&] {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
});
context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
menu_location));
});
}
connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
QMessageBox::information(this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
"and then vertically."));
handleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
InputCommon::Polling::DeviceType::Analog);
});
}
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
setPollingResult(params, false);
return;
}
}
});
this->loadConfiguration();
updateUIEnabled();
// TODO(wwylele): enable this when we actually emulate it
ui->buttonHome->setEnabled(false);
connect(ui->restore_defaults_button, &QPushButton::pressed, this,
&ConfigureInput::restoreDefaults);
for (auto* enabled : players_controller)
connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureInput::updateUIEnabled);
connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
connect(ui->handheld_connected, &QCheckBox::stateChanged, this,
&ConfigureInput::updateUIEnabled);
connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
&ConfigureInput::updateUIEnabled);
for (std::size_t i = 0; i < players_configure.size(); ++i) {
connect(players_configure[i], &QPushButton::pressed, this,
[this, i]() { CallConfigureDialog<ConfigureInputPlayer>(i, false); });
}
connect(ui->handheld_configure, &QPushButton::pressed, this,
[this]() { CallConfigureDialog<ConfigureInputPlayer>(8, false); });
connect(ui->debug_configure, &QPushButton::pressed, this,
[this]() { CallConfigureDialog<ConfigureInputPlayer>(9, true); });
connect(ui->mouse_advanced, &QPushButton::pressed, this,
[this]() { CallConfigureDialog<ConfigureMouseAdvanced>(); });
connect(ui->touchscreen_advanced, &QPushButton::pressed, this,
[this]() { CallConfigureDialog<ConfigureTouchscreenAdvanced>(); });
}
template <typename Dialog, typename... Args>
void ConfigureInput::CallConfigureDialog(Args&&... args) {
this->applyConfiguration();
Dialog dialog(this, std::forward<Args>(args)...);
const auto res = dialog.exec();
if (res == QDialog::Accepted) {
dialog.applyConfiguration();
}
}
void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) {
if (last_state == new_state) {
return;
}
Core::System& system{Core::System::GetInstance()};
if (!system.IsPoweredOn()) {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
// Message queue is shared between these services, we just need to signal an operation
// change to one and it will handle both automatically
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->OperationModeChanged();
has_signalled = true;
}
if (applet_ae != nullptr && !has_signalled) {
applet_ae->GetMessageQueue()->OperationModeChanged();
}
}
void ConfigureInput::applyConfiguration() {
std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
for (std::size_t i = 0; i < players_controller.size(); ++i) {
const auto controller_type_index = players_controller[i]->currentIndex();
Settings::values.players[i].connected = controller_type_index != 0;
if (controller_type_index > 0) {
Settings::values.players[i].type =
static_cast<Settings::ControllerType>(controller_type_index - 1);
} else {
Settings::values.players[i].type = Settings::ControllerType::DualJoycon;
}
}
const bool pre_docked_mode = Settings::values.use_docked_mode;
Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
Settings::values
.players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)]
.connected = ui->handheld_connected->isChecked();
Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
}
void ConfigureInput::updateUIEnabled() {
bool hit_disabled = false;
for (auto* player : players_controller) {
player->setDisabled(hit_disabled);
if (hit_disabled)
player->setCurrentIndex(0);
if (!hit_disabled && player->currentIndex() == 0)
hit_disabled = true;
}
for (std::size_t i = 0; i < players_controller.size(); ++i) {
players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);
}
ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked());
ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() &&
!ui->use_docked_mode->isChecked());
ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked());
ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
}
void ConfigureInput::loadConfiguration() {
std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(),
buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(),
analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
updateButtonLabels();
std::stable_partition(
Settings::values.players.begin(),
Settings::values.players.begin() +
Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
[](const auto& player) { return player.connected; });
for (std::size_t i = 0; i < players_controller.size(); ++i) {
const auto connected = Settings::values.players[i].connected;
players_controller[i]->setCurrentIndex(
connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
}
ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
ui->handheld_connected->setChecked(
Settings::values
.players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)]
.connected);
ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
updateUIEnabled();
}
void ConfigureInput::restoreDefaults() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
players_controller[0]->setCurrentIndex(2);
for (std::size_t i = 1; i < players_controller.size(); ++i) {
players_controller[i]->setCurrentIndex(0);
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
}
updateButtonLabels();
}
void ConfigureInput::ClearAll() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
if (button_map[button_id] && button_map[button_id]->isEnabled())
buttons_param[button_id].Clear();
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
if (analog_map_buttons[analog_id][sub_button_id] &&
analog_map_buttons[analog_id][sub_button_id]->isEnabled())
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
}
}
updateButtonLabels();
}
void ConfigureInput::updateButtonLabels() {
for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
if (analog_map_buttons[analog_id][sub_button_id]) {
analog_map_buttons[analog_id][sub_button_id]->setText(
AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
}
}
analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
}
}
void ConfigureInput::handleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
button->setText(tr("[press key]"));
button->setFocus();
input_setter = new_input_setter;
device_pollers = InputCommon::Polling::GetPollers(type);
// Keyboard keys can only be used as button devices
want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
for (auto& poller : device_pollers) {
poller->Start();
}
grabKeyboard();
grabMouse();
timeout_timer->start(5000); // Cancel after 5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
}
void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool abort) {
releaseKeyboard();
releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
if (!abort) {
(*input_setter)(params);
}
updateButtonLabels();
input_setter = {};
}
void ConfigureInput::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event)
return;
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_keys) {
setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
setPollingResult({}, true);
ui->use_docked_mode->setCheckState(Qt::Unchecked);
ui->handheld_connected->setCheckState(Qt::Unchecked);
ui->mouse_enabled->setCheckState(Qt::Unchecked);
ui->keyboard_enabled->setCheckState(Qt::Unchecked);
ui->debug_enabled->setCheckState(Qt::Unchecked);
ui->touchscreen_enabled->setCheckState(Qt::Checked);
updateUIEnabled();
}

View File

@@ -18,6 +18,7 @@
#include "core/settings.h"
#include "input_common/main.h"
#include "ui_configure_input.h"
#include "yuzu/configuration/config.h"
class QPushButton;
class QString;
@@ -37,57 +38,20 @@ public:
void applyConfiguration();
private:
std::unique_ptr<Ui::ConfigureInput> ui;
void updateUIEnabled();
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
template <typename Dialog, typename... Args>
void CallConfigureDialog(Args&&... args);
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
/// Each button input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
/// A group of five QPushButtons represent one analog input. The buttons each represent up,
/// down, left, right, and modifier, respectively.
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
analog_map_buttons;
/// Analog inputs are also represented each with a single button, used to configure with an
/// actual analog stick
std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
bool want_keyboard_keys = false;
void OnDockedModeChanged(bool last_state, bool new_state);
/// Load configuration settings.
void loadConfiguration();
/// Restore all buttons to their default values.
void restoreDefaults();
/// Clear all input configuration
void ClearAll();
/// Update UI to reflect current configuration.
void updateButtonLabels();
std::unique_ptr<Ui::ConfigureInput> ui;
/// Called when the button was pressed.
void handleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
/// Finish polling and configure input using the input_setter
void setPollingResult(const Common::ParamPackage& params, bool abort);
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
std::array<QComboBox*, 8> players_controller;
std::array<QPushButton*, 8> players_configure;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,508 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <QColorDialog>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include "common/assert.h"
#include "common/param_package.h"
#include "input_common/main.h"
#include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
ConfigureInputPlayer::analog_sub_buttons{{
"up",
"down",
"left",
"right",
"modifier",
}};
static void MoveGridElement(QGridLayout* grid, int row_old, int column_old, int row_new,
int column_new) {
const auto item = grid->itemAtPosition(row_old, column_old);
// grid->removeItem(item);
grid->addItem(item, row_new, column_new);
}
static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) {
const int index1 = grid->indexOf(item);
const int index2 = grid->indexOf(onTopOf);
int row, column, rowSpan, columnSpan;
grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan);
grid->takeAt(index1);
grid->addWidget(item, row, column, rowSpan, columnSpan);
}
static QString GetKeyName(int key_code) {
switch (key_code) {
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
return QObject::tr("Ctrl");
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
return "";
default:
return QKeySequence(key_code).toString();
}
}
static void SetAnalogButton(const Common::ParamPackage& input_param,
Common::ParamPackage& analog_param, const std::string& button_name) {
if (analog_param.Get("engine", "") != "analog_from_button") {
analog_param = {
{"engine", "analog_from_button"},
{"modifier_scale", "0.5"},
};
}
analog_param.Set(button_name, input_param.Serialize());
}
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
} else if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
} else if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
return QString(QObject::tr("Hat %1 %2"))
.arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str());
}
if (param.Has("axis")) {
return QString(QObject::tr("Axis %1%2"))
.arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str());
}
if (param.Has("button")) {
return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str());
}
return QString();
} else {
return QObject::tr("[unknown]");
}
};
static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
} else if (param.Get("engine", "") == "analog_from_button") {
return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
} else if (param.Get("engine", "") == "sdl") {
if (dir == "modifier") {
return QString(QObject::tr("[unused]"));
}
if (dir == "left" || dir == "right") {
return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str());
} else if (dir == "up" || dir == "down") {
return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str());
}
return QString();
} else {
return QObject::tr("[unknown]");
}
};
ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, u8 player_index, bool debug)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
player_index(player_index), debug(debug) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
button_map = {
ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown,
ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown,
ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
};
analog_map_buttons = {{
{
ui->buttonLStickUp,
ui->buttonLStickDown,
ui->buttonLStickLeft,
ui->buttonLStickRight,
ui->buttonLStickMod,
},
{
ui->buttonRStickUp,
ui->buttonRStickDown,
ui->buttonRStickLeft,
ui->buttonRStickRight,
ui->buttonRStickMod,
},
}};
debug_hidden = {
ui->buttonSL, ui->labelSL,
ui->buttonSR, ui->labelSR,
ui->buttonLStick, ui->labelLStickPressed,
ui->buttonRStick, ui->labelRStickPressed,
ui->buttonHome, ui->labelHome,
ui->buttonScreenshot, ui->labelScreenshot,
};
auto layout = Settings::values.players[player_index].type;
if (debug)
layout = Settings::ControllerType::DualJoycon;
switch (layout) {
case Settings::ControllerType::ProController:
case Settings::ControllerType::DualJoycon:
layout_hidden = {
ui->buttonSL,
ui->labelSL,
ui->buttonSR,
ui->labelSR,
};
break;
case Settings::ControllerType::LeftJoycon:
layout_hidden = {
ui->right_body_button,
ui->right_buttons_button,
ui->right_body_label,
ui->right_buttons_label,
ui->buttonR,
ui->labelR,
ui->buttonZR,
ui->labelZR,
ui->labelHome,
ui->buttonHome,
ui->buttonPlus,
ui->labelPlus,
ui->RStick,
ui->faceButtons,
};
break;
case Settings::ControllerType::RightJoycon:
layout_hidden = {
ui->left_body_button, ui->left_buttons_button,
ui->left_body_label, ui->left_buttons_label,
ui->buttonL, ui->labelL,
ui->buttonZL, ui->labelZL,
ui->labelScreenshot, ui->buttonScreenshot,
ui->buttonMinus, ui->labelMinus,
ui->LStick, ui->Dpad,
};
break;
}
if (debug || layout == Settings::ControllerType::ProController) {
ui->controller_color->hide();
} else {
if (layout == Settings::ControllerType::LeftJoycon ||
layout == Settings::ControllerType::RightJoycon) {
ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0});
LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad);
LayerGridElements(ui->buttons, ui->misc, ui->RStick);
LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons);
LayerGridElements(ui->buttons, ui->RStick, ui->LStick);
}
}
for (auto* widget : layout_hidden)
widget->setVisible(false);
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
if (!button_map[button_id])
continue;
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button_map[button_id], &QPushButton::released, [=]() {
handleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
InputCommon::Polling::DeviceType::Button);
});
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
[=](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Restore Default"), [&] {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
});
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
});
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
if (!analog_map_buttons[analog_id][sub_button_id])
continue;
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
Qt::CustomContextMenu);
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
handleClick(analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
},
InputCommon::Polling::DeviceType::Button);
});
connect(analog_map_buttons[analog_id][sub_button_id],
&QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Restore Default"), [&] {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
});
context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
menu_location));
});
}
connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
QMessageBox::information(this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
"and then vertically."));
handleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
InputCommon::Polling::DeviceType::Analog);
});
}
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
setPollingResult(params, false);
return;
}
}
});
controller_color_buttons = {
ui->left_body_button,
ui->left_buttons_button,
ui->right_body_button,
ui->right_buttons_button,
};
for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) {
connect(controller_color_buttons[i], &QPushButton::clicked, this,
std::bind(&ConfigureInputPlayer::OnControllerButtonClick, this, i));
}
this->loadConfiguration();
this->resize(0, 0);
// TODO(wwylele): enable this when we actually emulate it
ui->buttonHome->setEnabled(false);
}
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
void ConfigureInputPlayer::applyConfiguration() {
auto& buttons =
debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;
auto& analogs =
debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs;
std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
if (debug)
return;
std::array<u32, 4> colors{};
std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(),
[](QColor color) { return color.rgb(); });
Settings::values.players[player_index].body_color_left = colors[0];
Settings::values.players[player_index].button_color_left = colors[1];
Settings::values.players[player_index].body_color_right = colors[2];
Settings::values.players[player_index].button_color_right = colors[3];
}
void ConfigureInputPlayer::OnControllerButtonClick(int i) {
const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]);
if (!new_bg_color.isValid())
return;
controller_colors[i] = new_bg_color;
controller_color_buttons[i]->setStyleSheet(
QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
}
void ConfigureInputPlayer::loadConfiguration() {
if (debug) {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
std::transform(Settings::values.debug_pad_analogs.begin(),
Settings::values.debug_pad_analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
} else {
std::transform(Settings::values.players[player_index].buttons.begin(),
Settings::values.players[player_index].buttons.end(), buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
std::transform(Settings::values.players[player_index].analogs.begin(),
Settings::values.players[player_index].analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
}
updateButtonLabels();
if (debug)
return;
std::array<u32, 4> colors = {
Settings::values.players[player_index].body_color_left,
Settings::values.players[player_index].button_color_left,
Settings::values.players[player_index].body_color_right,
Settings::values.players[player_index].button_color_right,
};
std::transform(colors.begin(), colors.end(), controller_colors.begin(),
[](u32 rgb) { return QColor::fromRgb(rgb); });
for (std::size_t i = 0; i < colors.size(); ++i) {
controller_color_buttons[i]->setStyleSheet(
QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
}
}
void ConfigureInputPlayer::restoreDefaults() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
}
updateButtonLabels();
}
void ConfigureInputPlayer::ClearAll() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
if (button_map[button_id] && button_map[button_id]->isEnabled())
buttons_param[button_id].Clear();
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
if (analog_map_buttons[analog_id][sub_button_id] &&
analog_map_buttons[analog_id][sub_button_id]->isEnabled())
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
}
}
updateButtonLabels();
}
void ConfigureInputPlayer::updateButtonLabels() {
for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
if (analog_map_buttons[analog_id][sub_button_id]) {
analog_map_buttons[analog_id][sub_button_id]->setText(
AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
}
}
analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
}
}
void ConfigureInputPlayer::handleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
button->setText(tr("[press key]"));
button->setFocus();
const auto iter = std::find(button_map.begin(), button_map.end(), button);
ASSERT(iter != button_map.end());
const auto index = std::distance(button_map.begin(), iter);
ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
input_setter = new_input_setter;
device_pollers = InputCommon::Polling::GetPollers(type);
// Keyboard keys can only be used as button devices
want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
for (auto& poller : device_pollers) {
poller->Start();
}
grabKeyboard();
grabMouse();
timeout_timer->start(5000); // Cancel after 5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
}
void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) {
releaseKeyboard();
releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
if (!abort) {
(*input_setter)(params);
}
updateButtonLabels();
input_setter = std::nullopt;
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event)
return;
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_keys) {
setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
setPollingResult({}, true);
}

View File

@@ -0,0 +1,103 @@
// Copyright 2016 Citra 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 <optional>
#include <string>
#include <unordered_map>
#include <QDialog>
#include <QKeyEvent>
#include "common/param_package.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "ui_configure_input.h"
class QPushButton;
class QString;
class QTimer;
namespace Ui {
class ConfigureInputPlayer;
}
class ConfigureInputPlayer : public QDialog {
Q_OBJECT
public:
explicit ConfigureInputPlayer(QWidget* parent, u8 player_index, bool debug = false);
~ConfigureInputPlayer() override;
/// Save all button configurations to settings file
void applyConfiguration();
private:
std::unique_ptr<Ui::ConfigureInputPlayer> ui;
u8 player_index;
bool debug;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
/// Each button input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
std::vector<QWidget*> debug_hidden;
std::vector<QWidget*> layout_hidden;
/// A group of five QPushButtons represent one analog input. The buttons each represent up,
/// down, left, right, and modifier, respectively.
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
analog_map_buttons;
/// Analog inputs are also represented each with a single button, used to configure with an
/// actual analog stick
std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
bool want_keyboard_keys = false;
std::array<QPushButton*, 4> controller_color_buttons;
std::array<QColor, 4> controller_colors;
void OnControllerButtonClick(int i);
/// Load configuration settings.
void loadConfiguration();
/// Restore all buttons to their default values.
void restoreDefaults();
/// Clear all input configuration
void ClearAll();
/// Update UI to reflect current configuration.
void updateButtonLabels();
/// Called when the button was pressed.
void handleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
/// Finish polling and configure input using the input_setter
void setPollingResult(const Common::ParamPackage& params, bool abort);
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include "common/assert.h"
#include "common/param_package.h"
#include "input_common/main.h"
#include "ui_configure_mouse_advanced.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
static QString GetKeyName(int key_code) {
switch (key_code) {
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
return QObject::tr("Ctrl");
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
return "";
default:
return QKeySequence(key_code).toString();
}
}
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
} else if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
} else if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
return QString(QObject::tr("Hat %1 %2"))
.arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str());
}
if (param.Has("axis")) {
return QString(QObject::tr("Axis %1%2"))
.arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str());
}
if (param.Has("button")) {
return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str());
}
return QString();
} else {
return QObject::tr("[unknown]");
}
}
ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
button_map = {
ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button,
};
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
if (!button_map[button_id])
continue;
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button_map[button_id], &QPushButton::released, [=]() {
handleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
InputCommon::Polling::DeviceType::Button);
});
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
[=](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Restore Default"), [&] {
buttons_param[button_id] =
Common::ParamPackage{InputCommon::GenerateKeyboardParam(
Config::default_mouse_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
});
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
});
}
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
setPollingResult(params, false);
return;
}
}
});
loadConfiguration();
resize(0, 0);
}
ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default;
void ConfigureMouseAdvanced::applyConfiguration() {
std::transform(buttons_param.begin(), buttons_param.end(),
Settings::values.mouse_buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
}
void ConfigureMouseAdvanced::loadConfiguration() {
std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
updateButtonLabels();
}
void ConfigureMouseAdvanced::restoreDefaults() {
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
}
updateButtonLabels();
}
void ConfigureMouseAdvanced::ClearAll() {
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
if (button_map[i] && button_map[i]->isEnabled())
buttons_param[i].Clear();
}
updateButtonLabels();
}
void ConfigureMouseAdvanced::updateButtonLabels() {
for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
}
void ConfigureMouseAdvanced::handleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
button->setText(tr("[press key]"));
button->setFocus();
const auto iter = std::find(button_map.begin(), button_map.end(), button);
ASSERT(iter != button_map.end());
const auto index = std::distance(button_map.begin(), iter);
ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
input_setter = new_input_setter;
device_pollers = InputCommon::Polling::GetPollers(type);
// Keyboard keys can only be used as button devices
want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
for (auto& poller : device_pollers) {
poller->Start();
}
grabKeyboard();
grabMouse();
timeout_timer->start(5000); // Cancel after 5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
}
void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) {
releaseKeyboard();
releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
if (!abort) {
(*input_setter)(params);
}
updateButtonLabels();
input_setter = std::nullopt;
}
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event)
return;
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_keys) {
setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
setPollingResult({}, true);
}

View File

@@ -0,0 +1,68 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <optional>
#include <QDialog>
#include <QWidget>
#include "core/settings.h"
class QCheckBox;
class QPushButton;
class QTimer;
namespace Ui {
class ConfigureMouseAdvanced;
}
class ConfigureMouseAdvanced : public QDialog {
Q_OBJECT
public:
explicit ConfigureMouseAdvanced(QWidget* parent);
~ConfigureMouseAdvanced() override;
void applyConfiguration();
private:
std::unique_ptr<Ui::ConfigureMouseAdvanced> ui;
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<QPushButton*, Settings::NativeMouseButton::NumMouseButtons> button_map;
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons> buttons_param;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
bool want_keyboard_keys = false;
/// Load configuration settings.
void loadConfiguration();
/// Restore all buttons to their default values.
void restoreDefaults();
/// Clear all input configuration
void ClearAll();
/// Update UI to reflect current configuration.
void updateButtonLabels();
/// Called when the button was pressed.
void handleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
/// Finish polling and configure input using the input_setter
void setPollingResult(const Common::ParamPackage& params, bool abort);
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
};

View File

@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureMouseAdvanced</class>
<widget class="QDialog" name="ConfigureMouseAdvanced">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>250</width>
<height>261</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Mouse</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Mouse Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="4">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Right:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="right_button">
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Middle:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="middle_button">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>54</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Back:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="back_button">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Left:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="left_button">
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="3">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Forward:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="forward_button">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="buttonClearAll">
<property name="text">
<string>Clear All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="text">
<string>Restore Defaults</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>124</x>
<y>266</y>
</hint>
<hint type="destinationlabel">
<x>124</x>
<y>143</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>124</x>
<y>266</y>
</hint>
<hint type="destinationlabel">
<x>124</x>
<y>143</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,42 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "ui_configure_touchscreen_advanced.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) {
ui->setupUi(this);
connect(ui->restore_defaults_button, &QPushButton::pressed, this,
&ConfigureTouchscreenAdvanced::restoreDefaults);
loadConfiguration();
resize(0, 0);
}
ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default;
void ConfigureTouchscreenAdvanced::applyConfiguration() {
Settings::values.touchscreen.finger = ui->finger_box->value();
Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value();
Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value();
Settings::values.touchscreen.rotation_angle = ui->angle_box->value();
}
void ConfigureTouchscreenAdvanced::loadConfiguration() {
ui->finger_box->setValue(Settings::values.touchscreen.finger);
ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x);
ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y);
ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle);
}
void ConfigureTouchscreenAdvanced::restoreDefaults() {
ui->finger_box->setValue(0);
ui->diameter_x_box->setValue(15);
ui->diameter_y_box->setValue(15);
ui->angle_box->setValue(0);
}

View File

@@ -0,0 +1,32 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
#include <QWidget>
#include "yuzu/configuration/config.h"
namespace Ui {
class ConfigureTouchscreenAdvanced;
}
class ConfigureTouchscreenAdvanced : public QDialog {
Q_OBJECT
public:
explicit ConfigureTouchscreenAdvanced(QWidget* parent);
~ConfigureTouchscreenAdvanced() override;
void applyConfiguration();
private:
/// Load configuration settings.
void loadConfiguration();
/// Restore all buttons to their default values.
void restoreDefaults();
std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui;
};

View File

@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureTouchscreenAdvanced</class>
<widget class="QDialog" name="ConfigureTouchscreenAdvanced">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>298</width>
<height>339</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Touchscreen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>280</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Warning: The settings in this page affect the inner workings of yuzu's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Touch Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Touch Diameter Y</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Finger</string>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Touch Diameter X</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="finger_box">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Rotational Angle</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="diameter_x_box"/>
</item>
<item row="2" column="2">
<widget class="QSpinBox" name="diameter_y_box"/>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="angle_box"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="restore_defaults_button">
<property name="text">
<string>Restore Defaults</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>318</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>169</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>318</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>169</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -8,9 +8,11 @@
#include <thread>
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/software_keyboard.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -59,6 +61,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/submission_package.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_ldr.h"
@@ -204,6 +207,27 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
void GMainWindow::SoftwareKeyboardGetText(
const Core::Frontend::SoftwareKeyboardParameters& parameters) {
QtSoftwareKeyboardDialog dialog(this, parameters);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
if (!dialog.GetStatus()) {
emit SoftwareKeyboardFinishedText(std::nullopt);
return;
}
emit SoftwareKeyboardFinishedText(dialog.GetText());
}
void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message) {
QMessageBox::warning(this, tr("Text Check Failed"), QString::fromStdU16String(error_message));
emit SoftwareKeyboardFinishedCheckDialog();
}
void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
@@ -559,6 +583,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
const auto drd_callout =
@@ -1228,8 +1254,13 @@ void GMainWindow::OnMenuRecentFile() {
void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
"Core::Frontend::SoftwareKeyboardParameters");
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
ui.action_Start->setEnabled(false);

View File

@@ -29,6 +29,10 @@ class ProfilerWidget;
class WaitTreeWidget;
enum class GameListOpenTarget;
namespace Core::Frontend {
struct SoftwareKeyboardParameters;
} // namespace Core::Frontend
namespace FileSys {
class RegisteredCacheUnion;
class VfsFilesystem;
@@ -95,6 +99,13 @@ signals:
// Signal that tells widgets to update icons to use the current theme
void UpdateThemedIcons();
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
public slots:
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
private:
void InitializeWidgets();
void InitializeDebugWidgets();

View File

@@ -65,54 +65,246 @@ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs>
},
}};
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> default_mouse_buttons = {
SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_APOSTROPHE,
SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS,
};
static const std::array<int, 0x8A> keyboard_keys = {
0,
0,
0,
0,
SDL_SCANCODE_A,
SDL_SCANCODE_B,
SDL_SCANCODE_C,
SDL_SCANCODE_D,
SDL_SCANCODE_E,
SDL_SCANCODE_F,
SDL_SCANCODE_G,
SDL_SCANCODE_H,
SDL_SCANCODE_I,
SDL_SCANCODE_J,
SDL_SCANCODE_K,
SDL_SCANCODE_L,
SDL_SCANCODE_M,
SDL_SCANCODE_N,
SDL_SCANCODE_O,
SDL_SCANCODE_P,
SDL_SCANCODE_Q,
SDL_SCANCODE_R,
SDL_SCANCODE_S,
SDL_SCANCODE_T,
SDL_SCANCODE_U,
SDL_SCANCODE_V,
SDL_SCANCODE_W,
SDL_SCANCODE_X,
SDL_SCANCODE_Y,
SDL_SCANCODE_Z,
SDL_SCANCODE_1,
SDL_SCANCODE_2,
SDL_SCANCODE_3,
SDL_SCANCODE_4,
SDL_SCANCODE_5,
SDL_SCANCODE_6,
SDL_SCANCODE_7,
SDL_SCANCODE_8,
SDL_SCANCODE_9,
SDL_SCANCODE_0,
SDL_SCANCODE_RETURN,
SDL_SCANCODE_ESCAPE,
SDL_SCANCODE_BACKSPACE,
SDL_SCANCODE_TAB,
SDL_SCANCODE_SPACE,
SDL_SCANCODE_MINUS,
SDL_SCANCODE_EQUALS,
SDL_SCANCODE_LEFTBRACKET,
SDL_SCANCODE_RIGHTBRACKET,
SDL_SCANCODE_BACKSLASH,
0,
SDL_SCANCODE_SEMICOLON,
SDL_SCANCODE_APOSTROPHE,
SDL_SCANCODE_GRAVE,
SDL_SCANCODE_COMMA,
SDL_SCANCODE_PERIOD,
SDL_SCANCODE_SLASH,
SDL_SCANCODE_CAPSLOCK,
SDL_SCANCODE_F1,
SDL_SCANCODE_F2,
SDL_SCANCODE_F3,
SDL_SCANCODE_F4,
SDL_SCANCODE_F5,
SDL_SCANCODE_F6,
SDL_SCANCODE_F7,
SDL_SCANCODE_F8,
SDL_SCANCODE_F9,
SDL_SCANCODE_F10,
SDL_SCANCODE_F11,
SDL_SCANCODE_F12,
0,
SDL_SCANCODE_SCROLLLOCK,
SDL_SCANCODE_PAUSE,
SDL_SCANCODE_INSERT,
SDL_SCANCODE_HOME,
SDL_SCANCODE_PAGEUP,
SDL_SCANCODE_DELETE,
SDL_SCANCODE_END,
SDL_SCANCODE_PAGEDOWN,
SDL_SCANCODE_RIGHT,
SDL_SCANCODE_LEFT,
SDL_SCANCODE_DOWN,
SDL_SCANCODE_UP,
SDL_SCANCODE_NUMLOCKCLEAR,
SDL_SCANCODE_KP_DIVIDE,
SDL_SCANCODE_KP_MULTIPLY,
SDL_SCANCODE_KP_MINUS,
SDL_SCANCODE_KP_PLUS,
SDL_SCANCODE_KP_ENTER,
SDL_SCANCODE_KP_1,
SDL_SCANCODE_KP_2,
SDL_SCANCODE_KP_3,
SDL_SCANCODE_KP_4,
SDL_SCANCODE_KP_5,
SDL_SCANCODE_KP_6,
SDL_SCANCODE_KP_7,
SDL_SCANCODE_KP_8,
SDL_SCANCODE_KP_9,
SDL_SCANCODE_KP_0,
SDL_SCANCODE_KP_PERIOD,
0,
0,
SDL_SCANCODE_POWER,
SDL_SCANCODE_KP_EQUALS,
SDL_SCANCODE_F13,
SDL_SCANCODE_F14,
SDL_SCANCODE_F15,
SDL_SCANCODE_F16,
SDL_SCANCODE_F17,
SDL_SCANCODE_F18,
SDL_SCANCODE_F19,
SDL_SCANCODE_F20,
SDL_SCANCODE_F21,
SDL_SCANCODE_F22,
SDL_SCANCODE_F23,
SDL_SCANCODE_F24,
0,
SDL_SCANCODE_HELP,
SDL_SCANCODE_MENU,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
SDL_SCANCODE_KP_COMMA,
SDL_SCANCODE_KP_LEFTPAREN,
SDL_SCANCODE_KP_RIGHTPAREN,
0,
0,
0,
0,
};
static const std::array<int, 8> keyboard_mods{
SDL_SCANCODE_LCTRL, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_LALT, SDL_SCANCODE_LGUI,
SDL_SCANCODE_RCTRL, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_RALT, SDL_SCANCODE_RGUI,
};
void Config::ReadValues() {
// Controls
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
const auto group = fmt::format("ControlsP{}", p);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.players[p].buttons[i] =
sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
if (Settings::values.players[p].buttons[i].empty())
Settings::values.players[p].buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.players[p].analogs[i] =
sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
if (Settings::values.players[p].analogs[i].empty())
Settings::values.players[p].analogs[i] = default_param;
}
}
Settings::values.mouse_enabled =
sdl2_config->GetBoolean("ControlsGeneral", "mouse_enabled", false);
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
Settings::values.mouse_buttons[i] = sdl2_config->Get(
"ControlsGeneral", std::string("mouse_") + Settings::NativeMouseButton::mapping[i],
default_param);
if (Settings::values.mouse_buttons[i].empty())
Settings::values.mouse_buttons[i] = default_param;
}
Settings::values.motion_device = sdl2_config->Get(
"ControlsGeneral", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
Settings::values.keyboard_enabled =
sdl2_config->GetBoolean("ControlsGeneral", "keyboard_enabled", false);
Settings::values.debug_pad_enabled =
sdl2_config->GetBoolean("ControlsGeneral", "debug_pad_enabled", false);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.buttons[i] =
sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param);
if (Settings::values.buttons[i].empty())
Settings::values.buttons[i] = default_param;
Settings::values.debug_pad_buttons[i] = sdl2_config->Get(
"ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i],
default_param);
if (Settings::values.debug_pad_buttons[i].empty())
Settings::values.debug_pad_buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.analogs[i] =
sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param);
if (Settings::values.analogs[i].empty())
Settings::values.analogs[i] = default_param;
Settings::values.debug_pad_analogs[i] = sdl2_config->Get(
"ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i],
default_param);
if (Settings::values.debug_pad_analogs[i].empty())
Settings::values.debug_pad_analogs[i] = default_param;
}
Settings::values.motion_device = sdl2_config->Get(
"Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
Settings::values.touch_device =
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.device =
sdl2_config->Get("ControlsGeneral", "touch_device", "engine:emu_window");
Settings::values.touchscreen.finger =
sdl2_config->GetInteger("ControlsGeneral", "touch_finger", 0);
Settings::values.touchscreen.rotation_angle =
sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0);
Settings::values.touchscreen.diameter_x =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
Settings::values.touchscreen.diameter_y =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
// Renderer
Settings::values.resolution_factor =
(float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_accurate_gpu_emulation =
sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
// Audio
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching =
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
std::transform(keyboard_keys.begin(), keyboard_keys.end(),
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
std::transform(keyboard_mods.begin(), keyboard_mods.end(),
Settings::values.keyboard_keys.begin() +
Settings::NativeKeyboard::LeftControlKey,
InputCommon::GenerateKeyboardParam);
std::transform(keyboard_mods.begin(), keyboard_mods.end(),
Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam);
// Data Storage
Settings::values.use_virtual_sd =
@@ -139,6 +331,30 @@ void Config::ReadValues() {
Settings::values.rng_seed = std::nullopt;
}
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
// Renderer
Settings::values.resolution_factor =
(float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_accurate_gpu_emulation =
sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
// Audio
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching =
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1);
// Miscellaneous