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

From: martin rudalics <[email-masked]>
Date: Thu, 30 Jan 2025 19:00:01 +0100

>>>> Also when I want to move a child frame to the left of its parent via
 >>>>
 >>>>     (modify-frame-parameters nil '((left . 0)))
 >>>>
 >>>> the left border disappears.  Is that intended?
 >>>
 >>> If I understand that correctly, then probably yes. The borders are drawn
 >>> around the frame, so the left border is at left - 1, the right at left +
 >>> width + 1, and so on.
 >>
 >> I see.
 >>
 >>> Whatever is outside of the terminal is clipped.

Troublesome.  Please revise that.  When I want to resize a child frame
with the mouse, I have to drag its internal border.  But as it stands,
the internal border is part of the underlying or root frame and the
whole make_lispy_position mechanism is broken on ttys.

 > _Much_ nicer than the default IMO. I still wished Emacs would do that by
 > default, but here were objections.

Looks good.  But _where_ on earth (that is, in the code) do you that and
how is it related to the width of the internal border?

I attach my latest achievements both in the menu bar and mouse drag
child frame departments.  Menu bars now accept navigation with the
keyboard which was pretty non-trivial to do.  Mouse dragging works with
header and mode lines - the attached tty-child-frames.el should provide
the necessary ingredients via C-l and M-l.

One bug I noted now is the following.  Do C-l and M-l and drag the
yellow and orange frames somehow as in before.png with the cursor in the
yellow frame right before the left edge of the orange frame.  Do C-f -
the cursor appears on top of the left edge of the orange frame as in
middle.png.  Another C-f moves it into the orange frame as in after.png.

martin
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 28ab60f1d2d..f1f67cc0bfc 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 6fe691693b4..ea68909146e 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 4f3c1efc4a7..c84da3bb498 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
@@ -6478,6 +6499,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 +7302,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/keyboard.c b/src/keyboard.c
index 850b5e06480..8de61f8c02a 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.
@@ -5879,9 +5877,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 +5887,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 99291e72b3f..6cbe2febdd1 100644
--- a/src/keymap.c
+++ b/src/keymap.c
@@ -1659,7 +1659,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 ();
@@ -1689,6 +1690,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/xdisp.c b/src/xdisp.c
index a801caae06f..b44ff3d562c 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
@@ -38465,6 +38507,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");
 }