GestureClip (3) 오므리면 진짜로 복사까지 하게 만들기
지난 글까지 오므리면 보내고 펼치면 받는 건 됐는데, 두 가지가 아쉬웠다. 텍스트만 되고 이미지는 안 됐고, “오므리면 복사”가 아니라 “미리 복사해둔 걸 보내기”였다. 이번엔 이 둘을 채웠다.
텍스트 말고 이미지도 보내기
지금까지는 그냥 텍스트만 주고받아서 데이터가 뭔지 서로 알 필요가 없었는데, 이미지가 섞이려면 받는 쪽이 지금 온 게 텍스트인지 이미지인지부터 알아야 한다. 그래서 데이터 맨 앞에 1바이트짜리 표식을 하나 붙였다. 1이면 텍스트, 2면 이미지다.
1
2
3
4
5
// MPCPayloadType.swift (맥, 아이폰 양쪽에 똑같이 있음)
enum MPCPayloadType: UInt8 {
case text = 1
case image = 2
}
맥 쪽은 클립보드에 텍스트가 있으면 그걸, 없을 때만 이미지를 찾아서 PNG로 바꿔 보낸다.
1
2
3
4
5
6
7
8
9
// MPCSender.swift (맥)
if let text = pasteboard.string(forType: .string) {
sendPayload(type: .text, data: Data(text.utf8), successMessage: "텍스트 전송: \(text)")
return
}
if let image = NSImage(pasteboard: pasteboard), let png = pngData(from: image) {
sendPayload(type: .image, data: png, successMessage: "이미지 전송 (\(png.count / 1024)KB)")
return
}
아이폰은 표식을 보고 텍스트/이미지 중 뭐가 왔는지 나눠서 펼치기가 확정될 때까지 대기시킨다. 되돌리기도 손봤다. 원래는 클립보드 문자열만 기억해뒀는데, 이미지가 올 수도 있으니 UIPasteboard의 전체 상태(items)를 저장했다가 통째로 복원하도록 바꿨다.
오므리면 진짜 복사까지 하게 만들기
원래 목표는 손을 오므리는 동작 자체가 복사였다. 근데 지금까지 만든 건 “미리 Cmd+C 해둔 걸 오므릴 때 보내기”였다. 진짜로 오므리는 순간 지금 선택된 걸 복사하게 하려면, 맥 앱이 지금 앞에 있는 다른 앱한테 Cmd+C를 대신 눌러줘야 한다.
이건 CGEvent로 키 입력을 직접 만들어서 시스템에 주입하는 식으로 했다.
1
2
3
4
5
6
7
8
9
10
// SystemCopySimulator.swift (맥)
static func performCopyKeystroke() {
let source = CGEventSource(stateID: .hidSystemState)
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: true)
let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: false)
keyDown?.flags = .maskCommand
keyUp?.flags = .maskCommand
keyDown?.post(tap: .cghidEventTap)
keyUp?.post(tap: .cghidEventTap)
}
복사 명령을 보낸다고 그 순간 바로 클립보드에 반영되는 게 아니라서, 얼마나 기다렸다가 보내야 할지가 문제였다. 고정된 시간을 넣고 싶었는데, 지난번 펼침 기준값을 추측으로 넣었다가 틀렸던 게 떠올라서 이번엔 아예 추측을 안 했다. 클립보드에는 바뀔 때마다 올라가는 changeCount라는 값이 있어서, 복사 명령을 보낸 다음 이 값이 실제로 바뀔 때까지 짧은 간격으로 확인하고, 바뀌는 순간 바로 보내는 식으로 했다.
1
2
3
4
// MPCSender.swift (맥)
let beforeCount = NSPasteboard.general.changeCount
SystemCopySimulator.performCopyKeystroke()
waitForClipboardChange(from: beforeCount, attemptsLeft: 20)
만들고 테스트해보니 복사가 안 되는 것처럼 보였다. 원인은 두 가지였다.
하나는 구조적인 거였다. Cmd+C는 “지금 맨 앞에 있는 앱”한테 가는데, 오므릴 때 맥 앱 창을 보고 있으면 복사 명령이 맥 앱 자기 자신한테 가버린다. 그러면 클립보드는 당연히 안 바뀌고, 원래 있던 옛날 내용을 그대로 다시 보내게 된다. 다른 앱에서 뭔가 선택해두고, 그 앱이 맨 앞에 있는 채로(맥 앱 창은 다시 안 눌러도 된다, 카메라는 백그라운드에서 계속 돈다) 오므려야 한다.
다른 하나는 macOS 권한 쪽이었다. 이 기능은 다른 앱한테 가짜 키 입력을 보내는 거라 “손쉬운 사용” 권한이 있어야 하는데, 분명히 허용했는데도 없다고 나왔다. 이유는 앱을 서명 없이(팀 지정 없이) 빌드하고 있어서였다. 코드를 고치고 다시 빌드할 때마다 서명이 조금씩 달라지는데, 이 권한은 서명 단위로 앱을 구분해서 신뢰하기 때문에, 재빌드하는 순간 macOS 입장에서는 완전히 새 앱이 돼버려서 이전에 준 권한이 무효가 된다. Xcode에서 Team을 지정해서 서명을 고정시키고 나니, 한 번만 다시 허용하면 그 다음부터는 다시 빌드해도 권한이 유지됐다.