1 /* This file is part of the GNU plotutils package.  Copyright (C) 1995,
2    1996, 1997, 1998, 1999, 2000, 2005, 2008, Free Software Foundation, Inc.
3 
4    The GNU plotutils package is free software.  You may redistribute it
5    and/or modify it under the terms of the GNU General Public License as
6    published by the Free Software foundation; either version 2, or (at your
7    option) any later version.
8 
9    The GNU plotutils package is distributed in the hope that it will be
10    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with the GNU plotutils package; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor,
17    Boston, MA 02110-1301, USA. */
18 
19 /* This implementation is for XPlotters.  When invoked, it pops up a
20    plotting window on the default screen of the specified X display.  When
21    the corresponding closepl method is invoked, the window is `spun off',
22    i.e., is managed thenceforth by a forked-off child process. */
23 
24 /* This file also contains the internal functions _pl_y_maybe_get_new_colormap
25    and _pl_y_maybe_handle_x_events.  They override the corresponding functions
26    in the XDrawablePlotter superclass, which are no-ops.
27 
28    The function _pl_y_maybe_handle_x_events is very important: it contains our
29    hand-crafted loop for processing X events, which is called by an
30    XPlotter after any libplot drawing operation is invoked on it. */
31 
32 #include "sys-defines.h"
33 #include "extern.h"
34 
35 /* song and dance to define struct timeval, and declare select() */
36 #ifdef HAVE_SYS_TIME_H
37 #include <sys/time.h>		/* for struct timeval */
38 #endif
39 #ifdef HAVE_SYS_SELECT_H
40 #include <sys/select.h>		/* AIX needs this */
41 #endif
42 #ifdef HAVE_SYS_TYPES_H
43 #include <sys/types.h>		/* for struct fdset, FD_ZERO, FD_SET */
44 #endif
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>		/* for select() */
47 #endif
48 
49 /* Mutex for locking _xplotters[] and _xplotters_len.  Defined in
50    y_defplot.c. */
51 #ifdef PTHREAD_SUPPORT
52 #ifdef HAVE_PTHREAD_H
53 extern pthread_mutex_t _xplotters_mutex;
54 #endif
55 #endif
56 
57 /* fake app name, effectively our argv[0] */
58 #define XPLOT_APP_NAME "xplot"
59 
60 /* app class, use for specifying resources */
61 #define XPLOT_APP_CLASS "Xplot"
62 
63 /* Fallback resources for the preceding X11 class.  There are no
64    user-specifiable X resources, except for the geometry.
65 
66    The default size of the plotting window is set here, as a default X
67    resource, rather than in y_defplot.c.  Users may override the default by
68    specifying a geometry in their .Xdefaults files (by specifying the
69    Xplot.geometry or xplot.geometry resource).  This is equivalent to
70    specifying the Plotter parameter BITMAPSIZE. */
71 
72 static const String _xplot_fallback_resources[] =
73 {
74   (String)"Xplot*geometry:      570x570",
75   (String)NULL
76 };
77 
78 /* Support for command-line mimicry.  Our fake argument vector,
79    _fake_argv[], needs space for our fake application name,
80    i.e. XPLOT_APP_NAME, the three options "-display", "-geometry", "-bg",
81    each with an argument, and a final NULL. */
82 #define MAX_FAKE_ARGV_LENGTH 8
83 
84 /* Translations for the canvas widget, before and after the Plotter is
85    closed, i.e. before and after forking.  (After forking, translate any
86    pressing of the `q' key, and any mouse click, to `Foldup'.) */
87 static const String _xplot_translations_before_forking =
88 #ifdef USE_MOTIF
89 (String)"<Btn2Down>:	ProcessDrag()";
90 #else
91 (String)"";
92 #endif
93 
94 static const String _xplot_translations_after_forking =
95 #ifdef USE_MOTIF
96 (String)"<Btn1Down>:	Foldup()\n\
97  <Btn2Down>:	ProcessDrag()\n\
98  <Btn3Down>:	Foldup()\n\
99  <Key>Q:	Foldup()\n\
100  <Key>q:	Foldup()";
101 #else
102 (String)"<Btn1Down>:	Foldup()\n\
103  <Btn3Down>:	Foldup()\n\
104  <Key>Q:	Foldup()\n\
105  <Key>q:	Foldup()";
106 #endif
107 
108 /* forward references */
109 static bool _bitmap_size_ok (const char *bitmap_size_s);
110 static void Foldup (Widget widget, XEvent *event, String *params, Cardinal *num_params);
111 
112 #ifndef HAVE_STRERROR
113 static char * _plot_strerror (int errnum);
114 #define strerror _plot_strerror
115 #endif
116 
117 /* This is called by the child process produced in y_closepl.c, immediately
118    after forking takes place.  It alters the translation table for the
119    canvas widget so that Foldup() will be invoked when `q' is pressed or a
120    mouse click is seen. */
121 void
_pl_y_set_data_for_quitting(S___ (Plotter * _plotter))122 _pl_y_set_data_for_quitting (S___(Plotter *_plotter))
123 {
124   Arg wargs[1];		/* a lone werewolf */
125 
126 #ifdef USE_MOTIF
127   XtSetArg (wargs[0], XmNtranslations,
128 	    XtParseTranslationTable(_xplot_translations_after_forking));
129 #else
130   XtSetArg (wargs[0], XtNtranslations,
131 	    XtParseTranslationTable(_xplot_translations_after_forking));
132 #endif
133   XtSetValues (_plotter->y_canvas, wargs, (Cardinal)1);
134 }
135 
136 /* Foldup() is called by the Label widget when `q' is pressed or a mouse
137    click is seen, provided that closepl() has previously been invoked.  In
138    that case the spun-off window disappears (we destroy the parent widget,
139    and being a forked-off child process managing it, we exit). */
140 
141 static void
Foldup(Widget widget,XEvent * event,String * params,Cardinal * num_params)142 Foldup (Widget widget, XEvent *event, String *params, Cardinal *num_params)
143 {
144   Display *dpy;
145 
146   dpy = XtDisplay (widget);
147   XtDestroyWidget (XtParent (widget)); /* destroy toplevel widget */
148   XFlush (dpy);		/* flush X output buffer */
149   exit (EXIT_SUCCESS);
150 }
151 
152 /* Application context-specific action table. */
153 static const XtActionsRec _xplot_actions[] =
154 {
155   {(String)"Foldup",	Foldup},
156 };
157 
158 bool
_pl_y_begin_page(S___ (Plotter * _plotter))159 _pl_y_begin_page (S___(Plotter *_plotter))
160 {
161   Arg wargs[10];		/* werewolves */
162   Dimension window_height, window_width;
163   Screen *screen_struct;	/* screen structure */
164   String fake_argv[MAX_FAKE_ARGV_LENGTH];
165   const char *double_buffer_s;
166   int fake_argc;
167   int screen;			/* screen number */
168 
169   /* To permit openpl..closepl to be invoked repeatedly, we don't use the
170      convenience routine XtAppInitialize(), since that function starts out
171      by calling XtToolkitInitialize(), which shouldn't be called more than
172      once.  (At least, in early versions of X11; in X11R6 calling it more
173      than once is OK.)  Instead, we call XtToolkitInitialize() when the
174      first XPlotter is created; see y_defplot.c.
175 
176      On every invocation of openpl() we call the other four functions that
177      XtAppInitialize would call: XtCreateApplicationContext,
178      XtAppSetFallbackResources, XtOpenDisplay, and XtAppCreateShell.  That
179      sets up a new application context each time openpl() is called, which
180      looks wasteful.  But since each openpl..closepl will yield a window
181      managed by a forked-off process, it's appropriate. */
182 
183   /* create new application context for this Plotter page */
184   _plotter->y_app_con = XtCreateApplicationContext();
185   if (_plotter->y_app_con == (XtAppContext)NULL)
186     {
187       _plotter->error (R___(_plotter) "an X application context could not be created");
188       return false;
189     }
190   /* set fallback resources to be used by canvas widget (currently, only
191      the window size); specific to application context */
192   XtAppSetFallbackResources (_plotter->y_app_con,
193 			     (String *)_xplot_fallback_resources);
194 
195   /* register an action table [currently containing only
196      "Foldup"->Foldup(), see above]; specific to application context */
197   XtAppAddActions (_plotter->y_app_con, (XtActionsRec *)_xplot_actions,
198 		   XtNumber (_xplot_actions));
199 
200   /* punch options and parameters into our fake command line, beginning
201      with the fake app name */
202   fake_argc = 0;
203   fake_argv[fake_argc++] = (String)XPLOT_APP_NAME;
204 
205   /* take argument of the "-display" option from the DISPLAY parameter */
206   {
207     const char *display_s;
208 
209     display_s = (char *)_get_plot_param (_plotter->data, "DISPLAY");
210     if (display_s == NULL || *display_s == '\0')
211       {
212 	_plotter->error (R___(_plotter)
213 			 "the Plotter could not be opened, as the DISPLAY parameter is null");
214 	return false;
215       }
216     fake_argv[fake_argc++] = (String)"-display";
217     fake_argv[fake_argc++] = (String)display_s;
218   }
219 
220   /* Take argument of "-geometry" option from BITMAPSIZE parameter, if set;
221      otherwise size will be taken from Xplot.geometry.  Fallback size is
222      specified at head of this file. */
223   {
224     char *bitmap_size_s;
225 
226     bitmap_size_s = (char *)_get_plot_param (_plotter->data, "BITMAPSIZE");
227     if (bitmap_size_s && _bitmap_size_ok (bitmap_size_s))
228       {
229 	fake_argv[fake_argc++] = (String)"-geometry";
230 	fake_argv[fake_argc++] = (String)bitmap_size_s;
231       }
232   }
233 
234   /* Take argument of "-bg" option from BG_COLOR parameter, if set;
235      otherwise use default color (white). */
236   {
237     const char *bg_color_s;
238 
239     bg_color_s = (char *)_get_plot_param (_plotter->data, "BG_COLOR");
240     if (bg_color_s)
241       {
242 	plColor color;
243 	char rgb[8];		/* enough room for "#FFFFFF", incl. NUL */
244 
245 	if (_string_to_color (bg_color_s, &color, _plotter->data->color_name_cache))
246 	  /* color is in our database */
247 	  {
248 	    if (_plotter->data->emulate_color)
249 	      /* replace by grayscale approximation */
250 	      {
251 		int gray;
252 
253 		gray = _grayscale_approx (color.red, color.green, color.blue);
254 		sprintf (rgb, "#%02X%02X%02X", gray, gray, gray);
255 	      }
256 	    else
257 	      sprintf (rgb, "#%02X%02X%02X",
258 		       color.red, color.green, color.blue);
259 	    bg_color_s = rgb;
260 	  }
261 	else
262 	  /* color is not in our database */
263 	  {
264 	    if (_plotter->x_bg_color_warning_issued == false)
265 	      {
266 		char *buf;
267 
268 		buf = (char *)_pl_xmalloc (strlen (bg_color_s) + 100);
269 		sprintf (buf, "substituting \"white\" for undefined background color \"%s\"",
270 			 bg_color_s);
271 		_plotter->warning (R___(_plotter) buf);
272 		free (buf);
273 		_plotter->x_bg_color_warning_issued = true;
274 
275 		bg_color_s = "white";
276 	      }
277 	  }
278 
279 	fake_argv[fake_argc++] = (String)"-bg";
280 	fake_argv[fake_argc++] = (String)bg_color_s;
281       }
282   }
283 
284   /* append final NULL (some X implementations need this) */
285   fake_argv[fake_argc] = (String)NULL;
286 
287   /* open new connection to the X display, using fake argv */
288   _plotter->x_dpy =
289     XtOpenDisplay (_plotter->y_app_con,
290 		   /* display_string = NULL, so take from fake commandline */
291 		   (String)NULL,
292 		   /* application name = NULL, so take from fake commandline */
293 		   (String)NULL,
294 		   /* application class */
295 		   (String)XPLOT_APP_CLASS,
296 		   /* application-specific commandline parsetable (for
297 		      XrmParseCommand), used in setting display resources */
298 		   NULL, (Cardinal)0,
299 		   /* pass fake command-line (contains a fake argv[0] to
300 		      specify app name, and besides "-display", options
301 		      may include "-geometry", "-bg") */
302 		   &fake_argc, fake_argv);
303   if (_plotter->x_dpy == (Display *)NULL)
304     {
305       char *display_s;
306 
307       display_s = (char *)_get_plot_param (_plotter->data, "DISPLAY");
308       if (display_s == NULL)	/* shouldn't happen */
309 	_plotter->error (R___(_plotter)
310 			 "the X Window System display could not be opened, as it is null");
311       else
312 	{
313 	  char *buf;
314 
315 	  buf = (char *)_pl_xmalloc(strlen(display_s) + 1 + 50);
316 	  sprintf (buf, "the X Window System display \"%s\" could not be opened",
317 		   display_s);
318 	  _plotter->error (R___(_plotter) buf);
319 	  free (buf);
320 	}
321       return false;
322     }
323 
324   /* display was opened, so determine its default screen, visual, colormap */
325   screen = DefaultScreen (_plotter->x_dpy);
326   screen_struct = ScreenOfDisplay (_plotter->x_dpy, screen);
327   _plotter->x_visual = DefaultVisualOfScreen (screen_struct);
328   _plotter->x_cmap = DefaultColormapOfScreen (screen_struct);
329   _plotter->x_cmap_type = X_CMAP_ORIG; /* original cmap (not a private one) */
330 
331   /* find out how long polylines can get on this X display */
332   _plotter->x_max_polyline_len = XMaxRequestSize(_plotter->x_dpy) / 2;
333 
334   /* For every invocation of openpl(), we create a toplevel Shell widget,
335      associated with default screen of the opened display.  (N.B. could
336      vary name of app instance; also select a non-default colormap by
337      setting a value for XtNcolormap.) */
338   XtSetArg(wargs[0], XtNscreen, screen_struct);
339   XtSetArg(wargs[1], XtNargc, fake_argc);
340   XtSetArg(wargs[2], XtNargv, fake_argv);
341   _plotter->y_toplevel = XtAppCreateShell(NULL, /* name of app instance */
342 			     (String)XPLOT_APP_CLASS, /* app class */
343 			     applicationShellWidgetClass,
344 			     _plotter->x_dpy, /* x_dpy to get resources from */
345 			     /* pass XtNscreen resource, and also fake
346 				command-line, to get resources from
347 				(options may include "-display"
348 				[redundant], and "-geometry", "-bg") */
349 			     wargs, (Cardinal)3);
350 
351   /* Create drawing canvas (a Label widget) as child of toplevel Shell
352      widget.  Set many obscure spacing parameters to zero, so that origin
353      of bitmap will coincide with upper left corner of window. */
354 #ifdef USE_MOTIF
355   XtSetArg(wargs[0], XmNmarginHeight, (Dimension)0);
356   XtSetArg(wargs[1], XmNmarginWidth, (Dimension)0);
357   XtSetArg(wargs[2], XmNmarginLeft, (Dimension)0);
358   XtSetArg(wargs[3], XmNmarginRight, (Dimension)0);
359   XtSetArg(wargs[4], XmNmarginTop, (Dimension)0);
360   XtSetArg(wargs[5], XmNmarginBottom, (Dimension)0);
361   XtSetArg(wargs[6], XmNshadowThickness, (Dimension)0);
362   XtSetArg(wargs[7], XmNhighlightThickness, (Dimension)0);
363   _plotter->y_canvas = XtCreateManagedWidget ((String)"", xmLabelWidgetClass,
364 					    _plotter->y_toplevel,
365 					    wargs, (Cardinal)8);
366 #else
367   XtSetArg(wargs[0], XtNinternalHeight, (Dimension)0);
368   XtSetArg(wargs[1], XtNinternalWidth, (Dimension)0);
369   _plotter->y_canvas = XtCreateManagedWidget ((String)"", labelWidgetClass,
370 					    _plotter->y_toplevel,
371 					    wargs, (Cardinal)2);
372 #endif
373 
374   /* realize both widgets */
375   XtRealizeWidget (_plotter->y_toplevel);
376 
377   /* replace the Label widget's default translations by ours [see above;
378      our default is no translations at all, with a nod to Motif] */
379 #ifdef USE_MOTIF
380   XtSetArg (wargs[0], XmNtranslations,
381 	    XtParseTranslationTable(_xplot_translations_before_forking));
382 #else
383   XtSetArg (wargs[0], XtNtranslations,
384 	    XtParseTranslationTable(_xplot_translations_before_forking));
385 #endif
386   XtSetValues (_plotter->y_canvas, wargs, (Cardinal)1);
387 
388   /* get Label widget's window; store it in Plotter struct as
389      `drawable #2' */
390   _plotter->x_drawable2 = (Drawable)XtWindow(_plotter->y_canvas);
391 
392   /* get the window size that was actually chosen, store it */
393 #ifdef USE_MOTIF
394   XtSetArg (wargs[0], XmNwidth, &window_width);
395   XtSetArg (wargs[1], XmNheight, &window_height);
396 #else
397   XtSetArg (wargs[0], XtNwidth, &window_width);
398   XtSetArg (wargs[1], XtNheight, &window_height);
399 #endif
400   XtGetValues (_plotter->y_canvas, wargs, (Cardinal)2);
401   _plotter->data->imin = 0;
402   _plotter->data->imax = (int)window_width - 1;
403   /* note flipped-y convention for this device: min > max */
404   _plotter->data->jmin = (int)window_height - 1;
405   _plotter->data->jmax = 0;
406 
407   /* compute the NDC to device-frame affine map, set it in Plotter */
408   _compute_ndc_to_device_map (_plotter->data);
409 
410   /* request backing store for Label widget's window */
411   if (DoesBackingStore(screen_struct))
412     {
413       XSetWindowAttributes attributes;
414       unsigned long value_mask;
415 
416       attributes.backing_store = Always;
417       value_mask = CWBackingStore;
418       XChangeWindowAttributes (_plotter->x_dpy, (Window)_plotter->x_drawable2,
419 			       value_mask, &attributes);
420     }
421 
422   /* determine whether to use double buffering */
423   _plotter->x_double_buffering = X_DBL_BUF_NONE;
424   double_buffer_s = (const char *)_get_plot_param (_plotter->data,
425 						   "USE_DOUBLE_BUFFERING");
426 
427   /* backward compatibility: "fast" now means the same as "yes" */
428   if (strcmp (double_buffer_s, "fast") == 0)
429     double_buffer_s = "yes";
430 
431 #ifdef HAVE_X11_EXTENSIONS_XDBE_H
432 #ifdef HAVE_DBE_SUPPORT
433   if (strcmp (double_buffer_s, "yes") == 0)
434     /* check whether X server supports DBE extension */
435     {
436       int major_version, minor_version;
437       int one = 1;		/* number of screens to look at */
438       XdbeScreenVisualInfo *sv_info;
439 
440       if (XdbeQueryExtension (_plotter->x_dpy, &major_version, &minor_version)
441 	  && (sv_info = XdbeGetVisualInfo (_plotter->x_dpy,
442 					   /* 2nd arg specifies screen */
443 					   &_plotter->x_drawable2,
444 					   &one)) != NULL)
445 	/* server supports DBE extension; for screen, a list of
446 	   visuals / depths / performance hints was returned */
447 	{
448 	  bool ok = false;
449 	  int i, num_visuals = sv_info->count;
450 	  XdbeVisualInfo *vis_info = sv_info->visinfo;
451 	  VisualID visual_id = XVisualIDFromVisual (_plotter->x_visual);
452 
453 	  /* See whether default visual supports double buffering.  If not,
454 	     could invoke XGetVisualInfo() to check the depth and perflevel
455 	     of each visual that does, and select the `best' one.  (Would
456 	     also need to call XCreateColormap() to create a colormap of
457 	     that visual type.  When using the default visual we can use
458 	     the default colormap, but when not, we don't have that
459 	     luxury.)
460 
461 	     Maybe someday... That enhancement would be important for Xsgi,
462 	     which typically has a default 8-plane PseudoColor visual that
463 	     does _not_ support double buffering, and various other
464 	     visuals, including some 8-plane and 12-plane ones that do (and
465 	     some 15-plane and 24-plane ones that don't). */
466 
467 	  for (i = 0; i < num_visuals; i++)
468 	    /* check visual ID for each visual in list */
469 	    if (vis_info[i].visual == visual_id) /* matches the default */
470 	      {
471 		ok = true;	/* default visual is OK */
472 		break;
473 	      }
474 	  XdbeFreeVisualInfo (sv_info);
475 	  if (ok)
476 	    /* allocate back buffer, to serve as our graphics buffer;
477 	       save it as `x_drawable3' */
478 	    {
479 	      _plotter->x_drawable3 =
480 		XdbeAllocateBackBufferName (_plotter->x_dpy,
481 					    _plotter->x_drawable2,
482 					    (XdbeSwapAction)XdbeUndefined);
483 	      /* set double buffering type in Plotter structure */
484 	      _plotter->x_double_buffering = X_DBL_BUF_DBE;
485 	    }
486 	}
487     }
488 #endif /* HAVE_DBE_SUPPORT */
489 #endif /* HAVE_X11_EXTENSIONS_XDBE_H */
490 
491 #ifdef HAVE_X11_EXTENSIONS_MULTIBUF_H
492 #ifdef HAVE_MBX_SUPPORT
493   if (_plotter->x_double_buffering == X_DBL_BUF_NONE
494       && strcmp (double_buffer_s, "yes") == 0)
495     /* check whether X server supports the (obsolete) MBX extension, as a
496        substitute for DBE */
497     {
498       int event_base, error_base;
499       int major_version, minor_version;
500 
501       if (XmbufQueryExtension (_plotter->x_dpy, &event_base, &error_base)
502 	  && XmbufGetVersion (_plotter->x_dpy, &major_version, &minor_version))
503 	/* server supports MBX extension */
504 	{
505 	  Multibuffer multibuf[2];
506 	  int num;
507 
508 	  num = XmbufCreateBuffers (_plotter->x_dpy,
509 				    (Window)_plotter->x_drawable2, 2,
510 				    MultibufferUpdateActionUndefined,
511 				    MultibufferUpdateHintFrequent,
512 				    multibuf);
513 	  if (num == 2)
514 	    /* Yow, got a pair of multibuffers.  We'll write graphics to
515 	       the first (`x_drawable3'), and interchange them on each
516 	       erase().  See y_erase.c. */
517 	    {
518 	      _plotter->x_drawable3 = multibuf[0];
519 	      _plotter->y_drawable4 = multibuf[1];
520 	      /* set double buffering type in Plotter structure */
521 	      _plotter->x_double_buffering = X_DBL_BUF_MBX;
522 	    }
523 	  else
524 	    _plotter->warning (R___(_plotter)
525 			       "X server refuses to support multibuffering");
526 	}
527     }
528 #endif /* HAVE_MBX_SUPPORT */
529 #endif /* HAVE_X11_EXTENSIONS_MULTIBUF_H */
530 
531   if (_plotter->x_double_buffering == X_DBL_BUF_NONE)
532     /* user didn't request double buffering, or did but special support for
533        double buffering isn't contained in the X server */
534     {
535       Pixmap bg_pixmap;
536 
537       /* create background pixmap for Label widget; 2nd arg (window) is
538          only used for determining the screen */
539       bg_pixmap = XCreatePixmap(_plotter->x_dpy,
540 				_plotter->x_drawable2,
541 				(unsigned int)window_width,
542 				(unsigned int)window_height,
543 				(unsigned int)PlanesOfScreen(screen_struct));
544       /* If user requested double buffering but the server doesn't support
545 	 it, we'll double buffer `by hand', and this pixmap will be the one
546 	 (of two) into which we'll draw.  If user didn't request double
547 	 buffering, we'll use it as the 2nd of two drawables into which
548 	 we'll draw, the other being the window. */
549       if (strcmp (double_buffer_s, "yes") == 0)
550 	{
551 	  _plotter->x_drawable3 = (Drawable)bg_pixmap;
552 	  _plotter->x_double_buffering = X_DBL_BUF_BY_HAND;
553 	}
554       else
555 	{
556 	  _plotter->x_drawable1 = (Drawable)bg_pixmap;
557 	  _plotter->x_double_buffering = X_DBL_BUF_NONE;
558 	}
559     }
560 
561   /* add X GC's to drawing state (which was constructed by openpl() before
562      begin_page() was called), so we can at least fill with solid color */
563   _pl_x_add_gcs_to_first_drawing_state (S___(_plotter));
564 
565   /* If not double-buffering, clear both pixmap and window by filling them
566      with the drawing state's background color, via XFillRectangle.  If
567      double buffering, do something similar (see y_erase.c). */
568   _pl_y_erase_page (S___(_plotter));
569 
570   /* If double buffering, must invoke `erase' one more time to clear both
571      graphics buffer and window, since what `erase' does in that case is
572      (1) copy the graphics buffer to window, and (2) clear the graphics
573      buffer. */
574   if (_plotter->x_double_buffering != X_DBL_BUF_NONE)
575     _pl_y_erase_page (S___(_plotter));
576 
577   if (_plotter->x_double_buffering == X_DBL_BUF_NONE
578       || _plotter->x_double_buffering == X_DBL_BUF_BY_HAND)
579     /* have a pixmap, so install it as Label widget's background pixmap */
580     {
581       Pixmap bg_pixmap;
582 
583       bg_pixmap = ((_plotter->x_double_buffering == X_DBL_BUF_BY_HAND) ?
584 		   _plotter->x_drawable3 : _plotter->x_drawable1);
585 #ifdef USE_MOTIF
586       XtSetArg (wargs[0], XmNlabelPixmap, bg_pixmap);
587       XtSetArg (wargs[1], XmNlabelType, XmPIXMAP);
588       XtSetValues (_plotter->y_canvas, wargs, (Cardinal)2);
589 #else
590       XtSetArg (wargs[0], XtNbitmap, bg_pixmap);
591       XtSetValues (_plotter->y_canvas, wargs, (Cardinal)1);
592 #endif
593     }
594 
595   /* do an XSync on the display (this will cause the background color to
596    show up if it hasn't already) */
597   _pl_x_flush_output (S___(_plotter));
598 
599   /* Note: at this point the drawing state, which we added X GC's to, a few
600      lines above, won't be ready for drawing graphics, since it won't
601      contain an X font or meaningful line width.  To retrieve an X font and
602      set the line width, user will need to invoke space() after openpl().  */
603 
604   return true;
605 }
606 
607 static bool
_bitmap_size_ok(const char * bitmap_size_s)608 _bitmap_size_ok (const char *bitmap_size_s)
609 {
610   int width, height;
611 
612   if (bitmap_size_s
613       /* should parse this better */
614       && (sscanf (bitmap_size_s, "%dx%d", &width, &height) == 2)
615       && (width > 0) && (height > 0))
616     return true;
617   else
618     return false;
619 }
620 
621 /* This is the XPlotter-specific version of the _maybe_get_new_colormap()
622    method, which is invoked when a Plotter's original colormap fills up.
623    It overrides the XDrawable-specific version, which is a no-op. */
624 void
_pl_y_maybe_get_new_colormap(S___ (Plotter * _plotter))625 _pl_y_maybe_get_new_colormap (S___(Plotter *_plotter))
626 {
627   Colormap new_pl_x_cmap;
628 
629   /* sanity check */
630   if (_plotter->x_cmap_type != X_CMAP_ORIG)
631     return;
632 
633   _plotter->warning (R___(_plotter)
634 		     "color supply low, switching to private colormap");
635   new_pl_x_cmap = XCopyColormapAndFree (_plotter->x_dpy, _plotter->x_cmap);
636 
637   if (new_pl_x_cmap == 0)
638     /* couldn't create colormap */
639     {
640       _plotter->warning (R___(_plotter)
641 			 "unable to create private colormap");
642       _plotter->warning (R___(_plotter)
643 			 "color supply exhausted, can't create new colors");
644       _plotter->x_colormap_warning_issued = true;
645     }
646   else
647     /* got a new colormap */
648     {
649       Arg wargs[1];		/* a lone werewolf */
650 
651       /* place in Plotter, flag as new */
652       _plotter->x_cmap = new_pl_x_cmap;
653       _plotter->x_cmap_type = X_CMAP_NEW;
654 
655       /* switch to it: install in y_toplevel shell widget */
656       XtSetArg (wargs[0], XtNcolormap, _plotter->x_cmap);
657       XtSetValues (_plotter->y_toplevel, wargs, (Cardinal)1);
658     }
659 
660   return;
661 }
662 
663 /* This is the XPlotter-specific version of the _maybe_handle_x_events()
664    method, which is invoked at the end of most XDrawablePlotter drawing
665    operations.  It overrides the XDrawablePlotter-specific version, which
666    is a no-op.  It does two things.
667 
668    1. Provided an XPlotter's X_AUTO_FLUSH parameter is "yes" (the default),
669       it invokes XFlush() to flush the X output buffer.  This makes most
670       drawing operations more or less unbuffered: as the libplot functions
671       are invoked, the graphics are sent to the X display.
672 
673    2. It scans through the _xplotters[] sparse array, which contains
674       pointers to all currently existing XPlotters, and processes pending
675       X events associated with any of their application contexts.
676 
677    Why do we do #2?  Once closepl() has been invoked on an XPlotter, the
678    window popped up by openpl() is managed by a forked-off process via
679    XtAppMainLoop().  But until that time, we must process events manually.
680    (To speed up drawing, we perform #2 only once per
681    X_EVENT_HANDLING_PERIOD invocations of this function.)
682 
683    #2 is accomplished by an hand-crafted event loop, the heart of which is
684    the line
685 
686        if (XtAppPending (_xplotters[i]->y_app_con))
687          XtAppProcessEvent (_xplotters[i]->y_app_con, XtIMAll);
688 
689    which, for Plotter number i, flushes the X output buffer, checks for
690    events and processes them.  This line is executed as many times as we
691    think safe.  Thereby hangs a tale.
692 
693    Nathan Salwen <salwen@physics.harvard.edu> has discovered that before
694    invoking XtAppPending(), we should really check, using Xlib calls and
695    select(), whether there are events waiting (either in the libX11 input
696    buffer, or on the network socket).  The reason is that if no events are
697    available, XtAppPending() may block, at least on some systems.  This
698    does not agree with Xt documentation, but happens nonetheless.  And it
699    is not what we want.
700 
701    The reason for XtAppPending's unfortunate behavior is apparently the
702    following.
703 
704    XtAppPending() invokes the Xlib function XEventsQueued(), first with
705    mode=QueuedAfterReading [which returns the number of events in the input
706    queue if nonempty; if empty, tries to extract more events from the
707    socket] and then with mode=QueuedAfterFlush [which flushes the output
708    buffer with XFlush() and checks if there is anything in the input queue;
709    if not, it tries to extract more events from the socket].  (N.B. If,
710    alternatively, it used mode=QueuedAlready, it would look only at the
711    number of events in the input queue.)  And sadly, when trying to extract
712    events from the socket, XEventsQueued() calls select() in such a way
713    that it can block.
714 
715    So before invoking XtAppPending() we call select() ourselves to check
716    whether data is available on the network socket, and we don't allow it
717    to block.  We invoke XtAppPending and XtAppProcessEvent only if we're
718    absolutely sure they won't block.
719 
720    Thanks also to Massimo Santini <santini@dsi.unimi.it> for helping to
721    clear up the problem. */
722 
723 #define X_EVENT_HANDLING_PERIOD 4
724 
725 void
_pl_y_maybe_handle_x_events(S___ (Plotter * _plotter))726 _pl_y_maybe_handle_x_events(S___(Plotter *_plotter))
727 {
728   if (_plotter->y_auto_flush)
729   /* Flush output buffer if we're *not* in the middle of constructing a
730      path, or if we are, but the path will be drawn with a solid,
731      zero-width pen.  Latter is for consistency with our convention that
732      solid, zero-width paths should appear on the display as they're drawn
733      (see x_cont.c). */
734     {
735       if (_plotter->drawstate->path == (plPath *)NULL
736 	  || (_plotter->drawstate->line_type == PL_L_SOLID
737 	      && !_plotter->drawstate->dash_array_in_effect
738 	      && _plotter->drawstate->points_are_connected
739 	      && _plotter->drawstate->quantized_device_line_width == 0))
740 	XFlush (_plotter->x_dpy);
741     }
742 
743   if (_plotter->y_event_handler_count % X_EVENT_HANDLING_PERIOD == 0)
744     /* process all XPlotters' events, if any are available */
745     {
746       int i;
747 
748 #ifdef PTHREAD_SUPPORT
749 #ifdef HAVE_PTHREAD_H
750   /* lock the global variables _xplotters[] and _xplotters_len */
751   pthread_mutex_lock (&_xplotters_mutex);
752 #endif
753 #endif
754 
755   /* loop over XPlotters */
756   for (i = 0; i < _xplotters_len; i++)
757     {
758       if (_xplotters[i] != NULL
759 	  && _xplotters[i]->data->opened /* paranoia */
760 	  && _xplotters[i]->data->open
761 	  && _xplotters[i]->y_app_con != NULL) /* paranoia */
762 	/* XPlotter is open */
763 	{
764 	  /* Our handcrafted event handling loop.  Check for pending X
765 	     events, either in the libX11 input queue or on the network
766 	     socket itself, and pull them off and process them one by one,
767 	     trying very hard not to generate a call to select() that would
768 	     block.  We loop until no more events are available. */
769 	  for ( ; ; )
770 	    {
771 	      bool have_data;
772 
773 	      have_data = false; /* default */
774 
775 	      if (QLength(_xplotters[i]->x_dpy) > 0)
776 		/* one or more events has already been pulled off the
777 		   socket and are in the libX11 input queue; so we can
778 		   safely invoke XtAppPending(), and it will return `true' */
779 		have_data = true;
780 
781 	      else
782 		/* libX11 input queue is empty, so check whether data is
783 		   available on the socket by doing a non-blocking select() */
784 		{
785 		  int connection_number;
786 		  int maxfds, select_return;
787 		  fd_set readfds;
788 		  struct timeval timeout;
789 
790 		  timeout.tv_sec = 0; /* make select() non-blocking! */
791 		  timeout.tv_usec = 0;
792 
793 		  connection_number =
794 		    ConnectionNumber(_xplotters[i]->x_dpy);
795 		  maxfds = 1 + connection_number;
796 		  FD_ZERO (&readfds);
797 		  FD_SET (connection_number, &readfds);
798 		  select_return =
799 		    select (maxfds, &readfds, NULL, NULL, &timeout);
800 
801 		  if (select_return < 0 && errno != EINTR)
802 		    {
803 		      _plotter->error (R___(_plotter) strerror (errno));
804 		      break;	/* on to next Plotter */
805 		    }
806 		  if (select_return > 0)
807 		    /* have data waiting on the socket, waiting to be
808 		       pulled off, so we'll invoke XtAppPending() to move
809 		       it into the libX11 input queue */
810 		    have_data = true;
811 		}
812 
813 	      if (have_data == false)
814 		/* no data, so on to next XPlotter */
815 		break;
816 
817 	      /* Since we got here, we have waiting input data: at least
818 		 one event is either already in the libX11 queue or still
819 		 on the socket.  So we can safely call XtAppPending() to
820 		 read event(s) from the queue, if nonempty, or from the
821 		 socket.  In the latter case (the case of an empty queue),
822 		 XtAppPending() will call XEventsQueued(), which will, in
823 		 turn, do a [potentially blocking!] select().  But the way
824 		 we've done things, we should get an event without
825 		 blocking.
826 
827 		 After invoking XtAppPending, we invoke XtAppProcessEvent,
828 		 which could also potentially block, except that if an
829 		 event is pending, it won't.  So all should be well.
830 
831 		 (Possibly irrelevant side comment.  XtAppPending will
832 		 flush the output buffer if no events are pending.) */
833 
834 	      if (XtAppPending (_xplotters[i]->y_app_con))
835 		/* XtAppPending should always return true, but we invoke it
836 		   anyway to be on the safe side.  Note: it also checks for
837 		   timer and other types of event, besides X events. */
838 		XtAppProcessEvent (_xplotters[i]->y_app_con, XtIMAll);
839 	    }
840 	  /* end of for() loop, i.e. of our hand-crafted event loop */
841 	}
842       /* end of if() test for a open XPlotter */
843     }
844   /* end of loop over XPlotters */
845 
846 #ifdef PTHREAD_SUPPORT
847 #ifdef HAVE_PTHREAD_H
848   /* unlock the global variables _xplotters[] and _xplotters_len */
849   pthread_mutex_unlock (&_xplotters_mutex);
850 #endif
851 #endif
852 
853     }
854   _plotter->y_event_handler_count++;
855 }
856 
857 #ifndef HAVE_STRERROR
858 /* A libplot-specific version of strerror(), for very old systems that
859    don't have one. */
860 
861 extern char *sys_errlist[];
862 extern int sys_nerr;
863 
864 static char *
_plot_strerror(int errnum)865 _plot_strerror (int errnum)
866 {
867   if (errnum < 0 || errnum >= sys_nerr)
868     return "unknown error";
869 
870   return sys_errlist[errnum];
871 }
872 #endif
873