"스크린 리더가 화면을 읽어주게 하는 그 문이, Flint 가 내 글을 읽게 해주는 문이기도 해. 그 문은 내가 열어줘야만 열리고."
접근성이 대체 뭔데
macOS 엔 접근성 API 라는 게 통째로 있어. 다들 AX 라고 부르지. 스크린 리더나 스위치 컨트롤, 음성 제어 같은 보조 도구가 다른 앱 화면을 읽고 조작할 수 있게 만들어둔 거야. 생각해보면 대단한 권한이지. 앱 하나가 다른 앱 화면을 들여다보고 움직이는 거니까. Flint 는 스크린 리더가 아니지만 딱 그 힘이 좁은 목적으로 필요해. 지금 앞에 있는 앱에서 내가 긁어놓은 글을 읽어야 하거든. 그래서 보조 기술이 쓰는 바로 그 API 를 같은 권한으로 써.
요소로 이루어진 나무
앱은 저마다 자기 화면을 AXUIElement 라는 마디들의 나무로 내놔. 창, 버튼, 입력 칸 하나하나가 마디고, 마디마다 물어볼 수 있는 게 있어. 무슨 역할인지, 값이 뭔지, 지금 포커스를 쥐고 있는지, 안에 뭐가 긁혀 있는지. 시스템 전역 요소에서 출발해 포커스된 요소로 걸어가고, 거기다 물어보는 거야. 긁어놓은 글을 읽는 일이 결국 그 나무의 마디 하나한테 속성 하나를 물어보는 일이지. 이게 좋은 건 어디서나 똑같이 생겼다는 점이야. 제대로 구현한 네이티브 앱이면 같은 질문 몇 개가 전부 통해.
AXUIElementCreateSystemWide()
-> kAXFocusedUIElementAttribute (아무 앱에서든 지금 포커스 쥔 것)
-> kAXSelectedTextAttribute (긁어놓은 글)
-> kAXSelectedTextRangeAttribute (어디서 어디까지인지)
-> kAXRoleAttribute / kAXSubroleAttribute (어떤 종류 칸인지)
나무 하나, 질문 몇 개. 읽는 건 이게 전부다.
권한이 전부야
AX 로 아무 앱이나 읽고 조종할 수 있으니까 macOS 는 이걸 꽉 잠가놨어. 사용자가 시스템 설정에서 대놓고 접근성 권한을 주기 전엔 Flint 는 아무것도 못 해. AXIsProcessTrusted() 가 그 권한이 있는지 알려주고, 뭘 읽으려 들기 전에 그것부터 봐야 하지. 사용자가 문을 열어주기 전까지는 모든 읽기가 실패해. 그게 맞고. 권한은 돌아가야 할 장애물이 아니라 이 기능 전체가 딛고 선 신뢰거든.