Subject: bug#75056: 31.0.50; tty-child-frames with server / multiple clients possible hangs

From: martin rudalics <[email-masked]>
Date: Sun, 2 Feb 2025 18:37:46 +0100

> Also, moving child frames is pretty nice too!

The attached patch now also resizes child frames by dragging their edges
or corners.  One thing that does not work is that when another child
frame is beneath an edge, Emacs selects the child frame beneath the edge
before I can start dragging.  I think that frame/child frame selection
with the mouse is not perfect in the current state in three regards:

- When I do down-mouse-1 in the non-selected root frame, Emacs does
   'mouse-set-point' there.  I think it should do so only for a mouse-1
   so I can back out before releasing the mouse button and obviously use
   the down-mouse-1 for dragging.

- When I have two overlapping child frames and do down-mouse-1 on a
   border of the frame above and that border covers the frame beneath,
   Emacs should never select the frame beneath here.  Not even after I
   release the button.

- Clicking into a child frame anywhere but on a bar does not select it.
   This is uncomfortable and at least does not mimic the behavior of GUI
   frames on all window managers I know.

I'll look into these tomorrow.  Pointers welcome.

martin

diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 13911109fd7..1469df6316f 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -2691,53 +2691,74 @@ popup-menu
 			 (filter (when (symbolp map)
 				   (plist-get (get map 'menu-prop) :filter))))
 		    (if filter (funcall filter (symbol-function map)) map)))))
-	 (frame (selected-frame))
+	 (selected-frame (selected-frame))
+	 (frame (if (and (eq (framep selected-frame) t) (frame-parent)
+			 from-menu-bar
+			 (zerop (or (frame-parameter nil 'menu-bar-lines) 0)))
+		    ;; If the selected frame is a tty child frame
+		    ;; without its own menu bar and we are called from
+		    ;; the menu bar, the menu bar must be on the root
+		    ;; frame of the selected frame.
+		    (frame-root-frame)
+		  (selected-frame)))
 	 event cmd)
-    (if from-menu-bar
-	(let* ((xy (posn-x-y position))
-	       (menu-symbol (menu-bar-menu-at-x-y (car xy) (cdr xy))))
-	  (setq position (list menu-symbol (list frame '(menu-bar)
-						 xy 0))))
-      (setq position (popup-menu-normalize-position position)))
-    ;; The looping behavior was taken from lmenu's popup-menu-popup
-    (while (and map (setq event
-			  ;; map could be a prefix key, in which case
-			  ;; we need to get its function cell
-			  ;; definition.
-			  (x-popup-menu position (indirect-function map))))
-      ;; Strangely x-popup-menu returns a list.
-      ;; mouse-major-mode-menu was using a weird:
-      ;; (key-binding (apply 'vector (append '(menu-bar) menu-prefix events)))
-      (setq cmd
-	    (cond
-	     ((and from-menu-bar
-		   (consp event)
-		   (numberp (car event))
-		   (numberp (cdr event)))
-	      (let ((x (car event))
-		    (y (cdr event))
-		    menu-symbol)
-		(setq menu-symbol (menu-bar-menu-at-x-y x y))
-		(setq position (list menu-symbol (list frame '(menu-bar)
-						 event 0)))
-		(setq map
-		      (key-binding (vector 'menu-bar menu-symbol)))))
-	     ((and (not (keymapp map)) (listp map))
-	      ;; We were given a list of keymaps.  Search them all
-	      ;; in sequence until a first binding is found.
-	      (let ((mouse-click (apply 'vector event))
-		    binding)
-		(while (and map (null binding))
-		  (setq binding (lookup-key-ignore-too-long (car map) mouse-click))
-		  (setq map (cdr map)))
-                binding))
-	     (t
-	      ;; We were given a single keymap.
-	      (lookup-key map (apply 'vector event)))))
-      ;; Clear out echoing, which perhaps shows a prefix arg.
-      (message "")
-      ;; Maybe try again but with the submap.
-      (setq map (if (keymapp cmd) cmd)))
+    (with-selected-frame frame
+      (if from-menu-bar
+	  (let* ((xy (posn-x-y position))
+		 (menu-symbol (menu-bar-menu-at-x-y (car xy) (cdr xy))))
+	    (setq position (list menu-symbol (list frame '(menu-bar)
+						   xy 0))))
+	(setq position (popup-menu-normalize-position position)))
+
+      ;; The looping behavior was taken from lmenu's popup-menu-popup
+      (while (and map (setq event
+			    ;; map could be a prefix key, in which case
+			    ;; we need to get its function cell
+			    ;; definition.
+			    (x-popup-menu position (indirect-function map))))
+	;; Strangely x-popup-menu returns a list.
+	;; mouse-major-mode-menu was using a weird:
+	;; (key-binding (apply 'vector (append '(menu-bar) menu-prefix events)))
+	(setq cmd
+	      (cond
+	       ((and from-menu-bar
+		     (consp event)
+		     (numberp (car event))
+		     (numberp (cdr event)))
+		(let ((x (car event))
+		      (y (cdr event))
+		      menu-symbol)
+		  (setq menu-symbol (menu-bar-menu-at-x-y x y))
+		  (setq position (list menu-symbol (list frame '(menu-bar)
+							 event 0)))
+		  (if (not (eq frame selected-frame))
+		      ;; If we are using the menu bar from the root
+		      ;; frame, look up the key binding in the keymaps
+		      ;; of the initially selected window's buffer to
+		      ;; make sure that navigating the menu bar with the
+		      ;; keyboard works as intended.
+		      (setq map
+			    (key-binding (vector 'menu-bar menu-symbol) nil nil
+					 (frame-selected-window selected-frame)))
+		    (setq map
+			  (key-binding (vector 'menu-bar menu-symbol))))))
+	       ((and (not (keymapp map)) (listp map))
+		;; We were given a list of keymaps.  Search them all
+		;; in sequence until a first binding is found.
+		(let ((mouse-click (apply 'vector event))
+		      binding)
+		  (while (and map (null binding))
+		    (setq binding (lookup-key-ignore-too-long (car map) mouse-click))
+		    (setq map (cdr map)))
+		  binding))
+	       (t
+		;; We were given a single keymap.
+		(lookup-key map (apply 'vector event)))))
+	;; Clear out echoing, which perhaps shows a prefix arg.
+	(message "")
+	;; Maybe try again but with the submap.
+	(setq map (if (keymapp cmd) cmd))))
+
     ;; If the user did not cancel by refusing to select,
     ;; and if the result is a command, run it.
     (when (and (null map) (commandp cmd))
@@ -2808,14 +2829,27 @@ menu-bar-open
 If FRAME is nil or not given, use the selected frame."
   (interactive
    (list nil (prefix-numeric-value current-prefix-arg)))
-  (let ((type (framep (or frame (selected-frame)))))
+  (let* ((type (framep (or frame (selected-frame))))
+	 root
+	 (frame (if (and (eq type t) (frame-parent frame)
+			 (null tty-menu-open-use-tmm)
+			 (zerop (or (frame-parameter frame 'menu-bar-lines) 0))
+			 (setq root (frame-root-frame))
+			 (not (zerop
+			       (or (frame-parameter root 'menu-bar-lines) 0))))
+		    ;; If FRAME is a tty child frame without its own
+		    ;; menu bar, 'tty-menu-open-use-tmm' is false and
+		    ;; FRAME's root frame has a menu bar, use that root
+		    ;; frame's menu bar.
+		    root
+		  frame)))
     (cond
      ((eq type 'x) (x-menu-bar-open frame))
      ((eq type 'w32) (w32-menu-bar-open frame))
      ((eq type 'haiku) (haiku-menu-bar-open frame))
      ((eq type 'pgtk) (pgtk-menu-bar-open frame))
      ((and (null tty-menu-open-use-tmm)
-	   (not (zerop (or (frame-parameter nil 'menu-bar-lines) 0))))
+	   (not (zerop (or (frame-parameter frame 'menu-bar-lines) 0))))
       ;; Make sure the menu bar is up to date.  One situation where
       ;; this is important is when this function is invoked by name
       ;; via M-x, in which case the menu bar includes the "Minibuf"
@@ -2831,7 +2865,7 @@ menu-bar-open
                       (current-local-map) (vector 'menu-bar menu))
 		     (cdar (minor-mode-key-binding (vector 'menu-bar menu)))
                      (mouse-menu-bar-map))
-		    (posn-at-x-y x 0 nil t) nil t)))
+		    (posn-at-x-y x 0 frame t) nil t)))
      (t (with-selected-frame (or frame (selected-frame))
           (tmm-menubar))))))
 
diff --git a/lisp/mouse.el b/lisp/mouse.el
index b05115b237d..5d4d30af5e7 100644
--- a/lisp/mouse.el
+++ b/lisp/mouse.el
@@ -1106,7 +1106,10 @@ mouse-drag-frame-resize
 	 (drag-bottom (memq part '(bottom-right bottom bottom-left)))
 	 ;; Initial "first" mouse position.  While dragging we base all
 	 ;; calculations against that position.
-	 (first-x-y (mouse-absolute-pixel-position))
+	 (tty (tty-type frame))
+	 (first-x-y (if tty
+			(mouse-position-in-root-frame)
+		      (mouse-absolute-pixel-position)))
          (first-x (car first-x-y))
          (first-y (cdr first-x-y))
          (exitfun nil)
@@ -1114,7 +1117,9 @@ mouse-drag-frame-resize
           (lambda (event)
             (interactive "e")
             (when (consp event)
-              (let* ((last-x-y (mouse-absolute-pixel-position))
+              (let* ((last-x-y (if tty
+				   (mouse-position-in-root-frame)
+				 (mouse-absolute-pixel-position)))
 		     (last-x (car last-x-y))
 		     (last-y (cdr last-x-y))
 		     (left (- last-x first-x))
@@ -1223,10 +1228,13 @@ mouse-drag-frame-move
          (parent-bottom (and parent-edges (nth 3 parent-edges)))
 	 ;; Initial "first" mouse position.  While dragging we base all
 	 ;; calculations against that position.
-	 (first-x-y (mouse-absolute-pixel-position))
-         (first-x (car first-x-y))
-         (first-y (cdr first-x-y))
-         ;; `snap-width' (maybe also a yet to be provided `snap-height')
+	 (tty (tty-type frame))
+	 (first-x-y (if tty
+			(mouse-position-in-root-frame)
+		      (mouse-absolute-pixel-position)))
+	 (first-x (car first-x-y))
+	 (first-y (cdr first-x-y))
+	 ;; `snap-width' (maybe also a yet to be provided `snap-height')
          ;; could become floats to handle proportionality wrt PARENT.
          ;; We don't do any checks on this parameter so far.
          (snap-width (frame-parameter frame 'snap-width))
@@ -1242,7 +1250,9 @@ mouse-drag-frame-move
           (lambda (event)
             (interactive "e")
             (when (consp event)
-              (let* ((last-x-y (mouse-absolute-pixel-position))
+              (let* ((last-x-y (if tty
+				   (mouse-position-in-root-frame)
+				 (mouse-absolute-pixel-position)))
 		     (last-x (car last-x-y))
 		     (last-y (cdr last-x-y))
 		     (left (- last-x first-x))
@@ -1373,7 +1383,8 @@ mouse-drag-frame-move
                                    (- parent-bottom native-height))
                             (min (max top parent-top)
                                  (- parent-bottom par))))))
-                ;; Use `modify-frame-parameters' since `left' and `top'
+
+		;; Use 'modify-frame-parameters' since 'left' and 'top'
                 ;; may want to move FRAME out of its PARENT.
                 (modify-frame-parameters frame `((left . (+ ,left)) (top . (+ ,top))))))))
 	 (old-track-mouse track-mouse))
diff --git a/src/frame.c b/src/frame.c
index cac9b0a4acd..b9a69f9c1b1 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -1449,17 +1449,38 @@ get_future_frame_param (Lisp_Object parameter,
 #endif
 
 int
-tty_child_pos_param (struct frame *child, Lisp_Object key,
-		     Lisp_Object params, int dflt)
+tty_child_pos_param (struct frame *f, Lisp_Object key,
+		     Lisp_Object params, int pos)
 {
+  struct frame *p = XFRAME (f->parent_frame);
   Lisp_Object val = Fassq (key, params);
+
   if (CONSP (val))
     {
       val = XCDR (val);
-      if (FIXNUMP (val))
-	return XFIXNUM (val);
+
+      if (TYPE_RANGED_FIXNUMP (int, val))
+	{
+	  pos = XFIXNUM (val);
+
+	  if (pos < 0)
+	    /* Handle negative value. */
+	    pos = max (EQ (key, Qtop)
+		       ? p->pixel_height - f->pixel_height - pos
+		       : p->pixel_width - f->pixel_width - pos,
+		       0);
+	}
+      else if (CONSP (val) && EQ (XCAR (val), Qplus)
+	       && CONSP (XCDR (val))
+	       && TYPE_RANGED_FIXNUMP (int, XCAR (XCDR (val))))
+	pos = XFIXNUM (XCAR (XCDR (val)));
+      else if (CONSP (val) && EQ (XCAR (val), Qminus)
+	       && CONSP (XCDR (val))
+	       && RANGED_FIXNUMP (-INT_MAX, XCAR (XCDR (val)), INT_MAX))
+	pos = - XFIXNUM (XCAR (XCDR (val)));
     }
-  return dflt;
+
+  return pos;
 }
 
 int
@@ -2838,7 +2859,7 @@ DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
   return delete_frame (frame, !NILP (force) ? Qt : Qnil);
 }
 
-#ifdef HAVE_WINDOW_SYSTEM
+
 /**
  * frame_internal_border_part:
  *
@@ -2861,7 +2882,11 @@ DEFUN ("delete-frame", Fdelete_frame, Sdelete_frame, 0, 2, "",
 enum internal_border_part
 frame_internal_border_part (struct frame *f, int x, int y)
 {
-  int border = FRAME_INTERNAL_BORDER_WIDTH (f);
+  int border = (FRAME_INTERNAL_BORDER_WIDTH (f)
+		? FRAME_INTERNAL_BORDER_WIDTH (f)
+		: (is_tty_child_frame (f) && !FRAME_UNDECORATED (f))
+		? 1
+		: 0);
   int offset = FRAME_LINE_HEIGHT (f);
   int width = FRAME_PIXEL_WIDTH (f);
   int height = FRAME_PIXEL_HEIGHT (f);
@@ -2930,7 +2955,7 @@ frame_internal_border_part (struct frame *f, int x, int y)
 
   return part;
 }
-#endif
+
 
 /* Return mouse position in character cell units.  */
 
@@ -6478,6 +6503,37 @@ DEFUN ("frame-pointer-visible-p", Fframe_pointer_visible_p,
   return decode_any_frame (frame)->pointer_invisible ? Qnil : Qt;
 }
 
+DEFUN ("mouse-position-in-root-frame", Fmouse_position_in_root_frame,
+       Smouse_position_in_root_frame, 0, 0, 0,
+       doc: /* Return mouse position in selected frame's root frame.
+
+Return the position of `mouse-position' in coordinates of the root frame
+of the frame returned by 'mouse-position'.  */)
+  (void)
+{
+  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
+    {
+      f = FRAME_PARENT_FRAME (f);
+
+      while (f)
+	{
+	  x = x + f->left_pos;
+	  y = y + f->top_pos;
+	  f = FRAME_PARENT_FRAME (f);
+	}
+
+      return Fcons (make_fixnum (x), make_fixnum (y));
+    }
+}
+
 DEFUN ("frame--set-was-invisible", Fframe__set_was_invisible,
        Sframe__set_was_invisible, 2, 2, 0,
        doc: /* Set FRAME's was-invisible flag if WAS-INVISIBLE is non-nil.
@@ -7250,6 +7306,7 @@ focus (where a frame immediately loses focus when it's left by the mouse
   defsubr (&Sframe_position);
   defsubr (&Sset_frame_position);
   defsubr (&Sframe_pointer_visible_p);
+  defsubr (&Smouse_position_in_root_frame);
   defsubr (&Sframe__set_was_invisible);
   defsubr (&Sframe_window_state_change);
   defsubr (&Sset_frame_window_state_change);
diff --git a/src/frame.h b/src/frame.h
index fea8baa7332..6bacf2a3c49 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -31,6 +31,19 @@ #define EMACS_FRAME_H
   vertical_scroll_bar_right
 };
 
+enum internal_border_part
+  {
+   INTERNAL_BORDER_NONE,
+   INTERNAL_BORDER_LEFT_EDGE,
+   INTERNAL_BORDER_TOP_LEFT_CORNER,
+   INTERNAL_BORDER_TOP_EDGE,
+   INTERNAL_BORDER_TOP_RIGHT_CORNER,
+   INTERNAL_BORDER_RIGHT_EDGE,
+   INTERNAL_BORDER_BOTTOM_RIGHT_CORNER,
+   INTERNAL_BORDER_BOTTOM_EDGE,
+   INTERNAL_BORDER_BOTTOM_LEFT_CORNER,
+  };
+
 #ifdef HAVE_WINDOW_SYSTEM
 
 enum fullscreen_type
@@ -53,19 +66,6 @@ #define EMACS_FRAME_H
   z_group_above_suspended,
 };
 
-enum internal_border_part
-  {
-   INTERNAL_BORDER_NONE,
-   INTERNAL_BORDER_LEFT_EDGE,
-   INTERNAL_BORDER_TOP_LEFT_CORNER,
-   INTERNAL_BORDER_TOP_EDGE,
-   INTERNAL_BORDER_TOP_RIGHT_CORNER,
-   INTERNAL_BORDER_RIGHT_EDGE,
-   INTERNAL_BORDER_BOTTOM_RIGHT_CORNER,
-   INTERNAL_BORDER_BOTTOM_EDGE,
-   INTERNAL_BORDER_BOTTOM_LEFT_CORNER,
-  };
-
 #ifdef NS_IMPL_COCOA
 enum ns_appearance_type
   {
@@ -1875,7 +1875,6 @@ #define EMACS_CLASS "Emacs"
 extern void set_frame_menubar (struct frame *f, bool deep_p);
 extern void frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y);
 extern void free_frame_menubar (struct frame *);
-extern enum internal_border_part frame_internal_border_part (struct frame *f, int x, int y);
 
 #if defined HAVE_X_WINDOWS
 extern void x_wm_set_icon_position (struct frame *, int, int);
@@ -1901,6 +1900,8 @@ gui_set_bitmap_icon (struct frame *f)
 #endif /* !HAVE_NS */
 #endif /* HAVE_WINDOW_SYSTEM */
 
+extern enum internal_border_part frame_internal_border_part (struct frame *f,
+							     int x, int y);
 extern bool frame_ancestor_p (struct frame *af, struct frame *df);
 
 INLINE void
diff --git a/src/keyboard.c b/src/keyboard.c
index 2d8c45c05ee..234969a16f0 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -5552,7 +5552,6 @@ #define ISO_FUNCTION_KEY_OFFSET 0xfe00
   SYMBOL_INDEX (Qrightmost), SYMBOL_INDEX (Qend_scroll), SYMBOL_INDEX (Qratio)
 };
 
-#ifdef HAVE_WINDOW_SYSTEM
 /* An array of symbol indexes of internal border parts, indexed by an enum
    internal_border_part value.  Note that Qnil corresponds to
    internal_border_part_none and should not appear in Lisp events.  */
@@ -5563,7 +5562,6 @@ #define ISO_FUNCTION_KEY_OFFSET 0xfe00
   SYMBOL_INDEX (Qbottom_right_corner), SYMBOL_INDEX (Qbottom_edge),
   SYMBOL_INDEX (Qbottom_left_corner)
 };
-#endif
 
 /* A vector, indexed by button number, giving the down-going location
    of currently depressed buttons, both scroll bar and non-scroll bar.
@@ -5598,6 +5596,94 @@ #define ISO_FUNCTION_KEY_OFFSET 0xfe00
 
 static int double_click_count;
 
+enum frame_border_side
+{
+  ON_LEFT,
+  ON_TOP,
+  ON_RIGHT,
+  ON_BOTTOM,
+  ON_NONE
+};
+
+/* Handle make_lispy_event when a tty child frame's decorations shall be
+   used in lieu of internal borders.  R denotes the root frame under
+   investigation, MX and MY are the positions of the mouse relative to
+   R.  WINDOW_OR_FRAME denotes the frame previously reported as the
+   frame under (MX, MY).  Note: The decorations of a child frame are
+   always drawn outside the child frame, so WINDOW_OR_FRAME is certainly
+   not the frame we are looking for.  Neither is R.  A candidate frame
+   is any frame but WINDOW_OR_FRAME and R whose root is R, which is not
+   decorated and has a 'drag-internal-border' parameter.  If we find a
+   suitable frame, set WINDOW_OR_FRAME to it and POSN to the part of the
+   internal border corresponding to (MX, MY) on the frame found.  */
+
+static void
+make_lispy_tty_position (struct frame *r, int mx, int my,
+			 Lisp_Object *window_or_frame, Lisp_Object *posn)
+{
+  enum frame_border_side side = ON_NONE;
+  /* WINDOW_OR_FRAME is the window where (MX, MY) is located: Its frame
+     N is certainly not the frame we can drag because the position is
+     inside.  It can be an arbitrary frame whose root is R.  */
+  struct frame *n = WINDOW_XFRAME (XWINDOW (*window_or_frame));
+  struct frame *f = NULL;
+  Lisp_Object tail, frame;
+  int ix, iy = 0;
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+
+      int left = f->left_pos;
+      int top = f->top_pos;
+      int right = left + f->pixel_width;
+      int bottom = top + f->pixel_height;
+
+      if (root_frame (f) == r && f != n && f != r
+	  && !FRAME_UNDECORATED (f)
+	  && !NILP (get_frame_param (f, Qdrag_internal_border)))
+	{
+	  if (left == mx + 1 && my >= top && my <= bottom)
+	    {
+	      side = ON_LEFT;
+	      ix = 0;
+	      iy = my - top + 1;
+	      break;
+	    }
+	  else if (right == mx && my >= top && my <= bottom)
+	    {
+	      side = ON_RIGHT;
+	      ix = f->pixel_width - 1;
+	      iy = my - top + 1;
+	      break;
+	    }
+	  else if (top == my + 1 && mx >= left && mx <= right)
+	    {
+	      side = ON_TOP;
+	      ix = mx - left + 1;
+	      iy = 0;
+	      break;
+	    }
+	  else if (bottom == my && mx >= left && mx <= right)
+	    {
+	      side = ON_BOTTOM;
+	      ix = mx - left + 1;
+	      iy = f->pixel_height - 1;
+	      break;
+	    }
+	}
+    }
+
+  if (side != ON_NONE)
+    {
+      enum internal_border_part part
+	= frame_internal_border_part (f, ix, iy);
+
+      XSETFRAME (*window_or_frame, f);
+      *posn = builtin_lisp_symbol (internal_border_parts[part]);
+    }
+}
+
 /* X and Y are frame-relative coordinates for a click or wheel event.
    Return a Lisp-style event list.  */
 
@@ -5676,7 +5762,14 @@ make_lispy_position (struct frame *f, Lisp_Object x, Lisp_Object y,
       window_or_frame = Qnil;	/* see above */
     }
 
-  if (WINDOWP (window_or_frame))
+  if (WINDOWP (window_or_frame) && is_tty_frame (f)
+      && (is_tty_root_frame_with_visible_child (f)
+	  || is_tty_child_frame (f)))
+    make_lispy_tty_position (root_frame (f), mx, my, &window_or_frame, &posn);
+
+  if (!NILP (posn))
+    ;
+  else if (WINDOWP (window_or_frame))
     {
       /* It's a click in window WINDOW at frame coordinates (X,Y)  */
       struct window *w = XWINDOW (window_or_frame);
@@ -5879,9 +5972,7 @@ make_lispy_position (struct frame *f, Lisp_Object x, Lisp_Object y,
       xret = mx;
       yret = my;
 
-#ifdef HAVE_WINDOW_SYSTEM
-      if (FRAME_WINDOW_P (f)
-	  && FRAME_LIVE_P (f)
+      if (FRAME_LIVE_P (f)
 	  && NILP (posn)
 	  && FRAME_INTERNAL_BORDER_WIDTH (f) > 0
 	  && !NILP (get_frame_param (f, Qdrag_internal_border)))
@@ -5891,7 +5982,6 @@ make_lispy_position (struct frame *f, Lisp_Object x, Lisp_Object y,
 
 	  posn = builtin_lisp_symbol (internal_border_parts[part]);
 	}
-#endif
     }
   else
     {
diff --git a/src/keymap.c b/src/keymap.c
index 5691b34c40d..c98b83db68d 100644
--- a/src/keymap.c
+++ b/src/keymap.c
@@ -1652,7 +1652,8 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
        doc: /* Return a list of the currently active keymaps.
 OLP if non-nil indicates that we should obey `overriding-local-map' and
 `overriding-terminal-local-map'.  POSITION can specify a click position
-like in the respective argument of `key-binding'.  */)
+like in the respective argument of `key-binding' or a live window which
+means to return the active maps for that window's buffer.  */)
   (Lisp_Object olp, Lisp_Object position)
 {
   specpdl_ref count = SPECPDL_INDEX ();
@@ -1682,6 +1683,16 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
 	  set_buffer_internal (XBUFFER (XWINDOW (window)->contents));
 	}
     }
+  else if (WINDOW_LIVE_P (position))
+    {
+      if (BUFFERP (XWINDOW (position)->contents)
+	  && XBUFFER (XWINDOW (position)->contents) != current_buffer)
+	{
+	  /* See comment above.  */
+	  record_unwind_current_buffer ();
+	  set_buffer_internal (XBUFFER (XWINDOW (position)->contents));
+	}
+    }
 
   if (!NILP (olp)
       /* The doc said that overriding-terminal-local-map should
diff --git a/src/term.c b/src/term.c
index f307d709316..cf48fa5a734 100644
--- a/src/term.c
+++ b/src/term.c
@@ -2620,7 +2620,7 @@ tty_frame_at (int x, int y, int *cx, int *cy)
 }
 
 DEFUN ("tty-frame-at", Ftty_frame_at, Stty_frame_at, 2, 2, 0,
-       doc : /* Return tty frame containing absolute pixel position (X, Y).
+       doc: /* Return tty frame containing absolute pixel position (X, Y).
 Value is nil if no frame found.  Otherwise it is a list (FRAME CX CY),
 where FRAME is the frame containing (X, Y) and CX and CY are X and Y
 relative to FRAME.  */)
diff --git a/src/xdisp.c b/src/xdisp.c
index 36e82f873ab..7d51312f5c7 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -1147,7 +1147,7 @@ #define THIN_SPACE_WIDTH 1
 				 struct glyph_matrix *, ptrdiff_t, ptrdiff_t,
 				 int, int);
 static bool cursor_row_fully_visible_p (struct window *, bool, bool, bool);
-static bool update_menu_bar (struct frame *, bool, bool);
+static bool update_menu_bar (struct frame *, bool, bool, struct window *);
 static bool try_window_reusing_current_matrix (struct window *);
 static int try_window_id (struct window *);
 static void maybe_produce_line_number (struct it *);
@@ -14062,13 +14062,32 @@ prepare_menu_bars (void)
       /* True means that update_menu_bar has run its hooks
 	 so any further calls to update_menu_bar shouldn't do so again.  */
       bool menu_bar_hooks_run = false;
+      struct window *sw = XWINDOW (selected_window);
+      struct frame *sf = WINDOW_XFRAME (sw);
+      struct frame *rf = NULL;
+
+      if (FRAME_PARENT_FRAME (sf) && !FRAME_WINDOW_P (sf)
+	  && FRAME_MENU_BAR_LINES (sf) == 0
+	  && FRAME_MENU_BAR_LINES (rf = root_frame (sf)) != 0
+	  && NILP (Fdefault_value (Qtty_menu_open_use_tmm)))
+	/* If the selected window's frame is a tty child frame without
+	   menu bar, that frame's root frame has a menu bar and
+	   'tty-menu-open-use-tmm' is nil, update the menu bar of the
+	   root frame from the selected window.  */
+	sf = rf;
+      else
+	{
+	  sf = NULL;
+	  sw = NULL;
+	}
 
       record_unwind_save_match_data ();
 
       FOR_EACH_FRAME (tail, frame)
 	{
 	  struct frame *f = XFRAME (frame);
-	  struct window *w = XWINDOW (FRAME_SELECTED_WINDOW (f));
+	  struct window *w
+	    = sf == f ? sw : XWINDOW (FRAME_SELECTED_WINDOW (f));
 
 	  /* Ignore tooltip frame.  */
 	  if (FRAME_TOOLTIP_P (f))
@@ -14080,8 +14099,8 @@ prepare_menu_bars (void)
 	      && !XBUFFER (w->contents)->text->redisplay)
 	    continue;
 
-	  if (!FRAME_PARENT_FRAME (f))
-	    menu_bar_hooks_run = update_menu_bar (f, false, menu_bar_hooks_run);
+	  menu_bar_hooks_run
+	    = update_menu_bar (f, false, menu_bar_hooks_run, w);
 
 	  update_tab_bar (f, false);
 #ifdef HAVE_WINDOW_SYSTEM
@@ -14093,10 +14112,21 @@ prepare_menu_bars (void)
     }
   else
     {
-      struct frame *sf = SELECTED_FRAME ();
+      struct window *sw = XWINDOW (selected_window);
+      struct frame *sf = WINDOW_XFRAME (sw);
+      struct frame *rf = NULL;
 
-      if (!FRAME_PARENT_FRAME (sf))
-	update_menu_bar (sf, true, false);
+      if (FRAME_PARENT_FRAME (sf) && !FRAME_WINDOW_P (sf)
+	  && FRAME_MENU_BAR_LINES (sf) == 0
+	  && FRAME_MENU_BAR_LINES (rf = root_frame (sf)) != 0
+	  && NILP (Fdefault_value (Qtty_menu_open_use_tmm)))
+	/* If the selected window's frame is a tty child frame without
+	   menu bar, that frame's root frame has a menu bar and
+	   'tty-menu-open-use-tmm' is nil, update the menu bar of the
+	   root frame from the selected window.  */
+	sf = rf;
+
+      update_menu_bar (sf, true, false, sw);
 
       update_tab_bar (sf, true);
 #ifdef HAVE_WINDOW_SYSTEM
@@ -14118,20 +14148,14 @@ prepare_menu_bars (void)
    updated value of this flag, to pass to the next call.  */
 
 static bool
-update_menu_bar (struct frame *f, bool save_match_data, bool hooks_run)
+update_menu_bar (struct frame *f, bool save_match_data, bool hooks_run, struct window *w)
 {
-  Lisp_Object window;
-  struct window *w;
-
   /* If called recursively during a menu update, do nothing.  This can
      happen when, for instance, an activate-menubar-hook causes a
      redisplay.  */
   if (inhibit_menubar_update)
     return hooks_run;
 
-  window = FRAME_SELECTED_WINDOW (f);
-  w = XWINDOW (window);
-
   if (FRAME_WINDOW_P (f)
       ?
 #ifdef HAVE_EXT_MENU_BAR
@@ -21088,24 +21112,33 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 
   /* When we reach a frame's selected window, redo the frame's menu
      bar, tool bar, tab-bar, and the frame's title.  */
-  if (update_mode_line
-      && EQ (FRAME_SELECTED_WINDOW (f), window))
+  if (update_mode_line && EQ (FRAME_SELECTED_WINDOW (f), window))
     {
-      bool redisplay_menu_p;
-
       if (FRAME_WINDOW_P (f))
 	{
 #ifdef HAVE_EXT_MENU_BAR
-	  redisplay_menu_p = FRAME_EXTERNAL_MENU_BAR (f);
+	  if (FRAME_EXTERNAL_MENU_BAR (f))
+	    display_menu_bar (w);
 #else
-	  redisplay_menu_p = FRAME_MENU_BAR_LINES (f) > 0;
+	  if (FRAME_MENU_BAR_LINES (f) > 0)
+	    display_menu_bar (w);
 #endif
 	}
       else
-        redisplay_menu_p = FRAME_MENU_BAR_LINES (f) > 0;
+	{
+	  struct frame *rf = NULL;
 
-      if (redisplay_menu_p)
-        display_menu_bar (w);
+	  if (FRAME_PARENT_FRAME (f)
+	      && FRAME_MENU_BAR_LINES (f) == 0
+	      && FRAME_MENU_BAR_LINES (rf = root_frame (f)) != 0
+	      && NILP (Fdefault_value (Qtty_menu_open_use_tmm)))
+	    /* If F is a tty child frame without menu bar, that frame's root
+	       frame has a menu bar and 'tty-menu-open-use-tmm' is nil,
+	       display the menu bar of the root frame's selected window.  */
+	    display_menu_bar (XWINDOW (FRAME_SELECTED_WINDOW (rf)));
+	  else if (FRAME_MENU_BAR_LINES (f) > 0)
+	    display_menu_bar (w);
+	}
 
 #ifdef HAVE_WINDOW_SYSTEM
       if (FRAME_WINDOW_P (f))
@@ -27347,10 +27380,19 @@ display_tty_menu_item (const char *item_text, int width, int face_id,
 {
   struct it it;
   struct frame *f = SELECTED_FRAME ();
-  struct window *w = XWINDOW (f->selected_window);
   struct glyph_row *row;
   size_t item_len = strlen (item_text);
 
+  struct frame *rf = NULL;
+
+  if (FRAME_PARENT_FRAME (f) && !FRAME_WINDOW_P (f)
+      && FRAME_MENU_BAR_LINES (f) == 0
+      && FRAME_MENU_BAR_LINES (rf = root_frame (f)) != 0
+      && NILP (Fdefault_value (Qtty_menu_open_use_tmm)))
+    f = rf;
+
+  struct window *w = XWINDOW (f->selected_window);
+
   eassert (FRAME_TERMCAP_P (f));
 
   /* Don't write beyond the matrix's last row.  This can happen for
@@ -38466,6 +38508,7 @@ syms_of_xdisp (void)
   DEFSYM (Qnhdrag, "nhdrag");
   DEFSYM (Qvdrag, "vdrag");
   DEFSYM (Qhourglass, "hourglass");
+  DEFSYM (Qtty_menu_open_use_tmm, "tty-menu-open-use-tmm");
 }