mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 03:32:38 +08:00
feat: position
This commit is contained in:
parent
c711ccfd06
commit
a726e2b22b
|
@ -216,4 +216,6 @@ dependencies {
|
|||
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.coil.gif)
|
||||
|
||||
implementation(libs.exp4j)
|
||||
}
|
|
@ -10,7 +10,7 @@ import com.blankj.utilcode.util.ScreenUtils
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
typealias ActionFc = (context: AccessibilityService, node: AccessibilityNodeInfo) -> ActionResult
|
||||
typealias ActionFc = (context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?) -> ActionResult
|
||||
|
||||
|
||||
@Serializable
|
||||
|
@ -18,6 +18,7 @@ data class GkdAction(
|
|||
val selector: String,
|
||||
val quickFind: Boolean = false,
|
||||
val action: String? = null,
|
||||
val position: RawSubscription.Position? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -26,19 +27,21 @@ data class ActionResult(
|
|||
val result: Boolean,
|
||||
)
|
||||
|
||||
val clickNode: ActionFc = { _, node ->
|
||||
val clickNode: ActionFc = { _, node, _ ->
|
||||
ActionResult(
|
||||
action = "clickNode", result = node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
)
|
||||
}
|
||||
|
||||
val clickCenter: ActionFc = { context, node ->
|
||||
val react = Rect()
|
||||
node.getBoundsInScreen(react)
|
||||
val x = (react.right + react.left) / 2f
|
||||
val y = (react.bottom + react.top) / 2f
|
||||
val clickCenter: ActionFc = { context, node, position ->
|
||||
val rect = Rect()
|
||||
node.getBoundsInScreen(rect)
|
||||
val p = position?.calc(rect)
|
||||
val x = p?.first ?: ((rect.right + rect.left) / 2f)
|
||||
val y = p?.second ?: ((rect.bottom + rect.top) / 2f)
|
||||
ActionResult(
|
||||
action = "clickCenter",
|
||||
// TODO 在分屏/小窗模式下会点击到应用界面外部导致误触其它应用
|
||||
result = if (0 <= x && 0 <= y && x <= ScreenUtils.getScreenWidth() && y <= ScreenUtils.getScreenHeight()) {
|
||||
val gestureDescription = GestureDescription.Builder()
|
||||
val path = Path()
|
||||
|
@ -56,31 +59,32 @@ val clickCenter: ActionFc = { context, node ->
|
|||
)
|
||||
}
|
||||
|
||||
val click: ActionFc = { context, node ->
|
||||
val click: ActionFc = { context, node, position ->
|
||||
if (node.isClickable) {
|
||||
val result = clickNode(context, node)
|
||||
val result = clickNode(context, node, position)
|
||||
if (result.result) {
|
||||
result
|
||||
} else {
|
||||
clickCenter(context, node)
|
||||
clickCenter(context, node, position)
|
||||
}
|
||||
} else {
|
||||
clickCenter(context, node)
|
||||
clickCenter(context, node, position)
|
||||
}
|
||||
}
|
||||
|
||||
val longClickNode: ActionFc = { _, node ->
|
||||
val longClickNode: ActionFc = { _, node, _ ->
|
||||
ActionResult(
|
||||
action = "longClickNode",
|
||||
result = node.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)
|
||||
)
|
||||
}
|
||||
|
||||
val longClickCenter: ActionFc = { context, node ->
|
||||
val react = Rect()
|
||||
node.getBoundsInScreen(react)
|
||||
val x = (react.right + react.left) / 2f
|
||||
val y = (react.bottom + react.top) / 2f
|
||||
val longClickCenter: ActionFc = { context, node, position ->
|
||||
val rect = Rect()
|
||||
node.getBoundsInScreen(rect)
|
||||
val p = position?.calc(rect)
|
||||
val x = p?.first ?: ((rect.right + rect.left) / 2f)
|
||||
val y = p?.second ?: ((rect.bottom + rect.top) / 2f)
|
||||
// 内部的 DEFAULT_LONG_PRESS_TIMEOUT 常量是 400
|
||||
// 而 ViewConfiguration.getLongPressTimeout() 返回 300, 这将导致触发普通的 click 事件
|
||||
ActionResult(
|
||||
|
@ -104,20 +108,20 @@ val longClickCenter: ActionFc = { context, node ->
|
|||
}
|
||||
|
||||
|
||||
val longClick: ActionFc = { context, node ->
|
||||
val longClick: ActionFc = { context, node, position ->
|
||||
if (node.isLongClickable) {
|
||||
val result = longClickNode(context, node)
|
||||
val result = longClickNode(context, node, position)
|
||||
if (result.result) {
|
||||
result
|
||||
} else {
|
||||
longClickCenter(context, node)
|
||||
longClickCenter(context, node, position)
|
||||
}
|
||||
} else {
|
||||
longClickCenter(context, node)
|
||||
longClickCenter(context, node, position)
|
||||
}
|
||||
}
|
||||
|
||||
val backFc: ActionFc = { context, _ ->
|
||||
val backFc: ActionFc = { context, _, _ ->
|
||||
ActionResult(
|
||||
action = "back",
|
||||
result = context.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import android.graphics.Rect
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
|
@ -11,11 +12,14 @@ import kotlinx.serialization.json.boolean
|
|||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import li.songe.gkd.service.allowPropertyNames
|
||||
import li.songe.gkd.util.json
|
||||
import li.songe.gkd.util.json5ToJson
|
||||
import li.songe.selector.Selector
|
||||
import net.objecthunter.exp4j.Expression
|
||||
import net.objecthunter.exp4j.ExpressionBuilder
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
|
@ -90,6 +94,61 @@ data class RawSubscription(
|
|||
@Serializable
|
||||
data class RawCategory(val key: Int, val name: String, val enable: Boolean?)
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class Position(
|
||||
val left: String?,
|
||||
val top: String?,
|
||||
val right: String?,
|
||||
val bottom: String?
|
||||
) {
|
||||
private val leftExp by lazy { getExpression(left) }
|
||||
private val topExp by lazy { getExpression(top) }
|
||||
private val rightExp by lazy { getExpression(right) }
|
||||
private val bottomExp by lazy { getExpression(bottom) }
|
||||
|
||||
val isValid by lazy {
|
||||
((leftExp != null && (topExp != null || bottomExp != null)) || (rightExp != null && (topExp != null || bottomExp != null)))
|
||||
}
|
||||
|
||||
/**
|
||||
* return (x, y)
|
||||
*/
|
||||
fun calc(rect: Rect): Pair<Float, Float>? {
|
||||
if (!isValid) return null
|
||||
arrayOf(
|
||||
leftExp,
|
||||
topExp,
|
||||
rightExp,
|
||||
bottomExp
|
||||
).forEach { exp ->
|
||||
if (exp != null) {
|
||||
setVariables(exp, rect)
|
||||
}
|
||||
}
|
||||
if (leftExp != null) {
|
||||
if (topExp != null) {
|
||||
return (rect.left + leftExp!!.evaluate()
|
||||
.toFloat()) to (rect.top + topExp!!.evaluate().toFloat())
|
||||
}
|
||||
if (bottomExp != null) {
|
||||
return (rect.left + leftExp!!.evaluate()
|
||||
.toFloat()) to (rect.bottom - bottomExp!!.evaluate().toFloat())
|
||||
}
|
||||
} else if (rightExp != null) {
|
||||
if (topExp != null) {
|
||||
return (rect.right - rightExp!!.evaluate()
|
||||
.toFloat()) to (rect.top + topExp!!.evaluate().toFloat())
|
||||
}
|
||||
if (bottomExp != null) {
|
||||
return (rect.right - rightExp!!.evaluate()
|
||||
.toFloat()) to (rect.bottom - bottomExp!!.evaluate().toFloat())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface RawCommonProps {
|
||||
val actionCd: Long?
|
||||
|
@ -111,6 +170,7 @@ data class RawSubscription(
|
|||
val key: Int?
|
||||
val preKeys: List<Int>?
|
||||
val action: String?
|
||||
val position: Position?
|
||||
val matches: List<String>?
|
||||
val excludeMatches: List<String>?
|
||||
}
|
||||
|
@ -208,6 +268,9 @@ data class RawSubscription(
|
|||
unknownPropertyNames.forEach { n ->
|
||||
return@lazy "非法属性名:${n}"
|
||||
}
|
||||
if (rules.any { r -> r.position?.isValid == false }) {
|
||||
return@lazy "非法位置"
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
|
@ -239,6 +302,7 @@ data class RawSubscription(
|
|||
override val key: Int?,
|
||||
override val preKeys: List<Int>?,
|
||||
override val action: String?,
|
||||
override val position: Position?,
|
||||
override val matches: List<String>,
|
||||
override val excludeMatches: List<String>?,
|
||||
override val matchAnyApp: Boolean?,
|
||||
|
@ -318,6 +382,7 @@ data class RawSubscription(
|
|||
override val key: Int?,
|
||||
override val preKeys: List<Int>?,
|
||||
override val action: String?,
|
||||
override val position: Position?,
|
||||
override val matches: List<String>?,
|
||||
override val excludeMatches: List<String>?,
|
||||
|
||||
|
@ -345,11 +410,69 @@ data class RawSubscription(
|
|||
|
||||
companion object {
|
||||
|
||||
private val expVars = arrayOf(
|
||||
"left",
|
||||
"top",
|
||||
"right",
|
||||
"bottom",
|
||||
"width",
|
||||
"height"
|
||||
)
|
||||
|
||||
private fun setVariables(exp: Expression, rect: Rect) {
|
||||
exp.setVariable("left", rect.left.toDouble())
|
||||
exp.setVariable("top", rect.top.toDouble())
|
||||
exp.setVariable("right", rect.right.toDouble())
|
||||
exp.setVariable("bottom", rect.bottom.toDouble())
|
||||
exp.setVariable("width", rect.width().toDouble())
|
||||
exp.setVariable("height", rect.height().toDouble())
|
||||
}
|
||||
|
||||
private fun getExpression(value: String?): Expression? {
|
||||
return if (value != null) {
|
||||
try {
|
||||
ExpressionBuilder(value).variables(*expVars).build().apply {
|
||||
expVars.forEach { v ->
|
||||
// 预填充作 validate
|
||||
setVariable(v, 0.0)
|
||||
}
|
||||
}.let { e ->
|
||||
if (e.validate().isValid) {
|
||||
e
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPosition(jsonObject: JsonObject? = null): Position? {
|
||||
return when (val element = jsonObject?.get("position")) {
|
||||
JsonNull, null -> null
|
||||
is JsonObject -> {
|
||||
Position(
|
||||
left = element["left"]?.jsonPrimitive?.content,
|
||||
bottom = element["bottom"]?.jsonPrimitive?.content,
|
||||
top = element["top"]?.jsonPrimitive?.content,
|
||||
right = element["right"]?.jsonPrimitive?.content,
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStringIArray(
|
||||
json: JsonObject? = null,
|
||||
jsonObject: JsonObject? = null,
|
||||
name: String
|
||||
): List<String>? {
|
||||
return when (val element = json?.get(name)) {
|
||||
return when (val element = jsonObject?.get(name)) {
|
||||
JsonNull, null -> null
|
||||
is JsonObject -> error("Element ${this::class} can not be object")
|
||||
is JsonArray -> element.map {
|
||||
|
@ -363,8 +486,8 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getIntIArray(json: JsonObject? = null, name: String): List<Int>? {
|
||||
return when (val element = json?.get(name)) {
|
||||
private fun getIntIArray(jsonObject: JsonObject? = null, name: String): List<Int>? {
|
||||
return when (val element = jsonObject?.get(name)) {
|
||||
JsonNull, null -> null
|
||||
is JsonArray -> element.map {
|
||||
when (it) {
|
||||
|
@ -378,8 +501,8 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getLongIArray(json: JsonObject? = null, name: String): List<Long>? {
|
||||
return when (val element = json?.get(name)) {
|
||||
private fun getLongIArray(jsonObject: JsonObject? = null, name: String): List<Long>? {
|
||||
return when (val element = jsonObject?.get(name)) {
|
||||
JsonNull, null -> null
|
||||
is JsonArray -> element.map {
|
||||
when (it) {
|
||||
|
@ -393,8 +516,8 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getString(json: JsonObject? = null, key: String): String? =
|
||||
when (val p = json?.get(key)) {
|
||||
private fun getString(jsonObject: JsonObject? = null, key: String): String? =
|
||||
when (val p = jsonObject?.get(key)) {
|
||||
JsonNull, null -> null
|
||||
is JsonPrimitive -> {
|
||||
if (p.isString) {
|
||||
|
@ -407,8 +530,8 @@ data class RawSubscription(
|
|||
else -> error("Element $p is not a string")
|
||||
}
|
||||
|
||||
private fun getLong(json: JsonObject? = null, key: String): Long? =
|
||||
when (val p = json?.get(key)) {
|
||||
private fun getLong(jsonObject: JsonObject? = null, key: String): Long? =
|
||||
when (val p = jsonObject?.get(key)) {
|
||||
JsonNull, null -> null
|
||||
is JsonPrimitive -> {
|
||||
p.long
|
||||
|
@ -417,8 +540,8 @@ data class RawSubscription(
|
|||
else -> error("Element $p is not a long")
|
||||
}
|
||||
|
||||
private fun getInt(json: JsonObject? = null, key: String): Int? =
|
||||
when (val p = json?.get(key)) {
|
||||
private fun getInt(jsonObject: JsonObject? = null, key: String): Int? =
|
||||
when (val p = jsonObject?.get(key)) {
|
||||
JsonNull, null -> null
|
||||
is JsonPrimitive -> {
|
||||
p.int
|
||||
|
@ -427,8 +550,8 @@ data class RawSubscription(
|
|||
else -> error("Element $p is not a int")
|
||||
}
|
||||
|
||||
private fun getBoolean(json: JsonObject? = null, key: String): Boolean? =
|
||||
when (val p = json?.get(key)) {
|
||||
private fun getBoolean(jsonObject: JsonObject? = null, key: String): Boolean? =
|
||||
when (val p = jsonObject?.get(key)) {
|
||||
JsonNull, null -> null
|
||||
is JsonPrimitive -> {
|
||||
p.boolean
|
||||
|
@ -468,6 +591,7 @@ data class RawSubscription(
|
|||
excludeVersionCodes = getLongIArray(jsonObject, "excludeVersionCodes"),
|
||||
versionNames = getStringIArray(jsonObject, "versionNames"),
|
||||
excludeVersionNames = getStringIArray(jsonObject, "excludeVersionNames"),
|
||||
position = getPosition(jsonObject),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -568,6 +692,7 @@ data class RawSubscription(
|
|||
excludeMatches = getStringIArray(jsonObject, "excludeMatches"),
|
||||
matches = getStringIArray(jsonObject, "matches") ?: error("miss matches"),
|
||||
order = getInt(jsonObject, "order"),
|
||||
position = getPosition(jsonObject),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import kotlinx.coroutines.Job
|
||||
import li.songe.gkd.service.lastTriggerRule
|
||||
|
@ -130,7 +131,10 @@ sealed class ResolvedRule(
|
|||
return target
|
||||
}
|
||||
|
||||
val performAction = getActionFc(rule.action)
|
||||
private val fc = getActionFc(rule.action)
|
||||
fun performAction(context: AccessibilityService, node: AccessibilityNodeInfo): ActionResult {
|
||||
return fc(context, node, rule.position)
|
||||
}
|
||||
|
||||
var matchDelayJob: Job? = null
|
||||
|
||||
|
|
|
@ -533,7 +533,7 @@ class GkdAbService : CompositionAbService({
|
|||
)
|
||||
}
|
||||
|
||||
return getActionFc(gkdAction.action)(serviceVal, targetNode)
|
||||
return getActionFc(gkdAction.action)(serviceVal, targetNode, gkdAction.position)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -204,6 +204,9 @@ dependencyResolutionManagement {
|
|||
|
||||
// https://github.com/Calvin-LL/Reorderable
|
||||
library("others.reorderable", "sh.calvin.reorderable:reorderable:1.3.1")
|
||||
|
||||
// https://www.objecthunter.net/exp4j/
|
||||
library("exp4j", "net.objecthunter:exp4j:0.4.8")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user