본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

네이티브 자리는 다시 읽어서 맞춰봐

~12 min · revalidate, native-target, range-match, ax-set

Level 0식은 부싯돌
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"'앱이 떠 있나' 를 묻지 마. '내가 붙잡은 그게 아직 거기 있나' 를 물어. 아주 다른 질문이야."

믿지 말고 다시 읽어

네이티브 접근성 자리라면, 사진이 맞는지 확인한다는 건 세상을 다시 읽어서 붙잡아둔 것과 하나씩 맞춰본다는 뜻이야. 시스템 전역 요소로 돌아가 포커스된 요소를 다시 읽고, 거기 긁힌 글이랑 범위를 다시 읽고. Track 3 에서 걸었던 그 사슬을 넣는 시점에 한 번 더 도는 거지. 자리를 새로 구하려는 게 아니라, 이미 갖고 있는 자리가 아직 현실이랑 맞는지 확인하려는 거야. 옛 사진을 그냥 믿는 건 나를 안전하게 지켜주는 바로 그 단계를 건너뛰는 거고.

뭐가 맞아야 하나

네이티브 확인은 여러 개를 맞춰보고 전부 통과해야 해. 앞에 있는 앱이랑 프로세스 번호가 같아야 같은 앱이고. 포커스된 요소의 역할이랑 정체가 같아야 같은 칸이고. 그리고 제일 중요한 게 긁혀 있는 글이랑 그 범위야. 내가 대신하려던 바로 그 글자들이 아직 긁혀 있어야 하니까. 이 마지막 확인이 Flint 의 확인을 어설픈 확인이랑 갈라놔. 같은 앱만으론 부족해. 같은 칸도 부족하고. 같은 글이 긁혀 있는 것 이 기준이야. 그게 지금 덮어써질 거니까.

붙잡아둔 것 (t0)       다시 읽은 것 (t1)      판정
---------------------  ---------------------  -------
앱 이름 + pid          앱 이름 + pid          같아야 함
포커스된 요소 역할      포커스된 요소 역할      같아야 함
긁힌 글                긁힌 글                같아야 함
긁힌 범위              긁힌 범위              같아야 함
                                              다 맞으면 -> 대신한다
                                              하나라도 다르면 -> 거부

긁힌 자리만 정확히 대신하기

확인이 통과하면 네이티브 교체는 아주 정밀해. 포커스된 요소의 '긁힌 글' 속성에 바뀐 글을 넣으면 지금 긁혀 있는 범위만 정확히 덮어써져. 그 범위가 아직 붙잡아뒀던 그거라고 방금 확인했으니까, 덮어쓰기가 사용자가 뜻한 바로 그 자리에 떨어지고. 이게 있을 수 있는 제일 깨끗한 삽입이야. 클립보드도 안 건드리고, 가짜 키 입력도 없고, 확인된 범위만 딱 바꾸는 것. 그리고 네이티브 자리가 범위를 먼저 다시 확인하게 해줬기 때문에 가능한 거고.

포인터 말고 눈에 보이는 속성을 맞춰봐. 칠 때 붙잡아둔 AXUIElement 참조는 시간을 건너 비교하기엔 못 미더워. 같은 칸이 다른 참조를 돌려줄 수도 있고, 낡은 참조가 허공을 가리킬 수도 있거든. 그러니 '이게 같은 객체 포인터인가' 를 확인하지 마. '지금 포커스된 요소가 같은 앱, 같은 역할, 같은 글, 같은 범위를 말하고 있나' 를 확인해. 믿을 수 있는 정체는 다시 읽어서 확인되는 정체지, 캐시해둔 손잡이가 아니야.
편한 대리 확인 말고 뜻의 모든 면을 확인해. 제일 싼 것 하나만 보고 — '맞는 앱이 앞에 있네' — 확인했다고 하고 싶어져. 근데 뜻엔 여러 면이 있었고(앱, 칸, 긁힌 글, 범위), 낡은 자리는 그 싼 것만 맞고 나머지는 틀릴 수 있어. 맞는 앱, 틀린 글. 뜻 전체를, 특히 지금 사라질 그 면을 확인해. 대리 확인이 바로 도장은 찍혔는데 엉뚱한 글이 덮어써지는 길이야.

Code

다시 읽어 맞춰보는 것으로 네이티브 자리를 확인한다·swift
func proveNative(_ snap: TargetSnapshot) -> AXUIElement? {
    // 같은 앱 인스턴스인가?
    guard let front = NSWorkspace.shared.frontmostApplication,
          front.bundleIdentifier == snap.appBundleID,
          front.processIdentifier == snap.pid else { return nil }

    // 포커스된 요소랑 긁힌 글을 지금 다시 읽는다.
    guard let now = readSelection() else { return nil }   // Track 3 의 읽기

    // 긁힌 글과 범위가 붙잡아뒀던 그대로여야 한다.
    guard now.text == snap.selection,
          now.range == snap.range,
          now.role == snap.role else { return nil }

    return now.element   // 확인됨: 이 자리는 대신해도 안전하다
}

func replaceSelection(with result: String, in element: AXUIElement) {
    // 지금 긁혀 있는(방금 확인한) 범위만 정확히 덮어쓴다.
    AXUIElementSetAttributeValue(
        element, kAXSelectedTextAttribute as CFString, result as CFTypeRef)
}

External links

Exercise

어떤 확인 절차가 긁힌 걸 대신하기 전에 '같은 앱이 앞에 있나' 만 본다고 해보자. 그 확인은 통과하는데 교체가 엉뚱한 글을 없애버리는 상황을 구체적으로 하나 지어봐. 그다음 Flint 가 그 구멍을 막으려고 추가로 맞춰보는 것들을 나열하고, 넣는 게 아니라 덮어쓰는 도구라는 점에서 그중 어느 하나가 제일 중요한지도 말해봐.
Hint
상황은 이래. 같은 앱이 앞에 있는데 기다리는 동안 사용자가 다른 문단을 클릭해서 딴 글을 긁어놨어. 앱은 맞고 긁힌 건 다르고, 교체가 새로 긁힌 걸 덮어써. Flint 는 추가로 프로세스 번호, 포커스된 요소 역할, 긁힌 글, 범위를 맞춰보고. 제일 중요한 건 긁힌 글과 범위 맞추기야. 그게 지금 사라질 바로 그 내용이니까. 그것만 빼고 다 맞추는 건 중요한 것만 빼고 다 맞추는 거고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.