]> git.eshelyaron.com Git - emacs.git/commitdiff
Further amendments of child frame handling and documentation
authorMartin Rudalics <rudalics@gmx.at>
Wed, 26 Mar 2025 08:04:49 +0000 (09:04 +0100)
committerEshel Yaron <me@eshelyaron.com>
Thu, 27 Mar 2025 10:12:36 +0000 (11:12 +0100)
* src/frame.c (frame_subsumes_p): New static function
(delete_frame): On ttys refuse to delete a frame that could be
used as surrogate minibuffer frame by surviving frames.
(store_frame_param): Make sure 'minibuffer' parameter does not
reference a deleted window.  If on a tty it references a live
window, make sure its frame has the same root frame as the frame
where the parameter shall be installed.  Also on ttys make sure
that storing the 'parent-frame' parameter does not assign a
surrogate minibuffer frame a different root frame than that of
any of its client frames.  Further on ttys assert that making a
child a new root frame gives it the dimensions of the terminal.
(Fmouse_position_in_root_frame): Don't use XFRAME before
it's clear that FRAME is a frame.
* doc/lispref/elisp.texi (Top): Add menu for Child Frames section.
* doc/lispref/frames.texi (Buffer Parameters): Mention that
value 'child-frame' is not special for 'minibuffer' parameter on
text terminals.
(Visibility of Frames): Fix description of 'iconify-frame'.
(Raising and Lowering): 'minibuffer-auto-raise' is an option.
(Child Frames): Major rewrite using subsections.  Explain new
and deviant features on text terminals - menu bar access,
reparenting, deleting, visibility and minibuffer-only child
frames.
* etc/NEWS: Remove remark that child frames cannot be
arbitrarily reparented on ttys.

(cherry picked from commit 001359ce7650c1ec110b4c38b6f67274d2d29ad1)

doc/lispref/elisp.texi
doc/lispref/frames.texi
src/frame.c

index 0a30ed26f2bf637bf6442b7410f8512e767af691..54368da3dd20c298330e3be08f436c1fc7369912 100644 (file)
@@ -1181,6 +1181,12 @@ Window Frame Parameters
 * Cursor Parameters::       Controlling the cursor appearance.
 * Font and Color Parameters:: Fonts and colors for the frame text.
 
+Child Frames
+
+* Child Frame Operations::  Making and investigating child frames.
+* Child Frame Properties::  Special properties of child frames.
+* Child Frame Peculiarities:: Deviant behaviors of child frames.
+
 Positions
 
 * Point::                   The special position where editing takes place.
index 425baacd1c8d71d934d667fc12ee63bd67519459..197f4c17b46600e1c585f346042e30026a99e0e2 100644 (file)
@@ -2071,7 +2071,9 @@ 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.
+child frame after its creation.  The value @code{child-frame} has no
+effect on text terminals where you have to create a minibuffer-only
+frame manually (@pxref{Child Frame Peculiarities}).
 
 @vindex buffer-predicate@r{, a frame parameter}
 @item buffer-predicate
@@ -3311,11 +3313,12 @@ be seen even if they are considered visible by this function.
 
 @deffn Command iconify-frame &optional frame
 This function iconifies frame @var{frame}.  If you omit @var{frame}, it
-iconifies the selected frame.  This will also remove any child frames
-(@pxref{Child Frames}) of @var{frame} from display.  On the top frame of
-a text terminal this function has no effect.  visible.  If @var{frame} is
-a child frame, the behavior depends on the value of the variable
-@code{iconify-child-frame} (@pxref{Child Frames}).
+iconifies the selected frame.  This function also removes any child
+frames (@pxref{Child Frames}) of @var{frame} and their descendants from
+display.  If @var{frame} is a child frame itself, the behavior depends
+on the value of the variable @code{iconify-child-frame}.  If @var{frame}
+is the top frame of a text terminal (@pxref{Frames}), this function has
+no effect.
 @end deffn
 
 @deffn Command make-frame-visible &optional frame
@@ -3438,7 +3441,7 @@ function @code{frame-list-z-order} (@pxref{Finding All Frames}).
 
 @defopt minibuffer-auto-raise
 If this is non-@code{nil}, activation of the minibuffer raises the frame
-that the minibuffer window is in.  This function has no effect on text
+that the minibuffer window is in.  This variable has no effect on text
 terminals.
 @end defopt
 
@@ -3474,7 +3477,6 @@ unwanted frames are iconified instead.
 @node Child Frames
 @section Child Frames
 @cindex child frames
-@cindex parent frames
 
 Child frames are objects halfway between windows (@pxref{Windows}) and
 ``normal'' frames.  Like windows, they are attached to an owning frame.
@@ -3487,26 +3489,39 @@ with the help of frame parameters (@pxref{Frame Parameters}) without any
 specialized functions or customizable variables.  Child frames
 are meaningful on graphical and text terminals.
 
-  To create a new child frame or to convert a normal frame into a child
+@menu
+* Child Frame Operations::  Making and investigating child frames.
+* Child Frame Properties::  Special properties of child frames.
+* Child Frame Peculiarities:: Deviant behaviors of child frames.
+@end menu
+
+@node Child Frame Operations
+@subsection Child Frame Operations
+
+To create a new child frame or to convert a normal frame into a child
 frame, set that frame's @code{parent-frame} parameter (@pxref{Frame
 Interaction Parameters}) to that of an already existing frame.  The
 frame specified by that parameter will then be the frame's parent frame
-as long as the parameter is not changed or reset.  Technically, this
-makes the child frame's window-system window a child window of the
-parent frame's window-system window.
-
-@cindex reparent frame
-@cindex nest frame
-  The @code{parent-frame} parameter can be changed at any time.
-Setting it to another frame @dfn{reparents} the child frame.  Setting
-it to another child frame makes the frame a @dfn{nested} child frame.
-Setting it to @code{nil} restores the frame's status as a top-level
-frame---a frame whose window-system window is a child of its display's
-root window.@footnote{On Haiku, child frames are only visible when a
-parent frame is active, owing to a limitation of the Haiku windowing
-system.  Owing to the same limitation, child frames are only
-guaranteed to appear above their top-level parent; that is to say, the
-top-most frame in the hierarchy, which does not have a parent frame.}
+as long as the parameter is not changed or reset.  Technically, on a GUI
+this makes the child frame's window-system window a child window of the
+parent frame's window-system window.  On a text terminal, this makes the
+frame usually appear on the same terminal as its parent frame, obscuring
+some part of it.
+
+@cindex reparenting frames
+@cindex nesting frames
+@cindex top-level frame
+  The @code{parent-frame} parameter can be changed at any time.  Setting
+it to another frame @dfn{reparents} the child frame.  Setting it to
+another child frame makes the frame a @dfn{nested} child frame.  Setting
+it to @code{nil} restores the frame's status as a top-level frame---a
+frame whose window-system window is a child of its display's root
+window.@footnote{On Haiku, child frames are only visible when a parent
+frame is active, owing to a limitation of the Haiku windowing system.
+Owing to the same limitation, child frames are only guaranteed to appear
+above their top-level parent; that is to say, the top-most frame in the
+hierarchy, which does not have a parent frame.}  On text terminals,
+top-level frames are called root frames (see below).
 
   Since child frames can be arbitrarily nested, a frame can be both a
 child and a parent frame.  Also, the relative roles of child and parent
@@ -3515,16 +3530,69 @@ keep the size of a child frame sufficiently smaller than that of its
 parent).  An error will be signaled for the attempt to make a frame an
 ancestor of itself.
 
-   Most window-systems clip a child frame at the native edges
-(@pxref{Frame Geometry}) of its parent frame---everything outside these
-edges is usually invisible.  A child frame's @code{left} and @code{top}
+  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.  Since on
+text terminals no such conversion is possible, deleting a frame may
+throw an error if a surrogate minibuffer frame to be deleted is used by
+a frame that will not be deleted too.
+
+  The following three functions help to understand how parent and child
+frames related to each other.
+
+@defun frame-parent &optional frame
+This function returns the parent frame of @var{frame}.  It returns
+@code{nil} if @var{frame} has no parent frame.
+@end defun
+
+@cindex ancestor frame
+@cindex descendant frame
+@defun frame-ancestor-p ancestor descendant
+This functions returns non-@code{nil} if @var{ancestor} is an ancestor
+of @var{descendant}.  @var{ancestor} is an ancestor of @var{descendant}
+when it is either @var{descendant}'s parent frame or it is an ancestor
+of @var{descendant}'s parent frame.  Both, @var{ancestor} and
+@var{descendant} must specify live frames.
+@end defun
+
+@cindex root frame
+@defun frame-root-frame &optional frame
+This function returns the root frame of the specified @var{frame}.
+@var{frame} must be a live frame and defaults to the selected one.  The
+root frame of @var{frame} is the frame obtained by following the chain
+of parent frames starting with @var{frame} until a frame is reached that
+has no parent.  If @var{frame} has no parent, its root frame is
+@var{frame} itself.
+@end defun
+
+On a text terminal, a root frame is always positioned at the top left
+edge of its terminal and always occupies the full size of its terminal.
+
+
+@node Child Frame Properties
+@subsection Child Frame Properties
+
+Most window-systems clip child frames at the native edges (@pxref{Frame
+Geometry}) of their parent frame---everything outside these edges is
+usually invisible.  A child frame's @code{left} and @code{top}
 parameters specify a position relative to the top-left corner of its
 parent's native frame.  When the parent frame is resized, this position
 remains conceptually unaltered.
 
-  NS builds do not clip child frames at the parent frame's edges,
-allowing them to be positioned so they do not obscure the parent frame
-while still being visible themselves.
+  NS builds and text terminals do not clip child frames at the parent
+frame's edges, allowing them to be positioned so they do not obscure the
+parent frame while still being visible themselves.
+
+  Note also the function @code{window-largest-empty-rectangle}
+(@pxref{Coordinates and Windows}) which can be used to inscribe a child
+frame in the largest empty area of an existing window.  This can be
+useful to avoid that a child frame obscures any text shown in that
+window.
 
   Usually, moving a parent frame moves along all its child frames and
 their descendants as well, keeping their relative positions unaltered.
@@ -3546,24 +3614,21 @@ obscuring parts of it, except on NS builds where it may be positioned
 beneath the parent.  This is comparable to the window-system window of a
 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.  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.
+made invisible (@pxref{Visibility of Frames}), any child frames
+descending from it will not be shown either even if
+@code{frame-visible-p} returns @code{t} for them.  When a parent frame
+is deiconified or made visible, any child frames descending from it will
+be shown again (provided they and all their ancestor frames are visible
+too).  If a child frame is used as surrogate minibuffer frame
+(@pxref{Minibuffers and Frames}), it's up to the application to
+guarantee the frame's visibility whenever the minibuffer is activated.
 
   Whether a child frame can have a menu or tool bar is window-system or
 window manager dependent.  Most window-systems explicitly disallow menu
 bars for child frames.  It seems advisable to disable both, menu and
-tool bars, via the frame's initial parameters settings.
+tool bars, via the frame's initial parameters settings.  On a text
+terminal, child frames use the menu bar of their root frame (provided it
+has one).
 
   Usually, child frames do not exhibit window manager decorations like a
 title bar or external borders (@pxref{Frame Geometry}).  When the child
@@ -3575,15 +3640,18 @@ outer border can be used.  On MS-Windows, specifying a non-zero outer
 border width will show a one-pixel wide external border.  Under all
 window-systems, the internal border can be used.  In either case, it's
 advisable to disable a child frame's window manager decorations with the
-@code{undecorated} frame parameter (@pxref{Management Parameters}).
+@code{undecorated} frame parameter (@pxref{Management Parameters}).  On
+a text terminal, on the other hand, it's better to leave that parameter
+alone so your child frame will be drawn with an outer border.
 
-  To resize or move an undecorated child frame with the mouse, special
+  To resize or move a border-less child frame with the mouse, special
 frame parameters (@pxref{Mouse Dragging Parameters}) have to be used.
 The internal border of a child frame, if present, can be used to resize
 the frame with the mouse, provided that frame has a non-@code{nil}
 @code{drag-internal-border} parameter.  If set, the @code{snap-width}
 parameter indicates the number of pixels where the frame @dfn{snaps} at
-the respective edge or corner of its parent frame.
+the respective edge or corner of its parent frame.  On a text terminal,
+the outer border can used for resizing.
 
   There are two ways to drag an entire child frame with the mouse: The
 @code{drag-with-mode-line} parameter, if non-@code{nil}, enables
@@ -3627,8 +3695,12 @@ to display completions in a separate window, the @code{minibuffer-exit}
 parameter (@pxref{Frame Interaction Parameters}) is useful in order to
 deal with the frame when the minibuffer is exited.
 
-  The behavior of child frames deviates from that of top-level frames in
-a number of other ways as well.  Here we sketch a few of them:
+
+@node Child Frame Peculiarities
+@subsection Child Frame Peculiarities
+
+The behavior of child frames deviates from that of normal frames in a
+number of peculiar ways.  Here we sketch a few of them:
 
 @itemize @bullet
 @item
@@ -3644,7 +3716,7 @@ described below.
 Raising, lowering and restacking child frames (@pxref{Raising and
 Lowering}) or changing the @code{z-group} (@pxref{Position Parameters})
 of a child frame changes only the stacking order of child frames with
-the same parent.
+the same parent.  Restacking has not been implemented on text terminals.
 
 @item
 Many window-systems are not able to change the opacity (@pxref{Font and
@@ -3667,43 +3739,6 @@ work on all window-systems.  Some will drop the object on the parent
 frame or on some ancestor instead.
 @end itemize
 
-  The following three functions can be useful when working with child and
-parent frames:
-
-@defun frame-parent &optional frame
-This function returns the parent frame of @var{frame}.  The parent frame
-of @var{frame} is the Emacs frame whose window-system window is the
-parent window of @var{frame}'s window-system window.  If such a frame
-exists, @var{frame} is considered a child frame of that frame.
-
-This function returns @code{nil} if @var{frame} has no parent frame.
-@end defun
-
-@cindex ancestor frame
-@defun frame-ancestor-p ancestor descendant
-This functions returns non-@code{nil} if @var{ancestor} is an ancestor
-of @var{descendant}.  @var{ancestor} is an ancestor of @var{descendant}
-when it is either @var{descendant}'s parent frame or it is an ancestor
-of @var{descendant}'s parent frame.  Both, @var{ancestor} and
-@var{descendant} must specify live frames.
-@end defun
-
-@cindex root frame
-@defun frame-root-frame &optional frame
-This function returns the root frame of the specified @var{frame}.
-@var{frame} must be a live frame and defaults to the selected one.  The
-root frame of @var{frame} is the frame obtained by following the chain
-of parent frames starting with @var{frame} until a frame is reached that
-has no parent.  If @var{frame} has no parent, its root frame is
-@var{frame} itself.
-@end defun
-
-Note also the function @code{window-largest-empty-rectangle}
-(@pxref{Coordinates and Windows}) which can be used to inscribe a child
-frame in the largest empty area of an existing window.  This can be
-useful to avoid that a child frame obscures any text shown in that
-window.
-
 Customizing the following option can be useful to tweak the behavior of
 @code{iconify-frame} for child frames.
 
@@ -3724,6 +3759,68 @@ On a text terminal the only feasible values are @code{nil} and
 @code{make-invisible}.
 @end defopt
 
+On text terminals exist a few restrictions with respect to reparenting:
+One is that a top frame (@pxref{Frames}) cannot be directly made a child
+frame---you first have to make another root frame the new top frame of
+its terminal.  If, on the other hand, you want a child frame to become
+the new top frame of its terminal, you have to make it a root frame
+first.
+
+  Also, the surrogate minibuffer window of any frame on a text terminal
+must reside on a frame with the same root frame.  Reparenting will throw
+an error whenever it violates this restriction.  It also means that it's
+more tricky to make a minibuffer-less frame whose minibuffer window
+resides on a minibuffer-only child frame.  On a GUI, Emacs proceeds as
+follows when a user has specified the value @code{child-frame} for the
+@code{minibuffer} parameter in @code{initial-frame-alist}
+(@pxref{Initial Parameters}):
+
+@enumerate
+@item
+Create a minibuffer-only frame.
+
+@item
+Create a minibuffer-less frame with its @code{minibuffer} parameter set
+to the window of the minibuffer-only frame.
+
+@item
+Make the minibuffer-less frame the parent frame of the minibuffer-only
+frame.
+
+@item
+Delete the originally selected frame.
+@end enumerate
+
+  On a text terminal you have to perform these operations manually as
+sketched in the following snippet:
+
+@example
+@group
+(let* ((selected (selected-frame))
+       (mini-only
+        (make-frame
+         `((parent-frame . ,selected)
+           (minibuffer . only)
+           (left . 1) (top . -1) (width . 20) (height . 1))))
+       (mini-less
+        (make-frame
+         (append `((parent-frame . ,selected)
+                   (minibuffer . ,(minibuffer-window mini-only)))))))
+  (set-frame-parameter mini-only 'parent-frame mini-less)
+  (set-frame-parameter mini-less 'parent-frame nil)
+  (select-frame mini-less)
+  (delete-frame selected))
+@end group
+@end example
+
+  This means that you first have to install the minibuffer-less and the
+minibuffer-only frames both as child frames of the selected frame with
+the @code{minibuffer} parameter of the minibuffer-less frame set to the
+minibuffer window of the minibuffer-only frame.  Then make the
+minibuffer-only frame a child frame of the minibuffer-less frame and
+make the minibuffer-less frame a new root frame.  Finally, select the
+minibuffer-less frame and delete the originally selected frame.
+
 
 @node Mouse Tracking
 @section Mouse Tracking
index 9166b35dbd790dad27ce16af69db84463b741382..ee6246dd7bd19a2b75ab9cb3019d6e58d70b2519 100644 (file)
@@ -2070,6 +2070,7 @@ parent window is the window-system's root window) or an embedded window
     return Qnil;
 }
 
+/* Return true if frame AF is an ancestor of frame DF.  */
 bool
 frame_ancestor_p (struct frame *af, struct frame *df)
 {
@@ -2086,6 +2087,22 @@ frame_ancestor_p (struct frame *af, struct frame *df)
   return false;
 }
 
+/* A frame AF subsumes a frame DF if AF and DF are the same or AF is an
+   ancestor of DF.  */
+static bool
+frame_subsumes_p (struct frame *af, struct frame *df)
+{
+  while (df)
+    {
+      if (df == af)
+       return true;
+      else
+       df = FRAME_PARENT_FRAME (df);
+    }
+
+  return false;
+}
+
 DEFUN ("frame-ancestor-p", Fframe_ancestor_p, Sframe_ancestor_p,
        2, 2, 0,
        doc: /* Return non-nil if ANCESTOR is an ancestor of DESCENDANT.
@@ -2100,7 +2117,6 @@ frame.  */)
   return frame_ancestor_p (af, df) ? Qt : Qnil;
 }
 
-
 /* Return the root frame of frame F.  Follow the parent_frame chain
    until we reach a frame that has no parent.  That is the root frame.
    Note that the root of a root frame is itself. */
@@ -2448,6 +2464,18 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 
   XSETFRAME (frame, f);
 
+  if (is_tty_frame (f) && NILP (force))
+    /* If F is a tty frame, check for surrogate minibuffer frames F
+       subsumes used by a frame that is not subsumed by F. */
+    FOR_EACH_FRAME (frames, frame1)
+      {
+       struct frame *f1 = XFRAME (frame1);
+
+       if (frame_subsumes_p (f, WINDOW_XFRAME (XWINDOW (f1->minibuffer_window)))
+           && !frame_subsumes_p (f, f1))
+         error ("Cannot delete surrogate minibuffer frame");
+      }
+
   /* Softly delete all frames with this frame as their parent frame or
      as their `delete-before' frame parameter value.  */
   FOR_EACH_FRAME (frames, frame1)
@@ -3625,7 +3653,7 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val)
     {
       if (WINDOWP (val))
        {
-         if (!MINI_WINDOW_P (XWINDOW (val)))
+         if (!WINDOW_LIVE_P (val) || !MINI_WINDOW_P (XWINDOW (val)))
            error ("The `minibuffer' parameter does not specify a valid minibuffer window");
          else if (FRAME_MINIBUF_ONLY_P (f))
            {
@@ -3641,6 +3669,10 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val)
              else
                error ("Can't change the minibuffer window of a frame with its own minibuffer");
            }
+         else if (is_tty_frame (f)
+                  && (root_frame (WINDOW_XFRAME (XWINDOW (val)))
+                      != root_frame (f)))
+           error ("A frame and its surrogate minibuffer frame must have the same roots");
          else
            /* Store the chosen minibuffer window.  */
            fset_minibuffer_window (f, val);
@@ -3719,12 +3751,51 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val)
       val = old_val;
     }
 
-  /* Re-parenting is currently not implemented when changing a root
-     frame to a child frame or vice versa.  */
+  /* The parent frame parameter for ttys must be handled specially.  */
   if (is_tty_frame (f) && EQ (prop, Qparent_frame))
     {
-      if (NILP (f->parent_frame) != NILP (val))
-       error ("Making a root frame a child or vice versa is not supported");
+      /* Invariant: When a frame F1 uses a surrogate minibuffer frame M1
+        on a tty, both F1 and M1 must have the same root frame.  */
+      Lisp_Object frames, frame1, old_val = f->parent_frame;
+
+      FOR_EACH_FRAME (frames, frame1)
+       {
+         struct frame *f1 = XFRAME (frame1);
+         struct frame *m1 = WINDOW_XFRAME (XWINDOW (f1->minibuffer_window));
+         bool mismatch = false;
+
+         /* Temporarily install VAL and check whether our invariant
+            above gets violated.  */
+         f->parent_frame = val;
+         mismatch = root_frame (f1) != root_frame (m1);
+         f->parent_frame = old_val;
+
+         if (mismatch)
+           error ("Cannot re-root surrogate minibuffer frame");
+       }
+
+      if (f == XFRAME (FRAME_TERMINAL (f)->display_info.tty->top_frame)
+         && !NILP (val))
+       error ("Cannot make tty top frame a child frame");
+      else if (NILP (val))
+       {
+         if (!FRAME_HAS_MINIBUF_P (f)
+             && (!frame_ancestor_p
+                 (f, WINDOW_XFRAME (XWINDOW (f->minibuffer_window)))))
+           error ("Cannot make tty root frame without valid minibuffer window");
+         else
+           {
+             /* When making a frame a root frame, expand it to full size,
+                if necessary, and position it at top left corner.  */
+             int width, height;
+
+             get_tty_size (fileno (FRAME_TTY (f)->input), &width, &height);
+             adjust_frame_size (f, width, height - FRAME_TOP_MARGIN (f), 5, 0,
+                                Qterminal_frame);
+             f->left_pos = 0;
+             f->top_pos = 0;
+           }
+       }
 
       SET_FRAME_GARBAGED (root_frame (f));
       f->parent_frame = val;
@@ -6593,14 +6664,15 @@ of the frame returned by 'mouse-position'.  */)
 {
   Lisp_Object pos = mouse_position (true);
   Lisp_Object frame = XCAR (pos);
-  struct frame *f = XFRAME (frame);
-  int x = XFIXNUM (XCAR (XCDR (pos))) + f->left_pos;
-  int y = XFIXNUM (XCDR (XCDR (pos))) + f->top_pos;
 
   if (!FRAMEP (frame))
     return Qnil;
   else
     {
+      struct frame *f = XFRAME (frame);
+      int x = XFIXNUM (XCAR (XCDR (pos))) + f->left_pos;
+      int y = XFIXNUM (XCDR (XCDR (pos))) + f->top_pos;
+
       f = FRAME_PARENT_FRAME (f);
 
       while (f)