From a552cc21dc324b1be71c181684b0ab2e6cf0a60f Mon Sep 17 00:00:00 2001 From: Martin Rudalics Date: Tue, 5 Mar 2019 10:46:19 +0100 Subject: [PATCH] Fix handling of minibuffer-only child frames (Bug#33498) * doc/lispref/frames.texi (Buffer Parameters): Describe how to make a minibuffer-only child frame. (Child Frames): Describe how minbuffer child frames are deleted. * src/frame.c (delete_frame): Handle deletion of minibuffer child frames (Bug#33498). In the course, fix reassigning of 'default-minibuffer-frame' with minibuffer-only frames. * lisp/frame.el (frame-notice-user-settings): Handle creation of initial minibuffer-only child frame. (make-frame): Handle creation of frame with a minibuffer-only child frame. --- doc/lispref/frames.texi | 20 +++++++-- etc/NEWS | 5 +++ lisp/frame.el | 81 ++++++++++++++++++++++++++------- src/frame.c | 99 ++++++++++++++++++++++++++++++++++------- 4 files changed, 170 insertions(+), 35 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 820006a5675..9b3e02f4de0 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -1884,6 +1884,12 @@ minibuffer window to @code{t} and vice-versa, or from @code{t} to @code{nil}. If the parameter specifies a minibuffer window already, setting it to @code{nil} has no effect. +The special value @code{child-frame} means to make a minibuffer-only +child frame (@pxref{Child Frames}) whose parent becomes the frame +created. As if specified as @code{nil}, Emacs will set this parameter +to the minibuffer window of the child frame but will not select the +child frame after its creation. + @vindex buffer-predicate@r{, a frame parameter} @item buffer-predicate The buffer-predicate function for this frame. The function @@ -3214,9 +3220,17 @@ top-level frame which also always appears on top of its parent window---the desktop's root window. When a parent frame is iconified or made invisible (@pxref{Visibility of Frames}), its child frames are made invisible. When a parent frame is deiconified or made visible, its -child frames are made visible. When a parent frame is about to be -deleted (@pxref{Deleting Frames}), its child frames are recursively -deleted before it. +child frames are made visible. + + When a parent frame is about to be deleted (@pxref{Deleting +Frames}), its child frames are recursively deleted before it. There +is one exception to this rule: When the child frame serves as a +surrogate minibuffer frame (@pxref{Minibuffers and Frames}) for +another frame, it is retained until the parent frame has been deleted. +If, at this time, no remaining frame uses the child frame as its +minibuffer frame, Emacs will try to delete the child frame too. If +that deletion fails for whatever reason, the child frame is made a +top-level frame. Whether a child frame can have a menu or tool bar is window-system or window manager dependent. Most window-systems explicitly disallow menus diff --git a/etc/NEWS b/etc/NEWS index fc4e2c57268..3e347b5318a 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1166,6 +1166,11 @@ the 128...255 range, as expected. +++ *** New command 'make-frame-on-monitor' makes a frame on the specified monitor. ++++ +*** New value of 'minibuffer' frame parameter 'child-frame'. +This allows to create and parent immediately a minibuffer-only child +frame when making a frame. + * New Modes and Packages in Emacs 27.1 diff --git a/lisp/frame.el b/lisp/frame.el index d71a3fe5e8e..cdb2ac4af11 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -316,10 +316,15 @@ there (in decreasing order of priority)." ;; want to use save-excursion here, because that may also try to set ;; the buffer of the selected window, which fails when the selected ;; window is the minibuffer. - (let ((old-buffer (current-buffer)) - (window-system-frame-alist - (cdr (assq initial-window-system - window-system-default-frame-alist)))) + (let* ((old-buffer (current-buffer)) + (window-system-frame-alist + (cdr (assq initial-window-system + window-system-default-frame-alist))) + (minibuffer + (cdr (or (assq 'minibuffer initial-frame-alist) + (assq 'minibuffer window-system-frame-alist) + (assq 'minibuffer default-frame-alist) + '(minibuffer . t))))) (when (and frame-notice-user-settings (null frame-initial-frame)) @@ -410,11 +415,7 @@ there (in decreasing order of priority)." ;; default-frame-alist in the parameters of the screen we ;; create here, so that its new value, gleaned from the user's ;; init file, will be applied to the existing screen. - (if (not (eq (cdr (or (assq 'minibuffer initial-frame-alist) - (assq 'minibuffer window-system-frame-alist) - (assq 'minibuffer default-frame-alist) - '(minibuffer . t))) - t)) + (if (not (eq minibuffer t)) ;; Create the new frame. (let (parms new) ;; MS-Windows needs this to avoid inflooping below. @@ -442,7 +443,15 @@ there (in decreasing order of priority)." parms nil)) - ;; Get rid of `reverse', because that was handled + (when (eq minibuffer 'child-frame) + ;; When the minibuffer shall be shown in a child frame, + ;; remove the 'minibuffer' parameter from PARMS. It + ;; will get assigned by the usual routines to the child + ;; frame's root window below. + (setq parms (cons '(minibuffer) + (delq (assq 'minibuffer parms) parms)))) + + ;; Get rid of `reverse', because that was handled ;; when we first made the frame. (setq parms (cons '(reverse) (delq (assq 'reverse parms) parms))) @@ -465,7 +474,18 @@ there (in decreasing order of priority)." ;; the only frame with a minibuffer. If it is, create a ;; new one. (or (delq frame-initial-frame (minibuffer-frame-list)) - (make-initial-minibuffer-frame nil)) + (and (eq minibuffer 'child-frame) + ;; Create a minibuffer child frame and parent it + ;; immediately. Take any other parameters for + ;; the child frame from 'minibuffer-frame-list'. + (let* ((minibuffer-frame-alist + (cons `(parent-frame . ,new) minibuffer-frame-alist))) + (make-initial-minibuffer-frame nil) + ;; With a minibuffer child frame we do not want + ;; to select the minibuffer frame initially as + ;; we do for standard minibuffer-only frames. + (select-frame new))) + (make-initial-minibuffer-frame nil)) ;; If the initial frame is serving as a surrogate ;; minibuffer frame for any frames, we need to wean them @@ -795,7 +815,7 @@ the new frame according to its own rules." (t window-system))) (oldframe (selected-frame)) (params parameters) - frame) + frame child-frame) (unless (get w 'window-system-initialized) (let ((window-system w)) ;Hack attack! @@ -811,17 +831,44 @@ the new frame according to its own rules." (dolist (p default-frame-alist) (unless (assq (car p) params) (push p params))) - ;; Now make the frame. - (run-hooks 'before-make-frame-hook) ;; (setq frame-size-history '(1000)) - (setq frame (let ((window-system w)) ;Hack attack! + (when (eq (cdr (or (assq 'minibuffer params) '(minibuffer . t))) + 'child-frame) + ;; If the 'minibuffer' parameter equals 'child-frame' make a + ;; frame without minibuffer first using the root window of + ;; 'default-minibuffer-frame' as its minibuffer window + (setq child-frame t) + (setq params (cons '(minibuffer) + (delq (assq 'minibuffer params) params)))) + + ;; Now make the frame. + (run-hooks 'before-make-frame-hook) + + (setq frame (let ((window-system w)) ; Hack attack! (frame-creation-function params))) + + (when child-frame + ;; When we want to equip the new frame with a minibuffer-only + ;; child frame, make that frame and reparent it immediately. + (setq child-frame + (make-frame + (append + `((display . ,display) (minibuffer . only) + (parent-frame . ,frame)) + minibuffer-frame-alist))) + (when (frame-live-p child-frame) + ;; Have the 'minibuffer' parameter of our new frame refer to + ;; its child frame's root window. + (set-frame-parameter + frame 'minibuffer (frame-root-window child-frame)))) + (normal-erase-is-backspace-setup-frame frame) - ;; Inherit the original frame's parameters. + ;; Inherit original frame's parameters unless they are overridden + ;; by explicit parameters. (dolist (param frame-inherited-parameters) - (unless (assq param parameters) ;Overridden by explicit parameters. + (unless (assq param parameters) (let ((val (frame-parameter oldframe param))) (when val (set-frame-parameter frame param val))))) diff --git a/src/frame.c b/src/frame.c index 165ed4a4e52..3d83dc0a0d8 100644 --- a/src/frame.c +++ b/src/frame.c @@ -1849,6 +1849,7 @@ delete_frame (Lisp_Object frame, Lisp_Object force) Lisp_Object frames, frame1; int minibuffer_selected, is_tooltip_frame; bool nochild = !FRAME_PARENT_FRAME (f); + Lisp_Object minibuffer_child_frame = Qnil; if (!FRAME_LIVE_P (f)) return Qnil; @@ -1865,13 +1866,33 @@ delete_frame (Lisp_Object frame, Lisp_Object force) /* Softly delete all frames with this frame as their parent frame or as their `delete-before' frame parameter value. */ FOR_EACH_FRAME (frames, frame1) - if (FRAME_PARENT_FRAME (XFRAME (frame1)) == f + { + struct frame *f1 = XFRAME (frame1); + + if (EQ (frame1, frame) || FRAME_TOOLTIP_P (f1)) + continue; + else if (FRAME_PARENT_FRAME (f1) == f) + { + if (FRAME_HAS_MINIBUF_P (f1) && !FRAME_HAS_MINIBUF_P (f) + && EQ (FRAME_MINIBUF_WINDOW (f), FRAME_MINIBUF_WINDOW (f1))) + /* frame1 owns frame's minibuffer window so we must not + delete it here to avoid a surrogate minibuffer error. + Unparent frame1 and make it a top-level frame. */ + { + Fmodify_frame_parameters + (frame1, Fcons (Fcons (Qparent_frame, Qnil), Qnil)); + minibuffer_child_frame = frame1; + } + else + delete_frame (frame1, Qnil); + } + else if (nochild + && EQ (get_frame_param (XFRAME (frame1), Qdelete_before), frame)) /* Process `delete-before' parameter iff FRAME is not a child frame. This avoids that we enter an infinite chain of mixed dependencies. */ - || (nochild - && EQ (get_frame_param (XFRAME (frame1), Qdelete_before), frame))) - delete_frame (frame1, Qnil); + delete_frame (frame1, Qnil); + } /* Does this frame have a minibuffer, and is it the surrogate minibuffer for any other frame? */ @@ -2136,18 +2157,27 @@ delete_frame (Lisp_Object frame, Lisp_Object force) { struct frame *f1 = XFRAME (frame1); - /* Consider only frames on the same kboard - and only those with minibuffers. */ - if (kb == FRAME_KBOARD (f1) - && FRAME_HAS_MINIBUF_P (f1)) + /* Set frame_on_same_kboard to frame1 if it is on the same + keyboard. Set frame_with_minibuf to frame1 if it also + has a minibuffer. Leave the loop immediately if frame1 + is also minibuffer-only. + + Emacs 26 does _not_ set frame_on_same_kboard here when it + finds a minibuffer-only frame and subsequently fails to + set default_minibuffer_frame below. Not a great deal and + never noticed since make_frame_without_minibuffer creates + a new minibuffer frame in that case (which can be a minor + annoyance though). To consider for Emacs 26.3. */ + if (kb == FRAME_KBOARD (f1)) { - frame_with_minibuf = frame1; - if (FRAME_MINIBUF_ONLY_P (f1)) - break; + frame_on_same_kboard = frame1; + if (FRAME_HAS_MINIBUF_P (f1)) + { + frame_with_minibuf = frame1; + if (FRAME_MINIBUF_ONLY_P (f1)) + break; + } } - - if (kb == FRAME_KBOARD (f1)) - frame_on_same_kboard = frame1; } if (!NILP (frame_on_same_kboard)) @@ -2180,7 +2210,46 @@ delete_frame (Lisp_Object frame, Lisp_Object force) = Fcons (list3 (Qrun_hook_with_args, Qafter_delete_frame_functions, frame), pending_funcalls); else - safe_call2 (Qrun_hook_with_args, Qafter_delete_frame_functions, frame); + safe_call2 (Qrun_hook_with_args, Qafter_delete_frame_functions, frame); + + if (!NILP (minibuffer_child_frame)) + /* If minibuffer_child_frame is non-nil, it was FRAME's minibuffer + child frame. Delete it unless it's also the minibuffer frame + of another frame in which case we make sure it's visible. */ + { + struct frame *f1 = XFRAME (minibuffer_child_frame); + + if (FRAME_LIVE_P (f1)) + { + Lisp_Object window1 = FRAME_ROOT_WINDOW (f1); + Lisp_Object frame2; + + FOR_EACH_FRAME (frames, frame2) + { + struct frame *f2 = XFRAME (frame2); + + if (EQ (frame2, minibuffer_child_frame) || FRAME_TOOLTIP_P (f2)) + continue; + else if (EQ (FRAME_MINIBUF_WINDOW (f2), window1)) + { + /* minibuffer_child_frame serves as minibuffer frame + for at least one other frame - so make it visible + and quit. */ + if (!FRAME_VISIBLE_P (f1) && !FRAME_ICONIFIED_P (f1)) + Fmake_frame_visible (frame1); + + return Qnil; + } + } + + /* No other frame found that uses minibuffer_child_frame as + minibuffer frame. If FORCE is Qnoelisp or there are + other visible frames left, delete minibuffer_child_frame + since it presumably was used by FRAME only. */ + if (EQ (force, Qnoelisp) || other_frames (f1, false, !NILP (force))) + delete_frame (minibuffer_child_frame, Qnoelisp); + } + } return Qnil; } -- 2.39.2