#+TITLE: Emacs macOS Build — UAZoom Accessibility Patch
#+AUTHOR: Martin Sukaný
#+DATE: 2026-02-23
#+STARTUP: overview

* Emacs macOS Build s macOS Zoom Accessibility

Návod na sestavení GNU Emacs pro macOS s patchem, který opravuje sledování
kurzoru při zapnutém macOS Zoom ("Follow keyboard focus"). Postup vychází
z reálné zkušenosti z buildu v únoru 2026.

** Proč je to potřeba

macOS Zoom funguje jako magnifier přes celou obrazovku nebo v okně. Režim
"Follow keyboard focus" by měl automaticky posouvat viewport za textovým
kurzorem. Ve vanilla GNU Emacs to ale nefunguje.

Příčina: Emacs má vlastní NSView, který neimplementuje NSAccessibility protokol
a hlavně nevolá =UAZoomChangeFocus()= z =HIServices/UniversalAccess.h=. Tuto funkci
volají iTerm2 (=PTYTextView.m=) i Chromium (=render_widget_host_view_mac.mm=) a
je to jediný spolehlivý způsob, jak Zoom přinutit sledovat kurzor v custom view.

Standardní NSAccessibility notifikace samy o sobě nestačí — Zoom je event-driven
a potřebuje explicitní volání =UAZoomChangeFocus()= s aktuální pozicí kurzoru
v souřadnicích AX obrazovky (origin vlevo nahoře, tzn. y-osa je otočená oproti
Cocoa, kde origin je vlevo dole).

Patch je uložený v repozitáři =martin/emacs-doom= na Giteě:
=patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch=

** Krok 1: Prerekvizity

Nejprve nainstaluj Xcode Command Line Tools (nutné pro kompilaci):

#+begin_src sh
xcode-select --install
#+end_src

Pak nainstaluj závislosti přes Homebrew:

#+begin_src sh
brew install autoconf libgmp texinfo pkg-config gnutls libxml2 jansson tree-sitter librsvg imagemagick
#+end_src

Pro native compilation (volitelné, výrazně rychlejší Emacs):

#+begin_src sh
brew install libgccjit
#+end_src

** Krok 2: Stažení zdrojového kódu Emacs

#+begin_src sh
git clone https://github.com/emacs-mirror/emacs.git
cd emacs
git checkout emacs-31
#+end_src

Větev =emacs-31= je aktuálně doporučená stable větev. Až bude k dispozici =emacs-32=,
použij tu.

** Krok 3: Aplikace accessibility patche

Stáhni config repozitář s patchem:

#+begin_src sh
git clone https://git.apps.sukany.cz/martin/emacs-doom.git /tmp/emacs-doom-config
#+end_src

Aplikuj patch:

#+begin_src sh
git am /tmp/emacs-doom-config/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch
#+end_src

Ověř, že patch byl aplikovaný:

#+begin_src sh
grep "UAZoomChangeFocus" src/nsterm.m | head -3
#+end_src

Výsledkem by mělo být několik řádků s voláním =UAZoomChangeFocus=. Pokud grep
nic nevrátí, patch se neaplikoval správně.

Co patch přidává do =src/nsterm.m=:

- Volání =UAZoomChangeFocus()= po každém pohybu kurzoru — koordináty jsou
  přepočítány z Cocoa (origin vlevo dole) do AX prostoru (origin vlevo nahoře)
  ručním y-flipem: =primaryH - y - height=
- =NSAccessibilitySelectedTextChangedNotification= — notifikace po pohybu kurzoru
- =NSAccessibilityFocusedUIElementChangedNotification= — notifikace při zaměření okna
- =accessibilityBoundsForRange:= — nové API (macOS 10.10+)
- Fallback přes starší =AXBoundsForRange= pro starší verze
- =NSAccessibilityTextAreaRole= pro EmacsView

** Krok 4: Konfigurace

Spusť autogen a pak configure. Základní konfigurace (bez native compilation):

#+begin_src sh
./autogen.sh
#+end_src

#+begin_src sh
./configure \
  --with-ns \
  --with-tree-sitter \
  --with-gnutls \
  --with-xml2 \
  --with-json \
  --with-imagemagick \
  --with-rsvg \
  --with-mailutils \
  --without-x \
  --without-dbus \
  CFLAGS="-O2 -g3"
#+end_src

S native compilation (pokud máš libgccjit):

#+begin_src sh
./configure \
  --with-ns \
  --with-native-compilation=aot \
  --with-tree-sitter \
  --with-gnutls \
  --with-xml2 \
  --with-json \
  --without-x \
  CFLAGS="-O2 -g3"
#+end_src

** Krok 5: Build

#+begin_src sh
make -j$(sysctl -n hw.ncpu)
#+end_src

Parametr =-j$(sysctl -n hw.ncpu)= použije všechna dostupná jádra. Build trvá
přibližně 5–15 minut podle hardwaru.

Po dokončení build vytvoří =nextstep/Emacs.app=:

#+begin_src sh
make install
#+end_src

** Krok 6: Instalace

Přesuň Emacs.app do /Applications:

#+begin_src sh
cp -r nextstep/Emacs.app /Applications/Emacs.app
#+end_src

Vytvoř wrapper script. Pozor: použij wrapper script, ne symlink. Symlink nefunguje
správně — Emacs počítá cesty k Lisp souborům z =argv[0]=, a přes resolved symlink
je najde špatně. Projeví se to chybami jako:

#+begin_example
Warning: arch-dependent data dir 'Contents/MacOS/libexec/': No such file or directory
Warning: Lisp directory 'Contents/Resources/lisp': No such file or directory
#+end_example

Správné řešení — wrapper script:

#+begin_src sh
sudo tee /usr/local/bin/emacs > /dev/null << 'EOF'
#!/bin/sh
exec /Applications/Emacs.app/Contents/MacOS/Emacs "$@"
EOF
sudo chmod +x /usr/local/bin/emacs
#+end_src

Ověření:

#+begin_src sh
which emacs
emacs --version
#+end_src

Výstup =which emacs= musí být =/usr/local/bin/emacs=.

** Krok 7: Accessibility permission

Po každém novém buildu (nový binární soubor) je potřeba znovu udělit oprávnění.
Bez toho =UAZoomChangeFocus()= neovlivní Zoom viewport — volání proběhne, ale
macOS ho ignoruje bez Accessibility přístupu.

Postup:
1. Otevři System Settings → Privacy & Security → Accessibility
2. Přidej =/Applications/Emacs.app=
3. Zapni toggle vedle Emacs

Pokud Emacs v seznamu už je (ze starého buildu), odeber ho a přidej znovu —
macOS identifikuje aplikace i podle binary hash a starý záznam neplatí.

** Krok 8: Doom Emacs sync

Pokud používáš Doom Emacs, spusť sync po instalaci nového Emacsu:

#+begin_src sh
~/.emacs.d/bin/doom sync
#+end_src

Pokud doom hlásí, že nenajde Emacs, zkontroluj:

#+begin_src sh
which emacs
#+end_src

Musí vrátit =/usr/local/bin/emacs=. Pokud vrátí jinou cestu, je problém
s PATH nebo chybí wrapper script.

** Krok 9: Ověření Zoom funkcionality

Nastav Zoom v System Settings:
1. System Settings → Accessibility → Zoom
2. Zapni "Use keyboard shortcut to zoom" nebo "Use scroll gesture with modifier keys to zoom"
3. Zoom style: Full Screen nebo Window (obojí funguje)
4. Klikni na "Advanced..." → záložka "Zoom Follows the Cursor"
5. Nastav "Zoom follows: Keyboard focus"

Otevři Emacs a začni psát. Zoom viewport by měl automaticky sledovat textový
kurzor při každém pohybu (šipky, PageUp/Down, přechod mezi buffery).

** Řešení problémů

*Zoom nesleduje kurzor*

Nejčastější příčiny jsou dvě. Za prvé, chybí Accessibility permission — viz Krok 7.
Za druhé, patch nebyl správně aplikovaný. Ověření:

#+begin_src sh
grep "UAZoomChangeFocus" /Applications/Emacs.app/Contents/MacOS/../../../src/nsterm.m 2>/dev/null || \
  echo "Zkontroluj zdrojový kód nebo přelož znovu"
#+end_src

Nebo v build adresáři:

#+begin_src sh
grep "UAZoomChangeFocus" src/nsterm.m | wc -l
#+end_src

Pokud výsledek je 0, patch chybí. Aplikuj znovu a přelož.

*Build selže na nsterm.m*

Zkontroluj, zda jsou nainstalované Xcode Command Line Tools:

#+begin_src sh
xcode-select -p
#+end_src

Pokud cesta neexistuje nebo vrátí chybu, spusť =xcode-select --install= znovu.

*doom sync hlásí "emacs not found"*

Zkontroluj wrapper script:

#+begin_src sh
cat /usr/local/bin/emacs
#+end_src

Soubor musí existovat a obsahovat =exec /Applications/Emacs.app/Contents/MacOS/Emacs "$@"=.
Pokud neexistuje, viz Krok 6.

*Chyby "libexec not found" nebo "lisp not found" při startu*

Příčina je symlink místo wrapper scriptu. Zkontroluj:

#+begin_src sh
ls -la /usr/local/bin/emacs
#+end_src

Pokud výstup ukazuje =->= (symlink), smaž ho a vytvoř wrapper script podle Kroku 6.

** Reference

- Patch repozitář: [[https://git.apps.sukany.cz/martin/emacs-doom][martin/emacs-doom]]
- Podobný problém v Ghostty: https://github.com/nicowillis/Ghostty/issues/4053
- Stejný přístup v iTerm2: =src/PTYTextView.m=, funkce =refreshAccessibility=
- Stejný přístup v Chromium: =render_widget_host_view_mac.mm=, funkce =OnSelectionBoundsChanged=
