diff -cr modules/FvwmPager.orig/FvwmPager.c modules/FvwmPager/FvwmPager.c
*** modules/FvwmPager.orig/FvwmPager.c	Thu Aug 28 11:11:14 1997
--- modules/FvwmPager/FvwmPager.c	Sun Oct 19 16:58:30 1997
***************
*** 68,73 ****
--- 68,82 ----
  char *WindowHiBack = NULL;
  char *WindowHiFore = NULL;
  
+ int ShowBalloons = 0, ShowPagerBalloons = 0, ShowIconBalloons = 0;
+ char *BalloonTypeString = NULL;
+ char *BalloonBack = NULL;
+ char *BalloonFore = NULL;
+ char *BalloonFont = "fixed";
+ char *BalloonBorderColor = "Black";
+ int BalloonBorderWidth = 1;
+ int BalloonYOffset = 2;
+ 
  int window_w=0, window_h=0, window_x=0, window_y=0;
  int icon_x=-10000, icon_y=-10000, icon_w=0, icon_h=0;
  int usposition = 0,uselabel = 1;
***************
*** 650,655 ****
--- 659,669 ----
  	}
        t->width = t->icon_width;
        t->height = t->icon_height;
+ 
+       /* if iconifying main pager window turn balloons on or off */
+       if ( t->w == Scr.Pager_w )
+ 	ShowBalloons = ShowIconBalloons;
+ 
        MoveResizePagerView(t);
      }
  }
***************
*** 684,689 ****
--- 698,708 ----
        t->y = t->frame_y;
        t->width = t->frame_width;
        t->height = t->frame_height;
+ 
+       /* if deiconifying main pager window turn balloons on or off */
+       if ( t->w == Scr.Pager_w )
+ 	ShowBalloons = ShowPagerBalloons;
+       
        MoveResizePagerView(t);
        if(FocusWin == t)
  	Hilight(t,ON);
***************
*** 1060,1067 ****
  			colors = GetNextToken (colors, &WindowHiBack);
  		}
  	}
        GetConfigLine(fd,&tline);
      }
    return;
  }
- 
--- 1079,1152 ----
  			colors = GetNextToken (colors, &WindowHiBack);
  		}
  	}
+ 
+       
+       /* ... and get Balloon config options ...
+          -- ric@giccs.georgetown.edu */
+       else if ( (strlen(&tline[0]) > 1) &&
+                 (strncasecmp (tline, CatString3 ("*", MyName, "Balloons"),
+                               Clength + 9) == 0) ) {
+ 	CopyString(&BalloonTypeString, &tline[Clength+9]);
+ 
+ 	if ( strncasecmp(BalloonTypeString, "Pager", 5) == 0 ) {
+ 	   ShowPagerBalloons = 1;
+ 	}
+ 	else if ( strncasecmp(BalloonTypeString, "Icon", 4) == 0 ) {
+ 	   ShowIconBalloons = 1;
+ 	}
+ 	else {
+ 	  ShowPagerBalloons = 1;
+ 	  ShowIconBalloons = 1;
+ 	}
+ 
+ 	/* turn this on initially so balloon window is created; later this
+ 	   variable is changed to match ShowPagerBalloons or ShowIconBalloons
+ 	   whenever we receive iconify or deiconify packets */
+         ShowBalloons = 1;
+       }
+ 
+       else if ( (strlen(&tline[0]) > 1) &&
+                 (strncasecmp (tline, CatString3 ("*", MyName, "BalloonBack"),
+                               Clength + 12) == 0) ) {
+         if (Scr.d_depth > 1)
+           CopyString(&BalloonBack, &tline[Clength+12]);
+       }
+ 
+       else if ( (strlen(&tline[0]) > 1) &&
+                 (strncasecmp (tline, CatString3 ("*", MyName, "BalloonFore"), 
+                               Clength + 12) == 0) ) {
+         if (Scr.d_depth > 1)
+           CopyString(&BalloonFore, &tline[Clength+12]);
+       }
+ 
+       else if ( (strlen(&tline[0]) > 1) &&
+                 (strncasecmp (tline, CatString3 ("*", MyName, "BalloonFont"), 
+                               Clength + 12) == 0) ) {
+         CopyString(&BalloonFont, &tline[Clength+12]);
+       }
+ 
+       else if ( (strlen(&tline[0]) > 1) &&
+                 (strncasecmp (tline,
+                               CatString3 ("*", MyName, "BalloonBorderColor"), 
+                               Clength + 19) == 0) ) {
+         CopyString(&BalloonBorderColor, &tline[Clength+19]);
+       }
+       
+       else if ( (strlen(&tline[0]) > 1) &&
+                 (strncasecmp (tline,
+                               CatString3 ("*", MyName, "BalloonBorderWidth"), 
+                               Clength + 19) == 0) ) {
+         sscanf(&tline[Clength+19], "%d", &BalloonBorderWidth);
+       }
+       
+       else if ( (strlen(&tline[0]) > 1) &&
+                (strncasecmp (tline,
+                              CatString3 ("*", MyName, "BalloonYOffset"), 
+                              Clength + 15) == 0) ) {
+         sscanf(&tline[Clength+15], "%d", &BalloonYOffset);
+       }
+ 
        GetConfigLine(fd,&tline);
      }
    return;
  }
diff -cr modules/FvwmPager.orig/FvwmPager.h modules/FvwmPager/FvwmPager.h
*** modules/FvwmPager.orig/FvwmPager.h	Thu Aug 28 11:11:14 1997
--- modules/FvwmPager/FvwmPager.h	Sun Oct 19 15:47:22 1997
***************
*** 68,73 ****
--- 68,84 ----
  } PagerWindow;
  
  
+ typedef struct balloon_window
+ {
+   Window w;              /* ID of balloon window */
+   PagerWindow *pw;       /* pager window it's associated with */
+   XFontStruct *font;
+   int height;            /* height of balloon window based on font */
+   int border;            /* border width */
+   int yoffset;           /* pixels above (<0) or below (>0) pager win */
+ } BalloonWindow;
+ 
+ 
  typedef struct desk_info 
  {
    Window w;
***************
*** 144,149 ****
--- 155,164 ----
  void IconMoveWindow(XEvent *Event,PagerWindow *t);
  void HandleExpose(XEvent *Event);
  void MoveStickyWindows(void);
+ void MapBalloonWindow(XEvent *);
+ void UnmapBalloonWindow(void);
+ void DrawInBalloonWindow(void);
+ 
  #ifdef BROKEN_SUN_HEADERS
  #include "../../fvwm/sun_headers.h"
  #endif
diff -cr modules/FvwmPager.orig/FvwmPager.man modules/FvwmPager/FvwmPager.man
*** modules/FvwmPager.orig/FvwmPager.man	Thu Aug 28 11:11:14 1997
--- modules/FvwmPager/FvwmPager.man	Sun Oct 19 17:08:34 1997
***************
*** 181,190 ****
  Allow the pager to display a window's mini icon in the pager, if it has
  one, instead of showing the window's name.
  
  .SH AUTHOR
  Robert Nation 
  .br
  DeskColor patch contributed by Alan Wild
  .br
  MiniIcons and WindowColors patch contributed by Rob Whapham
! 
--- 181,226 ----
  Allow the pager to display a window's mini icon in the pager, if it has
  one, instead of showing the window's name.
  
+ .IP "*FvwmPagerBalloons [\fItype\fP]"
+ Show a balloon describing the window when the pointer is moved into
+ a window in the pager. Currently only the window's icon name is shown.
+ If \fItype\fP is \fIPager\fP balloons are just shown for an uniconified
+ pager; if \fItype\fP is \fIIcon\fP balloons are just shown for an
+ iconified pager. If \fItype\fP is anything else (or null) balloons are
+ always shown.
+ 
+ .IP "*FvwmPagerBalloonFore \fIcolor\fP"
+ Specifies the color for text in the balloon window. If omitted it
+ defaults to the foreground color for the window being described.
+ 
+ .IP "*FvwmPagerBalloonBack \fIcolor\fP"
+ Specifies the background color for the balloon window. If omitted it
+ defaults to the background color for the window being described.
+ 
+ .IP "*FvwmPagerBalloonFont \fIfont-name\fP"
+ Specifies a font to use for the balloon text. Defaults to \fIfixed\fP.
+ 
+ .IP "*FvwmPagerBalloonBorderWidth \fInumber\fP"
+ Sets the width of the balloon window's border. Defaults to 1.
+ 
+ .IP "*FvwmPagerBalloonBorderColor \fIcolor\fP"
+ Sets the color of the balloon window's border. Defaults to black.
+ 
+ .IP "*FvwmPagerBalloonYOffset \fInumber\fP"
+ The balloon window is positioned to be horizontally centered against
+ the pager window it is describing. The vertical position may be
+ set as an offset. Negative offsets of \fI-n\fP are placed \fIn\fP
+ pixels above the pager window, positive offsets of \fI+n\fP are placed
+ \fIn\fP pixels below. Offsets of 0 are not permitted, as this would permit
+ direct transit from pager window to balloon window, causing an event
+ loop. Defaults to +2.
+ 
+ 
  .SH AUTHOR
  Robert Nation 
  .br
  DeskColor patch contributed by Alan Wild
  .br
  MiniIcons and WindowColors patch contributed by Rob Whapham
! .br
! Balloons patch by Ric Lister <ric@giccs.georgetown.edu>.
diff -cr modules/FvwmPager.orig/x_pager.c modules/FvwmPager/x_pager.c
*** modules/FvwmPager.orig/x_pager.c	Thu Aug 28 11:11:14 1997
--- modules/FvwmPager/x_pager.c	Sun Oct 19 16:55:41 1997
***************
*** 33,44 ****
--- 33,47 ----
  extern int window_w, window_h,window_x,window_y,usposition,uselabel,xneg,yneg;
  extern int StartIconic;
  extern int MiniIcons;
+ extern int ShowBalloons, ShowPagerBalloons, ShowIconBalloons;
+ 
  extern int icon_w, icon_h, icon_x, icon_y;
  XFontStruct *font, *windowFont;
  
  GC NormalGC,DashedGC,HiliteGC,rvGC;
  GC StdGC;
  GC MiniIconGC;
+ GC BalloonGC;
  
  extern PagerWindow *Start;
  extern PagerWindow *FocusWin;
***************
*** 74,79 ****
--- 77,83 ----
  
  
  Window		icon_win;               /* icon window */
+ BalloonWindow balloon;            /* balloon window */
  
  /***********************************************************************
   *
***************
*** 107,112 ****
--- 111,119 ----
    XSetWindowAttributes attributes;
    extern char *PagerFore, *PagerBack, *HilightC;
    extern char *WindowBack, *WindowFore, *WindowHiBack, *WindowHiFore;
+   extern char *BalloonFore, *BalloonBack, *BalloonFont;
+   extern char *BalloonBorderColor;
+   extern int BalloonBorderWidth, BalloonYOffset;
    extern char *font_string, *smallFont;
    int n,m,w,h,i,x,y;
    XGCValues gcv;
***************
*** 402,407 ****
--- 409,483 ----
    gcv.background = back_pix;
    gcv.line_style = LineOnOffDash;
    DashedGC = XCreateGC(dpy, Scr.Root, gcm, &gcv);
+ 
+ 
+   /* create balloon window
+      -- ric@giccs.georgetown.edu */
+   if ( ShowBalloons ) {
+ 
+     valuemask = CWOverrideRedirect | CWEventMask | CWBackPixel | CWBorderPixel;
+ 
+     /* tell WM to ignore this window */
+     attributes.override_redirect = True;
+   
+     attributes.event_mask = ExposureMask;
+     attributes.border_pixel = GetColor(BalloonBorderColor);
+ 
+     /* if given in config set this now, otherwise it'll be set for each
+        pager window when drawn later */
+     attributes.background_pixel = 
+       (BalloonBack == NULL) ? 0 : GetColor(BalloonBack);
+ 
+     /* get font for balloon */
+     if ( (balloon.font = XLoadQueryFont(dpy, BalloonFont)) == NULL ) {
+       if ( (balloon.font = XLoadQueryFont(dpy, "fixed")) == NULL ) {
+         fprintf(stderr,"%s: No fonts available.\n", MyName);
+         exit(1);
+       }
+       fprintf(stderr, "%s: Can't find font '%s', using fixed.\n",
+               MyName, BalloonFont);
+     }
+ 
+     balloon.height = balloon.font->ascent + balloon.font->descent + 1;
+ 
+     /* this may have been set in config */
+     balloon.border = BalloonBorderWidth;
+ 
+ 
+     /* we don't allow yoffset of 0 because it allows direct transit
+        from pager window to balloon window, setting up a
+        LeaveNotify/EnterNotify event loop */
+     if ( BalloonYOffset ) 
+       balloon.yoffset = BalloonYOffset;
+     else {
+       fprintf(stderr,
+        "%s: Warning: you're not allowed BalloonYOffset 0; defaulting to +2\n",
+               MyName);
+       balloon.yoffset = 2;
+     }
+ 
+     /* now create the window */
+     balloon.w = XCreateWindow(dpy, Scr.Root, 
+                               0, 0,            /* coords set later */
+                               1,               /* width set later */
+                               balloon.height,
+                               balloon.border,
+                               CopyFromParent,
+                               InputOutput,
+                               CopyFromParent,
+                               valuemask,
+                               &attributes);
+ 
+     /* set font */
+     gcv.font = balloon.font->fid;
+ 
+     /* if fore given in config set now, otherwise it'll be set later */
+     gcv.foreground = (BalloonFore == NULL) ? 0 : GetColor(BalloonFore);
+ 
+     BalloonGC = XCreateGC(dpy, balloon.w,
+                           GCFont | GCForeground,
+                           &gcv);
+   } /* ShowBalloons */
  }
  
  
***************
*** 449,454 ****
--- 525,538 ----
  
    switch(Event->xany.type)
      {
+     case EnterNotify:
+       if ( ShowBalloons )
+         MapBalloonWindow(Event);
+       break;
+     case LeaveNotify:
+       if ( ShowBalloons )
+         UnmapBalloonWindow();
+       break;
      case ConfigureNotify:
        ReConfigure();
        break;
***************
*** 556,561 ****
--- 640,651 ----
    int i;
    PagerWindow *t;
  
+   /* ric@giccs.georgetown.edu */
+   if ( Event->xany.window == balloon.w ) {
+     DrawInBalloonWindow();
+     return;
+   }
+ 
    for(i=0;i<ndesks;i++)
      {
        if((Event->xany.window == Desks[i].w)
***************
*** 953,958 ****
--- 1043,1052 ----
    attributes.border_pixel = fore_pix;
    attributes.event_mask = (ExposureMask);
  
+   /* ric@giccs.georgetown.edu -- added Enter and Leave events for 
+      popping up balloon window */
+   attributes.event_mask = (ExposureMask | EnterWindowMask | LeaveWindowMask);
+ 
    if((i >= 0)&& (i <ndesks))
      {
        t->PagerView = XCreateWindow(dpy,Desks[i].w, x, y, w, h,1,
***************
*** 1804,1807 ****
--- 1898,2005 ----
  	XSetInputFocus (dpy, t->w, RevertToParent, Event->xbutton.time);
      }
  
+ }
+ 
+ 
+ /* Just maps window ... draw stuff in it later after Expose event
+    -- ric@giccs.georgetown.edu */
+ void MapBalloonWindow (XEvent *event)
+ {
+   PagerWindow *t;
+   XWindowChanges window_changes;
+   Window view, dummy;
+   int view_width, view_height;
+   int matched_window = 0;
+   int x, y;
+   extern char *BalloonBack;
+   
+   /* is this the best way to match X event window ID to PagerWindow ID? */
+   t = Start;
+ 
+   while ( ! matched_window ) {
+     if ( t->PagerView == event->xcrossing.window ) {
+       view = t->PagerView;
+       view_width = t->pager_view_width;
+       view_height = t->pager_view_height;
+       matched_window = 1;
+     }
+     else if ( t->IconView == event->xcrossing.window ) {
+       view = t->IconView;
+       view_width = t->icon_view_width;
+       view_height = t->icon_view_height;
+       matched_window = 1;
+     }
+     else if ( t == NULL ) {
+       return;
+     }
+     else {
+       t = t->next;
+     }
+   }
+ 
+   /* associate balloon with its pager window */
+   balloon.pw = t;
+ 
+   /* calculate window width to accommodate string */
+   window_changes.width = 4 + XTextWidth(balloon.font, t->icon_name,
+                                         strlen(t->icon_name));
+ 
+   /* get x and y coords relative to pager window */
+   x = (view_width / 2) - (window_changes.width / 2) - balloon.border;
+ 
+   if ( balloon.yoffset > 0 ) 
+     y = view_height + balloon.yoffset;
+   else
+     y = balloon.yoffset - balloon.height - (2 * balloon.border);
+ 
+   
+   /* balloon is a top-level window, therefore need to
+      translate pager window coords to root window coords */
+   XTranslateCoordinates(dpy, view, Scr.Root, 
+                         x, y, 
+                         &window_changes.x, &window_changes.y,
+                         &dummy);
+ 
+   /* make sure balloon doesn't go off screen horizontally
+      (actually 2 pixels from edge rather than 0 just to be pretty :-) */
+   if ( window_changes.x < 2 )
+     window_changes.x = 2;
+   else if ( window_changes.x + window_changes.width >
+             Scr.MyDisplayWidth - (2 * balloon.border) - 2 )
+     window_changes.x = Scr.MyDisplayWidth - window_changes.width -
+       (2 * balloon.border) - 2;
+ 
+ 
+   XConfigureWindow(dpy, balloon.w,
+                    CWX | CWY | CWWidth,
+                    &window_changes);
+ 
+   /* if background not set in config make it match pager window */
+   if ( BalloonBack == NULL )
+     XSetWindowBackground(dpy, balloon.w, t->back);
+ 
+   XMapRaised(dpy, balloon.w);
+ }
+ 
+ 
+ /* -- ric@giccs.georgetown.edu */
+ void UnmapBalloonWindow (void)
+ {
+   XUnmapWindow(dpy, balloon.w);
+ }
+ 
+ 
+ /* Draws string in balloon window -- call after it's received Expose event
+    -- ric@giccs.georgetown.edu */
+ void DrawInBalloonWindow (void)
+ {
+   extern char *BalloonFore;
+ 
+   /* if foreground not set in config make it match pager window */
+   if ( BalloonFore == NULL ) 
+     XSetForeground(dpy, BalloonGC, balloon.pw->text);
+ 
+   XDrawString(dpy, balloon.w, BalloonGC,
+               2, balloon.font->ascent, 
+               balloon.pw->icon_name, strlen(balloon.pw->icon_name));
  }

