1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2023 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
13 /* This module contains code for handling the popup menus. */
15 static Widget menushell;
16 static Widget queue_text_sink;
17 static Widget dialog_shell, dialog_widget;
19 static Widget text_create(uschar *, int);
21 static int highlighted_start, highlighted_end, highlighted_x, highlighted_y;
25 static Arg queue_get_arg[] = {
26 { "textSink", (XtArgVal)NULL },
27 { "textSource", (XtArgVal)NULL },
28 { "string", (XtArgVal)NULL } };
30 static Arg dialog_arg[] = {
31 { "label", (XtArgVal)"dialog" },
32 { "value", (XtArgVal)"value" } };
34 static Arg get_pos_args[] = {
35 {"x", (XtArgVal)NULL },
36 {"y", (XtArgVal)NULL } };
38 static Arg menushell_arg[] = {
39 { "label", (XtArgVal)NULL } };
41 static Arg button_arg[] = {
42 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
43 { XtNlabel, (XtArgVal) " Dismiss " },
44 { "left", XawChainLeft },
45 { "right", XawChainLeft },
46 { "top", XawChainBottom },
47 { "bottom", XawChainBottom } };
49 static Arg text_arg[] = {
50 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
51 { "editType", XawtextEdit },
52 { "string", (XtArgVal)"" }, /* dummy to get it going */
53 { "scrollVertical", XawtextScrollAlways },
54 { "wrap", XawtextWrapWord },
55 { "top", XawChainTop },
56 { "bottom", XawChainBottom } };
58 static Arg item_1_arg[] = {
59 { XtNfromVert, (XtArgVal)NULL }, /* must be first */
60 { "label", (XtArgVal)" Message log" } };
62 static Arg item_2_arg[] = {
63 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
64 { "label", (XtArgVal)" Headers" } };
66 static Arg item_3_arg[] = {
67 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
68 { "label", (XtArgVal)" Body" } };
70 static Arg item_4_arg[] = {
71 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
72 { "label", (XtArgVal)" Deliver message" } };
74 static Arg item_5_arg[] = {
75 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
76 { "label", (XtArgVal)" Freeze message" } };
78 static Arg item_6_arg[] = {
79 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
80 { "label", (XtArgVal)" Thaw message" } };
82 static Arg item_7_arg[] = {
83 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
84 { "label", (XtArgVal)" Give up on msg" } };
86 static Arg item_8_arg[] = {
87 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
88 { "label", (XtArgVal)" Remove message" } };
90 static Arg item_9_arg[] = {
91 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
92 { "label", (XtArgVal)"----------------" } };
94 static Arg item_10_arg[] = {
95 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
96 { "label", (XtArgVal)" Add recipient" } };
98 static Arg item_11_arg[] = {
99 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
100 { "label", (XtArgVal)" Mark delivered" } };
102 static Arg item_12_arg[] = {
103 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
104 { "label", (XtArgVal)" Mark all delivered" } };
106 static Arg item_13_arg[] = {
107 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
108 { "label", (XtArgVal)" Edit sender" } };
110 static Arg item_99_arg[] = {
111 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
112 { "label", (XtArgVal)" " } };
116 /*************************************************
117 * Destroy the menu when popped down *
118 *************************************************/
121 popdownAction(Widget w, XtPointer client_data, XtPointer call_data)
123 if (highlighted_x >= 0)
124 XawTextSinkDisplayText(queue_text_sink,
125 highlighted_x, highlighted_y,
126 highlighted_start, highlighted_end, 0);
133 /*************************************************
134 * Display the message log *
135 *************************************************/
138 msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
140 Widget text = text_create(US client_data, text_depth);
141 uschar * fname = NULL;
144 /* End up with the split version, so message looks right when non-exist */
146 for (int i = 0; i < (spool_is_split ? 2:1); i++)
148 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
149 fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
150 if ((f = fopen(CS fname, "r")))
155 text_showf(text, "%s: %s\n", fname, strerror(errno));
159 while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
166 /*************************************************
167 * Display the message body *
168 *************************************************/
171 bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
173 Widget text = text_create(US client_data, text_depth);
176 for (int i = 0; i < (spool_is_split? 2:1); i++)
179 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
180 fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
181 if ((f = fopen(CS fname, "r")))
186 text_showf(text, "Failed to open file: %s\n", strerror(errno));
192 while (Ufgets(buffer, sizeof(buffer), f) != NULL)
194 text_show(text, buffer);
195 count += Ustrlen(buffer);
196 if (count > body_max)
198 text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
208 /*************************************************
209 * Do something to a message *
210 *************************************************/
212 /* The output is not shown in a window for non-delivery actions that succeed,
213 unless action_output is set. We can't, however, tell until we have run
214 the command whether we want the output or not, so the pipe has to be set up in
218 ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
222 int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
223 uschar *quote = US"";
225 uschar *qualify = US"";
230 /* If the address arg is not empty and does not contain @ and there is a
231 qualify domain, qualify it. (But don't qualify '<>'.)*/
233 if (address_arg[0] != 0)
236 if (Ustrchr(address_arg, '@') == NULL &&
237 Ustrcmp(address_arg, "<>") != 0 &&
238 qualify_domain != NULL &&
239 qualify_domain[0] != 0)
242 qualify = qualify_domain;
245 sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
246 (alternate_config == NULL)? US"" : US"-C",
247 (alternate_config == NULL)? US"" : alternate_config,
248 action, id, quote, address_arg, at, qualify, quote);
250 /* If we know we are going to need the window, create it now. */
252 if (action_output || delivery)
254 text = text_create(id, text_depth);
255 text_showf(text, "%s\n", buffer);
258 /* Create the pipe for output. Remember, on most systems pipe[0] is
259 for reading and pipe[1] is for writing! Solaris, with its two-way
262 if (pipe(pipe_fd) != 0)
266 text = text_create(id, text_depth);
267 text_showf(text, "%s\n", buffer);
269 text_show(text, US"*** Failed to create pipe ***\n");
273 if ( fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
274 || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
276 perror("set nonblocking on pipe");
280 /* Delivering a message can take some time, and we want to show the
281 output as it goes along. This requires subprocesses and is coded below. For
282 other commands, we can assume an immediate response, and so need not waste
283 resources with subprocesses. If action_output is FALSE, don't show the
289 int save_stdout = dup(1);
290 int save_stderr = dup(2);
299 rc = system(CS buffer);
304 if (action_output || rc != 0)
308 text = text_create(id, text_depth);
309 text_showf(text, "%s\n", buffer);
311 while ((count = read(pipe_fd[0], buffer, 254)) > 0)
314 text_show(text, buffer);
320 dup2(save_stdout, 1);
321 dup2(save_stderr, 2);
325 /* If action was to change the sender, and it succeeded, we have to
326 update the in-store data. */
328 if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
330 queue_item *q = find_queue(id, queue_noop, 0);
333 if (q->sender) store_free(q->sender);
334 q->sender = store_malloc(Ustrlen(address_arg) + 1);
335 Ustrcpy(q->sender, address_arg);
339 /* If configured, cause a display update and return */
341 if (action_queue_update) tick_queue_accumulator = 999999;
345 /* Message is to be delivered. Ensure that it is marked unfrozen,
346 because nothing will get written to the log to show that this has
347 happened. (Other freezing/unfreezings get logged and picked up from
350 qq = find_queue(id, queue_noop, 0);
351 if (qq != NULL) qq->frozen = FALSE;
353 /* New, asynchronous code runs in a subprocess for commands that
354 will take some time. The main process does not wait. There is a
355 SIGCHLD handler in the main program that cleans up any terminating
358 if ((pid = fork()) == 0)
367 if (system(CS buffer)) ;
375 /* Main process - set up an item for the main ticker to watch. */
377 if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(errno)); else
379 pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
383 text_show(text, US"Run out of store\n");
390 p->next = pipe_chain;
400 /*************************************************
401 * Cause a message to be delivered *
402 *************************************************/
405 deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
407 ActOnMessage(US client_data, US"-v -M", US"");
410 /*************************************************
411 * Cause a message to be Frozen *
412 *************************************************/
415 freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
417 ActOnMessage(US client_data, US"-Mf", US"");
420 /*************************************************
421 * Cause a message to be thawed *
422 *************************************************/
425 thawAction(Widget w, XtPointer client_data, XtPointer call_data)
427 ActOnMessage(US client_data, US"-Mt", US"");
430 /*************************************************
431 * Take action using dialog data *
432 *************************************************/
434 /* This function is called after a dialog box has been filled
435 in. It is global because it is set up in the action table at
436 start-up time. If the string is empty, do nothing. */
439 dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c)
441 uschar *s = US XawDialogGetValueString(dialog_widget);
443 XtPopdown((Widget)dialog_shell);
444 XtDestroyWidget((Widget)dialog_shell);
445 while (isspace(*s)) s++;
447 if (actioned_message[0] != 0)
448 ActOnMessage(actioned_message, action_required, s);
450 NonMessageDialogue(s); /* When called from somewhere else */
456 /*************************************************
457 * Create a dialog box *
458 *************************************************/
460 /* The focus is grabbed exclusively, so nothing else can
461 be done to the application until the box is filled in. This
462 function is also used by the Hide button handler. */
465 create_dialog(uschar *label, uschar *value)
468 Dimension x, y, xx, yy;
469 XtTranslations pop_trans;
472 /* Get the position of a reference widget so the dialog box can be put
475 get_pos_args[0].value = (XtArgVal)(&x);
476 get_pos_args[1].value = (XtArgVal)(&y);
477 XtGetValues(dialog_ref_widget, get_pos_args, 2);
479 /* When this is not a message_specific thing, the position of the reference
480 widget is relative to the window. Get the position of the top level widget and
481 add to the position. */
483 if (dialog_ref_widget != menushell)
485 get_pos_args[0].value = (XtArgVal)(&xx);
486 get_pos_args[1].value = (XtArgVal)(&yy);
487 XtGetValues(toplevel_widget, get_pos_args, 2);
492 /* Create a transient shell for the dialog box. */
494 XtSetArg(warg[0], XtNtransientFor, queue_widget);
495 XtSetArg(warg[1], XtNx, x + 50);
496 XtSetArg(warg[2], XtNy, y + 50);
497 XtSetArg(warg[3], XtNallowShellResize, True);
498 dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
499 toplevel_widget, warg, 4);
501 /* Create the dialog box. */
503 dialog_arg[0].value = (XtArgVal)label;
504 dialog_arg[1].value = (XtArgVal)value;
505 dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
506 dialog_arg, XtNumber(dialog_arg));
508 /* Get the text widget from within the dialog box, give it the keyboard focus,
509 make it wider than the default, and override its translations to make Return
510 call the dialog action function. */
512 text = XtNameToWidget(dialog_widget, "value");
513 XawTextSetInsertionPoint(text, Ustrlen(value));
514 XtSetKeyboardFocus(dialog_widget, text);
515 xs_SetValues(text, 1, "width", 200);
516 pop_trans = XtParseTranslationTable(
517 "<Key>Return: dialogAction()\n");
518 XtOverrideTranslations(text, pop_trans);
520 /* Pop the thing up. */
522 XtPopup(dialog_shell, XtGrabExclusive);
527 /*************************************************
528 * Cause a recipient to be added *
529 *************************************************/
531 /* This just sets up the dialog box; the action happens when it has been filled
535 addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
537 Ustrncpy(actioned_message, client_data, 24);
538 actioned_message[23] = '\0';
539 action_required = US"-Mar";
540 dialog_ref_widget = menushell;
541 create_dialog(US"Recipient address to add?", US"");
544 /*************************************************
545 * Cause an address to be marked delivered *
546 *************************************************/
549 markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
551 Ustrncpy(actioned_message, client_data, 24);
552 actioned_message[23] = '\0';
553 action_required = US"-Mmd";
554 dialog_ref_widget = menushell;
555 create_dialog(US"Recipient address to mark delivered?", US"");
558 /*************************************************
559 * Cause all addresses to be marked delivered *
560 *************************************************/
563 markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
565 ActOnMessage(US client_data, US"-Mmad", US"");
568 /*************************************************
569 * Edit the message's sender *
570 *************************************************/
573 editsenderAction(Widget w, XtPointer client_data, XtPointer call_data)
578 Ustrncpy(actioned_message, client_data, 24);
579 actioned_message[23] = '\0';
580 q = find_queue(actioned_message, queue_noop, 0);
581 sender = !q ? US"" : q->sender[0] == 0 ? US"<>" : q->sender;
582 action_required = US"-Mes";
583 dialog_ref_widget = menushell;
584 create_dialog(US"New sender address?", sender);
587 /*************************************************
588 * Cause a message to be returned to sender *
589 *************************************************/
592 giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
594 ActOnMessage(US client_data, US"-v -Mg", US"");
597 /*************************************************
598 * Cause a message to be cancelled *
599 *************************************************/
602 removeAction(Widget w, XtPointer client_data, XtPointer call_data)
604 ActOnMessage(US client_data, US"-Mrm", US"");
607 /*************************************************
608 * Display a message's headers *
609 *************************************************/
612 headersAction(Widget w, XtPointer client_data, XtPointer call_data)
615 Widget text = text_create(US client_data, text_depth);
618 /* Remember the point in the dynamic store so we can recover to it afterwards.
619 Then use Exim's function to read the header. */
621 reset_point = store_mark();
623 sprintf(CS buffer, "%s-H", US client_data);
624 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
626 if (errno == ERRNO_SPOOLFORMAT)
629 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
630 if (Ustat(big_buffer, &statbuf) == 0)
631 text_showf(text, "Format error in spool file %s: size=%lu\n", buffer,
632 (unsigned long)statbuf.st_size);
633 else text_showf(text, "Format error in spool file %s\n", buffer);
635 else text_showf(text, "Read error for spool file %s\n", buffer);
636 store_reset(reset_point);
641 text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
646 text_show(text, US"Recipients:\n");
647 for (int i = 0; i < recipients_count; i++)
648 text_showf(text, " %s %s\n",
649 tree_search(tree_nonrecipients, recipients_list[i].address)
651 recipients_list[i].address);
652 text_show(text, US"\n");
655 for (header_line * next, * h = header_list; h; h = next)
658 text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */
659 text_show(text, h->text); /* expansion as it may be v large */
662 store_reset(reset_point);
665 /*************************************************
666 * Dismiss a text window *
667 *************************************************/
670 dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
672 XtPopdown((Widget)client_data);
673 XtDestroyWidget((Widget)client_data);
675 /* If this is a text widget for a sub-process, clear it out of
676 the chain so that subsequent data doesn't try to use it. We have
677 to search the parents of the saved widget to see if one of them
678 is what we have just destroyed. */
680 for (pipe_item * p = pipe_chain; p; p = p->next)
681 for (Widget pp = p->widget; pp; pp = XtParent(pp))
682 if (pp == (Widget)client_data) { p->widget = NULL; return; }
687 /*************************************************
688 * Set up popup text window *
689 *************************************************/
692 text_create(uschar *name, int height)
694 Widget textshell, form, text, button;
696 /* Create a popup shell widget to display as an additional
699 textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
700 toplevel_widget, NULL, 0);
701 xs_SetValues(textshell, 4,
707 /* Create a form widget, containing the text widget and the
708 dismiss button widget. */
710 form = XtCreateManagedWidget("textform", formWidgetClass,
712 xs_SetValues(form, 1, "defaultDistance", 8);
714 text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
715 form, text_arg, XtNumber(text_arg));
716 xs_SetValues(text, 4,
717 "editType", XawtextAppend,
720 "translations", text_trans);
721 XawTextDisplayCaret(text, TRUE);
723 /* Use the same font as for the queue display */
725 if (queue_font != NULL)
727 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
728 if (f != NULL) xs_SetValues(text, 1, "font", f);
731 button_arg[0].value = (XtArgVal)text;
732 button = XtCreateManagedWidget("dismiss", commandWidgetClass,
733 form, button_arg, XtNumber(button_arg));
734 XtAddCallback(button, "callback", dismissAction, (XtPointer)textshell);
736 /* Get the toplevel popup displayed, and yield the text widget so
737 that text can be put into it. */
739 XtPopup(textshell, XtGrabNone);
743 /*************************************************
744 * Set up menu in queue window *
745 *************************************************/
747 /* We have added an action table that causes this function to
748 be called, and set up button 2 in the text widgets to call it. */
751 menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
757 Widget src, menu_line, item_1, item_2, item_3, item_4,
758 item_5, item_6, item_7, item_8, item_9, item_10, item_11,
760 XtTranslations menu_trans = XtParseTranslationTable(
761 "<EnterWindow>: highlight()\n\
762 <LeaveWindow>: unhighlight()\n\
763 <BtnMotion>: highlight()\n\
764 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
767 /* Get the sink and source and the current text pointer */
769 queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
770 queue_get_arg[1].value = (XtArgVal)(&src);
771 queue_get_arg[2].value = (XtArgVal)(&s);
772 XtGetValues(w, queue_get_arg, 3);
774 /* Find the line number of the pointer in the window, and the
775 character offset of the top lefthand of the window. */
777 line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
778 p = XawTextTopPosition(w);
780 /* Find the start of the line on which the button was clicked. */
785 while (s[p] != 0 && s[p++] != '\n');
788 /* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
789 If 0, the click was beyond the end of the data; just set up a dummy
790 menu. (Not easy to ignore as several actions are specified for the
791 mouse click and it expects this one to set up a menu.) If on a
792 continuation line, move back to the main line. */
796 menushell_arg[0].value = (XtArgVal)"No message selected";
797 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
798 queue_widget, menushell_arg, XtNumber(menushell_arg));
799 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
800 xs_SetValues(menushell, 2,
801 "cursor", XCreateFontCursor(X_display, XC_arrow),
802 "translations", menu_trans);
804 /* To keep the widgets in XFree86 happy, we have to create at least one menu
805 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
806 there's a complaint about a zero width menu, and a crash. */
808 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
811 item_99_arg[0].value = (XtArgVal)menu_line;
812 (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
813 item_99_arg, XtNumber(item_99_arg));
819 while (p > 0 && s[p+11] == ' ')
823 while (p > 0 && s[p-1] != '\n') p--;
826 /* Now pointing at first character of a main line. */
828 Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH); /*III*/
829 message_id[MESSAGE_ID_LENGTH] = 0;
831 /* Highlight the line being menued, and save its parameters so that it
832 can be de-highlighted at popdown. */
834 highlighted_start = highlighted_end = p;
835 while (s[highlighted_end] != '\n') highlighted_end++;
837 highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
839 XawTextSinkDisplayText(queue_text_sink,
840 highlighted_x, highlighted_y,
841 highlighted_start, highlighted_end, 1);
843 /* Create the popup shell and the other widgets that comprise the menu.
844 Set the translations and pointer shape, and add the callback pointers. */
846 menushell_arg[0].value = (XtArgVal)message_id;
847 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
848 queue_widget, menushell_arg, XtNumber(menushell_arg));
849 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
851 xs_SetValues(menushell, 2,
852 "cursor", XCreateFontCursor(X_display, XC_arrow),
853 "translations", menu_trans);
855 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
858 item_1_arg[0].value = (XtArgVal)menu_line;
859 item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
860 item_1_arg, XtNumber(item_1_arg));
861 XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
863 item_2_arg[0].value = (XtArgVal)item_1;
864 item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
865 item_2_arg, XtNumber(item_2_arg));
866 XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
868 item_3_arg[0].value = (XtArgVal)item_2;
869 item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
870 item_3_arg, XtNumber(item_3_arg));
871 XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
873 item_4_arg[0].value = (XtArgVal)item_3;
874 item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
875 item_4_arg, XtNumber(item_4_arg));
876 XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
878 item_5_arg[0].value = (XtArgVal)item_4;
879 item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
880 item_5_arg, XtNumber(item_5_arg));
881 XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
883 item_6_arg[0].value = (XtArgVal)item_5;
884 item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
885 item_6_arg, XtNumber(item_6_arg));
886 XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
888 item_7_arg[0].value = (XtArgVal)item_6;
889 item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
890 item_7_arg, XtNumber(item_7_arg));
891 XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
893 item_8_arg[0].value = (XtArgVal)item_7;
894 item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
895 item_8_arg, XtNumber(item_8_arg));
896 XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
898 item_9_arg[0].value = (XtArgVal)item_8;
899 item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
900 item_9_arg, XtNumber(item_9_arg));
902 item_10_arg[0].value = (XtArgVal)item_9;
903 item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
904 item_10_arg, XtNumber(item_10_arg));
905 XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
907 item_11_arg[0].value = (XtArgVal)item_10;
908 item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
909 item_11_arg, XtNumber(item_11_arg));
910 XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
912 item_12_arg[0].value = (XtArgVal)item_11;
913 item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
914 item_12_arg, XtNumber(item_12_arg));
915 XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
917 item_13_arg[0].value = (XtArgVal)item_12;
918 item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
919 item_13_arg, XtNumber(item_13_arg));
920 XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
922 /* Arrange that the menu pops up with the first item selected. */
924 xs_SetValues(menushell, 1, "popupOnEntry", item_1);
926 /* Flag that the menu is up to suppress queue updates. */
931 /* End of em_menu.c */