TrendShift 딥다이브 · 2026-07-01

Vorssaint 딥다이브
— 메뉴바 아이콘 하나에 유료 Mac 유틸 한 다발을 담은 네이티브 앱

Vorssaint(저장소명 vorssaint-utils)는 macOS 메뉴바에서만 사는 단일 네이티브 앱이다. 보통 따로따로 (대부분 유료로) 사야 하는 Mac 유틸리티 — 화면 안 꺼지게 하기 · 시스템 모니터(온도·CPU·메모리) · 썸네일 창 전환기 · 앱별 볼륨 조절 · 클립보드 히스토리 · 파일 임시보관함 · 앱 삭제기 · Homebrew 관리 등 — 을 아이콘 하나 뒤에 몰아넣었다. 100% 네이티브(SwiftUI + AppKit), Electron 없음, 계정 없음, 텔레메트리 없음, 외부 라이브러리 0개. 약 5만 줄 Swift를 시스템 SDK만으로 짠, macOS 시스템 프로그래밍의 살아있는 교과서다. (저장소: vorssaint/vorssaint-utils · Swift 100% · v3.1.6 · GPL-3.0-or-later)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

이 저장소가 대체 무엇인가.

핵심 메시지

"맥북 메뉴바 아이콘 하나에, 따로 사면 다 유료인 Mac 유틸리티 열몇 개를 몰아넣은 스위스 군용칼."

Vorssaint은 화면 위(메뉴바)에 작은 아이콘 하나로만 존재한다. 클릭하면 패널이 열리고, 그 안에 잠 안 자게 하기·온도계·창 전환기·앱별 음량·클립보드 기록 같은 도구가 줄지어 있다. 각각은 시중에 Amphetamine·iStat Menus·AltTab·SoundSource·Maccy 같은 (대개 유료) 앱으로 따로 존재하는 기능인데, Vorssaint은 그걸 공짜 오픈소스 하나로 묶었다.

Mac 앱을 좀 써봤다면: "메뉴바 유틸 모음집 + 100% 네이티브 + 로컬 전용(서버 통신 0)"이라고 이해하면 된다. Electron(웹기술로 만든 무거운 데스크탑 앱)이 아니라 Apple의 SwiftUI/AppKit으로 직접 짜서 가볍고 빠르며, 인터넷으로 데이터를 보내지 않는다.

용어
메뉴바 앱 (Menu Bar App / LSUIElement)
독(Dock)에 아이콘이 뜨고 창이 있는 보통 앱과 달리, 화면 맨 위 메뉴바에만 작은 아이콘으로 사는 앱. 정보 표시 줄에 늘 떠 있으면서 클릭하면 작은 패널을 연다. macOS에서는 Info.plistLSUIElement = true를 넣어 "독에 안 뜨는 보조 앱"으로 만든다. iStat Menus, Bartender 같은 도구가 이 방식이다.

2왜 주목받는가

유료 단품 앱 묶음 · Electron 유틸 대비 장점 · 트렌딩 이유.

Mac 파워유저는 보통 작은 유틸을 여러 개 깔아 쓴다 — 잠 안 자게 하는 앱, 메뉴바 온도계, 창 전환기, 앱별 볼륨 조절기, 클립보드 매니저… 문제는 이게 대부분 유료이고, 제각각 다른 회사가 만들어 설정·디자인·업데이트가 따로 논다는 점이다. 게다가 일부는 Electron으로 만들어 메모리를 수백 MB씩 먹는다. Vorssaint은 이 한 다발을 단일 네이티브 앱·단일 권한 흐름·단일 GPL 라이선스로 통합했다.

"보통 이렇게 쓰던 것" vs Vorssaint

기능흔히 쓰는 단품 앱 (대개 유료)Vorssaint
화면 안 꺼지게Amphetamine / Caffeine내장 (전원 어서션 + 닫은 뚜껑 모드)
시스템 모니터iStat Menus (유료)내장 (온도·CPU·GPU·메모리·네트워크)
창 전환기AltTab내장 (실시간 썸네일 ⌘Tab)
앱별 볼륨SoundSource / BackgroundMusic (유료)내장 (드라이버 없이 Core Audio 탭)
클립보드 기록Maccy / Paste (유료)내장 (클립보드 히스토리)
설치 방식제각각단일 .app (메뉴바 하나)
구현 기술일부 Electron100% 네이티브 Swift
함정 — 흔한 방식
Electron 유틸 모음 = 무겁고 느리고 배터리 먹음

웹기술(Chromium)을 통째로 끼워 만든 Electron 앱은 메뉴바 유틸 하나에도 수백 MB 메모리와 별도 렌더러 프로세스를 쓴다. 작은 도구 여러 개를 이렇게 깔면 백그라운드에서 조용히 자원을 갉아먹는다.

해결 — Vorssaint의 방식
시스템 SDK만 쓴 네이티브 단일 바이너리 + 로컬 전용

외부 의존성이 0개다. IOKit·ScreenCaptureKit·Core Audio 같은 Apple 프레임워크만 호출하므로 가볍고 빠르다. 네트워크 호출·분석·계정이 전혀 없어 데이터가 기기 밖으로 나가지 않는다. 권한이 없는 기능은 깨지는 대신 조용히 비활성(graceful degradation)된다.

이런 상황을 상상해봐

발표 중이라 화면이 꺼지면 안 되고, CPU 온도도 곁눈질하고 싶고, 회의 앱 볼륨만 따로 줄이고 싶다. 보통은 앱 세 개(+ 두 개는 유료)를 깔아야 한다. Vorssaint은 메뉴바 아이콘 하나를 눌러 "8시간 깨어있기" 토글, 온도 핀 고정, 그 앱 볼륨 슬라이더를 한 패널에서 끝낸다.

그리고 이 모든 게 인터넷 연결 없이, 백그라운드 데이터 전송 없이 로컬에서만 돈다.

3기술 스택 전체 지도

언어 · UI · 시스템 API · 빌드/서명 파이프라인 전체.

언어 · UI · 빌드

레이어기술역할 / 비고
언어Swift 5.9swift-tools-version 5.9, 전체 약 5만 줄
UI 프레임워크SwiftUI + AppKit패널·설정은 SwiftUI, 메뉴바 아이템·창 제어는 AppKit(NSStatusItem 등)
외부 의존성없음 (0개)SPM 패키지 한 개도 안 씀 — 전부 시스템 SDK
빌드swiftc -O 직접 컴파일SwiftPM swift build가 아니라 build.sh가 컴파일러를 직접 호출
타깃arm64-apple-macosx14.0Apple Silicon 전용, macOS 14(Sonoma) 이상
다국어9개 언어타입세이프 Swift struct(.strings 파일 안 씀)

호출하는 핵심 시스템 프레임워크

프레임워크이 앱에서 하는 일
IOKit.pwr_mgt전원 어서션(IOPMAssertion)으로 잠/디스플레이 끄기 방지
IOKit (AppleSMC)SMC 칩에서 CPU/GPU/배터리 온도 직접 읽기
IOKit (IORegistry)GPU 사용률·배터리·디스크 처리량 등 레지스트리 속성 조회
CoreGraphicsCGEvent 탭으로 마우스 휠 스크롤 가로채기/뒤집기
ScreenCaptureKit창 전환기의 실시간 썸네일 캡처
CoreAudio프로세스 탭(process tap)으로 드라이버 없이 앱별 볼륨 조절
Accessibility (AX)창 목록 열거·활성화, 최소화 해제, 닫기 버튼 누르기
Carbon.HIToolbox전역 단축키 등록(RegisterEventHotKey)
Mach / sysctlCPU 부하(host_statistics), 메모리 압력, CPU 모델명

CI / 배포 파이프라인

단계방법
CI 러너GitHub Actions macos-26
테스트build.sh --test (순수 헬퍼만 컴파일한 자체 하네스) + --selftest 런타임 스모크
코드 서명3단계 폴백: Developer ID → 자체서명 → ad-hoc(-)
공증/배포릴리스 시 앱·DMG 모두 notarize + staple, spctl로 검증
패키징스타일 입힌 DMG, hardened runtime, 아이콘은 빌드 때 swift Tools/MakeIcon.swift로 생성
용어
SMC (System Management Controller)
맥 메인보드에 있는 작은 보조 칩. 온도 센서, 팬 속도, 전원·배터리 상태 등 "하드웨어 계기판" 데이터를 들고 있다. iStat Menus 같은 온도계 앱은 전부 이 SMC에서 값을 읽는다. 공식 공개 API가 없어서, IOKit으로 AppleSMC 서비스에 직접 명령을 보내 읽어야 한다(섹션 6 참고).
용어
TCC (Transparency, Consent & Control)
macOS의 권한 관리 시스템. 앱이 화면 녹화·손쉬운 사용(Accessibility)·전체 디스크 접근 같은 민감한 기능을 쓰려면 사용자가 시스템 설정에서 허용해야 한다. "이 앱이 화면 기록을 요청합니다" 같은 팝업이 바로 TCC다. Vorssaint은 각 기능을 해당 권한에 맞춰 켜고, 권한이 없으면 그 기능만 조용히 꺼둔다.
용어
공증 (Notarization) & Hardened Runtime
Apple이 배포 앱을 검사해 "악성코드 아님" 도장을 찍어주는 절차가 공증. Hardened Runtime은 코드 주입·메모리 변조를 막는 보안 강화 실행 모드로, 공증의 전제 조건이다. 이걸 거쳐야 사용자가 Gatekeeper 경고 없이 앱을 열 수 있다. Vorssaint은 릴리스마다 앱과 DMG를 둘 다 공증하고 도장을 박아(staple) 배포한다.

4아키텍처 심화 분석

전체 구조도 + 핵심 설계 패턴 + 대표 흐름 한 줄기 추적.

전체 시스템 구조도 (독수리 시점)

┌──────────────────────────────────────────────────────────────┐ │ macOS 메뉴바 (화면 맨 위 줄) │ │ [◑ Vorssaint 아이콘 + 핀 고정 계기판] │ └───────────────────────────────┬──────────────────────────────┘ │ 클릭(좌:패널 / 우:메뉴) ┌────────────▼─────────────┐ │ AppDelegate (조립 루트) │ │ - .accessory 정책 │ │ - 수동 메인메뉴 설치 │ │ - StatusItemController │ │ - syncWithPreferences() │ ← 서비스 ~20개 한 번에 동기화 └────┬───────────────┬──────┘ │ │ Combine 구독 ┌─────────────▼──┐ ┌────────▼───────────────────┐ │ UI 레이어 │ │ Core 레이어 │ │ (SwiftUI) │ │ Permissions(TCC) / L10n │ │ 패널·설정·온보딩│ │ Defaults / GlobalShortcut │ │ switcher 그리드│ └────────┬───────────────────┘ └───────▲────────┘ │ 권한 상태 게이팅 │ 관찰(observe) │ ┌───────┴─────────────────────▼───────────────────┐ │ Services 레이어 (싱글톤) │ │ KeepAwake · SystemMonitor · ScrollInverter · │ │ Switcher · AppVolumeMixer · ClipboardHistory · │ │ Shelf · Uninstaller · Homebrew · DockPreview… │ │ 각 서비스 = ObservableObject + 권한 의존 │ └───────────────────────┬─────────────────────────┘ │ 시스템 SDK 호출 ┌───────────────────────▼─────────────────────────┐ │ macOS 커널 / 하드웨어 인터페이스 │ │ IOKit(SMC·전원·IORegistry) · Mach(host_stats) · │ │ CoreGraphics(EventTap) · ScreenCaptureKit · │ │ CoreAudio(process tap) · Accessibility · Carbon │ └─────────────────────────────────────────────────┘

진입점 main.swift는 놀랄 만큼 짧다. @main 매크로 대신 손으로 NSApplication을 띄우고, CLI 인자(--selftest/--sensors/--uninstall)를 먼저 가로챈 뒤 AppDelegate에 위임한다. 진짜 조립은 전부 AppDelegate에서 일어난다 — 이 파일이 앱의 "조립 루트(composition root)"로, 약 20개의 서비스 싱글톤을 깨우고 설정과 동기화한다.

용어
조립 루트 (Composition Root)
앱의 부품(서비스)들을 한 곳에서 생성·연결하는 "배전반" 역할의 코드. 여기서는 AppDelegate가 그 역할을 한다. 부품을 여기저기 흩어서 만들지 않고 한 군데서 조립하면, 무엇이 무엇에 의존하는지 한눈에 보이고 테스트·교체가 쉬워진다.

핵심 설계 패턴 4가지

패턴 1

싱글톤 ObservableObject 서비스 + syncWithPreferences() + 권한 게이팅

모든 기능은 .shared 싱글톤이면서 SwiftUI의 ObservableObject다. 각자 syncWithPreferences() 메서드 하나로 "지금 설정대로 켜라/꺼라"를 받고, 자기에게 필요한 TCC 권한이 없으면 스스로 비활성된다. SwiftUI 뷰는 이 싱글톤을 관찰만 한다. 덕분에 "권한 없는 기능은 깨지는 대신 조용히 침묵"이라는 원칙이 코드 전반에 일관되게 박힌다.

패턴 2

수요 기반 샘플링 (Demand-driven, 유휴 비용 0)

시스템 모니터의 타이머는 패널이 열려 있거나 메뉴바에 계기판이 핀 고정돼 있을 때만 돈다. 아무도 안 보고 있으면 센서를 안 읽는다. "메뉴바 유틸은 늘 떠 있으니 항상 폴링한다"는 흔한 함정을 피해, 보고 있을 때만 일하는 구조로 배터리·CPU를 아낀다.

패턴 3

권한 변화에 Combine으로 즉시 반응 — 재실행 불필요

AppDelegatePermissions.$accessibility·$screenRecording 같은 퍼블리셔를 Combine으로 구독한다. 사용자가 시스템 설정에서 권한을 켜는 즉시 입력 기반 기능(스크롤 반전·창 전환기)이 앱 재시작 없이 살아난다. 언어(L10n.$language)가 바뀌면 메뉴를 다시 설치한다.

패턴 4

타입세이프 로컬라이제이션 (컴파일러가 번역 누락을 잡음)

UI 문자열을 .strings/.xcstrings 파일이 아니라 struct Strings의 저장 프로퍼티로 둔다. 새 문자열 필드를 추가하면 9개 언어 전부가 그 값을 제공해야 컴파일이 된다. 번역 빠뜨림이 런타임 빈 문자열이 아니라 컴파일 에러로 잡히는 영리한 트릭이다.

흐름 한 줄기 추적 — "메뉴바에서 CPU 온도를 보기까지"

아키텍처는 부품 이름만 외우면 안 꽂힌다. 가장 대표적인 동작 하나(온도 보기)가 어느 파일·API를 어떤 순서로 거치는지 끝까지 따라가 보자. 곁가지(에러 처리·캐시)는 생략한 해피패스다.

① 사용자가 메뉴바 아이콘 클릭 → StatusItemController 가 MetricPanel(SwiftUI) 표시 │ ② 패널이 열리니 SystemMonitor 샘플러 가동 (수요 기반 — 패턴 2) │ ③ SystemMonitor → TemperatureSensorSelector sysctlbyname("machdep.cpu.brand_string") 로 칩 판별 → 그 칩에 맞는 SMC 키 선택 (예: CPU die 온도 키) │ ④ SMCClient.read(key) IOServiceGetMatchingService(... "AppleSMC") ← SMC 서비스 열기 IOServiceOpen → 연결 핸들 확보 IOConnectCallStructMethod(conn, 2, &input, ...) ← 80바이트 구조체로 명령 │ ⑤ 응답 바이트를 데이터 타입으로 디코딩 "sp78" → Int16 / 256.0 (고정소수점 → 섭씨) │ ⑥ SystemMonitor 가 @Published 값 갱신 → SwiftUI MetricPanel 이 자동 리렌더 → "CPU 62.5°C" 표시

여기서 이 코드베이스 특유의 "정상"을 한 줄 짚자면: Services 폴더는 도메인별로 한 폴더씩(SystemMonitor/, Switcher/, Audio/…) 나뉘고, 각 폴더의 대표 타입은 보통 ...Manager/...Service 싱글톤이며, 화면에 보이는 값은 거의 다 @Published 프로퍼티를 SwiftUI가 관찰하는 형태다. 이 패턴만 알면 새 파일을 열었을 때 덜 낯설다.

5디렉토리 구조 해부

레이어별 폴더 + 루트의 빌드/CI 파일.

vorssaint-utils/ ├── Package.swift # 단일 executableTarget "Vorssaint", 의존성 0 ├── build.sh # swiftc 직접 컴파일 + 서명 + 번들 조립 (~290줄) ├── CHANGELOG.md # Keep a Changelog, 릴리스 61개 기록 ├── .github/workflows/ │ ├── ci.yml # PR마다: --test → 빌드 → --selftest → DMG │ └── release.yml # 태그(v*)마다: 빌드 → 공증 → staple → 릴리스 ├── Tools/ # 빌드 도우미 │ ├── MakeIcon.swift # 앱/메뉴바 아이콘 코드로 생성 │ ├── make-dmg.sh # 스타일 입힌 DMG │ ├── notarize.sh # Apple 공증 │ └── uninstall.sh # 흔적 없는 제거 ├── Tests/ │ └── MetricsTests.swift # XCTest 아님 — 자체 @main 테스트 하네스 └── Sources/Vorssaint/ ├── main.swift # 진입점 — CLI 가로채기 후 AppDelegate ├── App/ # 조립 루트 │ ├── AppDelegate.swift # ~1,258줄, 서비스 ~20개 조립 │ └── StatusItemController… # 메뉴바 아이템(좌클릭 패널/우클릭 메뉴) ├── Core/ # 공통 토대 │ ├── Permissions.swift # TCC: AX·화면녹화·전체디스크 │ ├── Localization.swift # 9개 언어 타입세이프 문자열 │ ├── Defaults.swift # UserDefaults 키 + 클램핑 │ └── GlobalShortcut… # Carbon 전역 단축키 ├── Services/ # 기능 = 폴더 하나씩 │ ├── KeepAwakeManager.swift # 전원 어서션 + 닫은 뚜껑 + 배터리 감시 │ ├── ScrollInverter.swift # CGEvent 탭, 휠만 반전 │ ├── SystemMonitor/ # SMCClient + 각종 샘플러 │ ├── Switcher/ # 창 열거·썸네일·활성화 (8개 파일) │ └── Audio/ # AppVolumeMixer (Core Audio 프로세스 탭) ├── UI/ # SwiftUI 뷰 (패널·설정·온보딩·switcher) └── Support/ # SelfTest · SensorDump · Uninstaller

주요 경로의 역할

경로역할
App/AppDelegate.swift모든 서비스를 깨우고 권한 변화에 반응시키는 조립 루트. 가장 큰 "지휘" 파일
Core/Permissions.swiftAX·화면녹화 권한 폴링(2.5초), 전체 디스크 접근은 API가 없어 우회 감지
Core/Localization.swift2천 줄대 문자열 테이블 — 컴파일러가 번역 누락 차단
Services/SystemMonitor/SMC·Mach·IORegistry·sysctl로 온도/CPU/GPU/메모리/네트워크/배터리 수집
Services/Switcher/CGWindowList + Accessibility로 창 열거, ScreenCaptureKit로 썸네일
Services/Audio/AppVolumeMixer.swiftmacOS 14.4+ Core Audio 프로세스 탭으로 드라이버 없이 앱별 볼륨
Tests/MetricsTests.swiftIOKit·UI 없는 순수 헬퍼만 빠르게 검증하는 자작 하네스
용어
레이어드 아키텍처 (Layered Architecture)
코드를 책임에 따라 층으로 나누는 방식. 여기선 App(조립) → UI(화면) → Services(기능) → Core(공통 토대) → 시스템 SDK 순으로 내려간다. 규칙은 "UI는 Services를 관찰하되, Services는 SwiftUI를 import하지 않는다" — 위층이 아래층을 알지만 그 반대는 막는다. 이렇게 하면 화면 코드와 기능 코드가 엉키지 않는다.

6학습 포인트 (기술별)

이 저장소에서 무엇을 배울 수 있는가 — macOS 시스템 프로그래밍의 보물창고.

1. 프레임워크 없이 저수준 macOS 시스템 프로그래밍

이 레포의 가장 큰 가치다. 편한 래퍼 라이브러리를 안 쓰고 raw IOKit·Mach·sysctl을 직접 호출한다. CPU 부하는 host_statistics(... HOST_CPU_LOAD_INFO ...), 메모리 압력은 sysctlbyname("kern.memorystatus_vm_pressure_level"), GPU 사용률은 IORegistry의 "Device Utilization %" 속성을 읽는다.

배울 것: Mach 호스트 통계 API, sysctl 키 읽기, IORegistry 속성 조회, C 구조체를 Swift에서 다루는 법.

2. SMC 센서 읽기 — 80바이트 ABI를 직접 두드리기

온도계 앱의 심장. 공식 API가 없는 AppleSMC 서비스에 IOKit으로 직접 명령을 보낸다. 고정된 80바이트 SMCParamStruct 구조체를 IOConnectCallStructMethod로 주고받고, 응답을 데이터 타입별로 해석한다.

// SMC에서 키 하나 읽는 흐름 (개념 코드)
// 1) AppleSMC 서비스 열기
let svc = IOServiceGetMatchingService(0, IOServiceMatching("AppleSMC"))
IOServiceOpen(svc, mach_task_self_, 0, &conn)
// 2) 80바이트 구조체로 "이 키 읽어줘" 명령 (selector 2 = handleYPCEvent)
IOConnectCallStructMethod(conn, 2, &input, size, &output, &outSize)
// 3) 데이터 타입별 디코딩
"sp78" → Int16 / 256.0   // 고정소수점 → 섭씨 온도
"flt " → Float32         // 4바이트 실수
"ioft" → UInt64 / 65536.0

배울 것: IOKit 사용자/커널 통신, FourCC(4글자 코드)를 UInt32로 패킹, 고정소수점 수 해석, 바이트 정렬·엔디안 감각.

3. CGEvent 탭 — 마우스 휠만 골라서 뒤집기

"마우스 휠은 반대로, 트랙패드는 그대로" 기능. HID 레벨에 CGEvent.tapCreate로 이벤트 탭을 걸어 스크롤 이벤트를 가로챈다. 핵심은 휠과 트랙패드를 구별하는 휴리스틱 — 불연속 이벤트(scrollWheelEventIsContinuous == 0)는 휠로 보고 델타를 뒤집고, 연속이지만 위상(phase) 정보가 없는 드라이버 합성 휠도 처리한다.

배울 것: CGEvent 탭 생성·재무장(tapDisabledByTimeout 대응), 런루프 소스 연결, HID 이벤트 필드(DeltaAxis1) 조작, 입력 장치 판별 로직.

4. ScreenCaptureKit — 창별 실시간 썸네일

⌘Tab 전환기가 앱 아이콘 대신 진짜 창 미리보기를 보여주는 비결. SCShareableContent로 창 목록을 받고, SCContentFilter(desktopIndependentWindow:) + SCScreenshotManager.captureImage(...)로 창 하나를 캡처한다. 화면 녹화 권한이 없으면 아이콘으로 우아하게 폴백한다.

배울 것: ScreenCaptureKit 최신 캡처 API, async Task + 이미지 캐시, 권한 미부여 시 폴백 설계.

5. Accessibility API + 비공개 SPI 끌어쓰기

창을 열거·활성화·최소화 해제하려면 손쉬운 사용(AX) API가 필요하다. AXUIElementCreateApplication(pid)로 앱의 창 목록을 읽고, 활성화는 AXUIElementSetAttributeValue/AXUIElementPerformAction로 한다. 압권은 공개 API에 없는 창 ID 매핑을 위해 비공개 함수 _AXUIElementGetWindow@_silgen_name으로 바인딩하는 부분 — 실전에서 SPI를 쓰는 법을 보여준다.

배울 것: AX 트리 탐색, 속성/액션 호출, @_silgen_name으로 비공개 심볼 연결(주의점 포함), CGWindowList와 AX 교차검증.

6. 최신 Core Audio 프로세스 탭 — 드라이버 없는 앱별 볼륨

예전엔 앱별 볼륨에 가상 오디오 드라이버(커널 확장)가 필요했다. Vorssaint은 macOS 14.4+의 새 API CATapDescriptionAudioHardwareCreateProcessTapAudioHardwareCreateAggregateDevice드라이버 설치 없이 특정 프로세스의 오디오를 가로채 볼륨을 조절한다. 통화가 끊기지 않도록 Zoom은 예외 처리하는 디테일까지 있다.

배울 것: Core Audio 프로세스 탭/애그리게이트 디바이스, 프로세스 오디오 객체 열거, OS 버전별 신·구 경로 분기.

7. TCC 권한 처리 + 전체 디스크 접근 "API 없음" 우회

AX는 AXIsProcessTrusted(), 화면 녹화는 CGPreflightScreenCaptureAccess()로 확인한다. 그런데 전체 디스크 접근(FDA)은 확인 API 자체가 없다. 그래서 보호된 경로(~/Library/.../TCC.db)를 실제로 읽어보고 성공 여부로 권한을 추론한다. 단, FDA는 폴링하면 거부 로그가 쌓이니 일부러 주기 폴링을 안 한다.

배울 것: TCC 권한 점검 API, 권한 폴링의 함정, "API 없는 상태"를 부작용 최소로 추론하는 우회 설계.

8. swiftc 직접 빌드 + 3단계 서명 + 공증 파이프라인

build.sh는 SwiftPM이 아니라 swiftc -O로 직접 컴파일하고 .app 번들을 손으로 조립한다. 서명은 Developer ID → 자체서명 → ad-hoc 3단계 폴백. ~/Documents 밖 임시폴더에서 번들을 만드는 이유까지 주석에 있다(파일 동기화가 붙이는 확장속성이 코드서명을 깨기 때문).

배울 것: swiftc 옵션, .app 번들 구조, codesign·hardened runtime·entitlements, notarize/staple/spctl 검증, 확장속성(xattr)과 서명의 관계.

7시스템 요구사항

실행 환경 · 권한 · 빌드 조건.

항목요구사항
OSmacOS 14 (Sonoma) 이상
Apple Silicon (arm64) — 빌드 타깃이 arm64 전용
샌드박스미적용 (시스템 전반 접근이 필요, 대신 TCC로 런타임 게이팅)
권한 (선택)손쉬운 사용(AX)·화면 녹화·알림·관리자(닫은 뚜껑용) — 전부 옵션
일부 기능 조건앱별 볼륨은 macOS 14.4+, 창 썸네일은 화면 녹화 권한 필요
빌드 도구Xcode Command Line Tools (swiftc), zsh
네트워크불필요 — 네트워크 호출·텔레메트리 0
운영 특성
권한 없으면 깨지지 않고 "그 기능만" 꺼진다

모든 권한이 선택이다. 화면 녹화를 안 주면 창 전환기가 썸네일 대신 아이콘으로 동작하고, 손쉬운 사용을 안 주면 스크롤 반전·전환기 키 처리만 꺼진다. 첫 실행 시 7단계 온보딩이 각 권한을 안내한다.

현실적 주의사항
신생 레포 · 무료 서명(ad-hoc) 빌드는 첫 실행에 한 번 손이 간다

유료 Apple 개발자 인증서로 서명된 정식 릴리스가 아닌 직접 빌드본은 ad-hoc 서명이라, 첫 실행 시 우클릭 → "열기"로 Gatekeeper를 통과시키거나 xattr로 격리 플래그를 지워야 한다. 또한 비교적 새 프로젝트라 기능·API가 빠르게 바뀔 수 있으니, 시스템 권한을 많이 요구하는 도구인 만큼 코드를 직접 살펴보고 신뢰를 판단하는 걸 권한다.

8직접 해볼 수 있는 실습 과제

난이도별 5개 — 보기만 말고 직접 두드려보기.

난이도: 입문

실습 1 — 소스에서 빌드하고 메뉴바에 띄우기 ★☆☆ 입문

저장소를 클론하고 build.sh로 직접 빌드해 메뉴바 아이콘을 올려본다. Apple Silicon + Xcode CLT만 있으면 된다.

git clone https://github.com/vorssaint/vorssaint-utils.git
cd vorssaint-utils
./build.sh --install      # 컴파일 + /Applications 설치 + 실행
# 메뉴바 아이콘 클릭 → 패널 확인

확인 포인트: 온보딩에서 권한을 하나만 켜고 나머지는 꺼둔 채, 어떤 기능이 살아있고 어떤 게 조용히 꺼지는지 관찰.

난이도: 초급

실습 2 — 숨은 CLI로 SMC 센서 덤프 읽기 ★☆☆ 입문~초급

이 앱은 GUI 말고도 진단용 CLI 서브커맨드를 품고 있다. SMC가 실제로 뱉는 키·값을 눈으로 확인해본다.

"/Applications/Vorssaint.app/Contents/MacOS/Vorssaint" --selftest
"/Applications/Vorssaint.app/Contents/MacOS/Vorssaint" --sensors  # SMC 센서 덤프

확인 포인트: 출력된 온도 키 이름을 메모하고, Services/SystemMonitor/에서 그 키를 쓰는 코드를 찾아 연결해본다.

난이도: 중급

실습 3 — 새 언어 번역 추가하기 ★★☆ 중급

타입세이프 로컬라이제이션을 체험한다. Core/Localization.swiftStrings 구조체에 새 언어(예: 한국어) 케이스를 추가하고, 빠진 필드가 있으면 컴파일러가 어떻게 잡아주는지 본다.

확인 포인트: 일부러 문자열 하나를 비우고 빌드해 컴파일 에러를 유발 → "번역 누락이 런타임이 아니라 빌드에서 잡힌다"는 설계 의도를 체감.

난이도: 중급

실습 4 — SMC 온도를 읽는 30줄짜리 미니 Swift 스크립트 ★★☆ 중급

SMCClient.swift를 참고해, IOKit으로 AppleSMC를 열고 CPU 온도 키 하나를 읽어 출력하는 독립 실행 스크립트를 직접 짜본다.

# 단일 파일 Swift 스크립트 실행
swift my_smc_reader.swift
# 흐름: IOServiceMatching("AppleSMC") → IOServiceOpen
#       → IOConnectCallStructMethod → sp78 디코딩 → print

확인 포인트: --sensors 덤프 값과 내 스크립트 결과가 일치하는지 비교. FourCC 패킹·고정소수점 해석을 손으로 구현하며 이해.

난이도: 고급

실습 5 — 새 서비스를 패턴대로 추가하기 ★★★ 고급

이 레포의 "싱글톤 ObservableObject + syncWithPreferences() + 권한 게이팅" 패턴을 그대로 따라, 작은 기능을 하나 추가해본다(예: "메뉴바에서 야간 모드 토글"). AppDelegate의 조립 부분에 등록하고, 설정 변화에 반응하게 만든다.

확인 포인트: 기존 서비스(예: ScrollInverter)를 템플릿 삼아 .shared 싱글톤·@Published 상태·syncWithPreferences()를 갖춘 클래스를 만들고, Combine 구독으로 즉시 반영되게 한다.

9관련 기술 심화 학습 로드맵

이 레포를 읽고 기여하기 위한 주차별 학습 경로 (macOS 시스템 프로그래밍).

1~2주차 — Swift + 메뉴바 앱 기초

목표: Swift 문법(옵셔널·프로토콜·클로저), SwiftUI 기본, AppKit으로 메뉴바 앱(NSStatusItem) 만들기.

자료: Apple Swift 공식 투어, "SwiftUI Tutorials"(developer.apple.com), NSStatusItem/MenuBarExtra 문서.

실습: 메뉴바에 아이콘 하나 올리고 클릭 시 팝오버를 띄우는 최소 앱. LSUIElement로 독에서 숨기기.

3주차 — IOKit · Mach · sysctl 기초

목표: 시스템 정보를 프레임워크 없이 읽는 감각. host_statistics, IORegistry 탐색, sysctl 키.

자료: Apple "IOKit Fundamentals", ioreg 명령으로 레지스트리 둘러보기, man sysctl.

실습: host_statistics로 CPU 사용률 출력, IORegistry에서 배터리 속성 읽기. 이 레포 SystemMonitor/와 대조.

4주차 — SMC 센서 ABI

목표: AppleSMC에 직접 명령을 보내 온도를 읽는다. 구조체 ABI·FourCC·고정소수점 해석.

자료: 공개된 SMC 리버스 자료(예: smcFanControl·iStats 오픈소스), 이 레포 SMCClient.swift 정독.

실습: 실습 4(미니 SMC 리더) 완성. 키 인덱스 순회로 가용 키 목록 덤프.

5주차 — CGEvent 탭 + Accessibility API

목표: 입력 이벤트 가로채기와 창 제어. 이벤트 탭 생애주기, AX 트리 탐색·액션.

자료: Apple "Quartz Event Services", "Accessibility" 프레임워크 문서, @_silgen_name 주의점 글.

실습: 스크롤 이벤트를 로깅하는 탭, 특정 앱의 창 목록을 AX로 열거. ScrollInverter.swift·Switcher/ 분석.

6주차 — ScreenCaptureKit + Core Audio 프로세스 탭

목표: 창 썸네일 캡처와 드라이버 없는 앱별 오디오. 최신(14.4+) API 흐름 이해.

자료: WWDC "Meet ScreenCaptureKit", "Capture audio from a process" 세션, Core Audio 탭 헤더.

실습: 창 하나를 PNG로 캡처, 특정 앱 오디오를 탭으로 받아 보기. WindowPreviewProvider·AppVolumeMixer 대조.

7~8주차 — 코드 서명·공증 + 오픈소스 기여

목표: .app 번들·codesign·hardened runtime·notarize 파이프라인 이해 후 기여.

자료: Apple "Notarizing macOS software", 이 레포 build.sh·Tools/notarize.sh·CONTRIBUTING.md.

실습: 새 번역·작은 서비스(실습 3·5)를 PR로. build.sh --test + --selftest 통과 확인.

10핵심 키워드 사전

이 저장소를 이해하는 데 필요한 핵심 용어.

메뉴바 앱
LSUIElement / Menu Bar Extra
독에 안 뜨고 화면 위 메뉴바에만 사는 보조 앱. Info.plistLSUIElement = true로 만든다. 앱 정책은 .accessory. iStat Menus·Bartender가 대표적. Vorssaint의 모든 UI는 이 메뉴바 아이콘에서 출발한다.
IOKit
IOKit / IOConnectCallStructMethod
macOS에서 하드웨어·드라이버와 통신하는 저수준 프레임워크. 사용자 앱이 커널 측 서비스(AppleSMC 등)에 명령을 보낼 때 IOServiceOpen으로 연결을 열고 IOConnectCallStructMethod로 구조체를 주고받는다. Vorssaint의 온도·전원·GPU 읽기가 전부 여기 의존.
SMC
System Management Controller
맥의 온도·팬·전원을 담당하는 보조 칩. 공개 API가 없어 IOKit으로 AppleSMC 서비스에 직접 접근한다. 키마다 데이터 타입(sp78 고정소수점, flt 실수 등)이 달라 바이트를 직접 해석해야 한다.
전원 어서션
Power Assertion (IOPMAssertion)
"지금은 잠들지 마"라고 시스템에 거는 선언. IOPMAssertionCreateWithName으로 PreventUserIdleSystemSleep(시스템 잠 방지)·PreventUserIdleDisplaySleep(화면 잠 방지)을 만들고, 끝나면 IOPMAssertionRelease로 푼다. "화면 안 꺼지게" 기능의 핵심.
닫은 뚜껑 모드
Clamshell / pmset disablesleep
맥북 뚜껑을 닫아도 계속 돌게 하는 모드. pmset disablesleep 1로 켜고 끝나면 0으로 되돌린다. 관리자 권한이 필요해, Vorssaint은 pmset만 비밀번호 없이 실행하도록 제한된 sudoers 규칙을 깔거나 그때그때 관리자 인증을 받는다. 앱이 죽어도 다음 실행에서 잠 설정을 복구한다.
CGEvent 탭
CGEvent Tap
시스템 입력 이벤트 흐름에 끼어들어 이벤트를 보거나 바꾸는 장치. CGEvent.tapCreate로 만든다. Vorssaint은 스크롤 이벤트만 가로채 마우스 휠 방향을 뒤집는다. 손쉬운 사용(AX) 권한이 필요하고, 탭이 타임아웃으로 꺼지면 다시 켜는 재무장 처리가 중요하다.
ScreenCaptureKit
SCContentFilter / SCScreenshotManager
macOS 12.3+의 현대적 화면 캡처 프레임워크. 창 하나만 골라(SCContentFilter(desktopIndependentWindow:)) 이미지로 캡처할 수 있다. 창 전환기의 실시간 썸네일이 이걸로 만들어진다. 화면 녹화 권한이 필요.
Accessibility API
AX (AXUIElement)
원래 보조기술(스크린리더)용이지만, 다른 앱의 창·버튼을 프로그램으로 제어하는 통로로도 쓰인다. AXUIElementCreateApplication(pid)로 앱의 창을 읽고, 속성을 바꿔 최소화 해제·활성화한다. 창 전환기·창 정리 기능의 토대.
Core Audio 프로세스 탭
CATapDescription / AudioHardwareCreateProcessTap
macOS 14.4에서 추가된 API. 가상 오디오 드라이버를 깔지 않고도 특정 프로세스의 오디오 출력을 가로챌 수 있다. 이를 애그리게이트 디바이스로 묶어 앱별 볼륨을 구현한다. 예전 방식(커널 확장)보다 훨씬 가볍고 안전하다.
비공개 SPI
@_silgen_name / _AXUIElementGetWindow
Apple이 공식 문서로 공개하지 않은 내부 함수(SPI)를 Swift에서 직접 부르는 기법. @_silgen_name으로 심볼 이름을 박아 연결한다. 공개 API에 없는 "AX 요소 → 윈도우서버 창 ID" 매핑에 쓰지만, OS 업데이트로 사라질 수 있어 신중히 다뤄야 하는 회색지대 기술이다.
TCC
Transparency, Consent & Control
macOS 권한 동의 시스템. 화면 녹화·손쉬운 사용·전체 디스크 접근 등을 사용자가 명시적으로 허용해야 한다. Vorssaint은 각 기능을 필요한 권한에 묶고, 미허용이면 그 기능만 끈다. 전체 디스크 접근은 확인 API가 없어 보호 경로를 읽어보며 우회 추론한다.

11참고 링크

더 깊이 파고들기 위한 공식 자료.