1/*
2
3Copyright (c) 1988, 1991, 1994  X Consortium
4
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
12
13The above copyright notice and this permission notice shall be included
14in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
20OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22OTHER DEALINGS IN THE SOFTWARE.
23
24Except as contained in this notice, the name of the X Consortium shall
25not be used in advertising or otherwise to promote the sale, use or
26other dealings in this Software without prior written authorization
27from the X Consortium.
28
29*/
30/* $XFree86: xc/programs/xmessage/xmessage.c,v 1.4 2000/02/17 16:53:03 dawes Exp $ */
31
32#ifdef HAVE_CONFIG_H
33# include "config.h"
34#endif
35
36#include <assert.h>
37#include <X11/Intrinsic.h>
38#include <X11/StringDefs.h>
39#include <X11/Shell.h>
40#include <stdio.h>
41#include <stdlib.h>
42
43#include "xmessage.h"
44#include "readfile.h"
45
46/*
47 * data used by xmessage
48 */
49
50const char *ProgramName;
51
52static struct _QueryResources {
53    char *file;
54    char *button_list;
55    char *default_button;
56    Boolean print_value;
57    Boolean center;
58    Boolean nearmouse;
59    int timeout_secs;
60    Dimension maxHeight;
61    Dimension maxWidth;
62} qres;				/* initialized by resources below */
63
64#define offset(field) XtOffsetOf(struct _QueryResources, field)
65static XtResource resources[] = {
66    { "file", "File", XtRString, sizeof (char *),
67      offset(file), XtRString, (XtPointer) NULL },
68    { "buttons", "Buttons", XtRString, sizeof (char *),
69      offset(button_list), XtRString, (XtPointer) "okay:0" },
70    { "defaultButton", "DefaultButton", XtRString, sizeof (char *),
71      offset(default_button), XtRString, (XtPointer) NULL },
72    { "printValue", "PrintValue", XtRBoolean, sizeof (Boolean),
73      offset(print_value), XtRString, "false" },
74    { "center", "Center", XtRBoolean, sizeof (Boolean),
75      offset(center), XtRString, "false" },
76    { "nearMouse", "NearMouse", XtRBoolean, sizeof (Boolean),
77      offset(nearmouse), XtRString, "false" },
78    { "timeout", "Timeout", XtRInt, sizeof (int),
79      offset(timeout_secs), XtRInt, NULL },
80    { "maxHeight", "Maximum", XtRDimension, sizeof (Dimension),
81      offset(maxHeight), XtRDimension, NULL },
82    { "maxWidth", "Maximum", XtRDimension, sizeof (Dimension),
83      offset(maxWidth), XtRDimension, NULL },
84};
85#undef offset
86
87static XrmOptionDescRec optionList[] =  {
88    { "-file",    ".file", XrmoptionSepArg, (XPointer) NULL },
89    { "-buttons", ".buttons", XrmoptionSepArg, (XPointer) NULL },
90    { "-default", ".defaultButton", XrmoptionSepArg, (XPointer) NULL },
91    { "-print",   ".printValue", XrmoptionNoArg, (XPointer) "on" },
92    { "-center",  ".center", XrmoptionNoArg, (XPointer) "on" },
93    { "-nearmouse", ".nearMouse", XrmoptionNoArg, (XPointer) "on" },
94    { "-timeout", ".timeout", XrmoptionSepArg, (XPointer) NULL },
95};
96
97static String fallback_resources[] = {
98    "*baseTranslations: #override :<Key>Return: default-exit()",
99    "*message.Scroll: whenNeeded",
100    NULL};
101
102
103/*
104 * usage
105 */
106
107static void
108usage (FILE *outf)
109{
110    static const char *options[] = {
111"    -file filename              file to read message from, \"-\" for stdin",
112"    -buttons string             comma-separated list of label:exitcode",
113"    -default button             button to activate if Return is pressed",
114"    -print                      print the button label when selected",
115"    -center                     pop up at center of screen",
116"    -nearmouse                  pop up near the mouse cursor",
117"    -timeout secs               exit with status 0 after \"secs\" seconds",
118"",
119NULL};
120    const char **cpp;
121
122    fprintf (outf, "usage: %s [-options] [message ...]\n\n",
123	     ProgramName);
124    fprintf (outf, "where options include:\n");
125    for (cpp = options; *cpp; cpp++)
126	fprintf (outf, "%s\n", *cpp);
127}
128
129/*
130 * Action to implement ICCCM delete_window and other translations.
131 * Takes one argument, the exit status.
132 */
133static Atom wm_delete_window;
134/* ARGSUSED */
135static void
136exit_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
137{
138    int exit_status = 0;
139
140    if(event->type == ClientMessage
141       && event->xclient.data.l[0] != wm_delete_window)
142	return;
143
144    if (*num_params == 1)
145	exit_status = atoi(params[0]);
146    exit(exit_status);
147}
148
149int default_exitstatus = -1;		/* value of button named by -default */
150
151/* ARGSUSED */
152static void
153default_exit_action(Widget w, XEvent *event, String *params,
154    Cardinal *num_params)
155{
156    if (default_exitstatus >= 0)
157	exit(default_exitstatus);
158}
159
160/* Convert tabs to spaces in *messagep,*lengthp, copying to a new block of
161   memory.  */
162static void
163detab (char **messagep, int *lengthp)
164{
165  int   i, n, col, psize;
166  char  *p;
167
168  /* count how many tabs there are */
169  n = 0;
170  for (i = 0; i < *lengthp; i++)
171    if ((*messagep)[i] == '\t')
172      n++;
173
174  /* length increases by at most seven extra spaces for each tab */
175  psize = *lengthp + n*7 + 1;
176  p = XtMalloc (psize);
177
178  /* convert tabs to spaces, copying into p */
179  n = 0;
180  col = 0;
181  for (i = 0; i < *lengthp; i++)
182    {
183      switch ((*messagep)[i]) {
184      case '\n':
185        p[n++] = '\n';
186        col = 0;
187        break;
188      case '\t':
189        do
190          {
191            p[n++] = ' ';
192            col++;
193          }
194        while ((col % 8) != 0);
195        break;
196      default:
197        p[n++] = (*messagep)[i];
198        col++;
199        break;
200      }
201    }
202
203  assert (n < psize);
204
205  /* null-terminator needed by Label widget */
206  p[n] = '\0';
207
208  free (*messagep);
209
210  *messagep = p;
211  *lengthp = n;
212}
213
214static XtActionsRec actions_list[] = {
215    {"exit", exit_action},
216    {"default-exit", default_exit_action},
217};
218
219static String top_trans =
220    "<ClientMessage>WM_PROTOCOLS: exit(1)\n";
221
222/* assumes shell widget has already been realized */
223
224static void
225position_near(Widget shell, int x, int y)
226{
227    int max_x, max_y;
228    Dimension width, height, border;
229    int gravity;
230
231    /* some of this is copied from CenterWidgetOnPoint in Xaw/TextPop.c */
232
233    XtVaGetValues(shell,
234		  XtNwidth, &width,
235		  XtNheight, &height,
236		  XtNborderWidth, &border,
237		  NULL);
238
239    width += 2 * border;
240    height += 2 * border;
241
242    max_x = WidthOfScreen(XtScreen(shell));
243    max_y = HeightOfScreen(XtScreen(shell));
244
245    /* set gravity hint based on position on screen */
246    gravity = 1;
247    if (x > max_x/3) gravity += 1;
248    if (x > max_x*2/3) gravity += 1;
249    if (y > max_y/3) gravity += 3;
250    if (y > max_y*2/3) gravity += 3;
251
252    max_x -= width;
253    max_y -= height;
254
255    x -= ( (Position) width/2 );
256    if (x < 0) x = 0;
257    if (x > max_x) x = max_x;
258
259    y -= ( (Position) height/2 );
260    if (y < 0) y = 0;
261    if ( y > max_y ) y = max_y;
262
263    XtVaSetValues(shell,
264		  XtNx, (Position)x,
265		  XtNy, (Position)y,
266		  XtNwinGravity, gravity,
267		  NULL);
268}
269
270static void
271position_near_mouse(Widget shell)
272{
273    int x, y;
274    Window root, child;
275    int winx, winy;
276    unsigned int mask;
277
278    XQueryPointer(XtDisplay(shell), XtWindow(shell),
279		  &root, &child, &x, &y, &winx, &winy, &mask);
280    position_near(shell, x, y);
281}
282
283static void
284position_near_center(Widget shell)
285{
286    position_near(shell,
287		  WidthOfScreen(XtScreen(shell))/2,
288		  HeightOfScreen(XtScreen(shell))/2);
289}
290
291/* ARGSUSED */
292static void
293time_out(XtPointer client_data, XtIntervalId *iid)
294{
295    exit(0);
296}
297
298/*
299 * xmessage main program - make sure that there is a message,
300 * then create the query box and go.  Callbacks take care of exiting.
301 */
302int
303main (int argc, char *argv[])
304{
305    Widget top, queryform;
306    XtAppContext app_con;
307    char *message_str;
308    int message_len;
309
310    ProgramName = argv[0];
311
312    XtSetLanguageProc(NULL, (XtLanguageProc) NULL, NULL);
313
314    /* Handle args that don't require opening a display */
315    for (int n = 1; n < argc; n++) {
316	const char *argn = argv[n];
317	/* accept single or double dash for -help & -version */
318	if (argn[0] == '-' && argn[1] == '-') {
319	    argn++;
320	}
321	if (strcmp (argn, "-help") == 0) {
322	    usage(stdout);
323	    exit(0);
324	}
325	if (strcmp (argn, "-version") == 0) {
326	    puts (PACKAGE_STRING);
327	    exit (0);
328	}
329    }
330
331    top = XtAppInitialize (&app_con, "Xmessage",
332			   optionList, XtNumber(optionList), &argc, argv,
333			   fallback_resources, NULL, 0);
334
335    XtGetApplicationResources (top, (XtPointer) &qres, resources,
336			       XtNumber(resources), NULL, 0);
337
338    if (argc == 1 && qres.file != NULL) {
339	message_str = read_file (qres.file, &message_len);
340	if (message_str == NULL) {
341	    fprintf (stderr, "%s: problems reading message file\n",
342		     ProgramName);
343	    exit (1);
344	}
345    } else if (argc > 1 && qres.file == NULL) {
346	int i, len;
347	char *cp;
348
349	len = argc - 1;		/* spaces between words and final NULL */
350	for (i=1; i<argc; i++)
351	    len += strlen(argv[i]);
352	message_str = malloc(len);
353	if (!message_str) {
354	    fprintf (stderr, "%s: cannot get memory for message string\n",
355		     ProgramName);
356	    exit (1);
357	}
358	cp = message_str;
359	for (i=1; i<argc; i++) {
360	    strcpy(cp, argv[i]);
361	    cp += strlen(argv[i]);
362	    if (i != argc-1)
363		*cp++ = ' ';
364	    else
365		*cp = '\0';
366	}
367	message_len = len;
368    } else {
369	fputs("Unknown argument(s):", stderr);
370	for (int n = 1; n < argc; n++) {
371	    fprintf(stderr, " %s", argv[n]);
372	}
373	fputs("\n\n", stderr);
374	usage(stderr);
375	exit(1);
376    }
377
378    wm_delete_window = XInternAtom(XtDisplay(top), "WM_DELETE_WINDOW", False);
379    XtAppAddActions(app_con, actions_list, XtNumber(actions_list));
380    XtOverrideTranslations(top, XtParseTranslationTable(top_trans));
381
382    detab (&message_str, &message_len);
383
384    /*
385     * create the query form; this is where most of the real work is done
386     */
387    queryform = make_queryform (top, message_str, message_len,
388				qres.button_list,
389				qres.print_value, qres.default_button,
390				qres.maxWidth, qres.maxHeight);
391    if (!queryform) {
392	fprintf (stderr,
393		 "%s: unable to create query form with buttons: %s\n",
394		 ProgramName, qres.button_list);
395	exit (1);
396    }
397
398    XtSetMappedWhenManaged(top, FALSE);
399    XtRealizeWidget(top);
400
401    /* do WM_DELETE_WINDOW before map */
402    XSetWMProtocols(XtDisplay(top), XtWindow(top), &wm_delete_window, 1);
403
404    if (qres.center)
405	position_near_center(top);
406    else if (qres.nearmouse)
407	position_near_mouse(top);
408
409    XtMapWidget(top);
410
411    if (qres.timeout_secs)
412	XtAppAddTimeOut(app_con, 1000*qres.timeout_secs, time_out, NULL);
413
414    XtAppMainLoop(app_con);
415
416    exit (0);
417}
418