Compare commits

..
7 Commits
Author SHA1 Message Date
Chloe df513983d1 cover the license 2026-09-07 17:45:15 -07:00
Chloe a71071e3ac Merge branch 'main' into latency-test 2026-09-07 17:22:30 -07:00
Chloe 8578200db1 Merge branch 'main' into latency-test 2026-09-07 00:52:32 -07:00
Chloe adb9dcc1e9 add toggle for frame pacing 2026-09-07 00:41:18 -07:00
Chloe a32589b06c disable auto swap interval 2026-09-07 00:41:18 -07:00
Chloe 1773e54c84 fix surface update being called outside gl thread 2026-09-07 00:41:18 -07:00
Chloe 5718b73470 my evil test 2026-09-07 00:41:17 -07:00
29 changed files with 1895 additions and 810 deletions

No files matched your search

+12 -9
View File
@@ -1,4 +1,5 @@
import groovy.json.JsonSlurper
import java.io.IOException
import java.net.URI
import java.security.MessageDigest
import java.util.zip.ZipInputStream
@@ -15,7 +16,7 @@ plugins {
id("org.jetbrains.kotlin.plugin.compose")
}
val currentGeodeVersion = "5.11.2"
val currentGeodeVersion = "5.10.1"
abstract class DownloadVersionMetadataTask : DefaultTask() {
@get:Input
@@ -247,9 +248,9 @@ android {
defaultConfig {
applicationId = "com.geode.launcher"
minSdk = 23
targetSdk = 37
versionCode = 31
versionName = "1.9.0"
targetSdk = 36
versionCode = 30
versionName = "1.8.0"
vectorDrawables {
useSupportLibrary = true
@@ -317,6 +318,7 @@ android {
buildFeatures {
compose = true
buildConfig = true
prefab = true
}
packaging {
resources {
@@ -330,7 +332,7 @@ android {
}
}
namespace = "com.geode.launcher"
ndkVersion = "30.0.16248370"
ndkVersion = "29.0.14206865"
}
androidComponents {
@@ -347,8 +349,8 @@ androidComponents {
}
dependencies {
implementation (platform("androidx.compose:compose-bom:2026.09.00"))
implementation ("androidx.core:core-ktx:1.19.1")
implementation (platform("androidx.compose:compose-bom:2026.08.00"))
implementation ("androidx.core:core-ktx:1.19.0")
implementation ("androidx.compose.ui:ui")
implementation ("androidx.compose.material3:material3")
implementation ("androidx.compose.ui:ui-tooling-preview")
@@ -358,6 +360,7 @@ dependencies {
implementation ("androidx.activity:activity-ktx:1.13.0")
implementation ("androidx.appcompat:appcompat:1.8.0")
implementation ("androidx.documentfile:documentfile:1.1.0")
implementation ("androidx.games:games-frame-pacing:2.1.3")
implementation ("com.squareup.okio:okio:3.18.2")
implementation ("com.squareup.okhttp3:okhttp:5.5.0")
@@ -367,8 +370,8 @@ dependencies {
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.11.0")
implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.46.0")
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.46.0")
implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.45.0")
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.45.0")
implementation ("androidx.browser:browser:1.10.0")
debugImplementation ("androidx.compose.ui:ui-tooling")
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
+1 -21
View File
@@ -125,7 +125,7 @@
</activity>
<activity
android:name=".GeometryDashActivity"
android:configChanges="fontScale|layoutDirection|density|smallestScreenSize|screenSize|uiMode|screenLayout|orientation|navigation|keyboardHidden|keyboard|locale"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard"
android:exported="true"
android:hardwareAccelerated="true"
android:screenOrientation="landscape"
@@ -233,26 +233,6 @@
<data android:scheme="geode-launcher" android:host="main" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".MainActivityGreen"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_green"
android:label="@string/app_name"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="main" />
</intent-filter>
</activity-alias>
<provider
android:name=".UserDirectoryProvider"
+5 -2
View File
@@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.21)
project(launcherfix)
add_library(launcherfix SHARED launcher-fix.cpp patcher.cpp)
find_package(games-frame-pacing REQUIRED CONFIG)
add_library(launcherfix SHARED launcher-fix.cpp patcher.cpp pacing.cpp)
target_compile_features(launcherfix PUBLIC cxx_std_20)
set_target_properties(launcherfix PROPERTIES CXX_EXTENSIONS OFF)
@@ -14,4 +16,5 @@ set_target_properties(launcherfix PROPERTIES
CXX_VISIBILITY_PRESET hidden
)
target_link_libraries(launcherfix PRIVATE log)
target_link_libraries(launcherfix PRIVATE log android)
target_link_libraries(launcherfix PRIVATE games-frame-pacing::swappy_static)
+65
View File
@@ -0,0 +1,65 @@
#include <swappy/swappyGL.h>
#include <swappy/swappyGL_extra.h>
#include <android/native_window_jni.h>
#include <memory>
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_initFramePacing(
JNIEnv *env,
jobject,
jobject activity
) {
SwappyGL_init(env, activity);
}
struct NativeWindowDeleter {
void operator()(ANativeWindow* p) const {
if (p != nullptr) ANativeWindow_release(p);
}
};
std::unique_ptr<ANativeWindow, NativeWindowDeleter> native_window = nullptr;
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_destroyFramePacing(JNIEnv*, jobject) {
SwappyGL_destroy();
}
extern "C"
JNIEXPORT jboolean JNICALL Java_com_geode_launcher_LauncherFix_swapFrame(
JNIEnv*,
jobject,
jlong display,
jlong surface
) {
return SwappyGL_swap(
reinterpret_cast<EGLDisplay>(display),
reinterpret_cast<EGLSurface>(surface)
);
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setSurface(
JNIEnv* env,
jobject,
jobject surface
) {
if (surface == nullptr) {
native_window.reset();
SwappyGL_setWindow(nullptr);
return;
}
native_window.reset(ANativeWindow_fromSurface(env, surface));
SwappyGL_setWindow(native_window.get());
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setSwapInterval(
JNIEnv* env,
jobject,
jlong interval_ns
) {
SwappyGL_setAutoSwapInterval(false);
SwappyGL_setSwapIntervalNS(interval_ns);
}
@@ -2,106 +2,33 @@ package com.customRobTop
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.*
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import android.provider.Settings
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import com.customRobTop.contracts.ImportFileActivityContract
import com.customRobTop.contracts.ShareFileActivityContract
import com.geode.launcher.R
import com.geode.launcher.utils.DownloadUtils
import com.geode.launcher.utils.LaunchUtils
import org.cocos2dx.lib.Cocos2dxGLSurfaceView
import org.cocos2dx.lib.Cocos2dxGLSurfaceView.Companion.closeIMEKeyboard
import org.cocos2dx.lib.Cocos2dxGLSurfaceView.Companion.openIMEKeyboard
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.OutputStream
import java.lang.ref.WeakReference
import java.util.UUID
import java.util.*
@Suppress("unused", "UNUSED_PARAMETER")
object BaseRobTopActivity {
const val KEYBOARD_INPUT_MULTILINE = 1
const val KEYBOARD_INPUT_NUMERIC = 4
const val KEYBOARD_INPUT_PASSWORD = 2
const val LEVEL_EXTENSION: String = ".gdlvl"
private var isLoaded = false
var blockBackButton = false
private var keyboardActive = false
var curKeyboardInputFlags = 0
var isPaused = false
lateinit var me: WeakReference<Activity>
private var shouldResumeSound = true
private var pendingExportPaths = emptyList<String>()
private lateinit var importFileActivityContract: ActivityResultLauncher<Unit>
private lateinit var shareFileActivityContract: ActivityResultLauncher<String>
private lateinit var shareFolderActivityContract: ActivityResultLauncher<Uri?>
fun setCurrentActivity(currentActivity: AppCompatActivity) {
fun setCurrentActivity(currentActivity: Activity) {
me = WeakReference(currentActivity)
importFileActivityContract = currentActivity.registerForActivityResult(ImportFileActivityContract()) { uris ->
val filePaths = copyPickedFiles(uris)
Cocos2dxGLSurfaceView.cocos2dxGLSurfaceView.queueEvent {
JniToCpp.filesPicked(filePaths.toTypedArray())
}
}
shareFileActivityContract = currentActivity.registerForActivityResult(ShareFileActivityContract()) { uri ->
val currentlyPending = pendingExportPaths
pendingExportPaths = emptyList()
val exportPath = currentlyPending.firstOrNull()
if (exportPath == null || uri == null) {
Cocos2dxGLSurfaceView.cocos2dxGLSurfaceView.queueEvent {
JniToCpp.fileShareFinished(false)
}
return@registerForActivityResult
}
val result = saveToDocument(exportPath, uri)
Cocos2dxGLSurfaceView.cocos2dxGLSurfaceView.queueEvent {
JniToCpp.fileShareFinished(result)
}
}
shareFolderActivityContract = currentActivity.registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
val currentlyPending = pendingExportPaths
pendingExportPaths = emptyList()
if (uri == null) {
Cocos2dxGLSurfaceView.cocos2dxGLSurfaceView.queueEvent {
JniToCpp.fileShareFinished(false)
}
return@registerForActivityResult
}
val result = saveToFolder(currentlyPending, uri) > 0
Cocos2dxGLSurfaceView.cocos2dxGLSurfaceView.queueEvent {
JniToCpp.fileShareFinished(result)
}
}
}
@SuppressLint("HardwareIds")
@@ -160,18 +87,6 @@ object BaseRobTopActivity {
}
}
@JvmStatic
fun getClipboardString(): String {
me.get()?.run {
val manager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = manager.primaryClip
if (clip != null && clip.itemCount > 0) {
return clip.getItemAt(0).coerceToText(this).toString()
}
}
return ""
}
@JvmStatic
fun sendMail(subject: String, body: String, to: String) {
me.get()?.runOnUiThread {
@@ -349,164 +264,4 @@ object BaseRobTopActivity {
level = DeprecationLevel.HIDDEN
)
fun logEvent(event: String) {}
@JvmStatic
fun setKeyboardInputFlags(flags: Int) {
curKeyboardInputFlags = flags
}
@JvmStatic
fun pickImportFiles() {
try {
importFileActivityContract.launch(Unit)
} catch (_: ActivityNotFoundException) {
me.get()?.apply {
runOnUiThread {
Toast.makeText(
this,
R.string.no_activity_found,
Toast.LENGTH_SHORT,
).show()
}
}
}
}
private fun copyPickedFiles(uris: List<Uri>): List<String> {
val me = me.get() ?: return emptyList()
val importsDir = File(LaunchUtils.getSaveDirectory(me), "imports")
importsDir.mkdirs()
return uris.mapIndexedNotNull { index, uri ->
val displayName = displayName(uri)
?: "import_$index$LEVEL_EXTENSION"
if (!displayName.endsWith(LEVEL_EXTENSION)) {
return@mapIndexedNotNull null
}
var outputFile = File(importsDir, displayName)
var counter = 2
while (outputFile.exists()) {
outputFile = File(importsDir, "${counter}_$displayName")
counter++
}
try {
me.contentResolver.openInputStream(uri)?.use { input ->
outputFile.outputStream().use { output ->
DownloadUtils.copyFile(input, output)
}
outputFile.absolutePath
}
} catch (_: Exception) {
outputFile.delete()
null
}
}
}
private fun displayName(uri: Uri): String? {
me.get()?.contentResolver?.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursorQuery ->
if (cursorQuery.moveToFirst()) {
val displayName = cursorQuery.getString(0)
if (displayName.isNotEmpty()) {
return File(displayName).name
}
}
}
return null
}
@JvmStatic
fun shareFiles(files: Array<String>) {
pendingExportPaths = files.toList()
try {
if (files.size == 1) {
shareFileActivityContract.launch(files.first())
return
} else if (files.size > 1) {
shareFolderActivityContract.launch(null)
return
}
} catch (_: ActivityNotFoundException) {
me.get()?.apply {
runOnUiThread {
Toast.makeText(
this,
R.string.no_activity_found,
Toast.LENGTH_SHORT,
).show()
}
}
Cocos2dxGLSurfaceView.cocos2dxGLSurfaceView.queueEvent {
JniToCpp.fileShareFinished(false)
}
}
}
private fun saveToFolder(exportPaths: List<String>, treeUri: Uri): Int {
val uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri))
val me = me.get() ?: return 0
return exportPaths.count { path ->
val filename = File(path).name
val documentUri = DocumentsContract.createDocument(me.contentResolver, uri, "application/octet-stream", filename)
?: return@count false
saveToDocument(path, documentUri)
}
}
private fun saveToDocument(exportPath: String, uri: Uri): Boolean {
try {
FileInputStream(exportPath).use { inputStream ->
openOutput(uri).use { outputStream ->
if (outputStream != null) {
DownloadUtils.copyFile(inputStream, outputStream)
}
}
}
ensureLevelExtension(uri)
return true
} catch (_: IOException) {
return false
}
}
private fun openOutput(uri: Uri): OutputStream? {
val me = me.get() ?: return null
return try {
me.contentResolver.openOutputStream(uri, "wt")
} catch (_: IOException) {
me.contentResolver.openOutputStream(uri)
}
}
private fun ensureLevelExtension(uri: Uri) {
val displayName = displayName(uri)
if (displayName == null || displayName.endsWith(LEVEL_EXTENSION)) {
return
}
val me = me.get() ?: return
val extensionIdx = displayName.lastIndexOf(".gdlvl.")
val substring = if (extensionIdx < 0) {
displayName + LEVEL_EXTENSION
} else {
displayName.substring(0, extensionIdx + 6)
}
try {
DocumentsContract.renameDocument(me.contentResolver, uri, substring)
} catch (_: IOException) {
}
}
}
@@ -55,16 +55,4 @@ object JniToCpp {
@JvmStatic
external fun videoAdShowed()
@JvmStatic
external fun filesPicked(files: Array<String>)
@JvmStatic
external fun fileShareFinished(success: Boolean)
@JvmStatic
external fun textInputCaretToEnd()
@JvmStatic
external fun textInputDone()
}
@@ -1,40 +0,0 @@
package com.customRobTop.contracts
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContract
class ImportFileActivityContract : ActivityResultContract<Unit, List<Uri>>() {
override fun createIntent(
context: Context,
input: Unit
): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
}
override fun parseResult(
resultCode: Int,
intent: Intent?
): List<Uri> {
if (resultCode != Activity.RESULT_OK || intent == null) {
return emptyList()
}
val clipData = intent.clipData
return if (clipData != null) {
List(clipData.itemCount) { i -> clipData.getItemAt(i).uri }
} else {
val intentData = intent.data
if (intentData != null)
listOf(intentData)
else
emptyList()
}
}
}
@@ -1,40 +0,0 @@
package com.customRobTop.contracts
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContract
import java.io.File
class ShareFileActivityContract : ActivityResultContract<String, Uri?>() {
override fun createIntent(
context: Context,
input: String
): Intent {
return Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
putExtra(Intent.EXTRA_TITLE, File(input).name)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
putExtra(DocumentsContract.EXTRA_INITIAL_URI,
DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Download")
)
}
}
}
override fun parseResult(
resultCode: Int,
intent: Intent?
): Uri? {
if (resultCode != Activity.RESULT_OK || intent == null) {
return null
}
return intent.data
}
}
File diff suppressed because it is too large. Load diff
@@ -5,7 +5,9 @@ import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.opengl.GLSurfaceView
import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLDisplay
import android.os.Build
import android.os.Bundle
import android.os.Environment
@@ -43,9 +45,6 @@ import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLDisplay
enum class DisplayMode {
DEFAULT, LIMITED, FULLSCREEN;
@@ -78,6 +77,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
private var mAspectRatio = 0.0f
private var mScreenZoom = 1.0f
private var mScreenZoomFit = false
private var mNativeFramePacing = false
override fun onCreate(savedInstanceState: Bundle?) {
setupUIState()
@@ -131,6 +131,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
})
mGLSurfaceView?.manualBackEvents = true
if (mNativeFramePacing) {
LauncherFix.initFramePacing(this)
}
}
private fun createVersionFile() {
@@ -171,8 +175,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
val gameVersionCode = GamePackageUtils.getGameVersionCode(packageManager)
if (gameVersionCode >= 39L) {
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
// this fix requires geode v3, which is 2.206+
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
LauncherFix.enableExceptionsRenaming()
@@ -183,9 +186,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
loadInternalMods()
val baseView = createView()
if (gameVersionCode >= 42L) {
mGLSurfaceView?.sendNewCocosEvents = true
}
if (!GeodeUtils.handleSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
val frameLayoutParams = ViewGroup.LayoutParams(
@@ -209,22 +209,12 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
left = 0, top = 0, right = 0, bottom = 0
)
}
Cocos2dxGLSurfaceView.onKeyboardInsets(insets)
WindowInsets.CONSUMED
}
frameLayout.addView(baseView)
setContentView(frameLayout)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
baseView.setOnApplyWindowInsetsListener { view, insets ->
Cocos2dxGLSurfaceView.onKeyboardInsets(insets)
return@setOnApplyWindowInsetsListener view.onApplyWindowInsets(insets)
}
}
setContentView(baseView)
}
@@ -505,16 +495,14 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val frameRate = mLimitedRefreshRate
if (frameRate != null) {
renderer.limitFrameRate(frameRate)
glSurfaceView.setMaxFrameRate(frameRate)
}
glSurfaceView.nativeFramePacing = mNativeFramePacing
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
glSurfaceView.cocos2dxEditText = editText
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
glSurfaceView.defaultFocusHighlightEnabled = false
}
return frameLayout
}
@@ -547,6 +535,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
}
mNativeFramePacing = preferenceUtils.getBoolean(PreferenceUtils.Key.NATIVE_FRAME_PACING)
if (displayMode == DisplayMode.FULLSCREEN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
GeodeUtils.handleSafeArea = true
@@ -580,6 +570,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
override fun onDestroy() {
super.onDestroy()
FMOD.close()
if (mNativeFramePacing) {
LauncherFix.destroyFramePacing()
}
}
private fun resumeGame() {
@@ -751,11 +745,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
}
class EGLConfigChooser : GLSurfaceView.EGLConfigChooser {
// this comes from EGL14, but is unavailable on EGL10
// also EGL14 is incompatible with EGL10. so whatever
private var EGL_OPENGL_ES2_BIT: Int = 0x04
class EGLConfigChooser : ExplicitGLSurfaceView.EGLConfigChooser {
private data class ConfigValues(
val redSize: Int,
val greenSize: Int,
@@ -765,29 +755,29 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val stencilSize: Int,
)
private fun getAttribValue(egl: EGL10, display: EGLDisplay, config: EGLConfig, attrib: Int): Int {
private fun getAttribValue(display: EGLDisplay, config: EGLConfig, attrib: Int): Int {
val value = IntArray(1)
egl.eglGetConfigAttrib(display, config, attrib, value)
EGL14.eglGetConfigAttrib(display, config, attrib, value, 0)
return value[0]
}
private fun testConfig(egl: EGL10, display: EGLDisplay, attrib: ConfigValues): EGLConfig? {
private fun testConfig(display: EGLDisplay, attrib: ConfigValues): EGLConfig? {
val attribList = intArrayOf(
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_RED_SIZE, attrib.redSize,
EGL10.EGL_GREEN_SIZE, attrib.greenSize,
EGL10.EGL_BLUE_SIZE, attrib.blueSize,
EGL10.EGL_ALPHA_SIZE, attrib.alphaSize,
EGL10.EGL_DEPTH_SIZE, attrib.depthSize,
EGL10.EGL_STENCIL_SIZE, attrib.stencilSize,
EGL10.EGL_NONE
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, attrib.redSize,
EGL14.EGL_GREEN_SIZE, attrib.greenSize,
EGL14.EGL_BLUE_SIZE, attrib.blueSize,
EGL14.EGL_ALPHA_SIZE, attrib.alphaSize,
EGL14.EGL_DEPTH_SIZE, attrib.depthSize,
EGL14.EGL_STENCIL_SIZE, attrib.stencilSize,
EGL14.EGL_NONE
)
val configs = arrayOfNulls<EGLConfig>(1)
val numConfigs = IntArray(1)
val res = egl.eglChooseConfig(display, attribList, configs, configs.size, numConfigs)
val res = EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.size, numConfigs, 0)
if (res && numConfigs[0] > 0 && configs[0] != null) {
return configs[0]
}
@@ -795,18 +785,18 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
return null
}
private fun printConfig(egl: EGL10, display: EGLDisplay, config: EGLConfig) {
val id = getAttribValue(egl, display, config, EGL10.EGL_CONFIG_ID)
val red = getAttribValue(egl, display, config, EGL10.EGL_RED_SIZE)
val green = getAttribValue(egl, display, config, EGL10.EGL_GREEN_SIZE)
val blue = getAttribValue(egl, display, config, EGL10.EGL_BLUE_SIZE)
val alpha = getAttribValue(egl, display, config, EGL10.EGL_ALPHA_SIZE)
val depth = getAttribValue(egl, display, config, EGL10.EGL_DEPTH_SIZE)
val stencil = getAttribValue(egl, display, config, EGL10.EGL_STENCIL_SIZE)
private fun printConfig(display: EGLDisplay, config: EGLConfig) {
val id = getAttribValue(display, config, EGL14.EGL_CONFIG_ID)
val red = getAttribValue(display, config, EGL14.EGL_RED_SIZE)
val green = getAttribValue(display, config, EGL14.EGL_GREEN_SIZE)
val blue = getAttribValue(display, config, EGL14.EGL_BLUE_SIZE)
val alpha = getAttribValue(display, config, EGL14.EGL_ALPHA_SIZE)
val depth = getAttribValue(display, config, EGL14.EGL_DEPTH_SIZE)
val stencil = getAttribValue(display, config, EGL14.EGL_STENCIL_SIZE)
println("EGLConfig $id: (red = $red, green = $green, blue = $blue, alpha = $alpha, depth = $depth, stencil = $stencil)")
}
override fun chooseConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
override fun chooseConfig(display: EGLDisplay): EGLConfig {
val configs = listOf(
ConfigValues(8, 8, 8, 8, 16, 8),
ConfigValues(8, 8, 8, 0, 16, 8),
@@ -815,9 +805,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
)
return configs.firstNotNullOf {
val config = testConfig(egl, display, it)
val config = testConfig(display, it)
if (config != null) {
printConfig(egl, display, config)
printConfig(display, config)
}
config
@@ -1,5 +1,7 @@
package com.geode.launcher
import android.app.Activity
import android.view.Surface
import androidx.annotation.Keep
import com.geode.launcher.utils.Constants
@@ -16,4 +18,10 @@ object LauncherFix {
external fun enableExceptionsRenaming()
external fun performPatches()
external fun initFramePacing(activity: Activity)
external fun destroyFramePacing()
external fun swapFrame(display: Long, surface: Long): Boolean
external fun setSurface(surface: Surface?)
external fun setSwapInterval(intervalNs: Long)
}
@@ -104,10 +104,8 @@ class MainActivity : ComponentActivity() {
val gdInstalled = GamePackageUtils.isGameInstalled(packageManager)
val geodeInstalled = LaunchUtils.isGeodeInstalled(this)
val restartImmediately = intent.getBooleanExtra("restarted", false)
if (gdInstalled && geodeInstalled && restartImmediately) {
val withSafeMode = intent.getBooleanExtra("safe-mode", false)
onLaunch(this, withSafeMode)
if (gdInstalled && geodeInstalled && intent.getBooleanExtra("restarted", false)) {
onLaunch(this)
}
enableEdgeToEdge()
@@ -10,10 +10,7 @@ import android.provider.DocumentsContract
import android.provider.DocumentsProvider
import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi
import com.geode.launcher.utils.ApplicationIcon
import com.geode.launcher.utils.IconUtils
import com.geode.launcher.utils.LaunchUtils
import com.geode.launcher.utils.PreferenceUtils
import java.io.File
private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf(
@@ -63,17 +60,8 @@ class UserDirectoryProvider : DocumentsProvider() {
DocumentsContract.Root.FLAG_SUPPORTS_RECENTS or
DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
)
val selectedIconKey = PreferenceUtils.get(context).getString(PreferenceUtils.Key.SELECTED_ICON)
?: "geode"
val selectedIcon = ApplicationIcon.fromId(selectedIconKey)
val iconDetails = IconUtils.getIconDetails(selectedIcon)
val nameId = if (selectedIcon == ApplicationIcon.SAPPHIRE) R.string.application_icon_sapphire else R.string.app_name
val logoId = if (selectedIcon == ApplicationIcon.GEODE) R.drawable.geode_logo else iconDetails.iconId
add(DocumentsContract.Root.COLUMN_TITLE, context.getString(nameId))
add(DocumentsContract.Root.COLUMN_ICON, logoId)
add(DocumentsContract.Root.COLUMN_TITLE, context.getString(R.string.app_name))
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.geode_logo)
}
return result
@@ -408,8 +408,7 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
}
}
// there's like a single pixel of the shadow that gets clipped without this...
ElevatedCard(modifier = modifier.padding(bottom = 1.dp)) {
ElevatedCard(modifier) {
Column(
// buttons add enough padding already, lower to compensate
modifier = Modifier.padding(
@@ -169,6 +169,12 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
}
)
SettingsCard(
title = stringResource(R.string.preference_native_pacing),
description = stringResource(R.string.preference_native_pacing_description),
preferenceKey = PreferenceUtils.Key.NATIVE_FRAME_PACING,
)
/*
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
SettingsCard(
@@ -53,7 +53,6 @@ object GamePackageUtils {
39L to "2.206",
40L to "2.2074", // ok
41L to "2.2081",
42L to "2.209",
)
/**
@@ -191,16 +191,10 @@ object GeodeUtils {
@JvmStatic
fun restartGame() {
restartGame(false)
}
@JvmStatic
fun restartGame(safeMode: Boolean) {
activity.get()?.run {
packageManager.getLaunchIntentForPackage(packageName)?.also {
val mainIntent = Intent.makeRestartActivityTask(it.component)
mainIntent.putExtra("restarted", true)
mainIntent.putExtra("safe-mode", safeMode)
startActivity(mainIntent)
exitProcess(0)
}
@@ -800,7 +794,7 @@ object GeodeUtils {
external fun internalKeyEvent(timestamp: Long, deviceId: Int, eventSource: Int, keyCode: Int, modifiers: Int, isDown: Boolean, repeatCount: Int)
external fun internalTouchEvent(timestamp: Long, deviceId: Int, eventSource: Int, eventType: Int, ids: IntArray, xs: FloatArray, ys: FloatArray)
external fun internalScrollEvent(timestamp: Long, deviceId: Int, eventSource: Int, scrollX: Float, scrollY: Float, mouseX: Float, mouseY: Float)
external fun internalScrollEvent(timestamp: Long, deviceId: Int, eventSource: Int, scrollX: Float, scrollY: Float)
external fun internalJoystickEvent(timestamp: Long, deviceId: Int, eventSource: Int, leftX: FloatArray, leftY: FloatArray, rightX: FloatArray, rightY: FloatArray, hatX: FloatArray, hatY: FloatArray, leftTrigger: FloatArray, rightTrigger: FloatArray)
external fun inputDeviceAdded(deviceId: Int)
@@ -44,7 +44,6 @@ enum class ApplicationIcon {
GEODE,
PRIDE,
TRANS,
GREEN,
SAPPHIRE;
companion object {
@@ -54,7 +53,6 @@ enum class ApplicationIcon {
"pride" -> PRIDE
"trans" -> TRANS
"sapphire" -> SAPPHIRE
"green" -> GREEN
else -> DEFAULT
}
}
@@ -65,7 +63,6 @@ enum class ApplicationIcon {
DEFAULT -> "default"
TRANS -> "trans"
SAPPHIRE -> "sapphire"
GREEN -> "green"
}
}
@@ -108,11 +105,5 @@ object IconUtils {
iconId = R.mipmap.ic_launcher_sapphire,
nameId = R.string.application_icon_sapphire,
)
ApplicationIcon.GREEN -> ApplicationIconDetails(
id = id.toId(),
component = "MainActivityGreen",
iconId = R.mipmap.ic_launcher_green,
nameId = R.string.application_icon_green,
)
}
}
@@ -150,6 +150,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
LAST_LAUNCHER_UPDATE,
DISABLE_UPDATE_CACHE,
PREBUNDLED_GEODE_MIGRATION_PERFORMED,
NATIVE_FRAME_PACING,
}
private fun defaultValueForBooleanKey(key: Key): Boolean {
@@ -201,6 +202,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
Key.LAST_LAUNCHER_UPDATE -> "PreferenceLastLauncherUpdate"
Key.DISABLE_UPDATE_CACHE -> "PreferenceDisableUpdateCache"
Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED -> "PreferencePrebundledGeodeMigrationPerformed"
Key.NATIVE_FRAME_PACING -> "PreferenceNativeFramePacing"
// Key.RELEASE_CHANNEL -> "PreferenceReleaseChannel"
// Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
// Key.FORCE_HRR -> "PreferenceForceHighRefreshRate"
@@ -8,73 +8,32 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.text.InputType
import android.util.Log
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.Surface
import android.view.WindowInsets
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatEditText
import com.customRobTop.BaseRobTopActivity
import com.geode.launcher.ExplicitGLSurfaceView
import com.geode.launcher.LauncherFix
import com.geode.launcher.utils.GeodeUtils
import kotlin.math.abs
import kotlin.math.roundToLong
private const val HANDLER_OPEN_IME_KEYBOARD = 2
private const val HANDLER_CLOSE_IME_KEYBOARD = 3
private const val MS_TO_NS = 1_000_000
private const val SEC_TO_NS = 1_000_000_000.0
fun inputTypeForFlags(i: Int): Int {
if ((i and BaseRobTopActivity.KEYBOARD_INPUT_NUMERIC) != 0) {
return InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED or InputType.TYPE_CLASS_NUMBER
}
if ((i and BaseRobTopActivity.KEYBOARD_INPUT_PASSWORD) != 0) {
return InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
}
if ((i and BaseRobTopActivity.KEYBOARD_INPUT_MULTILINE) != 0) {
return InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or InputType.TYPE_CLASS_TEXT
}
return InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_CLASS_TEXT
}
fun usesInlineKeyboard(): Boolean {
return Build.VERSION.SDK_INT >= 30
}
fun imeOptionsForFlags(i: Int): Int {
val i2 = if ((i and BaseRobTopActivity.KEYBOARD_INPUT_MULTILINE) == 0 || !usesInlineKeyboard())
EditorInfo.IME_ACTION_DONE else EditorInfo.IME_ACTION_NONE
return if (usesInlineKeyboard())
EditorInfo.IME_FLAG_NO_FULLSCREEN or EditorInfo.IME_FLAG_NO_EXTRACT_UI or i2
else i2
}
class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
companion object {
lateinit var cocos2dxGLSurfaceView: Cocos2dxGLSurfaceView
private lateinit var handler: Handler
private lateinit var cocos2dxTextInputWrapper: Cocos2dxTextInputWrapper
private var keyboardVisible = false
private var keyboardHeight = 0
private var keyboardHideCheck = Runnable {
if (keyboardVisible) {
return@Runnable
}
cocos2dxGLSurfaceView.queueEvent {
cocos2dxGLSurfaceView.cocos2dxRenderer.handleTextClosed()
}
}
@JvmStatic
fun openIMEKeyboard() {
val msg = Message()
@@ -94,30 +53,6 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
cocos2dxGLSurfaceView.queueEvent {
Cocos2dxAccelerometer.onSensorChanged(x, y, z, timestamp)
}
@RequiresApi(Build.VERSION_CODES.R)
fun onKeyboardInsets(windowInsets: WindowInsets) {
if (!cocos2dxGLSurfaceView.sendNewCocosEvents) {
return
}
val visible = windowInsets.isVisible(WindowInsets.Type.ime())
val offsetHeight = if (visible) windowInsets.getInsets(WindowInsets.Type.ime()).bottom else 0
if (visible == keyboardVisible && offsetHeight == keyboardHeight) {
return
}
keyboardVisible = visible
keyboardHeight = offsetHeight
handler.removeCallbacks(keyboardHideCheck)
if (!visible) {
handler.postDelayed(keyboardHideCheck, 150L)
} else {
cocos2dxGLSurfaceView.cocos2dxRenderer.handleKeyboardWillShow(keyboardHeight)
}
}
}
private lateinit var cocos2dxRenderer: Cocos2dxRenderer
@@ -132,9 +67,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
var controllerEventsEnabled = false
private set
var supportsResizeEvents = false
// 2.209+ events for keyboard and such
var sendNewCocosEvents = false
var saveNativeFramePacing = false
var cocos2dxEditText: AppCompatEditText? = null
set(value) {
@@ -156,13 +89,9 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
HANDLER_OPEN_IME_KEYBOARD -> {
if (cocos2dxEditText?.requestFocus() == true) {
cocos2dxEditText?.apply {
removeTextChangedListener(cocos2dxTextInputWrapper)
if (sendNewCocosEvents) {
inputType = inputTypeForFlags(BaseRobTopActivity.curKeyboardInputFlags)
imeOptions = imeOptionsForFlags(BaseRobTopActivity.curKeyboardInputFlags)
}
removeTextChangedListener(
cocos2dxTextInputWrapper
)
setText("")
val text = msg.obj as String
append(text)
@@ -175,13 +104,16 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
cocos2dxEditText,
0
)
Log.d("GLSurfaceView", "showSoftInput")
return
}
return
}
HANDLER_CLOSE_IME_KEYBOARD -> {
cocos2dxEditText?.apply {
removeTextChangedListener(cocos2dxTextInputWrapper)
removeTextChangedListener(
cocos2dxTextInputWrapper
)
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(
windowToken,
0
@@ -189,10 +121,8 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
}
requestFocus()
queueEvent {
Log.d("GLSurfaceView", "HideSoftInput")
cocos2dxRenderer.handleTextClosed()
}
return
}
else -> return
@@ -213,6 +143,10 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
println("updateRefreshRate: selecting refresh rate of ${chosenDisplay.refreshRate} (display ${chosenDisplay.modeId})")
if (saveNativeFramePacing) {
LauncherFix.setSwapInterval((1_000_000_000L / chosenRefreshRate).roundToLong())
}
holder.surface.setFrameRate(chosenRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
if (isAttachedToWindow) {
(context as Activity).window.attributes.preferredRefreshRate = chosenRefreshRate
@@ -227,6 +161,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
override fun onResume() {
super.onResume()
renderMode = RENDERMODE_CONTINUOUSLY
queueEvent { cocos2dxRenderer.handleOnResume() }
}
@@ -371,97 +306,6 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
}
}
private fun queueKeyboardEvent(keyCode: Int, event: KeyEvent, down: Boolean): Boolean {
val keypadCode = keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BACK
if (event.isSystem && !keypadCode) {
return false
}
if (keypadCode && BaseRobTopActivity.blockBackButton) {
return true
}
queueEvent {
sendNextEventTimestamp(event.eventTime * MS_TO_NS)
}
if (useKeyboardEvents) {
if (down) {
queueEvent {
GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0)
}
} else {
queueEvent {
GeodeUtils.nativeKeyUp(keyCode, event.modifiers)
}
}
}
if (controllerEventsEnabled) {
queueEvent {
GeodeUtils.internalKeyEvent(
event.eventTime * MS_TO_NS,
event.deviceId,
event.source,
event.keyCode,
event.modifiers,
down,
event.repeatCount
)
}
}
// geode events override native callbacks
if (useKeyboardEvents || controllerEventsEnabled) {
return true
}
if (sendNewCocosEvents) {
if (KeyEvent.isGamepadButton(keyCode)) {
return false
}
if (keyCode == KeyEvent.KEYCODE_ESCAPE && BaseRobTopActivity.blockBackButton) {
return true
}
// messy logic
val isRepeat = event.repeatCount > 0
if (keypadCode) {
if (down) {
if (isRepeat || BaseRobTopActivity.blockBackButton) {
return true
}
queueEvent {
cocos2dxRenderer.handleKeyDown(keyCode)
}
return true
} else {
return false
}
}
val eventTimestamp = event.eventTime / 1000.0
val shiftPressed = event.isShiftPressed
val ctrlPressed = event.isCtrlPressed
val altPressed = event.isAltPressed
queueEvent {
cocos2dxRenderer.handleKeyboardEvent(
keyCode, down, isRepeat, eventTimestamp,
shiftPressed, ctrlPressed, altPressed
)
}
return true
}
return false
}
private fun legacyKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_MENU -> {
@@ -519,21 +363,67 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (!queueKeyboardEvent(keyCode, event, true)) {
if (!useKeyboardEvents && !controllerEventsEnabled) {
return legacyKeyDown(keyCode, event)
}
return when (keyCode) {
// ignore system keys
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_MUTE -> {
super.onKeyDown(keyCode, event)
}
else -> {
if (BaseRobTopActivity.blockBackButton) {
return true
}
queueEvent {
sendNextEventTimestamp(event.eventTime * MS_TO_NS)
if (useKeyboardEvents) {
GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0)
}
if (controllerEventsEnabled) {
GeodeUtils.internalKeyEvent(event.eventTime * MS_TO_NS, event.deviceId, event.source, event.keyCode, event.modifiers, true, event.repeatCount)
}
}
true
}
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (!queueKeyboardEvent(keyCode, event, false)) {
if (!useKeyboardEvents && !controllerEventsEnabled) {
return super.onKeyUp(keyCode, event)
}
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_MUTE -> {
super.onKeyUp(keyCode, event)
}
else -> {
if (event.repeatCount != 0 || BaseRobTopActivity.blockBackButton) {
return true
}
queueEvent {
sendNextEventTimestamp(event.eventTime * MS_TO_NS)
if (useKeyboardEvents) {
GeodeUtils.nativeKeyUp(keyCode, event.modifiers)
}
if (controllerEventsEnabled) {
GeodeUtils.internalKeyEvent(event.eventTime * MS_TO_NS, event.deviceId, event.source, event.keyCode, event.modifiers, false, event.repeatCount)
}
}
true
}
}
}
// i'm lazy.
// https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
private fun getCenteredAxis(
@@ -594,15 +484,11 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
}
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
val hasGeodeEvents = useKeyboardEvents || controllerEventsEnabled
if (!hasGeodeEvents && !sendNewCocosEvents) {
if (!useKeyboardEvents && !controllerEventsEnabled) {
return super.onGenericMotionEvent(event)
}
if (event?.action == MotionEvent.ACTION_SCROLL) {
val mouseX = event.x
val mouseY = event.y
val scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL)
val scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
val timestamp = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -616,11 +502,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
}
if (controllerEventsEnabled) {
GeodeUtils.internalScrollEvent(timestamp, event.deviceId, event.source, scrollX, scrollY, mouseX, mouseY)
}
if (!hasGeodeEvents && sendNewCocosEvents) {
cocos2dxRenderer.handleScroll(mouseX, mouseY, scrollY, scrollX)
GeodeUtils.internalScrollEvent(timestamp, event.deviceId, event.source, scrollX, scrollY)
}
}
@@ -660,6 +542,11 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
setRenderer(this.cocos2dxRenderer)
}
override fun setNativeFramePacing(nativeFramePacing: Boolean) {
super.setNativeFramePacing(nativeFramePacing)
saveNativeFramePacing = nativeFramePacing
}
private fun getContentText(): String {
return cocos2dxRenderer.getContentText()
}
@@ -694,6 +581,10 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
inputManager.unregisterInputDeviceListener(InputListener)
}
fun setMaxFrameRate(rate: Int) {
cocos2dxRenderer.limitFrameRate(rate)
}
object InputListener : InputManager.InputDeviceListener {
override fun onInputDeviceAdded(deviceId: Int) {
GeodeUtils.inputDeviceAdded(deviceId)
@@ -1,15 +1,14 @@
package org.cocos2dx.lib
import android.opengl.GLSurfaceView
import android.opengl.EGLConfig
import android.os.Build
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import com.geode.launcher.ExplicitGLSurfaceView
private const val NANOSECONDS_PER_SECOND = 1000000000L
private const val NANOSECONDS_PER_MICROSECOND = 1000000L
@Suppress("unused", "KotlinJniMissingFunction")
class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceView.Renderer {
class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : ExplicitGLSurfaceView.Renderer {
companion object {
@JvmStatic
fun setAnimationInterval(@Suppress("UNUSED_PARAMETER") animationInterval: Double) {
@@ -28,29 +27,12 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
@JvmStatic
private external fun nativeKeyDown(keyCode: Int): Boolean
@JvmStatic
private external fun nativeKeyboardEvent(
keyCode: Int,
down: Boolean,
isRepeat: Boolean,
timestamp: Double,
shiftPressed: Boolean,
ctrlPressed: Boolean,
altPressed: Boolean
)
@JvmStatic
private external fun nativeKeyboardWillShow(offset: Int)
@JvmStatic
private external fun nativeOnPause()
@JvmStatic
private external fun nativeOnResume()
@JvmStatic
private external fun nativeScroll(x: Float, y: Float, scrollY: Float, scrollX: Float)
@JvmStatic
private external fun nativeRender()
@@ -97,12 +79,12 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
screenHeight = surfaceHeight
}
override fun onSurfaceCreated(gl10: GL10?, eglConfig: EGLConfig?) {
override fun onSurfaceCreated(eglConfig: EGLConfig?) {
nativeInit(screenWidth, screenHeight)
lastTickInNanoSeconds = System.nanoTime()
}
override fun onSurfaceChanged(gl10: GL10?, width: Int, height: Int) {
override fun onSurfaceChanged(width: Int, height: Int) {
if (setFrameRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
handler.updateRefreshRate()
}
@@ -110,7 +92,7 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
println("renderer surfaceChanged: ${width}x${height}@${handler.display.refreshRate}fps")
}
override fun onDrawFrame(gl: GL10?) {
override fun onDrawFrame() {
val animationInterval = mAnimationInterval
if (animationInterval == null) {
nativeRender()
@@ -150,18 +132,6 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
nativeKeyDown(keyCode)
}
fun handleKeyboardWillShow(offset: Int) {
nativeKeyboardWillShow(offset)
}
fun handleKeyboardEvent(keyCode: Int, down: Boolean, isRepeat: Boolean, timestamp: Double, shiftPressed: Boolean, ctrlPressed: Boolean, altPressed: Boolean) {
nativeKeyboardEvent(keyCode, down, isRepeat, timestamp, shiftPressed, ctrlPressed, altPressed)
}
fun handleScroll(x: Float, y: Float, scrollY: Float, scrollX: Float) {
nativeScroll(x, y, scrollY, scrollX)
}
fun handleOnPause() {
nativeOnPause()
}
@@ -5,10 +5,8 @@ import android.text.TextWatcher
import android.widget.TextView.OnEditorActionListener
import android.text.Editable
import android.view.KeyEvent
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import com.customRobTop.JniToCpp
class Cocos2dxTextInputWrapper(private val cocos2dxGLSurfaceView: Cocos2dxGLSurfaceView) : TextWatcher, OnEditorActionListener {
private var originText: String? = null
@@ -21,33 +19,21 @@ class Cocos2dxTextInputWrapper(private val cocos2dxGLSurfaceView: Cocos2dxGLSurf
this.originText = originText
}
override fun afterTextChanged(editable: Editable) {
if (isFullScreenEdit) {
return
}
val cText = text ?: return
val str = editable.toString()
val unchanged = str.commonPrefixWith(cText)
var unchangedUntil = unchanged.length
if (unchangedUntil > 0 && unchangedUntil < str.length && Character.isLowSurrogate(str[unchangedUntil])) {
unchangedUntil--
}
val codePointLength = cText.codePointCount(unchangedUntil, cText.length)
repeat(codePointLength) {
cocos2dxGLSurfaceView.deleteBackward()
}
if (unchangedUntil < str.length) {
override fun afterTextChanged(s: Editable) {
if (!isFullScreenEdit) {
var nModified = s.length - text!!.length
if (nModified > 0) {
cocos2dxGLSurfaceView.insertText(
str.substring(unchangedUntil)
s.subSequence(text!!.length, s.length).toString()
)
} else {
while (nModified < 0) {
cocos2dxGLSurfaceView.deleteBackward()
nModified++
}
}
text = s.toString()
}
text = str
}
override fun beforeTextChanged(
@@ -61,45 +47,22 @@ class Cocos2dxTextInputWrapper(private val cocos2dxGLSurfaceView: Cocos2dxGLSurf
override fun onTextChanged(pCharSequence: CharSequence, start: Int, before: Int, count: Int) {}
override fun onEditorAction(textView: TextView, actionID: Int, keyEvent: KeyEvent?): Boolean {
val fullscreenIme = cocos2dxGLSurfaceView.cocos2dxEditText == textView && isFullScreenEdit
if (fullscreenIme) {
var text = textView.text.toString()
if (originText != text || !cocos2dxGLSurfaceView.sendNewCocosEvents) {
if (cocos2dxGLSurfaceView.sendNewCocosEvents) {
cocos2dxGLSurfaceView.queueEvent {
JniToCpp.textInputCaretToEnd()
}
}
repeat(originText!!.length) {
if (cocos2dxGLSurfaceView.cocos2dxEditText == textView && isFullScreenEdit) {
for (i in originText!!.length downTo 1) {
cocos2dxGLSurfaceView.deleteBackward()
}
if (!cocos2dxGLSurfaceView.sendNewCocosEvents) {
var text = textView.text.toString()
if (!text.endsWith('\n')) {
text += '\n'
}
}
if (text.isNotEmpty()) {
cocos2dxGLSurfaceView.insertText(text)
}
if (actionID != 6) {
return false
}
}
if (fullscreenIme || actionID == EditorInfo.IME_ACTION_DONE) {
if (cocos2dxGLSurfaceView.sendNewCocosEvents) {
cocos2dxGLSurfaceView.queueEvent {
JniToCpp.textInputDone()
}
}
}
if (actionID == EditorInfo.IME_ACTION_DONE) {
cocos2dxGLSurfaceView.requestFocus()
Cocos2dxGLSurfaceView.closeIMEKeyboard()
}
return false
}
}
@@ -1,28 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1366"
android:viewportHeight="1366">
<group>
android:viewportWidth="1024"
android:viewportHeight="1024">
<group android:scaleX="0.75" android:scaleY="0.75" android:translateX="128" android:translateY="128">
<clip-path
android:pathData="M0,0h1366v1366h-1366z"/>
android:pathData="M0,0h1024v1024h-1024z"/>
<path
android:pathData="M0,0h1366v1366h-1366z"
android:pathData="M0,0h1024v1024h-1024z"
android:fillColor="#F4D48E"/>
<path
android:pathData="M598,683m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:pathData="M512,512m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:fillColor="#F4D48E"/>
<path
android:pathData="M873.8,459.7C752.5,412.3 642.7,323 397.2,323C151.8,323 18.6,418.7 4.3,463.8C-10.1,508.8 4.7,562.4 -1.7,561.4L-25,878H1382L1373,631.1C1346.8,597.8 1260.8,569.9 1217.2,562.4C1173.6,554.9 996.8,507.8 873.8,459.7Z"
android:pathData="M719.5,289C618,241.7 526,152.5 320.5,152.5C115,152.5 3.5,248 -8.5,293C-20.5,338 -8.2,391.5 -13.5,390.5L-33,706.5H1145L1137.5,460C1115.5,426.8 1043.5,399 1007,391.5C970.5,384 822.5,337 719.5,289Z"
android:fillColor="#F4A47B"/>
<path
android:pathData="M647.9,685.4C569.9,647.4 412.4,573 277.3,573C142.1,573 80.5,611.7 39.1,632.4C-2.4,653.2 -9.5,656.1 -14.9,657.4L-25,978.5L650,985L647.9,685.4Z"
android:pathData="M561.5,514.5C488.5,476.5 341,402 214.5,402C88,402 30.3,440.7 -8.5,461.5C-47.3,482.3 -54,485.2 -59,486.5L-68.5,808L563.5,814.5L561.5,514.5Z"
android:fillColor="#E27880"/>
<path
android:pathData="M1362.1,728.7C1327,696.7 1194.3,624.7 888.5,623C725.4,622.1 492.3,679.9 354.7,757.1C228.9,827.6 68.9,867 -5.2,873.5L-152,1140H1583L1362.1,728.7Z"
android:pathData="M1020.5,551C994,521.2 893.7,454.1 662.5,452.5C539.2,451.6 363,505.6 259,577.5C163.9,643.2 43,680 -13,686L-124,934.5H1187.5L1020.5,551Z"
android:fillColor="#B9588F"/>
<path
android:pathData="M1107,1027.2C885.5,1027.2 739,943.6 544.9,943.6C350.9,943.6 228.8,1031.7 186.1,1040.2L-8,1120.8V1385L1376,1379L1367,909C1361.5,929 1287.6,1027.2 1107,1027.2Z"
android:pathData="M819.5,856C638,856 518,772.5 359,772.5C200,772.5 100,860.5 65,869L-94,949.5V1041.5L1177.5,1025.5L1032.5,738C1028,758 967.4,856 819.5,856Z"
android:fillColor="#714A9A"/>
</group>
</vector>
@@ -1,29 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1366"
android:viewportHeight="1366">
<group>
<clip-path
android:pathData="M0,0h1366v1366h-1366z"/>
<path
android:pathData="M0,0h1366v1366h-1366z"
android:fillColor="#9EF4C3"/>
<path
android:pathData="M598,683m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:fillColor="#9EF4C3"/>
<path
android:pathData="M873.8,459.7C752.5,412.3 642.7,323 397.2,323C151.8,323 18.6,418.7 4.3,463.8C-10.1,508.8 4.7,562.4 -1.7,561.4L-25,878H1382L1373,631.1C1346.8,597.8 1260.8,569.9 1217.2,562.4C1173.6,554.9 996.8,507.8 873.8,459.7Z"
android:fillColor="#6DDBAD"/>
<path
android:pathData="M647.9,685.4C569.9,647.4 412.4,573 277.3,573C142.1,573 80.5,611.7 39.1,632.4C-2.4,653.2 -9.5,656.1 -14.9,657.4L-25,978.5L650,985L647.9,685.4Z"
android:fillColor="#64C780"/>
<path
android:pathData="M1362.1,728.7C1327,696.7 1194.3,624.7 888.5,623C725.4,622.1 492.3,679.9 354.7,757.1C228.9,827.6 68.9,867 -5.2,873.5L-152,1140H1583L1362.1,728.7Z"
android:fillColor="#46A47D"/>
<path
android:pathData="M1107,1027.2C885.5,1027.2 739,943.6 544.9,943.6C350.9,943.6 228.8,1031.7 186.1,1040.2L-8,1120.8V1385L1376,1379L1367,909C1361.5,929 1287.6,1027.2 1107,1027.2Z"
android:fillColor="#417563"/>
</group>
</vector>
@@ -1,29 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1366"
android:viewportHeight="1366">
<group>
android:viewportWidth="1024"
android:viewportHeight="1024">
<group android:scaleX="0.75" android:scaleY="0.75" android:translateX="128" android:translateY="128">
<clip-path
android:pathData="M0,0h1366v1366h-1366z"/>
android:pathData="M0,0h1024v1024h-1024z"/>
<path
android:pathData="M0,0h1366v1366h-1366z"
android:pathData="M0,0h1024v1024h-1024z"
android:fillColor="#CC7C7C"/>
<path
android:pathData="M598,683m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:pathData="M512,512m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:fillColor="#CC7C7C"/>
<path
android:pathData="M873.8,459.7C752.5,412.3 642.7,323 397.2,323C151.8,323 18.6,418.7 4.3,463.8C-10.1,508.8 4.7,562.4 -1.7,561.4L-25,878H1382L1373,631.1C1346.8,597.8 1260.8,569.9 1217.2,562.4C1173.6,554.9 996.8,507.8 873.8,459.7Z"
android:pathData="M719.5,289C618,241.7 526,152.5 320.5,152.5C115,152.5 3.5,248 -8.5,293C-20.5,338 -8.2,391.5 -13.5,390.5L-33,706.5H1145L1137.5,460C1115.5,426.8 1043.5,399 1007,391.5C970.5,384 822.5,337 719.5,289Z"
android:fillColor="#C6B471"/>
<path
android:pathData="M647.9,685.4C569.9,647.4 412.4,573 277.3,573C142.1,573 80.5,611.7 39.1,632.4C-2.4,653.2 -9.5,656.1 -14.9,657.4L-25,978.5L650,985L647.9,685.4Z"
android:pathData="M561.5,514.5C488.5,476.5 341,402 214.5,402C88,402 30.3,440.7 -8.5,461.5C-47.3,482.3 -54,485.2 -59,486.5L-68.5,808L563.5,814.5L561.5,514.5Z"
android:fillColor="#59AF7F"/>
<path
android:pathData="M1362.1,728.7C1327,696.7 1194.3,624.7 888.5,623C725.4,622.1 492.3,679.9 354.7,757.1C228.9,827.6 68.9,867 -5.2,873.5L-152,1140H1583L1362.1,728.7Z"
android:pathData="M1020.5,551C994,521.2 893.7,454.1 662.5,452.5C539.2,451.6 363,505.6 259,577.5C163.9,643.2 43,680 -13,686L-124,934.5H1187.5L1020.5,551Z"
android:fillColor="#5F8CAB"/>
<path
android:pathData="M1107,1027.2C885.5,1027.2 739,943.6 544.9,943.6C350.9,943.6 228.8,1031.7 186.1,1040.2L-8,1120.8V1385L1376,1379L1367,909C1361.5,929 1287.6,1027.2 1107,1027.2Z"
android:pathData="M819.5,856C638,856 518,772.5 359,772.5C200,772.5 100,860.5 65,869L-94,949.5V1041.5L1177.5,1025.5L1032.5,738C1028,758 967.4,856 819.5,856Z"
android:fillColor="#70558D"/>
</group>
</vector>
@@ -1,29 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1366"
android:viewportHeight="1366">
<group>
android:viewportWidth="1024"
android:viewportHeight="1024">
<group android:scaleX="0.75" android:scaleY="0.75" android:translateX="128" android:translateY="128">
<clip-path
android:pathData="M0,0h1366v1366h-1366z"/>
android:pathData="M0,0h1024v1024h-1024z"/>
<path
android:pathData="M0,0h1366v1366h-1366z"
android:pathData="M0,0h1024v1024h-1024z"
android:fillColor="#65B8C2"/>
<path
android:pathData="M598,683m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:pathData="M512,512m-512,0a512,512 0,1 1,1024 0a512,512 0,1 1,-1024 0"
android:fillColor="#65B8C2"/>
<path
android:pathData="M873.8,459.7C752.5,412.3 642.7,323 397.2,323C151.8,323 18.6,418.7 4.3,463.8C-10.1,508.8 4.7,562.4 -1.7,561.4L-25,878H1382L1373,631.1C1346.8,597.8 1260.8,569.9 1217.2,562.4C1173.6,554.9 996.8,507.8 873.8,459.7Z"
android:pathData="M719.5,289C618,241.7 526,152.5 320.5,152.5C115,152.5 3.5,248 -8.5,293C-20.5,338 -8.2,391.5 -13.5,390.5L-33,706.5H1145L1137.5,460C1115.5,426.8 1043.5,399 1007,391.5C970.5,384 822.5,337 719.5,289Z"
android:fillColor="#DEA5CD"/>
<path
android:pathData="M647.9,685.4C569.9,647.4 412.4,573 277.3,573C142.1,573 80.5,611.7 39.1,632.4C-2.4,653.2 -9.5,656.1 -14.9,657.4L-25,978.5L650,985L647.9,685.4Z"
android:pathData="M561.5,514.5C488.5,476.5 341,402 214.5,402C88,402 30.3,440.7 -8.5,461.5C-47.3,482.3 -54,485.2 -59,486.5L-68.5,808L563.5,814.5L561.5,514.5Z"
android:fillColor="#DEE8EA"/>
<path
android:pathData="M1362.1,728.7C1327,696.7 1194.3,624.7 888.5,623C725.4,622.1 492.3,679.9 354.7,757.1C228.9,827.6 68.9,867 -5.2,873.5L-152,1140H1583L1362.1,728.7Z"
android:pathData="M1020.5,551C994,521.2 893.7,454.1 662.5,452.5C539.2,451.6 363,505.6 259,577.5C163.9,643.2 43,680 -13,686L-124,934.5H1187.5L1020.5,551Z"
android:fillColor="#C485BD"/>
<path
android:pathData="M1107,1027.2C885.5,1027.2 739,943.6 544.9,943.6C350.9,943.6 228.8,1031.7 186.1,1040.2L-8,1120.8V1385L1376,1379L1367,909C1361.5,929 1287.6,1027.2 1107,1027.2Z"
android:pathData="M819.5,856C638,856 518,772.5 359,772.5C200,772.5 100,860.5 65,869L-94,949.5V1041.5L1177.5,1025.5L1032.5,738C1028,758 967.4,856 819.5,856Z"
android:fillColor="#259CB4"/>
</group>
</vector>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_green_background"/>
<foreground android:drawable="@drawable/ic_launcher_monochrome_shadow"/>
</adaptive-icon>
+3 -1
View File
@@ -298,6 +298,9 @@
<string name="preference_profiles_delete_alt">Delete</string>
<string name="preference_profiles_delete_undo_alt">Undo</string>
<string name="preference_native_pacing">Native frame pacing</string>
<string name="preference_native_pacing_description">Reduces latency, but may worsen frame drops.</string>
<!-- ApplicationLogsActivity strings -->
<string name="title_activity_application_logs">Application logs</string>
<string name="application_logs_no_logs">No logs available.</string>
@@ -338,7 +341,6 @@
<string name="application_icon_pride">Pride</string>
<string name="application_icon_trans">Transgender</string>
<string name="application_icon_sapphire">Sapphire</string>
<string name="application_icon_green">Android</string>
<string name="application_icon_confirm">Change icon to %1$s?</string>
<string name="application_icon_confirm_description">Geode Launcher will close itself to change the application icon.</string>
+4 -4
View File
@@ -1,9 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "9.4.1" apply false
id("com.android.library") version "9.4.1" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.4.21" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.4.21" apply false
id("com.android.application") version "9.4.0" apply false
id("com.android.library") version "9.4.0" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.4.10" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.4.10" apply false
}
tasks.register<Delete>("clean") {