2012년부터 리눅스를 써 온 필자가 MacOS로 넘어가며 겪은 경험과, 소프트웨어 관리, 창 관리, 키보드/단축키, 원격 개발, 게임 등에 관한 팁을 정리합니다.
KDE 사용자를 위한 MacOS
2025년 2월 23일 2012년경부터 줄곧 리눅스를 써 오다가(처음엔 Arch, 2015년부터는 NixOS) MacOS로 넘어왔습니다. 이 글은 그 경험을 정리한 것입니다.
저는 쓰는 도구에 대해 전문가가 되려고 합니다 — 매일 쓰는 도구라면 시간이 들더라도 동작 원리를 이해하고, 가능한 모든 요령을 배우려 하죠. 가끔은 아예 직접 만들어 쓰기도 합니다. 도구를 만드는 사람, 그리고 쓰는 사람으로서 저에게 핵심은 이렇습니다:
도구의 저자는 그 분야에서 세계 최고 전문가입니다. 그들은 무엇이 통하고 무엇이 통하지 않는지, xkcd://1205를 대놓고 무시하며, 자신의 시간을 들여 파악했습니다. 그리고 그 지식을 자신이 만든 도구에 담아 모든 사용자에게 무료로 제공합니다. 도구 저자로서의 목표는, 당신의 전문성을 사용자에게 전수함으로써 시간을 “확장 가능하게” 절약하게 해 주는 것입니다. 도구 사용자로서 성공의 길은, 당신이 유용하다고 생각하는 형태로 도구를 비틀어 맞추는 것이 아니라, 그 도구가 “의도한 방식”을 배우는 것입니다.
그래서 MacOS로 넘어올 때도, 리눅스를 억지로 흉내 내는 데 매달리기보다, 시스템의 바탕이 되는 사고방식을 파악하고자 했습니다. 그런데 실패했습니다 — 저 같은 사람을 겨냥한 자료가 없거나, 있어도 검색이 잘 되지 않더군요. 유일한 예외는 아주 훌륭하고 강력 추천하는 이 글입니다.
https://blog.xoria.org/macos-tips/
(부분적으로는 제 투정의 결과물이기도 합니다 :)
그럼 제가 배운 것들을 보태 보겠습니다!
맥을 리눅스보다 선호할 이유 중 일부는 10년 전과 같습니다:
하드웨어가 약간 더 좋습니다. 맥북은 강력한 직사각형 판, 스탠리 큐브릭이 발명했고 그 이후로 단 한 번도 능가되지 않은 디자인입니다. PC 노트북은 대체로 성능이 부족하거나 LED 크리스마스 트리죠. 포트 대강 방향으로 던져도 붙고, 선에 걸려 넘어져도 쉽게 빠지는 마그네틱 충전기는 참 좋습니다. 스피커도 실제로 쓸 만하고요.
소프트웨어는 대체로 그냥 잘 돌아갑니다! 이건 재미있게도 예전과 같습니다 — 리눅스도 확실히 좋아졌습니다! 예전엔 와이파이가 큰 골칫거리였는데, 이젠 대부분 그냥 됩니다. 하지만 주변기기는 더 복잡해졌습니다 — 웹캠이 여러 개고, 마이크도 셋, 블루투스까지 관리해야 하니, 합쳐 놓으면 리눅스에선 여전히 골치 아픕니다.
하지만, 이 두 가지는 예전부터 사실이었고, 그때도 저를 어두운 면으로 유혹하지는 못했습니다.
달라진 건 다음과 같습니다:
CPU가 비약적으로 좋아졌습니다. 아주 빠르고, 아주 시원하고, 아주 조용하며, 전력 소모도 적습니다. 사실 이게 지난 몇 대의 리눅스 노트북에서 가장 성가셨던 부분이었어요 — 계산 성능이 늘어나는 만큼 소음도 확 늘었습니다.
원격 개발(리모트 개발) 환경으로 뛰어들 마음의 준비가 됐습니다. 지금까지는 노트북을 주 개발 머신으로 봤고, 컴파일과 테스트를 모두 수행하며, 가능하면 배포 환경과도 어느 정도 닮아 있어야 한다고 여겼습니다. 이제는 얇은 클라이언트로 생각합니다. 코드와 에디터만 있고, 내가 만들고 있는 것을 꼭 실행 하지는 않아도 됩니다. 실제 작업은 어딘가 애매모호한 “원격 머신”에서 일어나는 거죠.
실례로 — 예전엔 IntelliJ Rust와 rust-analyzer를 해킹할 때, Windows 전용 버그를 재현하려면 (게임용) 윈도우로 듀얼부팅해서 그쪽에서 작업했습니다. 이 세팅은 점점 답답해졌습니다. 잘 꾸며 둔 개발 환경을, 가끔만 쓰는 OS 위에서 다시 만들어야 했거든요. 게다가 보안 문제도 있습니다 — 윈도우 OS에 제 SSH 키에 손쉽게 접근권을 주는 게 편하지 않았습니다.
그래서 어느 순간부터는 실 윈도우를 부팅하는 대신, 리눅스 박스에서 윈도우 VM을 띄우는 방식으로 바꿨습니다. 그리고 중요한 점 — 실제 개발은 여전히 리눅스 호스트에서 했고, 윈도우는 오로지 코드를 실행하는 용도로만 썼습니다.
그리고 그게 제가 Mac에서 개발하려는 방식입니다 — 코드를 편집하는 GUI는 Mac이 호스팅하고, 실제 코드를 돌릴 머신은 무엇이든 상관없게요.
또한 원격 개발을 하는 방식 자체가 서서히 더 나은 방향으로 변하고 있는 것도 중요합니다. 전통적인 원격 방식(ssh와 X 포워딩)은 타깃 애플리케이션을 원격 호스트에서 돌리고, 인터페이스와 사용자 상호작용을 투명하게 주고받습니다. 이 방식은, 동기 함수 호출을 RPC로 몰래 바꾸는 게 잘 안 되는 이유와 같습니다: 지연이 있고, 실패가 있고, 상태 동기화가 있습니다. 분산 시스템이 아니라는 척을 유의미하게 할 수는 없습니다.
저는 원격 개발을 접근하는 더 생산적인 방식은, 애플리케이션을 로컬에서 돌리되, 다른 컴퓨터의 존재를 명시적으로 인지하는 것이라고 생각 합니다. 그리고 VS Code나 WezTerm 같은 최신 앱들은 그렇게 동작합니다. 안타깝게도, 이런 원칙으로 제대로 동작하는 좋은 셸(아니, 그냥 좋은 셸조차) 아직 없습니다. 이야기가 샙니다, 이쯤에서 멈추죠.
어쨌든, 빠른 CPU와 원격 개발에 대한 새로운 관점이 저를 움직였고, 지금은 맥 사용자입니다.
오랜 NixOS 생활 끝에 다음 두 가지 기능의 유용성에는 확신이 섰습니다:
이 아이디어들의 Nix 구현이 마냥 마음에 든 건 아닙니다 — 현존 최고이긴 하지만, 여전히 충분히 좋지는 않습니다. 게다가 Nix는 MacOS에서도 쓸 수 있지만, 그건 플랫폼에 역행하는 일이 될 겁니다.
그래서 저는 homebrew를 씁니다. 핵심은 패키지 선언 관리를 Brewfile로 한다는 점입니다. 이 글을 강력 추천합니다:
https://matthiasportzel.com/brewfile/
요약하면 다음을 실행합니다.
brew bundle install --cleanup --file=~/config/Brewfile --no-lock
이렇게 하면 설치된 소프트웨어가 제 설정 파일에 적힌 내용과 동기화됩니다. 앱을 일시적으로 설치하고 싶으면 “일반” brew install을 쓰고, 다음에 brew bundle install을 실행할 때 그 앱은 제거됩니다.
이 긴 걸 매번 치는 건 금방 피곤해집니다(fish 셸의 영리한 히스토리가 있어도요). 그래서 설정 관리를 위한 작은 러스트 유틸리티를 하나 만들어 config(이름 그대로입니다, 소스)라고 부르고, 다음을 할 수 있게 했습니다:
$ config brew # bundle install 실행
$ config edit # 내 에디터로 설정 파일 열기
$ config link # 도트파일 심볼릭 링크 연결
NixOS를 쓸 때와 마찬가지로, 도트파일 관리를 위해 home-manager나 gnu stow 같은 걸 쓰지 않고, 도트파일 저장소에서 각 도구가 설정을 기대하는 위치로 링크만 거는 작은 스크립트를 씁니다.
Brewfile이 락 파일을 지원하긴 하지만 저는 쓰지 않습니다. 이유는 이렇습니다. GNU/리눅스 시스템에서 믿고 의존할 수 있고, 안정적 인터페이스를 보장하며, 그 안정성에 전담 게이트키퍼가 있는 부분은 커널입니다. 나머지는 수많은 구성요소를 서로 엮어 만든 것이고, 서로 다른 주체가 각기 다른 수준의 안정성으로 유지보수합니다. 결정적으로, 사용자 대상 데스크톱 소프트웨어를 만드는 데 중요한 대부분의 인터페이스는 커널 바깥에 있습니다. 그래서 X/Wayland가 업데이트 후 망가져도, 적어도 고통 없이 롤백할 수 있도록 모든 것 을 고정(pinning)하려면 니크스가 정말 필요합니다.
맥에선 베이스 시스템이 훨씬 더 방대합니다. 브라우저가 업데이트로 깨질까 봐 걱정할 현실적 이유가 없습니다. 애플이 배포하고, 부드러운 업그레이드를 만들 조직 역량이 있으니까요.
그래서 실제로 깨질 수 있는 건 최종 애플리케이션들뿐이고, 그런 부분에서 모든 것을 바짝 고정했을 때 얻는 이득은 크지 않다고 봅니다.
지금까진 제 판단이 틀리지 않았습니다. 시스템이나 소프트웨어 업데이트 모두 순조로웠습니다. 비쿼티(non-QWERTY) 레이아웃을 쓰는 저 때문에 Ghostty가 한 번 깨지긴 했는데, 다운그레이드는 다음만큼 쉬웠습니다:
curl https://raw.githubusercontent.com/Homebrew/homebrew-cask/99378d4eaa63a12947b8eccd526a6d9d27564cce/Casks/g/ghostty.rb > ghostty.rb
brew uninstall ghostty && brew install --cask ./ghostty.rb
레이아웃 얘기가 나와서 말인데, 재부팅 후 로그인 화면에서 제 레이아웃(workman)을 쓰는 법은 아직 못 찾았습니다.
창 관리는 제 습관이 조금 특이합니다. 타일링 윈도우 매니저는 쓰지 않습니다. 그리고 디스플레이도 하나만 씁니다(노트북을 외부 모니터에 연결하면, 덮개를 닫습니다). 한 번에 하나의 애플리케이션으로만 일할 수 있고, 그 앱이 전체 화면이길 선호합니다. 그래서 제가 필요한 건 창 관리가 아니라, 전체 화면 앱들 사이를 빠르게 전환하는 능력입니다.
Windows 7은 두 가지 기능로 이 워크로드를 완벽히 해결했습니다:
첫째, 시작 메뉴 옆에 여러 애플리케이션을 고정(pin)할 수 있습니다. 그리고 Win + 1, Win + 2, Win + 3으로 앱을 전환합니다. 단축키를 누르면 앱이 실행 중이 아니면 실행되고, 이미 떠 있다면 그 창을 앞으로 가져옵니다.
둘째, Windows 7은 가벼운 타일링을 구현했습니다: Win + 위 화살표는 창 최대화, Win + 왼쪽/오른쪽은 화면 절반에 타일링합니다.
MacOS도 요즘은 기본으로 비슷한 가벼운 타일링을 제공합니다. 이건 좋습니다.
앱 전환은 기본 상태로는 좋지 않습니다. 앱을 전체 화면으로 만들어 가상 데스크톱 하나를 통째로 차지하게는 할 수 있습니다. 하지만 짜증나게도, 이렇게 전체 화면 앱들 사이를 전환하면 끊을 수 없는 애니메이션이 나옵니다. 10여 년 전 회사 맥에서 이것 때문에 고생했던 기억이 있는데, 적어도 인터페이스의 “안정성”은 위안이 되네요(진심입니다만, 기억이 틀렸을 수도 있고요. 어쨌든 Mac OS는 수년 전 모습과 거의 똑같아 보입니다. 좋은 일입니다).
그래도 좋은 점은, hammerspoon으로 제가 원하는 기능을 스크립트로 아주 쉽게 만들 수 있다는 겁니다:
local apps =
{
F1 = "Ghostty",
F2 = "Visual Studio Code",
F3 = "Safari",
F4 = "Slack",
}
for key, app in pairs(apps) do
hs.hotkey.bind({"cmd"}, key, function()
hs.application.launchOrFocus(app)
end)
end
이를 리눅스에서의 동등한 스크립트와 비교해 보면 교훈적입니다:
win-or-app.sh
#!/bin/sh
function find_window {
windows=$(wmctrl -lx | awk -v name="$1" '$3 ~ name' | grep -v "Hangouts")
}
key=$1
bin=$2
shift 2
args=$@
find_window $key
if [ $? != 0 ];
then
$bin $args
sleep 0.2
fi
find_window $key
set -- $windows
if [ $1 ]; then
wmctrl -ia $1
fi
xbindkeysrc
"~/config/scripts/win-or-app.sh jetbrains-idea-ce idea"
Control + F2
"~/config/scripts/win-or-app.sh Chromium.Chromium chromium-browser"
Control + F3
여기서는 여러 부분을 이어 붙여야 했습니다:
wmctrlxbindkeysbash물론, wayland로 넘어가면서 이 리눅스 버전은 깨졌습니다.
그래서, 이 부분에서 맥은 기본 상태로는 Windows 7이나 KDE(요즘은 이 기능이 내장되어 있기도 합니다)보다 못하지만, 리눅스보다 제가 원하는 걸 하게 만들기는 더 쉽습니다! 다시 말하지만, 데스크톱 소프트웨어를 위한 안정적인 인터페이스가 있다는 건 정말 큰 도움이 됩니다!
비슷한 맥락에서, 리눅스에선 클립보드 도구를 감싸기 위해 ctrlc / ctrlv라는 커맨드라인 별칭을 썼는데, 맥에선 pbcopy / pbpaste가 별다른 인자 없이 필요한 걸 그대로 해 줍니다.
제 워크플로에서 가장 큰 개선 중 하나는 여러 해 전 홈 로우 컴퓨팅으로 바꾼 것입니다. 기본 관찰은 이렇습니다. 방향키는 가장 자주 필요한 키인데, 누르려면 손목을 홈 포지션에서 완전히 떼야 합니다. 악명 높은 Emacs의 ctrl+n, ctrl+p, ctrl+f, ctrl+b도 끔찍하긴 하지만, 방향키보다는 낫습니다. 타이핑 흐름을 깨지 않고 쓸 수 있으니까요.
하지만 홈 로우에서 방향키를 완전히 쓸 수 있습니다! 간단한 방법 하나는 장치 수준에서 Caps Lock + hjkl을 방향키로 동작하게 하는 것입니다. 또는 요즘 제가 하는 것처럼 space + ijkl도 좋습니다. Moonlander 같은 프로그래머블 키보드를 쓰면 이런 기능은 당연히 따라옵니다. 하지만 일반 노트북 키보드로도 할 수 있고, 오히려 그쪽이 더 값집니다!
여기서 표준적인 MacOS 앱은 Karabiner-Elements입니다. 두 부분으로 되어 있습니다: 가상 키보드를 제공하는 드라이버. 제가 이해하기로는, 이게 아주 훌륭해서 다른 대안 프로젝트들도 이 드라이버를 씁니다. 그리고 이 드라이버를 설정하는 GUI가 있습니다…
저는 리눅스 침입자 같은 도구를 쓰기보다, 맥 네이티브인 karabiner를 꼭 쓰고 싶었는데, GUI 부분이 별로였습니다. 키보드 설정은 GUI의 정말 좋은 사용처입니다. 예를 들어, Moonlander의 설정 도구는 끝내줍니다:
https://configure.zsa.io/moonlander/layouts/default/latest/0
하지만 Karabiner에는 이런 종류의 GUI가 실제로 없습니다. GUI를 피하자니 JSON으로 설정해야 하는데, 제가 본 것 중 가장 장황한 JSON 포맷이라 직접 쓰기엔 너무 불편합니다.
그래서 리눅스에서 쓰던 것과 같은 해결책, kanata를 썼습니다. 간단한 S-식으로 설정하는 사랑스러운 러스트 도구입니다. 최근 맥도 지원하게 되었고, 앞서 말한 Karabiner 프로젝트의 드라이버를 사용합니다. 맥용 패키지가 없길래 homebrew에 추가했는데, 꽤 쉬웠습니다(니크스로 패키징 방법을 파악하는 것보다 확실히 쉬웠습니다). 참고로, 제가 쓰는 설정은 이렇습니다:
(defcfg
process-unmapped-keys yes
concurrent-tap-hold yes
)
(defsrc
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
grv 1 2 3 4 5 6 7 8 9 0 - = bspc
tab q w e r t y u i o p [ ] \
caps a s d f g h j k l ; ' ret
lsft z x c v b n m , . / rsft
lctl lalt lmet spc rmet ralt)
(deflayer qwerty
esc 🔅 🔆 f3 f4 f5 f6 ◀◀ ▶⏸ ▶▶ 🔇 🔉 🔊
grv 1 2 3 4 5 6 7 8 9 0 - = bspc
tab q w e r t y u i o p [ ] \
esc a s d f g h j k l ; ' ret
lsft @lc @la @lm v b n m @rm @ra @rc rsft
lctl lalt lmet @sp rmet ralt)
(deflayer motion
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
grv @1 @2 @3 @4 @5 6 7 8 9 0 - = bspc
tab q w e r t y pgup up pgdn p [ ] \
esc a s d f g bspc lft down rght del ' ret
lsft @lc @la @lm v b ret m M-lft M-rght / rsft
lctl lalt lmet @sp rmet ralt)
(defalias
1 M-f1
2 M-f2
3 M-f3
4 M-f4
5 M-f5
sp (tap-hold-release-keys 200 200 spc (layer-toggle motion) (a s d f g))
lc (tap-hold 200 200 z lctl)
la (tap-hold 200 200 x lalt)
lm (tap-hold 200 200 c lmet)
rm (tap-hold 200 200 , rmet)
ra (tap-hold 200 200 . lalt)
rc (tap-hold 200 200 / rctl))
fn/글로브 키 주변에 뭔가 이상한 점이 있어서, 미디어 컨트롤 키는 수동으로 다시 넣어야 했습니다.
맥의 멋진 점 하나는 앞서 말한 Emacs 단축키(ctrl+a나 ctrl+e 같은)가 거의 어디서나 통한다는 겁니다. “거의”이긴 합니다 — 제 경우 Slack에서는 안 되는 것 같습니다? 맥의 잘못은 아니지만, 그래도 유용성이 꽤 줄어듭니다.
비슷하게, 줄 맨 앞으로 가는 “정상적인” 방법이 Home인지 Cmd+Left인지에 대해 이견이 있었습니다. 저는 다음 DefaultKeyBinding.Dict 파일로 이를 통일했습니다:
~/Library/KeyBindings/DefaultKeyBinding.Dict
{
"\UF729" = "moveToBeginningOfLine:"; /* Home */
"$\UF729" = "moveToBeginningOfLineAndModifySelection:"; /* Shift + Home */
"\UF72B" = "moveToEndOfLine:"; /* End */
"$\UF72B" = "moveToEndOfLineAndModifySelection:"; /* Shift + End */
}
일반적으로, 맥에 기대했던 큰 소망 중 하나는 시스템 전반의 단축키 일관성이었는데, 완전히 실현되지는 않았습니다. 예를 들어, 세로/가로 분할에 대한 표준 단축키가 있어서 셸과 에디터에서 동일하게 쓰길 바랐지만, 그런 건 없는 듯합니다.
그래도 일관된 단축키가 하나 있습니다 — Cmd+Shift+? 는 “커맨드 팔레트”를 토글합니다 — 애플리케이션 메뉴 전반을 퍼지 검색하는 것으로, Emacs의 M-x와 매우 비슷합니다. 재미있게도, 리눅스에선 M-x 같은 인터페이스가 필요해 브라우저로 Vivaldi를 썼는데, 맥에선 운영체제 설계의 자연스러운 결과로 이걸 기본 제공받습니다. Cmd+Shift+?, “pin”, Enter로 탭을 고정할 수 있는 건 꽤 좋습니다. 전용 커맨드 팔레트를 가진 앱에선 Cmd+Shift+?를 앱 전용 커맨드로 오버라이드합니다. Moonlander에는 이 단축키를 위한 전용 키도 따로 배정해 두었습니다.
기대했던 것보다 훨씬 좋습니다! 두 가지를 알게 됐습니다:
첫째, 요즘 맥은 꽤 강력한 GPU를 갖고 있습니다. 예를 들어, 발더스 게이트 III는 모든 화려한 그래픽을 켠 상태로도 네이티브로 아무 문제 없이 돌아갑니다. 하드웨어는 분명히 준비되어 있고, 소프트웨어가 가끔 따라오지 못할 뿐입니다.
하지만 둘째, 윈도우 에뮬레이션 레이어가 매우 성숙해졌습니다. 예를 들어, Windows 버전의 Path Of Exile 2가 그냥 잘 됩니다.
그리고 물론 Factorio는 네이티브로 제공됩니다.
실제로는 리눅스/윈도우 듀얼부팅을 쓰던 때보다 요즘 더 게임을 하게 됩니다 — 조용하고 상대적으로 시원한 노트북이 경험을 크게 끌어올려 줍니다!
일단 여기까지입니다! 실제 원격 개발에 관한 추가 팁도 좀 있지만, 그건 별도의 글로 미루겠습니다. 그동안은 이 글을 참고해 보세요: https://peter.bourgon.org/blog/2011/04/27/remote-development-from-mac-to-linux.html