Merge branch 'azahar-emu:master' into feat/game-custom-config

This commit is contained in:
Yassine Gherbi 2025-10-09 15:09:01 +02:00 committed by GitHub
commit 880375eb7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 115 additions and 16 deletions

View file

@ -62,7 +62,7 @@ jobs:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos:
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-13') || 'macos-14' }}
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
strategy:
fail-fast: false
matrix:
@ -103,7 +103,7 @@ jobs:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos-universal:
runs-on: macos-14
runs-on: macos-26
needs: macos
env:
OS: macos

2
externals/fmt vendored

@ -1 +1 @@
Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a
Subproject commit e424e3f2e607da02742f73db84873b8084fc714c

View file

@ -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

View file

@ -11,13 +11,16 @@ 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) {
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
private var pres: SecondaryDisplayPresentation? = null
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val vd: VirtualDisplay
@ -31,6 +34,7 @@ class SecondaryDisplay(val context: Context) {
null,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION
)
displayManager.registerDisplayListener(this, null)
}
fun updateSurface() {
@ -41,16 +45,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,29 +69,41 @@ 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
}
fun releaseVD() {
displayManager.unregisterDisplayListener(this)
vd.release()
}
override fun onDisplayAdded(displayId: Int) {
updateDisplay()
}
override fun onDisplayRemoved(displayId: Int) {
updateDisplay()
}
override fun onDisplayChanged(displayId: Int) {
updateDisplay()
}
}
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 +123,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
}

View file

@ -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
}

View file

@ -661,6 +661,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);

View file

@ -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 ||