Subject: Re: bug#51883: 29.0.50; Command to get accidentally deleted frames back

From: Juri Linkov <[email-masked]>
Date: Mon, 24 Jan 2022 20:12:48 +0200

>> I didn't mean to keep actual frames around.  I thought about keeping
>> only the same data that is used by `clone-frame` to make a new frame
>> identical to the original frame.  In case of `undelete-frame` this data
>> can be used to make a frame identical to the deleted frame.
>> I.e. a lightweight version of framesets, that avoids loading frameset.el.
>> This mostly means that after deleting the frame, only frame parameters
>> are kept from garbage collection, that later can be reused when making
>> a new frame on undeletion.
>
> Ah, got it, thanks.  Then fully agreed.  I was heading in the same
> direction when I suggested to try and use as little as possible of
> `frameset.el` and move the resulting code to `frame.el`.

While copying some code from `clone-frame` to `undelete-frame`,
I noticed that `clone-frame` doesn't work on non-GTK builds,
because window-id frame parameters keep frame-specific X-windows,
so `clone-frame` is broken in emacs-28.

Eli, is it ok to push the first patch to the release branch?

diff --git a/lisp/frame.el b/lisp/frame.el
index 599ffe591a..5de554eee6 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -798,8 +798,9 @@ clone-frame
          (windows (unless no-windows
                     (window-state-get (frame-root-window frame))))
          (default-frame-alist
-           (seq-remove (lambda (elem) (eq (car elem) 'name))
-                       (frame-parameters frame)))
+          (seq-remove (lambda (elem)
+                        (memq (car elem) '(name window-id outer-window-id parent-id)))
+                      (frame-parameters frame)))
          (new-frame (make-frame)))
     (when windows
       (window-state-put windows (frame-root-window new-frame) 'safe))

Then the second patch for master removes the dependency on frameset.el:

diff --git a/lisp/frame.el b/lisp/frame.el
index 599ffe591a..5de554eee6 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2529,8 +2530,6 @@ delete-other-frames
         (if iconify (iconify-frame this) (delete-frame this)))
       (setq this next))))
 
-(eval-when-compile (require 'frameset))
-
 (defvar undelete-frame--deleted-frames nil
   "Internal variable used by `undelete-frame--handle-delete-frame'.")
 
@@ -2540,20 +2539,12 @@ undelete-frame--handle-delete-frame
   (when (frame-live-p frame)
     (setq undelete-frame--deleted-frames
           (cons
-           (cons
+           (list
             (display-graphic-p)
-            (frameset-save
-             (list frame)
-             ;; When the daemon is started from a graphical
-             ;; environment, TTY frames have a 'display' parameter set
-             ;; to the value of $DISPLAY (see the note in
-             ;; `server--on-display-p').  Do not store that parameter
-             ;; in the frameset, otherwise `frameset-restore' attempts
-             ;; to restore a graphical frame.
-             :filters (if (display-graphic-p)
-                          frameset-filter-alist
-                        (cons '(display . :never)
-                              frameset-filter-alist))))
+            (seq-remove (lambda (elem)
+                          (memq (car elem) '(name window-id outer-window-id parent-id)))
+                       (frame-parameters frame))
+            (window-state-get (frame-root-window frame)))
            undelete-frame--deleted-frames))
     (if (> (length undelete-frame--deleted-frames) 16)
         (setq undelete-frame--deleted-frames
@@ -2583,26 +2574,25 @@ undelete-frame
     (if (consp arg)
         (user-error "Missing deleted frame number argument")
       (let* ((number (pcase arg ('nil 1) ('- -1) (_ arg)))
-             (frames (frame-list))
-             (frameset (nth (1- number) undelete-frame--deleted-frames))
+             (frame-data (nth (1- number) undelete-frame--deleted-frames))
              (graphic (display-graphic-p)))
         (if (not (<= 1 number 16))
             (user-error "%d is not a valid deleted frame number argument"
                         number)
-          (if (not frameset)
+          (if (not frame-data)
               (user-error "No deleted frame with number %d" number)
-            (if (not (eq graphic (car frameset)))
+            (if (not (eq graphic (nth 0 frame-data)))
                 (user-error
                  "Cannot undelete a %s display frame on a %s display"
                  (if graphic "non-graphic" "graphic")
                  (if graphic "graphic" "non-graphic"))
               (setq undelete-frame--deleted-frames
-                    (delq frameset undelete-frame--deleted-frames))
-              (frameset-restore (cdr frameset))
-              (let ((frame (car (seq-difference (frame-list) frames))))
-                (when frame
-                  (select-frame-set-input-focus frame)
-                  frame)))))))))
+                    (delq frame-data undelete-frame--deleted-frames))
+              (let* ((default-frame-alist (nth 1 frame-data))
+                     (frame (make-frame)))
+                (window-state-put (nth 2 frame-data) (frame-root-window frame) 'safe)
+                (select-frame-set-input-focus frame)
+                frame))))))))
 
 ;;; Window dividers.
 (defgroup window-divider nil