b101f8573a45e47a827fd808cff09e106736b968
[xscreensaver] / hacks / xjack.c
1 /* xscreensaver, Copyright (c) 1997-2008 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Wendy, let me explain something to you.  Whenever you come in here and
12  * interrupt me, you're BREAKING my CONCENTRATION.  You're DISTRACTING me!
13  * And it will then take me time to get back to where I was. You understand?
14  * Now, we're going to make a new rule.  When you come in here and you hear
15  * me typing, or whether you DON'T hear me typing, or whatever the FUCK you
16  * hear me doing; when I'm in here, it means that I am working, THAT means
17  * don't come in!  Now, do you think you can handle that?
18  */
19
20 #include <ctype.h>
21 #include "screenhack.h"
22
23 static const char *source = "All work and no play makes Jack a dull boy.  ";
24 /* If you're here because you're thinking about making the above string be
25    customizable, then you don't get the joke.  You loser. */
26
27 struct state {
28   Display *dpy;
29   Window window;
30   XWindowAttributes xgwa;
31   XFontStruct *font;
32   GC gc;
33
34   const char *s;
35   int columns, rows;            /* characters */
36   int left, right;              /* characters */
37   int char_width, line_height;  /* pixels */
38   int x, y;                     /* characters */
39   int mode;
40   int hspace;                   /* pixels */
41   int vspace;                   /* pixels */
42   Bool break_para;
43   Bool caps;
44   int sentences;
45   int paras;
46   int scrolling;
47   int subscrolling;
48   int pining;
49
50   int delay;
51 };
52
53
54 static void
55 xjack_reshape (Display *dpy, Window window, void *closure, 
56                  unsigned int w, unsigned int h)
57 {
58   struct state *st = (struct state *) closure;
59   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
60   st->columns = (st->xgwa.width  - st->hspace - st->hspace) / st->char_width;
61   st->rows    = (st->xgwa.height - st->vspace - st->vspace) / st->line_height;
62   st->rows--;
63   st->columns--;
64
65   if (st->y > st->rows)    st->y = st->rows-1;
66   if (st->x > st->columns) st->x = st->columns-2;
67
68   if (st->right > st->columns) st->right = st->columns;
69   if (st->left > st->columns-20) st->left = st->columns-20;
70   if (st->left < 0) st->left = 0;
71 }
72
73
74 static void *
75 xjack_init (Display *dpy, Window window)
76 {
77   struct state *st = (struct state *) calloc (1, sizeof(*st));
78   XGCValues gcv;
79   char *fontname;
80
81   st->dpy = dpy;
82   st->window = window;
83   st->s = source;
84   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
85   fontname = get_string_resource (st->dpy, "font", "Font");
86   st->font = XLoadQueryFont (st->dpy, fontname);
87
88   if (!st->font)
89     st->font = XLoadQueryFont (st->dpy, "-*-*-medium-r-*-*-*-240-*-*-m-*-*-*");
90   if (!st->font)
91     st->font = XLoadQueryFont (st->dpy,
92                                "-*-courier-medium-r-*-*-*-180-*-*-m-*-*-*");
93   if (!st->font)
94     st->font = XLoadQueryFont (st->dpy, "-*-*-*-r-*-*-*-240-*-*-m-*-*-*");
95   if (!st->font)
96     {
97       fprintf(stderr, "no big fixed-width font like \"%s\"\n", fontname);
98       exit(1);
99     }
100
101   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
102
103   gcv.font = st->font->fid;
104   gcv.foreground = get_pixel_resource (st->dpy, st->xgwa.colormap,
105                                        "foreground", "Foreground");
106   gcv.background = get_pixel_resource (st->dpy, st->xgwa.colormap,
107                                        "background", "Background");
108   st->gc = XCreateGC (st->dpy, st->window,
109                       (GCFont | GCForeground | GCBackground), &gcv);
110
111   st->char_width = 
112     (st->font->per_char
113      ? st->font->per_char['n'-st->font->min_char_or_byte2].rbearing
114      : st->font->min_bounds.rbearing);
115   st->line_height = st->font->ascent + st->font->descent + 1;
116
117   xjack_reshape (dpy, window, st, st->xgwa.width, st->xgwa.height);
118
119   st->left = 0xFF & (random() % ((st->columns / 2)+1));
120   st->right = st->left + (0xFF & (random() % (st->columns - st->left - 10)
121                                   + 10));
122   st->x = 0;
123   st->y = 0;
124
125   if (st->xgwa.width > 200 && st->xgwa.height > 200)
126     st->hspace = st->vspace = 40;
127
128   return st;
129 }
130
131 static unsigned long
132 xjack_scroll (struct state *st)
133 {
134   st->break_para = 0;
135   if (st->subscrolling)
136     {
137       int inc = st->line_height / 7;
138       XCopyArea (st->dpy, st->window, st->window, st->gc,
139                  0, inc,
140                  st->xgwa.width, st->xgwa.height - inc,
141                  0, 0);
142
143       /* See? It's OK. He saw it on the television. */
144       XClearArea (st->dpy, st->window,
145                   0, st->xgwa.height - inc, st->xgwa.width, inc,
146                   False);
147
148       st->subscrolling -= inc;
149       if (st->subscrolling <= 0)
150         st->subscrolling = 0;
151       if (st->subscrolling == 0)
152         {
153           if (st->scrolling > 0)
154             st->scrolling--;
155           st->y--;
156         }
157       return st->delay / 1000;
158     }
159   else if (st->scrolling)
160     st->subscrolling = st->line_height;
161
162   if (st->y < 0)
163     st->y = 0;
164   else if (st->y >= st->rows-1)
165     st->y = st->rows-1;
166
167   return st->delay;
168 }
169
170 static unsigned long
171 xjack_pine (struct state *st)
172 {
173   /* See also http://catalog.com/hopkins/unix-haters/login.html */
174   const char *n1 = "NFS server overlook not responding, still trying...";
175   const char *n2 = "NFS server overlook ok.";
176   int prev = st->pining;
177
178   if (!st->pining)
179     st->pining = 1 + (random() % 3);
180
181   if (prev)
182     while (*n2)
183       {
184         XDrawString (st->dpy, st->window, st->gc,
185                      (st->x * st->char_width) + st->hspace,
186                      ((st->y * st->line_height) + st->vspace
187                       + st->font->ascent),
188                      (char *) n2, 1);
189         st->x++;
190         if (st->x >= st->columns) st->x = 0, st->y++;
191         n2++;
192       }
193   st->y++;
194   st->x = 0;
195   st->pining--;
196
197   if (st->pining)
198     while (*n1)
199       {
200         XDrawString (st->dpy, st->window, st->gc,
201                      (st->x * st->char_width) + st->hspace,
202                      ((st->y * st->line_height) + st->vspace
203                       + st->font->ascent),
204                      (char *) n1, 1);
205         st->x++;
206         if (st->x >= st->columns) st->x = 0, st->y++;
207         n1++;
208       }
209
210   return 5000000;
211 }
212
213
214 static unsigned long
215 xjack_draw (Display *dpy, Window window, void *closure)
216 {
217   struct state *st = (struct state *) closure;
218   int this_delay = st->delay;
219   int word_length = 0;
220   const char *s2;
221
222   if (st->scrolling)
223     return xjack_scroll (st);
224   if (st->pining)
225     return xjack_pine (st);
226
227   for (s2 = st->s; *s2 && *s2 != ' '; s2++)
228     word_length++;
229
230   if (st->break_para ||
231       (*st->s != ' ' &&
232        (st->x + word_length) >= st->right))
233     {
234       st->x = st->left;
235       st->y++;
236
237       if (st->break_para)
238         st->y++;
239
240       if (st->mode == 1 || st->mode == 2)
241         {
242           /* 1 = left margin goes southwest; 2 = southeast */
243           st->left += (st->mode == 1 ? 1 : -1);
244           if (st->left >= st->right - 10)
245             {
246               if ((st->right < (st->columns - 10)) && (random() & 1))
247                 st->right += (0xFF & (random() % (st->columns - st->right)));
248               else
249                 st->mode = 2;
250             }
251           else if (st->left <= 0)
252             {
253               st->left = 0;
254               st->mode = 1;
255             }
256         }
257       else if (st->mode == 3 || st->mode == 4)
258         {
259           /* 3 = right margin goes southeast; 4 = southwest */
260           st->right += (st->mode == 3 ? 1 : -1);
261           if (st->right >= st->columns)
262             {
263               st->right = st->columns;
264               st->mode = 4;
265             }
266           else if (st->right <= st->left + 10)
267             st->mode = 3;
268         }
269
270       if (st->y >= st->rows-1)  /* bottom of page */
271         {
272           /* scroll by 1-5 lines */
273           st->scrolling = (random() % 5 ? 0 : (0xFF & (random() % 5))) + 1;
274           if (st->break_para)
275             st->scrolling++;
276
277           /* but sometimes scroll by a whole page */
278           if (0 == (random() % 100))
279             st->scrolling += st->rows;
280
281           return xjack_scroll (st);
282         }
283     }
284
285   if (*st->s != ' ')
286     {
287       char c = *st->s;
288       int xshift = 0, yshift = 0;
289       if (0 == random() % 50)
290         {
291           xshift = random() % ((st->char_width / 3) + 1);      /* mis-strike */
292           yshift = random() % ((st->line_height / 6) + 1);
293           if (0 == (random() % 3))
294             yshift *= 2;
295           if (random() & 1)
296             xshift = -xshift;
297           if (random() & 1)
298             yshift = -yshift;
299         }
300
301       if (0 == (random() % 250))        /* introduce adjascent-key typo */
302         {
303           static const char * const typo[] = {
304             "asqw", "ASQW", "bgvhn", "cxdfv", "dserfcx", "ewsdrf",
305             "Jhuikmn", "kjiol,m", "lkop;.,", "mnjk,", "nbhjm", "oiklp09",
306             "pol;(-0", "redft54", "sawedxz", "uyhji87", "wqase32",
307             "yuhgt67", ".,l;/", 0 };
308           int i = 0;
309           while (typo[i] && typo[i][0] != c)
310             i++;
311           if (typo[i])
312             c = typo[i][0xFF & ((random() % strlen(typo[i]+1)) + 1)];
313         }
314
315       /* caps typo */
316       if (c >= 'a' && c <= 'z' && (st->caps || 0 == (random() % 350)))
317         {
318           c -= ('a'-'A');
319           if (c == 'O' && random() & 1)
320             c = '0';
321         }
322
323     OVERSTRIKE:
324       XDrawString (st->dpy, st->window, st->gc,
325                    (st->x * st->char_width) + st->hspace + xshift,
326                    ((st->y * st->line_height) + st->vspace + st->font->ascent
327                     + yshift),
328                    &c, 1);
329       if (xshift == 0 && yshift == 0 && (0 == (random() & 3000)))
330         {
331           if (random() & 1)
332             xshift--;
333           else
334             yshift--;
335           goto OVERSTRIKE;
336         }
337
338       if ((tolower(c) != tolower(*st->s))
339           ? (0 == (random() % 10))              /* backup to correct */
340           : (0 == (random() % 400)))    /* fail to advance */
341         {
342           st->x--;
343           st->s--;
344           if (st->delay)
345             st->delay += (0xFFFF & (st->delay + 
346                                     (random() % (st->delay * 10))));
347         }
348     }
349
350   st->x++;
351   st->s++;
352
353   if (0 == random() % 200)
354     {
355       if (random() & 1 && st->s != source)
356         st->s--;        /* duplicate character */
357       else if (*st->s)
358         st->s++;        /* skip character */
359     }
360
361   if (*st->s == 0)
362     {
363       st->sentences++;
364       st->caps = (0 == random() % 40);  /* capitalize sentence */
365
366       if (0 == (random() % 10) ||       /* randomly break paragraph */
367           (st->mode == 0 &&
368            ((0 == (random() % 10)) || st->sentences > 20)))
369         {
370           st->break_para = True;
371           st->sentences = 0;
372           st->paras++;
373
374           if (random() & 1)             /* mode=0 50% of the time */
375             st->mode = 0;
376           else
377             st->mode = (0xFF & (random() % 5));
378
379           if (0 == (random() % 2))      /* re-pick margins */
380             {
381               st->left = 0xFF & (random() % (st->columns / 3));
382               st->right = (st->columns -
383                            (0xFF & (random() % (st->columns / 3))));
384
385               if (0 == random() % 3)    /* sometimes be wide */
386                 st->right = st->left + ((st->right - st->left) / 2);
387             }
388
389           if (st->right - st->left <= 10)       /* introduce sanity */
390             {
391               st->left = 0;
392               st->right = st->columns;
393             }
394
395           if (st->right - st->left > 50)        /* if wide, shrink and move */
396             {
397               st->left += (0xFF & (random() % ((st->columns - 50) + 1)));
398               st->right = st->left + (0xFF & ((random() % 40) + 10));
399             }
400
401           /* oh, gag. */
402           if (st->mode == 0 &&
403               st->right - st->left < 25 &&
404               st->columns > 40)
405             {
406               st->right += 20;
407               if (st->right > st->columns)
408                 st->left -= (st->right - st->columns);
409             }
410         }
411       st->s = source;
412     }
413
414   if (st->delay)
415     {
416       if (0 == random() % 3)
417         this_delay += (0xFFFFFF & ((random() % (st->delay * 5)) + 1));
418
419       if (st->break_para)
420         this_delay += (0xFFFFFF & ((random() % (st->delay * 15)) + 1));
421     }
422
423   if (st->paras > 5 &&
424       (0 == (random() % 1000)) &&
425       st->y < st->rows-2)
426     return xjack_pine (st);
427
428   return this_delay;
429 }
430
431 static Bool
432 xjack_event (Display *dpy, Window window, void *closure, XEvent *event)
433 {
434   struct state *st = (struct state *) closure;
435   if (event->xany.type == ButtonPress)
436     {
437       st->scrolling++;
438       return True;
439     }
440
441   return False;
442 }
443
444 static void
445 xjack_free (Display *dpy, Window window, void *closure)
446 {
447   struct state *st = (struct state *) closure;
448   free (st);
449 }
450
451
452 static const char *xjack_defaults [] = {
453   ".background:         #FFF0B4",
454   ".foreground:         #000000",
455   "*fpsSolid:           true",
456 #ifdef HAVE_COCOA
457   ".font:               American Typewriter 24",
458 #else
459   ".font:               -*-courier-medium-r-*-*-*-240-*-*-m-*-*-*",
460 #endif
461   "*delay:              50000",
462   0
463 };
464
465 static XrmOptionDescRec xjack_options [] = {
466   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
467   { "-font",            ".font",        XrmoptionSepArg, 0 },
468   { 0, 0, 0, 0 }
469 };
470
471 XSCREENSAVER_MODULE ("XJack", xjack)