#+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=