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