Function: frameset-restore

frameset-restore is an autoloaded and byte-compiled function defined in frameset.el.gz.

Signature

(frameset-restore FRAMESET &key PREDICATE FILTERS REUSE-FRAMES FORCE-DISPLAY FORCE-ONSCREEN CLEANUP-FRAMES)

Documentation

Restore a FRAMESET into the current display(s).

PREDICATE is a function called with two arguments, the parameter alist and the window-state of the frame being restored, in that order (see the docstring of the frameset defstruct for additional details). If PREDICATE returns nil, the frame described by that parameter alist and window-state is not restored.

FILTERS is an alist of parameter filters; if nil, the value of frameset-filter-alist is used instead.

REUSE-FRAMES selects the policy to reuse frames when restoring:
  t All existing frames can be reused.
  nil No existing frame can be reused.
  match Only frames with matching frame ids can be reused.
  PRED A predicate function; it receives as argument a live frame,
             and must return non-nil to allow reusing it, nil otherwise.

FORCE-DISPLAY can be:
  t Frames are restored in the current display.
  nil Frames are restored, if possible, in their original displays.
  delete Frames in other displays are deleted instead of restored.
  PRED A function called with two arguments, the parameter alist and
the window state (in that order). It must return t, nil or
delete, as above but affecting only the frame that will
be created from that parameter alist.

FORCE-ONSCREEN can be:
  t Force onscreen only those frames that are fully offscreen.
  nil Do not force any frame back onscreen.
  all Force onscreen any frame fully or partially offscreen.
  PRED A function called with three arguments,
- the live frame just restored,
- a list (LEFT TOP WIDTH HEIGHT), describing the frame,
- a list (LEFT TOP WIDTH HEIGHT), describing the workarea.
It must return non-nil to force the frame onscreen, nil otherwise.

CLEANUP-FRAMES allows "cleaning up" the frame list after restoring a frameset:
  t Delete all frames that were not created or restored upon.
  nil Keep all frames.
  FUNC A function called with two arguments:
           - FRAME, a live frame.
           - ACTION, which can be one of
             :rejected Frame existed, but was not a candidate for reuse.
             :ignored Frame existed, was a candidate, but wasn't reused.
             :reused Frame existed, was a candidate, and restored upon.
             :created Frame didn't exist, was created and restored upon.
           Return value is ignored.

Note the timing and scope of the operations described above: REUSE-FRAMES affects existing frames; PREDICATE, FILTERS and FORCE-DISPLAY affect the frame being restored before that happens; FORCE-ONSCREEN affects the frame once it has been restored; and CLEANUP-FRAMES affects all frames alive after the restoration, including those that have been reused or created anew.

All keyword parameters default to nil.

Source Code

;; Defined in /usr/src/emacs/lisp/frameset.el.gz
;;;###autoload
(cl-defun frameset-restore (frameset
			    &key predicate filters reuse-frames
				 force-display force-onscreen
				 cleanup-frames)
  "Restore a FRAMESET into the current display(s).

PREDICATE is a function called with two arguments, the parameter alist
and the window-state of the frame being restored, in that order (see
the docstring of the `frameset' defstruct for additional details).
If PREDICATE returns nil, the frame described by that parameter alist
and window-state is not restored.

FILTERS is an alist of parameter filters; if nil, the value of
`frameset-filter-alist' is used instead.

REUSE-FRAMES selects the policy to reuse frames when restoring:
  t        All existing frames can be reused.
  nil      No existing frame can be reused.
  match    Only frames with matching frame ids can be reused.
  PRED     A predicate function; it receives as argument a live frame,
             and must return non-nil to allow reusing it, nil otherwise.

FORCE-DISPLAY can be:
  t        Frames are restored in the current display.
  nil      Frames are restored, if possible, in their original displays.
  delete   Frames in other displays are deleted instead of restored.
  PRED     A function called with two arguments, the parameter alist and
	     the window state (in that order).  It must return t, nil or
	     `delete', as above but affecting only the frame that will
	     be created from that parameter alist.

FORCE-ONSCREEN can be:
  t        Force onscreen only those frames that are fully offscreen.
  nil      Do not force any frame back onscreen.
  all      Force onscreen any frame fully or partially offscreen.
  PRED     A function called with three arguments,
	   - the live frame just restored,
	   - a list (LEFT TOP WIDTH HEIGHT), describing the frame,
	   - a list (LEFT TOP WIDTH HEIGHT), describing the workarea.
	   It must return non-nil to force the frame onscreen, nil otherwise.

CLEANUP-FRAMES allows \"cleaning up\" the frame list after
restoring a frameset:
  t        Delete all frames that were not created or restored upon.
  nil      Keep all frames.
  FUNC     A function called with two arguments:
           - FRAME, a live frame.
           - ACTION, which can be one of
             :rejected  Frame existed, but was not a candidate for reuse.
             :ignored   Frame existed, was a candidate, but wasn't reused.
             :reused    Frame existed, was a candidate, and restored upon.
             :created   Frame didn't exist, was created and restored upon.
           Return value is ignored.

Note the timing and scope of the operations described above: REUSE-FRAMES
affects existing frames; PREDICATE, FILTERS and FORCE-DISPLAY affect the frame
being restored before that happens; FORCE-ONSCREEN affects the frame once
it has been restored; and CLEANUP-FRAMES affects all frames alive after the
restoration, including those that have been reused or created anew.

All keyword parameters default to nil."

  (cl-assert (frameset-valid-p frameset))

  (let* ((frames (frame-list))
	 (frameset--action-map (make-hash-table :test #'eq))
	 ;; frameset--reuse-list is a list of frames potentially reusable.  Later we
	 ;; will decide which ones can be reused, and how to deal with any leftover.
	 (frameset--reuse-list
	  (pcase reuse-frames
	    ('t
	     frames)
	    ('nil
	     nil)
	    ('match
	     (cl-loop for (state) in (frameset-states frameset)
		      when (frameset-frame-with-id (frameset-cfg-id state) frames)
		      collect it))
	    ((pred functionp)
	     (cl-remove-if-not reuse-frames frames))
	    (_
	     (error "Invalid arg :reuse-frames %s" reuse-frames))))
         (dx 0)
         (dy 0))

    ;; Mark existing frames in the map; candidates to reuse are marked as :ignored;
    ;; they will be reassigned later, if chosen.
    (dolist (frame frames)
      (puthash frame
	       (if (memq frame frameset--reuse-list) :ignored :rejected)
	       frameset--action-map))

    ;; Sort saved states to guarantee that minibufferless frames will be created
    ;; after the frames that contain their minibuffer windows.
    (dolist (state (sort (copy-sequence (frameset-states frameset))
			 #'frameset--minibufferless-last-p))
      (pcase-let ((`(,frame-cfg . ,window-cfg) state))
	(when (or (null predicate) (funcall predicate frame-cfg window-cfg))
	  (condition-case-unless-debug err
	      (let* ((d-mini (cdr (assq 'frameset--mini frame-cfg)))
		     (mb-id (cdr d-mini))
		     (default (and (car d-mini) mb-id))
		     (force-display (if (functionp force-display)
					(funcall force-display frame-cfg window-cfg)
				      force-display))
		     (frameset--target-display nil)
		     frame to-tty duplicate)
		;; Only set target if forcing displays and the target display is different.
		(unless (or (frameset-keep-original-display-p force-display)
			    (equal (frame-parameter nil 'display)
				   (cdr (assq 'display frame-cfg))))
		  (setq frameset--target-display (cons 'display
						       (frame-parameter nil 'display))
			to-tty (null (cdr frameset--target-display))))
		;; Time to restore frames and set up their minibuffers as they were.
		;; We only skip a frame (thus deleting it) if either:
		;; - we're switching displays, and the user chose the option to delete, or
		;; - we're switching to tty, and the frame to restore is minibuffer-only.
		(unless (and frameset--target-display
			     (or (eq force-display 'delete)
				 (and to-tty
				      (eq (cdr (assq 'minibuffer frame-cfg)) 'only))))
		  ;; To avoid duplicating frame ids after restoration, we note any
		  ;; existing frame whose id matches a frame configuration in the
		  ;; frameset.  Once the frame config is properly restored, we can
		  ;; reset the old frame's id to nil.
		  (setq duplicate (frameset-frame-with-id (frameset-cfg-id frame-cfg)
							  frames))
		  ;; Restore minibuffers.  Some of this stuff could be done in a filter
		  ;; function, but it would be messy because restoring minibuffers affects
		  ;; global state; it's best to do it here than add a bunch of global
		  ;; variables to pass info back-and-forth to/from the filter function.
		  (cond
		   ((null d-mini)) ;; No frameset--mini.  Process as normal frame.
		   (to-tty) ;; Ignore minibuffer stuff and process as normal frame.
		   ((car d-mini) ;; Frame has minibuffer (or it is minibuffer-only).
		    (when (eq (cdr (assq 'minibuffer frame-cfg)) 'only)
		      (setq frame-cfg (append '((tool-bar-lines . 0) (menu-bar-lines . 0))
					      frame-cfg))))
		   (t ;; Frame depends on other frame's minibuffer window.
		    (when mb-id
		      (let ((mb-frame (frameset-frame-with-id mb-id))
			    (mb-window nil))
			(if (not mb-frame)
			    (delay-warning 'frameset
					   (format "Minibuffer frame %S not found" mb-id)
					   :warning)
			  (setq mb-window (minibuffer-window mb-frame))
			  (unless (and (window-live-p mb-window)
				       (window-minibuffer-p mb-window))
			    (delay-warning 'frameset
					   (format "Not a minibuffer window %s" mb-window)
					   :warning)
			    (setq mb-window nil)))
			(when mb-window
			  (push (cons 'minibuffer mb-window) frame-cfg))))))
                  ;; Apply small offsets to each frame that came from
                  ;; a TTY-saved desktop, so that they don't obscure
                  ;; each other, but only if we don't have real frame
                  ;; position info from a GUI session in some,
                  ;; possibly distant, past.
                  (when (and (frameset-switch-to-gui-p frame-cfg)
                             (null (cdr (assq 'GUI:top frame-cfg)))
                             (null (cdr (assq 'GUI:left frame-cfg))))
                    (setq dx (+ dx 20)
                          dy (+ dy 10)))
		  ;; OK, we're ready at last to create (or reuse) a frame and
		  ;; restore the window config.
		  (setq frame (frameset--restore-frame frame-cfg window-cfg
						       (or filters frameset-filter-alist)
						       force-onscreen dx dy))
		  ;; Now reset any duplicate frameset--id
		  (when (and duplicate (not (eq frame duplicate)))
		    (set-frame-parameter duplicate 'frameset--id nil))
		  ;; Set default-minibuffer if required.
		  (when default (setq default-minibuffer-frame frame))))
	    (error
	     (delay-warning 'frameset (error-message-string err) :error))))))

    ;; Setting the parent frame after the frame has been created is a
    ;; pain because one can see the frame move on the screen.  Ideally,
    ;; we would restore minibuffer equipped child frames after their
    ;; respective parents have been made but this might interfere with
    ;; the reordering of minibuffer frames.  Left to the experts ...
    (dolist (frame (frame-list))
      (let* ((frame-id (frame-parameter frame 'frameset--parent-frame))
             (parent-frame
              (and frame-id (frameset-frame-with-id frame-id))))
        (when (and (not (eq (frame-parameter frame 'parent-frame) parent-frame))
                   (frame-live-p parent-frame))
          (set-frame-parameter frame 'parent-frame parent-frame)))
      (let* ((frame-id (frame-parameter frame 'frameset--delete-before))
             (delete-before
              (and frame-id (frameset-frame-with-id frame-id))))
        (when (frame-live-p delete-before)
          (set-frame-parameter frame 'delete-before delete-before)))
      (let* ((frame-id (frame-parameter frame 'frameset--mouse-wheel-frame))
             (mouse-wheel-frame
              (and frame-id (frameset-frame-with-id frame-id))))
        (when (frame-live-p mouse-wheel-frame)
          (set-frame-parameter frame 'mouse-wheel-frame mouse-wheel-frame))))

    ;; In case we try to delete the initial frame, we want to make sure that
    ;; other frames are already visible (discussed in thread for bug#14841).
    (sit-for 0 t)

    ;; Clean up the frame list
    (when cleanup-frames
      (let ((map nil)
	    (cleanup (if (eq cleanup-frames t)
			 (lambda (frame action)
			   (when (memq action '(:rejected :ignored))
			     (delete-frame frame)))
		       cleanup-frames)))
	(maphash (lambda (frame _action) (push frame map)) frameset--action-map)
	(dolist (frame (sort map
			     ;; Minibufferless frames must go first to avoid
			     ;; errors when attempting to delete a frame whose
			     ;; minibuffer window is used by another frame.
			     #'frameset-minibufferless-first-p))
	  (condition-case-unless-debug err
	      (funcall cleanup frame (gethash frame frameset--action-map))
	    (error
	     (delay-warning 'frameset (error-message-string err) :warning))))))

    ;; Make sure the frame with last-focus-update has focus.
    (let ((last-focus-frame
           (catch 'last-focus
             (maphash (lambda (frame _)
                        (when (frame-parameter frame 'last-focus-update)
                          (throw 'last-focus frame)))
                      frameset--action-map))))
      (when last-focus-frame
        (select-frame-set-input-focus last-focus-frame)))

    ;; Make sure there's at least one visible frame.
    (unless (or (daemonp)
		(catch 'visible
		  (maphash (lambda (frame _)
			     (and (frame-live-p frame) (frame-visible-p frame)
				  (throw 'visible t)))
			   frameset--action-map)))
      (make-frame-visible (selected-frame)))))