;;; ebox-viewport.el --- Event-loop viewport delivery -*- lexical-binding: t; -*- ;;; Commentary: ;; Emacs owns window events; Ebox owns the mounted viewport. This module is ;; the single bridge between them. Size hooks record frame intents; one task ;; samples current visible dimensions and publishes after the redisplay hook. ;;; Code: (require 'cl-lib) (require 'ebox-surface) (declare-function ebox--buffer-render-state "ebox-incremental" (buffer)) (declare-function ebox-rerender-buffer-with-context "ebox" (buffer viewport-width &optional viewport-height)) (defvar ebox--window-size-change-in-progress nil "Non-nil while Ebox is draining one batch of viewport notifications.") (defvar ebox--window-size-change-pending-frames nil "Frames awaiting viewport synchronization, deduplicated by EQ. The queue holds no buffers, runtime roots or sampled dimensions.") (defvar ebox--window-size-change-timer nil "The single owned task scheduled to drain viewport notifications.") (defun ebox-viewport-window-width (window) "Return Ebox's display-safe viewport width for live WINDOW." (and (window-live-p window) (ebox-surface--window-content-width window))) (defun ebox--window-size-change-sync-frame (frame) "Synchronize current mounted Ebox viewports whose canonical window is in FRAME." (when (frame-live-p frame) (let ((seen (make-hash-table :test #'eq))) (dolist (window (window-list frame 'no-minibuf)) (when (window-live-p window) (let ((buffer (window-buffer window))) (unless (gethash buffer seen) (puthash buffer t seen) (let ((display-window (and (buffer-live-p buffer) (ebox-surface--buffer-display-window buffer)))) (when (and (window-live-p display-window) (eq (window-frame display-window) frame) (ebox-surface-buffer-mounted-p buffer)) (let* ((state (ebox--buffer-render-state buffer)) (width (ebox-viewport-window-width display-window)) (height (window-body-height display-window))) (when (and width state (not (and (= width (or (plist-get state :viewport-width) -1)) (= height (or (plist-get state :viewport-height) -1))))) (ebox-rerender-buffer-with-context buffer width height)))))))))))) (defun ebox--window-size-change-schedule () "Schedule one task for live pending frames outside the current batch." (setq ebox--window-size-change-pending-frames (cl-delete-if-not #'frame-live-p ebox--window-size-change-pending-frames)) (when (and ebox--window-size-change-pending-frames (not ebox--window-size-change-in-progress) (not ebox--window-size-change-timer)) (setq ebox--window-size-change-timer (run-at-time 0 nil #'ebox--window-size-change-drain)))) (defun ebox--window-size-change-drain () "Consume one frame batch, leaving nested notifications for a later task." (unless ebox--window-size-change-in-progress (let ((ebox--window-size-change-in-progress t) (frames (nreverse ebox--window-size-change-pending-frames)) first-error) (unwind-protect (progn (setq ebox--window-size-change-pending-frames nil ebox--window-size-change-timer nil) ;; Timer dispatch inhibits quit by default. Enable C-g only after ;; cleanup is installed; TP retains its own final-accept protection. (let ((inhibit-quit nil)) (while frames (let ((frame (pop frames))) (condition-case failure (ebox--window-size-change-sync-frame frame) (error (unless first-error (setq first-error failure)))))))) ;; A quit may leave unstarted frames. Keep those intents, but do not ;; retry the frame whose publication already started and failed. (dolist (frame frames) (unless (memq frame ebox--window-size-change-pending-frames) (push frame ebox--window-size-change-pending-frames))) (let ((ebox--window-size-change-in-progress nil)) (ebox--window-size-change-schedule))) (when first-error (signal (car first-error) (cdr first-error)))))) (defun ebox--window-size-change (frame) "Record FRAME's latest viewport intent without rendering inside the hook." (when (and (frame-live-p frame) (not noninteractive)) (unless (memq frame ebox--window-size-change-pending-frames) (push frame ebox--window-size-change-pending-frames)) (ebox--window-size-change-schedule))) (when (boundp 'window-size-change-functions) (add-hook 'window-size-change-functions #'ebox--window-size-change)) (provide 'ebox-viewport) ;;; ebox-viewport.el ends here