[NOT FOR MERGE] Add opt-in virtual mouse pointer for touch devices

Provide a trackpad-style on-screen cursor for touch environments (mainly
Android), where touching the screen previously jumped the pointer to the
touched location (absolute) and SDL hardware cursors are invisible.

When enabled, a finger drag moves an arrow cursor relatively, a tap
left-clicks (held briefly so polling games detect it), two fingers
right-click, and a stationary long press starts a left-button drag. Real
mouse motion is ignored (including the spurious startup (0,0) event), and
program-driven cursor moves are followed via the internal pointer location
instead of warping the OS cursor.

The feature is disabled by default and enabled via the -virtualpointer
command-line option, the virtualpointer profile setting, or the "Virtual
mouse pointer" toggle in the Android launcher (persisted in
SharedPreferences and passed to the engine as -virtualpointer).

The gesture handling lives in event.c and the cursor rendering in the new
platform-independent virtual_pointer module, so it works on any touch
target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kichikuou
2026-06-24 20:29:45 +09:00
co-authored by Claude Opus 4.8
parent 50d05e5b56
commit dac0345e02
15 changed files with 359 additions and 1 deletions
@@ -61,9 +61,13 @@ class GameActivity : SDLActivity() {
}
override fun getArguments(): Array<String> {
return arrayOf(
val args = mutableListOf(
"-gamedir", intent.getStringExtra(EXTRA_GAME_ROOT)!!,
"-savedir", intent.getStringExtra(EXTRA_SAVE_DIRECTORY)!!)
val prefs = getSharedPreferences(Launcher.PREFS_NAME, MODE_PRIVATE)
if (prefs.getBoolean(Launcher.PREF_VIRTUAL_POINTER, false))
args.add("-virtualpointer")
return args.toTypedArray()
}
override fun setTitle(title: CharSequence?) {
@@ -42,6 +42,8 @@ interface LauncherObserver {
class Launcher private constructor(private val rootDir: File) {
companion object {
const val SAVE_DIR = "save"
const val PREFS_NAME = "settings"
const val PREF_VIRTUAL_POINTER = "virtual_pointer"
const val TITLE_FILE = "title.txt"
const val GAMEDIR_FILE = "game_directory.txt"
const val PLAYLIST_FILE = "playlist.txt"
@@ -92,6 +92,9 @@ class LauncherActivity : Activity(), LauncherObserver {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.launcher_menu, menu)
val prefs = getSharedPreferences(Launcher.PREFS_NAME, MODE_PRIVATE)
menu.findItem(R.id.virtual_pointer).isChecked =
prefs.getBoolean(Launcher.PREF_VIRTUAL_POINTER, false)
return true
}
@@ -143,6 +146,13 @@ class LauncherActivity : Activity(), LauncherObserver {
startActivity(intent)
true
}
R.id.virtual_pointer -> {
val enabled = !item.isChecked
item.isChecked = enabled
getSharedPreferences(Launcher.PREFS_NAME, MODE_PRIVATE).edit()
.putBoolean(Launcher.PREF_VIRTUAL_POINTER, enabled).apply()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -12,6 +12,10 @@
<item
android:id="@+id/clear_savedata"
android:title="@string/action_clear_save_data" />
<item
android:id="@+id/virtual_pointer"
android:checkable="true"
android:title="@string/action_virtual_pointer" />
<item
android:id="@+id/help"
android:title="@string/action_help" />
@@ -7,6 +7,7 @@
<string name="action_install_from_zip">ZIPからインストール</string>
<string name="action_licenses">オープンソースライセンス</string>
<string name="action_clear_save_data">セーブデータをクリア</string>
<string name="action_virtual_pointer">仮想マウスポインタ</string>
<string name="cancel">キャンセル</string>
<string name="cannot_find_ald">System 3.x のファイル (*.ald) が見つかりません。</string>
<string name="choose_a_file">ファイルを選択</string>
@@ -6,6 +6,7 @@
<string name="action_import_save_data">Import Save Files</string>
<string name="action_install_from_zip">Install from ZIP</string>
<string name="action_licenses">Open source licenses</string>
<string name="action_virtual_pointer">Virtual mouse pointer</string>
<string name="cancel">Cancel</string>
<string name="cannot_find_ald">Cannot find System 3.x game files (*.ald).</string>
<string name="choose_a_file">Choose a file</string>