1 /* vim:set nowrap:
2 *
3 * Rizzle - a 'rollup' manager for Motif.
4 *
5 * Copyright 2002-2004, David Wijnants. All rights reserved.
6 * See LICENSE for details.
7 * email: [protected]
8 **/
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdarg.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <signal.h>
18 #include <X11/Xlib.h>
19 #include <X11/Xmu/WinUtil.h>
20 #include <X11/Xmu/Error.h>
Version control:
26 #ifndef VERSION
27 #error What, no version ?
28 #endif
30 const char *rizzle_idents[] = {
31 "@(#) $Compiled: " __DATE__ " " __TIME__ " $",
32 "@(#) $Id: rizzle.c,v 1.33.2.11 2004/07/07 13:23:45 dave Exp $",
33 "@(#) $Name: rel1-2-2 $",
34 };
Symbolic constants:
40 #define RESCLASS "Rizzle"
41 #define RESNAME "pager"
42 #define RESTITLE "Pager"
44 #define SILENT 0
45 #define QUIET 1
46 #define VERBOSE 2
48 #define DEFAULTMARGIN 4
Global variables:
54 static int hush = 0;
55 static int errors = 0;
56 static int noise = SILENT;
57 static int nodaemon = 0;
58 static int marg = -1;
59 static int sqs = -1;
60 static Atom wm_state;
61 static Atom wm_delete_window;
62 static Atom motif_wm_hints;
63 static Atom rollup_for;
64 static Atom rolled_up;
65 static int bg;
67 typedef struct wininfo {
68 Window full;
69 Window small;
70 char *name;
71 struct wininfo *prev;
72 struct wininfo *next;
73 } WININFO;
74 static WININFO *rollups;
76 typedef struct transinfo {
77 Window win;
78 Window client;
79 Window leader;
80 int hidden;
81 struct transinfo *prev;
82 struct transinfo *next;
83 } TRANSINFO;
84 static TRANSINFO *transients;
86 static struct {
87 Display *dpy;
88 int screen;
89 Window root;
90 Window win;
91 Window pager;
92 int x;
93 int y;
94 } current;
Prototypes:
100 void ParseParams (int argc, char **argv);
101 int Initialise (void);
102 int AlreadyRunning (void);
103 void InitPager (void);
104 void Daemon (void);
105 void RegisterTransients (void);
106 Window UpdateCurrent (int window);
107 WININFO *IsRolledUp (Window w);
108 int IsFullSize (Window w);
109 WININFO *IsOurs (Window w);
110 int IsFragile (Window w);
111 WININFO *RollUpWindow (Window w);
112 void RestoreWindow (Window w);
113 WININFO *AddWindow (void);
114 void RemoveWindow (WININFO *this);
115 Window GetClient (Window w);
116 TRANSINFO *AddTransient (Window w);
117 void RemoveTransient (Window w);
118 void HideTransients (Window cw);
119 void UnHideTransients (Window cw);
120 Window TransientFor (Window cw);
121 Window RizzleRollupFor (Window cw);
122 Window IsRizzleRolledUp (Window cw);
123 void RemoveRollupProps (void);
124 void GetWindowState (Window cw, int *state, Window *icon);
125 Window FindTopmost (void);
126 int CheckWindow (Window w);
127 void InitX (void);
128 void ExitX (void);
129 int ErrorHandler (Display *dpy, XErrorEvent *event);
130 void CleanExit (void);
131 char *EventName (int type);
132 void quiet (char *fmt, ...);
133 void verbose (char *fmt, ...);
134 void always (char *fmt, ...);
Making sure thing are clean on exit (cf. atexit):
140 void sighand (int signo) {
141 char buf[80];
142 sprintf (buf, "\nRizzle killed by signal %d\n", signo);
143 write (2, buf, strlen(buf));
144 exit (EXIT_SUCCESS);
145 }
147 int IOErrorHandler (Display *dpy) {
148 char buf[80];
149 sprintf ( buf,
150 "\nRizzle killed by X server on %s\n",
151 DisplayString(dpy) );
152 write (2, buf, strlen(buf));
153 exit (EXIT_SUCCESS);
154 return (0);
155 }
157 int stricmp (char *s1, char *s2) {
158 while ( *s1 != 0 &&
159 *s2 != 0 &&
160 *s1 == *s2 ) {
161 s1++;
162 s2++;
163 }
164 return ( *s1 - *s2 );
165 }
main(argc,argv)
|
Main Program.
|
Parameters
|
| argc | (in) | int; number of command line parameters. |
| argv | (in) | char**; command line parameters.
|
Returns
|
int; EXIT_SUCCESS or EXIT_FAILURE.
|
181 int main (int argc, char **argv)
182 {
183 int dx = -1, dy = -1; /* button-down coordinates */
185 Window mw = None; /* temp variable - plain window */
187 XEvent event; /* message from event queue */
Initialisation.
193 ParseParams (argc, argv);
194 if ( !Initialise() ) return (EXIT_FAILURE);
195 if ( !nodaemon ) Daemon ();
196 RegisterTransients ();
197 UpdateCurrent (1);
198 atexit (CleanExit);
199 RemoveRollupProps ();
Make clear from the start what we're going to do.
205 printf ("rizzle: rollup windows enabled\n");
206 if ( marg != DEFAULTMARGIN ) printf ("rizzle: wiggle margin %d\n", marg);
207 printf ("\n");
If running as a daemon, stop all further stdio.
213 if ( !nodaemon ) {
214 fflush (stdout);
215 close (STDIN_FILENO); open ("/dev/null", O_RDONLY);
216 close (STDOUT_FILENO); open ("/dev/null", O_WRONLY);
217 }
The event loop:
223 for (;;) {
225 XNextEvent (current.dpy, &event);
226 if ( event.type != UnmapNotify ) mw = None;
228 switch ( event.type ) {
We track motion events because we need to know which window the mouse cursor is on before the popup menu appears. If we didn't, we'd get the window id of the popup menu every time.
237 case MotionNotify:
239 current.x = event.xmotion.x;
240 current.y = event.xmotion.y;
241 current.win = event.xmotion.window;
242 dx = dy = -1;
243 break;
If a window suddenly appears, remember its id and the current mouse position, and also check that the cursor has not been moved by more than a few pixels. The problem is, though, that our own windows will also generate MapNotify events, so we have to check these first.
253 case MapNotify:
255 verbose ("MapNotify event\n");
256 if ( IsOurs(GetClient(event.xmap.window)) ) break;
257 dx = current.x;
258 dy = current.y;
259 UpdateCurrent (0);
260 if ( abs(current.x-dx) <= marg &&
261 abs(current.y-dy) <= marg ) {
262 mw = event.xmap.window;
263 dx = current.x;
264 dy = current.y;
265 }
266 break;
When a window disappears from view, do the clever stuff. Check that the disappearing window is the same as the one that appeared just a moment ago (i.e. it came and went), which suggests it may have been a popup window. Even so, it might still be one of ours (it might be the full-size window we've just hidden away). Second, check that the cursor has not moved (by more than a few pixels); and that the window has not gone altogether (i.e. been destroyed). If it looks like it was the elusive popup window, do some more checks to decide whether to roll up or restore the window, or to ignore it anyway (e.g. it must be in the title bar of a window).
284 case UnmapNotify:
286 verbose ("UnmapNotify event\n");
287 if ( event.xunmap.window == mw &&
288 IsOurs(event.xunmap.window) == 0 ) {
289 UpdateCurrent (1);
290 if ( abs(current.x-dx) <= marg &&
291 abs(current.y-dy) <= marg &&
292 current.win != None &&
293 CheckWindow(current.win) != 0 &&
294 IsFragile(GetClient(current.win)) == 0 ) {
295 mw = current.win;
296 if ( IsRolledUp(mw) ) RestoreWindow (mw);
297 else if ( IsFullSize(mw) ) RollUpWindow (mw);
298 }
299 }
300 dx = dy = -1;
301 mw = None;
302 break;
We also need to track the creation of transient windows, because they need to be manually remapped when a rolled-up window is restored.
310 case CreateNotify:
311 verbose ("CreateNotify event\n");
312 AddTransient (event.xcreatewindow.window);
313 break;
314 case DestroyNotify:
315 verbose ("DestroyNotify event\n");
316 RemoveTransient (event.xdestroywindow.window);
317 break;
Anything else ? Just ignore it for now. Expect to see client messages when a rolled-up window is closed by double-clicking the top-left-hand corner (the window will have been restored as a side-effect).
326 case ClientMessage:
327 default:
329 verbose ("%s event\n", EventName(event.type));
330 break;
331 }
332 }
Event loop over. This code is never actually reached, as there is no stop condition in the event loop (we just keep running until we're killed or the X server terminates).
340 XCloseDisplay (current.dpy);
341 return (EXIT_SUCCESS);
342 }
ParseParams(argc,argv)
|
Interpret command line parameters.
|
Parameters
|
| argc | (in) | int; number of command line parameters. |
| argv | (in) | char**; command line parameters.
|
Returns
|
None.
|
358 void ParseParams (int argc, char **argv)
359 {
360 char *p; /* sliding pointer */
362 int t, /* loop counter */
363 i, /* just another int */
364 usage = 0; /* nonzero to display usage */
366 for (t=1; t<argc; t++) {
367 if ( *argv[t] == '-' ) {
368 for (p=argv[t]+1; *p; p++) {
369 switch ( *p ) {
370 case 's': noise = SILENT; break;
371 case 'q': noise = QUIET; break;
372 case 'v': noise = VERBOSE; break;
373 case 'd': nodaemon = 1; break;
374 default: usage = 1; break;
375 }
376 }
377 } else if ( sscanf(argv[t],"sqs=%d",&i) ) {
378 sqs = i;
379 verbose ("Assuming %d-pixel title bar\n", sqs);
380 } else {
381 usage = 1;
382 break;
383 }
384 }
386 if ( usage ) {
387 fprintf (stderr, "\n");
388 fprintf (stderr, "Usage: $ rizzle [-dsqv]\n");
389 fprintf (stderr, "\n");
390 fprintf (stderr, "Options: d do not become a daemon\n");
391 fprintf (stderr, " s silent, no diagnostics\n");
392 fprintf (stderr, " q quiet, minimum diagnostics\n");
393 fprintf (stderr, " v verbose, lots of diagnostics\n");
394 fprintf (stderr, "\n");
395 exit (EXIT_FAILURE);
396 }
397 }
Initialise()
|
Initialise X and prepare for pointer and map/unmap events on the root window; and allocate memory for the window and transients lists.
|
Parameters
|
None.
|
Returns
|
int; nonzero ok, zero on failure.
|
414 int Initialise (void)
415 {
416 int yesno = 0; /* nonzero for success */
418 char *res; /* resource string */
420 long mask = PointerMotionMask /* Motion */
421 | SubstructureNotifyMask /* Map, Unmap, Create, Destroy */
422 ;
424 XColor col; /* colour */
426 if ( (rollups=(WININFO*)calloc(1,sizeof(WININFO))) &&
427 (transients=(TRANSINFO*)calloc(1,sizeof(TRANSINFO))) ) {
428 InitX ();
429 if ( !AlreadyRunning() ) {
430 InitPager ();
431 /* some more X initialisation */
432 XSelectInput (current.dpy, current.root, mask);
433 /* get default background colour */
434 if (( res=XGetDefault(current.dpy,"Rizzle",XNBackground) )) {
435 Colormap cmap = DefaultColormap (current.dpy, current.screen);
436 if ( XParseColor(current.dpy,cmap,res,&col) ) {
437 XAllocColor (current.dpy, cmap, &col);
438 bg = col.pixel;
439 } else {
440 quiet ("Invalid background colour '%s', defaulting to black\n", res);
441 }
442 } else {
443 quiet ("No *background resource, defaulting to black\n");
444 }
445 /* wiggle margin */
446 if ( marg <= 0 ) {
447 res = XGetDefault (current.dpy, "Rizzle", "moveThreshold");
448 if ( !res ) res = XGetDefault (current.dpy, "Mwm", "moveThreshold");
449 if ( !res ) res = "(undefined)";
450 marg = atoi (res);
451 if ( marg <= 0 ) marg = DEFAULTMARGIN;
452 }
453 /* install error handlers */
454 XSetErrorHandler (ErrorHandler);
455 XSetIOErrorHandler (IOErrorHandler);
456 signal (SIGINT, sighand);
457 signal (SIGTERM, sighand);
458 yesno = 1;
459 } else {
460 fprintf ( stderr,
461 "Already running on display %s\n",
462 DisplayString(current.dpy) );
463 }
464 } else {
465 fprintf (stderr, "Out of memory\n");
466 }
468 if ( yesno ) {
469 printf ( "\n"
470 "Rizzle %s - a 'rollup' manager for Motif.\n"
471 "\n"
472 "Copyright (c) 2002-2004 David Wijnants\n"
473 "\n"
474 "No money back, no guarantees, no nothing; just the software and the full source\n"
475 "code, for you to do with whatever you like. See the file LICENSE for details.\n"
476 "\n"
477 "email: [protected]\n"
478 "\n",
479 VERSION );
480 }
482 return (yesno);
483 }
AlreadyRunning()
|
Check that there isn't already a rizzle running, as multiple instances will only interfere with each other.
|
Parameters
|
None.
|
Returns
|
int; nonzero there is already a rizzle, zero if not.
|
499 int AlreadyRunning (void)
500 {
501 unsigned int ncld, /* number of child windows */
502 c; /* (child) loop counter */
504 Window wdummy, /* dummy parameter */
505 *cld=NULL, /* child window array */
506 cw, /* client window id */
507 running=None; /* running rizzle's window id */
509 XClassHint ch; /* class hints */
511 if ( XQueryTree ( current.dpy,
512 current.root,
513 &wdummy,
514 &wdummy,
515 &cld,
516 &ncld ) && ncld ) {
517 for (c=0; c<ncld; c++) {
518 cw = XmuClientWindow (current.dpy, cld[c]);
519 if ( XGetClassHint(current.dpy,cw,&ch) ) {
520 if ( ch.res_class && !strcmp(ch.res_class,RESCLASS) &&
521 ch.res_name && !strcmp(ch.res_name,RESNAME) ) {
522 running = cw;
523 }
524 XFree (ch.res_class);
525 XFree (ch.res_name);
526 if ( running ) break;
527 }
528 }
529 XFree (cld);
530 }
532 return (running);
533 }
InitPager()
|
Create the pager window.
This doesn't do anything yet, but the fact that the window exists, gives an indication that there's already a rizzle running. The window is also guaranteed to go away if we are killed uncleanly, whereas a lockfile tends to stay around rather annoyingly.
|
Parameters
|
None.
|
Returns
|
None.
|
554 void InitPager (void)
555 {
556 XClassHint chint; /* class hints */
558 current.pager = XCreateSimpleWindow ( current.dpy,
559 current.root,
560 10, 10,
561 10, 10,
562 1, 1,
563 0 );
564 XStoreName (current.dpy, current.pager, RESTITLE);
565 chint.res_class = RESCLASS;
566 chint.res_name = RESNAME;
567 XSetWMProperties ( current.dpy,
568 current.pager,
569 NULL,
570 NULL,
571 NULL, 0,
572 NULL, /* size hints */
573 NULL,
574 &chint );
575 }
Daemon()
|
Become a daemon process; this ensures we don't end up a zombie when we're killed.
|
Parameters
|
None.
|
Returns
|
None.
|
591 void Daemon (void)
592 {
593 fflush (stdout);
594 if ( fork() ) exit (EXIT_SUCCESS);
595 setsid ();
596 if ( fork() ) exit (EXIT_SUCCESS);
597 }
RegisterTransients()
|
Check already existing toplevel windows, keeping track of the ones that are transient.
|
Parameters
|
None.
|
Returns
|
None.
|
613 void RegisterTransients (void)
614 {
615 unsigned int ncld, /* number of child windows */
616 c; /* (child) loop counter */
618 Window wdummy, /* dummy parameter */
619 *cld=NULL; /* child window array */
621 if ( !XQueryTree ( current.dpy,
622 RootWindow(current.dpy,current.screen),
623 &wdummy,
624 &wdummy,
625 &cld,
626 &ncld ) ) {
627 always ("XQueryTree() failed\n");
628 return;
629 }
631 if ( cld ) {
632 for (c=0; c<ncld; c++) AddTransient (cld[c]);
633 XFree (cld);
634 }
635 }
UpdateCurrent(window)
|
Register the current pointer location and, optionally, which window it's on. Keep the bits in the global structure current.
|
Parameters
|
| window | (in) | int; nonzero to include window in update.
|
Returns
|
Window; current window.
|
652 Window UpdateCurrent (int window)
653 {
654 int idummy; /* dummy integer */
655 unsigned int udummy; /* dummy unsigned */
656 Window wdummy; /* dummy window */
658 Window cwin; /* current window */
660 XQueryPointer ( current.dpy, /* display */
661 current.root, /* window */
662 &wdummy, /* root return */
663 &cwin, /* child return */
664 ¤t.x, ¤t.y, /* root x,y return */
665 &idummy, &idummy, /* child x,y return */
666 &udummy ); /* mask return */
668 if ( window ) current.win = cwin;
670 return (cwin);
671 }
IsRolledUp(w)
|
Check if the specified window is one of our rolled-up title bars; just a matter of scanning our linked list.
|
Parameters
|
| w | (in) | Window; (plain) window id.
|
Returns
|
WININFO*; the window list element if the window is currently rolled up, NULL if not.
|
688 WININFO *IsRolledUp (Window w)
689 {
690 WININFO *this; /* sliding pointer */
692 if ( !CheckWindow(w) ) return (NULL);
693 w = GetClient (w);
695 for ( this = rollups;
696 this->next != NULL && this->small != w;
697 this = this->next ) ;
699 if ( this != NULL && this->small == w ) {
700 verbose ("Window 0x%lx is a rollup\n", w);
701 return (this);
702 }
704 verbose ("Window 0x%lx is not a rollup\n", w);
705 return (NULL);
706 }
IsFullSize(w)
|
Check that we've clicked on the title bar of a full size window. This is the complicated bit that probably depends most on mwm, as other window managers are likely to give different XQueryTree() results.
|
Parameters
|
| w | (in) | Window; (plain) window id.
|
Returns
|
int; nonzero if on title bar, zero if not.
|
724 int IsFullSize (Window w)
725 {
726 unsigned int ncld; /* number of children */
728 Window *cld = NULL, /* child window array */
729 tcw, /* 'topmost child window' */
730 wdummy; /* dummy window variable */
732 XWindowAttributes attr; /* window attributes */
734 int wx, wy; unsigned int ww, wh, wb, wd; /* window geometry */
Note: we do not convert to client window ids here, as the X routines [ the XQueryTree() hierarchy in particular ] require the plain window id from the X event. We do make a quick check to see if the window still exists (I know, this is an impossible thing to do...).
744 if ( !CheckWindow(w) ) return (0);
First of all, check that it's not an override redirect window, i.e. one we have to ignore.
751 verbose ("Attributes: ");
752 if ( !XGetWindowAttributes(current.dpy,w,&attr) ) {
753 always ("XGetWindowAttributes() failed !\n");
754 return (0);
755 }
756 if ( attr.override_redirect ) {
757 verbose ("override redirect !\n");
758 return (0);
759 }
760 verbose ("ok.\n");
Check if the window has children, so we don't blitz mwm icons (because they are not override redirect). While we've got the list of children, remember the id of the topmost in the stacking order [ which is the last one in the list returned by QueryTree() ] in case we need to figure out the size of the title bar.
771 verbose ("Children: ");
772 if ( !XQueryTree ( current.dpy,
773 w,
774 &wdummy,
775 &wdummy,
776 &cld,
777 &ncld ) || !cld ) {
778 verbose ("none !\n");
779 return (0);
780 }
781 verbose ("%d, ok\n", ncld);
782 tcw = cld[ncld-1];
783 XFree (cld);
If we don't already know the size of the title bar, now's the time to work it out from the x,y coordinates of the topmost child window.
791 if ( sqs == -1 ) {
792 XGetGeometry ( current.dpy,
793 tcw,
794 &wdummy,
795 &wx, &wy,
796 &ww, &wh,
797 &wb, &wd );
798 if ( !wy ) {
799 verbose ("Still no title bar size\n");
800 return (0);
801 }
802 sqs = wy;
803 verbose ("Title bar is %d pixels\n", sqs);
804 }
Check that the current pointer location is in the top band of the window (title bar), but not on the system menu button.
811 verbose ("Pointer location: ");
812 XGetGeometry ( current.dpy,
813 w,
814 &wdummy,
815 &wx, &wy,
816 &ww, &wh,
817 &wb, &wd );
818 if ( (current.y-wy) > sqs || (current.x-wx) < sqs ) {
819 verbose ("not in top %d pixels !\n", sqs);
820 return (0);
821 }
822 verbose ("in top %d pixels, ok.\n", sqs);
All the boxes have been ticked, we're in business !
828 verbose ("We're on a title bar.\n");
829 return (1);
830 }
IsOurs(w)
|
Check if the specified window is one of ours (full-size or rolled-up), so we can ignore the map/unmap events for these. Just a matter of scanning our linked list.
|
Parameters
|
| w | (in) | Window; window id.
|
Returns
|
WININFO*; the window list element if the window is one of ours, NULL if not.
|
848 WININFO *IsOurs (Window w)
849 {
850 WININFO *this; /* sliding pointer */
852 for ( this = rollups;
853 this->next != NULL && w != this->small && w != this->full;
854 this = this->next ) ;
856 if ( this != NULL && this->small == w ) {
857 return (this);
858 }
860 return (NULL);
861 }
IsFragile(w)
|
Check if the specified window is one we should definitely not roll up because something horrible might happen (like mwm dying).
If the window has a resource called isFragile that is set to 'true', treat the application as fragile. If the resource is set to 'false', treat the application as safe, regardless of 'known dodgyness'.
One or two particularly important and commonly-used 'dodgy' applications (such as mwm) are hard-coded for convenience, because you wouldn't want rizzle to break a typical system by default if you happen to forget to define the necessary resources.
|
Parameters
|
| w | (in) | Window; (client) window id.
|
Returns
|
int; zero if it's safe to rollup this window, nonzero if it isn't.
|
890 int IsFragile (Window cw)
891 {
892 char *res; /* resource value */
894 int unsafe = 0; /* nonzero means do not roll up */
896 XClassHint ch; /* class hints: name and resource */
898 if ( XGetClassHint(current.dpy,cw,&ch) ) {
899 if ( ch.res_class ) {
900 if ( !strcmp(ch.res_class,"Mwm") ) {
901 unsafe = 1;
902 res = XGetDefault (current.dpy, ch.res_class, "isFragile");
903 if ( res ) {
904 if ( !stricmp(res,"f") ||
905 !stricmp(res,"false") ) {
906 verbose ( "Your %s is not fragile? "
907 "Ok, on your head be it, then.\n",
908 ch.res_class );
909 unsafe = 0;
910 }
911 }
912 } else {
913 res = XGetDefault (current.dpy, ch.res_class, "isFragile");
914 if ( res ) {
915 if ( !stricmp(res,"t") ||
916 !stricmp(res,"true") ) {
917 unsafe = 1;
918 }
919 }
920 }
921 }
922 XFree (ch.res_name);
923 XFree (ch.res_class);
924 }
926 return (unsafe);
927 }
RollUpWindow(w)
|
Remove the specified window from view, and replace it with a rolled-up title bar. The replacement title bar gets the same position, width and properties as the original window.
|
Parameters
|
| w | (in) | Window; (plain) window id of full-size window.
|
Returns
|
WININFO*; new window list element if the window has been rolled up, NULL if not.
|
946 WININFO *RollUpWindow (Window w)
947 {
948 int wx, wy, /* window location */
949 idummy, /* dummy int variable */
950 format; /* format of property data */
952 unsigned int ww, wb, /* window dimensions */
953 udummy; /* dummy unsigned int variable */
955 unsigned long nitems, /* number of items in props */
956 bytesafter; /* number of bytes not read */
958 unsigned char *props = NULL; /* mwm properties */
960 Window wdummy, /* dummy window variable */
961 cw; /* client window */
963 XSizeHints hints; /* minimum window size */
965 WININFO *new; /* new window list element */
967 Atom type; /* type of property data */
Add the window to the linked list.
973 new = AddWindow ();
974 if ( !new ) return (NULL);
975 cw = GetClient (w);
XGetGeometry() and XResizeWindow() are a bit weird; to get the rolled-up title bar in the same position as the original window, we must use the client window width (i.e. without a border), and the toplevel position (because the client window always has 0,0 coordinates) - hence the two function calls.
985 XGetGeometry ( current.dpy,
986 cw,
987 &wdummy,
988 &idummy, &idummy,
989 &ww, &udummy,
990 &udummy, &udummy );
991 XGetGeometry ( current.dpy,
992 w,
993 &wdummy,
994 &wx, &wy,
995 &udummy, &udummy,
996 &wb, &udummy );
Create a new rolled-up window, and store its client window id. Also make sure we trap the WM_DELETE_WINDOW protocol events, otherwise we'll get deaded (you rotten swine, Eccles!) whenever a rolled-up window gets double-clicked in its top-left-hand corner.
1006 new->small = XCreateSimpleWindow ( current.dpy, /* display */
1007 current.root, /* parent */
1008 1, 1, /* x,y */
1009 1, 1, /* width,height */
1010 wb, /* border width */
1011 0, /* border pixel */
1012 bg ); /* background */
1013 XSetWMProtocols (current.dpy, new->small, &wm_delete_window, 1);
Copy the original window's properties to the rollup (including such things as the absence of resize handles on dialog boxes).
1020 if ( !XGetWindowProperty ( current.dpy, /* display */
1021 cw, /* window */
1022 motif_wm_hints, /* property */
1023 0L, /* long offset */
1024 20L, /* long length */
1025 False, /* don't delete */
1026 motif_wm_hints, /* required type */
1027 &type, /* actual type return */
1028 &format, /* format return */
1029 &nitems, /* number of items */
1030 &bytesafter, /* bytes after return */
1031 &props ) /* mwm hints */
1032 && props ) {
1033 XChangeProperty ( current.dpy,
1034 new->small,
1035 motif_wm_hints,
1036 type,
1037 format,
1038 PropModeReplace,
1039 props,
1040 nitems );
1041 XFree (props);
1042 }
If the original window is transient, make the rolled-up one transient for the same leader.
1049 new->small = GetClient (new->small);
1050 if (( wdummy=TransientFor(cw) )) {
1051 XSetTransientForHint (current.dpy, new->small, wdummy);
1052 }
The rolled-up window wants to be resizeable horizontally, but not vertically.
1059 hints.max_height = 0;
1060 hints.max_width = DisplayWidth (current.dpy, current.screen);
1061 hints.flags = PMaxSize;
1062 XSetWMProperties ( current.dpy, /* display */
1063 new->small, /* window */
1064 NULL, /* window name */
1065 NULL, /* icon name */
1066 NULL, /* argv */
1067 0, /* argc */
1068 &hints, /* normal_hints */
1069 NULL, /* wm_hints */
1070 NULL ); /* class_hint */
Make the rolled-up window easier to recognise and pair up with its original, by setting their _RIZZLE_ROLLUP_FOR and _RIZZLE_ROLLED_UP properties.
1078 XChangeProperty ( current.dpy,
1079 new->small,
1080 rollup_for,
1081 rollup_for,
1082 32,
1083 PropModeReplace,
1084 (unsigned char*)&cw,
1085 1 );
1086 XChangeProperty ( current.dpy,
1087 cw,
1088 rolled_up,
1089 rolled_up,
1090 32,
1091 PropModeReplace,
1092 (unsigned char*)&new->small,
1093 1 );
If a window has mapped transients, we will get DestroyNotify events when those windows are automatically popped down by the window manager. When this happens, we must not remove those windows from the list of transients, as we will need to remap them ourselves when the rolled-up window is restored.
1103 HideTransients (cw);
Popup the rolled-up window, and remove the original from view.
1109 new->full = cw;
1110 if ( XFetchName(current.dpy,new->full,&new->name) && new->name ) {
1111 XStoreName (current.dpy, new->small, new->name);
1112 }
1113 XMapWindow (current.dpy, new->small);
1114 XMoveResizeWindow (current.dpy, new->small, wx, wy, ww, 1);
1115 quiet ("Rolling up 0x%lx (%s)\n", new->full, new->name?new->name:"");
1116 XWithdrawWindow (current.dpy, new->full, current.screen);
1117 return (new);
1118 }
RestoreWindow(w)
|
Replace the specified rolled-up window with its full-size equivalent. The full-size window has its original height, but the position and width are taken from the rollup, which may have been moved or resized by the user.
|
Parameters
|
| w | (in) | Window; window id of rolled-up window.
|
Returns
|
None.
|
1137 void RestoreWindow (Window w)
1138 {
1139 int wx, wy, /* window location */
1140 idummy; /* dummy int variable */
1142 unsigned int ww, wh, /* window dimensions */
1143 udummy; /* dummy unsigned int variable */
1145 Window wdummy, /* dummy window variable */
1146 cw; /* client window */
1148 XWMHints *hints; /* did window start iconic ? */
1150 WININFO *this; /* sliding pointer */
Lookup the rollup window in the linked list, using its client window id.
1157 cw = GetClient (w);
1158 for ( this = rollups;
1159 this->next != NULL && this->small != cw;
1160 this = this->next ) ;
1161 if ( !this ) {
1162 always ("'ere, this isn't a rollup...\n");
1163 return;
1164 }
If the application was started as an icon, make sure it stays up, and doesn't go back to its iconic state.
1171 if (( hints=XGetWMHints(current.dpy,this->full) )) {
1172 if ( hints->initial_state == IconicState ) {
1173 hints->initial_state = NormalState;
1174 XSetWMHints (current.dpy, this->full, hints);
1175 }
1176 XFree (hints);
1177 }
We have the same problem restoring the window as when we shrank it, namely that we have to ask for a client-size window (without a border), and get a slightly larger window, but the position is given with the border included. In this case we have three function calls, because we also need the original height (even though we're not going to be changing it).
1189 XGetGeometry ( current.dpy,
1190 this->full,
1191 &wdummy,
1192 &idummy, &idummy,
1193 &udummy, &wh,
1194 &udummy, &udummy );
1195 XGetGeometry ( current.dpy,
1196 w,
1197 &wdummy,
1198 &wx, &wy,
1199 &udummy, &udummy,
1200 &udummy, &udummy );
1201 XGetGeometry ( current.dpy,
1202 this->small,
1203 &wdummy,
1204 &idummy, &idummy,
1205 &ww, &udummy,
1206 &udummy, &udummy );
Remove the window's _RIZZLE_ROLLED_UP property.
1212 XDeleteProperty (current.dpy, this->full, rolled_up);
Popup the full-size window, and remove the rollup from view. Make sure the restored window is the same width as the rolled-up title bar, and get rid of the rollup. Before removing the window from the list, we will need to manually remap (unhide) its transients.
1221 quiet ("Unrolling 0x%lx (%s)\n", this->full, this->name?this->name:"");
1222 XMapWindow (current.dpy, this->full);
1223 XMoveResizeWindow ( current.dpy,
1224 this->full,
1225 wx, wy,
1226 ww, wh );
1227 XDestroyWindow (current.dpy, this->small);
1228 if ( this->name ) XFree (this->name);
1229 UnHideTransients (this->full);
1230 RemoveWindow (this);
1231 }
AddWindow()
|
Create a new entry in the linked list of currently rolled-up windows. The new entry will be inserted at head of the linked list, as the entries are not in any particular order.
|
Parameters
|
None.
|
Returns
|
WININFO*; newly created window list item.
|
1249 WININFO *AddWindow (void)
1250 {
1251 WININFO *head = rollups, /* start of window list */
1252 *new; /* new item to insert at head */
1254 if (( new=(WININFO*)malloc(sizeof(WININFO)) )) {
1255 memset (new, 0, sizeof(WININFO));
1256 new->prev = head;
1257 new->next = head->next;
1258 if ( new->next ) new->next->prev = new;
1259 head->next = new;
1260 } else {
1261 quiet ("Not enough memory\n");
1262 }
1264 return (new);
1265 }
RemoveWindow(this)
|
Remove an entry from the linked list of currently rolled-up windows. Any dynamic data in the entry (e.g. malloc'ed memory, windows we've created) must have been freed beforehand.
|
Parameters
|
| this | (inout) | WININFO*; entry to remove, will be zeroed and freed.
|
Returns
|
None.
|
1284 void RemoveWindow (WININFO *this)
1285 {
1286 this->prev->next = this->next;
1287 if ( this->next ) this->next->prev = this->prev;
1288 memset (this, 0, sizeof(WININFO));
1289 free (this);
1290 }
GetClient(w)
|
Convenience routine to get the client window id for a given window.
|
Parameters
|
| w | (in) | Window; window to get client window id for.
|
Returns
|
Window; client window of w.
|
1306 Window GetClient (Window w)
1307 {
1308 return ( XmuClientWindow(current.dpy,w) );
1309 }
AddTransient(w)
|
Add a window to the linked list of transients if the window is transient.
|
Parameters
|
| w | (in) | Window; (plain) window id for entry.
|
Returns
|
TRANSINFO*; newly created linked list item if the window is transient, NULL if not.
|
1326 TRANSINFO *AddTransient (Window w)
1327 {
1328 TRANSINFO *head = transients, /* head of list */
1329 *new, /* new item to insert at head */
1330 *this; /* sliding pointer */
1332 Window tfor, /* window we're transient for */
1333 cw = GetClient(w); /* client window */
Check that the window is transient in the first place. If the window is the same as its client window, ignore it, as it is about to be reparented.
1341 if ( cw == w ) return (NULL);
1342 tfor = TransientFor (cw);
1343 if ( !tfor ) return (NULL);
Lookup the window in the linked list of transients. If the window is already in the list, update the details, as they are likely to have changed (transient windows have an unfortunate tendency to be destroyed for no apparent reason, that's their very nature).
1352 for ( this = transients;
1353 this->next != NULL && this->client != cw;
1354 this = this->next ) ;
1355 if ( this != NULL && this->client == cw ) {
1356 this->win = w;
1357 this->leader = tfor;
1358 return (this);
1359 }
Create a new linked list entry, and insert it at the start of the list.
1366 if (( new=(TRANSINFO*)malloc(sizeof(TRANSINFO)) )) {
1367 memset (new, 0, sizeof(TRANSINFO));
1368 new->win = w;
1369 new->client = cw;
1370 new->leader = tfor;
1371 new->hidden = 0;
1372 new->prev = head;
1373 new->next = head->next;
1374 if ( new->next ) new->next->prev = new;
1375 head->next = new;
1376 } else {
1377 quiet ("Not enough memory\n");
1378 }
Return the new item, or NULL if malloc failed.
1384 return (new);
1385 }
RemoveTransient(w)
|
Remove an entry from the linked list of transients.
|
Parameters
|
| w | (in) | Window; (plain) window id of entry to remove.
|
Returns
|
None.
|
1400 void RemoveTransient (Window w)
1401 {
1402 TRANSINFO *this; /* sliding pointer */
1404 for ( this = transients;
1405 this->next != NULL && this->win != w;
1406 this = this->next ) ;
1408 if ( this != NULL && this->win == w && this->hidden == 0 ) {
1409 this->prev->next = this->next;
1410 if ( this->next ) this->next->prev = this->prev;
1411 memset (this, 0, sizeof(TRANSINFO));
1412 free (this);
1413 }
1414 }
HideTransients(cw)
|
When a window is rolled up, its transients will automatically be unmapped by the window manager.
When this happens, we will receive DestroyNotify events, so we must remember not to remove them, as we'll need to know which transients to remap when the rolled-up window is restored.
|
Parameters
|
| cw | | client window id.
|
Returns
|
None.
|
1435 static void HideTransientsR (Window cw)
1436 {
1437 TRANSINFO *this;
1438 for (this=transients; this; this=this->next) {
1439 if ( this->leader == cw ) {
1440 this->hidden++;
1441 HideTransientsR (this->client);
1442 }
1443 }
1444 }
1446 void HideTransients (Window cw)
1447 {
1448 TRANSINFO *this;
1449 for (this=transients; this; this=this->next) {
1450 if ( this->client == cw ) {
1451 this->hidden++;
1452 break;
1453 }
1454 }
1455 HideTransientsR (cw);
1456 }
UnHideTransients(cw)
|
When a rolled-up window is restored, we will need to remap its associated transients ourselves, as mwm will not do it for us. When we do, we will also need to re-associate the windows with the window they were transient for.
|
Parameters
|
| cw | | client window id.
|
Returns
|
None.
|
1475 static void UnHideTransientsR (Window cw)
1476 {
1477 TRANSINFO *this;
1478 for (this=transients; this; this=this->next) {
1479 if ( this->leader == cw ) {
1480 if ( this->hidden ) this->hidden--;
1481 if ( !this->hidden ) XMapWindow (current.dpy, this->client);
1482 UnHideTransientsR (this->client);
1483 }
1484 }
1485 }
1487 void UnHideTransients (Window cw)
1488 {
1489 TRANSINFO *this;
1490 for (this=transients; this; this=this->next) {
1491 if ( this->client == cw ) {
1492 if ( this->hidden ) this->hidden--;
1493 break;
1494 }
1495 }
1496 UnHideTransientsR (cw);
1497 }
TransientFor(cw)
|
Convenience routine to return the transient for hint of a window, if it has one.
|
Parameters
|
| cw | (in) | Window; client window to get hint for.
|
Returns
|
Window; window id of the leader the window is transient for, or None if the window is not transient.
|
1514 Window TransientFor (Window cw)
1515 {
1516 Window tfor; /* window we're transient for */
1518 if ( XGetTransientForHint(current.dpy,cw,&tfor) && tfor )
1519 return (tfor);
1520 else
1521 return (None);
1522 }
RizzleRollupFor(cw)
|
Return the specified window's _RIZZLE_ROLLUP_FOR property - if it has one.
If the window has the said property, its value is the window id of the hidden full-size window. If the window hasn't got the property, None is returned, since the window is not a rollup.
|
Parameters
|
| cw | (in) | Window; (client) window id.
|
Returns
|
None.
|
1543 Window RizzleRollupFor (Window cw)
1544 {
1545 int format; /* property format - ignored */
1547 unsigned char *prop; /* property value - window id */
1549 unsigned long nitems, /* number of properties - ignored */
1550 bytesafter; /* number of unread bytes - ignored */
1552 Window ret=None; /* window id to return */
1554 Atom type; /* property type - ignored */
1556 if ( !XGetWindowProperty ( current.dpy,
1557 cw,
1558 rollup_for,
1559 0L,
1560 20L,
1561 False,
1562 AnyPropertyType,
1563 &type,
1564 &format,
1565 &nitems,
1566 &bytesafter,
1567 &prop ) && prop ) {
1568 ret = *(Window*)prop;
1569 XFree (prop);
1570 }
1571 return (ret);
1572 }
IsRizzleRolledUp(cw)
|
Check if the specified window is invisible because it is rolled up (by looking at its _RIZZLE_ROLLED_UP property).
If if is rolled up, then the returned (property) value is the window id of its rollup. If it the window isn't rolled up, None is returned.
|
Parameters
|
| cw | (in) | Window; (client) window id.
|
Returns
|
None.
|
1593 Window IsRizzleRolledUp (Window cw)
1594 {
1595 int format; /* property format - ignored */
1597 unsigned char *prop; /* property value - window id */
1599 unsigned long nitems, /* number of properties - ignored */
1600 bytesafter; /* number of unread bytes - ignored */
1602 Window ret=None; /* window id to return */
1604 Atom type; /* property type - ignored */
1606 if ( !XGetWindowProperty ( current.dpy,
1607 cw,
1608 rolled_up,
1609 0L,
1610 20L,
1611 False,
1612 AnyPropertyType,
1613 &type,
1614 &format,
1615 &nitems,
1616 &bytesafter,
1617 &prop ) && prop ) {
1618 ret = *(Window*)prop;
1619 XFree (prop);
1620 }
1621 return (ret);
1622 }
RemoveRollupProps()
|
Remove the _RIZZLE_ROLLED_UP properties from all toplevel windows (the _RIZZLE_ROLLUP_FORs are cleaned up automatically as the rollups are destroyed whenever rizzle exits or dies).
|
Parameters
|
None.
|
Returns
|
None.
|
1640 void RemoveRollupProps (void)
1641 {
1642 unsigned int ncld, /* number of child windows */
1643 c; /* (child) loop counter */
1645 Window wdummy, /* dummy Window parameter */
1646 *cld=NULL, /* child window array */
1647 cw; /* client window id */
1649 if ( XQueryTree ( current.dpy,
1650 current.root,
1651 &wdummy,
1652 &wdummy,
1653 &cld,
1654 &ncld ) && ncld ) {
1655 for (c=0; c<ncld; c++) {
1656 cw = GetClient (cld[c]);
1657 XDeleteProperty (current.dpy, cw, rolled_up);
1658 }
1659 }
1660 }
GetWindowState(cw,state,icon)
|
Get the specified window's WM_STATE property, which returns the window's state (IconicState, NormalState or WithdrawnState) and its icon window.
|
Parameters
|
| cw | (in) | Window; (client) window id. |
| state | (out) | int; IconicState, WithdrawnState or NormalState. |
| icon | (out) | Window*; the window's icon.
|
Returns
|
None.
|
1680 void GetWindowState (Window cw, int *state, Window *icon)
1681 {
1682 int format, /* property format - ignored */
1683 *prop; /* property value - window id */
1685 unsigned long nitems, /* number of properties - ignored */
1686 bytesafter; /* number of unread bytes - ignored */
1688 Atom type; /* property type - ignored */
1690 if ( !XGetWindowProperty ( current.dpy,
1691 cw,
1692 wm_state,
1693 0L,
1694 8L,
1695 False,
1696 wm_state,
1697 &type,
1698 &format,
1699 &nitems,
1700 &bytesafter,
1701 (unsigned char**)&prop ) && prop ) {
1702 *state = prop[0];
1703 *icon = prop[1];
1704 XFree (prop);
1705 } else {
1706 *state = WithdrawnState;
1707 *icon = None;
1708 }
1709 }
CheckWindow(w)
|
Check a window id for validity (this is impossible !). Since X is asynchronous, errors happen after the event, and there is no way of predicting when the error will surface.
We cover up most of the errors here, by getting our custom error handler to suppress BadWindow diagnostics while the hush flag is set. Some errors will still creep through. Note I could have avoided this problem if I'd kept a list of all windows in the first place, but then I'd have ended up writing a window manager...
|
Parameters
|
| w | (in) | Window; window id to be checked.
|
Returns
|
int; nonzero if ok, zero on error.
|
1735 int CheckWindow (Window w)
1736 {
1737 int ret; /* return value */
1739 XWindowAttributes attr; /* (dummy) window attributes */
1741 errors = 0;
1743 hush = 1;
1744 XGetWindowAttributes (current.dpy, w, &attr);
1745 XSync (current.dpy, False);
1746 hush = 0;
1748 ret = ( errors == 0 );
1749 errors = 0;
1750 if ( !ret ) quiet ("Window 0x%lx is faulty\n", w);
1752 return (ret);
1753 }
InitX()
|
Initialise standard X things.
|
Parameters
|
None.
|
Returns
|
None.
|
1768 void InitX (void)
1769 {
1770 current.dpy = XOpenDisplay (NULL);
1771 if ( !current.dpy ) {
1772 fprintf (stderr, "Cannot open display\n");
1773 exit (EXIT_FAILURE);
1774 }
1775 current.screen = DefaultScreen (current.dpy);
1776 current.root = RootWindow (current.dpy, current.screen);
1777 wm_state = XInternAtom (current.dpy, "WM_STATE", False);
1778 wm_delete_window = XInternAtom (current.dpy, "WM_DELETE_WINDOW", False);
1779 motif_wm_hints = XInternAtom (current.dpy, "_MOTIF_WM_HINTS", False);
1780 rollup_for = XInternAtom (current.dpy, "_RIZZLE_ROLLUP_FOR", False);
1781 rolled_up = XInternAtom (current.dpy, "_RIZZLE_ROLLED_UP", False);
1782 }
1784 void ExitX (void)
1785 {
1786 XFlush (current.dpy);
1787 exit (EXIT_SUCCESS);
1788 }
ErrorHandler(dpy,event)
|
Catch X diagnostics happening asynchronously after some error.
Expect (!) some BadWindow diagnostics as a result of trying to find out the client window id for a window that is dead...
|
Parameters
|
| dpy | (in) | Display*; where it happened. |
| event | (in) | XErrorEvent*; what happened.
|
Returns
|
int; always zero.
|
1809 int ErrorHandler (Display *dpy, XErrorEvent *event)
1810 {
1811 errors++;
1813 if ( !hush ) {
1814 if ( event->error_code != BadWindow ||
1815 noise >= QUIET ) {
1816 fprintf (stderr, "\n");
1817 fprintf (stderr, "\n");
1818 fprintf (stderr, "***************************************************************************\n");
1819 XmuPrintDefaultErrorMessage (dpy, event, stderr);
1820 fprintf (stderr, "***************************************************************************\n");
1821 fprintf (stderr, "\n");
1822 fprintf (stderr, "\n");
1823 }
1825 if ( event->error_code == BadWindow ||
1826 event->error_code == BadDrawable ) return (0);
1827 else exit (EXIT_FAILURE);
1828 }
1830 return (0);
1831 }
CleanExit()
|
Before exiting the program, make sure everything is ok; in particular that any rolled-up windows are remapped.
|
Parameters
|
None.
|
Returns
|
None.
|
1847 void CleanExit (void)
1848 {
1849 char str[80]; /* string buffer */
1851 WININFO *this = rollups; /* sliding pointer */
Bring back the rolled-up windows. FIXME this would be done much nicer by RestoreWindow(), but that won't work for some reason; perhaps this is just not a terribly good time to be doing things with Xlib...
1860 while (( this=this->next )) {
1861 sprintf (str, "Restoring 0x%lx ", this->full);
1862 write (2, str, strlen(str));
1863 if ( this->name ) write (2, this->name, strlen(this->name));
1864 write (2, "\n", 1);
1865 XMapWindow (current.dpy, this->full);
1866 }
1867 RemoveRollupProps ();
1868 XSync (current.dpy, False);
1869 }
EventName(type)
|
Return the event name for a given event type.
|
Parameters
|
| type | (in) | int; event type.
|
Returns
|
char*; event name.
|
1884 char *EventName (int type)
1885 {
1886 switch ( type ) {
1887 case KeyPress: return ("KeyPress");
1888 case KeyRelease: return ("KeyRelease");
1889 case ButtonPress: return ("ButtonPress");
1890 case ButtonRelease: return ("ButtonRelease");
1891 case MotionNotify: return ("MotionNotify");
1892 case EnterNotify: return ("EnterNotify");
1893 case LeaveNotify: return ("LeaveNotify");
1894 case FocusIn: return ("FocusIn");
1895 case FocusOut: return ("FocusOut");
1896 case KeymapNotify: return ("KeymapNotify");
1897 case Expose: return ("Expose");
1898 case GraphicsExpose: return ("GraphicsExpose");
1899 case NoExpose: return ("NoExpose");
1900 case VisibilityNotify: return ("VisibilityNotify");
1901 case CreateNotify: return ("CreateNotify");
1902 case DestroyNotify: return ("DestroyNotify");
1903 case UnmapNotify: return ("UnmapNotify");
1904 case MapNotify: return ("MapNotify");
1905 case MapRequest: return ("MapRequest");
1906 case ReparentNotify: return ("ReparentNotify");
1907 case ConfigureNotify: return ("ConfigureNotify");
1908 case ConfigureRequest: return ("ConfigureRequest");
1909 case GravityNotify: return ("GravityNotify");
1910 case ResizeRequest: return ("ResizeRequest");
1911 case CirculateNotify: return ("CirculateNotify");
1912 case CirculateRequest: return ("CirculateRequest");
1913 case PropertyNotify: return ("PropertyNotify");
1914 case SelectionClear: return ("SelectionClear");
1915 case SelectionRequest: return ("SelectionRequest");
1916 case SelectionNotify: return ("SelectionNotify");
1917 case ColormapNotify: return ("ColormapNotify");
1918 case ClientMessage: return ("ClientMessage");
1919 case MappingNotify: return ("MappingNotify");
1920 default: return ("???");
1921 }
1922 return ("???");
1923 }
quiet|verbose|always(fmt,...)
|
printf-type convenience routines to output debugging info depending on the requested noise level.
quiet() speaks when necessary, or when saying something useful, except if the noise level is set to silent. verbose() is used to give lots of information, not all of it useful, and can be told to shut up by setting the noise level to quiet or silent. always() displays the information, regardless of the noise level, even when set to silent [use this only for essential diagnostics].
|
Parameters
|
| fmt | (in) | char*; printf format specifier. |
| ... | (in) | additional parameters depending on format.
|
Returns
|
None.
|
1949 void quiet (char *fmt, ...)
1950 {
1951 if ( noise >= QUIET ) {
1952 va_list list;
1953 va_start (list, fmt);
1954 vfprintf (stderr, fmt, list);
1955 va_end (list);
1956 }
1957 }
1959 void verbose (char *fmt, ...)
1960 {
1961 if ( noise >= VERBOSE ) {
1962 va_list list;
1963 va_start (list, fmt);
1964 vfprintf (stderr, fmt, list);
1965 va_end (list);
1966 }
1967 }
1969 void always (char *fmt, ...)
1970 {
1971 va_list list;
1972 va_start (list, fmt);
1973 vfprintf (stderr, fmt, list);
1974 va_end (list);
1975 }