branch: externals/bufferlo commit b2112494bbfd98038a5e79463938a88094b3e9ce Author: shipmints <shipmi...@gmail.com> Commit: shipmints <shipmi...@gmail.com>
Frame handling improvements These include geometry and speeding up frame creation. New defcustoms: - bufferlo-set-frame-geometry-function - bufferlo-frame-sleep-for bufferlo--make-frame needed due to an undocumented "feature" in make-frame (read frame.c:make_frame) which prevents the current buffer from being a hidden buffer; i.e., those that start with a space. We will report this to either be made an option (my preference), or at least clearly document this limitation. --- README.org | 16 +++++ bufferlo.el | 201 ++++++++++++++++++++++++++++++++++++------------------------ 2 files changed, 136 insertions(+), 81 deletions(-) diff --git a/README.org b/README.org index e1281b00ce..48edfb18e3 100644 --- a/README.org +++ b/README.org @@ -652,6 +652,9 @@ Frame bookmarks saved via Emacs tty will not store a frame geometry (none available on tty). Conversely, frame bookmarks saved via GUI and restored on tty will ignore frame geometry. +Note: See below to adjust ~bufferlo-frame-sleep-for~ for your window +manager. + Note: Not much testing has been done in hybrid tty/GUI environments using ~emacsclient~, or with multi-display setups where frames may be expected to be restored on their originating displays. @@ -662,6 +665,19 @@ expected to be restored on their originating displays. (setq bufferlo-frame-geometry-function #'bufferlo-frame-geometry-default) ; the default uses text-width and text-height (setq bufferlo-frame-geometry-function #'my/bufferlo-frame-geometry) ; or your own #+end_src +#+begin_src emacs-lisp + ;; function to set a frame's pixelwise geometry (it is not likely you + ;; will need to replace this--but is provided just in case) + (setq bufferlo-set-frame-geometry-function #'bufferlo-set-frame-geometry-default) + (setq bufferlo-set-frame-geometry-function #'my/bufferlo-set-frame-geometry) ; or your own +#+end_src +#+begin_src emacs-lisp + ;; seconds to sleep after each frame parameter change that requires + ;; external window manager cooperation. + (setq bufferlo-frame-sleep-for 0) ; the default, which seems to work on macOS + (setq bufferlo-frame-sleep-for 0.3) ; seems to work for GTK/GNOME +#+end_src + #+begin_src emacs-lisp ;; methodology for bookmark-set frameset geometry restoration (setq bufferlo-frameset-restore-geometry 'bufferlo) ; the pixel-level precision default diff --git a/bufferlo.el b/bufferlo.el index 7e5312177c..3807beb6c4 100644 --- a/bufferlo.el +++ b/bufferlo.el @@ -650,6 +650,22 @@ Replace this function with your own if the default produces suboptimal results for your platform." :type 'function) +(defcustom bufferlo-set-frame-geometry-function #'bufferlo-set-frame-geometry-default + "Function to set frame geometry based on bufferlo-frame-geometry alist. +It defaults to `bufferlo-set-frame-geometry-default', which see for +parameters. + +Replace this function with your own if the default produces +suboptimal results for your platform." + :type 'function) + +(defcustom bufferlo-frame-sleep-for 0 + "Window manager catch-up delay for changing frame parameters. +Delay is specified in seconds using `sleep-for', which see. +GTK/GNOME seems to need 0.3 seconds. YMMV. +No delay seems needed on macOS." + :type 'natnum) + (defcustom bufferlo-mode-line-prefix "Bfl" "Bufferlo mode-line prefix." :type 'string) @@ -981,6 +997,21 @@ string, FACE is the face for STR." `("Bufferlo" ,bufferlo-menu-item-raise)) +;; NOTE: Undocumented in `make-frame' that the current buffer cannot be +;; conventionally hidden (space as first character). `with-temp-buffer' +;; doesn't work either in this context. +(defmacro bufferlo--with-temp-buffer (&rest body) + "Execute BODY with \"*bufferlo tenp buffer*\" current buffer." + (let ((buff-name "*bufferlo temp buffer*")) + (with-current-buffer (get-buffer-create buff-name t) + (unwind-protect + `(progn ,@body) + (kill-buffer buff-name))))) + +(defun bufferlo--make-frame (&optional restore-geometry) + "Make a new frame with `fullscreen' suppressed if RESTORE-GEOMETRY is non nil." + (make-frame (if restore-geometry '((fullscreen . nil)) nil))) + (defun bufferlo-local-buffer-p (buffer &optional frame tabnum include-hidden) "Return non-nil if BUFFER is in the list of local buffers. A non-nil value of FRAME selects a specific frame instead of the current one. @@ -2258,51 +2289,53 @@ the message after successfully restoring the bookmark." ;; Do the real work with the target frame selected (current or newly created) ;; NOTE: No :abort throws after this point - (let ((frame (if new-frame-p - (with-temp-buffer (make-frame)) - (selected-frame)))) - (with-selected-frame frame - ;; Clear existing tabs unless merging - (unless (eq load-policy 'merge) - (if (>= emacs-major-version 28) - (tab-bar-tabs-set nil) - (set-frame-parameter nil 'tabs nil))) - - ;; Load tabs - (let ((first (if (eq load-policy 'merge) nil t)) - (tab-bar-new-tab-choice t)) - (mapc - (lambda (tbm) - (if first - (setq first nil) - (tab-bar-new-tab-to)) - (bufferlo--bookmark-tab-handler tbm t 'embedded-tab) - (when-let* ((tab-name (alist-get 'tab-name tbm))) - (tab-bar-rename-tab tab-name))) - (alist-get 'tabs bookmark))) - (tab-bar-select-tab (alist-get 'current bookmark)) - - ;; Handle duplicate frame bookmark - (when abm - (pcase duplicate-policy - ;; Do nothing for 'allow or nil - ('clear - (setq fbm nil)) - ('clear-warn - (setq fbm nil) - (funcall msg-append "cleared frame bookmark")))) - - (set-frame-parameter nil 'bufferlo-bookmark-frame-name fbm) - - ;; Restore geometry - (when (and new-frame-p - (display-graphic-p) - (eq bufferlo-bookmark-frame-load-make-frame 'restore-geometry)) - (when-let* ((fg (alist-get 'bufferlo--frame-geometry bookmark))) - (bufferlo--set-frame-geometry fg)))) - - ;; Select and raise the restored frame outside the context of with-selected-frame - (select-frame-set-input-focus frame)) + (bufferlo--with-temp-buffer + (let ((frame (if new-frame-p + (bufferlo--make-frame + (eq bufferlo-bookmark-frame-load-make-frame 'restore-geometry)) + (selected-frame)))) + (with-selected-frame frame + ;; Restore geometry + (when (and new-frame-p + (display-graphic-p) + (eq bufferlo-bookmark-frame-load-make-frame 'restore-geometry)) + (when-let* ((fg (alist-get 'bufferlo--frame-geometry bookmark))) + (funcall bufferlo-set-frame-geometry-function fg))) + + ;; Clear existing tabs unless merging + (unless (eq load-policy 'merge) + (if (>= emacs-major-version 28) + (tab-bar-tabs-set nil) + (set-frame-parameter nil 'tabs nil))) + + ;; Load tabs + (let ((first (if (eq load-policy 'merge) nil t)) + (tab-bar-new-tab-choice t)) + (mapc + (lambda (tbm) + (if first + (setq first nil) + (tab-bar-new-tab-to)) + (bufferlo--bookmark-tab-handler tbm t 'embedded-tab) + (when-let* ((tab-name (alist-get 'tab-name tbm))) + (tab-bar-rename-tab tab-name))) + (alist-get 'tabs bookmark))) + (tab-bar-select-tab (alist-get 'current bookmark)) + + ;; Handle duplicate frame bookmark + (when abm + (pcase duplicate-policy + ;; Do nothing for 'allow or nil + ('clear + (setq fbm nil)) + ('clear-warn + (setq fbm nil) + (funcall msg-append "cleared frame bookmark")))) + + (set-frame-parameter nil 'bufferlo-bookmark-frame-name fbm)) + + ;; Select and raise the restored frame outside the context of with-selected-frame + (select-frame-set-input-focus frame))) ;; Log message (unless (or no-message bufferlo--bookmark-handler-no-message) @@ -2443,7 +2476,7 @@ display is a tty." (height .,(frame-text-height frame))) nil)) -(defun bufferlo--set-frame-geometry (frame-geometry &optional frame) +(defun bufferlo-set-frame-geometry-default (frame-geometry &optional frame) "Set FRAME-GEOMETRY as produced by `bufferlo-frame-geometry-default'. Geometry set for FRAME or the current frame, if nil." ;; Some window managers need an extra display cycle for frame @@ -2451,21 +2484,20 @@ Geometry set for FRAME or the current frame, if nil." ;; needed sit-for calls. (setq frame (or frame (selected-frame))) (let-alist frame-geometry + ;; sleeps wait for window managers like GTK/GNOME to catch up (when (and .left .top .width .height) ; defensive in case geometry stored from a tty (let ((frame-resize-pixelwise t) (frame-inhibit-implied-resize t)) - (lower-frame frame) - (set-frame-parameter frame 'fullscreen nil) - (sit-for 0 t) + (make-frame-invisible frame) (set-frame-position frame .left .top) - (sit-for 0 t) + (sleep-for bufferlo-frame-sleep-for) ;; Clamp to restore frames larger than the current display size. (set-frame-size frame (min .width (display-pixel-width)) (min .height (display-pixel-height)) 'pixelwise) - (sit-for 0 t) - (raise-frame frame))))) + (sleep-for bufferlo-frame-sleep-for) + (make-frame-visible frame))))) (defvar bufferlo--active-sets nil "Global active bufferlo sets. @@ -2542,31 +2574,32 @@ the message after successfully restoring the bookmark." (setq tabsets (car (read-from-string tabsets-str))) (when tabsets ; could be readable and nil (let ((first-tab-frame t)) - (dolist (tab-group tabsets) - (when (or (not first-tab-frame) - (and first-tab-frame (not bufferlo-set-restore-tabs-reuse-init-frame))) - (with-temp-buffer - (select-frame (make-frame)))) - ;; (lower-frame) ; attempt to reduce visual flashing - (when-let* ((fg (alist-get 'bufferlo--frame-geometry tab-group))) - (when (and - (display-graphic-p) - (memq bufferlo-set-restore-geometry-policy '(all tab-frames)) - (or (not first-tab-frame) - (and first-tab-frame (eq bufferlo-set-restore-tabs-reuse-init-frame 'reuse-reset-geometry)))) - (bufferlo--set-frame-geometry fg))) - (when-let* ((tbm-names (alist-get 'bufferlo--tbms tab-group))) - (let ((bufferlo-bookmark-tab-replace-policy 'replace) ; we handle making tabs in this loop - (tab-bar-new-tab-choice t) - (first-tab (or - (not first-tab-frame) - (and first-tab-frame (not bufferlo-set-restore-tabs-reuse-init-frame))))) - (dolist (tbm-name tbm-names) - (unless first-tab - (tab-bar-new-tab-to)) - (bufferlo--bookmark-jump tbm-name) - (setq first-tab nil)))) - (setq first-tab-frame nil)) + (bufferlo--with-temp-buffer + (dolist (tab-group tabsets) + (when (or (not first-tab-frame) + (and first-tab-frame (not bufferlo-set-restore-tabs-reuse-init-frame))) + (select-frame + (bufferlo--make-frame + (eq bufferlo-set-restore-tabs-reuse-init-frame 'reuse-reset-geometry)))) + (when-let* ((fg (alist-get 'bufferlo--frame-geometry tab-group))) + (when (and + (display-graphic-p) + (memq bufferlo-set-restore-geometry-policy '(all tab-frames)) + (or (not first-tab-frame) + (and first-tab-frame (eq bufferlo-set-restore-tabs-reuse-init-frame 'reuse-reset-geometry)))) + (funcall bufferlo-set-frame-geometry-function fg))) + (when-let* ((tbm-names (alist-get 'bufferlo--tbms tab-group))) + (let ((bufferlo-bookmark-tab-replace-policy 'replace) ; we handle making tabs in this loop + (tab-bar-new-tab-choice t) + (first-tab (or + (not first-tab-frame) + (and first-tab-frame (not bufferlo-set-restore-tabs-reuse-init-frame))))) + (dolist (tbm-name tbm-names) + (unless first-tab + (tab-bar-new-tab-to)) + (bufferlo--bookmark-jump tbm-name) + (setq first-tab nil)))) + (setq first-tab-frame nil))) (raise-frame))))) (let ((frameset-str (bookmark-prop-get bookmark-record 'bufferlo-frameset)) (frameset)) @@ -2580,7 +2613,6 @@ the message after successfully restoring the bookmark." (dolist (frame (frame-list)) (with-selected-frame frame (when (frame-parameter nil 'bufferlo--frame-to-restore) - ;; (lower-frame) ; attempt to reduce visual flashing (when-let* ((fbm-name (frame-parameter nil 'bufferlo--bookmark-frame-name))) (let ((bufferlo-bookmark-frame-load-make-frame nil) (bufferlo-bookmark-frame-load-policy 'replace-frame-adopt-loaded-bookmark) @@ -2590,7 +2622,7 @@ the message after successfully restoring the bookmark." (display-graphic-p frame) (memq bufferlo-set-restore-geometry-policy '(all frames))) (when-let* ((fg (frame-parameter nil 'bufferlo--frame-geometry))) - (bufferlo--set-frame-geometry fg))) + (funcall bufferlo-set-frame-geometry-function fg))) (set-frame-parameter nil 'bufferlo--frame-to-restore nil)) (raise-frame)))))) (push @@ -3270,6 +3302,7 @@ current or new frame according to ;; `bufferlo--bookmark-frame-handler' to create frames should ;; that policy be set to do so. (current-prefix-arg nil)) + ;; load bookmark sets (dolist (bookmark-name (bufferlo--bookmark-get-names #'bufferlo--bookmark-set-handler)) (unless (assoc bookmark-name bufferlo--active-sets) @@ -3277,7 +3310,10 @@ current or new frame according to (if (bufferlo--bookmark-jump bookmark-name) (push bookmark-name bookmarks-loaded) (push bookmark-name bookmarks-failed))))) - ;; load tab bookmarks, making a new frame if required + + ;; load tab bookmarks, making a new frame, if required (the + ;; geometry of which is via the user's default-frame-alist) + (select-frame orig-frame) ; default frame for tabs (let ((bufferlo-bookmark-tab-replace-policy 'replace) ; we handle making tabs in this loop (tab-bar-new-tab-choice t) (new-tab-frame nil)) @@ -3290,6 +3326,7 @@ current or new frame according to (if (bufferlo--bookmark-jump bookmark-name) (push bookmark-name bookmarks-loaded) (push bookmark-name bookmarks-failed)))))) + ;; load frame bookmarks (dolist (bookmark-name (bufferlo--bookmark-get-names #'bufferlo--bookmark-frame-handler)) (unless (assoc bookmark-name (bufferlo--active-bookmarks)) @@ -3297,7 +3334,9 @@ current or new frame according to (if (bufferlo--bookmark-jump bookmark-name) (push bookmark-name bookmarks-loaded) (push bookmark-name bookmarks-failed))))) - (select-frame orig-frame) + + ;; leave the user on the starting frame + (select-frame-set-input-focus orig-frame) (when bookmarks-loaded (message "Loaded bufferlo bookmarks: %s, in %.2f seconds%s" (mapconcat #'identity bookmarks-loaded " ")