Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,12 @@ private class GhosttyAndroidSurfaceView(
}

override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
val renderer = terminalRenderer ?: return false
// ponytail: pop the IME on every confirmed tap, even before
// the renderer binds. The prior guard returned false when
// terminalRenderer was null, so tapping the black surface
// during startup never summoned the keyboard.
showIme()
val renderer = terminalRenderer ?: return true
if (currentSelectionRange() != null) {
clearSelection()
return true
Expand All @@ -444,7 +449,6 @@ private class GhosttyAndroidSurfaceView(
runCatching { context.startActivity(intent) }
return true
}
showIme()
return true
}
},
Expand All @@ -457,10 +461,21 @@ private class GhosttyAndroidSurfaceView(
isFocusableInTouchMode = true
isLongClickable = true
isHapticFeedbackEnabled = true
activeSurface = java.lang.ref.WeakReference(this)
}

internal fun forceShowIme() = showIme()

override fun onCheckIsTextEditor(): Boolean = true

companion object {
// ponytail: process-wide handle so the terminal header's keyboard
// button can summon the IME without threading a callback through
// every Composable. Weak so the view can be GC'd normally.
@Volatile
internal var activeSurface: java.lang.ref.WeakReference<GhosttyAndroidSurfaceView>? = null
}

override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
outAttrs.inputType = (
InputType.TYPE_CLASS_TEXT or
Expand Down Expand Up @@ -917,3 +932,10 @@ private object KeyEventTranslator {
else -> 0
}
}

// ponytail: top-level entry so the terminal header (or any Composable in
// the terminal package) can pop the IME on the current SurfaceView without
// exposing the private view class.
internal fun showTerminalKeyboard() {
GhosttyAndroidSurfaceView.activeSurface?.get()?.forceShowIme()
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.outlined.Keyboard
import androidx.compose.material.icons.outlined.PhoneIphone
import androidx.compose.material.icons.outlined.Storage
import androidx.compose.material3.DropdownMenu
Expand Down Expand Up @@ -539,6 +540,20 @@ private fun TerminalHeader(
}
}
Spacer(Modifier.weight(1f))
// ponytail: explicit keyboard toggle — the tap-to-focus path on
// the SurfaceView is easy to miss (users often tap chrome, not
// the black viewport) and some devices/IMEs never restore focus
// after a reconnect.
IconButton(
onClick = { showTerminalKeyboard() },
modifier = Modifier.size(40.dp),
) {
Icon(
Icons.Outlined.Keyboard,
contentDescription = "Show keyboard",
tint = LitterTheme.accent,
)
}
TextButton(
onClick = onConfigClick,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,12 @@ async fn should_accept_session(
) -> bool {
match expected_session_id.lock().await.as_deref() {
Some(expected) => expected == actual_session_id,
None => true,
// ponytail: drop replayed backlog frames from prior killed shell
// sessions until our own shell/spawn returns and sets the expected
// id. Without this a stale shell/exit in the ring breaks the read
// loop and the terminal reports "JSON-RPC stream closed" on every
// reconnect.
None => false,
}
}

Expand Down