mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-03-27 13:50:22 -06:00
Android: Hotkey Enable Button (#1464)
This commit is contained in:
parent
5ac0ef8fde
commit
6b2ac400eb
@ -267,36 +267,28 @@ class EmulationActivity : AppCompatActivity() {
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
val button =
|
||||
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
|
||||
val action: Int = when (event.action) {
|
||||
when (event.action) {
|
||||
KeyEvent.ACTION_DOWN -> {
|
||||
hotkeyUtility.handleHotkey(button)
|
||||
|
||||
// On some devices, the back gesture / button press is not intercepted by androidx
|
||||
// and fails to open the emulation menu. So we're stuck running deprecated code to
|
||||
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
|
||||
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// If the hotkey is pressed, we don't want to open the drawer
|
||||
if (!hotkeyUtility.HotkeyIsPressed) {
|
||||
if (!hotkeyUtility.hotkeyIsPressed) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Normal key events.
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
return hotkeyUtility.handleKeyPress(event)
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> {
|
||||
hotkeyUtility.HotkeyIsPressed = false
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
return hotkeyUtility.handleKeyRelease(event)
|
||||
}
|
||||
else -> {
|
||||
return false;
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
val input = event.device
|
||||
?: // Controller was disconnected
|
||||
return false
|
||||
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
|
||||
}
|
||||
|
||||
private fun onAmiiboSelected(selectedFile: String) {
|
||||
|
||||
@ -11,5 +11,6 @@ enum class Hotkey(val button: Int) {
|
||||
PAUSE_OR_RESUME(10004),
|
||||
QUICKSAVE(10005),
|
||||
QUICKLOAD(10006),
|
||||
TURBO_LIMIT(10007);
|
||||
TURBO_LIMIT(10007),
|
||||
ENABLE(10008);
|
||||
}
|
||||
|
||||
@ -5,50 +5,140 @@
|
||||
package org.citra.citra_emu.features.hotkeys
|
||||
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class HotkeyUtility(
|
||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||
private val context: Context) {
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
||||
var HotkeyIsPressed = false
|
||||
private var hotkeyIsEnabled = false
|
||||
var hotkeyIsPressed = false
|
||||
private val currentlyPressedButtons = mutableSetOf<Int>()
|
||||
|
||||
fun handleKeyPress(keyEvent: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val enableButton =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
.getString(Settings.HOTKEY_ENABLE, "")
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
hotkeyIsEnabled = hotkeyIsEnabled || enableButton == "" || thisKeyIsEnableButton
|
||||
|
||||
// Now process all internal buttons associated with this keypress
|
||||
for (button in buttonSet) {
|
||||
currentlyPressedButtons.add(button)
|
||||
//option 1 - this is the enable command, which was already handled
|
||||
if (button == Hotkey.ENABLE.button) {
|
||||
handled = true
|
||||
}
|
||||
// option 2 - this is a different hotkey command
|
||||
else if (hotkeyButtons.contains(button)) {
|
||||
if (hotkeyIsEnabled) {
|
||||
handled = handleHotkey(button) || handled
|
||||
}
|
||||
}
|
||||
// option 3 - this is a normal key
|
||||
else {
|
||||
// if this key press is ALSO associated with a hotkey that will process, skip
|
||||
// the normal key event.
|
||||
if (!thisKeyIsHotkey || !hotkeyIsEnabled) {
|
||||
handled = NativeLibrary.onGamePadEvent(
|
||||
keyEvent.device.descriptor,
|
||||
button,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
) || handled
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
fun handleKeyRelease(keyEvent: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
if (thisKeyIsEnableButton) {
|
||||
handled = true; hotkeyIsEnabled = false
|
||||
}
|
||||
|
||||
for (button in buttonSet) {
|
||||
// this is a hotkey button
|
||||
if (hotkeyButtons.contains(button)) {
|
||||
currentlyPressedButtons.remove(button)
|
||||
if (!currentlyPressedButtons.any { hotkeyButtons.contains(it) }) {
|
||||
// all hotkeys are no longer pressed
|
||||
hotkeyIsPressed = false
|
||||
}
|
||||
} else {
|
||||
// if this key ALSO sends a hotkey command that we already/will handle,
|
||||
// or if we did not register the press of this button, e.g. if this key
|
||||
// was also a hotkey pressed after enable, but released after enable button release, then
|
||||
// skip the normal key event
|
||||
if ((!thisKeyIsHotkey || !hotkeyIsEnabled) && currentlyPressedButtons.contains(
|
||||
button
|
||||
)
|
||||
) {
|
||||
handled = NativeLibrary.onGamePadEvent(
|
||||
keyEvent.device.descriptor,
|
||||
button,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
) || handled
|
||||
currentlyPressedButtons.remove(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
fun handleHotkey(bindedButton: Int): Boolean {
|
||||
if(hotkeyButtons.contains(bindedButton)) {
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.saving),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Hotkey.QUICKLOAD.button -> {
|
||||
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||
val stringRes = if(wasLoaded) {
|
||||
R.string.loading
|
||||
} else {
|
||||
R.string.quickload_not_found
|
||||
}
|
||||
Toast.makeText(context,
|
||||
context.getString(stringRes),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
else -> {}
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.saving),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
HotkeyIsPressed = true
|
||||
return true
|
||||
|
||||
Hotkey.QUICKLOAD.button -> {
|
||||
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||
val stringRes = if (wasLoaded) {
|
||||
R.string.loading
|
||||
} else {
|
||||
R.string.quickload_not_found
|
||||
}
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(stringRes),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
return false
|
||||
hotkeyIsPressed = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +135,7 @@ class Settings {
|
||||
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
|
||||
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
|
||||
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
|
||||
const val HOTKEY_ENABLE = "hotkey_enable"
|
||||
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
|
||||
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
|
||||
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
|
||||
@ -202,6 +203,7 @@ class Settings {
|
||||
R.string.button_zr
|
||||
)
|
||||
val hotKeys = listOf(
|
||||
HOTKEY_ENABLE,
|
||||
HOTKEY_SCREEN_SWAP,
|
||||
HOTKEY_CYCLE_LAYOUT,
|
||||
HOTKEY_CLOSE_GAME,
|
||||
@ -211,6 +213,7 @@ class Settings {
|
||||
HOTKEY_TURBO_LIMIT
|
||||
)
|
||||
val hotkeyTitles = listOf(
|
||||
R.string.controller_hotkey_enable_button,
|
||||
R.string.emulation_swap_screens,
|
||||
R.string.emulation_cycle_landscape_layouts,
|
||||
R.string.emulation_close_game,
|
||||
|
||||
@ -128,6 +128,7 @@ class InputBindingSetting(
|
||||
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
||||
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
||||
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
||||
Settings.HOTKEY_ENABLE -> Hotkey.ENABLE.button
|
||||
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
|
||||
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
|
||||
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
|
||||
@ -162,36 +163,40 @@ class InputBindingSetting(
|
||||
fun removeOldMapping() {
|
||||
// Try remove all possible keys we wrote for this setting
|
||||
val oldKey = preferences.getString(reverseKey, "")
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
if (oldKey != "") {
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
preferences.edit()
|
||||
.remove(abstractSetting.key) // Used for ui text
|
||||
.remove(oldKey) // Used for button mapping
|
||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||
.remove(oldKey + "_GuestButton") // Used for axis button
|
||||
.remove(oldKey + "_Inverted") // used for axis inversion
|
||||
.apply()
|
||||
.remove(reverseKey)
|
||||
val buttonCodes = try {
|
||||
preferences.getStringSet(oldKey, mutableSetOf<String>())!!.toMutableSet()
|
||||
} catch (e: ClassCastException) {
|
||||
// if this is an int pref, either old button or an axis, so just remove it
|
||||
preferences.edit().remove(oldKey).apply()
|
||||
return;
|
||||
}
|
||||
buttonCodes.remove(buttonCode.toString());
|
||||
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to write a gamepad button mapping for the setting.
|
||||
*/
|
||||
private fun writeButtonMapping(key: String) {
|
||||
private fun writeButtonMapping(keyEvent: KeyEvent) {
|
||||
val editor = preferences.edit()
|
||||
|
||||
// Remove mapping for another setting using this input
|
||||
val oldButtonCode = preferences.getInt(key, -1)
|
||||
if (oldButtonCode != -1) {
|
||||
val oldKey = getButtonKey(oldButtonCode)
|
||||
editor.remove(oldKey) // Only need to remove UI text setting, others will be overwritten
|
||||
}
|
||||
|
||||
val key = getInputButtonKey(keyEvent)
|
||||
// Pull in all codes associated with this key
|
||||
// Migrate from the old int preference if need be
|
||||
val buttonCodes = InputBindingSetting.getButtonSet(keyEvent)
|
||||
buttonCodes.add(buttonCode)
|
||||
// Cleanup old mapping for this setting
|
||||
removeOldMapping()
|
||||
|
||||
// Write new mapping
|
||||
editor.putInt(key, buttonCode)
|
||||
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
|
||||
|
||||
// Write next reverse mapping for future cleanup
|
||||
editor.putString(reverseKey, key)
|
||||
@ -229,7 +234,7 @@ class InputBindingSetting(
|
||||
}
|
||||
|
||||
val code = translateEventToKeyId(keyEvent)
|
||||
writeButtonMapping(getInputButtonKey(code))
|
||||
writeButtonMapping(keyEvent)
|
||||
val uiString = "${keyEvent.device.name}: Button $code"
|
||||
value = uiString
|
||||
}
|
||||
@ -289,6 +294,26 @@ class InputBindingSetting(
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
||||
else -> ""
|
||||
}
|
||||
/**
|
||||
* Get the mutable set of int button values this key should map to given an event
|
||||
*/
|
||||
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
|
||||
val key = getInputButtonKey(keyCode)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
var buttonCodes = try {
|
||||
preferences.getStringSet(key, mutableSetOf<String>())
|
||||
} catch (e: ClassCastException) {
|
||||
val prefInt = preferences.getInt(key, -1);
|
||||
val migratedSet = if (prefInt != -1) {
|
||||
mutableSetOf(prefInt.toString())
|
||||
} else {
|
||||
mutableSetOf<String>()
|
||||
}
|
||||
migratedSet
|
||||
}
|
||||
if (buttonCodes == null) buttonCodes = mutableSetOf<String>()
|
||||
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad button.
|
||||
|
||||
@ -811,7 +811,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
add(InputBindingSetting(button, Settings.triggerTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_hotkeys))
|
||||
add(HeaderSetting(R.string.controller_hotkeys,R.string.controller_hotkeys_description))
|
||||
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
||||
|
||||
@ -122,6 +122,8 @@
|
||||
<string name="controller_circlepad">Circle Pad</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Hotkeys</string>
|
||||
<string name="controller_hotkeys_description">If the "Hotkey Enable" key is mapped, that key must be pressed in addition to the mapped hotkey</string>
|
||||
<string name="controller_hotkey_enable_button">Hotkey Enable</string>
|
||||
<string name="controller_triggers">Triggers</string>
|
||||
<string name="controller_trigger">Trigger</string>
|
||||
<string name="controller_dpad">D-Pad</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user