mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-06 15:09:58 +01:00
Android: Dual screen fixes for Handhelds that have 2 screens like Ayaneo Pocket DS (#1341)
* Prevent SecondaryDisplay from stealing focus The SecondaryDisplay Activity was stealing focus from the main Activity when it was launched. Set the `FLAG_NOT_FOCUSABLE` and `FLAG_NOT_TOUCH_MODAL` window flags to prevent the SecondaryDisplay from gaining focus. * Implement touch controls for secondary display This commit introduces touch input handling for the secondary display. The following changes were made: - Added `onSecondaryTouchEvent` and `onSecondaryTouchMoved` to `NativeLibrary.kt` and `native.cpp` to process touch events on the secondary display. - Implemented `onTouchListener` in `SecondaryDisplay.kt` to capture touch events and forward them to the native layer. - Handles `ACTION_DOWN`, `ACTION_POINTER_DOWN`, `ACTION_MOVE`, `ACTION_UP`, `ACTION_POINTER_UP`, and `ACTION_CANCEL` motion events. - Tracks the active pointer to ensure correct touch event handling. * Refactor display logic for multi-display support This commit introduces a `DisplayHelper` class to centralize display-related logic, particularly for handling scenarios where the application might be launched on an external display. Key changes: - Added `DisplayHelper.kt` to manage internal and external display identification based on launch conditions. - `MainActivity` and `EmulationActivity` now use `DisplayHelper.checkLaunchDisplay()` to determine the initial display. - `SecondaryDisplay` now uses `DisplayHelper.getExternalDisplay()` to correctly identify the target display for the secondary presentation. - `InputOverlay` now queries `DisplayHelper.isBottomOnPrimary()` to determine if touch input should be processed for the primary display based on the current screen layout. - `SecondaryDisplay` now queries `DisplayHelper.isBottomOnSecondary()` to conditionally pass touch events to the native layer based on which screen (primary or secondary) is currently displaying the 3DS bottom screen. These changes ensure that the application behaves correctly when launched on either the internal or an external display, and that touch input is routed appropriately based on the user's chosen screen layout for the dual screens. * Removed primary-screen checks so the input overlay always forwards touch events, ensuring all touches reach the native handler even when multiple displays are active * Remove DisplayHelper class and adjust external display logic * Formatting adjustments --------- Co-authored-by: DavidRGriswold <novachild@gmail.com> Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
parent
3af2cd1227
commit
3716f6b9b6
@ -96,6 +96,24 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun onTouchMoved(xAxis: Float, yAxis: Float)
|
||||
|
||||
/**
|
||||
* Handles touch events on the secondary display.
|
||||
*
|
||||
* @param xAxis The value of the x-axis.
|
||||
* @param yAxis The value of the y-axis.
|
||||
* @param pressed To identify if the touch held down or released.
|
||||
* @return true if the pointer is within the touchscreen
|
||||
*/
|
||||
external fun onSecondaryTouchEvent(xAxis: Float, yAxis: Float, pressed: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Handles touch movement on the secondary display.
|
||||
*
|
||||
* @param xAxis The value of the instantaneous x-axis.
|
||||
* @param yAxis The value of the instantaneous y-axis.
|
||||
*/
|
||||
external fun onSecondaryTouchMoved(xAxis: Float, yAxis: Float)
|
||||
|
||||
external fun reloadSettings()
|
||||
|
||||
external fun getTitleId(filename: String): Long
|
||||
|
||||
@ -11,11 +11,14 @@ import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.display.SecondaryDisplayLayout
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
|
||||
class SecondaryDisplay(val context: Context) {
|
||||
private var pres: SecondaryDisplayPresentation? = null
|
||||
@ -41,16 +44,23 @@ class SecondaryDisplay(val context: Context) {
|
||||
NativeLibrary.secondarySurfaceDestroyed()
|
||||
}
|
||||
|
||||
private fun getExternalDisplay(context: Context): Display? {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val internalId = context.display.displayId ?: Display.DEFAULT_DISPLAY
|
||||
val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||
return displays.firstOrNull { it.displayId != internalId && it.name != "HiddenDisplay" }
|
||||
}
|
||||
|
||||
fun updateDisplay() {
|
||||
// decide if we are going to the external display or the internal one
|
||||
var display = getCustomerDisplay()
|
||||
var display = getExternalDisplay(context)
|
||||
if (display == null ||
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
||||
display = vd.display
|
||||
}
|
||||
|
||||
// if our presentation is already on the right display, ignore
|
||||
if (pres?.display == display) return;
|
||||
if (pres?.display == display) return
|
||||
|
||||
// otherwise, make a new presentation
|
||||
releasePresentation()
|
||||
@ -58,13 +68,6 @@ class SecondaryDisplay(val context: Context) {
|
||||
pres?.show()
|
||||
}
|
||||
|
||||
private fun getCustomerDisplay(): Display? {
|
||||
val displays = displayManager.displays
|
||||
// code taken from MelonDS dual screen - should fix odin 2 detection bug
|
||||
return displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||
.firstOrNull { it.displayId != Display.DEFAULT_DISPLAY && it.name != "Built-in Screen" && it.name != "HiddenDisplay"}
|
||||
}
|
||||
|
||||
fun releasePresentation() {
|
||||
pres?.dismiss()
|
||||
pres = null
|
||||
@ -78,9 +81,16 @@ class SecondaryDisplayPresentation(
|
||||
context: Context, display: Display, val parent: SecondaryDisplay
|
||||
) : Presentation(context, display) {
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private var touchscreenPointerId = -1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window?.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
)
|
||||
|
||||
// Initialize SurfaceView
|
||||
surfaceView = SurfaceView(context)
|
||||
@ -100,6 +110,42 @@ class SecondaryDisplayPresentation(
|
||||
}
|
||||
})
|
||||
|
||||
this.surfaceView.setOnTouchListener { _, event ->
|
||||
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
if (touchscreenPointerId == -1) {
|
||||
touchscreenPointerId = pointerId
|
||||
NativeLibrary.onSecondaryTouchEvent(
|
||||
event.getX(pointerIndex),
|
||||
event.getY(pointerIndex),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val index = event.findPointerIndex(touchscreenPointerId)
|
||||
if (index != -1) {
|
||||
NativeLibrary.onSecondaryTouchMoved(
|
||||
event.getX(index),
|
||||
event.getY(index)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
if (pointerId == touchscreenPointerId) {
|
||||
NativeLibrary.onSecondaryTouchEvent(0f, 0f, false)
|
||||
touchscreenPointerId = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
setContentView(surfaceView) // Set SurfaceView as content
|
||||
}
|
||||
|
||||
|
||||
@ -153,8 +153,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
if (isActionMove) {
|
||||
NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat())
|
||||
continue
|
||||
}
|
||||
else if (isActionUp) {
|
||||
} else if (isActionUp) {
|
||||
NativeLibrary.onTouchEvent(0f, 0f, false)
|
||||
break // Up and down actions shouldn't loop
|
||||
}
|
||||
|
||||
@ -657,6 +657,25 @@ void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEn
|
||||
window->OnTouchMoved((int)x, (int)y);
|
||||
}
|
||||
|
||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jfloat x, jfloat y,
|
||||
jboolean pressed) {
|
||||
if (!secondary_window) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return static_cast<jboolean>(secondary_window->OnTouchEvent(
|
||||
static_cast<int>(x + 0.5), static_cast<int>(y + 0.5), pressed));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchMoved([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jfloat x, jfloat y) {
|
||||
if (secondary_window) {
|
||||
secondary_window->OnTouchMoved((int)x, (int)y);
|
||||
}
|
||||
}
|
||||
|
||||
jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj,
|
||||
jstring j_filename) {
|
||||
std::string filepath = GetJString(env, j_filename);
|
||||
|
||||
@ -66,6 +66,10 @@ bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, uns
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!layout.bottom_screen_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user