refactor: Python ctypes daemon for macOS Zoom (no external tools)
This commit is contained in:
99
config.el
99
config.el
@@ -888,64 +888,73 @@ Keeps the status bar and tab bar fully visible at any zoom level.")
|
||||
;;; ============================================================
|
||||
;;; ACCESSIBILITY — MACOS ZOOM CURSOR TRACKING (SPC z t)
|
||||
;;; ============================================================
|
||||
;; Writes cursor screen position to ~/.emacs-cursor-pos for Hammerspoon.
|
||||
;; Hammerspoon posts a real mouseMoved HID event — required for macOS Zoom
|
||||
;; "Follow mouse cursor" (CGWarpMouseCursorPosition is silently ignored).
|
||||
;; Python daemon (scripts/macos-zoom-daemon.py) čte frame-relative
|
||||
;; souřadnice kurzoru ze stdin, získá přesnou pozici Emacs okna přes
|
||||
;; macOS AX API, a postne real CGEvent.mouseMoved → macOS Zoom sleduje.
|
||||
;;
|
||||
;; Debounced via run-with-idle-timer 0.05s: rapid j/k spam produces one write.
|
||||
;; CCM compatible: idle timer fires after ccm recenter + redisplay.
|
||||
;; Výhody oproti přímému Emacs warpu:
|
||||
;; - CGEventPost (ne CGWarpMouseCursorPosition) → Zoom skutečně reaguje
|
||||
;; - Window origin z AX API (ne buggy frame-position Emacsu)
|
||||
;; - Debounce 50ms: rapid j/k spam → jeden event
|
||||
;;
|
||||
;; SETUP macOS: System Settings → Accessibility → Zoom → Full Screen
|
||||
;; "When zoomed in, the screen image moves: Continuously with pointer"
|
||||
;; SETUP Hammerspoon: copy hammerspoon/cursor-warp.lua to ~/.hammerspoon/
|
||||
;; SETUP macOS:
|
||||
;; System Settings → Accessibility → Zoom → Full Screen
|
||||
;; "When zoomed in, screen image moves: Continuously with pointer"
|
||||
;; Privacy & Security → Accessibility → povolit Terminal (nebo Emacs)
|
||||
;;
|
||||
;; SPC z t toggle cursor tracking on/off
|
||||
;; SPC z t toggle cursor tracking (spustí/zastaví daemon)
|
||||
|
||||
(defvar my/warp-timer nil "Debounce timer for cursor warp.")
|
||||
(defvar my/zoom-daemon-process nil "Subprocess: macos-zoom-daemon.py.")
|
||||
(defvar my/zoom-warp-timer nil "Debounce timer pro cursor tracking.")
|
||||
(defvar my/zoom-daemon-script
|
||||
(expand-file-name "scripts/macos-zoom-daemon.py" doom-private-dir)
|
||||
"Cesta k macos-zoom-daemon.py.")
|
||||
|
||||
(defvar my/cursor-pos-file (expand-file-name "~/.emacs-cursor-pos")
|
||||
"File path for Hammerspoon cursor tracking IPC.")
|
||||
(defun my/zoom-daemon-start ()
|
||||
"Spustí Python cursor tracking daemon."
|
||||
(when (and (display-graphic-p) (file-exists-p my/zoom-daemon-script))
|
||||
(setq my/zoom-daemon-process
|
||||
(start-process "macos-zoom-daemon" nil
|
||||
"python3" my/zoom-daemon-script))
|
||||
(set-process-query-on-exit-flag my/zoom-daemon-process nil)
|
||||
(message "Cursor tracking ON")))
|
||||
|
||||
(defun my/warp-mouse-to-cursor ()
|
||||
"Write cursor screen position to file for Hammerspoon to pick up.
|
||||
Hammerspoon posts a real mouseMoved HID event — required for macOS Zoom
|
||||
'Follow mouse cursor' (CGWarpMouseCursorPosition is silently ignored by Zoom)."
|
||||
(when my/warp-timer (cancel-timer my/warp-timer))
|
||||
(setq my/warp-timer
|
||||
(defun my/zoom-daemon-stop ()
|
||||
"Zastaví Python cursor tracking daemon."
|
||||
(when (and my/zoom-daemon-process (process-live-p my/zoom-daemon-process))
|
||||
(delete-process my/zoom-daemon-process))
|
||||
(setq my/zoom-daemon-process nil)
|
||||
(message "Cursor tracking OFF"))
|
||||
|
||||
(defun my/zoom-send-cursor-pos ()
|
||||
"Pošle frame-relative pozici kurzoru do daemon stdin (debounced 50ms)."
|
||||
(when my/zoom-warp-timer (cancel-timer my/zoom-warp-timer))
|
||||
(setq my/zoom-warp-timer
|
||||
(run-with-idle-timer 0.05 nil
|
||||
(lambda ()
|
||||
(setq my/warp-timer nil)
|
||||
(when (display-graphic-p)
|
||||
(when-let ((pos (window-absolute-pixel-position)))
|
||||
;; Write screen-absolute coords for Hammerspoon
|
||||
;; Also write frame-position for Hammerspoon cross-check
|
||||
(let* ((fp (frame-position))
|
||||
(content (format "%d %d\n%d %d\n"
|
||||
(car pos) (cdr pos)
|
||||
(car fp) (cdr fp))))
|
||||
(with-temp-file my/cursor-pos-file
|
||||
(insert content)))))))))
|
||||
|
||||
(defun my/refresh-frame-position ()
|
||||
"Workaround for bug#71912: stale frame position after sleep/fullscreen."
|
||||
(when (display-graphic-p)
|
||||
(ignore-errors
|
||||
(let ((p (frame-position)))
|
||||
(set-frame-position (selected-frame) (car p) (cdr p))))))
|
||||
(setq my/zoom-warp-timer nil)
|
||||
(when (and my/zoom-daemon-process
|
||||
(process-live-p my/zoom-daemon-process)
|
||||
(display-graphic-p))
|
||||
(let* ((win (selected-window))
|
||||
(vis (pos-visible-in-window-p (window-point win) win t)))
|
||||
(when (and vis (listp vis))
|
||||
(let* ((edges (window-pixel-edges win))
|
||||
(x (+ (nth 0 edges) (nth 0 vis)))
|
||||
(y (+ (nth 1 edges) (nth 1 vis)
|
||||
(/ (line-pixel-height) 2))))
|
||||
(process-send-string my/zoom-daemon-process
|
||||
(format "%d %d\n" x y))))))))))
|
||||
|
||||
(define-minor-mode my/cursor-zoom-mode
|
||||
"Warp mouse to text cursor for macOS Zoom accessibility tracking."
|
||||
"Cursor tracking pro macOS Zoom (via Python daemon)."
|
||||
:global t
|
||||
(if my/cursor-zoom-mode
|
||||
(progn
|
||||
(add-hook 'post-command-hook #'my/warp-mouse-to-cursor)
|
||||
(add-hook 'window-size-change-functions
|
||||
(lambda (_) (my/refresh-frame-position)))
|
||||
(run-with-timer 60 60 #'my/refresh-frame-position))
|
||||
(remove-hook 'post-command-hook #'my/warp-mouse-to-cursor)))
|
||||
|
||||
;; Enable by default
|
||||
(my/cursor-zoom-mode 1)
|
||||
(my/zoom-daemon-start)
|
||||
(add-hook 'post-command-hook #'my/zoom-send-cursor-pos))
|
||||
(remove-hook 'post-command-hook #'my/zoom-send-cursor-pos)
|
||||
(my/zoom-daemon-stop)))
|
||||
|
||||
;; --------------- keybindings ---------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user