xmessage.c revision 165cb819
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#include <assert.h>
33#include <X11/Intrinsic.h>
34#include <X11/StringDefs.h>
35#include <X11/Shell.h>
36#include <stdio.h>
37#include <stdlib.h>
38
39#include "xmessage.h"
40#include "readfile.h"
41
42/*
43 * data used by xmessage
44 */
45
46const char *ProgramName;
47
48static struct _QueryResources {
49    char *file;
50    char *button_list;
51    char *default_button;
52    Boolean print_value;
53    Boolean center;
54    Boolean nearmouse;
55    int timeout_secs;
56    Dimension maxHeight;
57    Dimension maxWidth;
58} qres;				/* initialized by resources below */
59
60#define offset(field) XtOffsetOf(struct _QueryResources, field)
61static XtResource resources[] = {
62    { "file", "File", XtRString, sizeof (char *),
63      offset(file), XtRString, (XtPointer) NULL },
64    { "buttons", "Buttons", XtRString, sizeof (char *),
65      offset(button_list), XtRString, (XtPointer) "okay:0" },
66    { "defaultButton", "DefaultButton", XtRString, sizeof (char *),
67      offset(default_button), XtRString, (XtPointer) NULL },
68    { "printValue", "PrintValue", XtRBoolean, sizeof (Boolean),
69      offset(print_value), XtRString, "false" },
70    { "center", "Center", XtRBoolean, sizeof (Boolean),
71      offset(center), XtRString, "false" },
72    { "nearMouse", "NearMouse", XtRBoolean, sizeof (Boolean),
73      offset(nearmouse), XtRString, "false" },
74    { "timeout", "Timeout", XtRInt, sizeof (int),
75      offset(timeout_secs), XtRInt, NULL },
76    { "maxHeight", "Maximum", XtRDimension, sizeof (Dimension),
77      offset(maxHeight), XtRDimension, NULL },
78    { "maxWidth", "Maximum", XtRDimension, sizeof (Dimension),
79      offset(maxWidth), XtRDimension, NULL },
80};
81#undef offset
82
83static XrmOptionDescRec optionList[] =  {
84    { "-file",    ".file", XrmoptionSepArg, (XPointer) NULL },
85    { "-buttons", ".buttons", XrmoptionSepArg, (XPointer) NULL },
86    { "-default", ".defaultButton", XrmoptionSepArg, (XPointer) NULL },
87    { "-print",   ".printValue", XrmoptionNoArg, (XPointer) "on" },
88    { "-center",  ".center", XrmoptionNoArg, (XPointer) "on" },
89    { "-nearmouse", ".nearMouse", XrmoptionNoArg, (XPointer) "on" },
90    { "-timeout", ".timeout", XrmoptionSepArg, (XPointer) NULL },
91};
92
93static String fallback_resources[] = {
94    "*baseTranslations: #override :<Key>Return: default-exit()",
95    "*message.Scroll: whenNeeded",
96    NULL};
97
98
99/*
100 * usage
101 */
102
103static void
104usage (FILE *outf)
105{
106    static const char *options[] = {
107"    -file filename              file to read message from, \"-\" for stdin",
108"    -buttons string             comma-separated list of label:exitcode",
109"    -default button             button to activate if Return is pressed",
110"    -print                      print the button label when selected",
111"    -center                     pop up at center of screen",
112"    -nearmouse                  pop up near the mouse cursor",
113"    -timeout secs               exit with status 0 after \"secs\" seconds",
114"",
115NULL};
116    const char **cpp;
117
118    fprintf (outf, "usage: %s [-options] [message ...]\n\n",
119	     ProgramName);
120    fprintf (outf, "where options include:\n");
121    for (cpp = options; *cpp; cpp++)
122	fprintf (outf, "%s\n", *cpp);
123}
124
125/*
126 * Action to implement ICCCM delete_window and other translations.
127 * Takes one argument, the exit status.
128 */
129static Atom wm_delete_window;
130/* ARGSUSED */
131static void
132exit_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
133{
134    int exit_status = 0;
135
136    if(event->type == ClientMessage
137       && event->xclient.data.l[0] != wm_delete_window)
138	return;
139
140    if (*num_params == 1)
141	exit_status = atoi(params[0]);
142    exit(exit_status);
143}
144
145int default_exitstatus = -1;		/* value of button named by -default */
146
147/* ARGSUSED */
148static void
149default_exit_action(Widget w, XEvent *event, String *params,
150    Cardinal *num_params)
151{
152    if (default_exitstatus >= 0)
153	exit(default_exitstatus);
154}
155
156/* Convert tabs to spaces in *messagep,*lengthp, copying to a new block of
157   memory.  */
158static void
159detab (char **messagep, int *lengthp)
160{
161  int   i, n, col, psize;
162  char  *p;
163
164  /* count how many tabs there are */
165  n = 0;
166  for (i = 0; i < *lengthp; i++)
167    if ((*messagep)[i] == '\t')
168      n++;
169
170  /* length increases by at most seven extra spaces for each tab */
171  psize = *lengthp + n*7 + 1;
172  p = XtMalloc (psize);
173
174  /* convert tabs to spaces, copying into p */
175  n = 0;
176  col = 0;
177  for (i = 0; i < *lengthp; i++)
178    {
179      switch ((*messagep)[i]) {
180      case '\n':
181        p[n++] = '\n';
182        col = 0;
183        break;
184      case '\t':
185        do
186          {
187            p[n++] = ' ';
188            col++;
189          }
190        while ((col % 8) != 0);
191        break;
192      default:
193        p[n++] = (*messagep)[i];
194        col++;
195        break;
196      }
197    }
198
199  assert (n < psize);
200
201  /* null-terminator needed by Label widget */
202  p[n] = '\0';
203
204  free (*messagep);
205
206  *messagep = p;
207  *lengthp = n;
208}
209
210static XtActionsRec actions_list[] = {
211    {"exit", exit_action},
212    {"default-exit", default_exit_action},
213};
214
215static String top_trans =
216    "<ClientMessage>WM_PROTOCOLS: exit(1)\n";
217
218/* assumes shell widget has already been realized */
219
220static void
221position_near(Widget shell, int x, int y)
222{
223    int max_x, max_y;
224    Dimension width, height, border;
225    int gravity;
226
227    /* some of this is copied from CenterWidgetOnPoint in Xaw/TextPop.c */
228
229    XtVaGetValues(shell,
230		  XtNwidth, &width,
231		  XtNheight, &height,
232		  XtNborderWidth, &border,
233		  NULL);
234
235    width += 2 * border;
236    height += 2 * border;
237
238    max_x = WidthOfScreen(XtScreen(shell));
239    max_y = HeightOfScreen(XtScreen(shell));
240
241    /* set gravity hint based on position on screen */
242    gravity = 1;
243    if (x > max_x/3) gravity += 1;
244    if (x > max_x*2/3) gravity += 1;
245    if (y > max_y/3) gravity += 3;
246    if (y > max_y*2/3) gravity += 3;
247
248    max_x -= width;
249    max_y -= height;
250
251    x -= ( (Position) width/2 );
252    if (x < 0) x = 0;
253    if (x > max_x) x = max_x;
254
255    y -= ( (Position) height/2 );
256    if (y < 0) y = 0;
257    if ( y > max_y ) y = max_y;
258
259    XtVaSetValues(shell,
260		  XtNx, (Position)x,
261		  XtNy, (Position)y,
262		  XtNwinGravity, gravity,
263		  NULL);
264}
265
266static void
267position_near_mouse(Widget shell)
268{
269    int x, y;
270    Window root, child;
271    int winx, winy;
272    unsigned int mask;
273
274    XQueryPointer(XtDisplay(shell), XtWindow(shell),
275		  &root, &child, &x, &y, &winx, &winy, &mask);
276    position_near(shell, x, y);
277}
278
279static void
280position_near_center(Widget shell)
281{
282    position_near(shell,
283		  WidthOfScreen(XtScreen(shell))/2,
284		  HeightOfScreen(XtScreen(shell))/2);
285}
286
287/* ARGSUSED */
288static void
289time_out(XtPointer client_data, XtIntervalId *iid)
290{
291    exit(0);
292}
293
294/*
295 * xmessage main program - make sure that there is a message,
296 * then create the query box and go.  Callbacks take care of exiting.
297 */
298int
299main (int argc, char *argv[])
300{
301    Widget top, queryform;
302    XtAppContext app_con;
303    char *message_str;
304    int message_len;
305
306    ProgramName = argv[0];
307
308    XtSetLanguageProc(NULL, (XtLanguageProc) NULL, NULL);
309
310    top = XtAppInitialize (&app_con, "Xmessage",
311			   optionList, XtNumber(optionList), &argc, argv,
312			   fallback_resources, NULL, 0);
313
314    XtGetApplicationResources (top, (XtPointer) &qres, resources,
315			       XtNumber(resources), NULL, 0);
316
317    if (argc > 1 && !strcmp(argv[1], "-help")) {
318	usage(stdout);
319	exit(0);
320    }
321    if (argc == 1 && qres.file != NULL) {
322	message_str = read_file (qres.file, &message_len);
323	if (message_str == NULL) {
324	    fprintf (stderr, "%s: problems reading message file\n",
325		     ProgramName);
326	    exit (1);
327	}
328    } else if (argc > 1 && qres.file == NULL) {
329	int i, len;
330	char *cp;
331
332	len = argc - 1;		/* spaces between words and final NULL */
333	for (i=1; i<argc; i++)
334	    len += strlen(argv[i]);
335	message_str = malloc(len);
336	if (!message_str) {
337	    fprintf (stderr, "%s: cannot get memory for message string\n",
338		     ProgramName);
339	    exit (1);
340	}
341	cp = message_str;
342	for (i=1; i<argc; i++) {
343	    strcpy(cp, argv[i]);
344	    cp += strlen(argv[i]);
345	    if (i != argc-1)
346		*cp++ = ' ';
347	    else
348		*cp = '\0';
349	}
350	message_len = len;
351    } else {
352	usage(stderr);
353	exit(1);
354    }
355
356    wm_delete_window = XInternAtom(XtDisplay(top), "WM_DELETE_WINDOW", False);
357    XtAppAddActions(app_con, actions_list, XtNumber(actions_list));
358    XtOverrideTranslations(top, XtParseTranslationTable(top_trans));
359
360    detab (&message_str, &message_len);
361
362    /*
363     * create the query form; this is where most of the real work is done
364     */
365    queryform = make_queryform (top, message_str, message_len,
366				qres.button_list,
367				qres.print_value, qres.default_button,
368				qres.maxWidth, qres.maxHeight);
369    if (!queryform) {
370	fprintf (stderr,
371		 "%s: unable to create query form with buttons: %s\n",
372		 ProgramName, qres.button_list);
373	exit (1);
374    }
375
376    XtSetMappedWhenManaged(top, FALSE);
377    XtRealizeWidget(top);
378
379    /* do WM_DELETE_WINDOW before map */
380    XSetWMProtocols(XtDisplay(top), XtWindow(top), &wm_delete_window, 1);
381
382    if (qres.center)
383	position_near_center(top);
384    else if (qres.nearmouse)
385	position_near_mouse(top);
386
387    XtMapWidget(top);
388
389    if (qres.timeout_secs)
390	XtAppAddTimeOut(app_con, 1000*qres.timeout_secs, time_out, NULL);
391
392    XtAppMainLoop(app_con);
393
394    exit (0);
395}
396