emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/elisp-benchmarks 94acd95591: * benchmarks/elb-smie.el:


From: Stefan Monnier
Subject: [elpa] externals/elisp-benchmarks 94acd95591: * benchmarks/elb-smie.el: New benchmark
Date: Tue, 8 Feb 2022 09:34:05 -0500 (EST)

branch: externals/elisp-benchmarks
commit 94acd955912811d67ac0174a1ab08b5547927621
Author: Stefan Monnier <monnier@iro.umontreal.ca>
Commit: Stefan Monnier <monnier@iro.umontreal.ca>

    * benchmarks/elb-smie.el: New benchmark
    
    * resources/xmenu.c: New file.
    * elisp-benchmarks.el (elb-bench-directory): Use `expand-file-name` and
    use `macroexp-file-name` when available.
---
 benchmarks/elb-smie.el |  901 +++++++++++++++++
 elisp-benchmarks.el    |    7 +-
 resources/xmenu.c      | 2512 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 3418 insertions(+), 2 deletions(-)

diff --git a/benchmarks/elb-smie.el b/benchmarks/elb-smie.el
new file mode 100644
index 0000000000..45387f801c
--- /dev/null
+++ b/benchmarks/elb-smie.el
@@ -0,0 +1,901 @@
+;;; elb-smie.el --- C major mode based on SMIE  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015-2022  Free Software Foundation, Inc.
+
+;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
+;; Version: 1.1
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Copy of the 2021 version of `sm-c-mode'.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'smie)
+
+(defgroup elb-smie-mode nil
+  "Major mode to edit C code, based on SMIE."
+  :group 'programming)
+
+(defcustom elb-smie-indent-basic 2
+  "Basic step of indentation.
+Typically 2 for GNU style and `tab-width' for Linux style."
+  :type 'integer)
+
+(defcustom elb-smie-indent-braces t
+  "If nil, braces in if/while/... are aligned with the if/while/...
+Else, they're indented by `elb-smie-indent-basic' columns.
+For braces placed at the end of lines (which SMIE calls \"hanging\"), it makes
+no difference."
+  :type 'boolean)
+
+;;; Handling CPP directives.
+
+(defsubst elb-smie--cpp-inside-p (ppss)
+  (eq 2 (nth 7 ppss)))
+
+(eval-and-compile
+  (defconst elb-smie--cpp-regexp "^[ \t]*\\(\\(#\\)[ \t]*\\([a-z]+\\)\\)"))
+
+(defconst elb-smie--cpp-syntax-table
+  (let ((st (make-syntax-table)))
+    (modify-syntax-entry ?/ ". 124" st)
+    (modify-syntax-entry ?* ". 23b" st)
+    (modify-syntax-entry ?\n ">" st)
+    st))
+
+(defun elb-smie--cpp-goto-end (ppss &optional limit)
+  (cl-assert (elb-smie--cpp-inside-p ppss))
+  (let (found)
+    (while
+        (and (setq found (re-search-forward "\\(?:\\\\\\\\\\)*\n" limit 'move))
+             ;; We could also check (nth 5 ppss) to figure out if we're
+             ;; after a backslash, but this is a very common case, so it's good
+             ;; to avoid calling parse-partial-sexp for that.
+             (or (eq ?\\ (char-before (match-beginning 0)))
+                 (with-syntax-table elb-smie--cpp-syntax-table
+                   (nth 4 (parse-partial-sexp (1+ (nth 8 ppss)) (point)))))))
+    found))
+
+(defvar syntax-ppss-cache)
+(defvar syntax-ppss-last)
+
+(defun elb-smie--cpp-fontify-syntactically (ppss)
+  ;; FIXME: ¡¡BIG UGLY HACK!!
+  ;; Copied from font-lock.el's font-lock-fontify-syntactically-region.
+  (cl-assert (> (point) (nth 8 ppss)))
+  (save-excursion
+    (save-restriction
+      (elb-smie--cpp-goto-end ppss)
+      (narrow-to-region (1+ (nth 8 ppss)) (point))
+      ;; FIXME: We should add some "with-local-syntax-ppss" macro to
+      ;; encapsulate this.
+      (let ((syntax-propertize-function nil)
+            (syntax-ppss-cache nil)
+            (syntax-ppss-last nil))
+        (font-lock-fontify-syntactically-region (point-min) (point-max))))))
+
+(defun elb-smie--cpp-syntax-propertize (end)
+  (let ((ppss (syntax-ppss))
+        found)
+    (when (elb-smie--cpp-inside-p ppss)
+      (while
+          (and (setq found (re-search-forward "\\(\\\\\\\\\\)*\n" end 'move))
+               (or (eq ?\\ (char-before (match-beginning 0)))
+                   (with-syntax-table elb-smie--cpp-syntax-table
+                     (nth 4 (parse-partial-sexp (1+ (nth 8 ppss)) (point)))))))
+      (when found
+        (let* ((ppss-in
+                (save-excursion
+                  (parse-partial-sexp (1+ (nth 8 ppss)) (1- (point)))))
+               ;; Put the end before a closing //...\n comment so as to avoid
+               ;; a bug in back_comment.  The problem is that back_comment
+               ;; otherwise will see "// <...> <...> \n" and will consider the
+               ;; CPP pseudo-comments as nested within the //...\n comment.
+               (end (if (and (nth 4 ppss-in)        ;Inside a comment.
+                             (null (nth 7 ppss-in)) ;A style `a' comment.
+                             (memq (char-before (nth 8 ppss-in)) '(?\s ?\t)))
+                        (nth 8 ppss-in)
+                      (point))))
+          (put-text-property (1- end) end
+                             'syntax-table (string-to-syntax "> c")))))))
+
+;;;; Indenting CPP directives.
+
+(defcustom elb-smie-indent-cpp-basic 1
+  "Indent step for CPP directives.
+If non-zero, CPP directives are indented according to CPP depth.
+E.g. a #define nested within 2 #ifs will be turned into \"#  define\"."
+  :type 'integer)
+
+(defun elb-smie--cpp-prev (tok)
+  (let ((offset nil))
+    (while
+        (when (re-search-backward elb-smie--cpp-regexp nil t)
+          (pcase (cons tok (match-string 3))
+            (`(,_ . "endif") (elb-smie--cpp-prev "endif"))
+            ((or `(,(or "endif" "else" "elif") . ,(or "if" "ifdef" "ifndef"))
+                 `(,(or "else" "elif") . "elif"))
+             (setq offset 0))
+            (`(,(or "endif" "else" "elif") . ,_) nil)
+            (`(,_ . ,(or "if" "ifdef" "ifndef" "elif" "else"))
+             (setq offset elb-smie-indent-cpp-basic))
+            (_ (setq offset 0)))
+          (not offset)))
+    (when offset
+      (goto-char (match-beginning 3))
+      (+ offset (current-column)))))
+
+
+(defun elb-smie--cpp-indent-line (&optional _arg)
+  ;; FIXME: Also align the terminating \, if any.
+  (when (> elb-smie-indent-cpp-basic 0)
+    (let* ((pos (point-marker))
+           (beg)
+           (indent
+            (save-excursion
+              (forward-line 0)
+              (when (looking-at elb-smie--cpp-regexp)
+                (setq beg (match-beginning 3))
+                (or (elb-smie--cpp-prev (match-string 3)) 0)))))
+      (when indent
+        (let ((before (<= pos beg)))
+          (goto-char beg)
+          (unless (= (current-column) indent)
+            (skip-chars-backward " \t")
+            (delete-region (point)
+                           (progn (skip-chars-forward " \t") (point)))
+            (indent-to indent))
+          (unless before (goto-char pos)))))))
+
+;;;; Indenting inside CPP #define.
+
+(defconst elb-smie--cpp-smie-indent-functions
+  ;; FIXME: Don't just align line after #define with the "d"!
+  (mapcar
+   (lambda (f)
+     (cond
+      ((eq f #'smie-indent-comment-inside) 
#'elb-smie--cpp-indent-comment-inside)
+      ;; ((eq f #'smie-indent-exps) #'elb-smie--cpp-indent-exps)
+      (t f)))
+   (default-value 'smie-indent-functions)))
+
+(defun elb-smie--cpp-indent-comment-inside ()
+  (let ((ppss (syntax-ppss)))
+    (when (nth 4 ppss)
+      ;; Indicate where's the comment start.
+      `(noindent . ,(nth 8 ppss)))))
+
+(defun elb-smie--cpp-smie-indent ()
+  (let ((ppss (syntax-ppss)))
+    (cond
+     ((elb-smie--cpp-inside-p ppss)
+      (save-restriction
+        (narrow-to-region (nth 8 ppss) (point-max))
+        (let ((indent
+               (let ((smie-indent-functions 
elb-smie--cpp-smie-indent-functions)
+                     (syntax-ppss-cache nil)
+                     (syntax-ppss-last nil)
+                     (parse-sexp-lookup-properties nil))
+                 (smie-indent-calculate))))
+          (if (not (eq 'noindent (car-safe indent)))
+              (if (integerp indent)
+                  (max (funcall smie-rules-function :elem 'basic) indent)
+                indent)
+            ;; We can't just return `noindent' if we're inside a comment,
+            ;; because the indent.el code would then be similarly confused,
+            ;; thinking the `noindent' is because we're inside the cpp
+            ;; pseudo-comment, and would hence align the code with the content
+            ;; of the psuedo-comment rather than the nested real comment!
+            ;;
+            ;; FIXME: Copy&paste from indent--default-inside-comment.
+            ;; FIXME: This will always re-indent inside these comments, even
+            ;; during indent-region.
+            (save-excursion
+              (forward-line -1)
+              (skip-chars-forward " \t")
+              (when (< (1- (point)) (cdr indent) (line-end-position))
+                (goto-char (cdr indent))
+                (when (looking-at comment-start-skip)
+                  (goto-char (match-end 0))))
+              (current-column))))))
+
+     ((equal (syntax-after (point)) (string-to-syntax "< c")) 0)
+     )))
+
+;;; Syntax table
+
+(defvar elb-smie-mode-syntax-table
+  (let ((st (make-syntax-table)))
+    (modify-syntax-entry ?/ ". 124" st)
+    (modify-syntax-entry ?* ". 23b" st)
+    (modify-syntax-entry ?\n ">" st)
+    (modify-syntax-entry ?\" "\"" st)
+    (modify-syntax-entry ?\' "\"" st)
+    (modify-syntax-entry ?= "." st)
+    (modify-syntax-entry ?+ "." st)
+    (modify-syntax-entry ?- "." st)
+    (modify-syntax-entry ?< "." st)
+    (modify-syntax-entry ?> "." st)
+    st))
+
+(defun elb-smie-syntax-propertize (start end)
+  (goto-char start)
+  (elb-smie--cpp-syntax-propertize end)
+  (funcall
+   (syntax-propertize-rules
+    (elb-smie--cpp-regexp
+     (2 (prog1 "< c"
+          (when (and (equal (match-string 3) "include")
+                     (looking-at "[ \t]*\\(<\\)[^>\n]*\\(>\\)"))
+            (put-text-property (match-beginning 1) (match-end 1)
+                               'syntax-table (string-to-syntax "|"))
+            (put-text-property (match-beginning 2) (match-end 2)
+                               'syntax-table (string-to-syntax "|")))
+          (elb-smie--cpp-syntax-propertize end))))
+    ;; Handle // comments that span multiple lines via \\\n!
+    ("\\\\\\(\n\\)"
+     (1 (let ((ppss (save-excursion (syntax-ppss (match-beginning 0)))))
+          (when (and (nth 4 ppss)        ;Within a comment
+                     (null (nth 7 ppss)) ;Within a // comment
+                     (save-excursion     ;The \ is not itself escaped
+                       (goto-char (match-beginning 0))
+                       (zerop (mod (skip-chars-backward "\\\\") 2))))
+            (string-to-syntax "."))))))
+   (point) end))
+
+(defun elb-smie-syntactic-face-function (ppss)
+  (if (elb-smie--cpp-inside-p ppss)
+      (prog1 nil (elb-smie--cpp-fontify-syntactically ppss))
+    (funcall (default-value 'font-lock-syntactic-face-function) ppss)))
+
+;;; SMIE support
+
+(defconst elb-smie-paren-block-keywords '("if" "while" "for" "switch"))
+
+(defconst elb-smie-smie-precedence-table
+  '((assoc ";")
+    ;; Compiled from https://en.wikipedia.org/wiki/Operators_in_C_and_C++.
+    (assoc ",")                         ;1
+    ;; (nonassoc "throw")
+    (nonassoc "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|=") ;2
+    ;; (nonassoc "?" ":") ;; Better handle it in the BNF.
+    (assoc "||")                        ;3
+    (assoc "&&")                        ;4
+    (assoc "|")                         ;5
+    (assoc "^")                         ;6
+    ;; (assoc "&") ;; Binary and.  Confused with address-of.
+    (nonassoc "==" "!=")                ;7
+    (nonassoc "<" "<=" ">" ">=")        ;8
+    (nonassoc "<<" ">>")                ;9
+    (assoc "+" "-")                     ;10
+    (assoc "/" "* mult" "%")            ;11
+    ;; (nonassoc ".*" "->*")            ;12   ;; Only C++
+    ;; (nonassoc "++" "--" "+" "-" "!" "~" "(type)" "*" "&"
+    ;;          "sizeof" "new" "delete");13  ;; All prefix.
+    (left "." "->") ;; "++" "--" suffixes, "()", "[]", "typeid", "*_cast". ;14
+    ;; (noassoc "::") ;; Only C++
+    ))
+
+(defconst elb-smie-smie-grammar
+  ;; `((:smie-closer-alist ("{" . "}")) ("{" (39) 0) ("}" 0 (40)) ("else" 27 
26) ("," 38 38) ("do" (41) 22) ("while" (42) 23) ("for" (43) 24) (";" 11 11) 
("if" (44) 25))
+  (let ((grm
+         (smie-prec2->grammar
+          (smie-merge-prec2s
+           (smie-bnf->prec2
+            '((decls ("typedef" decl) ("extern" decl)
+                     (decls ";" decls))
+              (decl)
+              (id)
+              (insts ("{" insts "}")
+                     (insts ";" insts)
+                     ("return" exp)
+                     ("goto" exp)
+                     (":label")
+                     ("case" subexp ": case")
+                     ("else" exp-if))
+              (exp-if ("if" exp) ("do" exp) ("while" exp) ("switch" exp) 
("for" exp)
+                      (exp))
+              (exp ("(" exp ")") (exp "," exp) (subexp "?" exp ":" exp))
+              (subexp (subexp "||" subexp))
+              ;; Some of the precedence table deals with pre/postfixes, which
+              ;; smie-precs->prec2 can't handle, so handle it here instead.
+              (exp11 (exp12) (exp11 "/" exp11))
+              (exp12 (exp13))           ;C++ only.
+              (exp13 (exp14) ("++ prefix" exp13) ("-- prefix" exp13)
+                     ("!" exp13) ("~" exp13) ("&" exp13) ("* deref" exp13))
+              (exp14 (id) (exp14 "++ postfix") (exp14 "-- postfix")
+                     (exp14 "->" id) (exp14 "." id)))
+            '((assoc ";") (assoc ",") (nonassoc "?" ":"))
+            elb-smie-smie-precedence-table)
+           (smie-precs->prec2 elb-smie-smie-precedence-table)
+           (smie-precs->prec2 '((nonassoc ";") (nonassoc ":")))))))
+    ;; SMIE gives (":label" 261 262), but really this could just as well be
+    ;; (":label" nil nil) because labels don't have any argument to their left
+    ;; or right.  They're like both openers and closers at the same time.
+    (mapcar (lambda (x)
+              (if (equal (car-safe x) ":label")
+                  ;; Rather than (":label" (n1) (n2)) we use
+                  ;; (":label" (n1) n2) because SMIE otherwise complains:
+                  ;; cl--assertion-failed((numberp (funcall op-forw 
toklevels)))
+                  ;; in smie-next-sexp.
+                  `(,(nth 0 x) (,(nth 1 x)) ,(nth 2 x)) x))
+            grm)))
+
+;; (defun elb-smie--:-discriminate ()
+;;   (save-excursion
+;;     (and (null (smie-backward-sexp))
+;;          (let ((prev (smie-indent-backward-token)))
+;;            (cond
+;;             ((equal prev "case" ) ": case")
+;;             ((member prev '(";" "{" "}")) ":-label")
+;;             (t ":"))))))
+
+(defconst elb-smie-smie-operator-regexp
+  (let ((ops '()))
+    (pcase-dolist (`(,token . ,_) elb-smie-smie-grammar)
+      (when (and (stringp token) (string-match "\\`[^ [:alnum:](){}]+" token))
+        (push (match-string 0 token) ops)))
+    (regexp-opt ops)))
+
+(defun elb-smie-smie-forward-token ()
+  (forward-comment (point-max))
+  (let ((tok (if (looking-at elb-smie-smie-operator-regexp)
+                 (progn (goto-char (match-end 0)) (match-string 0))
+               (smie-default-forward-token))))
+    (cond
+     ((and (equal tok "") (looking-at "\\\\\n"))
+      (goto-char (match-end 0))
+      (elb-smie-smie-forward-token))
+     ((member tok '(":" "*"))
+      (save-excursion (elb-smie-smie-backward-token)))
+     ((looking-at "[ \t]*:")
+      (if (not (equal (save-excursion (elb-smie-smie-forward-token)) ":label"))
+          tok
+        (looking-at "[ \t]*:")
+        (goto-char (match-end 0)) ":label"))
+     (t tok))))
+
+
+(defun elb-smie-smie-backward-token ()
+  (forward-comment (- (point)))
+  (let ((tok (if (looking-back elb-smie-smie-operator-regexp (- (point) 3) t)
+                 (progn (goto-char (match-beginning 0)) (match-string 0))
+               (smie-default-backward-token))))
+    (cond
+     ((and (equal tok "") (looking-at "\n"))
+      (let ((pos (point)))
+        (if (not (= 0 (mod (skip-chars-backward "\\\\") 2)))
+            (elb-smie-smie-backward-token)
+          (goto-char pos)
+          tok)))
+     ((equal tok "*") (elb-smie-smie--*-token))
+     ((equal tok ":")
+      (let ((pos1 (point))
+            (prev (elb-smie-smie-backward-token)))
+        (if (zerop (length prev))
+            (progn (goto-char pos1) tok)
+          (let ((pos2 (point)))
+            (pcase (car (smie-indent-backward-token))
+              ("case" (goto-char pos1) ": case")
+              ((or ";" "{" "}") (goto-char pos2) ":label")
+              (_ (goto-char pos1) tok))))))
+     (t tok))))
+
+(defun elb-smie--prev-token ()
+  (car (smie-indent-backward-token)))
+
+(defun elb-smie--else-to-if ()
+  (let ((pos (point)))
+    (unless (equal (elb-smie--prev-token) ";")
+      (goto-char pos))
+    (while
+        (pcase (smie-backward-sexp)
+          (`(,_ ,pos "if") (goto-char pos) nil) ;Found it!
+          (`(,_ ,_ ";") nil)                    ;Can't find it!
+          (`(,_ ,pos "else") (goto-char pos) (elb-smie--else-to-if) t)
+          (`(,_ ,pos "while")
+           (goto-char pos) (unless (elb-smie--while-to-do) (goto-char pos)) t)
+          (`(t . ,_) nil)               ;Can't find it!
+          (`(,_ ,pos . ,_) (goto-char pos) t)
+          (`nil t)))))
+
+(defun elb-smie--while-to-do ()
+  "Jump to the matching `do' and return non-nil, if any.  Return nil 
otherwise."
+  (pcase (elb-smie--prev-token)
+    ("}"
+     ;; The easy case!
+     (forward-char 1) (backward-sexp 1)
+     (equal (elb-smie--prev-token) "do"))
+    (";"
+     (let ((found-do nil))
+       (while
+           (pcase (smie-backward-sexp)
+             (`(,_ ,pos "do") (goto-char pos) (setq found-do t) nil)
+             (`(,_ ,_ ";") nil)         ;Can't find it!
+             (`(,_ ,pos "else") (goto-char pos) (elb-smie--else-to-if) t)
+             (`(,_ ,pos "while")
+              (goto-char pos) (unless (elb-smie--while-to-do) (goto-char pos)) 
t)
+             (`(t . ,_) nil)            ;Can't find it!
+             (`(,_ ,pos . ,_) (goto-char pos) t)
+             (`nil (or (not (looking-at "{"))
+                       (smie-rule-prev-p "=")))))
+       found-do))))
+
+(defun elb-smie--skip-labels (max)
+  (while
+      (let ((start (point)))
+        (pcase (elb-smie-smie-forward-token)
+          ("case"
+           (smie-forward-sexp "case")
+           (forward-comment (point-max))
+           (if (>= (point) max) (progn (goto-char start) nil)
+             t))
+          (":label"
+           (forward-comment (point-max))
+           (if (>= (point) max) (progn (goto-char start) nil)
+             t))
+          (_ (goto-char start) nil)))))
+
+(defun elb-smie--boi (&optional inner)
+  "Jump to the beginning-of-instruction.
+By default for things like nested ifs, it jumps to the outer if, but
+if INNER is non-nil, it stops at the innermost one."
+  (while
+      (let ((pos (point)))
+        (pcase (smie-backward-sexp)
+          (`(,_ ,_ ";") nil)            ;Found it!
+          (`(,_ ,pos "else") (goto-char pos) (elb-smie--else-to-if) t)
+          (`(,_ ,pos "while")
+           (goto-char pos) (unless (elb-smie--while-to-do) (goto-char pos)) t)
+          (`(,(pred numberp) ,pos . ,_) (goto-char pos) t)
+          ((or `nil `(nil . ,_))
+           (if (and (or (not (looking-at "{"))
+                        (smie-rule-prev-p "="))
+                    (not (bobp)))
+               t
+             (goto-char pos) nil))
+          (`(,_ ,_ ,(or "(" "{" "[")) nil) ;Found it!
+          (`(,_ ,pos ,(and tok
+                           (guard (when inner
+                                    (or (member tok 
elb-smie-paren-block-keywords)
+                                        (equal tok "do"))))))
+           (goto-char pos) nil) ;Found it!
+          (`(t ,(pred (eq (point-min))) . ,_) nil)
+          (`(,_ ,pos . ,_) (goto-char pos) t)))))
+
+;; (defun elb-smie--if-tail-to-head ()
+;;   (pcase (elb-smie--prev-token)
+;;     (")"
+;;      (forward-char 1) (backward-sexp 1)
+;;      (pcase (elb-smie--prev-token)
+;;        ("if" nil)
+;;        ((or "while" "for") (elb-smie--if-tail-to-head))))
+;;     ("do" (elb-smie--if-tail-to-head))))
+
+(defun elb-smie--boe (tok)
+  (let ((start (point))
+        (res (smie-backward-sexp tok)))
+    (when (member (nth 2 res) '("if" "while" "do" "for" "else"))
+      (when (member (nth 2 res) '("if" "for"))
+        (let ((forward-sexp-function nil))
+          (forward-sexp 1))
+        (forward-comment (point-max)))
+      (when (looking-at "{")
+        (let ((forward-sexp-function nil))
+          (forward-sexp 1))
+        (forward-comment (point-max)))
+      (if (> (point) start) (goto-char start)))))
+
+(defun elb-smie-smie--*-token ()
+  (save-excursion
+    (let ((pos (point)))
+      (pcase (car (smie-indent-backward-token))
+        (")"
+         ;; Can be a multiplication (as in "(a+b)*c"), or a deref
+         ;; (as in "if (stop) *a = 0;")
+         (if (and (goto-char (nth 1 (syntax-ppss)))
+                  (eq ?\( (char-after))
+                  (member (smie-default-backward-token) '("if" "for")))
+             "* deref"
+           "* mult"))
+        ("]" "* mult")                         ;Multiplication.
+        ((or "(" "[" "{" "}") "* deref")
+        (`nil
+         (goto-char pos)
+         (let ((res nil))
+           (while (not (or res (bobp)))
+             (pcase (smie-backward-sexp)
+               (`(,_ ,_ ,(or ";" "{")) (setq res "* deref"))
+               ((and `nil (guard (looking-at "{"))) (setq res "* deref"))
+               (`(,left ,_ ,op)
+                (if (and (numberp left)
+                         (numberp (nth 2 (assoc op smie-grammar)))
+                         (< (nth 2 (assoc op smie-grammar))
+                            (nth 1 (assoc "* mult" smie-grammar))))
+                    (smie-backward-sexp 'halfsexp)
+                  (setq res "* mult")))))
+           (or res "* mult")))
+        (_ "* mult")))))
+
+(defun elb-smie-smie-hanging-eolp ()
+  (let ((start (point))
+        (prev (smie-indent-backward-token)))
+    (if (and (not (numberp (nth 1 prev)))
+             (save-excursion (equal (elb-smie-smie-backward-token) ";")))
+        ;; Treat instructions that start after ";" as always "hanging".
+        (end-of-line)
+      (goto-char start)))
+  (skip-chars-forward " \t")
+  (or (eolp)
+      (forward-comment (point-max))
+      (and (looking-at "\\\\\n")
+           (goto-char (match-end 0)))))
+
+(defvar elb-smie-smie--inhibit-case/label-rule nil)
+
+(defun elb-smie--smie-virtual ()
+  (if (and (smie-indent--bolp)
+           (not (save-excursion
+                  (member (elb-smie-smie-forward-token)
+                          '("case" ":label")))))
+      (current-column)
+    (let ((elb-smie-smie--inhibit-case/label-rule t))
+      (smie-indent-calculate))))
+
+(defun elb-smie-smie-rules (kind token)
+  (pcase (cons kind token)
+    (`(:elem . basic) elb-smie-indent-basic)
+    (`(:list-intro . ";")
+     (save-excursion
+       (forward-char 1)
+       (if (and (null (smie-forward-sexp))
+                ;; FIXME: Handle \\\n as well!
+                (progn (forward-comment (point-max))
+                       (looking-at "(")))
+           nil
+         t)))
+    (`(:before . "else")
+     (save-excursion
+       (elb-smie--else-to-if)
+       `(column . ,(smie-indent-virtual))))
+    (`(:before . "while")
+     (save-excursion
+       (when (elb-smie--while-to-do)
+         `(column . ,(smie-indent-virtual)))))
+    (`(:before . ,(or "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|="))
+     (save-excursion
+       (elb-smie--boe token)
+       `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                      (smie-indent-virtual)))))
+    (`(:before . "if")
+     (when (and (not (smie-rule-bolp)) (smie-rule-prev-p "else"))
+       (save-excursion
+         (smie-indent-backward-token)
+         `(column . ,(elb-smie--smie-virtual)))))
+    ;; (`(:after . ,(or "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" 
"|="))
+    ;;  (funcall smie-rules-function :elem 'basic))
+    (`(:before . "{")
+     (cond
+      ((smie-rule-prev-p "=") nil)      ;Not a block of instructions!
+      ((save-excursion
+         (let ((pos (point)))
+           (elb-smie--boi 'inner) (elb-smie--skip-labels (point-max))
+           (let ((tok (save-excursion (elb-smie-smie-forward-token))))
+             (cond
+              ((or (equal tok "typedef")
+                   (and (member tok '("enum" "struct"))
+                        ;; Make sure that the {...} is about this struct/enum,
+                        ;; as opposed to "struct foo *get_foo () {...}"!
+                        (save-excursion
+                          (smie-indent-forward-token)
+                          (smie-indent-forward-token)
+                          (forward-comment (point-max))
+                          (>= (point) pos))))
+               `(column . ,(+ (if (save-excursion
+                                    (goto-char pos)
+                                    (smie-rule-hanging-p))
+                                  0
+                                (funcall smie-rules-function :elem 'basic))
+                              (smie-indent-virtual))))
+              ((and (member tok '("enum" "struct"))
+                    ;; Make sure that the {...} is about this struct/enum, as
+                    ;; opposed to "struct foo *get_foo () {...}"!
+                    (save-excursion
+                      (smie-indent-forward-token)
+                      (smie-indent-forward-token)
+                      (forward-comment (point-max))
+                      (>= (point) pos)))
+               `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                              (smie-indent-virtual))))
+              ((or (member tok elb-smie-paren-block-keywords)
+                   (equal tok "do"))
+               nil)
+              ((save-excursion
+                 (goto-char pos)
+                 (when (and (> (car (syntax-ppss)) 0)
+                            (equal ")" (car (smie-indent-backward-token))))
+                   (up-list -1)
+                   `(column . ,(elb-smie--smie-virtual)))))
+              ((>= (point) pos) nil)
+              (t `(column . ,(smie-indent-virtual))))))))
+      ((smie-rule-hanging-p)
+       (cond
+        ((smie-rule-prev-p "do" "else")
+         (smie-indent-backward-token))
+        ((smie-rule-prev-p ")")
+         (smie-backward-sexp)
+         (smie-indent-backward-token))
+        (t (elb-smie--boi 'inner)))
+       `(column . ,(elb-smie--smie-virtual)))
+      (t
+       (let ((pos (point)))
+         (pcase (elb-smie--prev-token)
+           ((or "do" "else")
+            (cond
+             (elb-smie-indent-braces
+              `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                             (smie-indent-virtual))))))
+           (")" nil)
+           (_ (goto-char pos) (elb-smie--boi)
+              (if (< (point) pos)
+                  `(column . ,(elb-smie--smie-virtual)))))))))
+    (`(:before . "(")
+     (save-excursion
+       (let ((res (smie-backward-sexp)))
+         (pcase res
+           (`nil
+            (if (looking-at "(")
+                ;; (unless (save-excursion
+                ;;           (member (elb-smie-smie-backward-token)
+                ;;                   elb-smie-paren-block-keywords))
+                ;;   `(column . ,(elb-smie--smie-virtual)))
+                nil
+              `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                             (elb-smie--smie-virtual)))))))))
+    (`(:after . "else")
+     (save-excursion
+       (funcall smie-rules-function :elem 'basic)))
+    (`(:after . ")")
+     (save-excursion
+       (let ((_ (progn (forward-char 1) (backward-sexp 1)))
+             (pos (point))
+             (prev (elb-smie-smie-backward-token)))
+         (cond
+          ((member prev elb-smie-paren-block-keywords)
+           `(column . ,(+ (funcall smie-rules-function :elem 'basic)
+                          (smie-indent-virtual))))
+          ((and (looking-at "[[:alnum:]_]+(")
+                (save-excursion
+                  (forward-line 0)
+                  (and (bobp) (looking-at elb-smie--cpp-regexp))))
+           ;; Will be bumped up presumably by the "max" in
+           ;; elb-smie--cpp-smie-indent.
+           `(column . 0))
+          (t (goto-char pos) `(column . ,(elb-smie--smie-virtual)))))))
+    (`(:after . "}")
+     (save-excursion
+       (forward-char 1) (backward-sexp 1)
+       (elb-smie--boi)
+       `(column . ,(elb-smie--smie-virtual))))
+    (`(:after . ";")
+     (save-excursion
+       (elb-smie--boi)
+       `(column . ,(elb-smie--smie-virtual))))
+    (`(:after . ":label")
+     ;; Yuck!
+     `(column . ,(elb-smie--smie-virtual)))
+    (`(:after . ": case")
+     ;; Yuck!
+     (save-excursion
+       (smie-backward-sexp ": case")
+       `(column . ,(elb-smie--smie-virtual))))
+    (`(:after . "* deref") `(column . ,(elb-smie--smie-virtual)))
+    ((and `(:before . ":label") (guard (not 
elb-smie-smie--inhibit-case/label-rule)))
+     (let ((ppss (syntax-ppss)))
+       (when (nth 1 ppss)
+         (save-excursion
+           (goto-char (nth 1 ppss))
+           `(column . ,(smie-indent-virtual))))))
+    ((and `(:before . "case") (guard (not 
elb-smie-smie--inhibit-case/label-rule)))
+     (catch 'found
+       (dolist (pos (reverse (nth 9 (syntax-ppss))))
+         (save-excursion
+           (goto-char pos)
+           (and (looking-at "{")
+                (null (car-safe (smie-backward-sexp)))
+                (equal "switch" (elb-smie-smie-backward-token))
+                (goto-char pos)
+                (throw 'found `(column . ,(smie-indent-virtual))))))))))
+
+;;; Backslash alignment
+
+(defvar-local elb-smie--bs-changed nil)
+
+(defun elb-smie--bs-after-change (beg end _len)
+  (unless undo-in-progress
+    (if (null elb-smie--bs-changed)
+        (setq elb-smie--bs-changed (cons beg end))
+      (cl-callf (lambda (x) (min x beg)) (car elb-smie--bs-changed))
+      (cl-callf (lambda (x) (max x end)) (cdr elb-smie--bs-changed)))))
+
+(defun elb-smie--bs-realign ()
+  (when elb-smie--bs-changed
+    (elb-smie--bs-realign-1 (car elb-smie--bs-changed) (cdr 
elb-smie--bs-changed))
+    (setq elb-smie--bs-changed nil)))
+
+(defcustom elb-smie-backslash-max-align-column 78
+  "Maximum column to align backslashes.
+Past this column, we do not try to align the backslashes."
+  :type 'integer)
+
+(defun elb-smie--bs-current-column ()
+  (let ((col (current-column)))
+    (if (> col elb-smie-backslash-max-align-column)
+        0 col)))
+
+(defun elb-smie--bs-realign-1 (from to)
+  (save-excursion
+    (goto-char from)
+    (end-of-line)
+    (unless (zerop (mod (skip-chars-backward "\\\\") 2))
+      (skip-chars-backward " \t")
+      (setq from (point))
+      (let ((col (elb-smie--bs-current-column))
+            start end)
+        (while
+            (progn (setq start (point))
+                   (end-of-line 0)
+                   (and (< (point) start)
+                        (not (zerop (mod (skip-chars-backward "\\\\") 2)))))
+          (unless (>= col elb-smie-backslash-max-align-column)
+            (skip-chars-backward " \t")
+            (setq col (max (elb-smie--bs-current-column) col))))
+        (goto-char from)
+        (while
+            (progn (setq end (point))
+                   (end-of-line 2)
+                   (and (> (line-beginning-position) end)
+                        (not (zerop (mod (skip-chars-backward "\\\\") 2)))))
+          (unless (>= col elb-smie-backslash-max-align-column)
+            (skip-chars-backward " \t")
+            (setq col (max (elb-smie--bs-current-column) col))))
+        (goto-char to)
+        (beginning-of-line)
+        (unless (or (> (point) end)     ;Don't realign if we changed outside!
+                    (<= end start))     ;A lone \
+          
+          (setq col (1+ col))         ;Add a space before the backslashes.
+          (goto-char end)
+          (end-of-line)
+          (while (>= (point) start)
+            (cl-assert (eq (char-before) ?\\))
+            (forward-char -1)
+            (let ((curcol (current-column)))
+              (cond
+               ((> col curcol) (indent-to col))
+               ((< col curcol)
+                (skip-chars-backward " \t")
+                (unless (> (current-column) col)
+                  (move-to-column col t)
+                  (delete-region (point) (1- (line-end-position)))))))
+            (end-of-line 0)))))))
+            
+;;; Font-lock support
+
+(defconst elb-smie--comment-regexp
+  "/\\(?:/.*\n\\|\\*[^*]*\\(?:\\*+[^/*][^*]*\\)*\\*+/\\)")
+
+(defconst elb-smie--defun-regexp
+  (let* ((spc0 (concat "\\(?:\n?[ \t]\\|" elb-smie--comment-regexp "\\)*"))
+         (spc1 (concat "\n?[ \t]" spc0))
+         (id "\\(?:\\sw\\|\\s_\\)+"))
+    (cl-flet ((repeat (repetition &rest res)
+                      (concat "\\(?:" (apply #'concat res) "\\)"
+                              (pcase repetition
+                                ((pred symbolp) (symbol-name repetition))
+                                (1 "")))))
+      (concat
+       "^\\(?:"
+       (repeat '* "\\*" spc0)                               ;Pointer symbols.
+       (repeat '* id (repeat 1 spc1 "\\|" spc0 "\\*" spc0)) ;Type(s).
+       "\\(" id "\\)[ \t\n]*("                              ;Function name.
+       "\\|"
+       "[ \t]*#[ \t]*define[ \t]+\\(?1:" id "\\)("
+       "\\)"))))
+
+(defconst elb-smie-font-lock-keywords
+  `((,elb-smie--cpp-regexp (1 font-lock-preprocessor-face))
+    ("\\_<\\(?:true\\|false\\)\\_>" (0 font-lock-constant-face))
+    ("\\_<\\(case\\)\\_>[ \t]*\\([^: \t]+\\)"
+     (1 font-lock-keyword-face)
+     (2 font-lock-constant-face))
+    ("\\(?:[{};]\\(\\)\\|^\\)[ \t]*\\([[:alpha:]_][[:alnum:]_]*\\)[ \t]*:"
+     (2 (if (or (match-beginning 1)
+                (save-excursion (equal ":label" 
(elb-smie-smie-backward-token))))
+            font-lock-constant-face)))
+    (,(let ((kws (delq nil (mapcar (lambda (x)
+                                     (setq x (car x))
+                                     (and (stringp x)
+                                          (string-match "\\`[a-z]" x)
+                                          x))
+                                   elb-smie-smie-grammar))))
+        (concat "\\_<" (regexp-opt
+                        (append
+                         ;; Elements not in SMIE's grammar.  Either because
+                         ;; they're uninteresting from a parsing point of view,
+                         ;; or because SMIE's parsing engine can't handle them
+                         ;; even poorly.
+                         '("break" "continue" "struct" "enum" "union" "static")
+                         ;; "case" already handled above.
+                         (delete "case" kws)))
+                "\\_>"))
+     (0 font-lock-keyword-face))
+    (,elb-smie--defun-regexp
+     (1
+      (prog1 font-lock-function-name-face
+        (if (< (match-beginning 0) (line-beginning-position))
+            (put-text-property (match-beginning 0) (match-end 0)
+                               'font-lock-multiline t)))))))
+
+(defconst elb-smie--def-regexp
+  (let ((spc0 (concat "\\(?:[ \t\n]\\|" elb-smie--comment-regexp "\\)*"))
+        (id "\\(?:\\sw\\|\\s_\\)+"))
+    (concat elb-smie--defun-regexp
+            "\\|"
+            "\\_<\\(?1:\\(?:struct\\|enum\\)[ \t]+" id "\\)" spc0 "{")))
+
+;;;###autoload
+(define-derived-mode elb-smie-mode prog-mode "smC"
+  "C editing mode based on SMIE."
+  ;; (setq-local font-lock-support-mode nil) ;; To help debugging.
+  (setq-local comment-start "/* ")
+  (setq-local comment-end " */")
+  (setq-local parse-sexp-lookup-properties t)
+  (setq-local open-paren-in-column-0-is-defun-start nil)
+  (setq-local syntax-propertize-function #'elb-smie-syntax-propertize)
+  (setq-local font-lock-defaults '(elb-smie-font-lock-keywords))
+  (setq-local font-lock-syntactic-face-function 
#'elb-smie-syntactic-face-function)
+  (smie-setup elb-smie-smie-grammar #'elb-smie-smie-rules
+              :backward-token #'elb-smie-smie-backward-token
+              :forward-token #'elb-smie-smie-forward-token)
+  ;; FIXME: The stock SMIE forward-sexp-function is not good enough here, since
+  ;; our grammar is much too poor.  We should setup another function instead
+  ;; (or ideally teach SMIE to use it).
+  (kill-local-variable 'forward-sexp-function)
+  (add-hook 'smie-indent-functions #'elb-smie--cpp-smie-indent nil t)
+  (add-function :after (local 'indent-line-function)
+                #'elb-smie--cpp-indent-line)
+  (setq-local smie--hanging-eolp-function #'elb-smie-smie-hanging-eolp)
+  ;; Backslash auto-realign.
+  (add-hook 'after-change-functions #'elb-smie--bs-after-change nil t)
+  (add-hook 'post-command-hook #'elb-smie--bs-realign nil t)
+  (setq-local add-log-current-defun-header-regexp elb-smie--def-regexp)
+  (setq-local imenu-generic-expression `((nil ,elb-smie--def-regexp 1))))
+
+;;; The actual benchmark
+
+(defun elb-smie-entry ()
+  (with-temp-buffer
+    (insert-file-contents (expand-file-name
+                           "../resources/xmenu.c" elb-bench-directory))
+    (elb-smie-mode)
+    (dotimes (_ 3)
+      (indent-region (point-min) (point-max)))))
+
+(provide 'elb-smie)
+;;; elb-smie.el ends here
diff --git a/elisp-benchmarks.el b/elisp-benchmarks.el
index 4c1a20a0f5..4fe10b87a4 100644
--- a/elisp-benchmarks.el
+++ b/elisp-benchmarks.el
@@ -63,8 +63,11 @@
   :type 'number)
 
 (defconst elb-bench-directory
-  (concat (file-name-directory (or load-file-name buffer-file-name))
-         "benchmarks/"))
+  (expand-file-name "benchmarks/"
+                   (file-name-directory
+                    (if (fboundp 'macroexp-file-name)
+                        (macroexp-file-name)
+                      (or load-file-name buffer-file-name)))))
 
 (defconst elb-result-buffer-name "elisp-benchmarks-results"
   "Buffer name where results are presented.")
diff --git a/resources/xmenu.c b/resources/xmenu.c
new file mode 100644
index 0000000000..9e4e6b62fc
--- /dev/null
+++ b/resources/xmenu.c
@@ -0,0 +1,2512 @@
+/* X Communication module for terminals which understand the X protocol.
+
+Copyright (C) 1986, 1988, 1993-1994, 1996, 1999-2022 Free Software
+Foundation, Inc.
+
+Author: Jon Arnold
+       Roman Budzianowski
+       Robert Krawitz
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* X pop-up deck-of-cards menu facility for GNU Emacs.
+ *
+ */
+
+/* Modified by Fred Pierresteguy on December 93
+   to make the popup menus and menubar use the Xt.  */
+
+/* Rewritten for clarity and GC protection by rms in Feb 94.  */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "lisp.h"
+#include "keyboard.h"
+#include "frame.h"
+#include "systime.h"
+#include "termhooks.h"
+#include "window.h"
+#include "blockinput.h"
+#include "buffer.h"
+#include "coding.h"
+#include "sysselect.h"
+#include "pdumper.h"
+
+#ifdef MSDOS
+#include "msdos.h"
+#endif
+
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
+#ifdef HAVE_X_WINDOWS
+/* This may include sys/types.h, and that somehow loses
+   if this is not done before the other system files.  */
+#include "xterm.h"
+#endif
+
+/* Load sys/types.h if not already loaded.
+   In some systems loading it twice is suicidal.  */
+#ifndef makedev
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_X_WINDOWS
+/*  Defining HAVE_MULTILINGUAL_MENU would mean that the toolkit menu
+    code accepts the Emacs internal encoding.  */
+#undef HAVE_MULTILINGUAL_MENU
+#ifdef USE_X_TOOLKIT
+#include "widget.h"
+#include <X11/Xlib.h>
+#include <X11/IntrinsicP.h>
+#include <X11/CoreP.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+#ifdef USE_LUCID
+#include "xsettings.h"
+#include "../lwlib/xlwmenu.h"
+#ifdef HAVE_XAW3D
+#include <X11/Xaw3d/Paned.h>
+#else /* !HAVE_XAW3D */
+#include <X11/Xaw/Paned.h>
+#endif /* HAVE_XAW3D */
+#endif /* USE_LUCID */
+#ifdef USE_MOTIF
+#include "../lwlib/lwlib.h"
+#endif
+#else /* not USE_X_TOOLKIT */
+#ifndef USE_GTK
+#include "../oldXMenu/XMenu.h"
+#endif
+#endif /* not USE_X_TOOLKIT */
+#endif /* HAVE_X_WINDOWS */
+
+#ifdef USE_GTK
+#include "gtkutil.h"
+#ifdef HAVE_GTK3
+#include "xgselect.h"
+#endif
+#endif
+
+#include "menu.h"
+
+
+/* Flag which when set indicates a dialog or menu has been posted by
+   Xt on behalf of one of the widget sets.  */
+#ifndef HAVE_XINPUT2
+static int popup_activated_flag;
+#else
+int popup_activated_flag;
+#endif
+
+
+#ifdef USE_X_TOOLKIT
+
+static LWLIB_ID next_menubar_widget_id;
+
+/* Return the frame whose ->output_data.x->id equals ID, or 0 if none.  */
+
+static struct frame *
+menubar_id_to_frame (LWLIB_ID id)
+{
+  Lisp_Object tail, frame;
+  struct frame *f;
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+      if (!FRAME_WINDOW_P (f))
+       continue;
+      if (f->output_data.x->id == id)
+       return f;
+    }
+  return 0;
+}
+
+#endif
+
+#ifndef MSDOS
+
+#if defined USE_GTK || defined USE_MOTIF
+
+/* Set menu_items_inuse so no other popup menu or dialog is created.  */
+
+void
+x_menu_set_in_use (bool in_use)
+{
+  Lisp_Object frames, frame;
+
+  menu_items_inuse = in_use;
+  popup_activated_flag = in_use;
+#ifdef USE_X_TOOLKIT
+  if (popup_activated_flag)
+    x_activate_timeout_atimer ();
+#endif
+
+  /* Don't let frames in `above' z-group obscure popups.  */
+  FOR_EACH_FRAME (frames, frame)
+    {
+      struct frame *f = XFRAME (frame);
+
+      if (in_use && FRAME_Z_GROUP_ABOVE (f))
+       x_set_z_group (f, Qabove_suspended, Qabove);
+      else if (!in_use && FRAME_Z_GROUP_ABOVE_SUSPENDED (f))
+       x_set_z_group (f, Qabove, Qabove_suspended);
+    }
+}
+#endif
+
+/* Wait for an X event to arrive or for a timer to expire.  */
+
+void
+x_menu_wait_for_event (void *data)
+{
+  /* Another way to do this is to register a timer callback, that can be
+     done in GTK and Xt.  But we have to do it like this when using only X
+     anyway, and with callbacks we would have three variants for timer handling
+     instead of the small ifdefs below.  */
+
+  while (
+#ifdef USE_X_TOOLKIT
+         ! XtAppPending (Xt_app_con)
+#elif defined USE_GTK
+         ! gtk_events_pending ()
+#else
+         ! XPending (data)
+#endif
+         )
+    {
+      struct timespec next_time = timer_check (), *ntp;
+      fd_set read_fds;
+      struct x_display_info *dpyinfo;
+      int n = 0;
+
+      FD_ZERO (&read_fds);
+      for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
+        {
+          int fd = ConnectionNumber (dpyinfo->display);
+          FD_SET (fd, &read_fds);
+          if (fd > n) n = fd;
+          XFlush (dpyinfo->display);
+        }
+
+      if (! timespec_valid_p (next_time))
+        ntp = 0;
+      else
+        ntp = &next_time;
+
+#if defined USE_GTK && defined HAVE_GTK3
+      /* Gtk3 have arrows on menus when they don't fit.  When the
+        pointer is over an arrow, a timeout scrolls it a bit.  Use
+        xg_select so that timeout gets triggered.  */
+      xg_select (n + 1, &read_fds, NULL, NULL, ntp, NULL);
+#else
+      pselect (n + 1, &read_fds, NULL, NULL, ntp, NULL);
+#endif
+    }
+}
+#endif /* ! MSDOS */
+
+
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+
+#ifdef USE_X_TOOLKIT
+
+/* Loop in Xt until the menu pulldown or dialog popup has been
+   popped down (deactivated).  This is used for x-popup-menu
+   and x-popup-dialog; it is not used for the menu bar.
+
+   NOTE: All calls to popup_get_selection should be protected
+   with BLOCK_INPUT, UNBLOCK_INPUT wrappers.  */
+
+static void
+popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo,
+                    LWLIB_ID id, bool do_timers)
+{
+  XEvent event;
+
+  while (popup_activated_flag)
+    {
+      if (initial_event)
+        {
+          event = *initial_event;
+          initial_event = 0;
+        }
+      else
+        {
+          if (do_timers) x_menu_wait_for_event (0);
+          XtAppNextEvent (Xt_app_con, &event);
+        }
+
+      /* Make sure we don't consider buttons grabbed after menu goes.
+         And make sure to deactivate for any ButtonRelease,
+         even if XtDispatchEvent doesn't do that.  */
+      if (event.type == ButtonRelease
+          && dpyinfo->display == event.xbutton.display)
+        {
+          dpyinfo->grabbed &= ~(1 << event.xbutton.button);
+#ifdef USE_MOTIF /* Pretending that the event came from a
+                    Btn1Down seems the only way to convince Motif to
+                    activate its callbacks; setting the XmNmenuPost
+                    isn't working. --marcus@sysc.pdx.edu.  */
+          event.xbutton.button = 1;
+          /*  Motif only pops down menus when no Ctrl, Alt or Mod
+              key is pressed and the button is released.  So reset key state
+              so Motif thinks this is the case.  */
+          event.xbutton.state = 0;
+#endif
+        }
+      /* Pop down on C-g and Escape.  */
+      else if (event.type == KeyPress
+               && dpyinfo->display == event.xbutton.display)
+        {
+          KeySym keysym = XLookupKeysym (&event.xkey, 0);
+
+          if ((keysym == XK_g && (event.xkey.state & ControlMask) != 0)
+              || keysym == XK_Escape) /* Any escape, ignore modifiers.  */
+            popup_activated_flag = 0;
+        }
+
+      x_dispatch_event (&event, event.xany.display);
+    }
+}
+
+DEFUN ("x-menu-bar-open-internal", Fx_menu_bar_open_internal, 
Sx_menu_bar_open_internal, 0, 1, "i",
+       doc: /* SKIP: real doc in USE_GTK definition in xmenu.c.  */)
+  (Lisp_Object frame)
+{
+  XEvent ev;
+  struct frame *f = decode_window_system_frame (frame);
+  Widget menubar;
+  block_input ();
+
+  if (FRAME_EXTERNAL_MENU_BAR (f))
+    set_frame_menubar (f, true);
+
+  menubar = FRAME_X_OUTPUT (f)->menubar_widget;
+  if (menubar)
+    {
+      Window child;
+      bool error_p = false;
+
+      x_catch_errors (FRAME_X_DISPLAY (f));
+      memset (&ev, 0, sizeof ev);
+      ev.xbutton.display = FRAME_X_DISPLAY (f);
+      ev.xbutton.window = XtWindow (menubar);
+      ev.xbutton.root = FRAME_DISPLAY_INFO (f)->root_window;
+      ev.xbutton.time = XtLastTimestampProcessed (FRAME_X_DISPLAY (f));
+      ev.xbutton.button = Button1;
+      ev.xbutton.x = ev.xbutton.y = FRAME_MENUBAR_HEIGHT (f) / 2;
+      ev.xbutton.same_screen = True;
+
+#ifdef USE_MOTIF
+      {
+        Arg al[2];
+        WidgetList list;
+        Cardinal nr;
+        XtSetArg (al[0], XtNchildren, &list);
+        XtSetArg (al[1], XtNnumChildren, &nr);
+        XtGetValues (menubar, al, 2);
+        ev.xbutton.window = XtWindow (list[0]);
+      }
+#endif
+
+      XTranslateCoordinates (FRAME_X_DISPLAY (f),
+                             /* From-window, to-window.  */
+                             ev.xbutton.window, ev.xbutton.root,
+
+                             /* From-position, to-position.  */
+                             ev.xbutton.x, ev.xbutton.y,
+                             &ev.xbutton.x_root, &ev.xbutton.y_root,
+
+                             /* Child of win.  */
+                             &child);
+      error_p = x_had_errors_p (FRAME_X_DISPLAY (f));
+      x_uncatch_errors_after_check ();
+
+      if (! error_p)
+        {
+          ev.type = ButtonPress;
+          ev.xbutton.state = 0;
+
+          XtDispatchEvent (&ev);
+          ev.xbutton.type = ButtonRelease;
+          ev.xbutton.state = Button1Mask;
+          XtDispatchEvent (&ev);
+        }
+    }
+
+  unblock_input ();
+
+  return Qnil;
+}
+#endif /* USE_X_TOOLKIT */
+
+
+#ifdef USE_GTK
+DEFUN ("x-menu-bar-open-internal", Fx_menu_bar_open_internal, 
Sx_menu_bar_open_internal, 0, 1, "i",
+       doc: /* Start key navigation of the menu bar in FRAME.
+This initially opens the first menu bar item and you can then navigate with the
+arrow keys, select a menu entry with the return key or cancel with the
+escape key.  If FRAME has no menu bar this function does nothing.
+
+If FRAME is nil or not given, use the selected frame.  */)
+  (Lisp_Object frame)
+{
+  GtkWidget *menubar;
+  struct frame *f;
+
+  block_input ();
+  f = decode_window_system_frame (frame);
+
+  if (FRAME_EXTERNAL_MENU_BAR (f))
+    set_frame_menubar (f, true);
+
+  menubar = FRAME_X_OUTPUT (f)->menubar_widget;
+  if (menubar)
+    {
+      /* Activate the first menu.  */
+      GList *children = gtk_container_get_children (GTK_CONTAINER (menubar));
+
+      if (children)
+        {
+          g_signal_emit_by_name (children->data, "activate_item");
+          popup_activated_flag = 1;
+          g_list_free (children);
+        }
+    }
+  unblock_input ();
+
+  return Qnil;
+}
+
+/* Loop util popup_activated_flag is set to zero in a callback.
+   Used for popup menus and dialogs. */
+
+static void
+popup_widget_loop (bool do_timers, GtkWidget *widget)
+{
+  ++popup_activated_flag;
+
+  /* Process events in the Gtk event loop until done.  */
+  while (popup_activated_flag)
+    {
+      if (do_timers) x_menu_wait_for_event (0);
+      gtk_main_iteration ();
+    }
+}
+#endif
+
+/* Activate the menu bar of frame F.
+   This is called from keyboard.c when it gets the
+   MENU_BAR_ACTIVATE_EVENT out of the Emacs event queue.
+
+   To activate the menu bar, we use the X button-press event
+   that was saved in saved_menu_event.
+   That makes the toolkit do its thing.
+
+   But first we recompute the menu bar contents (the whole tree).
+
+   The reason for saving the button event until here, instead of
+   passing it to the toolkit right away, is that we can safely
+   execute Lisp code.  */
+
+void
+x_activate_menubar (struct frame *f)
+{
+  eassert (FRAME_X_P (f));
+
+  if (!f->output_data.x->saved_menu_event->type)
+    return;
+
+#ifdef USE_GTK
+  if (! xg_win_to_widget (FRAME_X_DISPLAY (f),
+                          f->output_data.x->saved_menu_event->xany.window))
+    return;
+#endif
+
+  set_frame_menubar (f, true);
+  block_input ();
+  popup_activated_flag = 1;
+#ifdef USE_GTK
+  XPutBackEvent (f->output_data.x->display_info->display,
+                 f->output_data.x->saved_menu_event);
+#else
+#if defined USE_X_TOOLKIT && defined HAVE_XINPUT2
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  /* Clear the XI2 grab so Motif or lwlib can set a core grab.
+     Otherwise some versions of Motif will emit a warning and hang,
+     and lwlib will fail to destroy the menu window.  */
+
+  if (dpyinfo->num_devices)
+    {
+      for (int i = 0; i < dpyinfo->num_devices; ++i)
+       {
+         if (dpyinfo->devices[i].grab)
+           XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id,
+                           CurrentTime);
+       }
+    }
+#endif
+  XtDispatchEvent (f->output_data.x->saved_menu_event);
+#endif
+  unblock_input ();
+
+  /* Ignore this if we get it a second time.  */
+  f->output_data.x->saved_menu_event->type = 0;
+}
+
+/* This callback is invoked when the user selects a menubar cascade
+   pushbutton, but before the pulldown menu is posted.  */
+
+#ifndef USE_GTK
+static void
+popup_activate_callback (Widget widget, LWLIB_ID id, XtPointer client_data)
+{
+  popup_activated_flag = 1;
+  x_activate_timeout_atimer ();
+}
+#endif
+
+/* This callback is invoked when a dialog or menu is finished being
+   used and has been unposted.  */
+
+static void
+popup_deactivate_callback (
+#ifdef USE_GTK
+                          GtkWidget *widget, gpointer client_data
+#else
+                          Widget widget, LWLIB_ID id, XtPointer client_data
+#endif
+                          )
+{
+  popup_activated_flag = 0;
+}
+
+
+/* Function that finds the frame for WIDGET and shows the HELP text
+   for that widget.
+   F is the frame if known, or NULL if not known.  */
+static void
+show_help_event (struct frame *f, xt_or_gtk_widget widget, Lisp_Object help)
+{
+  Lisp_Object frame;
+
+  if (f)
+    {
+      XSETFRAME (frame, f);
+      kbd_buffer_store_help_event (frame, help);
+    }
+  else
+    show_help_echo (help, Qnil, Qnil, Qnil);
+}
+
+/* Callback called when menu items are highlighted/unhighlighted
+   while moving the mouse over them.  WIDGET is the menu bar or menu
+   popup widget.  ID is its LWLIB_ID.  CALL_DATA contains a pointer to
+   the data structure for the menu item, or null in case of
+   unhighlighting.  */
+
+#ifdef USE_GTK
+static void
+menu_highlight_callback (GtkWidget *widget, gpointer call_data)
+{
+  xg_menu_item_cb_data *cb_data;
+  Lisp_Object help;
+
+  cb_data = g_object_get_data (G_OBJECT (widget), XG_ITEM_DATA);
+  if (! cb_data) return;
+
+  help = call_data ? cb_data->help : Qnil;
+
+  /* If popup_activated_flag is greater than 1 we are in a popup menu.
+     Don't pass the frame to show_help_event for those.
+     Passing frame creates an Emacs event.  As we are looping in
+     popup_widget_loop, it won't be handled.  Passing NULL shows the tip
+     directly without using an Emacs event.  This is what the Lucid code
+     does below.  */
+  show_help_event (popup_activated_flag <= 1 ? cb_data->cl_data->f : NULL,
+                   widget, help);
+}
+#else
+static void
+menu_highlight_callback (Widget widget, LWLIB_ID id, void *call_data)
+{
+  widget_value *wv = call_data;
+  Lisp_Object help = wv ? wv->help : Qnil;
+
+  /* Determine the frame for the help event.  */
+  struct frame *f = menubar_id_to_frame (id);
+
+  show_help_event (f, widget, help);
+}
+#endif
+
+#ifdef USE_GTK
+/* Gtk calls callbacks just because we tell it what item should be
+   selected in a radio group.  If this variable is set to a non-zero
+   value, we are creating menus and don't want callbacks right now.
+*/
+static bool xg_crazy_callback_abort;
+
+/* This callback is called from the menu bar pulldown menu
+   when the user makes a selection.
+   Figure out what the user chose
+   and put the appropriate events into the keyboard buffer.  */
+static void
+menubar_selection_callback (GtkWidget *widget, gpointer client_data)
+{
+  xg_menu_item_cb_data *cb_data = client_data;
+
+  if (xg_crazy_callback_abort)
+    return;
+
+  if (! cb_data || ! cb_data->cl_data || ! cb_data->cl_data->f)
+    return;
+
+  /* For a group of radio buttons, GTK calls the selection callback first
+     for the item that was active before the selection and then for the one 
that
+     is active after the selection.  For C-h k this means we get the help on
+     the deselected item and then the selected item is executed.  Prevent that
+     by ignoring the non-active item.  */
+  if (GTK_IS_RADIO_MENU_ITEM (widget)
+      && ! gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
+    return;
+
+  /* When a menu is popped down, X generates a focus event (i.e. focus
+     goes back to the frame below the menu).  Since GTK buffers events,
+     we force it out here before the menu selection event.  Otherwise
+     sit-for will exit at once if the focus event follows the menu selection
+     event.  */
+
+  block_input ();
+  while (gtk_events_pending ())
+    gtk_main_iteration ();
+  unblock_input ();
+
+  find_and_call_menu_selection (cb_data->cl_data->f,
+                                cb_data->cl_data->menu_bar_items_used,
+                                cb_data->cl_data->menu_bar_vector,
+                                cb_data->call_data);
+}
+
+#else /* not USE_GTK */
+
+/* This callback is called from the menu bar pulldown menu
+   when the user makes a selection.
+   Figure out what the user chose
+   and put the appropriate events into the keyboard buffer.  */
+static void
+menubar_selection_callback (Widget widget, LWLIB_ID id, XtPointer client_data)
+{
+  struct frame *f;
+
+  f = menubar_id_to_frame (id);
+  if (!f)
+    return;
+  find_and_call_menu_selection (f, f->menu_bar_items_used,
+                                f->menu_bar_vector, client_data);
+}
+#endif /* not USE_GTK */
+
+/* Recompute all the widgets of frame F, when the menu bar has been
+   changed.  */
+
+static void
+update_frame_menubar (struct frame *f)
+{
+#ifdef USE_GTK
+  xg_update_frame_menubar (f);
+#else
+  struct x_output *x;
+
+  eassert (FRAME_X_P (f));
+
+  x = f->output_data.x;
+
+  if (!x->menubar_widget || XtIsManaged (x->menubar_widget))
+    return;
+
+  block_input ();
+
+  /* Do the voodoo which means "I'm changing lots of things, don't try
+     to refigure sizes until I'm done."  */
+  lw_refigure_widget (x->column_widget, False);
+
+  /* The order in which children are managed is the top to bottom
+     order in which they are displayed in the paned window.  First,
+     remove the text-area widget.  */
+  XtUnmanageChild (x->edit_widget);
+
+  /* Remove the menubar that is there now, and put up the menubar that
+     should be there.  */
+  XtManageChild (x->menubar_widget);
+  XtMapWidget (x->menubar_widget);
+  XtVaSetValues (x->menubar_widget, XtNmappedWhenManaged, 1, NULL);
+
+  /* Re-manage the text-area widget, and then thrash the sizes.  */
+  XtManageChild (x->edit_widget);
+  lw_refigure_widget (x->column_widget, True);
+
+  /* Force the pane widget to resize itself.  */
+  adjust_frame_size (f, -1, -1, 2, false, Qmenu_bar_lines);
+  unblock_input ();
+#endif /* USE_GTK */
+}
+
+#ifdef USE_LUCID
+static void
+apply_systemfont_to_dialog (Widget w)
+{
+  const char *fn = xsettings_get_system_normal_font ();
+  if (fn)
+    {
+      XrmDatabase db = XtDatabase (XtDisplay (w));
+      if (db)
+        XrmPutStringResource (&db, "*dialog.font", fn);
+    }
+}
+
+static void
+apply_systemfont_to_menu (struct frame *f, Widget w)
+{
+  const char *fn = xsettings_get_system_normal_font ();
+
+  if (fn)
+    {
+      XrmDatabase db = XtDatabase (XtDisplay (w));
+      if (db)
+        {
+          XrmPutStringResource (&db, "*menubar*font", fn);
+          XrmPutStringResource (&db, "*popup*font", fn);
+        }
+    }
+}
+
+#endif
+
+/* Set the contents of the menubar widgets of frame F.  */
+
+void
+set_frame_menubar (struct frame *f, bool deep_p)
+{
+  xt_or_gtk_widget menubar_widget, old_widget;
+#ifdef USE_X_TOOLKIT
+  LWLIB_ID id;
+#endif
+  Lisp_Object items;
+  widget_value *wv, *first_wv, *prev_wv = 0;
+  int i;
+  int *submenu_start, *submenu_end;
+  bool *submenu_top_level_items;
+  int *submenu_n_panes;
+
+  eassert (FRAME_X_P (f));
+
+  menubar_widget = old_widget = f->output_data.x->menubar_widget;
+
+  XSETFRAME (Vmenu_updating_frame, f);
+
+#ifdef USE_X_TOOLKIT
+  if (f->output_data.x->id == 0)
+    f->output_data.x->id = next_menubar_widget_id++;
+  id = f->output_data.x->id;
+#endif
+
+  if (! menubar_widget)
+    deep_p = true;
+  /* Make the first call for any given frame always go deep.  */
+  else if (!f->output_data.x->saved_menu_event && !deep_p)
+    {
+      deep_p = true;
+      f->output_data.x->saved_menu_event = xmalloc (sizeof (XEvent));
+      f->output_data.x->saved_menu_event->type = 0;
+    }
+
+  if (deep_p)
+    {
+      /* Make a widget-value tree representing the entire menu trees.  */
+
+      struct buffer *prev = current_buffer;
+      Lisp_Object buffer;
+      ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+      int previous_menu_items_used = f->menu_bar_items_used;
+      Lisp_Object *previous_items
+       = alloca (previous_menu_items_used * sizeof *previous_items);
+      int subitems;
+
+      /* If we are making a new widget, its contents are empty,
+        do always reinitialize them.  */
+      if (! menubar_widget)
+       previous_menu_items_used = 0;
+
+      buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
+      specbind (Qinhibit_quit, Qt);
+      /* Don't let the debugger step into this code
+        because it is not reentrant.  */
+      specbind (Qdebug_on_next_call, Qnil);
+
+      record_unwind_save_match_data ();
+      if (NILP (Voverriding_local_map_menu_flag))
+       {
+         specbind (Qoverriding_terminal_local_map, Qnil);
+         specbind (Qoverriding_local_map, Qnil);
+       }
+
+      set_buffer_internal_1 (XBUFFER (buffer));
+
+      /* Run the Lucid hook.  */
+      safe_run_hooks (Qactivate_menubar_hook);
+
+      /* If it has changed current-menubar from previous value,
+        really recompute the menubar from the value.  */
+      if (! NILP (Vlucid_menu_bar_dirty_flag))
+       call0 (Qrecompute_lucid_menubar);
+      safe_run_hooks (Qmenu_bar_update_hook);
+      fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
+
+      items = FRAME_MENU_BAR_ITEMS (f);
+
+      /* Save the frame's previous menu bar contents data.  */
+      if (previous_menu_items_used)
+       memcpy (previous_items, xvector_contents (f->menu_bar_vector),
+               previous_menu_items_used * word_size);
+
+      /* Fill in menu_items with the current menu bar contents.
+        This can evaluate Lisp code.  */
+      save_menu_items ();
+
+      menu_items = f->menu_bar_vector;
+      menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
+      subitems = ASIZE (items) / 4;
+      submenu_start = alloca ((subitems + 1) * sizeof *submenu_start);
+      submenu_end = alloca (subitems * sizeof *submenu_end);
+      submenu_n_panes = alloca (subitems * sizeof *submenu_n_panes);
+      submenu_top_level_items = alloca (subitems
+                                       * sizeof *submenu_top_level_items);
+      init_menu_items ();
+      for (i = 0; i < subitems; i++)
+       {
+         Lisp_Object key, string, maps;
+
+         key = AREF (items, 4 * i);
+         string = AREF (items, 4 * i + 1);
+         maps = AREF (items, 4 * i + 2);
+         if (NILP (string))
+           break;
+
+         submenu_start[i] = menu_items_used;
+
+         menu_items_n_panes = 0;
+         submenu_top_level_items[i]
+           = parse_single_submenu (key, string, maps);
+         submenu_n_panes[i] = menu_items_n_panes;
+
+         submenu_end[i] = menu_items_used;
+       }
+
+      submenu_start[i] = -1;
+      finish_menu_items ();
+
+      /* Convert menu_items into widget_value trees
+        to display the menu.  This cannot evaluate Lisp code.  */
+
+      wv = make_widget_value ("menubar", NULL, true, Qnil);
+      wv->button_type = BUTTON_TYPE_NONE;
+      first_wv = wv;
+
+      for (i = 0; submenu_start[i] >= 0; i++)
+       {
+         menu_items_n_panes = submenu_n_panes[i];
+         wv = digest_single_submenu (submenu_start[i], submenu_end[i],
+                                     submenu_top_level_items[i]);
+         if (prev_wv)
+           prev_wv->next = wv;
+         else
+           first_wv->contents = wv;
+         /* Don't set wv->name here; GC during the loop might relocate it.  */
+         wv->enabled = true;
+         wv->button_type = BUTTON_TYPE_NONE;
+         prev_wv = wv;
+       }
+
+      set_buffer_internal_1 (prev);
+
+      /* If there has been no change in the Lisp-level contents
+        of the menu bar, skip redisplaying it.  Just exit.  */
+
+      /* Compare the new menu items with the ones computed last time.  */
+      for (i = 0; i < previous_menu_items_used; i++)
+       if (menu_items_used == i
+           || (!EQ (previous_items[i], AREF (menu_items, i))))
+         break;
+      if (i == menu_items_used && i == previous_menu_items_used && i != 0)
+       {
+         /* The menu items have not changed.  Don't bother updating
+            the menus in any form, since it would be a no-op.  */
+         free_menubar_widget_value_tree (first_wv);
+         discard_menu_items ();
+         unbind_to (specpdl_count, Qnil);
+         return;
+       }
+
+      /* The menu items are different, so store them in the frame.  */
+      fset_menu_bar_vector (f, menu_items);
+      f->menu_bar_items_used = menu_items_used;
+
+      /* This undoes save_menu_items.  */
+      unbind_to (specpdl_count, Qnil);
+
+      /* Now GC cannot happen during the lifetime of the widget_value,
+        so it's safe to store data from a Lisp_String.  */
+      wv = first_wv->contents;
+      for (i = 0; i < ASIZE (items); i += 4)
+       {
+         Lisp_Object string;
+         string = AREF (items, i + 1);
+         if (NILP (string))
+            break;
+          wv->name = SSDATA (string);
+          update_submenu_strings (wv->contents);
+          wv = wv->next;
+       }
+
+    }
+  else
+    {
+      /* Make a widget-value tree containing
+        just the top level menu bar strings.  */
+
+      wv = make_widget_value ("menubar", NULL, true, Qnil);
+      wv->button_type = BUTTON_TYPE_NONE;
+      first_wv = wv;
+
+      items = FRAME_MENU_BAR_ITEMS (f);
+      for (i = 0; i < ASIZE (items); i += 4)
+       {
+         Lisp_Object string;
+
+         string = AREF (items, i + 1);
+         if (NILP (string))
+           break;
+
+         wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
+         wv->button_type = BUTTON_TYPE_NONE;
+         /* This prevents lwlib from assuming this
+            menu item is really supposed to be empty.  */
+         /* The intptr_t cast avoids a warning.
+            This value just has to be different from small integers.  */
+         wv->call_data = (void *) (intptr_t) (-1);
+
+         if (prev_wv)
+           prev_wv->next = wv;
+         else
+           first_wv->contents = wv;
+         prev_wv = wv;
+       }
+
+      /* Forget what we thought we knew about what is in the
+        detailed contents of the menu bar menus.
+        Changing the top level always destroys the contents.  */
+      f->menu_bar_items_used = 0;
+    }
+
+  /* Create or update the menu bar widget.  */
+
+  block_input ();
+
+#ifdef USE_GTK
+  xg_crazy_callback_abort = true;
+  if (menubar_widget)
+    {
+      /* The fourth arg is DEEP_P, which says to consider the entire
+        menu trees we supply, rather than just the menu bar item names.  */
+      xg_modify_menubar_widgets (menubar_widget,
+                                 f,
+                                 first_wv,
+                                 deep_p,
+                                 G_CALLBACK (menubar_selection_callback),
+                                 G_CALLBACK (popup_deactivate_callback),
+                                 G_CALLBACK (menu_highlight_callback));
+    }
+  else
+    {
+      menubar_widget
+        = xg_create_widget ("menubar", "menubar", f, first_wv,
+                            G_CALLBACK (menubar_selection_callback),
+                            G_CALLBACK (popup_deactivate_callback),
+                            G_CALLBACK (menu_highlight_callback));
+
+      f->output_data.x->menubar_widget = menubar_widget;
+    }
+
+
+#else /* not USE_GTK */
+  if (menubar_widget)
+    {
+      /* Disable resizing (done for Motif!) */
+      lw_allow_resizing (f->output_data.x->widget, False);
+
+      /* The third arg is DEEP_P, which says to consider the entire
+        menu trees we supply, rather than just the menu bar item names.  */
+      lw_modify_all_widgets (id, first_wv, deep_p);
+
+      /* Re-enable the edit widget to resize.  */
+      lw_allow_resizing (f->output_data.x->widget, True);
+    }
+  else
+    {
+      char menuOverride[] = "Ctrl<KeyPress>g: MenuGadgetEscape()";
+      XtTranslations  override = XtParseTranslationTable (menuOverride);
+
+#ifdef USE_LUCID
+      apply_systemfont_to_menu (f, f->output_data.x->column_widget);
+#endif
+      menubar_widget = lw_create_widget ("menubar", "menubar", id,
+                                         first_wv,
+                                        f->output_data.x->column_widget,
+                                        false,
+                                        popup_activate_callback,
+                                        menubar_selection_callback,
+                                        popup_deactivate_callback,
+                                        menu_highlight_callback);
+      f->output_data.x->menubar_widget = menubar_widget;
+
+      /* Make menu pop down on C-g.  */
+      XtOverrideTranslations (menubar_widget, override);
+    }
+
+  {
+    int menubar_size;
+    if (f->output_data.x->menubar_widget)
+      XtRealizeWidget (f->output_data.x->menubar_widget);
+
+    menubar_size
+      = (f->output_data.x->menubar_widget
+        ? (f->output_data.x->menubar_widget->core.height
+#ifndef USE_LUCID
+           /* Damn me...  With Lucid I get a core.border_width of 1
+              only the first time this is called and an ibw of 1 every
+              time this is called.  So the first time this is called I
+              was off by one.  Fix that here by never adding
+              core.border_width for Lucid.  */
+           + f->output_data.x->menubar_widget->core.border_width
+#endif /* USE_LUCID */
+           )
+        : 0);
+
+#ifdef USE_LUCID
+      /* Experimentally, we now get the right results
+        for -geometry -0-0 without this.  24 Aug 96, rms.
+         Maybe so, but the menu bar size is missing the pixels so the
+         WM size hints are off by these pixels.  Jan D, oct 2009.  */
+    if (FRAME_EXTERNAL_MENU_BAR (f))
+      {
+        Dimension ibw = 0;
+
+        XtVaGetValues (f->output_data.x->column_widget,
+                      XtNinternalBorderWidth, &ibw, NULL);
+       menubar_size += ibw;
+      }
+#endif /* USE_LUCID */
+
+    FRAME_MENUBAR_HEIGHT (f) = menubar_size;
+  }
+#endif /* not USE_GTK */
+
+  free_menubar_widget_value_tree (first_wv);
+  update_frame_menubar (f);
+
+#ifdef USE_GTK
+  xg_crazy_callback_abort = false;
+#endif
+
+  unblock_input ();
+}
+
+/* Called from Fx_create_frame to create the initial menubar of a frame
+   before it is mapped, so that the window is mapped with the menubar already
+   there instead of us tacking it on later and thrashing the window after it
+   is visible.  */
+
+void
+initialize_frame_menubar (struct frame *f)
+{
+  /* This function is called before the first chance to redisplay
+     the frame.  It has to be, so the frame will have the right size.  */
+  fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
+  set_frame_menubar (f, true);
+}
+
+
+/* Get rid of the menu bar of frame F, and free its storage.
+   This is used when deleting a frame, and when turning off the menu bar.
+   For GTK this function is in gtkutil.c.  */
+
+#ifndef USE_GTK
+void
+free_frame_menubar (struct frame *f)
+{
+  Widget menubar_widget;
+#ifdef USE_MOTIF
+  /* Motif automatically shrinks the frame in lw_destroy_all_widgets.
+     If we want to preserve the old height, calculate it now so we can
+     restore it below.  */
+  int old_width = FRAME_TEXT_WIDTH (f);
+  int old_height = FRAME_TEXT_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f);
+#endif
+
+  eassert (FRAME_X_P (f));
+
+  menubar_widget = f->output_data.x->menubar_widget;
+
+  FRAME_MENUBAR_HEIGHT (f) = 0;
+
+  if (menubar_widget)
+    {
+#ifdef USE_MOTIF
+      /* Removing the menu bar magically changes the shell widget's x
+        and y position of (0, 0) which, when the menu bar is turned
+        on again, leads to pull-down menus appearing in strange
+        positions near the upper-left corner of the display.  This
+        happens only with some window managers like twm and ctwm,
+        but not with other like Motif's mwm or kwm, because the
+        latter generate ConfigureNotify events when the menu bar
+        is switched off, which fixes the shell position.  */
+      Position x0, y0, x1, y1;
+#endif
+
+      block_input ();
+
+#ifdef USE_MOTIF
+      if (f->output_data.x->widget)
+       XtVaGetValues (f->output_data.x->widget, XtNx, &x0, XtNy, &y0, NULL);
+#endif
+
+      lw_destroy_all_widgets ((LWLIB_ID) f->output_data.x->id);
+      f->output_data.x->menubar_widget = NULL;
+
+      /* When double-buffering is enabled and the frame shall not be
+        resized either because resizing is inhibited or the frame is
+        fullheight, some (usually harmless) display artifacts like a
+        doubled mode line may show up.  Sometimes the configuration
+        gets messed up in a more serious fashion though and you may
+        have to resize the frame to get it back in a normal state.  */
+      if (f->output_data.x->widget)
+       {
+#ifdef USE_MOTIF
+         XtVaGetValues (f->output_data.x->widget, XtNx, &x1, XtNy, &y1, NULL);
+         if (x1 == 0 && y1 == 0)
+           XtVaSetValues (f->output_data.x->widget, XtNx, x0, XtNy, y0, NULL);
+         /* When resizing is inhibited and a normal Motif frame is not
+            fullheight, we have to explicitly request its old sizes
+            here since otherwise turning off the menu bar will shrink
+            the frame but turning them on again will not resize it
+            back.  For a fullheight frame we let the window manager
+            deal with this problem.  */
+         if (frame_inhibit_resize (f, false, Qmenu_bar_lines)
+             && !EQ (get_frame_param (f, Qfullscreen), Qfullheight))
+           adjust_frame_size (f, old_width, old_height, 1, false,
+                              Qmenu_bar_lines);
+         else
+           adjust_frame_size (f, -1, -1, 2, false, Qmenu_bar_lines);
+#else
+         adjust_frame_size (f, -1, -1, 2, false, Qmenu_bar_lines);
+#endif /* USE_MOTIF */
+       }
+      else
+       {
+#ifdef USE_MOTIF
+         if (WINDOWP (FRAME_ROOT_WINDOW (f))
+             /* See comment above.  */
+             && frame_inhibit_resize (f, false, Qmenu_bar_lines)
+             && !EQ (get_frame_param (f, Qfullscreen), Qfullheight))
+           adjust_frame_size (f, old_width, old_height, 1, false,
+                              Qmenu_bar_lines);
+#endif
+       }
+
+      unblock_input ();
+    }
+}
+#endif /* not USE_GTK */
+
+#endif /* USE_X_TOOLKIT || USE_GTK */
+
+/* x_menu_show actually displays a menu using the panes and items in menu_items
+   and returns the value selected from it.
+   There are two versions of x_menu_show, one for Xt and one for Xlib.
+   Both assume input is blocked by the caller.  */
+
+/* F is the frame the menu is for.
+   X and Y are the frame-relative specified position,
+   relative to the inside upper left corner of the frame F.
+   Bitfield MENUFLAGS bits are:
+   MENU_FOR_CLICK is set if this menu was invoked for a mouse click.
+   MENU_KEYMAPS is set if this menu was specified with keymaps;
+    in that case, we return a list containing the chosen item's value
+    and perhaps also the pane's prefix.
+   TITLE is the specified menu title.
+   ERROR is a place to store an error message string in case of failure.
+   (We return nil on failure, but the value doesn't actually matter.)  */
+
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+
+/* The item selected in the popup menu.  */
+static Lisp_Object *volatile menu_item_selection;
+
+#ifdef USE_GTK
+
+/* Used when position a popup menu.  See menu_position_func and
+   create_and_show_popup_menu below.  */
+struct next_popup_x_y
+{
+  struct frame *f;
+  int x;
+  int y;
+};
+
+/* The menu position function to use if we are not putting a popup
+   menu where the pointer is.
+   MENU is the menu to pop up.
+   X and Y shall on exit contain x/y where the menu shall pop up.
+   PUSH_IN is not documented in the GTK manual.
+   USER_DATA is any data passed in when calling gtk_menu_popup.
+   Here it points to a struct next_popup_x_y where the coordinates
+   to store in *X and *Y are as well as the frame for the popup.
+
+   Here only X and Y are used.  */
+static void
+menu_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, 
gpointer user_data)
+{
+  struct next_popup_x_y *data = user_data;
+  GtkRequisition req;
+  int max_x = -1;
+  int max_y = -1;
+#ifdef HAVE_GTK3
+  int scale;
+#endif
+
+  Lisp_Object frame, workarea;
+
+  XSETFRAME (frame, data->f);
+
+#ifdef HAVE_GTK3
+  scale = xg_get_scale (data->f);
+#endif
+  /* TODO: Get the monitor workarea directly without calculating other
+     items in x-display-monitor-attributes-list. */
+  workarea = call3 (Qframe_monitor_workarea,
+                    Qnil,
+                    make_fixnum (data->x),
+                    make_fixnum (data->y));
+
+  if (CONSP (workarea))
+    {
+      int min_x, min_y;
+
+      min_x = XFIXNUM (XCAR (workarea));
+      min_y = XFIXNUM (Fnth (make_fixnum (1), workarea));
+      max_x = min_x + XFIXNUM (Fnth (make_fixnum (2), workarea));
+      max_y = min_y + XFIXNUM (Fnth (make_fixnum (3), workarea));
+    }
+
+  if (max_x < 0 || max_y < 0)
+    {
+      struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (data->f);
+
+      max_x = x_display_pixel_width (dpyinfo);
+      max_y = x_display_pixel_height (dpyinfo);
+    }
+
+  /* frame-monitor-workarea and {x,y}_display_pixel_width/height all
+     return device pixels, but GTK wants scaled pixels.  The positions
+     passed in via data were already scaled for us.  */
+#ifdef HAVE_GTK3
+  max_x /= scale;
+  max_y /= scale;
+#endif
+  *x = data->x;
+  *y = data->y;
+
+  /* Check if there is room for the menu.  If not, adjust x/y so that
+     the menu is fully visible.  gtk_widget_get_preferred_size returns
+     scaled pixels, so there is no need to apply the scaling
+     factor.  */
+  gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &req);
+  if (data->x + req.width > max_x)
+    *x -= data->x + req.width - max_x;
+  if (data->y + req.height > max_y)
+    *y -= data->y + req.height - max_y;
+}
+
+static void
+popup_selection_callback (GtkWidget *widget, gpointer client_data)
+{
+  xg_menu_item_cb_data *cb_data = client_data;
+
+  if (xg_crazy_callback_abort) return;
+  if (cb_data) menu_item_selection = cb_data->call_data;
+}
+
+static void
+pop_down_menu (void *arg)
+{
+  popup_activated_flag = 0;
+  block_input ();
+  gtk_widget_destroy (GTK_WIDGET (arg));
+  unblock_input ();
+}
+
+/* Pop up the menu for frame F defined by FIRST_WV at X/Y and loop until the
+   menu pops down.
+   menu_item_selection will be set to the selection.  */
+static void
+create_and_show_popup_menu (struct frame *f, widget_value *first_wv,
+                           int x, int y, bool for_click)
+{
+  int i;
+  GtkWidget *menu;
+  GtkMenuPositionFunc pos_func = 0;  /* Pop up at pointer.  */
+  struct next_popup_x_y popup_x_y;
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+  bool use_pos_func = ! for_click;
+
+#ifdef HAVE_GTK3
+  /* Always use position function for Gtk3.  Otherwise menus may become
+     too small to show anything.  */
+  use_pos_func = true;
+#endif
+
+  eassert (FRAME_X_P (f));
+
+  xg_crazy_callback_abort = true;
+  menu = xg_create_widget ("popup", first_wv->name, f, first_wv,
+                           G_CALLBACK (popup_selection_callback),
+                           G_CALLBACK (popup_deactivate_callback),
+                           G_CALLBACK (menu_highlight_callback));
+  xg_crazy_callback_abort = false;
+
+  if (use_pos_func)
+    {
+      Window dummy_window;
+
+      /* Not invoked by a click.  pop up at x/y.  */
+      pos_func = menu_position_func;
+
+      /* Adjust coordinates to be root-window-relative.  */
+      block_input ();
+      XTranslateCoordinates (FRAME_X_DISPLAY (f),
+
+                             /* From-window, to-window.  */
+                             FRAME_X_WINDOW (f),
+                             FRAME_DISPLAY_INFO (f)->root_window,
+
+                             /* From-position, to-position.  */
+                             x, y, &x, &y,
+
+                             /* Child of win.  */
+                             &dummy_window);
+#ifdef HAVE_GTK3
+      /* Use window scaling factor to adjust position for hidpi screens. */
+      x /= xg_get_scale (f);
+      y /= xg_get_scale (f);
+#endif
+      unblock_input ();
+      popup_x_y.x = x;
+      popup_x_y.y = y;
+      popup_x_y.f = f;
+
+      i = 0;  /* gtk_menu_popup needs this to be 0 for a non-button popup.  */
+    }
+
+  if (for_click)
+    {
+      for (i = 0; i < 5; i++)
+        if (FRAME_DISPLAY_INFO (f)->grabbed & (1 << i))
+          break;
+      /* If keys aren't grabbed (i.e., a mouse up event), use 0.  */
+      if (i == 5) i = 0;
+    }
+
+  /* Display the menu.  */
+  gtk_widget_show_all (menu);
+
+  gtk_menu_popup (GTK_MENU (menu), 0, 0, pos_func, &popup_x_y, i,
+                 FRAME_DISPLAY_INFO (f)->last_user_time);
+
+  record_unwind_protect_ptr (pop_down_menu, menu);
+
+  if (gtk_widget_get_mapped (menu))
+    {
+      /* Set this to one.  popup_widget_loop increases it by one, so it becomes
+         two.  show_help_echo uses this to detect popup menus.  */
+      popup_activated_flag = 1;
+      /* Process events that apply to the menu.  */
+      popup_widget_loop (true, menu);
+    }
+
+  unbind_to (specpdl_count, Qnil);
+
+  /* Must reset this manually because the button release event is not passed
+     to Emacs event loop. */
+  FRAME_DISPLAY_INFO (f)->grabbed = 0;
+}
+
+#else /* not USE_GTK */
+
+/* We need a unique id for each widget handled by the Lucid Widget
+   library.
+
+   For the main windows, and popup menus, we use this counter, which we
+   increment each time after use.  This starts from WIDGET_ID_TICK_START.
+
+   For menu bars, we use numbers starting at 0, counted in
+   next_menubar_widget_id.  */
+LWLIB_ID widget_id_tick;
+
+static void
+popup_selection_callback (Widget widget, LWLIB_ID id, XtPointer client_data)
+{
+  menu_item_selection = client_data;
+}
+
+/* ID is the LWLIB ID of the dialog box.  */
+
+static void
+pop_down_menu (int id)
+{
+  block_input ();
+  lw_destroy_all_widgets ((LWLIB_ID) id);
+  unblock_input ();
+  popup_activated_flag = 0;
+}
+
+/* Pop up the menu for frame F defined by FIRST_WV at X/Y and loop until the
+   menu pops down.
+   menu_item_selection will be set to the selection.  */
+static void
+create_and_show_popup_menu (struct frame *f, widget_value *first_wv,
+                           int x, int y, bool for_click)
+{
+  int i;
+  Arg av[2];
+  int ac = 0;
+  XEvent dummy;
+  XButtonPressedEvent *event = &(dummy.xbutton);
+  LWLIB_ID menu_id;
+  Widget menu;
+  Window dummy_window;
+
+  eassert (FRAME_X_P (f));
+
+#ifdef USE_LUCID
+  apply_systemfont_to_menu (f, f->output_data.x->widget);
+#endif
+
+  menu_id = widget_id_tick++;
+  menu = lw_create_widget ("popup", first_wv->name, menu_id, first_wv,
+                           f->output_data.x->widget, true, 0,
+                           popup_selection_callback,
+                           popup_deactivate_callback,
+                           menu_highlight_callback);
+
+  event->type = ButtonPress;
+  event->serial = 0;
+  event->send_event = false;
+  event->display = FRAME_X_DISPLAY (f);
+  event->time = CurrentTime;
+  event->root = FRAME_DISPLAY_INFO (f)->root_window;
+  event->window = event->subwindow = event->root;
+  event->x = x;
+  event->y = y;
+
+  /* Adjust coordinates to be root-window-relative.  */
+  block_input ();
+  x += FRAME_LEFT_SCROLL_BAR_AREA_WIDTH (f);
+  XTranslateCoordinates (FRAME_X_DISPLAY (f),
+
+                         /* From-window, to-window.  */
+                         FRAME_X_WINDOW (f),
+                         FRAME_DISPLAY_INFO (f)->root_window,
+
+                         /* From-position, to-position.  */
+                         x, y, &x, &y,
+
+                         /* Child of win.  */
+                         &dummy_window);
+  unblock_input ();
+
+  event->x_root = x;
+  event->y_root = y;
+
+  event->state = 0;
+  event->button = 0;
+  for (i = 0; i < 5; i++)
+    if (FRAME_DISPLAY_INFO (f)->grabbed & (1 << i))
+      event->button = i;
+
+  /* Don't allow any geometry request from the user.  */
+  XtSetArg (av[ac], (char *) XtNgeometry, 0); ac++;
+  XtSetValues (menu, av, ac);
+#if defined HAVE_XINPUT2 && defined USE_LUCID
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  /* Clear the XI2 grab so lwlib can set a core grab.  */
+
+  if (dpyinfo->num_devices)
+    {
+      for (int i = 0; i < dpyinfo->num_devices; ++i)
+       {
+         if (dpyinfo->devices[i].grab)
+           XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id,
+                           CurrentTime);
+       }
+    }
+#endif
+  /* Display the menu.  */
+  lw_popup_menu (menu, &dummy);
+  popup_activated_flag = 1;
+  x_activate_timeout_atimer ();
+
+  {
+    ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+    record_unwind_protect_int (pop_down_menu, (int) menu_id);
+
+    /* Process events that apply to the menu.  */
+    popup_get_selection (0, FRAME_DISPLAY_INFO (f), menu_id, true);
+
+    unbind_to (specpdl_count, Qnil);
+  }
+}
+
+#endif /* not USE_GTK */
+
+static void
+cleanup_widget_value_tree (void *arg)
+{
+  free_menubar_widget_value_tree (arg);
+}
+
+Lisp_Object
+x_menu_show (struct frame *f, int x, int y, int menuflags,
+            Lisp_Object title, const char **error_name)
+{
+  int i;
+  widget_value *wv, *save_wv = 0, *first_wv = 0, *prev_wv = 0;
+  widget_value **submenu_stack
+    = alloca (menu_items_used * sizeof *submenu_stack);
+  Lisp_Object *subprefix_stack
+    = alloca (menu_items_used * sizeof *subprefix_stack);
+  int submenu_depth = 0;
+
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  eassert (FRAME_X_P (f));
+
+  *error_name = NULL;
+
+  if (menu_items_used <= MENU_ITEMS_PANE_LENGTH)
+    {
+      *error_name = "Empty menu";
+      return Qnil;
+    }
+
+  block_input ();
+
+  /* Create a tree of widget_value objects
+     representing the panes and their items.  */
+  wv = make_widget_value ("menu", NULL, true, Qnil);
+  wv->button_type = BUTTON_TYPE_NONE;
+  first_wv = wv;
+  bool first_pane = true;
+
+  /* Loop over all panes and items, filling in the tree.  */
+  i = 0;
+  while (i < menu_items_used)
+    {
+      if (NILP (AREF (menu_items, i)))
+       {
+         submenu_stack[submenu_depth++] = save_wv;
+         save_wv = prev_wv;
+         prev_wv = 0;
+         first_pane = true;
+         i++;
+       }
+      else if (EQ (AREF (menu_items, i), Qlambda))
+       {
+         prev_wv = save_wv;
+         save_wv = submenu_stack[--submenu_depth];
+         first_pane = false;
+         i++;
+       }
+      else if (EQ (AREF (menu_items, i), Qt)
+              && submenu_depth != 0)
+       i += MENU_ITEMS_PANE_LENGTH;
+      /* Ignore a nil in the item list.
+        It's meaningful only for dialog boxes.  */
+      else if (EQ (AREF (menu_items, i), Qquote))
+       i += 1;
+      else if (EQ (AREF (menu_items, i), Qt))
+       {
+         /* Create a new pane.  */
+         Lisp_Object pane_name, prefix;
+         const char *pane_string;
+
+         pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
+         prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+
+#ifndef HAVE_MULTILINGUAL_MENU
+         if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
+           {
+             pane_name = ENCODE_MENU_STRING (pane_name);
+             ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
+           }
+#endif
+         pane_string = (NILP (pane_name)
+                        ? "" : SSDATA (pane_name));
+         /* If there is just one top-level pane, put all its items directly
+            under the top-level menu.  */
+         if (menu_items_n_panes == 1)
+           pane_string = "";
+
+         /* If the pane has a meaningful name,
+            make the pane a top-level menu item
+            with its items as a submenu beneath it.  */
+         if (!(menuflags & MENU_KEYMAPS) && strcmp (pane_string, ""))
+           {
+             wv = make_widget_value (pane_string, NULL, true, Qnil);
+             if (save_wv)
+               save_wv->next = wv;
+             else
+               first_wv->contents = wv;
+             if ((menuflags & MENU_KEYMAPS) && !NILP (prefix))
+               wv->name++;
+             wv->button_type = BUTTON_TYPE_NONE;
+             save_wv = wv;
+             prev_wv = 0;
+           }
+         else if (first_pane)
+           {
+             save_wv = wv;
+             prev_wv = 0;
+           }
+         first_pane = false;
+         i += MENU_ITEMS_PANE_LENGTH;
+       }
+      else
+       {
+         /* Create a new item within current pane.  */
+         Lisp_Object item_name, enable, descrip, def, type, selected, help;
+         item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+         enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+         descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
+         def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
+         type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
+         selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
+         help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
+
+#ifndef HAVE_MULTILINGUAL_MENU
+          if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
+           {
+             item_name = ENCODE_MENU_STRING (item_name);
+             ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
+           }
+
+          if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
+           {
+             descrip = ENCODE_MENU_STRING (descrip);
+             ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
+           }
+#endif /* not HAVE_MULTILINGUAL_MENU */
+
+         wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
+                                 STRINGP (help) ? help : Qnil);
+         if (prev_wv)
+           prev_wv->next = wv;
+         else if (!save_wv)
+           {
+             /* This emacs_abort call pacifies gcc 11.2.1 when Emacs
+                is configured with --enable-gcc-warnings.  FIXME: If
+                save_wv can be null, do something better; otherwise,
+                explain why save_wv cannot be null.  */
+             emacs_abort ();
+           }
+         else
+           save_wv->contents = wv;
+         if (!NILP (descrip))
+           wv->key = SSDATA (descrip);
+         /* If this item has a null value,
+            make the call_data null so that it won't display a box
+            when the mouse is on it.  */
+         wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
+
+         if (NILP (type))
+           wv->button_type = BUTTON_TYPE_NONE;
+         else if (EQ (type, QCtoggle))
+           wv->button_type = BUTTON_TYPE_TOGGLE;
+         else if (EQ (type, QCradio))
+           wv->button_type = BUTTON_TYPE_RADIO;
+         else
+           emacs_abort ();
+
+         wv->selected = !NILP (selected);
+
+         prev_wv = wv;
+
+         i += MENU_ITEMS_ITEM_LENGTH;
+       }
+    }
+
+  /* Deal with the title, if it is non-nil.  */
+  if (!NILP (title))
+    {
+      widget_value *wv_title;
+      widget_value *wv_sep1 = make_widget_value ("--", NULL, false, Qnil);
+      widget_value *wv_sep2 = make_widget_value ("--", NULL, false, Qnil);
+
+      wv_sep2->next = first_wv->contents;
+      wv_sep1->next = wv_sep2;
+
+#ifndef HAVE_MULTILINGUAL_MENU
+      if (STRING_MULTIBYTE (title))
+       title = ENCODE_MENU_STRING (title);
+#endif
+
+      wv_title = make_widget_value (SSDATA (title), NULL, true, Qnil);
+      wv_title->button_type = BUTTON_TYPE_NONE;
+      wv_title->next = wv_sep1;
+      first_wv->contents = wv_title;
+    }
+
+  /* No selection has been chosen yet.  */
+  menu_item_selection = 0;
+
+  /* Make sure to free the widget_value objects we used to specify the
+     contents even with longjmp.  */
+  record_unwind_protect_ptr (cleanup_widget_value_tree, first_wv);
+
+  /* Actually create and show the menu until popped down.  */
+  create_and_show_popup_menu (f, first_wv, x, y,
+                             menuflags & MENU_FOR_CLICK);
+
+  unbind_to (specpdl_count, Qnil);
+
+  /* Find the selected item, and its pane, to return
+     the proper value.  */
+  if (menu_item_selection != 0)
+    {
+      Lisp_Object prefix, entry;
+
+      prefix = entry = Qnil;
+      i = 0;
+      while (i < menu_items_used)
+       {
+         if (NILP (AREF (menu_items, i)))
+           {
+             subprefix_stack[submenu_depth++] = prefix;
+             prefix = entry;
+             i++;
+           }
+         else if (EQ (AREF (menu_items, i), Qlambda))
+           {
+             prefix = subprefix_stack[--submenu_depth];
+             i++;
+           }
+         else if (EQ (AREF (menu_items, i), Qt))
+           {
+             prefix
+               = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+             i += MENU_ITEMS_PANE_LENGTH;
+           }
+         /* Ignore a nil in the item list.
+            It's meaningful only for dialog boxes.  */
+         else if (EQ (AREF (menu_items, i), Qquote))
+           i += 1;
+         else
+           {
+             entry
+               = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+             if (menu_item_selection == aref_addr (menu_items, i))
+               {
+                 if (menuflags & MENU_KEYMAPS)
+                   {
+                     int j;
+
+                     entry = list1 (entry);
+                     if (!NILP (prefix))
+                       entry = Fcons (prefix, entry);
+                     for (j = submenu_depth - 1; j >= 0; j--)
+                       if (!NILP (subprefix_stack[j]))
+                         entry = Fcons (subprefix_stack[j], entry);
+                   }
+                 unblock_input ();
+                 return entry;
+               }
+             i += MENU_ITEMS_ITEM_LENGTH;
+           }
+       }
+    }
+  else if (!(menuflags & MENU_FOR_CLICK))
+    {
+      unblock_input ();
+      /* Make "Cancel" equivalent to C-g.  */
+      quit ();
+    }
+
+  unblock_input ();
+  return Qnil;
+}
+
+#ifdef USE_GTK
+static void
+dialog_selection_callback (GtkWidget *widget, gpointer client_data)
+{
+  /* Treat the pointer as an integer.  There's no problem
+     as long as pointers have enough bits to hold small integers.  */
+  if ((intptr_t) client_data != -1)
+    menu_item_selection = client_data;
+
+  popup_activated_flag = 0;
+}
+
+/* Pop up the dialog for frame F defined by FIRST_WV and loop until the
+   dialog pops down.
+   menu_item_selection will be set to the selection.  */
+static void
+create_and_show_dialog (struct frame *f, widget_value *first_wv)
+{
+  GtkWidget *menu;
+
+  eassert (FRAME_X_P (f));
+
+  menu = xg_create_widget ("dialog", first_wv->name, f, first_wv,
+                           G_CALLBACK (dialog_selection_callback),
+                           G_CALLBACK (popup_deactivate_callback),
+                           0);
+
+  if (menu)
+    {
+      ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+      record_unwind_protect_ptr (pop_down_menu, menu);
+
+      /* Display the menu.  */
+      gtk_widget_show_all (menu);
+
+      /* Process events that apply to the menu.  */
+      popup_widget_loop (true, menu);
+
+      unbind_to (specpdl_count, Qnil);
+    }
+}
+
+#else /* not USE_GTK */
+static void
+dialog_selection_callback (Widget widget, LWLIB_ID id, XtPointer client_data)
+{
+  /* Treat the pointer as an integer.  There's no problem
+     as long as pointers have enough bits to hold small integers.  */
+  if ((intptr_t) client_data != -1)
+    menu_item_selection = client_data;
+
+  block_input ();
+  lw_destroy_all_widgets (id);
+  unblock_input ();
+  popup_activated_flag = 0;
+}
+
+
+/* Pop up the dialog for frame F defined by FIRST_WV and loop until the
+   dialog pops down.
+   menu_item_selection will be set to the selection.  */
+static void
+create_and_show_dialog (struct frame *f, widget_value *first_wv)
+{
+  LWLIB_ID dialog_id;
+
+  eassert (FRAME_X_P (f));
+
+  dialog_id = widget_id_tick++;
+#ifdef USE_LUCID
+  apply_systemfont_to_dialog (f->output_data.x->widget);
+#endif
+  lw_create_widget (first_wv->name, "dialog", dialog_id, first_wv,
+                    f->output_data.x->widget, true, 0,
+                    dialog_selection_callback, 0, 0);
+  lw_modify_all_widgets (dialog_id, first_wv->contents, True);
+  /* Display the dialog box.  */
+  lw_pop_up_all_widgets (dialog_id);
+  popup_activated_flag = 1;
+  x_activate_timeout_atimer ();
+
+  /* Process events that apply to the dialog box.
+     Also handle timers.  */
+  {
+    ptrdiff_t count = SPECPDL_INDEX ();
+
+    /* xdialog_show_unwind is responsible for popping the dialog box down.  */
+
+    record_unwind_protect_int (pop_down_menu, (int) dialog_id);
+
+    popup_get_selection (0, FRAME_DISPLAY_INFO (f), dialog_id, true);
+
+    unbind_to (count, Qnil);
+  }
+}
+
+#endif /* not USE_GTK */
+
+static const char * button_names [] = {
+  "button1", "button2", "button3", "button4", "button5",
+  "button6", "button7", "button8", "button9", "button10" };
+
+static Lisp_Object
+x_dialog_show (struct frame *f, Lisp_Object title,
+              Lisp_Object header, const char **error_name)
+{
+  int i, nb_buttons=0;
+  char dialog_name[6];
+
+  widget_value *wv, *first_wv = 0, *prev_wv = 0;
+
+  /* Number of elements seen so far, before boundary.  */
+  int left_count = 0;
+  /* Whether we've seen the boundary between left-hand elts and right-hand.  */
+  bool boundary_seen = false;
+
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  eassert (FRAME_X_P (f));
+
+  *error_name = NULL;
+
+  if (menu_items_n_panes > 1)
+    {
+      *error_name = "Multiple panes in dialog box";
+      return Qnil;
+    }
+
+  /* Create a tree of widget_value objects
+     representing the text label and buttons.  */
+  {
+    Lisp_Object pane_name;
+    const char *pane_string;
+    pane_name = AREF (menu_items, MENU_ITEMS_PANE_NAME);
+    pane_string = (NILP (pane_name)
+                  ? "" : SSDATA (pane_name));
+    prev_wv = make_widget_value ("message", (char *) pane_string, true, Qnil);
+    first_wv = prev_wv;
+
+    /* Loop over all panes and items, filling in the tree.  */
+    i = MENU_ITEMS_PANE_LENGTH;
+    while (i < menu_items_used)
+      {
+
+       /* Create a new item within current pane.  */
+       Lisp_Object item_name, enable, descrip;
+       item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+       enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+       descrip
+         = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
+
+       if (NILP (item_name))
+         {
+           free_menubar_widget_value_tree (first_wv);
+           *error_name = "Submenu in dialog items";
+           return Qnil;
+         }
+       if (EQ (item_name, Qquote))
+         {
+           /* This is the boundary between left-side elts
+              and right-side elts.  Stop incrementing right_count.  */
+           boundary_seen = true;
+           i++;
+           continue;
+         }
+       if (nb_buttons >= 9)
+         {
+           free_menubar_widget_value_tree (first_wv);
+           *error_name = "Too many dialog items";
+           return Qnil;
+         }
+
+       wv = make_widget_value (button_names[nb_buttons],
+                               SSDATA (item_name),
+                               !NILP (enable), Qnil);
+       prev_wv->next = wv;
+       if (!NILP (descrip))
+         wv->key = SSDATA (descrip);
+       wv->call_data = aref_addr (menu_items, i);
+       prev_wv = wv;
+
+       if (! boundary_seen)
+         left_count++;
+
+       nb_buttons++;
+       i += MENU_ITEMS_ITEM_LENGTH;
+      }
+
+    /* If the boundary was not specified,
+       by default put half on the left and half on the right.  */
+    if (! boundary_seen)
+      left_count = nb_buttons - nb_buttons / 2;
+
+    wv = make_widget_value (dialog_name, NULL, false, Qnil);
+
+    /*  Frame title: 'Q' = Question, 'I' = Information.
+        Can also have 'E' = Error if, one day, we want
+        a popup for errors. */
+    if (NILP (header))
+      dialog_name[0] = 'Q';
+    else
+      dialog_name[0] = 'I';
+
+    /* Dialog boxes use a really stupid name encoding
+       which specifies how many buttons to use
+       and how many buttons are on the right. */
+    dialog_name[1] = '0' + nb_buttons;
+    dialog_name[2] = 'B';
+    dialog_name[3] = 'R';
+    /* Number of buttons to put on the right.  */
+    dialog_name[4] = '0' + nb_buttons - left_count;
+    dialog_name[5] = 0;
+    wv->contents = first_wv;
+    first_wv = wv;
+  }
+
+  /* No selection has been chosen yet.  */
+  menu_item_selection = 0;
+
+  /* Make sure to free the widget_value objects we used to specify the
+     contents even with longjmp.  */
+  record_unwind_protect_ptr (cleanup_widget_value_tree, first_wv);
+
+  /* Actually create and show the dialog.  */
+  create_and_show_dialog (f, first_wv);
+
+  unbind_to (specpdl_count, Qnil);
+
+  /* Find the selected item, and its pane, to return
+     the proper value.  */
+  if (menu_item_selection != 0)
+    {
+      i = 0;
+      while (i < menu_items_used)
+       {
+         Lisp_Object entry;
+
+         if (EQ (AREF (menu_items, i), Qt))
+           i += MENU_ITEMS_PANE_LENGTH;
+         else if (EQ (AREF (menu_items, i), Qquote))
+           {
+             /* This is the boundary between left-side elts and
+                right-side elts.  */
+             ++i;
+           }
+         else
+           {
+             entry
+               = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+             if (menu_item_selection == aref_addr (menu_items, i))
+               return entry;
+             i += MENU_ITEMS_ITEM_LENGTH;
+           }
+       }
+    }
+  else
+    /* Make "Cancel" equivalent to C-g.  */
+    quit ();
+
+  return Qnil;
+}
+
+Lisp_Object
+xw_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
+{
+  Lisp_Object title;
+  const char *error_name;
+  Lisp_Object selection;
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  check_window_system (f);
+
+  /* Decode the dialog items from what was specified.  */
+  title = Fcar (contents);
+  CHECK_STRING (title);
+  record_unwind_protect_void (unuse_menu_items);
+
+  if (NILP (Fcar (Fcdr (contents))))
+    /* No buttons specified, add an "Ok" button so users can pop down
+       the dialog.  Also, the lesstif/motif version crashes if there are
+       no buttons.  */
+    contents = list2 (title, Fcons (build_string ("Ok"), Qt));
+
+  list_of_panes (list1 (contents));
+
+  /* Display them in a dialog box.  */
+  block_input ();
+  selection = x_dialog_show (f, title, header, &error_name);
+  unblock_input ();
+
+  unbind_to (specpdl_count, Qnil);
+  discard_menu_items ();
+
+  if (error_name) error ("%s", error_name);
+  return selection;
+}
+
+#else /* not USE_X_TOOLKIT && not USE_GTK */
+
+/* The frame of the last activated non-toolkit menu bar.
+   Used to generate menu help events.  */
+
+static struct frame *menu_help_frame;
+
+
+/* Show help HELP_STRING, or clear help if HELP_STRING is null.
+
+   PANE is the pane number, and ITEM is the menu item number in
+   the menu (currently not used).
+
+   This cannot be done with generating a HELP_EVENT because
+   XMenuActivate contains a loop that doesn't let Emacs process
+   keyboard events.  */
+
+static void
+menu_help_callback (char const *help_string, int pane, int item)
+{
+  Lisp_Object *first_item;
+  Lisp_Object pane_name;
+  Lisp_Object menu_object;
+
+  first_item = XVECTOR (menu_items)->contents;
+  if (EQ (first_item[0], Qt))
+    pane_name = first_item[MENU_ITEMS_PANE_NAME];
+  else if (EQ (first_item[0], Qquote))
+    /* This shouldn't happen, see x_menu_show.  */
+    pane_name = empty_unibyte_string;
+  else
+    pane_name = first_item[MENU_ITEMS_ITEM_NAME];
+
+  /* (menu-item MENU-NAME PANE-NUMBER)  */
+  menu_object = list3 (Qmenu_item, pane_name, make_fixnum (pane));
+  show_help_echo (help_string ? build_string (help_string) : Qnil,
+                 Qnil, menu_object, make_fixnum (item));
+}
+
+struct pop_down_menu
+{
+  struct frame *frame;
+  XMenu *menu;
+};
+
+static void
+pop_down_menu (void *arg)
+{
+  struct pop_down_menu *data = arg;
+  struct frame *f = data->frame;
+  XMenu *menu = data->menu;
+
+  block_input ();
+#ifndef MSDOS
+  XUngrabPointer (FRAME_X_DISPLAY (f), CurrentTime);
+  XUngrabKeyboard (FRAME_X_DISPLAY (f), CurrentTime);
+#endif
+  XMenuDestroy (FRAME_X_DISPLAY (f), menu);
+
+#ifdef HAVE_X_WINDOWS
+  /* Assume the mouse has moved out of the X window.
+     If it has actually moved in, we will get an EnterNotify.  */
+  x_mouse_leave (FRAME_DISPLAY_INFO (f));
+
+  /* State that no mouse buttons are now held.
+     (The oldXMenu code doesn't track this info for us.)
+     That is not necessarily true, but the fiction leads to reasonable
+     results, and it is a pain to ask which are actually held now.  */
+  FRAME_DISPLAY_INFO (f)->grabbed = 0;
+
+#endif /* HAVE_X_WINDOWS */
+
+  unblock_input ();
+}
+
+
+Lisp_Object
+x_menu_show (struct frame *f, int x, int y, int menuflags,
+            Lisp_Object title, const char **error_name)
+{
+  Window root;
+  XMenu *menu;
+  int pane, selidx, lpane, status;
+  Lisp_Object entry = Qnil;
+  Lisp_Object pane_prefix;
+  char *datap;
+  int ulx, uly, width, height;
+  int dispwidth, dispheight;
+  int i, j, lines, maxlines;
+  int maxwidth;
+  int dummy_int;
+  unsigned int dummy_uint;
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  eassert (FRAME_X_P (f) || FRAME_MSDOS_P (f));
+
+  *error_name = 0;
+  if (menu_items_n_panes == 0)
+    return Qnil;
+
+  if (menu_items_used <= MENU_ITEMS_PANE_LENGTH)
+    {
+      *error_name = "Empty menu";
+      return Qnil;
+    }
+
+  USE_SAFE_ALLOCA;
+  block_input ();
+
+  /* Figure out which root window F is on.  */
+  XGetGeometry (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), &root,
+               &dummy_int, &dummy_int, &dummy_uint, &dummy_uint,
+               &dummy_uint, &dummy_uint);
+
+  /* Make the menu on that window.  */
+  menu = XMenuCreate (FRAME_X_DISPLAY (f), root, "emacs");
+  if (menu == NULL)
+    {
+      *error_name = "Can't create menu";
+      goto return_entry;
+    }
+
+  /* Don't GC while we prepare and show the menu,
+     because we give the oldxmenu library pointers to the
+     contents of strings.  */
+  inhibit_garbage_collection ();
+
+#ifdef HAVE_X_WINDOWS
+  {
+    /* Adjust coordinates to relative to the outer (window manager) window. */
+    int left_off, top_off;
+
+    x_real_pos_and_offsets (f, &left_off, NULL, &top_off, NULL,
+                            NULL, NULL, NULL, NULL, NULL);
+
+    x += left_off;
+    y += top_off;
+  }
+#endif /* HAVE_X_WINDOWS */
+
+  x += f->left_pos;
+  y += f->top_pos;
+
+  /* Create all the necessary panes and their items.  */
+  maxwidth = maxlines = lines = i = 0;
+  lpane = XM_FAILURE;
+  while (i < menu_items_used)
+    {
+      if (EQ (AREF (menu_items, i), Qt))
+       {
+         /* Create a new pane.  */
+         Lisp_Object pane_name, prefix;
+         const char *pane_string;
+
+          maxlines = max (maxlines, lines);
+          lines = 0;
+         pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
+         prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+         pane_string = (NILP (pane_name)
+                        ? "" : SSDATA (pane_name));
+         if ((menuflags & MENU_KEYMAPS) && !NILP (prefix))
+           pane_string++;
+
+         lpane = XMenuAddPane (FRAME_X_DISPLAY (f), menu, pane_string, true);
+         if (lpane == XM_FAILURE)
+           {
+             XMenuDestroy (FRAME_X_DISPLAY (f), menu);
+             *error_name = "Can't create pane";
+             goto return_entry;
+           }
+         i += MENU_ITEMS_PANE_LENGTH;
+
+         /* Find the width of the widest item in this pane.  */
+         j = i;
+         while (j < menu_items_used)
+           {
+             Lisp_Object item;
+             item = AREF (menu_items, j);
+             if (EQ (item, Qt))
+               break;
+             if (NILP (item))
+               {
+                 j++;
+                 continue;
+               }
+             width = SBYTES (item);
+             if (width > maxwidth)
+               maxwidth = width;
+
+             j += MENU_ITEMS_ITEM_LENGTH;
+           }
+       }
+      /* Ignore a nil in the item list.
+        It's meaningful only for dialog boxes.  */
+      else if (EQ (AREF (menu_items, i), Qquote))
+       i += 1;
+      else
+       {
+         /* Create a new item within current pane.  */
+         Lisp_Object item_name, enable, descrip, help;
+         char *item_data;
+         char const *help_string;
+
+         item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+         enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+         descrip
+           = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
+         help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
+         help_string = STRINGP (help) ? SSDATA (help) : NULL;
+
+         if (!NILP (descrip))
+           {
+             item_data = SAFE_ALLOCA (maxwidth + SBYTES (descrip) + 1);
+             memcpy (item_data, SSDATA (item_name), SBYTES (item_name));
+             for (j = SCHARS (item_name); j < maxwidth; j++)
+               item_data[j] = ' ';
+             memcpy (item_data + j, SSDATA (descrip), SBYTES (descrip));
+             item_data[j + SBYTES (descrip)] = 0;
+           }
+         else
+           item_data = SSDATA (item_name);
+
+         if (lpane == XM_FAILURE
+             || (XMenuAddSelection (FRAME_X_DISPLAY (f),
+                                    menu, lpane, 0, item_data,
+                                    !NILP (enable), help_string)
+                 == XM_FAILURE))
+           {
+             XMenuDestroy (FRAME_X_DISPLAY (f), menu);
+             *error_name = "Can't add selection to menu";
+             goto return_entry;
+           }
+         i += MENU_ITEMS_ITEM_LENGTH;
+          lines++;
+       }
+    }
+
+  maxlines = max (maxlines, lines);
+
+  /* All set and ready to fly.  */
+  XMenuRecompute (FRAME_X_DISPLAY (f), menu);
+  dispwidth = DisplayWidth (FRAME_X_DISPLAY (f), FRAME_X_SCREEN_NUMBER (f));
+  dispheight = DisplayHeight (FRAME_X_DISPLAY (f), FRAME_X_SCREEN_NUMBER (f));
+  x = min (x, dispwidth);
+  y = min (y, dispheight);
+  x = max (x, 1);
+  y = max (y, 1);
+  XMenuLocate (FRAME_X_DISPLAY (f), menu, 0, 0, x, y,
+              &ulx, &uly, &width, &height);
+  if (ulx+width > dispwidth)
+    {
+      x -= (ulx + width) - dispwidth;
+      ulx = dispwidth - width;
+    }
+  if (uly+height > dispheight)
+    {
+      y -= (uly + height) - dispheight;
+      uly = dispheight - height;
+    }
+#ifndef HAVE_X_WINDOWS
+  if (FRAME_HAS_MINIBUF_P (f) && uly+height > dispheight - 1)
+    {
+      /* Move the menu away of the echo area, to avoid overwriting the
+        menu with help echo messages or vice versa.  */
+      if (BUFFERP (echo_area_buffer[0]) && WINDOWP (echo_area_window))
+       {
+         y -= WINDOW_TOTAL_LINES (XWINDOW (echo_area_window));
+         uly -= WINDOW_TOTAL_LINES (XWINDOW (echo_area_window));
+       }
+      else
+       {
+         y--;
+         uly--;
+       }
+    }
+#endif
+  if (ulx < 0) x -= ulx;
+  if (uly < 0) y -= uly;
+
+  if (!(menuflags & MENU_FOR_CLICK))
+    {
+      /* If position was not given by a mouse click, adjust so upper left
+         corner of the menu as a whole ends up at given coordinates.  This
+         is what x-popup-menu says in its documentation.  */
+      x += width/2;
+      y += 1.5*height/(maxlines+2);
+    }
+
+  XMenuSetAEQ (menu, true);
+  XMenuSetFreeze (menu, true);
+  pane = selidx = 0;
+
+#ifndef MSDOS
+  XMenuActivateSetWaitFunction (x_menu_wait_for_event, FRAME_X_DISPLAY (f));
+#endif
+
+  record_unwind_protect_ptr (pop_down_menu,
+                            &(struct pop_down_menu) {f, menu});
+
+  /* Help display under X won't work because XMenuActivate contains
+     a loop that doesn't give Emacs a chance to process it.  */
+  menu_help_frame = f;
+
+#ifdef HAVE_XINPUT2
+  struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  /* Clear the XI2 grab so a core grab can be set.  */
+
+  if (dpyinfo->num_devices)
+    {
+      for (int i = 0; i < dpyinfo->num_devices; ++i)
+       {
+         if (dpyinfo->devices[i].grab)
+           XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id,
+                           CurrentTime);
+       }
+    }
+#endif
+
+  status = XMenuActivate (FRAME_X_DISPLAY (f), menu, &pane, &selidx,
+                          x, y, ButtonReleaseMask, &datap,
+                          menu_help_callback);
+  pane_prefix = Qnil;
+
+  switch (status)
+    {
+    case XM_SUCCESS:
+#ifdef XDEBUG
+      fprintf (stderr, "pane= %d line = %d\n", panes, selidx);
+#endif
+
+      /* Find the item number SELIDX in pane number PANE.  */
+      i = 0;
+      while (i < menu_items_used)
+       {
+         if (EQ (AREF (menu_items, i), Qt))
+           {
+             if (pane == 0)
+               pane_prefix
+                 = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+             pane--;
+             i += MENU_ITEMS_PANE_LENGTH;
+           }
+         else
+           {
+             if (pane == -1)
+               {
+                 if (selidx == 0)
+                   {
+                     entry
+                       = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+                     if (menuflags & MENU_KEYMAPS)
+                       {
+                         entry = list1 (entry);
+                         if (!NILP (pane_prefix))
+                           entry = Fcons (pane_prefix, entry);
+                       }
+                     break;
+                   }
+                 selidx--;
+               }
+             i += MENU_ITEMS_ITEM_LENGTH;
+           }
+       }
+      break;
+
+    case XM_FAILURE:
+      *error_name = "Can't activate menu";
+    case XM_IA_SELECT:
+      break;
+    case XM_NO_SELECT:
+      /* Make "Cancel" equivalent to C-g unless FOR_CLICK (which means
+        the menu was invoked with a mouse event as POSITION).  */
+      if (!(menuflags & MENU_FOR_CLICK))
+       {
+         unblock_input ();
+         quit ();
+       }
+      break;
+    }
+
+ return_entry:
+  unblock_input ();
+  return SAFE_FREE_UNBIND_TO (specpdl_count, entry);
+}
+
+#endif /* not USE_X_TOOLKIT */
+
+#ifndef MSDOS
+/* Detect if a dialog or menu has been posted.  MSDOS has its own
+   implementation on msdos.c.  */
+
+int
+popup_activated (void)
+{
+  return popup_activated_flag;
+}
+#endif /* not MSDOS */
+
+/* The following is used by delayed window autoselection.  */
+
+DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, 
Smenu_or_popup_active_p, 0, 0, 0,
+       doc: /* Return t if a menu or popup dialog is active.
+\(On MS Windows, this refers to the selected frame.)  */)
+  (void)
+{
+  return (popup_activated ()) ? Qt : Qnil;
+}
+
+
+static void syms_of_xmenu_for_pdumper (void);
+
+void
+syms_of_xmenu (void)
+{
+  DEFSYM (Qdebug_on_next_call, "debug-on-next-call");
+  defsubr (&Smenu_or_popup_active_p);
+
+#ifdef USE_GTK
+  DEFSYM (Qframe_monitor_workarea, "frame-monitor-workarea");
+#endif
+
+#if defined (USE_GTK) || defined (USE_X_TOOLKIT)
+  defsubr (&Sx_menu_bar_open_internal);
+  Ffset (intern_c_string ("accelerate-menu"),
+        intern_c_string (Sx_menu_bar_open_internal.s.symbol_name));
+#endif
+
+  pdumper_do_now_and_after_load (syms_of_xmenu_for_pdumper);
+}
+
+static void
+syms_of_xmenu_for_pdumper (void)
+{
+#ifdef USE_X_TOOLKIT
+  enum { WIDGET_ID_TICK_START = 1 << 16 };
+  widget_id_tick = WIDGET_ID_TICK_START;
+  next_menubar_widget_id = 1;
+#endif
+}



reply via email to

[Prev in Thread] Current Thread [Next in Thread]