1 #include <X11/Xlib.h>
2 #include <X11/Xutil.h>
3 #include <X11/Xatom.h>
4 #include <X11/cursorfont.h>
5 #include <math.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <time.h>
9 #include <sys/time.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #define clamp(min, x, max) ((x) < (min) ? (min) : (x) > (max) ? (max) : (x))
14 #define sign(x) ((x) > 0 ? 1 : (x) < 0 ? -1 : 0)
15 #define abs(x) ((x) >= 0 ? (x) : -(x))
16 #define min(a, b) ((a) <= (b) ? (a) : (b))
17 #define max(a, b) ((a) >= (b) ? (a) : (b))
18 
19 #ifdef NEED_HYPOT
20 /* SVID 3, BSD 4.3, XOpen, C99 and GNU all have hypot(). Why don't you? */
21 #define hypot(a, b) sqrt((a)*(a) + (b)*(b))
22 #endif
23 
24 /* Look and feel parameters to play with: */
25 int length = 300;
26 int thumb = 100;
27 int thickness = 20;
28 int padding = 5;
29 int depth = 2;
30 double relief_frac = .1; /*relief area / thickness, 0 => relief doesn't scale*/
31 XColor trough = {0, 0xa3a3, 0xa3a3, 0xb3b3, 0,  0};
32 XColor bg = {0, 0xc6c6, 0xc6c6, 0xd6d6, 0,  0};
33 XColor fill = {0, 0xb6b6, 0x3030, 0x6060, 0,  0};
34 double shade = .5001; /* 0 => shadows black, hilights white; 1 => no shading */
35 /* for relief, 0 => raised, 1 => sunk, 2 => ridge, 3 => groove */
36 int prog_relief = 1; int sbar_relief = 1; int slider_relief = 0;
37 int arrow_relief = 0; int dimple_relief = 1;
38 int arrow_change = 1; /* these bits will flip when pressed */
39 double dimple = .3001; /* size / scrollbar thickness, 0 for none */
40 double font_frac = .6001;/* text fills 60% of the height of the progresss bar*/
41 /* Note that the progress bar prefers scalable fonts, so that it can keep
42    the same proportions when the window is resized. Depending on how modern
43    your X installation is, this may be nontrivial.
44    * The best case is if you have a font that includes both hand-edited
45    bitmaps for small sizes and outlines that can be scaled arbitrarily.
46    All recent X releases come with bitmaps provided by Adobe for Helvetica,
47    so if you also have a corresponding Type 1 outline, that's the best
48    choice:
49    (bitmaps for sizes 8, 10, 11, 12, 14, 17, 18, 20, 24, 25, and 34) */
50 /*char *fontname="-adobe-helvetica-medium-r-normal--%d-*-*-*-*-*-iso8859-1";*/
51 /* Appending the following subsetting hint will speed up resizes, at the
52    expense of excluding premade bitmaps:
53    "[48 49 50 51 52 53 54 55 56 57 37]"; */
54 /* (If you're using Debian Linux like me, you'll need to install the
55    gsfonts and gsfonts-x11 packages to get the Type 1 versions. The
56    outline isn't the genuine Adobe version; it's a free clone that
57    can also be accessed directly (without Adobe's bitmaps) as)*/
58 char *fontname = "-urw-nimbus sans l-regular-r-normal--%d-*-*-*-*-*-iso8859-1";
59 /* * Recent X releases also include some scalable fonts, though not any
60    sans-serif ones. In the following, adobe-utopia can be replaced by
61    adobe-courier, bitstream-courier, or bitstream-charter:
62 char *fontname = "-adobe-utopia-medium-r-normal--%d-*-*-*-*-*-iso8859-1";
63    * Also, recent X servers can scale bitmaps, though the results are usually
64    fairly ugly.
65    * If your X system predates XLFD (the 14-hyphen names), your font
66    selection is probably pretty miniscule; try to pick something around
67    12 pixels:
68 char *fontname = "7x13"; */
69 int cursor_id = XC_top_left_arrow;
70 int initial_delay = 150000; /* usecs */
71 int delay = 50000; /* usecs */
72 double accel = 0.5;
73 Bool smooth_progress = False; /* and un-smooth scrollbar */
74 int text_shading_style = 1; /* 0 => diagonalish, 1 => squarish */
75 
76 /*
77    +--------------------------------------------------+
78    | main_win	   ^v padding   	[bg]          |
79    | +----------------------------------------------+ |
80    | |#prog_win###########        ^                 |<|
81    | |##########[fill]####        :thickness        |>|
82    | |####################        :                 |:|
83    | |<-------- length -----------:---------------->|:|
84    | |####################        V     [trough]    |:|
85    | +----------------------------------------------+:|
86    |         	     ^v padding 	             :|
87    | +----------------------------------------------+:|
88    | | sbar_win +------------------------+          |:|
89    |<|          |+----+ slider_win +----+| [trough] |:|
90    |>|<-slider->|| <| |<-lt_win    | |> ||          |:|
91    |:|	  pos  	|+----+    rt_win->+----+|          |:|
92    |:|	       	+------------------------+          |:|
93    |:+----------:------------------------:----------+:|
94    |:         	 :   ^v padding           :          :|
95    +:-----------:------------------------:-----------:+
96     :  	 :	                  :	      :
97     :           :	                  :	      :
98   padding       :<------- thumb -------->:	   padding
99 */
100 
101 Window main_win, prog_win, sbar_win, slider_win, lt_win, rt_win;
102 GC trough_gc, bg_gc, fill_gc, hilite_gc, shadow_gc;
103 double frac = 0;
104 
105 Display *dpy;
106 Colormap cmap;
107 
108 XColor shadow, hilite;
109 Atom delete_atom;
110 
111 int fontsize;
112 XFontStruct *font;
113 char buf[256];
114 
115 int total_wd, base_wd, total_ht, base_ht;
116 
117 int inner_thick, slider_pos, pos_min, pos_max;
118 
119 int lt_state, rt_state;
120 
121 int text_wd, text_x, text_baseline;
122 
123 Pixmap prog_pixmap;
124 
125 int font_height;
126 
127 /* floor : ceil :: int : away */
away(double x)128 int away(double x) {
129     return sign(x) * (int)(abs(x) + 0.9999);
130 }
131 
draw_slope_poly(Window win,int relief,int dep,GC fill,XPoint * p,int n)132 void draw_slope_poly(Window win, int relief, int dep, GC fill,
133 		     XPoint *p, int n) {
134     GC tl, br;
135     GC *gc;
136     XPoint *ip;
137     int j;
138 
139     if (relief > 1) {
140 	draw_slope_poly(win, relief ^ 3,  dep,      fill,     p, n);
141 	/* tail recurse( */  relief &= 1; dep /= 2; fill = 0;
142     }
143     if (relief) {
144 	tl = shadow_gc; br = hilite_gc;
145     } else {
146 	tl = hilite_gc; br = shadow_gc;
147     }
148     gc = (GC*)malloc(n * sizeof(GC));
149     ip = (XPoint*)malloc(n * sizeof(XPoint));
150     for (j = 0; j < n; j++) {
151 	int j_t_1 = (j + 1) % n; int j_t_2 = (j + 2) % n;
152 	double ix = (double)p[j_t_1].x - (double)p[j].x;
153 	double iy = (double)p[j_t_1].y - (double)p[j].y;
154 	double ox, oy, in, on, mx, my, mn;
155 	ox = (double)p[j_t_2].x - (double)p[j_t_1].x;
156 	oy = (double)p[j_t_2].y - (double)p[j_t_1].y;
157 	gc[j] = ix > iy ? tl : ix < iy ? br : ix > 0 ? tl : br;
158 	if (ix * oy > iy * ox) {
159 	    ix = -ix; iy = -iy;
160 	} else {
161 	    ox = -ox; oy = -oy;
162 	}
163 	in = hypot(ix, iy); ix /= in; iy /= in;
164 	on = hypot(ox, oy); ox /= on; oy /= on;
165 	mx = (ix + ox) / 2; my = (iy + oy) / 2;
166 	mn = max(abs(mx), abs(my)); mx /= mn; my /= mn;
167 	ip[j_t_1].x = p[j_t_1].x + away((double)(dep - 1) * mx);
168 	ip[j_t_1].y = p[j_t_1].y + away((double)(dep - 1) * my);
169     }
170 
171     if (fill)
172 	XFillPolygon(dpy, win, fill, ip, n, Nonconvex, CoordModeOrigin);
173 
174     for (j = 0; j < n; j++) {
175 	XPoint quad[4];
176 	int j_t_1 = (j + 1) % n;
177 	quad[0] = p[j];      quad[1] = ip[j];
178 	quad[2] = ip[j_t_1]; quad[3] = p[j_t_1];
179 	XFillPolygon(dpy, win, gc[j], quad, 4, Convex, CoordModeOrigin);
180 	XDrawLine(dpy, win, gc[j], p[j].x, p[j].y, p[j_t_1].x, p[j_t_1].y);
181 	XDrawLine(dpy, win, gc[j], ip[j].x, ip[j].y, ip[j_t_1].x, ip[j_t_1].y);
182     }
183 
184     for (j = 0; j < n; j++) {
185 	int j_t_1 = (j + 1) % n;
186 	if (gc[j] != gc[j_t_1])
187 	    XDrawLine(dpy, win, bg_gc, p[j_t_1].x, p[j_t_1].y,
188 		      ip[j_t_1].x, ip[j_t_1].y);
189     }
190     free(gc);
191     free(ip);
192 }
193 
draw_slope(Window win,int x,int y,int wd,int ht,int relief)194 void draw_slope(Window win, int x, int y, int wd, int ht, int relief) {
195     XPoint rect[4];
196     rect[0].x = x;          rect[0].y = y;
197     rect[1].x = x + wd - 1; rect[1].y = y;
198     rect[2].x = x + wd - 1; rect[2].y = y + ht - 1;
199     rect[3].x = x;          rect[3].y = y + ht - 1;
200     draw_slope_poly(win, relief, depth, 0, rect, 4);
201 }
202 
paint_arrow(Window win,int x,int y,int s,int dir,int relief)203 void paint_arrow(Window win, int x, int y, int s, int dir, int relief) {
204     XPoint p[3];
205     int S[4];
206     S[0] = 0; S[1] = s / 2; S[2] = s; S[3] = s / 2;
207     p[0].x = x + S[(dir + 1) % 4]; p[0].y = y + S[dir];
208     if (!(dir & 1) == !(dir & 2)) {
209 	p[1].x = x + s; p[1].y = y + s;
210     } else {
211 	p[1].x = x; p[1].y = y;
212     }
213     if (dir & 2) {
214 	p[2].x = x + s; p[2].y = y;
215     } else {
216 	p[2].x = x; p[2].y = y + s;
217     }
218     if (dir & 1) {
219 	XPoint temp;
220 	temp = p[1];
221 	p[1] = p[2];
222 	p[2] = temp;
223     }
224     draw_slope_poly(win, relief, depth, bg_gc, p, 3);
225 }
226 
paint_slope_circle(Window win,int x,int y,int s,int dep,int relief)227 void paint_slope_circle(Window win, int x, int y, int s, int dep, int relief) {
228     GC tl, br;
229     int inner_x = x + dep; int inner_y = y + dep; int inner_s = s - 2 * dep;
230     if (relief & 1) {
231 	tl = shadow_gc; br = hilite_gc;
232     } else {
233 	tl = hilite_gc; br = shadow_gc;
234     }
235     XFillArc(dpy, win, bg_gc, x, y, s, s, 0, 360 * 64);
236     XFillArc(dpy, win, tl, x, y, s, s, 35 * 64, 160 * 64);
237     XDrawArc(dpy, win, tl, x, y, s, s, 35 * 64, 160 * 64);
238     XDrawArc(dpy, win, tl, inner_x, inner_y, inner_s, inner_s, 35*64, 160*64);
239     XFillArc(dpy, win, br, x, y, s, s, 215 * 64, 160 * 64);
240     XDrawArc(dpy, win, br, x, y, s, s, 215 * 64, 160 * 64);
241     XDrawArc(dpy, win, br, inner_x, inner_y, inner_s, inner_s, 215*64, 160*64);
242     if (relief & 2) {
243 	int mid_x = x + dep / 2; int mid_y = y + dep / 2; int mid_s = s - dep;
244 	XFillArc(dpy, win, br, mid_x, mid_y, mid_s, mid_s, 35*64, 160*64);
245 	XFillArc(dpy, win, tl, mid_x, mid_y, mid_s, mid_s, 215*64, 160*64);
246     }
247     XFillArc(dpy, win, bg_gc, inner_x, inner_y, inner_s, inner_s, 0, 360*64);
248 }
249 
paint_shaded_text(Drawable dable,int x,int y,XTextItem * text,int n)250 void paint_shaded_text(Drawable dable, int x, int y, XTextItem *text, int n) {
251     GC br_gc = shadow_gc;
252     GC tl_gc = hilite_gc;
253 
254     if (text_shading_style)
255 	XDrawText(dpy, dable, br_gc, x + 1, y + 1, text, n);
256     XDrawText(dpy, dable, br_gc, x, y + 1, text, n);
257     XDrawText(dpy, dable, br_gc, x + 1, y, text, n);
258 
259     if (text_shading_style)
260 	XDrawText(dpy, dable, tl_gc, x - 1, y - 1, text, n);
261     XDrawText(dpy, dable, tl_gc, x, y - 1, text, n);
262     XDrawText(dpy, dable, tl_gc, x - 1, y, text, n);
263 
264     XDrawText(dpy, dable, bg_gc, x, y, text, n);
265 }
266 
prog_update(double newfrac,int increm)267 void prog_update(double newfrac, int increm) {
268     double oldfrac = frac;
269     char str[5]; /* 1 0 0 % \0 */
270     int realend, end, n, wd;
271     XTextItem text[4];
272     frac = newfrac;
273     sprintf(str, "%d%%", (int)(frac * 100.0));
274     for (n = 0; str[n] != '\0'; n++) {
275 	text[n].font = None;
276 	text[n].nchars = 1;
277 	text[n].chars = &str[n];
278 	text[n].delta = 1;
279     }
280     if (str[0] == '1')
281 	text[1].delta = -font_height / 10; /* kerning */
282     realend = (int)(frac * (double)(length - 2 * depth)) + depth;
283     if (increm) {
284 	int newend = realend;
285 	int oldend = (int)(oldfrac * (double)(length - 2 * depth)) + depth;
286 	int x, *left, *right, count = 0;
287 	if (newend > oldend) {
288 	    right = &newend; left = &oldend;
289 	} else {
290 	    right = &oldend; left = &newend;
291 	}
292 	if (*left >= text_x && *left < text_x + text_wd) {
293 	    *left = text_x + text_wd - 1;
294 	    count++;
295 	}
296 	if (*right >= text_x && *right < text_x + text_wd) {
297 	    *right = text_x;
298 	    count++;
299 	}
300 	if (count == 2) {
301 	    /* do nothing */
302 	} else if (newend > oldend) {
303 	    if (smooth_progress) {
304 		for (x = oldend; x < newend; x++) {
305 		    XDrawLine(dpy, prog_win, fill_gc, x, depth, x,
306 			      thickness - depth - 1);
307 		}
308 	    } else {
309 		XFillRectangle(dpy, prog_win, fill_gc, oldend, depth,
310 			       newend - oldend, inner_thick);
311 	    }
312 	} else if (newend < oldend) {
313 	    if (smooth_progress) {
314 		for (x = oldend - 1; x >= newend; x--) {
315 		    XDrawLine(dpy, prog_win, trough_gc, x, depth,
316 			      x, thickness - depth - 1);
317 		}
318 	    } else {
319 		XFillRectangle(dpy, prog_win, trough_gc, newend, depth,
320 			       oldend - newend, inner_thick);
321 	    }
322 	}
323     } else {
324 	XFillRectangle(dpy, prog_win, fill_gc, depth, depth,
325 		       realend - depth, inner_thick);
326     }
327     end = clamp(0, realend - text_x, text_wd);
328     if (end > 0)
329 	XFillRectangle(dpy, prog_pixmap, fill_gc, 0, 0, end, inner_thick);
330     if (end < text_wd)
331 	XFillRectangle(dpy, prog_pixmap, trough_gc, end, 0,
332 	text_wd - end, inner_thick);
333     wd = XTextWidth(font, str, n);
334     paint_shaded_text(prog_pixmap, 1 + (text_wd - wd) / 2, text_baseline,
335 		      text, n);
336     XCopyArea(dpy, prog_pixmap, prog_win, bg_gc, 0, 0, text_wd, inner_thick,
337 	      text_x, depth);
338 }
339 
slider_update(double delta,Bool warp)340 void slider_update(double delta, Bool warp) {
341     XWindowChanges changes;
342     int old_pos = slider_pos;
343     slider_pos = clamp(pos_min, slider_pos + delta, pos_max);
344     if (warp)
345 	XWarpPointer(dpy, None, None, 0, 0, 0, 0, slider_pos - old_pos, 0);
346     changes.x = slider_pos;
347     XConfigureWindow(dpy, slider_win, CWX, &changes);
348     prog_update((double)(slider_pos - pos_min) / (pos_max - pos_min), 1);
349 }
350 
351 void mainloop(void);
352 
main(int argc,char ** argv)353 int main(int argc, char **argv) {
354     XSetWindowAttributes attr;
355     XGCValues gc_values;
356 
357     dpy = XOpenDisplay(0);
358     cmap = DefaultColormap(dpy, DefaultScreen(dpy));
359 
360     shadow.red = (unsigned short)((double)(bg.red) * shade);
361     shadow.green = (unsigned short)((double)(bg.green) * shade);
362     shadow.blue = (unsigned short)((double)(bg.blue) * shade);
363 
364     hilite.red = 65535 - (unsigned short)((65535.0-(double)(bg.red)) * shade);
365     hilite.green = 65535-(unsigned short)((65535.0-(double)(bg.green))*shade);
366     hilite.blue = 65535 - (unsigned short)((65535.0-(double)(bg.blue))*shade);
367 
368     XAllocColor(dpy, cmap, &bg);
369     XAllocColor(dpy, cmap, &trough);
370     XAllocColor(dpy, cmap, &shadow);
371     XAllocColor(dpy, cmap, &hilite);
372     XAllocColor(dpy, cmap, &fill);
373 
374     fontsize = (int)(font_frac * (double)thickness);
375     sprintf(buf, fontname, fontsize);
376     font = XLoadQueryFont(dpy, buf);
377 
378     total_wd = 2 * padding + length;
379     base_wd =  2 * padding + 2 * depth + 4;
380     total_ht = 3 * padding + 2 * thickness;
381     base_ht =  3 * padding + 4 * depth + 3;
382 
383     attr.cursor = XCreateFontCursor(dpy, cursor_id);
384     attr.background_pixel = bg.pixel;
385     attr.event_mask = StructureNotifyMask;
386     main_win = XCreateWindow(dpy, RootWindow(dpy, DefaultScreen(dpy)),
387 			     0, 0, total_wd, total_ht, 0,
388 			     CopyFromParent,CopyFromParent,CopyFromParent,
389 			     CWCursor|CWBackPixel|CWEventMask, &attr);
390 
391     {
392 	XSizeHints normal_hints;
393 	XWMHints wm_hints;
394 	XClassHint class_hints;
395 	XTextProperty window_name, icon_name;
396 	char *window_str = "Raw X Widgets (C Xlib)";
397 	char *icon_str = "widgets";
398 	normal_hints.min_width = normal_hints.base_width = base_wd;
399 	normal_hints.min_height = normal_hints.base_height = base_ht;
400 	normal_hints.min_aspect.x = 3; normal_hints.min_aspect.y = 2;
401 	normal_hints.max_aspect.x = 1000; normal_hints.max_aspect.y = 1;
402 	normal_hints.flags = PSize | PMinSize | PAspect | PBaseSize;
403 	wm_hints.input = True;
404 	wm_hints.initial_state = NormalState;
405 	wm_hints.flags = InputHint | StateHint;
406 	class_hints.res_name = argv[0];
407 	class_hints.res_class = "widgets";
408 	XStringListToTextProperty(&window_str, 1, &window_name);
409 	XStringListToTextProperty(&icon_str, 1, &icon_name);
410 	XSetWMProperties(dpy, main_win, &window_name, &icon_name, argv, argc,
411 			 &normal_hints, &wm_hints, &class_hints);
412     }
413 
414     delete_atom = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
415     XSetWMProtocols(dpy, main_win, &delete_atom, 1);
416 
417     attr.background_pixel = trough.pixel;
418     attr.event_mask = ExposureMask;
419     prog_win = XCreateWindow(dpy, main_win, padding, padding,
420 			     length, thickness, 0,
421 			     CopyFromParent,CopyFromParent,CopyFromParent,
422 			     CWBackPixel|CWEventMask, &attr);
423 
424     attr.background_pixel = trough.pixel;
425     attr.event_mask = ExposureMask;
426     sbar_win = XCreateWindow(dpy, main_win, padding, 2* padding + thickness,
427 			     length, thickness, 0,
428 			     CopyFromParent,CopyFromParent,CopyFromParent,
429 			     CWBackPixel|CWEventMask, &attr);
430 
431     gc_values.foreground = bg.pixel;
432     bg_gc = XCreateGC(dpy, main_win, GCForeground, &gc_values);
433 
434     gc_values.foreground = shadow.pixel;
435     shadow_gc = XCreateGC(dpy, main_win, GCForeground, &gc_values);
436 
437     gc_values.foreground = hilite.pixel;
438     hilite_gc = XCreateGC(dpy, main_win, GCForeground, &gc_values);
439 
440     inner_thick = thickness - 2 * depth;
441     slider_pos = depth;
442     pos_min = depth;
443     pos_max = length - thumb - depth - 2 * inner_thick;
444 
445     attr.background_pixel = bg.pixel;
446     attr.event_mask = ExposureMask | ButtonPressMask | ButtonMotionMask
447 	| PointerMotionHintMask;
448     slider_win = XCreateWindow(dpy, sbar_win, slider_pos, depth,
449 			       thumb + 2 * inner_thick, inner_thick, 0,
450 			       CopyFromParent,CopyFromParent,CopyFromParent,
451 			       CWBackPixel|CWEventMask, &attr);
452 
453     attr.background_pixel = trough.pixel;
454     attr.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask;
455     lt_win = XCreateWindow(dpy, slider_win, 0, 0,
456 			   inner_thick, inner_thick, 0,
457 			   CopyFromParent,CopyFromParent,CopyFromParent,
458 			   CWBackPixel|CWEventMask, &attr);
459     rt_win = XCreateWindow(dpy, slider_win, thumb + inner_thick, 0,
460 			   inner_thick, inner_thick, 0,
461 			   CopyFromParent,CopyFromParent,CopyFromParent,
462 			   CWBackPixel|CWEventMask, &attr);
463 
464     lt_state = rt_state = 0;
465 
466     XMapWindow(dpy, lt_win);
467     XMapWindow(dpy, rt_win);
468     XMapWindow(dpy, slider_win);
469 
470     text_wd = XTextWidth(font, "100%", 4) + 4 + 2;
471     text_x = (length - text_wd) / 2;
472     text_baseline = (thickness + font->ascent - font->descent) / 2 - depth;
473 
474     prog_pixmap = XCreatePixmap(dpy, prog_win, text_wd, inner_thick,
475 				DisplayPlanes(dpy, DefaultScreen(dpy)));
476 
477     gc_values.foreground = trough.pixel;
478     gc_values.font = font->fid;
479     trough_gc = XCreateGC(dpy, main_win, GCForeground|GCFont, &gc_values);
480 
481     gc_values.foreground = fill.pixel;
482     fill_gc = XCreateGC(dpy, main_win, GCForeground|GCFont, &gc_values);
483 
484     XSetFont(dpy, shadow_gc, font->fid);
485     XSetFont(dpy, hilite_gc, font->fid);
486     XSetFont(dpy, bg_gc, font->fid);
487 
488     font_height = font->ascent + font->descent;
489 
490     XMapWindow(dpy, prog_win);
491     XMapWindow(dpy, sbar_win);
492     XMapWindow(dpy, main_win);
493 
494     mainloop();
495     return 0;
496 }
497 
mainloop(void)498 void mainloop(void) {
499     fd_set fds;
500     struct timeval timeout, short_time;
501 
502     double slider_speed;
503     int pointer_pos, last_pos = -1;
504     int prog_dirty = 0, sbar_dirty = 0, slider_dirty = 0,
505       lt_dirty = 0, rt_dirty = 0;
506     int resize_pending = 0;
507     int x_fd = ConnectionNumber(dpy);
508     XEvent e;
509 
510     FD_ZERO(&fds);
511     FD_SET(x_fd, &fds);
512     timeout.tv_sec = 0; timeout.tv_usec = 0;
513 
514     /* Even though this program can probably handle events as fast as
515        the X server can generate them, it can't hurt to use some sort
516        of `flow control' to throw out excess events in case we're ever
517        behind. */
518 
519     /* For pointer motion events, this is accomplished by selecting
520        PointerMotionHint on the slider (see above), so that the server
521        never sends a sequence of motion events -- instead, it sends
522        one, which we throw away but use as our cue to query the
523        pointer position. The query_pointer is then a sign to the
524        server that we'd be willing to accept one more event, and so
525        on. Notice that this requires at least one round trip between
526        the server and the client for each motion, which puts a limit
527        on performance. */
528 
529     /* Expose and ConfigureNotify (resize) events have the same
530        problem, though it's only noticeable if your window manager
531        supports opaque window movement or opaque resize, respectively
532        (the latter is fairly rare in X, perhaps because average X
533        clients handle it fairly poorly; I for one am quite envious of
534        how smoothly windows resize in Windows NT). We can't do
535        anything to tell the server to only send us one of these
536        events, but the next best thing is to just ignore them until
537        there aren't any other events pending. (In some toolkits this
538        would be called `idle-loop' processing). It's always safe to
539        ignore intermediate resizes, but with expose events we can only
540        do this because we always redraw the whole window, instead of
541        just the newly-visible part. A more sophisticated approach
542        would keep track of the exposed region, either with a bounding
543        box or some more precise data structure, and then clip the
544        drawing to that (either client-side or using a clip mask in the
545        GC). */
546 
547     for (;;) {
548 	if (timeout.tv_usec) {
549 	    XFlush(dpy);
550 	    while (!select(x_fd + 1, &fds, 0, 0, &timeout)) {
551 		FD_SET(x_fd, &fds);
552 		slider_update(slider_speed, 1);
553 		slider_speed += sign(slider_speed) * accel;
554 		if (slider_pos == pos_min || slider_pos == pos_max) {
555 		    timeout.tv_sec = timeout.tv_usec = 0;
556 		    break;
557 		} else {
558 		    timeout.tv_sec = 0; timeout.tv_usec = delay;
559 		}
560 		XFlush(dpy);
561 	    }
562 	}
563 	FD_SET(x_fd, &fds);
564 	XFlush(dpy);
565 	short_time.tv_sec = 0; short_time.tv_usec = 1000;
566 	if (!select(x_fd + 1, &fds, 0, 0, &short_time)) {
567 	    if (resize_pending) {
568 		XWindowChanges changes;
569 
570 		resize_pending = 0;
571 		total_ht = max(total_ht, base_ht);
572 		length = total_wd - 2 * padding;
573 		thickness = (total_ht - 3 * padding + 1) / 2;
574 		if (relief_frac)
575 		    depth = (int)(relief_frac * (double)thickness);
576 		inner_thick = thickness - 2 * depth;
577 		thumb = length / 3;
578 		XResizeWindow(dpy, prog_win, length, thickness);
579 		fontsize = (int)(font_frac * (double)thickness);
580 		XFreeFont(dpy, font);
581 		sprintf(buf, fontname, fontsize);
582 		font = XLoadQueryFont(dpy, buf);
583 		XSetFont(dpy, bg_gc, font->fid);
584 		XSetFont(dpy, hilite_gc, font->fid);
585 		XSetFont(dpy, shadow_gc, font->fid);
586 
587 		text_wd = XTextWidth(font, "100%", 4) + 4 + 2;
588 		text_x = (length - text_wd) / 2;
589 		text_baseline = (thickness + font->ascent
590 				 - font->descent) / 2 - depth;
591 		font_height = font->ascent + font->descent;
592 
593 		XFreePixmap(dpy, prog_pixmap);
594 		prog_pixmap =
595 		    XCreatePixmap(dpy, prog_win, text_wd, inner_thick,
596 				  DisplayPlanes(dpy, DefaultScreen(dpy)));
597 		changes.y = 2 * padding + thickness;
598 		changes.width = length; changes.height = thickness;
599 		XConfigureWindow(dpy, sbar_win, CWY|CWWidth|CWHeight,
600 				 &changes);
601 		pos_min = depth;
602 		pos_max = length - thumb - depth - 2 * inner_thick;
603 		slider_pos = pos_min
604 		    + (int)(frac * (double)(pos_max - pos_min));
605 		XMoveResizeWindow(dpy, slider_win, slider_pos, depth,
606 				  thumb + 2 * inner_thick, inner_thick);
607 		XResizeWindow(dpy, lt_win, inner_thick, inner_thick);
608 		changes.x = thumb + inner_thick;
609 		changes.width = changes.height = inner_thick;
610 		XConfigureWindow(dpy, rt_win, CWX|CWWidth|CWHeight,
611 				 &changes);
612 	    }
613 	    if (prog_dirty) {
614 		draw_slope(prog_win, 0, 0, length, thickness, prog_relief);
615 		prog_update(frac, 0);
616 		prog_dirty = 0;
617 	    }
618 	    if (sbar_dirty) {
619 		draw_slope(sbar_win, 0, 0, length, thickness, sbar_relief);
620 		sbar_dirty = 0;
621 	    }
622 	    if (slider_dirty) {
623 		draw_slope(slider_win, inner_thick, 0, thumb,
624 			   inner_thick, slider_relief);
625 		if (dimple)
626 		    paint_slope_circle(slider_win,
627 				       thumb / 2 +(int)((2.0 - dimple)/2.0
628 							* (double)inner_thick),
629 				       (int)((1.0 - dimple)
630 					     * (double)inner_thick / 2.0),
631 				       (int)(dimple * (double)inner_thick),
632 				       depth, dimple_relief);
633 		slider_dirty = 0;
634 	    }
635 	    if (lt_dirty) {
636 		paint_arrow(lt_win, 0, 0, inner_thick - 1, 3,
637 			    arrow_relief ^ lt_state);
638 		lt_dirty = 0;
639 	    }
640 	    if (rt_dirty) {
641 		paint_arrow(rt_win, 0, 0, inner_thick - 1, 1,
642 			    arrow_relief ^ rt_state);
643 		rt_dirty = 0;
644 	    }
645 	}
646 	XNextEvent(dpy, &e);
647 	switch (e.type) {
648 	case ClientMessage:
649 	    if (e.xclient.data.l[0] == delete_atom)
650 		exit(0);
651 	    break;
652 	case ConfigureNotify: {
653 	    int wd = e.xconfigure.width;
654 	    int ht = e.xconfigure.height;
655 	    if (wd != total_wd || ht != total_ht) {
656 		resize_pending++;
657 		total_wd = wd; total_ht = ht;
658 	    }
659 	    break; }
660 	case Expose: {
661 	    Window win = e.xexpose.window;
662 	    if (win == sbar_win) {
663 		if (e.xexpose.x < depth || e.xexpose.y < depth
664 		    || e.xexpose.x + e.xexpose.width > length - depth
665 		    || e.xexpose.y + e.xexpose.height > thickness - depth) {
666 		    /* In the scrollbar, we throw out exposures that
667 		       don't include the border (including all the
668 		       ones caused by moving the slider), since the
669 		       server fills the trough in with the window's
670 		       background color automatically. */
671 		    sbar_dirty++;
672 		}
673 	    } else if (win == prog_win)
674 		prog_dirty++;
675 	    else if (win == slider_win)
676 		slider_dirty++;
677 	    else if (win == lt_win)
678 		lt_dirty++;
679 	    else if (win == rt_win)
680 		rt_dirty++;
681 	    break; }
682 	case ButtonPress: {
683 	    Window win = e.xbutton.window;
684 	    if (win == slider_win) {
685 		pointer_pos = slider_pos;
686 		last_pos = e.xbutton.x_root;
687 	    } else if (win == lt_win) {
688 		if (2*abs(e.xbutton.y - inner_thick / 2) <= e.xbutton.x) {
689 		    lt_state = arrow_change;
690 		    slider_update(-1, 1);
691 		    paint_arrow(lt_win, 0, 0, inner_thick - 1, 3,
692 				arrow_relief ^ lt_state);
693 		    slider_speed = -1;
694 		    timeout.tv_sec = 0; timeout.tv_usec = initial_delay;
695 		}
696 	    } else if (win == rt_win) {
697 		if (2*abs(e.xbutton.y - inner_thick / 2)
698 		    <= inner_thick - e.xbutton.x)
699 		{
700 		    rt_state = arrow_change;
701 		    slider_update(1, 1);
702 		    paint_arrow(rt_win, 0, 0, inner_thick - 1, 1,
703 				arrow_relief ^ rt_state);
704 		    slider_speed = 1;
705 		    timeout.tv_sec = 0; timeout.tv_usec = initial_delay;
706 		}
707 	     }
708 	    break; }
709 	case MotionNotify:
710 	    if (e.xmotion.window == slider_win && last_pos != -1) {
711 		int na, root_x;
712 		Window NA;
713 		XQueryPointer(dpy, slider_win, &NA, &NA, &root_x, &na,
714 			      &na, &na, (unsigned int *)&na);
715 		pointer_pos += root_x - last_pos;
716 		slider_update(pointer_pos - slider_pos, 0);
717 		last_pos = root_x;
718 	    }
719 	    break;
720 	case ButtonRelease: {
721 	    Window win = e.xbutton.window;
722 	    if (win == slider_win && last_pos != -1) {
723 		slider_update(e.xbutton.x_root - last_pos, 0);
724 		last_pos = -1;
725 	    } else if (win == lt_win) {
726 		lt_state = 0;
727 		paint_arrow(lt_win, 0, 0, inner_thick - 1, 3,
728 			    arrow_relief ^ lt_state);
729 		timeout.tv_sec = 0; timeout.tv_usec = 0;
730 	    } else if (win == rt_win) {
731 		rt_state = 0;
732 		paint_arrow(rt_win, 0, 0, inner_thick - 1, 1,
733 			    arrow_relief ^ rt_state);
734 		timeout.tv_sec = 0; timeout.tv_usec = 0;
735 	    }
736 	    break; }
737 	}
738     }
739 }
740