diff options
Diffstat (limited to 'lisp/desktop.el')
| -rw-r--r-- | lisp/desktop.el | 689 |
1 files changed, 526 insertions, 163 deletions
diff --git a/lisp/desktop.el b/lisp/desktop.el index 322b95715a2..d5895a8de57 100644 --- a/lisp/desktop.el +++ b/lisp/desktop.el @@ -33,6 +33,7 @@ ;; - the mark & mark-active ;; - buffer-read-only ;; - some local variables +;; - frame and window configuration ;; To use this, use customize to turn on desktop-save-mode or add the ;; following line somewhere in your init file: @@ -127,12 +128,13 @@ ;; --------------------------------------------------------------------------- ;; TODO: ;; -;; Save window configuration. ;; Recognize more minor modes. ;; Save mark rings. ;;; Code: +(require 'cl-lib) + (defvar desktop-file-version "206" "Version number of desktop file format. Written into the desktop file and used at desktop read to provide @@ -369,16 +371,36 @@ modes are restored automatically; they should not be listed here." :type '(repeat symbol) :group 'desktop) -(defcustom desktop-restore-frames nil +(defcustom desktop-restore-frames t "When non-nil, save window/frame configuration to desktop file." :type 'boolean :group 'desktop :version "24.4") (defcustom desktop-restore-in-current-display nil - "When non-nil, frames are restored in the current display. -Otherwise they are restored, if possible, in their original displays." - :type 'boolean + "If t, frames are restored in the current display. +If nil, frames are restored, if possible, in their original displays. +If `delete', frames on other displays are deleted instead of restored." + :type '(choice (const :tag "Restore in current display" t) + (const :tag "Restore in original display" nil) + (const :tag "Delete frames in other displays" 'delete)) + :group 'desktop + :version "24.4") + +(defcustom desktop-restoring-reuses-frames t + "If t, restoring frames reuses existing frames. +If nil, existing frames are deleted. +If `keep', existing frames are kept and not reused." + :type '(choice (const :tag "Reuse existing frames" t) + (const :tag "Delete existing frames" nil) + (const :tag "Keep existing frames" 'keep)) + :group 'desktop + :version "24.4") + +(defcustom desktop-before-saving-frames-functions nil + "Abnormal hook run before saving frames. +Functions in this hook are called with one argument, a live frame." + :type 'hook :group 'desktop :version "24.4") @@ -565,8 +587,9 @@ DIRNAME omitted or nil means use `desktop-dirname'." "Checksum of the last auto-saved contents of the desktop file. Used to avoid writing contents unchanged between auto-saves.") -(defvar desktop--saved-states nil - "Internal use only.") +(defvar desktop-saved-frame-states nil + "Saved state of all frames. +Only valid during frame saving & restoring; intended for internal use.") ;; ---------------------------------------------------------------------------- ;; Desktop file conflict detection @@ -621,22 +644,17 @@ Furthermore, it clears the variables listed in `desktop-globals-to-clear'." (if (symbolp var) (eval `(setq-default ,var nil)) (eval `(setq-default ,(car var) ,(cdr var))))) - (let ((buffers (buffer-list)) - (preserve-regexp (concat "^\\(" + (let ((preserve-regexp (concat "^\\(" (mapconcat (lambda (regexp) (concat "\\(" regexp "\\)")) desktop-clear-preserve-buffers "\\|") "\\)$"))) - (while buffers - (let ((bufname (buffer-name (car buffers)))) - (or - (null bufname) - (string-match-p preserve-regexp bufname) - ;; Don't kill buffers made for internal purposes. - (and (not (equal bufname "")) (eq (aref bufname 0) ?\s)) - (kill-buffer (car buffers)))) - (setq buffers (cdr buffers)))) + (dolist (buffer (buffer-list)) + (let ((bufname (buffer-name buffer))) + (unless (or (eq (aref bufname 0) ?\s) ;; Don't kill internal buffers + (string-match-p preserve-regexp bufname)) + (kill-buffer buffer))))) (delete-other-windows)) ;; ---------------------------------------------------------------------------- @@ -673,15 +691,7 @@ is nil, ask the user where to save the desktop." ;; ---------------------------------------------------------------------------- (defun desktop-list* (&rest args) - (if (null (cdr args)) - (car args) - (setq args (nreverse args)) - (let ((value (cons (nth 1 args) (car args)))) - (setq args (cdr (cdr args))) - (while args - (setq value (cons (car args) value)) - (setq args (cdr args))) - value))) + (and args (apply #'cl-list* args))) ;; ---------------------------------------------------------------------------- (defun desktop-buffer-info (buffer) @@ -713,16 +723,14 @@ is nil, ask the user where to save the desktop." (when (functionp desktop-save-buffer) (funcall desktop-save-buffer desktop-dirname)) ;; local variables - (let ((locals desktop-locals-to-save) - (loclist (buffer-local-variables)) - (ll)) - (while locals - (let ((here (assq (car locals) loclist))) - (if here - (setq ll (cons here ll)) - (when (member (car locals) loclist) - (setq ll (cons (car locals) ll))))) - (setq locals (cdr locals))) + (let ((loclist (buffer-local-variables)) + (ll nil)) + (dolist (local desktop-locals-to-save) + (let ((here (assq local loclist))) + (cond (here + (push here ll)) + ((member local loclist) + (push local ll))))) ll))) ;; ---------------------------------------------------------------------------- @@ -869,43 +877,224 @@ DIRNAME must be the directory in which the desktop file will be saved." ;; ---------------------------------------------------------------------------- -(defconst desktop--excluded-frame-parameters - '(buffer-list - buffer-predicate - buried-buffer-list - explicit-name - font - font-backend - minibuffer - name - outer-window-id - parent-id - window-id - window-system) - "Frame parameters not saved or restored.") - -(defun desktop--filter-frame-parms (frame) - "Return frame parameters of FRAME. -Parameters in `desktop--excluded-frame-parameters' are excluded. +(defvar desktop-filter-parameters-alist + '((background-color . desktop--filter-*-color) + (buffer-list . t) + (buffer-predicate . t) + (buried-buffer-list . t) + (desktop--font . desktop--filter-restore-desktop-parm) + (desktop--fullscreen . desktop--filter-restore-desktop-parm) + (desktop--height . desktop--filter-restore-desktop-parm) + (desktop--width . desktop--filter-restore-desktop-parm) + (font . desktop--filter-save-desktop-parm) + (font-backend . t) + (foreground-color . desktop--filter-*-color) + (fullscreen . desktop--filter-save-desktop-parm) + (height . desktop--filter-save-desktop-parm) + (left . desktop--filter-iconified-position) + (minibuffer . desktop--filter-minibuffer) + (name . t) + (outer-window-id . t) + (parent-id . t) + (top . desktop--filter-iconified-position) + (tty . desktop--filter-tty*) + (tty-type . desktop--filter-tty*) + (width . desktop--filter-save-desktop-parm) + (window-id . t) + (window-system . t)) + "Alist of frame parameters and filtering functions. + +Each element is a cons (PARAM . FILTER), where PARAM is a parameter +name (a symbol identifying a frame parameter), and FILTER can be t +\(meaning the parameter is removed from the parameter list on saving +and restoring), or a function that will be called with three args: + + CURRENT a cons (PARAM . VALUE), where PARAM is the one being + filtered and VALUE is its current value + PARAMETERS the complete alist of parameters being filtered + SAVING non-nil if filtering before saving state, nil otherwise + +The FILTER function must return: + nil CURRENT is removed from the list + t CURRENT is left as is + (PARAM' . VALUE') replace CURRENT with this + +Frame parameters not on this list are passed intact.") + +(defvar desktop--target-display nil + "Either (minibuffer . VALUE) or nil. +This refers to the current frame config being processed inside +`frame--restore-frames' and its auxiliary functions (like filtering). +If nil, there is no need to change the display. +If non-nil, display parameter to use when creating the frame. +Internal use only.") + +(defun desktop-switch-to-gui-p (parameters) + "True when switching to a graphic display. +Return t if PARAMETERS describes a text-only terminal and +the target is a graphic display; otherwise return nil. +Only meaningful when called from a filtering function in +`desktop-filter-parameters-alist'." + (and desktop--target-display ; we're switching + (null (cdr (assq 'display parameters))) ; from a tty + (cdr desktop--target-display))) ; to a GUI display + +(defun desktop-switch-to-tty-p (parameters) + "True when switching to a text-only terminal. +Return t if PARAMETERS describes a graphic display and +the target is a text-only terminal; otherwise return nil. +Only meaningful when called from a filtering function in +`desktop-filter-parameters-alist'." + (and desktop--target-display ; we're switching + (cdr (assq 'display parameters)) ; from a GUI display + (null (cdr desktop--target-display)))) ; to a tty + +(defun desktop--filter-tty* (_current parameters saving) + ;; Remove tty and tty-type parameters when switching + ;; to a GUI frame. + (or saving + (not (desktop-switch-to-gui-p parameters)))) + +(defun desktop--filter-*-color (current parameters saving) + ;; Remove (foreground|background)-color parameters + ;; when switching to a GUI frame if they denote an + ;; "unspecified" color. + (or saving + (not (desktop-switch-to-gui-p parameters)) + (not (stringp (cdr current))) + (not (string-match-p "^unspecified-[fb]g$" (cdr current))))) + +(defun desktop--filter-minibuffer (current _parameters saving) + ;; When minibuffer is a window, save it as minibuffer . t + (or (not saving) + (if (windowp (cdr current)) + '(minibuffer . t) + t))) + +(defun desktop--filter-restore-desktop-parm (current parameters saving) + ;; When switching to a GUI frame, convert desktop--XXX parameter to XXX + (or saving + (not (desktop-switch-to-gui-p parameters)) + (let ((val (cdr current))) + (if (eq val :desktop-processed) + nil + (cons (intern (substring (symbol-name (car current)) + 9)) ;; (length "desktop--") + val))))) + +(defun desktop--filter-save-desktop-parm (current parameters saving) + ;; When switching to a tty frame, save parameter XXX as desktop--XXX so it + ;; can be restored in a subsequent GUI session, unless it already exists. + (cond (saving t) + ((desktop-switch-to-tty-p parameters) + (let ((sym (intern (format "desktop--%s" (car current))))) + (if (assq sym parameters) + nil + (cons sym (cdr current))))) + ((desktop-switch-to-gui-p parameters) + (let* ((dtp (assq (intern (format "desktop--%s" (car current))) + parameters)) + (val (cdr dtp))) + (if (eq val :desktop-processed) + nil + (setcdr dtp :desktop-processed) + (cons (car current) val)))) + (t t))) + +(defun desktop--filter-iconified-position (_current parameters saving) + ;; When saving an iconified frame, top & left are meaningless, + ;; so remove them to allow restoring to a default position. + (not (and saving (eq (cdr (assq 'visibility parameters)) 'icon)))) + +(defun desktop-restore-in-original-display-p () + "True if saved frames' displays should be honored." + (cond ((daemonp) t) + ((eq system-type 'windows-nt) nil) + (t (null desktop-restore-in-current-display)))) + +(defun desktop--filter-frame-parms (parameters saving) + "Filter frame parameters and return filtered list. +PARAMETERS is a parameter alist as returned by `frame-parameters'. +If SAVING is non-nil, filtering is happening before saving frame state; +otherwise, filtering is being done before restoring frame state. +Parameters are filtered according to the setting of +`desktop-filter-parameters-alist' (which see). Internal use only." - (let (params) - (dolist (param (frame-parameters frame)) - (unless (memq (car param) desktop--excluded-frame-parameters) - (push param params))) - params)) - -(defun desktop--save-frames () - "Save window/frame state, as a global variable. -Intended to be called from `desktop-save'. -Internal use only." - (setq desktop--saved-states + (let ((filtered nil)) + (dolist (param parameters) + (let ((filter (cdr (assq (car param) desktop-filter-parameters-alist))) + this) + (cond (;; no filter: pass param + (null filter) + (push param filtered)) + (;; filter = t; skip param + (eq filter t)) + (;; filter func returns nil: skip param + (null (setq this (funcall filter param parameters saving)))) + (;; filter func returns t: pass param + (eq this t) + (push param filtered)) + (;; filter func returns a new param: use it + t + (push this filtered))))) + ;; Set the display parameter after filtering, so that filter functions + ;; have access to its original value. + (when desktop--target-display + (let ((display (assq 'display filtered))) + (if display + (setcdr display (cdr desktop--target-display)) + (push desktop--target-display filtered)))) + filtered)) + +(defun desktop--process-minibuffer-frames (frames) + ;; Adds a desktop--mini parameter to frames + ;; desktop--mini is a list (MINIBUFFER NUMBER DEFAULT?) where + ;; MINIBUFFER t if the frame (including minibuffer-only) owns a minibuffer + ;; NUMBER if MINIBUFFER = t, an ID for the frame; if nil, the ID of + ;; the frame containing the minibuffer used by this frame + ;; DEFAULT? if t, this frame is the value of default-minibuffer-frame + (let ((count 0)) + ;; Reset desktop--mini for all frames + (dolist (frame (frame-list)) + (set-frame-parameter frame 'desktop--mini nil)) + ;; Number all frames with its own minibuffer + (dolist (frame (minibuffer-frame-list)) + (set-frame-parameter frame 'desktop--mini + (list t + (cl-incf count) + (eq frame default-minibuffer-frame)))) + ;; Now link minibufferless frames with their minibuffer frames + (dolist (frame frames) + (unless (frame-parameter frame 'desktop--mini) + (let ((mb-frame (window-frame (minibuffer-window frame)))) + ;; Frames whose minibuffer frame has been filtered out will have + ;; desktop--mini = nil, so desktop-restore-frames will restore them + ;; according to their minibuffer parameter. Set up desktop--mini + ;; for the rest. + (when (memq mb-frame frames) + (set-frame-parameter frame 'desktop--mini + (list nil + (cl-second (frame-parameter mb-frame 'desktop--mini)) + nil)))))))) + +(defun desktop-save-frames () + "Save frame state in `desktop-saved-frame-states'. +Runs the hook `desktop-before-saving-frames-functions'. +Frames with a non-nil `desktop-dont-save' parameter are not saved." + (setq desktop-saved-frame-states (and desktop-restore-frames - (mapcar (lambda (frame) - (cons (desktop--filter-frame-parms frame) - (window-state-get (frame-root-window frame) t))) - (cons (selected-frame) - (delq (selected-frame) (frame-list)))))) - (desktop-outvar 'desktop--saved-states)) + (let ((frames (cl-delete-if + (lambda (frame) + (run-hook-with-args 'desktop-before-saving-frames-functions frame) + (frame-parameter frame 'desktop-dont-save)) + (frame-list)))) + ;; In case some frame was deleted by a hook function + (setq frames (cl-delete-if-not #'frame-live-p frames)) + (desktop--process-minibuffer-frames frames) + (mapcar (lambda (frame) + (cons (desktop--filter-frame-parms (frame-parameters frame) t) + (window-state-get (frame-root-window frame) t))) + frames))))) ;;;###autoload (defun desktop-save (dirname &optional release auto-save) @@ -947,8 +1136,11 @@ and don't save the buffer if they are the same." (insert "\n;; Global section:\n") ;; Called here because we save the window/frame state as a global ;; variable for compatibility with previous Emacsen. - (desktop--save-frames) + (desktop-save-frames) + (unless (memq 'desktop-saved-frame-states desktop-globals-to-save) + (desktop-outvar 'desktop-saved-frame-states)) (mapc (function desktop-outvar) desktop-globals-to-save) + (setq desktop-saved-frame-states nil) ; after saving desktop-globals-to-save (when (memq 'kill-ring desktop-globals-to-save) (insert "(setq kill-ring-yank-pointer (nthcdr " @@ -1006,71 +1198,242 @@ This function also sets `desktop-dirname' to nil." (defvar desktop-lazy-timer nil) ;; ---------------------------------------------------------------------------- -(defun desktop--restore-in-this-display-p () - (or desktop-restore-in-current-display - (and (eq system-type 'windows-nt) (not (display-graphic-p))))) - -(defun desktop--find-frame-in-display (frames display) - (let (result) - (while (and frames (not result)) - (if (equal display (frame-parameter (car frames) 'display)) - (setq result (car frames)) - (setq frames (cdr frames)))) - result)) - -(defun desktop--make-full-frame (full display config) - (let ((width (and (eq full 'fullheight) (cdr (assq 'width config)))) - (height (and (eq full 'fullwidth) (cdr (assq 'height config)))) - (params '((visibility))) +(defvar desktop--reuse-list nil + "Internal use only.") + +(defun desktop--find-frame (predicate display &rest args) + "Find a suitable frame in `desktop--reuse-list'. +Look through frames whose display property matches DISPLAY and +return the first one for which (PREDICATE frame ARGS) returns t. +If PREDICATE is nil, it is always satisfied. Internal use only. +This is an auxiliary function for `desktop--select-frame'." + (cl-find-if (lambda (frame) + (and (equal (frame-parameter frame 'display) display) + (or (null predicate) + (apply predicate frame args)))) + desktop--reuse-list)) + +(defun desktop--select-frame (display frame-cfg) + "Look for an existing frame to reuse. +DISPLAY is the display where the frame will be shown, and FRAME-CFG +is the parameter list of the frame being restored. Internal use only." + (if (eq desktop-restoring-reuses-frames t) + (let ((frame nil) + mini) + ;; There are no fancy heuristics there. We could implement some + ;; based on frame size and/or position, etc., but it is not clear + ;; that any "gain" (in the sense of reduced flickering, etc.) is + ;; worth the added complexity. In fact, the code below mainly + ;; tries to work nicely when M-x desktop-read is used after a desktop + ;; session has already been loaded. The other main use case, which + ;; is the initial desktop-read upon starting Emacs, should usually + ;; only have one, or very few, frame(s) to reuse. + (cond ((null display) + ;; When the target is tty, every existing frame is reusable. + (setq frame (desktop--find-frame nil display))) + ((car (setq mini (cdr (assq 'desktop--mini frame-cfg)))) + ;; If the frame has its own minibuffer, let's see whether + ;; that frame has already been loaded (which can happen after + ;; M-x desktop-read). + (setq frame (desktop--find-frame + (lambda (f m) + (equal (frame-parameter f 'desktop--mini) m)) + display mini)) + ;; If it has not been loaded, and it is not a minibuffer-only frame, + ;; let's look for an existing non-minibuffer-only frame to reuse. + (unless (or frame (eq (cdr (assq 'minibuffer frame-cfg)) 'only)) + (setq frame (desktop--find-frame + (lambda (f) + (let ((w (frame-parameter f 'minibuffer))) + (and (window-live-p w) + (window-minibuffer-p w) + (eq (window-frame w) f)))) + display)))) + (mini + ;; For minibufferless frames, check whether they already exist, + ;; and that they are linked to the right minibuffer frame. + (setq frame (desktop--find-frame + (lambda (f n) + (pcase-let (((and m `(,hasmini ,num)) + (frame-parameter f 'desktop--mini))) + (and m + (null hasmini) + (= num n) + (equal (cl-second (frame-parameter + (window-frame (minibuffer-window f)) + 'desktop--mini)) + n)))) + display (cl-second mini)))) + (t + ;; Default to just finding a frame in the same display. + (setq frame (desktop--find-frame nil display)))) + ;; If found, remove from the list. + (when frame + (setq desktop--reuse-list (delq frame desktop--reuse-list))) frame) - (when width - (setq params (append `((user-size . t) (width . ,width)) params) - config (assq-delete-all 'height config))) - (when height - (setq params (append `((user-size . t) (height . ,height)) params) - config (assq-delete-all 'width config))) - (setq frame (make-frame-on-display display params)) - (modify-frame-parameters frame config) + nil)) + +(defun desktop--make-frame (frame-cfg window-cfg) + "Set up a frame according to its saved state. +That means either creating a new frame or reusing an existing one. +FRAME-CFG is the parameter list of the new frame; WINDOW-CFG is +its window state. Internal use only." + (let* ((fullscreen (cdr (assq 'fullscreen frame-cfg))) + (lines (assq 'tool-bar-lines frame-cfg)) + (filtered-cfg (desktop--filter-frame-parms frame-cfg nil)) + (display (cdr (assq 'display filtered-cfg))) ;; post-filtering + alt-cfg frame) + + ;; This works around bug#14795 (or feature#14795, if not a bug :-) + (setq filtered-cfg (assq-delete-all 'tool-bar-lines filtered-cfg)) + (push '(tool-bar-lines . 0) filtered-cfg) + + (when fullscreen + ;; Currently Emacs has the limitation that it does not record the size + ;; and position of a frame before maximizing it, so we cannot save & + ;; restore that info. Instead, when restoring, we resort to creating + ;; invisible "fullscreen" frames of default size and then maximizing them + ;; (and making them visible) which at least is somewhat user-friendly + ;; when these frames are later de-maximized. + (let ((width (and (eq fullscreen 'fullheight) (cdr (assq 'width filtered-cfg)))) + (height (and (eq fullscreen 'fullwidth) (cdr (assq 'height filtered-cfg)))) + (visible (assq 'visibility filtered-cfg))) + (setq filtered-cfg (cl-delete-if (lambda (p) + (memq p '(visibility fullscreen width height))) + filtered-cfg :key #'car)) + (when width + (setq filtered-cfg (append `((user-size . t) (width . ,width)) + filtered-cfg))) + (when height + (setq filtered-cfg (append `((user-size . t) (height . ,height)) + filtered-cfg))) + ;; These are parameters to apply after creating/setting the frame. + (push visible alt-cfg) + (push (cons 'fullscreen fullscreen) alt-cfg))) + + ;; Time to select or create a frame an apply the big bunch of parameters + (if (setq frame (desktop--select-frame display filtered-cfg)) + (modify-frame-parameters frame + (if (eq (frame-parameter frame 'fullscreen) fullscreen) + ;; Workaround for bug#14949 + (assq-delete-all 'fullscreen filtered-cfg) + filtered-cfg)) + (setq frame (make-frame-on-display display filtered-cfg))) + + ;; Let's give the finishing touches (visibility, tool-bar, maximization). + (when lines (push lines alt-cfg)) + (when alt-cfg (modify-frame-parameters frame alt-cfg)) + ;; Now restore window state. + (window-state-put window-cfg (frame-root-window frame) 'safe) frame)) -(defun desktop--restore-frames () +(defun desktop--sort-states (state1 state2) + ;; Order: default minibuffer frame + ;; other frames with minibuffer, ascending ID + ;; minibufferless frames, ascending ID + (pcase-let ((`(,_p1 ,hasmini1 ,num1 ,default1) (assq 'desktop--mini (car state1))) + (`(,_p2 ,hasmini2 ,num2 ,default2) (assq 'desktop--mini (car state2)))) + (cond (default1 t) + (default2 nil) + ((eq hasmini1 hasmini2) (< num1 num2)) + (t hasmini1)))) + +(defun desktop-restoring-frames-p () + "True if calling `desktop-restore-frames' will actually restore frames." + (and desktop-restore-frames desktop-saved-frame-states t)) + +(defun desktop-restore-frames () "Restore window/frame configuration. -Internal use only." - (when (and desktop-restore-frames desktop--saved-states) - (let ((frames (frame-list)) - (current (frame-parameter nil 'display)) - (selected nil)) - (dolist (state desktop--saved-states) +This function depends on the value of `desktop-saved-frame-states' +being set (usually, by reading it from the desktop)." + (when (desktop-restoring-frames-p) + (let* ((frame-mb-map nil) ;; Alist of frames with their own minibuffer + (delete-saved (eq desktop-restore-in-current-display 'delete)) + (forcing (not (desktop-restore-in-original-display-p))) + (target (and forcing (cons 'display (frame-parameter nil 'display))))) + + ;; Sorting saved states allows us to easily restore minibuffer-owning frames + ;; before minibufferless ones. + (setq desktop-saved-frame-states (sort desktop-saved-frame-states + #'desktop--sort-states)) + ;; Potentially all existing frames are reusable. Later we will decide which ones + ;; to reuse, and how to deal with any leftover. + (setq desktop--reuse-list (frame-list)) + + (dolist (state desktop-saved-frame-states) (condition-case err - (let* ((config (car state)) - (display (if (desktop--restore-in-this-display-p) - (setcdr (assq 'display config) current) - (cdr (assq 'display config)))) - (full (cdr (assq 'fullscreen config))) - (frame (and (not full) - (desktop--find-frame-in-display frames display)))) - (cond (full - ;; treat fullscreen/maximized frames specially - (setq frame (desktop--make-full-frame full display config))) - (frame - ;; found a frame in the right display -- reuse - (setq frames (delq frame frames)) - (modify-frame-parameters frame config)) - (t - ;; no frames in the display -- make a new one - (setq frame (make-frame-on-display display config)))) - ;; restore windows - (window-state-put (cdr state) (frame-root-window frame) 'safe) - (unless selected (setq selected frame))) + (pcase-let* ((`(,frame-cfg . ,window-cfg) state) + ((and d-mini `(,hasmini ,num ,default)) + (cdr (assq 'desktop--mini frame-cfg))) + (frame nil) (to-tty nil)) + ;; Only set target if forcing displays and the target display is different. + (if (or (not forcing) + (equal target (or (assq 'display frame-cfg) '(display . nil)))) + (setq desktop--target-display nil) + (setq desktop--target-display target + to-tty (null (cdr target)))) + ;; 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 desktop--target-display + (or delete-saved + (and to-tty + (eq (cdr (assq 'minibuffer frame-cfg)) 'only)))) + + ;; 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 desktop--mini. Process as normal frame. + (to-tty) ;; Ignore minibuffer stuff and process as normal frame. + (hasmini ;; 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. + (let ((mb-frame (cdr (assq num frame-mb-map)))) + (unless (frame-live-p mb-frame) + (error "Minibuffer frame %s not found" num)) + (let ((mb-param (assq 'minibuffer frame-cfg)) + (mb-window (minibuffer-window mb-frame))) + (unless (and (window-live-p mb-window) + (window-minibuffer-p mb-window)) + (error "Not a minibuffer window %s" mb-window)) + (if mb-param + (setcdr mb-param mb-window) + (push (cons 'minibuffer mb-window) frame-cfg)))))) + ;; OK, we're ready at last to create (or reuse) a frame and + ;; restore the window config. + (setq frame (desktop--make-frame frame-cfg window-cfg)) + ;; Set default-minibuffer if required. + (when default (setq default-minibuffer-frame frame)) + ;; Store NUM/frame to assign to minibufferless frames. + (when hasmini (push (cons num frame) frame-mb-map)))) (error - (message "Error restoring frame: %S" (error-message-string err))))) - (when selected - ;; make sure the original selected frame is visible and selected - (unless (or (frame-parameter selected 'visibility) (daemonp)) - (modify-frame-parameters selected '((visibility . t)))) - (select-frame-set-input-focus selected) - ;; delete any remaining frames - (mapc #'delete-frame frames))))) + (delay-warning 'desktop (error-message-string err) :error)))) + + ;; 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) + + ;; Delete remaining frames, but do not fail if some resist being deleted. + (unless (eq desktop-restoring-reuses-frames 'keep) + (dolist (frame desktop--reuse-list) + (condition-case err + (delete-frame frame) + (error + (delay-warning 'desktop (error-message-string err)))))) + (setq desktop--reuse-list nil) + ;; Make sure there's at least one visible frame, and select it. + (unless (or (daemonp) + (cl-find-if #'frame-visible-p (frame-list))) + (let ((visible (if (frame-live-p default-minibuffer-frame) + default-minibuffer-frame + (car (frame-list))))) + (make-frame-visible visible) + (select-frame-set-input-focus visible)))))) ;;;###autoload (defun desktop-read (&optional dirname) @@ -1131,16 +1494,17 @@ Using it may cause conflicts. Use it anyway? " owner))))) (file-error (message "Couldn't record use of desktop file") (sit-for 1)))) - ;; `desktop-create-buffer' puts buffers at end of the buffer list. - ;; We want buffers existing prior to evaluating the desktop (and - ;; not reused) to be placed at the end of the buffer list, so we - ;; move them here. - (mapc 'bury-buffer - (nreverse (cdr (memq desktop-first-buffer (nreverse (buffer-list)))))) - (switch-to-buffer (car (buffer-list))) + (unless (desktop-restoring-frames-p) + ;; `desktop-create-buffer' puts buffers at end of the buffer list. + ;; We want buffers existing prior to evaluating the desktop (and + ;; not reused) to be placed at the end of the buffer list, so we + ;; move them here. + (mapc 'bury-buffer + (nreverse (cdr (memq desktop-first-buffer (nreverse (buffer-list)))))) + (switch-to-buffer (car (buffer-list)))) (run-hooks 'desktop-delay-hook) (setq desktop-delay-hook nil) - (desktop--restore-frames) + (desktop-restore-frames) (run-hooks 'desktop-after-read-hook) (message "Desktop: %d buffer%s restored%s%s." desktop-buffer-ok-count @@ -1152,18 +1516,19 @@ Using it may cause conflicts. Use it anyway? " owner))))) (format ", %d to restore lazily" (length desktop-buffer-args-list)) "")) - ;; Bury the *Messages* buffer to not reshow it when burying - ;; the buffer we switched to above. - (when (buffer-live-p (get-buffer "*Messages*")) - (bury-buffer "*Messages*")) - ;; Clear all windows' previous and next buffers, these have - ;; been corrupted by the `switch-to-buffer' calls in - ;; `desktop-restore-file-buffer' (bug#11556). This is a - ;; brute force fix and should be replaced by a more subtle - ;; strategy eventually. - (walk-window-tree (lambda (window) - (set-window-prev-buffers window nil) - (set-window-next-buffers window nil))) + (unless (desktop-restoring-frames-p) + ;; Bury the *Messages* buffer to not reshow it when burying + ;; the buffer we switched to above. + (when (buffer-live-p (get-buffer "*Messages*")) + (bury-buffer "*Messages*")) + ;; Clear all windows' previous and next buffers, these have + ;; been corrupted by the `switch-to-buffer' calls in + ;; `desktop-restore-file-buffer' (bug#11556). This is a + ;; brute force fix and should be replaced by a more subtle + ;; strategy eventually. + (walk-window-tree (lambda (window) + (set-window-prev-buffers window nil) + (set-window-next-buffers window nil)))) t)) ;; No desktop file found. (desktop-clear) @@ -1387,17 +1752,15 @@ integer, start a new timer to call `desktop-auto-save' in that many seconds." (set-mark desktop-buffer-mark))) ;; Never override file system if the file really is read-only marked. (when desktop-buffer-read-only (setq buffer-read-only desktop-buffer-read-only)) - (while desktop-buffer-locals - (let ((this (car desktop-buffer-locals))) - (if (consp this) - ;; an entry of this form `(symbol . value)' - (progn - (make-local-variable (car this)) - (set (car this) (cdr this))) - ;; an entry of the form `symbol' - (make-local-variable this) - (makunbound this))) - (setq desktop-buffer-locals (cdr desktop-buffer-locals)))))))) + (dolist (this desktop-buffer-locals) + (if (consp this) + ;; an entry of this form `(symbol . value)' + (progn + (make-local-variable (car this)) + (set (car this) (cdr this))) + ;; an entry of the form `symbol' + (make-local-variable this) + (makunbound this)))))))) ;; ---------------------------------------------------------------------------- ;; Backward compatibility -- update parameters to 205 standards. |
