diff options
Diffstat (limited to 'lisp/frameset.el')
| -rw-r--r-- | lisp/frameset.el | 1169 |
1 files changed, 859 insertions, 310 deletions
diff --git a/lisp/frameset.el b/lisp/frameset.el index 63ee9af23fc..53ab0a5f081 100644 --- a/lisp/frameset.el +++ b/lisp/frameset.el | |||
| @@ -41,155 +41,560 @@ | |||
| 41 | (require 'cl-lib) | 41 | (require 'cl-lib) |
| 42 | 42 | ||
| 43 | 43 | ||
| 44 | ;; Framesets have two fields: | 44 | (cl-defstruct (frameset (:type vector) :named |
| 45 | ;; - properties: a property list to store both frameset-specific and | 45 | (:constructor frameset--make) |
| 46 | ;; user-defined serializable data. Currently defined properties | 46 | ;; Copier is defined below. |
| 47 | ;; include: | 47 | (:copier nil)) |
| 48 | ;; :version ID - Identifies the version of the frameset struct; | 48 | |
| 49 | ;; this is the only property always present and | 49 | "A frameset encapsulates a serializable view of a set of frames and windows. |
| 50 | ;; must not be modified. | 50 | |
| 51 | ;; :app APPINFO - Freeform. Can be used by applications and | 51 | It contains the following slots, which can be accessed with |
| 52 | ;; packages to indicate the intended (but by no | 52 | \(frameset-SLOT fs) and set with (setf (frameset-SLOT fs) VALUE): |
| 53 | ;; means exclusive) use of the frameset. For | 53 | |
| 54 | ;; example, currently desktop.el sets :app to | 54 | version A read-only version number, identifying the format |
| 55 | ;; `(desktop . ,desktop-file-version). | 55 | of the frameset struct. Currently its value is 1. |
| 56 | ;; :name NAME - The name of the frameset instance; a string. | 56 | timestamp A read-only timestamp, the output of `current-time'. |
| 57 | ;; :desc TEXT - A description for user consumption (to choose | 57 | app A symbol, or a list whose first element is a symbol, which |
| 58 | ;; among framesets, etc.); a string. | 58 | identifies the creator of the frameset and related info; |
| 59 | ;; - states: an alist of items (FRAME-PARAMETERS . WINDOW-STATE) in | 59 | for example, desktop.el sets this slot to a list |
| 60 | ;; no particular order. Each item represents a frame to be | 60 | `(desktop . ,desktop-file-version). |
| 61 | ;; restored. | 61 | name A string, the name of the frameset instance. |
| 62 | 62 | description A string, a description for user consumption (to show in | |
| 63 | (cl-defstruct (frameset (:type list) :named | 63 | menus, messages, etc). |
| 64 | (:copier nil) | 64 | properties A property list, to store both frameset-specific and |
| 65 | (:predicate nil)) | 65 | user-defined serializable data. |
| 66 | properties ;; property list | 66 | states A list of items (FRAME-PARAMETERS . WINDOW-STATE), in no |
| 67 | states) ;; list of conses (frame-state . window-state) | 67 | particular order. Each item represents a frame to be |
| 68 | 68 | restored. FRAME-PARAMETERS is a frame's parameter alist, | |
| 69 | (defun copy-frameset (frameset) | 69 | extracted with (frame-parameters FRAME) and filtered |
| 70 | "Return a copy of FRAMESET. | 70 | through `frameset-filter-params'. |
| 71 | This is a deep copy done with `copy-tree'." | 71 | WINDOW-STATE is the output of `window-state-get' applied |
| 72 | to the root window of the frame. | ||
| 73 | |||
| 74 | To avoid collisions, it is recommended that applications wanting to add | ||
| 75 | private serializable data to `properties' either store all info under a | ||
| 76 | single, distinctive name, or use property names with a well-chosen prefix. | ||
| 77 | |||
| 78 | A frameset is intended to be used through the following simple API: | ||
| 79 | |||
| 80 | - `frameset-save', the type's constructor, captures all or a subset of the | ||
| 81 | live frames, and returns a serializable snapshot of them (a frameset). | ||
| 82 | - `frameset-restore' takes a frameset, and restores the frames and windows | ||
| 83 | it describes, as faithfully as possible. | ||
| 84 | - `frameset-p' is the predicate for the frameset type. | ||
| 85 | - `frameset-valid-p' checks a frameset's validity. | ||
| 86 | - `frameset-copy' returns a deep copy of a frameset. | ||
| 87 | - `frameset-prop' is a `setf'able accessor for the contents of the | ||
| 88 | `properties' slot. | ||
| 89 | - The `frameset-SLOT' accessors described above." | ||
| 90 | |||
| 91 | (version 1 :read-only t) | ||
| 92 | (timestamp (current-time) :read-only t) | ||
| 93 | (app nil) | ||
| 94 | (name nil) | ||
| 95 | (description nil) | ||
| 96 | (properties nil) | ||
| 97 | (states nil)) | ||
| 98 | |||
| 99 | ;; Add nicer docstrings for built-in predicate and accessors. | ||
| 100 | (put 'frameset-p 'function-documentation | ||
| 101 | "Return non-nil if OBJECT is a frameset, nil otherwise.\n\n(fn OBJECT)") | ||
| 102 | (put 'frameset-version 'function-documentation | ||
| 103 | "Return the version number of FRAMESET.\n | ||
| 104 | It is an integer that identifies the format of the frameset struct. | ||
| 105 | This slot cannot be modified.\n\n(fn FRAMESET)") | ||
| 106 | (put 'frameset-timestamp 'function-documentation | ||
| 107 | "Return the creation timestamp of FRAMESET.\n | ||
| 108 | The value is in the format returned by `current-time'. | ||
| 109 | This slot cannot be modified.\n\n(fn FRAMESET)") | ||
| 110 | (put 'frameset-app 'function-documentation | ||
| 111 | "Return the application identifier for FRAMESET.\n | ||
| 112 | The value is either a symbol, like `my-app', or a list | ||
| 113 | \(my-app ADDITIONAL-DATA...).\n\n(fn FRAMESET)") | ||
| 114 | (put 'frameset-name 'function-documentation | ||
| 115 | "Return the name of FRAMESET (a string).\n\n(fn FRAMESET)") | ||
| 116 | (put 'frameset-description 'function-documentation | ||
| 117 | "Return the description of FRAMESET (a string).\n\n(fn FRAMESET)") | ||
| 118 | (put 'frameset-properties 'function-documentation | ||
| 119 | "Return the property list of FRAMESET.\n | ||
| 120 | This list is useful to store both frameset-specific and user-defined | ||
| 121 | serializable data. The simplest way to access and modify it is | ||
| 122 | through `frameset-prop' (which see).\n\n(fn FRAMESET)") | ||
| 123 | (put 'frameset-states 'function-documentation | ||
| 124 | "Return the list of frame states of FRAMESET.\n | ||
| 125 | A frame state is a pair (FRAME-PARAMETERS . WINDOW-STATE), where | ||
| 126 | FRAME-PARAMETERS is a frame's parameter alist, extracted with | ||
| 127 | \(frame-parameters FRAME) and filtered through `frameset-filter-params', | ||
| 128 | and WINDOW-STATE is the output of `window-state-get' applied to the | ||
| 129 | root window of the frame.\n | ||
| 130 | IMPORTANT: Modifying this slot may cause frameset functions to fail, | ||
| 131 | unless the type constraints defined above are respected.\n\n(fn FRAMESET)") | ||
| 132 | |||
| 133 | ;; We autoloaded this for use in register.el, but now that we use registerv | ||
| 134 | ;; objects, this autoload is not useful any more. | ||
| 135 | ;; ;;;###autoload (autoload 'frameset-p "frameset" | ||
| 136 | ;; ;;;###autoload "Return non-nil if OBJECT is a frameset, nil otherwise." nil) | ||
| 137 | |||
| 138 | (defun frameset-copy (frameset) | ||
| 139 | "Return a deep copy of FRAMESET. | ||
| 140 | FRAMESET is copied with `copy-tree'." | ||
| 72 | (copy-tree frameset t)) | 141 | (copy-tree frameset t)) |
| 73 | 142 | ||
| 74 | ;;;###autoload | 143 | (defun frameset-valid-p (object) |
| 75 | (defun frameset-p (frameset) | 144 | "Return non-nil if OBJECT is a valid frameset, nil otherwise." |
| 76 | "If FRAMESET is a frameset, return its :version. | 145 | (and (frameset-p object) |
| 77 | Else return nil." | 146 | (integerp (frameset-version object)) |
| 78 | (and (eq (car-safe frameset) 'frameset) | 147 | (consp (frameset-timestamp object)) |
| 79 | (plist-get (cl-second frameset) :version))) | 148 | (let ((app (frameset-app object))) |
| 149 | (or (null app) ; APP is nil | ||
| 150 | (symbolp app) ; or a symbol | ||
| 151 | (and (consp app) ; or a list | ||
| 152 | (symbolp (car app))))) ; starting with a symbol | ||
| 153 | (stringp (or (frameset-name object) "")) | ||
| 154 | (stringp (or (frameset-description object) "")) | ||
| 155 | (listp (frameset-properties object)) | ||
| 156 | (let ((states (frameset-states object))) | ||
| 157 | (and (listp states) | ||
| 158 | (cl-every #'consp (frameset-states object)))) | ||
| 159 | (frameset-version object))) ; And VERSION is non-nil. | ||
| 160 | |||
| 161 | (defun frameset--prop-setter (frameset property value) | ||
| 162 | "Setter function for `frameset-prop'. Internal use only." | ||
| 163 | (setf (frameset-properties frameset) | ||
| 164 | (plist-put (frameset-properties frameset) property value)) | ||
| 165 | value) | ||
| 80 | 166 | ||
| 81 | ;; A setf'able accessor to the frameset's properties | 167 | ;; A setf'able accessor to the frameset's properties |
| 82 | (defun frameset-prop (frameset prop) | 168 | (defun frameset-prop (frameset property) |
| 83 | "Return the value of the PROP property of FRAMESET. | 169 | "Return the value for FRAMESET of PROPERTY. |
| 84 | |||
| 85 | Properties other than :version can be set with | ||
| 86 | 170 | ||
| 87 | (setf (frameset-prop FRAMESET PROP) NEW-VALUE)" | 171 | Properties can be set with |
| 88 | (plist-get (frameset-properties frameset) prop)) | ||
| 89 | 172 | ||
| 90 | (gv-define-setter frameset-prop (val fs prop) | 173 | (setf (frameset-prop FRAMESET PROPERTY) NEW-VALUE)" |
| 91 | (macroexp-let2 nil v val | 174 | (declare (gv-setter frameset--prop-setter)) |
| 92 | `(progn | 175 | (plist-get (frameset-properties frameset) property)) |
| 93 | (cl-assert (not (eq ,prop :version)) t ":version can not be set") | ||
| 94 | (setf (frameset-properties ,fs) | ||
| 95 | (plist-put (frameset-properties ,fs) ,prop ,v)) | ||
| 96 | ,v))) | ||
| 97 | 176 | ||
| 98 | 177 | ||
| 99 | ;; Filtering | 178 | ;; Filtering |
| 100 | 179 | ||
| 101 | (defvar frameset-filter-alist | 180 | ;; What's the deal with these "filter alists"? |
| 102 | '((background-color . frameset-filter-sanitize-color) | 181 | ;; |
| 103 | (buffer-list . t) | 182 | ;; Let's say that Emacs' frame parameters were never designed as a tool to |
| 104 | (buffer-predicate . t) | 183 | ;; precisely record (or restore) a frame's state. They grew organically, |
| 105 | (buried-buffer-list . t) | 184 | ;; and their uses and behaviors reflect their history. In using them to |
| 106 | (font . frameset-filter-save-parm) | 185 | ;; implement framesets, the unwary implementer, or the prospective package |
| 107 | (foreground-color . frameset-filter-sanitize-color) | 186 | ;; writer willing to use framesets in their code, might fall victim of some |
| 108 | (fullscreen . frameset-filter-save-parm) | 187 | ;; unexpected... oddities. |
| 109 | (GUI:font . frameset-filter-restore-parm) | 188 | ;; |
| 110 | (GUI:fullscreen . frameset-filter-restore-parm) | 189 | ;; You can find frame parameters that: |
| 111 | (GUI:height . frameset-filter-restore-parm) | 190 | ;; |
| 112 | (GUI:width . frameset-filter-restore-parm) | 191 | ;; - can be used to get and set some data from the frame's current state |
| 113 | (height . frameset-filter-save-parm) | 192 | ;; (`height', `width') |
| 114 | (left . frameset-filter-iconified) | 193 | ;; - can be set at creation time, and setting them afterwards has no effect |
| 115 | (minibuffer . frameset-filter-minibuffer) | 194 | ;; (`window-state', `minibuffer') |
| 116 | (top . frameset-filter-iconified) | 195 | ;; - can be set at creation time, and setting them afterwards will fail with |
| 117 | (width . frameset-filter-save-parm)) | 196 | ;; an error, *unless* you set it to the same value, a noop (`border-width') |
| 197 | ;; - act differently when passed at frame creation time, and when set | ||
| 198 | ;; afterwards (`height') | ||
| 199 | ;; - affect the value of other parameters (`name', `visibility') | ||
| 200 | ;; - can be ignored by window managers (most positional args, like `height', | ||
| 201 | ;; `width', `left' and `top', and others, like `auto-raise', `auto-lower') | ||
| 202 | ;; - can be set externally in X resources or Window registry (again, most | ||
| 203 | ;; positional parameters, and also `toolbar-lines', `menu-bar-lines' etc.) | ||
| 204 | ;, - can contain references to live objects (`buffer-list', `minibuffer') or | ||
| 205 | ;; code (`buffer-predicate') | ||
| 206 | ;; - are set automatically, and cannot be changed (`window-id', `parent-id'), | ||
| 207 | ;; but setting them produces no error | ||
| 208 | ;; - have a noticeable effect in some window managers, and are ignored in | ||
| 209 | ;; others (`menu-bar-lines') | ||
| 210 | ;; - can not be safely set in a tty session and then copied back to a GUI | ||
| 211 | ;; session (`font', `background-color', `foreground-color') | ||
| 212 | ;; | ||
| 213 | ;; etc etc. | ||
| 214 | ;; | ||
| 215 | ;; Which means that, in order to save a parameter alist to disk and read it | ||
| 216 | ;; back later to reconstruct a frame, some processing must be done. That's | ||
| 217 | ;; what `frameset-filter-params' and the `frameset-*-filter-alist' variables | ||
| 218 | ;; are for. | ||
| 219 | ;; | ||
| 220 | ;; First, a clarification. The word "filter" in these names refers to both | ||
| 221 | ;; common meanings of filter: to filter out (i.e., to remove), and to pass | ||
| 222 | ;; through a transformation function (think `filter-buffer-substring'). | ||
| 223 | ;; | ||
| 224 | ;; `frameset-filter-params' takes a parameter alist PARAMETERS, a filtering | ||
| 225 | ;; alist FILTER-ALIST, and a flag SAVING to indicate whether we are filtering | ||
| 226 | ;; parameters with the intent of saving a frame or restoring it. It then | ||
| 227 | ;; accumulates an output alist, FILTERED, by checking each parameter in | ||
| 228 | ;; PARAMETERS against FILTER-ALIST and obeying any rule found there. The | ||
| 229 | ;; absence of a rule just means the parameter/value pair (called CURRENT in | ||
| 230 | ;; filtering functions) is copied to FILTERED as is. Keyword values :save, | ||
| 231 | ;; :restore and :never tell the function to copy CURRENT to FILTERED in the | ||
| 232 | ;; respective situations, that is, when saving, restoring, or never at all. | ||
| 233 | ;; Values :save and :restore are not used in this package, because usually if | ||
| 234 | ;; you don't want to save a parameter, you don't want to restore it either. | ||
| 235 | ;; But they can be useful, for example, if you already have a saved frameset | ||
| 236 | ;; created with some intent, and want to reuse it for a different objective | ||
| 237 | ;; where the expected parameter list has different requirements. | ||
| 238 | ;; | ||
| 239 | ;; Finally, the value can also be a filtering function, or a filtering | ||
| 240 | ;; function plus some arguments. The function is called for each matching | ||
| 241 | ;; parameter, and receives CURRENT (the parameter/value pair being processed), | ||
| 242 | ;; FILTERED (the output alist so far), PARAMETERS (the full parameter alist), | ||
| 243 | ;; SAVING (the save/restore flag), plus any additional ARGS set along the | ||
| 244 | ;; function in the `frameset-*-filter-alist' entry. The filtering function | ||
| 245 | ;; then has the possibility to pass along CURRENT, or reject it altogether, | ||
| 246 | ;; or pass back a (NEW-PARAM . NEW-VALUE) pair, which does not even need to | ||
| 247 | ;; refer to the same parameter (so you can filter `width' and return `height' | ||
| 248 | ;; and vice versa, if you're feeling silly and want to mess with the user's | ||
| 249 | ;; mind). As a help in deciding what to do, the filtering function has | ||
| 250 | ;; access to PARAMETERS, but must not change it in any way. It also has | ||
| 251 | ;; access to FILTERED, which can be modified at will. This allows two or | ||
| 252 | ;; more filters to coordinate themselves, because in general there's no way | ||
| 253 | ;; to predict the order in which they will be run. | ||
| 254 | ;; | ||
| 255 | ;; So, which parameters are filtered by default, and why? Let's see. | ||
| 256 | ;; | ||
| 257 | ;; - `buffer-list', `buried-buffer-list', `buffer-predicate': They contain | ||
| 258 | ;; references to live objects, or in the case of `buffer-predicate', it | ||
| 259 | ;; could also contain an fbound symbol (a predicate function) that could | ||
| 260 | ;; not be defined in a later session. | ||
| 261 | ;; | ||
| 262 | ;; - `window-id', `outer-window-id', `parent-id': They are assigned | ||
| 263 | ;; automatically and cannot be set, so keeping them is harmless, but they | ||
| 264 | ;; add clutter. `window-system' is similar: it's assigned at frame | ||
| 265 | ;; creation, and does not serve any useful purpose later. | ||
| 266 | ;; | ||
| 267 | ;; - `left', `top': Only problematic when saving an iconified frame, because | ||
| 268 | ;; when the frame is iconified they are set to (- 32000), which doesn't | ||
| 269 | ;; really help in restoring the frame. Better to remove them and let the | ||
| 270 | ;; window manager choose a default position for the frame. | ||
| 271 | ;; | ||
| 272 | ;; - `background-color', `foreground-color': In tty frames they can be set | ||
| 273 | ;; to "unspecified-bg" and "unspecified-fg", which aren't understood on | ||
| 274 | ;; GUI sessions. They have to be filtered out when switching from tty to | ||
| 275 | ;; a graphical display. | ||
| 276 | ;; | ||
| 277 | ;; - `tty', `tty-type': These are tty-specific. When switching to a GUI | ||
| 278 | ;; display they do no harm, but they clutter the parameter alist. | ||
| 279 | ;; | ||
| 280 | ;; - `minibuffer': It can contain a reference to a live window, which cannot | ||
| 281 | ;; be serialized. Because of Emacs' idiosyncratic treatment of this | ||
| 282 | ;; parameter, frames created with (minibuffer . t) have a parameter | ||
| 283 | ;; (minibuffer . #<window...>), while frames created with | ||
| 284 | ;; (minibuffer . #<window...>) have (minibuffer . nil), which is madness | ||
| 285 | ;; but helps to differentiate between minibufferless and "normal" frames. | ||
| 286 | ;; So, changing (minibuffer . #<window...>) to (minibuffer . t) allows | ||
| 287 | ;; Emacs to set up the new frame correctly. Nice, uh? | ||
| 288 | ;; | ||
| 289 | ;; - `name': If this parameter is directly set, `explicit-name' is | ||
| 290 | ;; automatically set to t, and then `name' no longer changes dynamically. | ||
| 291 | ;; So, in general, not saving `name' is the right thing to do, though | ||
| 292 | ;; surely there are applications that will want to override this filter. | ||
| 293 | ;; | ||
| 294 | ;; - `font', `fullscreen', `height' and `width': These parameters suffer | ||
| 295 | ;; from the fact that they are badly mangled when going through a | ||
| 296 | ;; tty session, though not all in the same way. When saving a GUI frame | ||
| 297 | ;; and restoring it in a tty, the height and width of the new frame are | ||
| 298 | ;; those of the tty screen (let's say 80x25, for example); going back | ||
| 299 | ;; to a GUI session means getting frames of the tty screen size (so all | ||
| 300 | ;; your frames are 80 cols x 25 rows). For `fullscreen' there's a | ||
| 301 | ;; similar problem, because a tty frame cannot really be fullscreen or | ||
| 302 | ;; maximized, so the state is lost. The problem with `font' is a bit | ||
| 303 | ;; different, because a valid GUI font spec in `font' turns into | ||
| 304 | ;; (font . "tty") in a tty frame, and when read back into a GUI session | ||
| 305 | ;; it fails because `font's value is no longer a valid font spec. | ||
| 306 | ;; | ||
| 307 | ;; In most cases, the filtering functions just do the obvious thing: remove | ||
| 308 | ;; CURRENT when it is meaningless to keep it, or pass a modified copy if | ||
| 309 | ;; that helps (as in the case of `minibuffer'). | ||
| 310 | ;; | ||
| 311 | ;; The exception are the parameters in the last set, which should survive | ||
| 312 | ;; the roundtrip though tty-land. The answer is to add "stashing | ||
| 313 | ;; parameters", working in pairs, to shelve the GUI-specific contents and | ||
| 314 | ;; restore it once we're back in pixel country. That's what functions | ||
| 315 | ;; `frameset-filter-shelve-param' and `frameset-filter-unshelve-param' do. | ||
| 316 | ;; | ||
| 317 | ;; Basically, if you set `frameset-filter-shelve-param' as the filter for | ||
| 318 | ;; a parameter P, it will detect when it is restoring a GUI frame into a | ||
| 319 | ;; tty session, and save P's value in the custom parameter X:P, but only | ||
| 320 | ;; if X:P does not exist already (so it is not overwritten if you enter | ||
| 321 | ;; the tty session more than once). If you're not switching to a tty | ||
| 322 | ;; frame, the filter just passes CURRENT along. | ||
| 323 | ;; | ||
| 324 | ;; The parameter X:P, on the other hand, must have been setup to be | ||
| 325 | ;; filtered by `frameset-filter-unshelve-param', which unshelves the | ||
| 326 | ;; value: if we're entering a GUI session, returns P instead of CURRENT, | ||
| 327 | ;; while in other cases it just passes it along. | ||
| 328 | ;; | ||
| 329 | ;; The only additional trick is that `frameset-filter-shelve-param' does | ||
| 330 | ;; not set P if switching back to GUI and P already has a value, because | ||
| 331 | ;; it assumes that `frameset-filter-unshelve-param' did set it up. And | ||
| 332 | ;; `frameset-filter-unshelve-param', when unshelving P, must look into | ||
| 333 | ;; FILTERED to determine if P has already been set and if so, modify it; | ||
| 334 | ;; else just returns P. | ||
| 335 | ;; | ||
| 336 | ;; Currently, the value of X in X:P is `GUI', but you can use any prefix, | ||
| 337 | ;; by passing its symbol as argument in the filter: | ||
| 338 | ;; | ||
| 339 | ;; (my-parameter frameset-filter-shelve-param MYPREFIX) | ||
| 340 | ;; | ||
| 341 | ;; instead of | ||
| 342 | ;; | ||
| 343 | ;; (my-parameter . frameset-filter-shelve-param) | ||
| 344 | ;; | ||
| 345 | ;; Note that `frameset-filter-unshelve-param' does not need MYPREFIX | ||
| 346 | ;; because it is available from the parameter name in CURRENT. Also note | ||
| 347 | ;; that the colon between the prefix and the parameter name is hardcoded. | ||
| 348 | ;; The reason is that X:P is quite readable, and that the colon is a | ||
| 349 | ;; very unusual character in symbol names, other than in initial position | ||
| 350 | ;; in keywords (emacs -Q has only two such symbols, and one of them is a | ||
| 351 | ;; URL). So the probability of a collision with existing or future | ||
| 352 | ;; symbols is quite insignificant. | ||
| 353 | ;; | ||
| 354 | ;; Now, what about the filter alist variables? There are three of them, | ||
| 355 | ;; though only two sets of parameters: | ||
| 356 | ;; | ||
| 357 | ;; - `frameset-session-filter-alist' contains these filters that allow to | ||
| 358 | ;; save and restore framesets in-session, without the need to serialize | ||
| 359 | ;; the frameset or save it to disk (for example, to save a frameset in a | ||
| 360 | ;; register and restore it later). Filters in this list do not remove | ||
| 361 | ;; live objects, except in `minibuffer', which is dealt especially by | ||
| 362 | ;; `frameset-save' / `frameset-restore'. | ||
| 363 | ;; | ||
| 364 | ;; - `frameset-persistent-filter-alist' is the whole deal. It does all | ||
| 365 | ;; the filtering described above, and the result is ready to be saved on | ||
| 366 | ;; disk without loss of information. That's the format used by the | ||
| 367 | ;; desktop.el package, for example. | ||
| 368 | ;; | ||
| 369 | ;; IMPORTANT: These variables share structure and should NEVER be modified. | ||
| 370 | ;; | ||
| 371 | ;; - `frameset-filter-alist': The value of this variable is the default | ||
| 372 | ;; value for the FILTERS arguments of `frameset-save' and | ||
| 373 | ;; `frameset-restore'. It is set to `frameset-persistent-filter-alist', | ||
| 374 | ;; though it can be changed by specific applications. | ||
| 375 | ;; | ||
| 376 | ;; How to use them? | ||
| 377 | ;; | ||
| 378 | ;; The simplest way is just do nothing. The default should work | ||
| 379 | ;; reasonably and sensibly enough. But, what if you really need a | ||
| 380 | ;; customized filter alist? Then you can create your own variable | ||
| 381 | ;; | ||
| 382 | ;; (defvar my-filter-alist | ||
| 383 | ;; '((my-param1 . :never) | ||
| 384 | ;; (my-param2 . :save) | ||
| 385 | ;; (my-param3 . :restore) | ||
| 386 | ;; (my-param4 . my-filtering-function-without-args) | ||
| 387 | ;; (my-param5 my-filtering-function-with arg1 arg2) | ||
| 388 | ;; ;;; many other parameters | ||
| 389 | ;; ) | ||
| 390 | ;; "My customized parameter filter alist.") | ||
| 391 | ;; | ||
| 392 | ;; or, if you're only changing a few items, | ||
| 393 | ;; | ||
| 394 | ;; (defvar my-filter-alist | ||
| 395 | ;; (nconc '((my-param1 . :never) | ||
| 396 | ;; (my-param2 . my-filtering-function)) | ||
| 397 | ;; frameset-filter-alist) | ||
| 398 | ;; "My brief customized parameter filter alist.") | ||
| 399 | ;; | ||
| 400 | ;; and pass it to the FILTER arg of the save/restore functions, | ||
| 401 | ;; ALWAYS taking care of not modifying the original lists; if you're | ||
| 402 | ;; going to do any modifying of my-filter-alist, please use | ||
| 403 | ;; | ||
| 404 | ;; (nconc '((my-param1 . :never) ...) | ||
| 405 | ;; (copy-sequence frameset-filter-alist)) | ||
| 406 | ;; | ||
| 407 | ;; One thing you shouldn't forget is that they are alists, so searching | ||
| 408 | ;; in them is sequential. If you just want to change the default of | ||
| 409 | ;; `name' to allow it to be saved, you can set (name . nil) in your | ||
| 410 | ;; customized filter alist; it will take precedence over the latter | ||
| 411 | ;; setting. In case you decide that you *always* want to save `name', | ||
| 412 | ;; you can add it to `frameset-filter-alist': | ||
| 413 | ;; | ||
| 414 | ;; (push '(name . nil) frameset-filter-alist) | ||
| 415 | ;; | ||
| 416 | ;; In certain applications, having a parameter filtering function like | ||
| 417 | ;; `frameset-filter-params' can be useful, even if you're not using | ||
| 418 | ;; framesets. The interface of `frameset-filter-params' is generic | ||
| 419 | ;; and does not depend of global state, with one exception: it uses | ||
| 420 | ;; the internal variable `frameset--target-display' to decide if, and | ||
| 421 | ;; how, to modify the `display' parameter of FILTERED. But that | ||
| 422 | ;; should not represent any problem, because it's only meaningful | ||
| 423 | ;; when restoring, and customized uses of `frameset-filter-params' | ||
| 424 | ;; are likely to use their own filter alist and just call | ||
| 425 | ;; | ||
| 426 | ;; (setq my-filtered (frameset-filter-params my-params my-filters t)) | ||
| 427 | ;; | ||
| 428 | ;; In case you want to use it with the standard filters, you can | ||
| 429 | ;; wrap the call to `frameset-filter-params' in a let form to bind | ||
| 430 | ;; `frameset--target-display' to nil or the desired value. | ||
| 431 | ;; | ||
| 432 | |||
| 433 | ;;;###autoload | ||
| 434 | (defvar frameset-session-filter-alist | ||
| 435 | '((name . :never) | ||
| 436 | (left . frameset-filter-iconified) | ||
| 437 | (minibuffer . frameset-filter-minibuffer) | ||
| 438 | (top . frameset-filter-iconified)) | ||
| 439 | "Minimum set of parameters to filter for live (on-session) framesets. | ||
| 440 | DO NOT MODIFY. See `frameset-filter-alist' for a full description.") | ||
| 441 | |||
| 442 | ;;;###autoload | ||
| 443 | (defvar frameset-persistent-filter-alist | ||
| 444 | (nconc | ||
| 445 | '((background-color . frameset-filter-sanitize-color) | ||
| 446 | (buffer-list . :never) | ||
| 447 | (buffer-predicate . :never) | ||
| 448 | (buried-buffer-list . :never) | ||
| 449 | (font . frameset-filter-shelve-param) | ||
| 450 | (foreground-color . frameset-filter-sanitize-color) | ||
| 451 | (fullscreen . frameset-filter-shelve-param) | ||
| 452 | (GUI:font . frameset-filter-unshelve-param) | ||
| 453 | (GUI:fullscreen . frameset-filter-unshelve-param) | ||
| 454 | (GUI:height . frameset-filter-unshelve-param) | ||
| 455 | (GUI:width . frameset-filter-unshelve-param) | ||
| 456 | (height . frameset-filter-shelve-param) | ||
| 457 | (outer-window-id . :never) | ||
| 458 | (parent-id . :never) | ||
| 459 | (tty . frameset-filter-tty-to-GUI) | ||
| 460 | (tty-type . frameset-filter-tty-to-GUI) | ||
| 461 | (width . frameset-filter-shelve-param) | ||
| 462 | (window-id . :never) | ||
| 463 | (window-system . :never)) | ||
| 464 | frameset-session-filter-alist) | ||
| 465 | "Parameters to filter for persistent framesets. | ||
| 466 | DO NOT MODIFY. See `frameset-filter-alist' for a full description.") | ||
| 467 | |||
| 468 | ;;;###autoload | ||
| 469 | (defvar frameset-filter-alist frameset-persistent-filter-alist | ||
| 118 | "Alist of frame parameters and filtering functions. | 470 | "Alist of frame parameters and filtering functions. |
| 119 | 471 | ||
| 120 | Each element is a cons (PARAM . ACTION), where PARAM is a parameter | 472 | This alist is the default value of the FILTERS argument of |
| 121 | name (a symbol identifying a frame parameter), and ACTION can be: | 473 | `frameset-save' and `frameset-restore' (which see). |
| 474 | |||
| 475 | Initially, `frameset-filter-alist' is set to, and shares the value of, | ||
| 476 | `frameset-persistent-filter-alist'. You can override any item in | ||
| 477 | this alist by `push'ing a new item onto it. If, for some reason, you | ||
| 478 | intend to modify existing values, do | ||
| 122 | 479 | ||
| 123 | t The parameter is always removed from the parameter list. | 480 | (setq frameset-filter-alist (copy-tree frameset-filter-alist)) |
| 124 | :save The parameter is removed when saving the frame. | 481 | |
| 125 | :restore The parameter is removed when restoring the frame. | 482 | before changing anything. |
| 483 | |||
| 484 | On saving, PARAMETERS is the parameter alist of each frame processed, | ||
| 485 | and FILTERED is the parameter alist that gets saved to the frameset. | ||
| 486 | |||
| 487 | On restoring, PARAMETERS is the parameter alist extracted from the | ||
| 488 | frameset, and FILTERED is the resulting frame parameter alist used | ||
| 489 | to restore the frame. | ||
| 490 | |||
| 491 | Elements of `frameset-filter-alist' are conses (PARAM . ACTION), | ||
| 492 | where PARAM is a parameter name (a symbol identifying a frame | ||
| 493 | parameter), and ACTION can be: | ||
| 494 | |||
| 495 | nil The parameter is copied to FILTERED. | ||
| 496 | :never The parameter is never copied to FILTERED. | ||
| 497 | :save The parameter is copied only when saving the frame. | ||
| 498 | :restore The parameter is copied only when restoring the frame. | ||
| 126 | FILTER A filter function. | 499 | FILTER A filter function. |
| 127 | 500 | ||
| 128 | FILTER can be a symbol FILTER-FUN, or a list (FILTER-FUN ARGS...). | 501 | FILTER can be a symbol FILTER-FUN, or a list (FILTER-FUN ARGS...). |
| 129 | It will be called with four arguments CURRENT, FILTERED, PARAMETERS | 502 | FILTER-FUN is invoked with |
| 130 | and SAVING, plus any additional ARGS: | 503 | |
| 504 | (apply FILTER-FUN CURRENT FILTERED PARAMETERS SAVING ARGS) | ||
| 505 | |||
| 506 | where | ||
| 131 | 507 | ||
| 132 | CURRENT A cons (PARAM . VALUE), where PARAM is the one being | 508 | CURRENT A cons (PARAM . VALUE), where PARAM is the one being |
| 133 | filtered and VALUE is its current value. | 509 | filtered and VALUE is its current value. |
| 134 | FILTERED The alist of parameters filtered so far. | 510 | FILTERED The resulting alist (so far). |
| 135 | PARAMETERS The complete alist of parameters being filtered, | 511 | PARAMETERS The complete alist of parameters being filtered, |
| 136 | SAVING Non-nil if filtering before saving state, nil otherwise. | 512 | SAVING Non-nil if filtering before saving state, nil if filtering |
| 513 | before restoring it. | ||
| 514 | ARGS Any additional arguments specified in the ACTION. | ||
| 515 | |||
| 516 | FILTER-FUN is allowed to modify items in FILTERED, but no other arguments. | ||
| 517 | It must return: | ||
| 518 | nil Skip CURRENT (do not add it to FILTERED). | ||
| 519 | t Add CURRENT to FILTERED as is. | ||
| 520 | (NEW-PARAM . NEW-VALUE) Add this to FILTERED instead of CURRENT. | ||
| 137 | 521 | ||
| 138 | The FILTER-FUN function must return: | 522 | Frame parameters not on this alist are passed intact, as if they were |
| 139 | nil CURRENT is removed from the list. | 523 | defined with ACTION = nil.") |
| 140 | t CURRENT is left as is. | ||
| 141 | (PARAM' . VALUE') Replace CURRENT with this. | ||
| 142 | 524 | ||
| 143 | Frame parameters not on this list are passed intact.") | ||
| 144 | 525 | ||
| 145 | (defvar frameset--target-display nil | 526 | (defvar frameset--target-display nil |
| 146 | ;; Either (minibuffer . VALUE) or nil. | 527 | ;; Either (minibuffer . VALUE) or nil. |
| 147 | ;; This refers to the current frame config being processed inside | 528 | ;; This refers to the current frame config being processed inside |
| 148 | ;; `frame--restore-frames' and its auxiliary functions (like filtering). | 529 | ;; `frameset-restore' and its auxiliary functions (like filtering). |
| 149 | ;; If nil, there is no need to change the display. | 530 | ;; If nil, there is no need to change the display. |
| 150 | ;; If non-nil, display parameter to use when creating the frame. | 531 | ;; If non-nil, display parameter to use when creating the frame. |
| 151 | "Internal use only.") | 532 | "Internal use only.") |
| 152 | 533 | ||
| 153 | (defun frameset-switch-to-gui-p (parameters) | 534 | (defun frameset-switch-to-gui-p (parameters) |
| 154 | "True when switching to a graphic display. | 535 | "True when switching to a graphic display. |
| 155 | Return t if PARAMETERS describes a text-only terminal and | 536 | Return non-nil if the parameter alist PARAMETERS describes a frame on a |
| 156 | the target is a graphic display; otherwise return nil. | 537 | text-only terminal, and the frame is being restored on a graphic display; |
| 157 | Only meaningful when called from a filtering function in | 538 | otherwise return nil. Only meaningful when called from a filtering |
| 158 | `frameset-filter-alist'." | 539 | function in `frameset-filter-alist'." |
| 159 | (and frameset--target-display ; we're switching | 540 | (and frameset--target-display ; we're switching |
| 160 | (null (cdr (assq 'display parameters))) ; from a tty | 541 | (null (cdr (assq 'display parameters))) ; from a tty |
| 161 | (cdr frameset--target-display))) ; to a GUI display | 542 | (cdr frameset--target-display))) ; to a GUI display |
| 162 | 543 | ||
| 163 | (defun frameset-switch-to-tty-p (parameters) | 544 | (defun frameset-switch-to-tty-p (parameters) |
| 164 | "True when switching to a text-only terminal. | 545 | "True when switching to a text-only terminal. |
| 165 | Return t if PARAMETERS describes a graphic display and | 546 | Return non-nil if the parameter alist PARAMETERS describes a frame on a |
| 166 | the target is a text-only terminal; otherwise return nil. | 547 | graphic display, and the frame is being restored on a text-only terminal; |
| 167 | Only meaningful when called from a filtering function in | 548 | otherwise return nil. Only meaningful when called from a filtering |
| 168 | `frameset-filter-alist'." | 549 | function in `frameset-filter-alist'." |
| 169 | (and frameset--target-display ; we're switching | 550 | (and frameset--target-display ; we're switching |
| 170 | (cdr (assq 'display parameters)) ; from a GUI display | 551 | (cdr (assq 'display parameters)) ; from a GUI display |
| 171 | (null (cdr frameset--target-display)))) ; to a tty | 552 | (null (cdr frameset--target-display)))) ; to a tty |
| 553 | |||
| 554 | (defun frameset-filter-tty-to-GUI (_current _filtered parameters saving) | ||
| 555 | "Remove CURRENT when switching from tty to a graphic display. | ||
| 556 | |||
| 557 | For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING, | ||
| 558 | see `frameset-filter-alist'." | ||
| 559 | (or saving | ||
| 560 | (not (frameset-switch-to-gui-p parameters)))) | ||
| 172 | 561 | ||
| 173 | (defun frameset-filter-sanitize-color (current _filtered parameters saving) | 562 | (defun frameset-filter-sanitize-color (current _filtered parameters saving) |
| 174 | "When switching to a GUI frame, remove \"unspecified\" colors. | 563 | "When switching to a GUI frame, remove \"unspecified\" colors. |
| 175 | Useful as a filter function for tty-specific parameters." | 564 | Useful as a filter function for tty-specific parameters. |
| 565 | |||
| 566 | For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING, | ||
| 567 | see `frameset-filter-alist'." | ||
| 176 | (or saving | 568 | (or saving |
| 177 | (not (frameset-switch-to-gui-p parameters)) | 569 | (not (frameset-switch-to-gui-p parameters)) |
| 178 | (not (stringp (cdr current))) | 570 | (not (stringp (cdr current))) |
| 179 | (not (string-match-p "^unspecified-[fb]g$" (cdr current))))) | 571 | (not (string-match-p "^unspecified-[fb]g$" (cdr current))))) |
| 180 | 572 | ||
| 181 | (defun frameset-filter-minibuffer (current _filtered _parameters saving) | 573 | (defun frameset-filter-minibuffer (current filtered _parameters saving) |
| 182 | "When saving, convert (minibuffer . #<window>) parameter to (minibuffer . t)." | 574 | "Force the minibuffer parameter to have a sensible value. |
| 183 | (or (not saving) | 575 | |
| 184 | (if (windowp (cdr current)) | 576 | When saving, convert (minibuffer . #<window>) to (minibuffer . t). |
| 185 | '(minibuffer . t) | 577 | When restoring, if there are two copies, keep the one pointing to |
| 186 | t))) | 578 | a live window. |
| 579 | |||
| 580 | For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING, | ||
| 581 | see `frameset-filter-alist'." | ||
| 582 | (let ((value (cdr current)) mini) | ||
| 583 | (cond (saving | ||
| 584 | (if (windowp value) '(minibuffer . t) t)) | ||
| 585 | ((setq mini (assq 'minibuffer filtered)) | ||
| 586 | (when (windowp value) (setcdr mini value)) | ||
| 587 | nil) | ||
| 588 | (t t)))) | ||
| 187 | 589 | ||
| 188 | (defun frameset-filter-save-parm (current _filtered parameters saving | 590 | (defun frameset-filter-shelve-param (current _filtered parameters saving |
| 189 | &optional prefix) | 591 | &optional prefix) |
| 190 | "When switching to a tty frame, save parameter P as PREFIX:P. | 592 | "When switching to a tty frame, save parameter P as PREFIX:P. |
| 191 | The parameter can be later restored with `frameset-filter-restore-parm'. | 593 | The parameter can be later restored with `frameset-filter-unshelve-param'. |
| 192 | PREFIX defaults to `GUI'." | 594 | PREFIX defaults to `GUI'. |
| 595 | |||
| 596 | For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING, | ||
| 597 | see `frameset-filter-alist'." | ||
| 193 | (unless prefix (setq prefix 'GUI)) | 598 | (unless prefix (setq prefix 'GUI)) |
| 194 | (cond (saving t) | 599 | (cond (saving t) |
| 195 | ((frameset-switch-to-tty-p parameters) | 600 | ((frameset-switch-to-tty-p parameters) |
| @@ -201,9 +606,12 @@ PREFIX defaults to `GUI'." | |||
| 201 | (not (assq (intern (format "%s:%s" prefix (car current))) parameters))) | 606 | (not (assq (intern (format "%s:%s" prefix (car current))) parameters))) |
| 202 | (t t))) | 607 | (t t))) |
| 203 | 608 | ||
| 204 | (defun frameset-filter-restore-parm (current filtered parameters saving) | 609 | (defun frameset-filter-unshelve-param (current filtered parameters saving) |
| 205 | "When switching to a GUI frame, restore PREFIX:P parameter as P. | 610 | "When switching to a GUI frame, restore PREFIX:P parameter as P. |
| 206 | CURRENT must be of the form (PREFIX:P . value)." | 611 | CURRENT must be of the form (PREFIX:P . value). |
| 612 | |||
| 613 | For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING, | ||
| 614 | see `frameset-filter-alist'." | ||
| 207 | (or saving | 615 | (or saving |
| 208 | (not (frameset-switch-to-gui-p parameters)) | 616 | (not (frameset-switch-to-gui-p parameters)) |
| 209 | (let* ((prefix:p (symbol-name (car current))) | 617 | (let* ((prefix:p (symbol-name (car current))) |
| @@ -218,38 +626,40 @@ CURRENT must be of the form (PREFIX:P . value)." | |||
| 218 | 626 | ||
| 219 | (defun frameset-filter-iconified (_current _filtered parameters saving) | 627 | (defun frameset-filter-iconified (_current _filtered parameters saving) |
| 220 | "Remove CURRENT when saving an iconified frame. | 628 | "Remove CURRENT when saving an iconified frame. |
| 221 | This is used for positions parameters `left' and `top', which are | 629 | This is used for positional parameters `left' and `top', which are |
| 222 | meaningless in an iconified frame, so the frame is restored in a | 630 | meaningless in an iconified frame, so the frame is restored in a |
| 223 | default position." | 631 | default position. |
| 224 | (not (and saving (eq (cdr (assq 'visibility parameters)) 'icon)))) | ||
| 225 | 632 | ||
| 226 | (defun frameset-keep-original-display-p (force-display) | 633 | For the meaning of CURRENT, FILTERED, PARAMETERS and SAVING, |
| 227 | "True if saved frames' displays should be honored." | 634 | see `frameset-filter-alist'." |
| 228 | (cond ((daemonp) t) | 635 | (not (and saving (eq (cdr (assq 'visibility parameters)) 'icon)))) |
| 229 | ((eq system-type 'windows-nt) nil) | ||
| 230 | (t (null force-display)))) | ||
| 231 | 636 | ||
| 232 | (defun frameset-filter-params (parameters filter-alist saving) | 637 | (defun frameset-filter-params (parameters filter-alist saving) |
| 233 | "Filter parameter list PARAMETERS and return a filtered list. | 638 | "Filter parameter alist PARAMETERS and return a filtered alist. |
| 234 | FILTER-ALIST is an alist of parameter filters, in the format of | 639 | FILTER-ALIST is an alist of parameter filters, in the format of |
| 235 | `frameset-filter-alist' (which see). | 640 | `frameset-filter-alist' (which see). |
| 236 | SAVING is non-nil while filtering parameters to save a frameset, | 641 | SAVING is non-nil while filtering parameters to save a frameset, |
| 237 | nil while the filtering is done to restore it." | 642 | nil while the filtering is done to restore it." |
| 238 | (let ((filtered nil)) | 643 | (let ((filtered nil)) |
| 239 | (dolist (current parameters) | 644 | (dolist (current parameters) |
| 645 | ;; When saving, the parameter alist is temporary, so modifying it | ||
| 646 | ;; is not a problem. When restoring, the parameter alist is part | ||
| 647 | ;; of a frameset, so we must copy parameters to avoid inadvertent | ||
| 648 | ;; modifications. | ||
| 240 | (pcase (cdr (assq (car current) filter-alist)) | 649 | (pcase (cdr (assq (car current) filter-alist)) |
| 241 | (`nil | 650 | (`nil |
| 242 | (push current filtered)) | 651 | (push (if saving current (copy-tree current)) filtered)) |
| 243 | (`t | 652 | (:never |
| 244 | nil) | 653 | nil) |
| 245 | (:save | ||
| 246 | (unless saving (push current filtered))) | ||
| 247 | (:restore | 654 | (:restore |
| 655 | (unless saving (push (copy-tree current) filtered))) | ||
| 656 | (:save | ||
| 248 | (when saving (push current filtered))) | 657 | (when saving (push current filtered))) |
| 249 | ((or `(,fun . ,args) (and fun (pred fboundp))) | 658 | ((or `(,fun . ,args) (and fun (pred fboundp))) |
| 250 | (let ((this (apply fun current filtered parameters saving args))) | 659 | (let* ((this (apply fun current filtered parameters saving args)) |
| 251 | (when this | 660 | (val (if (eq this t) current this))) |
| 252 | (push (if (eq this t) current this) filtered)))) | 661 | (when val |
| 662 | (push (if saving val (copy-tree val)) filtered)))) | ||
| 253 | (other | 663 | (other |
| 254 | (delay-warning 'frameset (format "Unknown filter %S" other) :error)))) | 664 | (delay-warning 'frameset (format "Unknown filter %S" other) :error)))) |
| 255 | ;; Set the display parameter after filtering, so that filter functions | 665 | ;; Set the display parameter after filtering, so that filter functions |
| @@ -262,21 +672,48 @@ nil while the filtering is done to restore it." | |||
| 262 | filtered)) | 672 | filtered)) |
| 263 | 673 | ||
| 264 | 674 | ||
| 265 | ;; Saving framesets | 675 | ;; Frame ids |
| 266 | 676 | ||
| 267 | (defun frameset--set-id (frame) | 677 | (defun frameset--set-id (frame) |
| 268 | "Set FRAME's `frameset-id' if not yet set. | 678 | "Set FRAME's id if not yet set. |
| 269 | Internal use only." | 679 | Internal use only." |
| 270 | (unless (frame-parameter frame 'frameset-id) | 680 | (unless (frame-parameter frame 'frameset--id) |
| 271 | (set-frame-parameter frame | 681 | (set-frame-parameter frame |
| 272 | 'frameset-id | 682 | 'frameset--id |
| 273 | (mapconcat (lambda (n) (format "%04X" n)) | 683 | (mapconcat (lambda (n) (format "%04X" n)) |
| 274 | (cl-loop repeat 4 collect (random 65536)) | 684 | (cl-loop repeat 4 collect (random 65536)) |
| 275 | "-")))) | 685 | "-")))) |
| 686 | ;;;###autoload | ||
| 687 | (defun frameset-frame-id (frame) | ||
| 688 | "Return the frame id of FRAME, if it has one; else, return nil. | ||
| 689 | A frame id is a string that uniquely identifies a frame. | ||
| 690 | It is persistent across `frameset-save' / `frameset-restore' | ||
| 691 | invocations, and once assigned is never changed unless the same | ||
| 692 | frame is duplicated (via `frameset-restore'), in which case the | ||
| 693 | newest frame keeps the id and the old frame's is set to nil." | ||
| 694 | (frame-parameter frame 'frameset--id)) | ||
| 695 | |||
| 696 | ;;;###autoload | ||
| 697 | (defun frameset-frame-id-equal-p (frame id) | ||
| 698 | "Return non-nil if FRAME's id matches ID." | ||
| 699 | (string= (frameset-frame-id frame) id)) | ||
| 700 | |||
| 701 | ;;;###autoload | ||
| 702 | (defun frameset-frame-with-id (id &optional frame-list) | ||
| 703 | "Return the live frame with id ID, if exists; else nil. | ||
| 704 | If FRAME-LIST is a list of frames, check these frames only. | ||
| 705 | If nil, check all live frames." | ||
| 706 | (cl-find-if (lambda (f) | ||
| 707 | (and (frame-live-p f) | ||
| 708 | (frameset-frame-id-equal-p f id))) | ||
| 709 | (or frame-list (frame-list)))) | ||
| 710 | |||
| 711 | |||
| 712 | ;; Saving framesets | ||
| 276 | 713 | ||
| 277 | (defun frameset--process-minibuffer-frames (frame-list) | 714 | (defun frameset--record-minibuffer-relationships (frame-list) |
| 278 | "Process FRAME-LIST and record minibuffer relationships. | 715 | "Process FRAME-LIST and record minibuffer relationships. |
| 279 | FRAME-LIST is a list of frames." | 716 | FRAME-LIST is a list of frames. Internal use only." |
| 280 | ;; Record frames with their own minibuffer | 717 | ;; Record frames with their own minibuffer |
| 281 | (dolist (frame (minibuffer-frame-list)) | 718 | (dolist (frame (minibuffer-frame-list)) |
| 282 | (when (memq frame frame-list) | 719 | (when (memq frame frame-list) |
| @@ -291,73 +728,106 @@ FRAME-LIST is a list of frames." | |||
| 291 | (dolist (frame frame-list) | 728 | (dolist (frame frame-list) |
| 292 | (unless (frame-parameter frame 'frameset--mini) | 729 | (unless (frame-parameter frame 'frameset--mini) |
| 293 | (frameset--set-id frame) | 730 | (frameset--set-id frame) |
| 294 | (let* ((mb-frame (window-frame (minibuffer-window frame))) | 731 | (let ((mb-frame (window-frame (minibuffer-window frame)))) |
| 295 | (id (and mb-frame (frame-parameter mb-frame 'frameset-id)))) | 732 | ;; For minibufferless frames, frameset--mini is a cons |
| 296 | (if (null id) | 733 | ;; (nil . FRAME-ID), where FRAME-ID is the frameset--id of |
| 297 | (error "Minibuffer frame %S for %S is excluded" mb-frame frame) | 734 | ;; the frame containing its minibuffer window. |
| 298 | ;; For minibufferless frames, frameset--mini is a cons | 735 | ;; FRAME-ID can be set to nil, if FRAME-LIST doesn't contain |
| 299 | ;; (nil . FRAME-ID), where FRAME-ID is the frameset-id of | 736 | ;; the minibuffer frame of a minibufferless frame; we allow |
| 300 | ;; the frame containing its minibuffer window. | 737 | ;; it without trying to second-guess the user. |
| 301 | (set-frame-parameter frame | 738 | (set-frame-parameter frame |
| 302 | 'frameset--mini | 739 | 'frameset--mini |
| 303 | (cons nil id))))))) | 740 | (cons nil |
| 741 | (and mb-frame | ||
| 742 | (frameset-frame-id mb-frame)))))))) | ||
| 304 | 743 | ||
| 305 | ;;;###autoload | 744 | ;;;###autoload |
| 306 | (cl-defun frameset-save (frame-list &key filters predicate properties) | 745 | (cl-defun frameset-save (frame-list |
| 307 | "Return the frameset of FRAME-LIST, a list of frames. | 746 | &key app name description |
| 308 | If nil, FRAME-LIST defaults to all live frames. | 747 | filters predicate properties) |
| 309 | FILTERS is an alist of parameter filters; defaults to `frameset-filter-alist'. | 748 | "Return a frameset for FRAME-LIST, a list of frames. |
| 749 | Dead frames and non-frame objects are silently removed from the list. | ||
| 750 | If nil, FRAME-LIST defaults to the output of `frame-list' (all live frames). | ||
| 751 | APP, NAME and DESCRIPTION are optional data; see the docstring of the | ||
| 752 | `frameset' defstruct for details. | ||
| 753 | FILTERS is an alist of parameter filters; if nil, the value of the variable | ||
| 754 | `frameset-filter-alist' is used instead. | ||
| 310 | PREDICATE is a predicate function, which must return non-nil for frames that | 755 | PREDICATE is a predicate function, which must return non-nil for frames that |
| 311 | should be saved; it defaults to saving all frames from FRAME-LIST. | 756 | should be saved; if PREDICATE is nil, all frames from FRAME-LIST are saved. |
| 312 | PROPERTIES is a user-defined property list to add to the frameset." | 757 | PROPERTIES is a user-defined property list to add to the frameset." |
| 313 | (let ((frames (cl-delete-if-not #'frame-live-p | 758 | (let* ((list (or (copy-sequence frame-list) (frame-list))) |
| 314 | (cl-delete-if-not (or predicate #'framep) | 759 | (frames (cl-delete-if-not #'frame-live-p |
| 315 | (or (copy-sequence frame-list) | 760 | (if predicate |
| 316 | (frame-list)))))) | 761 | (cl-delete-if-not predicate list) |
| 317 | (frameset--process-minibuffer-frames frames) | 762 | list))) |
| 318 | (make-frameset :properties (append '(:version 1) properties) | 763 | fs) |
| 319 | :states (mapcar | 764 | (frameset--record-minibuffer-relationships frames) |
| 320 | (lambda (frame) | 765 | (setq fs (frameset--make |
| 321 | (cons | 766 | :app app |
| 322 | (frameset-filter-params (frame-parameters frame) | 767 | :name name |
| 323 | (or filters | 768 | :description description |
| 324 | frameset-filter-alist) | 769 | :properties properties |
| 325 | t) | 770 | :states (mapcar |
| 326 | (window-state-get (frame-root-window frame) t))) | 771 | (lambda (frame) |
| 327 | frames)))) | 772 | (cons |
| 773 | (frameset-filter-params (frame-parameters frame) | ||
| 774 | (or filters | ||
| 775 | frameset-filter-alist) | ||
| 776 | t) | ||
| 777 | (window-state-get (frame-root-window frame) t))) | ||
| 778 | frames))) | ||
| 779 | (cl-assert (frameset-valid-p fs)) | ||
| 780 | fs)) | ||
| 328 | 781 | ||
| 329 | 782 | ||
| 330 | ;; Restoring framesets | 783 | ;; Restoring framesets |
| 331 | 784 | ||
| 332 | (defvar frameset--reuse-list nil | 785 | (defvar frameset--reuse-list nil |
| 333 | "Internal use only.") | 786 | "The list of frames potentially reusable. |
| 334 | 787 | Its value is only meaningful during execution of `frameset-restore'. | |
| 335 | (defun frameset--compute-pos (value left/top right/bottom) | 788 | Internal use only.") |
| 789 | |||
| 790 | (defun frameset-compute-pos (value left/top right/bottom) | ||
| 791 | "Return an absolute positioning value for a frame. | ||
| 792 | VALUE is the value of a positional frame parameter (`left' or `top'). | ||
| 793 | If VALUE is relative to the screen edges (like (+ -35) or (-200), it is | ||
| 794 | converted to absolute by adding it to the corresponding edge; if it is | ||
| 795 | an absolute position, it is returned unmodified. | ||
| 796 | LEFT/TOP and RIGHT/BOTTOM indicate the dimensions of the screen in | ||
| 797 | pixels along the relevant direction: either the position of the left | ||
| 798 | and right edges for a `left' positional parameter, or the position of | ||
| 799 | the top and bottom edges for a `top' parameter." | ||
| 336 | (pcase value | 800 | (pcase value |
| 337 | (`(+ ,val) (+ left/top val)) | 801 | (`(+ ,val) (+ left/top val)) |
| 338 | (`(- ,val) (+ right/bottom val)) | 802 | (`(- ,val) (+ right/bottom val)) |
| 339 | (val val))) | 803 | (val val))) |
| 340 | 804 | ||
| 341 | (defun frameset--move-onscreen (frame force-onscreen) | 805 | (defun frameset-move-onscreen (frame force-onscreen) |
| 342 | "If FRAME is offscreen, move it back onscreen and, if necessary, resize it. | 806 | "If FRAME is offscreen, move it back onscreen and, if necessary, resize it. |
| 343 | For the description of FORCE-ONSCREEN, see `frameset-restore'. | 807 | For the description of FORCE-ONSCREEN, see `frameset-restore'. |
| 344 | When forced onscreen, frames wider than the monitor's workarea are converted | 808 | When forced onscreen, frames wider than the monitor's workarea are converted |
| 345 | to fullwidth, and frames taller than the workarea are converted to fullheight. | 809 | to fullwidth, and frames taller than the workarea are converted to fullheight. |
| 346 | NOTE: This only works for non-iconified frames. Internal use only." | 810 | NOTE: This only works for non-iconified frames." |
| 347 | (pcase-let* ((`(,left ,top ,width ,height) (cl-cdadr (frame-monitor-attributes frame))) | 811 | (pcase-let* ((`(,left ,top ,width ,height) (cl-cdadr (frame-monitor-attributes frame))) |
| 348 | (right (+ left width -1)) | 812 | (right (+ left width -1)) |
| 349 | (bottom (+ top height -1)) | 813 | (bottom (+ top height -1)) |
| 350 | (fr-left (frameset--compute-pos (frame-parameter frame 'left) left right)) | 814 | (fr-left (frameset-compute-pos (frame-parameter frame 'left) left right)) |
| 351 | (fr-top (frameset--compute-pos (frame-parameter frame 'top) top bottom)) | 815 | (fr-top (frameset-compute-pos (frame-parameter frame 'top) top bottom)) |
| 352 | (ch-width (frame-char-width frame)) | 816 | (ch-width (frame-char-width frame)) |
| 353 | (ch-height (frame-char-height frame)) | 817 | (ch-height (frame-char-height frame)) |
| 354 | (fr-width (max (frame-pixel-width frame) (* ch-width (frame-width frame)))) | 818 | (fr-width (max (frame-pixel-width frame) (* ch-width (frame-width frame)))) |
| 355 | (fr-height (max (frame-pixel-height frame) (* ch-height (frame-height frame)))) | 819 | (fr-height (max (frame-pixel-height frame) (* ch-height (frame-height frame)))) |
| 356 | (fr-right (+ fr-left fr-width -1)) | 820 | (fr-right (+ fr-left fr-width -1)) |
| 357 | (fr-bottom (+ fr-top fr-height -1))) | 821 | (fr-bottom (+ fr-top fr-height -1))) |
| 358 | (when (pcase force-onscreen | 822 | (when (pcase force-onscreen |
| 823 | ;; A predicate. | ||
| 824 | ((pred functionp) | ||
| 825 | (funcall force-onscreen | ||
| 826 | frame | ||
| 827 | (list fr-left fr-top fr-width fr-height) | ||
| 828 | (list left top width height))) | ||
| 359 | ;; Any corner is outside the screen. | 829 | ;; Any corner is outside the screen. |
| 360 | (`all (or (< fr-bottom top) (> fr-bottom bottom) | 830 | (:all (or (< fr-bottom top) (> fr-bottom bottom) |
| 361 | (< fr-left left) (> fr-left right) | 831 | (< fr-left left) (> fr-left right) |
| 362 | (< fr-right left) (> fr-right right) | 832 | (< fr-right left) (> fr-right right) |
| 363 | (< fr-top top) (> fr-top bottom))) | 833 | (< fr-top top) (> fr-top bottom))) |
| @@ -396,7 +866,7 @@ NOTE: This only works for non-iconified frames. Internal use only." | |||
| 396 | (when params | 866 | (when params |
| 397 | (modify-frame-parameters frame params)))))) | 867 | (modify-frame-parameters frame params)))))) |
| 398 | 868 | ||
| 399 | (defun frameset--find-frame (predicate display &rest args) | 869 | (defun frameset--find-frame-if (predicate display &rest args) |
| 400 | "Find a frame in `frameset--reuse-list' satisfying PREDICATE. | 870 | "Find a frame in `frameset--reuse-list' satisfying PREDICATE. |
| 401 | Look through available frames whose display property matches DISPLAY | 871 | Look through available frames whose display property matches DISPLAY |
| 402 | and return the first one for which (PREDICATE frame ARGS) returns t. | 872 | and return the first one for which (PREDICATE frame ARGS) returns t. |
| @@ -407,10 +877,10 @@ If PREDICATE is nil, it is always satisfied. Internal use only." | |||
| 407 | (apply predicate frame args)))) | 877 | (apply predicate frame args)))) |
| 408 | frameset--reuse-list)) | 878 | frameset--reuse-list)) |
| 409 | 879 | ||
| 410 | (defun frameset--reuse-frame (display frame-cfg) | 880 | (defun frameset--reuse-frame (display parameters) |
| 411 | "Look for an existing frame to reuse. | 881 | "Return an existing frame to reuse, or nil if none found. |
| 412 | DISPLAY is the display where the frame will be shown, and FRAME-CFG | 882 | DISPLAY is the display where the frame will be shown, and PARAMETERS |
| 413 | is the parameter list of the frame being restored. Internal use only." | 883 | is the parameter alist of the frame being restored. Internal use only." |
| 414 | (let ((frame nil) | 884 | (let ((frame nil) |
| 415 | mini) | 885 | mini) |
| 416 | ;; There are no fancy heuristics there. We could implement some | 886 | ;; There are no fancy heuristics there. We could implement some |
| @@ -423,19 +893,19 @@ is the parameter list of the frame being restored. Internal use only." | |||
| 423 | ;; will usually have only one frame, and should already work. | 893 | ;; will usually have only one frame, and should already work. |
| 424 | (cond ((null display) | 894 | (cond ((null display) |
| 425 | ;; When the target is tty, every existing frame is reusable. | 895 | ;; When the target is tty, every existing frame is reusable. |
| 426 | (setq frame (frameset--find-frame nil display))) | 896 | (setq frame (frameset--find-frame-if nil display))) |
| 427 | ((car (setq mini (cdr (assq 'frameset--mini frame-cfg)))) | 897 | ((car (setq mini (cdr (assq 'frameset--mini parameters)))) |
| 428 | ;; If the frame has its own minibuffer, let's see whether | 898 | ;; If the frame has its own minibuffer, let's see whether |
| 429 | ;; that frame has already been loaded (which can happen after | 899 | ;; that frame has already been loaded (which can happen after |
| 430 | ;; M-x desktop-read). | 900 | ;; M-x desktop-read). |
| 431 | (setq frame (frameset--find-frame | 901 | (setq frame (frameset--find-frame-if |
| 432 | (lambda (f id) | 902 | (lambda (f id) |
| 433 | (string= (frame-parameter f 'frameset-id) id)) | 903 | (frameset-frame-id-equal-p f id)) |
| 434 | display (cdr mini))) | 904 | display (cdr (assq 'frameset--id parameters)))) |
| 435 | ;; If it has not been loaded, and it is not a minibuffer-only frame, | 905 | ;; If it has not been loaded, and it is not a minibuffer-only frame, |
| 436 | ;; let's look for an existing non-minibuffer-only frame to reuse. | 906 | ;; let's look for an existing non-minibuffer-only frame to reuse. |
| 437 | (unless (or frame (eq (cdr (assq 'minibuffer frame-cfg)) 'only)) | 907 | (unless (or frame (eq (cdr (assq 'minibuffer parameters)) 'only)) |
| 438 | (setq frame (frameset--find-frame | 908 | (setq frame (frameset--find-frame-if |
| 439 | (lambda (f) | 909 | (lambda (f) |
| 440 | (let ((w (frame-parameter f 'minibuffer))) | 910 | (let ((w (frame-parameter f 'minibuffer))) |
| 441 | (and (window-live-p w) | 911 | (and (window-live-p w) |
| @@ -445,29 +915,40 @@ is the parameter list of the frame being restored. Internal use only." | |||
| 445 | (mini | 915 | (mini |
| 446 | ;; For minibufferless frames, check whether they already exist, | 916 | ;; For minibufferless frames, check whether they already exist, |
| 447 | ;; and that they are linked to the right minibuffer frame. | 917 | ;; and that they are linked to the right minibuffer frame. |
| 448 | (setq frame (frameset--find-frame | 918 | (setq frame (frameset--find-frame-if |
| 449 | (lambda (f id mini-id) | 919 | (lambda (f id mini-id) |
| 450 | (and (string= (frame-parameter f 'frameset-id) id) | 920 | (and (frameset-frame-id-equal-p f id) |
| 451 | (string= (frame-parameter (window-frame (minibuffer-window f)) | 921 | (or (null mini-id) ; minibuffer frame not saved |
| 452 | 'frameset-id) | 922 | (frameset-frame-id-equal-p |
| 453 | mini-id))) | 923 | (window-frame (minibuffer-window f)) |
| 454 | display (cdr (assq 'frameset-id frame-cfg)) (cdr mini)))) | 924 | mini-id)))) |
| 925 | display | ||
| 926 | (cdr (assq 'frameset--id parameters)) (cdr mini)))) | ||
| 455 | (t | 927 | (t |
| 456 | ;; Default to just finding a frame in the same display. | 928 | ;; Default to just finding a frame in the same display. |
| 457 | (setq frame (frameset--find-frame nil display)))) | 929 | (setq frame (frameset--find-frame-if nil display)))) |
| 458 | ;; If found, remove from the list. | 930 | ;; If found, remove from the list. |
| 459 | (when frame | 931 | (when frame |
| 460 | (setq frameset--reuse-list (delq frame frameset--reuse-list))) | 932 | (setq frameset--reuse-list (delq frame frameset--reuse-list))) |
| 461 | frame)) | 933 | frame)) |
| 462 | 934 | ||
| 463 | (defun frameset--get-frame (frame-cfg window-cfg filters force-onscreen) | 935 | (defun frameset--initial-params (parameters) |
| 936 | "Return a list of PARAMETERS that must be set when creating the frame. | ||
| 937 | Setting position and size parameters as soon as possible helps reducing | ||
| 938 | flickering; other parameters, like `minibuffer' and `border-width', can | ||
| 939 | not be changed once the frame has been created. Internal use only." | ||
| 940 | (cl-loop for param in '(left top with height border-width minibuffer) | ||
| 941 | collect (assq param parameters))) | ||
| 942 | |||
| 943 | (defun frameset--restore-frame (parameters window-state filters force-onscreen) | ||
| 464 | "Set up and return a frame according to its saved state. | 944 | "Set up and return a frame according to its saved state. |
| 465 | That means either reusing an existing frame or creating one anew. | 945 | That means either reusing an existing frame or creating one anew. |
| 466 | FRAME-CFG is the frame's parameter list; WINDOW-CFG is its window state. | 946 | PARAMETERS is the frame's parameter alist; WINDOW-STATE is its window state. |
| 467 | For the meaning of FORCE-ONSCREEN, see `frameset-restore'." | 947 | For the meaning of FILTERS and FORCE-ONSCREEN, see `frameset-restore'. |
| 468 | (let* ((fullscreen (cdr (assq 'fullscreen frame-cfg))) | 948 | Internal use only." |
| 469 | (lines (assq 'tool-bar-lines frame-cfg)) | 949 | (let* ((fullscreen (cdr (assq 'fullscreen parameters))) |
| 470 | (filtered-cfg (frameset-filter-params frame-cfg filters nil)) | 950 | (lines (assq 'tool-bar-lines parameters)) |
| 951 | (filtered-cfg (frameset-filter-params parameters filters nil)) | ||
| 471 | (display (cdr (assq 'display filtered-cfg))) ;; post-filtering | 952 | (display (cdr (assq 'display filtered-cfg))) ;; post-filtering |
| 472 | alt-cfg frame) | 953 | alt-cfg frame) |
| 473 | 954 | ||
| @@ -502,14 +983,12 @@ For the meaning of FORCE-ONSCREEN, see `frameset-restore'." | |||
| 502 | ;; If a frame needs to be created and it falls partially or fully offscreen, | 983 | ;; If a frame needs to be created and it falls partially or fully offscreen, |
| 503 | ;; sometimes it gets "pushed back" onscreen; however, moving it afterwards is | 984 | ;; sometimes it gets "pushed back" onscreen; however, moving it afterwards is |
| 504 | ;; allowed. So we create the frame as invisible and then reapply the full | 985 | ;; allowed. So we create the frame as invisible and then reapply the full |
| 505 | ;; parameter list (including position and size parameters). | 986 | ;; parameter alist (including position and size parameters). |
| 506 | (setq frame (or (and frameset--reuse-list | 987 | (setq frame (or (and frameset--reuse-list |
| 507 | (frameset--reuse-frame display filtered-cfg)) | 988 | (frameset--reuse-frame display filtered-cfg)) |
| 508 | (make-frame-on-display display | 989 | (make-frame-on-display display |
| 509 | (cons '(visibility) | 990 | (cons '(visibility) |
| 510 | (cl-loop | 991 | (frameset--initial-params filtered-cfg))))) |
| 511 | for param in '(left top width height minibuffer) | ||
| 512 | collect (assq param filtered-cfg)))))) | ||
| 513 | (modify-frame-parameters frame | 992 | (modify-frame-parameters frame |
| 514 | (if (eq (frame-parameter frame 'fullscreen) fullscreen) | 993 | (if (eq (frame-parameter frame 'fullscreen) fullscreen) |
| 515 | ;; Workaround for bug#14949 | 994 | ;; Workaround for bug#14949 |
| @@ -521,76 +1000,102 @@ For the meaning of FORCE-ONSCREEN, see `frameset-restore'." | |||
| 521 | ;; FIXME: iconified frames should be checked too, | 1000 | ;; FIXME: iconified frames should be checked too, |
| 522 | ;; but it is impossible without deiconifying them. | 1001 | ;; but it is impossible without deiconifying them. |
| 523 | (not (eq (frame-parameter frame 'visibility) 'icon))) | 1002 | (not (eq (frame-parameter frame 'visibility) 'icon))) |
| 524 | (frameset--move-onscreen frame force-onscreen)) | 1003 | (frameset-move-onscreen frame force-onscreen)) |
| 525 | 1004 | ||
| 526 | ;; Let's give the finishing touches (visibility, tool-bar, maximization). | 1005 | ;; Let's give the finishing touches (visibility, tool-bar, maximization). |
| 527 | (when lines (push lines alt-cfg)) | 1006 | (when lines (push lines alt-cfg)) |
| 528 | (when alt-cfg (modify-frame-parameters frame alt-cfg)) | 1007 | (when alt-cfg (modify-frame-parameters frame alt-cfg)) |
| 529 | ;; Now restore window state. | 1008 | ;; Now restore window state. |
| 530 | (window-state-put window-cfg (frame-root-window frame) 'safe) | 1009 | (window-state-put window-state (frame-root-window frame) 'safe) |
| 531 | frame)) | 1010 | frame)) |
| 532 | 1011 | ||
| 533 | (defun frameset--sort-states (state1 state2) | 1012 | (defun frameset--minibufferless-last-p (state1 state2) |
| 534 | "Predicate to sort frame states in a suitable order to be created. | 1013 | "Predicate to sort frame states in an order suitable for creating frames. |
| 535 | It sorts minibuffer-owning frames before minibufferless ones." | 1014 | It sorts minibuffer-owning frames before minibufferless ones. |
| 1015 | Internal use only." | ||
| 536 | (pcase-let ((`(,hasmini1 ,id-def1) (assq 'frameset--mini (car state1))) | 1016 | (pcase-let ((`(,hasmini1 ,id-def1) (assq 'frameset--mini (car state1))) |
| 537 | (`(,hasmini2 ,id-def2) (assq 'frameset--mini (car state2)))) | 1017 | (`(,hasmini2 ,id-def2) (assq 'frameset--mini (car state2)))) |
| 538 | (cond ((eq id-def1 t) t) | 1018 | (cond ((eq id-def1 t) t) |
| 539 | ((eq id-def2 t) nil) | 1019 | ((eq id-def2 t) nil) |
| 540 | ((not (eq hasmini1 hasmini2)) (eq hasmini1 t)) | 1020 | ((not (eq hasmini1 hasmini2)) (eq hasmini1 t)) |
| 541 | ((eq hasmini1 nil) (string< id-def1 id-def2)) | 1021 | ((eq hasmini1 nil) (or id-def1 id-def2)) |
| 542 | (t t)))) | 1022 | (t t)))) |
| 543 | 1023 | ||
| 544 | (defun frameset-sort-frames-for-deletion (frame1 _frame2) | 1024 | (defun frameset-keep-original-display-p (force-display) |
| 545 | "Predicate to sort live frames for deletion. | 1025 | "True if saved frames' displays should be honored. |
| 546 | Minibufferless frames must go first to avoid errors when attempting | 1026 | For the meaning of FORCE-DISPLAY, see `frameset-restore'." |
| 547 | to delete a frame whose minibuffer window is used by another frame." | 1027 | (cond ((daemonp) t) |
| 1028 | ((eq system-type 'windows-nt) nil) ;; Does ns support more than one display? | ||
| 1029 | (t (not force-display)))) | ||
| 1030 | |||
| 1031 | (defun frameset-minibufferless-first-p (frame1 _frame2) | ||
| 1032 | "Predicate to sort minibufferless frames before other frames." | ||
| 548 | (not (frame-parameter frame1 'minibuffer))) | 1033 | (not (frame-parameter frame1 'minibuffer))) |
| 549 | 1034 | ||
| 550 | ;;;###autoload | 1035 | ;;;###autoload |
| 551 | (cl-defun frameset-restore (frameset &key filters reuse-frames force-display force-onscreen) | 1036 | (cl-defun frameset-restore (frameset |
| 1037 | &key predicate filters reuse-frames | ||
| 1038 | force-display force-onscreen) | ||
| 552 | "Restore a FRAMESET into the current display(s). | 1039 | "Restore a FRAMESET into the current display(s). |
| 553 | 1040 | ||
| 554 | FILTERS is an alist of parameter filters; defaults to `frameset-filter-alist'. | 1041 | PREDICATE is a function called with two arguments, the parameter alist |
| 1042 | and the window-state of the frame being restored, in that order (see | ||
| 1043 | the docstring of the `frameset' defstruct for additional details). | ||
| 1044 | If PREDICATE returns nil, the frame described by that parameter alist | ||
| 1045 | and window-state is not restored. | ||
| 555 | 1046 | ||
| 556 | REUSE-FRAMES describes how to reuse existing frames while restoring a frameset: | 1047 | FILTERS is an alist of parameter filters; if nil, the value of |
| 557 | t Reuse any existing frame if possible; delete leftover frames. | 1048 | `frameset-filter-alist' is used instead. |
| 558 | nil Restore frameset in new frames and delete existing frames. | 1049 | |
| 559 | keep Restore frameset in new frames and keep the existing ones. | 1050 | REUSE-FRAMES selects the policy to use to reuse frames when restoring: |
| 560 | LIST A list of frames to reuse; only these will be reused, if possible, | 1051 | t Reuse existing frames if possible, and delete those not reused. |
| 561 | and any leftover one will be deleted; other frames not on this | 1052 | nil Restore frameset in new frames and delete existing frames. |
| 562 | list will be kept. | 1053 | :keep Restore frameset in new frames and keep the existing ones. |
| 1054 | LIST A list of frames to reuse; only these are reused (if possible). | ||
| 1055 | Remaining frames in this list are deleted; other frames not | ||
| 1056 | included on the list are left untouched. | ||
| 563 | 1057 | ||
| 564 | FORCE-DISPLAY can be: | 1058 | FORCE-DISPLAY can be: |
| 565 | t Frames will be restored in the current display. | 1059 | t Frames are restored in the current display. |
| 566 | nil Frames will be restored, if possible, in their original displays. | 1060 | nil Frames are restored, if possible, in their original displays. |
| 567 | delete Frames in other displays will be deleted instead of restored. | 1061 | :delete Frames in other displays are deleted instead of restored. |
| 1062 | PRED A function called with two arguments, the parameter alist and | ||
| 1063 | the window state (in that order). It must return t, nil or | ||
| 1064 | `:delete', as above but affecting only the frame that will | ||
| 1065 | be created from that parameter alist. | ||
| 568 | 1066 | ||
| 569 | FORCE-ONSCREEN can be: | 1067 | FORCE-ONSCREEN can be: |
| 570 | all Force onscreen any frame fully or partially offscreen. | 1068 | t Force onscreen only those frames that are fully offscreen. |
| 571 | t Force onscreen only those frames that are fully offscreen. | 1069 | nil Do not force any frame back onscreen. |
| 572 | nil Do not force any frame back onscreen. | 1070 | :all Force onscreen any frame fully or partially offscreen. |
| 1071 | PRED A function called with three arguments, | ||
| 1072 | - the live frame just restored, | ||
| 1073 | - a list (LEFT TOP WIDTH HEIGHT), describing the frame, | ||
| 1074 | - a list (LEFT TOP WIDTH HEIGHT), describing the workarea. | ||
| 1075 | It must return non-nil to force the frame onscreen, nil otherwise. | ||
| 1076 | |||
| 1077 | Note the timing and scope of the operations described above: REUSE-FRAMES | ||
| 1078 | affects existing frames; PREDICATE, FILTERS and FORCE-DISPLAY affect the frame | ||
| 1079 | being restored before that happens; and FORCE-ONSCREEN affects the frame once | ||
| 1080 | it has been restored. | ||
| 573 | 1081 | ||
| 574 | All keywords default to nil." | 1082 | All keyword parameters default to nil." |
| 575 | 1083 | ||
| 576 | (cl-assert (frameset-p frameset)) | 1084 | (cl-assert (frameset-valid-p frameset)) |
| 577 | 1085 | ||
| 578 | (let* ((delete-saved (eq force-display 'delete)) | 1086 | (let (other-frames) |
| 579 | (forcing (not (frameset-keep-original-display-p force-display))) | ||
| 580 | (target (and forcing (cons 'display (frame-parameter nil 'display)))) | ||
| 581 | other-frames) | ||
| 582 | 1087 | ||
| 583 | ;; frameset--reuse-list is a list of frames potentially reusable. Later we | 1088 | ;; frameset--reuse-list is a list of frames potentially reusable. Later we |
| 584 | ;; will decide which ones can be reused, and how to deal with any leftover. | 1089 | ;; will decide which ones can be reused, and how to deal with any leftover. |
| 585 | (pcase reuse-frames | 1090 | (pcase reuse-frames |
| 586 | ((or `nil `keep) | 1091 | ((or `nil `:keep) |
| 587 | (setq frameset--reuse-list nil | 1092 | (setq frameset--reuse-list nil |
| 588 | other-frames (frame-list))) | 1093 | other-frames (frame-list))) |
| 589 | ((pred consp) | 1094 | ((pred consp) |
| 590 | (setq frameset--reuse-list (copy-sequence reuse-frames) | 1095 | (setq frameset--reuse-list (copy-sequence reuse-frames) |
| 591 | other-frames (cl-delete-if (lambda (frame) | 1096 | other-frames (cl-delete-if (lambda (frame) |
| 592 | (memq frame frameset--reuse-list)) | 1097 | (memq frame frameset--reuse-list)) |
| 593 | (frame-list)))) | 1098 | (frame-list)))) |
| 594 | (_ | 1099 | (_ |
| 595 | (setq frameset--reuse-list (frame-list) | 1100 | (setq frameset--reuse-list (frame-list) |
| 596 | other-frames nil))) | 1101 | other-frames nil))) |
| @@ -598,96 +1103,140 @@ All keywords default to nil." | |||
| 598 | ;; Sort saved states to guarantee that minibufferless frames will be created | 1103 | ;; Sort saved states to guarantee that minibufferless frames will be created |
| 599 | ;; after the frames that contain their minibuffer windows. | 1104 | ;; after the frames that contain their minibuffer windows. |
| 600 | (dolist (state (sort (copy-sequence (frameset-states frameset)) | 1105 | (dolist (state (sort (copy-sequence (frameset-states frameset)) |
| 601 | #'frameset--sort-states)) | 1106 | #'frameset--minibufferless-last-p)) |
| 602 | (condition-case-unless-debug err | 1107 | (pcase-let ((`(,frame-cfg . ,window-cfg) state)) |
| 603 | (pcase-let* ((`(,frame-cfg . ,window-cfg) state) | 1108 | (when (or (null predicate) (funcall predicate frame-cfg window-cfg)) |
| 604 | ((and d-mini `(,hasmini . ,mb-id)) | 1109 | (condition-case-unless-debug err |
| 605 | (cdr (assq 'frameset--mini frame-cfg))) | 1110 | (let* ((d-mini (cdr (assq 'frameset--mini frame-cfg))) |
| 606 | (default (and (booleanp mb-id) mb-id)) | 1111 | (mb-id (cdr d-mini)) |
| 607 | (frame nil) (to-tty nil)) | 1112 | (default (and (car d-mini) mb-id)) |
| 608 | ;; Only set target if forcing displays and the target display is different. | 1113 | (force-display (if (functionp force-display) |
| 609 | (if (or (not forcing) | 1114 | (funcall force-display frame-cfg window-cfg) |
| 610 | (equal target (or (assq 'display frame-cfg) '(display . nil)))) | 1115 | force-display)) |
| 611 | (setq frameset--target-display nil) | 1116 | frame to-tty) |
| 612 | (setq frameset--target-display target | 1117 | ;; Only set target if forcing displays and the target display is different. |
| 613 | to-tty (null (cdr target)))) | 1118 | (cond ((frameset-keep-original-display-p force-display) |
| 614 | ;; If keeping non-reusable frames, and the frame-id of one of them | 1119 | (setq frameset--target-display nil)) |
| 615 | ;; matches the frame-id of a frame being restored (because, for example, | 1120 | ((eq (frame-parameter nil 'display) (cdr (assq 'display frame-cfg))) |
| 616 | ;; the frameset has already been read in the same session), remove the | 1121 | (setq frameset--target-display nil)) |
| 617 | ;; frame-id from the non-reusable frame, which is not useful anymore. | 1122 | (t |
| 618 | (when (and other-frames | 1123 | (setq frameset--target-display (cons 'display |
| 619 | (or (eq reuse-frames 'keep) (consp reuse-frames))) | 1124 | (frame-parameter nil 'display)) |
| 620 | (let ((dup (cl-find (cdr (assq 'frameset-frame-id frame-cfg)) | 1125 | to-tty (null (cdr frameset--target-display))))) |
| 621 | other-frames | 1126 | ;; Time to restore frames and set up their minibuffers as they were. |
| 622 | :key (lambda (frame) | 1127 | ;; We only skip a frame (thus deleting it) if either: |
| 623 | (frame-parameter frame 'frameset-frame-id)) | 1128 | ;; - we're switching displays, and the user chose the option to delete, or |
| 624 | :test #'string=))) | 1129 | ;; - we're switching to tty, and the frame to restore is minibuffer-only. |
| 625 | (when dup | 1130 | (unless (and frameset--target-display |
| 626 | (set-frame-parameter dup 'frameset-frame-id nil)))) | 1131 | (or (eq force-display :delete) |
| 627 | ;; Time to restore frames and set up their minibuffers as they were. | 1132 | (and to-tty |
| 628 | ;; We only skip a frame (thus deleting it) if either: | 1133 | (eq (cdr (assq 'minibuffer frame-cfg)) 'only)))) |
| 629 | ;; - we're switching displays, and the user chose the option to delete, or | 1134 | ;; If keeping non-reusable frames, and the frameset--id of one of them |
| 630 | ;; - we're switching to tty, and the frame to restore is minibuffer-only. | 1135 | ;; matches the id of a frame being restored (because, for example, the |
| 631 | (unless (and frameset--target-display | 1136 | ;; frameset has already been read in the same session), remove the |
| 632 | (or delete-saved | 1137 | ;; frameset--id from the non-reusable frame, which is not useful anymore. |
| 633 | (and to-tty | 1138 | (when (and other-frames |
| 634 | (eq (cdr (assq 'minibuffer frame-cfg)) 'only)))) | 1139 | (or (eq reuse-frames :keep) (consp reuse-frames))) |
| 635 | 1140 | (let ((dup (frameset-frame-with-id (cdr (assq 'frameset--id frame-cfg)) | |
| 636 | ;; Restore minibuffers. Some of this stuff could be done in a filter | 1141 | other-frames))) |
| 637 | ;; function, but it would be messy because restoring minibuffers affects | 1142 | (when dup |
| 638 | ;; global state; it's best to do it here than add a bunch of global | 1143 | (set-frame-parameter dup 'frameset--id nil)))) |
| 639 | ;; variables to pass info back-and-forth to/from the filter function. | 1144 | ;; Restore minibuffers. Some of this stuff could be done in a filter |
| 640 | (cond | 1145 | ;; function, but it would be messy because restoring minibuffers affects |
| 641 | ((null d-mini)) ;; No frameset--mini. Process as normal frame. | 1146 | ;; global state; it's best to do it here than add a bunch of global |
| 642 | (to-tty) ;; Ignore minibuffer stuff and process as normal frame. | 1147 | ;; variables to pass info back-and-forth to/from the filter function. |
| 643 | (hasmini ;; Frame has minibuffer (or it is minibuffer-only). | 1148 | (cond |
| 644 | (when (eq (cdr (assq 'minibuffer frame-cfg)) 'only) | 1149 | ((null d-mini)) ;; No frameset--mini. Process as normal frame. |
| 645 | (setq frame-cfg (append '((tool-bar-lines . 0) (menu-bar-lines . 0)) | 1150 | (to-tty) ;; Ignore minibuffer stuff and process as normal frame. |
| 646 | frame-cfg)))) | 1151 | ((car d-mini) ;; Frame has minibuffer (or it is minibuffer-only). |
| 647 | (t ;; Frame depends on other frame's minibuffer window. | 1152 | (when (eq (cdr (assq 'minibuffer frame-cfg)) 'only) |
| 648 | (let* ((mb-frame (or (cl-find-if | 1153 | (setq frame-cfg (append '((tool-bar-lines . 0) (menu-bar-lines . 0)) |
| 649 | (lambda (f) | 1154 | frame-cfg)))) |
| 650 | (string= (frame-parameter f 'frameset-id) | 1155 | (t ;; Frame depends on other frame's minibuffer window. |
| 651 | mb-id)) | 1156 | (when mb-id |
| 652 | (frame-list)) | 1157 | (let ((mb-frame (frameset-frame-with-id mb-id)) |
| 653 | (error "Minibuffer frame %S not found" mb-id))) | 1158 | (mb-window nil)) |
| 654 | (mb-param (assq 'minibuffer frame-cfg)) | 1159 | (if (not mb-frame) |
| 655 | (mb-window (minibuffer-window mb-frame))) | 1160 | (delay-warning 'frameset |
| 656 | (unless (and (window-live-p mb-window) | 1161 | (format "Minibuffer frame %S not found" mb-id) |
| 657 | (window-minibuffer-p mb-window)) | 1162 | :warning) |
| 658 | (error "Not a minibuffer window %s" mb-window)) | 1163 | (setq mb-window (minibuffer-window mb-frame)) |
| 659 | (if mb-param | 1164 | (unless (and (window-live-p mb-window) |
| 660 | (setcdr mb-param mb-window) | 1165 | (window-minibuffer-p mb-window)) |
| 661 | (push (cons 'minibuffer mb-window) frame-cfg)))))) | 1166 | (delay-warning 'frameset |
| 662 | ;; OK, we're ready at last to create (or reuse) a frame and | 1167 | (format "Not a minibuffer window %s" mb-window) |
| 663 | ;; restore the window config. | 1168 | :warning) |
| 664 | (setq frame (frameset--get-frame frame-cfg window-cfg | 1169 | (setq mb-window nil))) |
| 665 | (or filters frameset-filter-alist) | 1170 | (when mb-window |
| 666 | force-onscreen)) | 1171 | (push (cons 'minibuffer mb-window) frame-cfg)))))) |
| 667 | ;; Set default-minibuffer if required. | 1172 | ;; OK, we're ready at last to create (or reuse) a frame and |
| 668 | (when default (setq default-minibuffer-frame frame))) | 1173 | ;; restore the window config. |
| 669 | (error | 1174 | (setq frame (frameset--restore-frame frame-cfg window-cfg |
| 670 | (delay-warning 'frameset (error-message-string err) :error)))) | 1175 | (or filters frameset-filter-alist) |
| 1176 | force-onscreen)) | ||
| 1177 | ;; Set default-minibuffer if required. | ||
| 1178 | (when default (setq default-minibuffer-frame frame)))) | ||
| 1179 | (error | ||
| 1180 | (delay-warning 'frameset (error-message-string err) :error)))))) | ||
| 671 | 1181 | ||
| 672 | ;; In case we try to delete the initial frame, we want to make sure that | 1182 | ;; In case we try to delete the initial frame, we want to make sure that |
| 673 | ;; other frames are already visible (discussed in thread for bug#14841). | 1183 | ;; other frames are already visible (discussed in thread for bug#14841). |
| 674 | (sit-for 0 t) | 1184 | (sit-for 0 t) |
| 675 | 1185 | ||
| 676 | ;; Delete remaining frames, but do not fail if some resist being deleted. | 1186 | ;; Delete remaining frames, but do not fail if some resist being deleted. |
| 677 | (unless (eq reuse-frames 'keep) | 1187 | (unless (eq reuse-frames :keep) |
| 678 | (dolist (frame (sort (nconc (if (listp reuse-frames) nil other-frames) | 1188 | (dolist (frame (sort (nconc (if (listp reuse-frames) nil other-frames) |
| 679 | frameset--reuse-list) | 1189 | frameset--reuse-list) |
| 680 | #'frameset-sort-frames-for-deletion)) | 1190 | ;; Minibufferless frames must go first to avoid |
| 1191 | ;; errors when attempting to delete a frame whose | ||
| 1192 | ;; minibuffer window is used by another frame. | ||
| 1193 | #'frameset-minibufferless-first-p)) | ||
| 681 | (condition-case err | 1194 | (condition-case err |
| 682 | (delete-frame frame) | 1195 | (delete-frame frame) |
| 683 | (error | 1196 | (error |
| 684 | (delay-warning 'frameset (error-message-string err)))))) | 1197 | (delay-warning 'frameset (error-message-string err)))))) |
| 685 | (setq frameset--reuse-list nil) | 1198 | (setq frameset--reuse-list nil |
| 1199 | frameset--target-display nil) | ||
| 686 | 1200 | ||
| 687 | ;; Make sure there's at least one visible frame. | 1201 | ;; Make sure there's at least one visible frame. |
| 688 | (unless (or (daemonp) (visible-frame-list)) | 1202 | (unless (or (daemonp) (visible-frame-list)) |
| 689 | (make-frame-visible (car (frame-list)))))) | 1203 | (make-frame-visible (car (frame-list)))))) |
| 690 | 1204 | ||
| 1205 | |||
| 1206 | ;; Register support | ||
| 1207 | |||
| 1208 | (defun frameset--jump-to-register (data) | ||
| 1209 | "Restore frameset from DATA stored in register. | ||
| 1210 | Called from `jump-to-register'. Internal use only." | ||
| 1211 | (let* ((delete (and current-prefix-arg t)) | ||
| 1212 | (iconify-list (if delete nil (frame-list)))) | ||
| 1213 | (frameset-restore (aref data 0) | ||
| 1214 | :filters frameset-session-filter-alist | ||
| 1215 | :reuse-frames (if delete t :keep)) | ||
| 1216 | (mapc #'iconify-frame iconify-list) | ||
| 1217 | (let ((frame (frameset-frame-with-id (aref data 1)))) | ||
| 1218 | (when frame | ||
| 1219 | (select-frame-set-input-focus frame) | ||
| 1220 | (goto-char (aref data 2)))))) | ||
| 1221 | |||
| 1222 | ;;;###autoload | ||
| 1223 | (defun frameset-to-register (register &optional _arg) | ||
| 1224 | "Store the current frameset in register REGISTER. | ||
| 1225 | Use \\[jump-to-register] to restore the frameset. | ||
| 1226 | Argument is a character, naming the register." | ||
| 1227 | (interactive "cFrameset to register: \nP") | ||
| 1228 | (set-register register | ||
| 1229 | (registerv-make | ||
| 1230 | (vector (frameset-save nil | ||
| 1231 | :app 'register | ||
| 1232 | :filters frameset-session-filter-alist) | ||
| 1233 | ;; frameset-save does not include the value of point | ||
| 1234 | ;; in the current buffer, so record that separately. | ||
| 1235 | (frameset-frame-id nil) | ||
| 1236 | (point-marker)) | ||
| 1237 | :print-func (lambda (_data) (princ "a frameset.")) | ||
| 1238 | :jump-func #'frameset--jump-to-register))) | ||
| 1239 | |||
| 691 | (provide 'frameset) | 1240 | (provide 'frameset) |
| 692 | 1241 | ||
| 693 | ;;; frameset.el ends here | 1242 | ;;; frameset.el ends here |