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 @@ -145,6 +145,7 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
private val handler = Handler(Looper.getMainLooper()) // Instance handler

private var pendingScreenshotDelayMillis: Long = 0L
private var sawNonTermuxCommandSinceLastScreenshot: Boolean = false
private var pendingDelayedScreenshotRunnable: Runnable? = null

// App name to package mapper
Expand Down Expand Up @@ -418,14 +419,35 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
}
}
}
.also { _ ->
if (command !is Command.TakeScreenshot && command !is Command.TermuxCommand) {
sawNonTermuxCommandSinceLastScreenshot = true
}
}
}

private fun executeTakeScreenshotCommand(): Boolean {
val delayMillis = pendingScreenshotDelayMillis
pendingScreenshotDelayMillis = 0L
val onlyTermuxContext = !sawNonTermuxCommandSinceLastScreenshot

if (!isTermuxRunCommandPermissionGranted()) {
val denialCount = TermuxFeedbackPreferences.incrementPermissionDenialCount(applicationContext)
if (denialCount >= 2) {
showToast("Enable Termux permissions in the Android settings", true)
}
Log.w(TAG, "Blocking screenshot/AI handoff because Termux RUN_COMMAND permission is not granted.")
return false
} else {
TermuxFeedbackPreferences.resetPermissionDenialCount(applicationContext)
}

fun buildScreenInfoPayload(rawScreenInfo: String?): String? {
val termuxOutput = TermuxOutputPreferences.consumeOutput(applicationContext)?.trim().orEmpty()
val termuxOutput = if (onlyTermuxContext) {
TermuxOutputPreferences.peekOutput(applicationContext)?.trim().orEmpty()
} else {
TermuxOutputPreferences.consumeOutput(applicationContext)?.trim().orEmpty()
}
if (termuxOutput.isBlank()) {
return rawScreenInfo
}
Expand All @@ -435,7 +457,7 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {

val captureAndRequestScreenshot = {
val currentModel = GenerativeAiViewModelFactory.getCurrentModel()
if (!currentModel.supportsScreenshot) {
if (!currentModel.supportsScreenshot || onlyTermuxContext) {
Log.d(TAG, "Command.TakeScreenshot: Model has no screenshot support, capturing screen info only.")
showToast("Capturing screen info...", false)
val screenInfo = buildScreenInfoPayload(captureScreenInformation())
Expand All @@ -445,6 +467,7 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
applicationContext,
screenInfo
)
sawNonTermuxCommandSinceLastScreenshot = false
} else {
Log.d(TAG, "Command.TakeScreenshot: Capturing screen info and sending request broadcast to MainActivity.")
showToast("Preparing screenshot...", false)
Expand All @@ -457,6 +480,7 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
}
applicationContext.sendBroadcast(intent)
Log.d(TAG, "Sent broadcast ACTION_REQUEST_MEDIAPROJECTION_SCREENSHOT to MainActivity with screenInfo.")
sawNonTermuxCommandSinceLastScreenshot = false
}
}

Expand All @@ -477,6 +501,10 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
return true
}

private fun isTermuxRunCommandPermissionGranted(): Boolean {
return checkSelfPermission("com.termux.permission.RUN_COMMAND") == PackageManager.PERMISSION_GRANTED
}

private fun cancelPendingDelayedScreenshot() {
pendingScreenshotDelayMillis = 0L
pendingDelayedScreenshotRunnable?.let { runnable ->
Expand Down Expand Up @@ -594,8 +622,8 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/bash")
putExtra("com.termux.RUN_COMMAND_ARGUMENTS", arrayOf("-lc", trimmedCommand))
putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home")
putExtra("com.termux.RUN_COMMAND_BACKGROUND", true)
putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", 0)
putExtra("com.termux.RUN_COMMAND_BACKGROUND", false)
putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", 1)
putExtra("com.termux.RUN_COMMAND_RUNNER", "app-shell")
putExtra("com.termux.RUN_COMMAND_PENDING_INTENT", pendingResultIntent)
putExtra("com.termux.RUN_COMMAND_BACKGROUND_CUSTOM_LOG_LEVEL", 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
object TermuxFeedbackPreferences {
private const val PREF_NAME = "termux_feedback_prefs"
private const val KEY_TERMUX_NOT_FOUND = "termux_not_found"
private const val KEY_TERMUX_PERMISSION_DENIAL_COUNT = "termux_permission_denial_count"

fun markTermuxNotFound(context: Context) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
Expand All @@ -21,4 +22,18 @@ object TermuxFeedbackPreferences {
}
return value
}

fun incrementPermissionDenialCount(context: Context): Int {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val updated = prefs.getInt(KEY_TERMUX_PERMISSION_DENIAL_COUNT, 0) + 1
prefs.edit().putInt(KEY_TERMUX_PERMISSION_DENIAL_COUNT, updated).apply()
return updated
}

fun resetPermissionDenialCount(context: Context) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putInt(KEY_TERMUX_PERMISSION_DENIAL_COUNT, 0)
.apply()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ object TermuxOutputPreferences {
}
return value
}

fun peekOutput(context: Context): String? {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val value = prefs.getString(KEY_PENDING_OUTPUT, "").orEmpty().trim()
return value.ifBlank { null }
}
}
Loading