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